<?xml version="1.0"?>
<!DOCTYPE webpage PUBLIC "-//Norman Walsh//DTD Website V2.1b1//EN" "../../../dtd/website.dtd" [
	<!ENTITY multilinguisme SYSTEM "multilinguisme.xml" NDATA XML>
]>
<webpage id="analyzers">
	<head>
		<title>Les analyseurs</title>
		<titleabbrev>Analyseurs</titleabbrev>
		<author>
			<firstname>Pierrick</firstname>
			<surname>Brihaye</surname>
		</author>
		<revhistory>
			<revision>
				<revnumber>1.0</revnumber>
				<date>2003-11-22</date>
			</revision>
		</revhistory>
	</head>
	<para>En informatique documentaire, on est généralement amené à gérer l'indexation et la recherche sur du plein-texte, autrement nommé "texte intégral", "texte libre", "texte rédigé"... Comme il est hors de question d'indexer et donc de proposer des requêtes sur des phrases, des paragraphes voire des textes entiers, il est nécessaire de faire subir un traitement préalable au texte : l'<emphasis>analyse</emphasis>.</para>
	<para>Dans SDX, les champs (ou index) qui bénéficient d'une analyse sont définis ainsi dans les fichiers de configuration des applications (<filename>application.xconf</filename>)&nbsp;:</para>
	<para>
		<sgmltag>&lt;sdx:field name="champ" type="word"&gt;</sgmltag>
	</para>
	<para>Les linguistes bondiront certainement face à l'intitulé <sgmltag class="attribute">word</sgmltag>, on ne peut plus flou. On aurait pu utiliser un intitulé moins sémantisé ou sémantisé sur le plan informatique, <sgmltag class="attribute">tokenized</sgmltag> par exemple. Peu importe.</para>
	<para>SDX ne proposant pour l'instant qu'une indexation <ulink url="http://jakarta.apache.org/lucene/docs/index.html">Lucene</ulink>, l'analyse repose entièrement sur les API d'analyse de ce logiciel. Le concept étant suffisament générique, ces API pourraient servir à tout autre moteur d'indexation. C'est pourquoi nous allons le détailler.</para>
	<section>
		<title>Comment se déroule une analyse&nbsp;?</title>
		<para>La classe <literal>
				<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/analysis/Analyzer.html">org.apache.lucene.analysis.Analyzer</ulink>
			</literal> est naturellement au coeur du processus d'analyse et en particulier son unique méthode, disposant de deux signatures (dont l'une est dépréciée), <literal>
				<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/analysis/Analyzer.html#tokenStream(java.lang.String, java.io.Reader)">tokenStream</ulink>
			</literal>.
		</para>
		<para>Comme son nom l'indique, cette méthode a pour but de fournir un flux de jetons (<emphasis>tokens</emphasis>) pour un champ donné à partir d'un <literal>
				<ulink url="http://java.sun.com/j2se/1.4.2/docs/api/java/io/Reader.html">java.io.Reader</ulink>
			</literal>. Ce <literal>Reader</literal> permet une très grande abstraction&nbsp;: il peut bien évidemment lire un flux textuel mais aussi tout autre type de flux si bien que l'analyse d'une image ou d'un son serait tout à fait concevable&nbsp;!</para>
		<para>La plupart du temps cependant, le <literal>Reader</literal> que SDX fournira à l'analyseur sera celui fourni par le parseur du document XML généré par la XSLT d'indexation.</para>
		<para>En sortie, l'analyseur doit fournir un <literal>
				<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/analysis/TokenStream.html">org.apache.lucene.analysis.TokenStream</ulink>
			</literal>, c'est à dire une suite de <literal>
				<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/analysis/Token.html">org.apache.lucene.analysis.Token</ulink>
			</literal>. Cet objet, finalité du processus d'analyse, possède les propriétés suivantes&nbsp;:</para>
		<itemizedlist>
			<listitem>
				<para>une représentation textuelle, accessible via la méthode <literal>
						<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/analysis/Token.html#termText()">termText()</ulink>
					</literal>. Quand bien même on analyse une image, l'analyse doit donc, en l'état actuel des choses, renvoyer des <literal>Token</literal>s textuels :-)</para>
			</listitem>
			<listitem>
				<para>une position de début dans le flux d'origine, accessible via la méthode <literal>
						<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/analysis/Token.html#startOffset()">startOffset()</ulink>
					</literal>. Il est important de noter que cette information est perdue lorsque le <literal>Token</literal> devient un <literal>Term</literal> indexé (v. plus bas).</para>
			</listitem>
			<listitem>
				<para>une position de fin dans le flux d'origine, accessible via la méthode <literal>
						<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/analysis/Token.html#endOffset()">endOffset()</ulink>
					</literal>. Il est important de noter que cette information est perdue lorsque le <literal>Token</literal> devient un <literal>Term</literal> indexé (v. plus bas).

				</para>
			</listitem>
			<listitem>
				<para>un incrément par rapport au <literal>Token</literal> précédemment reçu, accessible via la méthode <literal>
						<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/analysis/Token.html#getPositionIncrement()">getPositionIncrement()</ulink>
					</literal>. Cet incrément est généralement positionné à <literal>1</literal>, ce qui signifie que les <literal>Token</literal>s se <emphasis>suivent immédiatement les uns à la suite des autres</emphasis>. Ce marqueur de position prend toute son importance dans les <ulink url="http://lucene.sourceforge.net/cgi-bin/faq/faqmanager.cgi?file=chapter.search&amp;toc=faq#q8">requêtes de phrase</ulink>, y compris les <ulink url="http://jakarta.apache.org/lucene/docs/queryparsersyntax.html#Proximity Searches">requêtes de proximité</ulink>.</para>
			</listitem>
			<listitem>
				<para>un type, accessible via la méthode <literal>
						<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/analysis/Token.html#type()">type()</ulink>
					</literal>. Il est important de noter que cette information est perdue lorsque le <literal>Token</literal> devient un <literal>Term</literal> indexé (v. plus bas).</para>
			</listitem>
		</itemizedlist>
		<para>La première tâche que devra remplir un <literal>Analyzer</literal> consistera à découper (tokeniser) le flux reçu par le <literal>Reader</literal>. A cet effet, on utilisera généralement un descendant de la classe <literal>
				<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/analysis/Tokenizer.html">org.apache.lucene.analysis.Tokenizer</ulink>
			</literal> charger de découper le texte en <literal>Token</literal>s, sur les espaces ou les signes de ponctuation par exemple. Il va de soi que le découpage des écritures sans espaces (chinois, égyptien ancien....) relève d'une problématique de découpage complexe.</para>
		<para>Une fois ces <literal>Token</literal>s bruts obtenus, il sera souhaitable de les transformer et/ou de les supprimer du flux (cas des mots-vides). Cette opération est réalisée grâce à des descendants de la classe <literal>
				<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/analysis/TokenFilter.html">org.apache.lucene.analysis.TokenFilter</ulink>
			</literal> qui recevront un <literal>TokenStream</literal> en entrée, et qui a leur tour, émettront des <literal>Token</literal>s, éventuellement à destination d'autres <literal>TokenFilter</literal>s.</para>
		<para>On obtient donc <emphasis>in fine</emphasis> un flux de <literal>Token</literal>s qui vont être transformés en objets <literal>
				<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/index/Term.html">org.apache.lucene.index.Term</ulink>
			</literal> stockés dans les index Lucene. Un objet <literal>Term</literal> comporte 2 propriétés principales&nbsp;:</para>
		<itemizedlist>
			<listitem>
				<para>une représentation textuelle, accessible via la méthode <literal>
						<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/index/Term.html#text()">text()</ulink>
					</literal>. Comparer avec <literal>
						<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/analysis/Token.html#termText()">Token.termText()</ulink>
					</literal>.</para>
			</listitem>
			<listitem>
				<para>le champ pour lequel on définit le terme, accessible via la méthode <literal>
						<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/index/Term.html#field()">field()</ulink>
					</literal>. Dans SDX, il reprendra naturellement l'attribut <sgmltag class="attribute">name</sgmltag> de l'élément <sgmltag class="element">&lt;sdx:field/&gt;</sgmltag>.</para>
			</listitem>
		</itemizedlist>
		<para>A première vue, il semblerait que le passage d'un <literal>Token</literal> à un <literal>Term</literal> fasse perdre beaucoup d'information&nbsp;: la position de début dans le flux d'entrée, la position de fin dans le flux d'entrée, le type de <literal>Token</literal>, ainsi que la position relative par rapport au <literal>Token</literal> précédent disparaissent.</para>
		<para>C'est vrai pour les trois premières propriétés, qui n'ont pour but que de récupérer les information fournies par le flux d'entrée et/ou les différents processus de filtrage des <literal>Token</literal>s, mais c'est faux pour la dernière&nbsp;: la méthode <literal>
				<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/index/IndexReader.html#termPositions(org.apache.lucene.index.Term)">termPositions()</ulink>
			</literal> de la classe <literal>
				<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/index/IndexReader.html">org.apache.lucene.index.IndexReader</ulink>
			</literal> est capable de retrouver la position d'un terme et donc de permettre les requêtes de proximité. En effet, pour des raisons de performances, l'architecture de Lucene a préféré mutualiser la position des termes du niveau des <emphasis>index</emphasis> ainsi que l'indique la documentation de la méthode&nbsp;:</para>
		<para>Returns an enumeration of all the documents which contain term. For each document, in addition to the document number and frequency of the term in that document, a list of all of the ordinal positions of the term in the document is available. Thus, this method implements the mapping:
