grimboite/old/2015-03-15-termsets.md

5.7 KiB
Raw Permalink Blame History

Title: Petit hack des taxonomies et auto-complétion jQuery

Les banques de termes dans SharePoint sont des taxonomies ayant sept niveaux max de profondeur. Il est possible de les interroger au travers des services REST, mais au vu des performances un chouia catastrophiques, j'ai préféré généré les valeurs directement dans la page afin d'optimiser l'expérience utilisateur (et aussi parce qu'on a encore des vieilles machines sous XP/IE8, et que la complétion jQuery/API REST SP n'est pas toujours au top). A chaque fois que Word accède aux banques de termes, les valeurs sont stockées dans un fichier plat, en XML.

Dans l'ordre:

  1. J'ai défini un petit programme en Python qui va lire ce fichier et qui structure les informations pertinentes à l'auto-complétion,
  2. Une page statique SharePoint reçoit ces informations, charge la librairie jQuery et affiche les informations
  3. Dès qu'une valeur est sélectionnée par l'utilisateur, je redirige gentiment l'utilisateur vers une page de recherche, en ayant au préalable sélectionné l'élément de taxonomie sélectionné.

Dump de la banque de termes avec Word

Le fichier XML sur lequel Word se base se trouve à l'emplacement C:\Users\<user>\AppData\Local\Microsoft\Office\TermSets\. Il est tout à fait possible d'avoir plusieurs fichiers à cet emplacement, chacun d'entre eux correspondant au guid des ensembles de termes auxquels Word a accédé dernièrement.

Attributs

  • a9 Term ID or TermSet ID
  • a11 TermSet Description
  • a12 Term Set Name the Term belongs to
  • a16 Submission Policy (Is the termset open or closed)
  • a17 Term is Enabled (can be used for tagging)
  • a21 (boolean) False if the term is not deprecated
  • a24 Term Set ID the Term belongs to
  • a25 Parent Term ID
  • a31 (boolean) - définit s'il s'agit du terme par défaut
  • a32 Label Text
  • a40 Parent Term Name
  • a45 A semicolon separated list of the IDs of the Terms to the current terms location in the taxonomy. (I.e. its path!)
  • a61 (integer) ?
  • a67 ?
  • a68 TermSet contact email

Le bidule en Python

#coding=utf-8
from __future__ import print_function

import codecs
import xml.etree.ElementTree as ET

tree = ET.parse(r"C:\Users\<user>\AppData\Local\Microsoft\Office\TermSets\<termset>.xml")
root = tree.getroot()

d = dict()

l = []
for x in root.findall(".//T"):
    attr = None
    for a in x.findall('.//TL[@a31="true"]'):
        attr = a.attrib['a32']
        
    for y in x.findall(".//TL"):
        if not d.has_key(y.attrib['a32']):
            d[y.attrib['a32']] = None
            l.append((y.attrib['a32'], attr, x.attrib['a9']))

f = codecs.open('workfile', 'w', "utf-8")
print('[' + ', '.join(['{key: "%s", value: "%s", id: "%s"}' % (s[1], s[0], s[2]) for s in l]) + ']', file=f)

La deuxième boucle ne sert finalement qu'à récupérer le seul élément dont l'attribut a31 est à True. Un simple get ou single aurait fait l'affaire, c'est du code pas propre, j'assume. Cela nous donne un résultat sérialisé à la main en JSON (quand je dis que ce n'est pas propre...), qu'on peut mettre dans un fichier. Ce fichier sera lu par le code jQuery pour la complétion.

<script type="text/javascript" src="/_layouts/SiteAssets/jquery-1.11.1.js"></script>
<script type="text/javascript" src="/_layouts/SiteAssets/jquery-ui-1.10.4.js"></script>
<script type="text/javascript">
	$(function() {
		var availableKeywords = <le brol généré ci-dessus>

		var accentMap = {
			"à": "a",
			"ö": "o",
			"é": "e",
			"è": "e",
			"ê": "e",
			"ù": "u"
		};
		var normalize = function( term ) {
			var ret = "";
			for ( var i = 0; i < term.length; i++ ) {
				ret += accentMap[ term.charAt(i) ] || term.charAt(i);
			}
			return ret;
		};

		var matchfunc = function(request, response) {
			var matcher = new RegExp( $.ui.autocomplete.escapeRegex( request.term ), "i" );
			response( $.grep( availableKeywords, function( value ) {
				value = value.value || value;
				return matcher.test( value ) || matcher.test( normalize( value ) );
			}) 
		)};
		
		$("#searchbox").autocomplete({
			source: matchfunc,
			select: function(event, ui) {
				doSearchCareProc(ui.item);
			}
		});
		
		function doSearchCareProc(item) {
			window.location.href = "http://my-search-page.aspx?k=\"" + item.key + "\"&#Default={\"k\":\"\\\"" + item.key + "\\\"\",\"r\":[{\"n\":\"owstaxIdMots-clés\",\"t\":[\"string(\\\"%230" + item.id + "\\\")\"],\"o\":\"and\",\"k\":false,\"m\":null}]}";
		}
	});		
</script>

<div>
	<input id="searchbox" class="inputbox" type="text" placeholder="Recherche...">
</div>

La partie de redirection window.location.href est initialisée à la page de recherche, sur laquelle on donne le mot à surligner grâce au paramètre k=. On définit aussi un paramètre #Default={}. Ce paramètre accepte les clés suivantes:

  • Une clé k pour le mot à surligner. L'enchevêtrement de slashs permet d'introduire les guillemets, et donc la possibilité de ne garder que l'ensemble exact des mots.
  • Une clé r, qui a un tableau comme valeur. Ce tableau contient les clés suivantes:
    • Une clé n pour le raffinement à traiter
    • Une clé t, représentée par un tableau contenant les identifiants des termes sélectionnés.

La partie chiante a été de trouver la requête à construire pour que SharePoint interprète correctement (et sélectionne directement) la valeur recherchée.

Sources