</para>
		<para>
Term    =&gt;    &lt;docNum, freq, &lt;pos1, pos2, ... posfreq-1&gt; &gt;*
</para>
		<para>Pour un terme donné, l'index stocke donc un numéro d'ordre de document, le nombre d'occurences du terme dans le document, ainsi que les différentes positions du terme dans ce document.</para>
		<para>On peut donc tout à fait légitimement comparer un analyseur d'indexation à un <literal>Pipeline</literal> Cocoon, le <literal>Tokenizer</literal> jouant le rôle du générateur, les <literal>TokenFilter</literal>s jouant les rôle des transformateurs et la classe <literal>
				<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/index/IndexWriter.html">org.apache.lucene.index.IndexWriter</ulink>
			</literal> jouant le rôle de sérialiseur. Un analyseur de requête, serait, lui, sérialisé vers un objet <literal>
				<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/search/Query.html">org.apache.lucene.search.Query</ulink>
			</literal> composé d'une ou plusieurs requêtes "atomiques" issues du package <literal>
				<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/search/package-summary.html">org.apache.lucene.search</ulink>
			</literal>
		</para>
	</section>
	<section>
		<title>L'analyseur français</title>
		<para>La documentation sur la <olink targetdocent="multilinguisme">multilinguisme</olink> explique clairement la façon de définir un analyseur. Nous allons décrire la façon dont opère l'analyseur français fourni par défaut dans SDX, <literal>
				<ulink url="http://www.nongnu.org/sdx/docs/html/doc-sdx2/apidocs/fr/gouv/culture/sdx/search/lucene/analysis/Analyzer_fr.html">fr.gouv.culture.sdx.search.lucene.analysis.Analyzer_fr</ulink>
			</literal>
		</para>
		<para>Exmaminons sa methode <literal>tokenStream</literal>&nbsp;:</para>
		<programlisting><![CDATA[
public final TokenStream tokenStream(String fieldName, Reader reader) {

	// The token stream that will be returned.
	TokenStream result;

	// Builds the chain...
	result = new StandardTokenizer(reader);

	FrenchStandardFilter fsf = new FrenchStandardFilter();
	fsf.enableLogging(logger);
	fsf.setUp(result);

	result = fsf;

	result = new LowerCaseFilter(result);
	if (!keepAccents) {
		ISOLatin1AccentFilter ilf = new ISOLatin1AccentFilter();
		ilf.enableLogging(logger);
		ilf.setUp(result);
		result = ilf;
	}

	if (stopTable != null) result = new StopFilter(result, stopTable);

	// And returns the end of the chain
	return result;
}
]]></programlisting>
		<para>La tokenization est assurée par la classe <literal>
				<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/analysis/standard/StandardTokenizer.html">org.apache.lucene.analysis.standard.StandardTokenizer</ulink>
			</literal> qui renvoie des <literal>Token</literal>s typés suivant les constantes définies dans <literal>
				<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/analysis/standard/StandardTokenizerConstants.html">org.apache.lucene.analysis.standard.StandardTokenizerConstants</ulink>
			</literal>. Tout caractère ne participant pas à la définition d'un <literal>Token</literal> d'un type reconnu est considéré comme un non-<literal>Token</literal> remplissant fondamentalement la fonction de <emphasis>séparateur</emphasis> de <literal>Token</literal>s.
		</para>
		<para>Les <literal>Token</literal>s sont ensuite filtrés par <literal>
				<ulink url="http://www.nongnu.org/sdx/docs/html/doc-sdx2/apidocs/fr/gouv/culture/sdx/search/lucene/analysis/filter/FrenchStandardFilter.html">fr.gouv.culture.sdx.search.lucene.analysis.filter.FrenchStandardFilter</ulink>
			</literal> qui supprime les apostrophes et normalise les acronymes en ôtant les points.</para>
		<para>Le filtre suivant, <literal>
				<ulink url="http://www.nongnu.org/sdx/docs/html/doc-sdx2/apidocs/fr/gouv/culture/sdx/search/lucene/analysis/filter/ISOLatin1AccentFilter.html">fr.gouv.culture.sdx.search.lucene.analysis.filter.ISOLatin1AccentFilter</ulink>
			</literal>, est appelé en fonction de la configuration de l'analyseur. Il a pour rôle d'appauvrir les caractères diacritiques, notamment les caractères accentués, du jeu de caractères ISO-8859-1.</para>
		<para>Le filtre suivant, <literal>
				<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/analysis/LowerCaseFilter.html">org.apache.lucene.analysis.LowerCaseFilter</ulink>
			</literal> remplace les majuscules par des minuscules.</para>
		<para>Le dernier filtre, <literal>
				<ulink url="http://jakarta.apache.org/lucene/docs/api/org/apache/lucene/analysis/StopFilter.html">org.apache.lucene.analysis.StopFilter</ulink>
			</literal> filtre les <literal>Token</literal>s qui apparaissent dans une liste de mots vides&nbsp;; cette liste peut éventuellement être mentionnée dans le fichier de configuration de l'analyseur.</para>
	</section>
	<section>
		<title>Un cas particulier d'analyseur&nbsp;: l'analyseur arabe</title>
		<para>L'arabe, comme l'hébreu et certaines autre langues sémitiques anciennes (égyptien, araméen, ougaritique...), n'exprime pas le besoin de noter les voyelles brèves. Quand bien même celles-ci ont une importance fondamentale quand à la catégorisation d'un mot (mode, voie, nombre, sens...), l'écriture attend du lecteur un processus de déduction à partir du contexte, censé lever toute ambiguité entre mots dotés d'un vocalisme différent mais disposant <emphasis>à l'écrit</emphasis> du même squelette consonnantique.</para>
	</section>
</webpage>
