1<?xml version="1.0" encoding="utf-8"?>
2
3<overlay xmlns="http://hoa-project.net/xyl/xylophone">
4<yield id="chapter">
5
6  <p>Les chaînes de caractères peuvent parfois être <strong>complexes</strong>,
7  particulièrement lorsqu'elles utilisent l'encodage <strong>Unicode</strong>.
8  La bibliothèque <code>Hoa\Ustring</code> propose plusieurs opérations sur des
9  chaînes de caractères UTF-8.</p>
10
11  <h2 id="Table_of_contents">Table des matières</h2>
12
13  <tableofcontents id="main-toc" />
14
15  <h2 id="Introduction" for="main-toc">Introduction</h2>
16
17  <p>Lorsque nous manipulons des chaînes de caractères, le format
18  <a href="http://unicode.org/">Unicode</a> s'impose par sa
19  <strong>compatibilité</strong> avec les formats de base historiques (comme
20  ASCII) et par sa grande capacité à comprendre une très <strong>large</strong>
21  plage de caractères et de symboles, pour toutes les cultures et toutes les
22  régions de notre monde. PHP propose plusieurs outils pour manipuler de telles
23  chaînes, comme les extensions
24  <a href="http://php.net/mbstring"><code>mbstring</code></a>,
25  <a href="http://php.net/iconv"><code>iconv</code></a> ou encore l'excellente
26  <a href="http://php.net/intl"><code>intl</code></a> qui se base sur
27  <a href="http://icu-project.org/">ICU</a>, l'implémentation de référence
28  d'Unicode. Malheureusement, il faut parfois mélanger ces extensions pour
29  arriver à nos fins et au prix d'une certaine <strong>complexité</strong> et
30  d'une <strong>verbosité</strong> regrettable.</p>
31  <p>La bibliothèque <code>Hoa\Ustring</code> répond à ces problématiques en
32  proposant une façon <strong>simple</strong> de manipuler des chaînes de
33  caractères, de manière <strong>performante</strong> et
34  <strong>efficace</strong>. Elle propose également des algorithmes évolués pour
35  des opérations de <strong>recherche</strong> sur des chaînes de
36  caractères.</p>
37
38  <h2 id="Unicode_strings" for="main-toc">Chaîne de caractères Unicode</h2>
39
40  <p>La classe <code>Hoa\Ustring\Ustring</code> représente une chaîne de
41  caractères Unicode <strong>UTF-8</strong> et permet de la manipuler
42  facilement. Elle implémente les interfaces
43  <a href="http://php.net/arrayaccess"><code>ArrayAccess</code></a>,
44  <a href="http://php.net/countable"><code>Countable</code></a> et
45  <a href="http://php.net/iteratoraggregate"><code>IteratorAggregate</code></a>.
46  Nous allons utiliser trois exemples dans trois langues différentes : français,
47  arabe et japonais. Ainsi :</p>
48  <pre><code class="language-php">$french   = new Hoa\Ustring\Ustring('Je t\'aime');
49$arabic   = new Hoa\Ustring\Ustring('أحبك');
50$japanese = new Hoa\Ustring\Ustring('私はあなたを愛して');</code></pre>
51  <p>Maintenant, voyons les opérations possibles sur ces trois chaînes.</p>
52
53  <h3 id="String_manipulation" for="main-toc">Manipulation de la chaîne</h3>
54
55  <p>Commençons par les opérations <strong>élémentaires</strong>. Si nous
56  voulons <strong>compter</strong> le nombre de caractères (et non pas
57  d'octets), nous allons utiliser <a href="http://php.net/count">la fonction
58  <code>count</code></a> de PHP. Ainsi :</p>
59  <pre><code class="language-php">var_dump(
60    count($french),
61    count($arabic),
62    count($japanese)
63);
64
65/**
66 * Will output:
67 *     int(9)
68 *     int(4)
69 *     int(9)
70 */</code></pre>
71  <p>Quand nous parlons de position sur un texte, il n'est pas adéquat de parler
72  de droite ou de gauche, mais plutôt de <strong>début</strong> ou de
73  <strong>fin</strong>, et cela à partir de la <strong>direction</strong> (sens
74  d'écriture) du texte. Nous pouvons connaître cette direction grâce à la
75  méthode <code>Hoa\Ustring\Ustring::getDirection</code>. Elle retourne la
76  valeur d'une des constantes suivantes :</p>
77  <ul>
78    <li><code>Hoa\Ustring\Ustring::LTR</code>, pour
79    <em lang="en">left-to-right</em>, si le texte s'écrit de gauche à
80    droite ;</li>
81    <li><code>Hoa\Ustring\Ustring::RTL</code>, pour
82    <em lang="en">right-to-left</em>, si le texte s'écrit de droite à
83    gauche.</li>
84  </ul>
85  <p>Observons le résultat sur nos exemples :</p>
86  <pre><code class="language-php">var_dump(
87    $french->getDirection()   === Hoa\Ustring\Ustring::LTR, // is left-to-right?
88    $arabic->getDirection()   === Hoa\Ustring\Ustring::RTL, // is right-to-left?
89    $japanese->getDirection() === Hoa\Ustring\Ustring::LTR  // is left-to-right?
90);
91
92/**
93 * Will output:
94 *     bool(true)
95 *     bool(true)
96 *     bool(true)
97 */</code></pre>
98  <p>Le résultat de cette méthode est calculé grâce à la méthode statique
99  <code>Hoa\Ustring\Ustring::getCharDirection</code> qui calcule la direction
100  d'un seul caractère.</p>
101  <p>Si nous voulons <strong>concaténer</strong> une autre chaîne à la fin ou au
102  début, nous utiliserons respectivement les méthodes
103  <code>Hoa\Ustring\Ustring::append</code> et
104  <code>Hoa\Ustring\Ustring::prepend</code>. Ces méthodes, comme la plupart de
105  celles qui modifient la chaîne, retournent l'objet lui-même, ce afin de
106  chaîner les appels. Par exemple :</p>
107  <pre><code class="language-php">echo $french->append('… et toi, m\'aimes-tu ?')->prepend('Mam\'zelle ! ');
108
109/**
110 * Will output:
111 *     Mam'zelle ! Je t'aime… et toi, m'aimes-tu ?
112 */</code></pre>
113  <p>Nous avons également les méthodes
114  <code>Hoa\Ustring\Ustring::toLowerCase</code> et
115  <code>Hoa\Ustring\Ustring::toUpperCase</code> pour, respectivement, mettre la
116  chaîne en <strong>minuscules</strong> ou en <strong>majuscules</strong>. Par
117  exemple :</p>
118  <pre><code class="language-php">echo $french->toUpperCase();
119
120/**
121 * Will output:
122 *     MAM'ZELLE ! JE T'AIME… ET TOI, M'AIMES-TU ?
123 */</code></pre>
124  <p>Nous pouvons aussi ajouter des caractères en début ou en fin de chaîne pour
125  atteindre une taille <strong>minimum</strong>. Cette opération est plus
126  couramment appelée le <em lang="en">padding</em> (pour des raisons historiques
127  remontant aux machines à écrire). C'est pourquoi nous trouvons la méthode
128  <code>Hoa\Ustring\Ustring::pad</code> qui prend trois arguments : la taille
129  minimum, les caractères à ajouter et une constante indiquant si nous devons
130  ajouter en fin ou en début de chaîne (respectivement
131  <code>Hoa\Ustring\Ustring::END</code>, par défaut, et
132  <code>Hoa\Ustring\Ustring::BEGINNING</code>).</p>
133  <pre><code class="language-php">echo $arabic->pad(20, ' ');
134
135/**
136 * Will output:
137 *                     أحبك
138 */</code></pre>
139  <p>Une opération similairement inverse permet de supprimer, par défaut, les
140  <strong>espaces</strong> en début et en fin de chaîne grâce à la méthode
141  <code>Hoa\Ustring\Ustring::trim</code>. Par exemple, pour revenir à notre
142  chaîne arabe originale :</p>
143  <pre><code class="language-php">echo $arabic->trim();
144
145/**
146 * Will output:
147 *     أحبك
148 */</code></pre>
149  <p>Si nous voulons supprimer d'autres caractères, nous pouvons utiliser son
150  premier argument qui doit être une expression régulière. Enfin, son second
151  argument permet de préciser de quel côté nous voulons supprimer les
152  caractères : en début, en fin ou les deux, toujours en utilisant les
153  constantes <code>Hoa\Ustring\Ustring::BEGINNING</code> et
154  <code>Hoa\Ustring\Ustring::END</code>.  Nous pouvons combiner ces constantes
155  pour exprimer « les deux côtés », ce qui est la valeur par défaut :
156  <code class="language-php">Hoa\Ustring\Ustring::BEGINNING |
157  Hoa\Ustring\Ustring::END</code>. Par exemple, pour supprimer tous les nombres
158  et les espaces uniquement à la fin, nous écrirons :</p>
159  <pre><code class="language-php">$arabic->trim('\s|\d', Hoa\Ustring\Ustring::END);</code></pre>
160  <p>Nous pouvons également <strong>réduire</strong> la chaîne à une
161  <strong>sous-chaîne</strong> en précisant la position du premier caractère
162  puis la taille de la sous-chaîne à la méthode
163  <code>Hoa\Ustring\Ustring::reduce</code> :</p>
164  <pre><code class="language-php">echo $french->reduce(3, 6)->reduce(2, 4);
165
166/**
167 * Will output:
168 *     aime
169 */</code></pre>
170  <p>Si nous voulons obtenir un caractère en particulier, nous pouvons exploiter
171  l'interface <code>ArrayAccess</code>. Par exemple, pour obtenir le premier
172  caractère de chacun de nos exemples (en les reprenant depuis le début) :</p>
173  <pre><code class="language-php">var_dump(
174    $french[0],
175    $arabic[0],
176    $japanese[0]
177);
178
179/**
180 * Will output:
181 *     string(1) "J"
182 *     string(2) "أ"
183 *     string(3) "私"
184 */</code></pre>
185  <p>Si nous voulons le dernier caractère, nous utiliserons l'index -1. L'index
186  n'est pas borné à la taille de la chaîne. Si jamais l'index dépasse cette
187  taille, alors un <em>modulo</em> sera appliqué.</p>
188  <p>Nous pouvons aussi modifier ou supprimer un caractère précis avec cette
189  méthode. Par exemple :</p>
190  <pre><code class="language-php">$french->append(' ?');
191$french[-1] = '!';
192echo $french;
193
194/**
195 * Will output:
196 *     Je t'aime !
197 */</code></pre>
198  <p>Une autre méthode fort utile est la transformation en
199  <strong>ASCII</strong>. Attention, ce n'est pas toujours possible, selon votre
200  installation. Par exemple :</p>
201  <pre><code class="language-php">$title = new Hoa\Ustring\Ustring('Un été brûlant sur la côte');
202echo $title->toAscii();
203
204/**
205 * Will output:
206 *     Un ete brulant sur la cote
207 */</code></pre>
208  <p>Nous pouvons aussi transformer de l'arabe ou du japonais vers de l'ASCII.
209  Les symboles, comme les symboles Mathématiques ou les emojis, sont aussi
210  transformés :</p>
211  <pre><code class="language-php">$emoji = new Hoa\Ustring\Ustring('I ❤ Unicode');
212$maths = new Hoa\Ustring\Ustring('∀ i ∈ ℕ');
213
214echo
215    $arabic->toAscii(), "\n",
216    $japanese->toAscii(), "\n",
217    $emoji->toAscii(), "\n",
218    $maths->toAscii(), "\n";
219
220/**
221 * Will output:
222 *     ahbk
223 *     sihaanatawo aishite
224 *     I (heavy black heart)️ Unicode
225 *     (for all) i (element of) N
226 */</code></pre>
227  <p>Pour que cette méthode fonctionne correctement, il faut que l'extension
228  <a href="http://php.net/intl"><code>intl</code></a> soit présente, pour que la
229  classe <a href="http://php.net/transliterator"><code>Transliterator</code></a>
230  existe. Si elle n'existe pas, la classe
231  <a href="http://php.net/normalizer"><code>Normalizer</code></a> doit exister.
232  Si cette classe n'existe pas non plus, la méthode
233  <code>Hoa\Ustring\Ustring::toAscii</code> peut quand même essayer une
234  transformation mais moins efficace. Pour cela, il faut passer
235  <code>true</code> en seul argument. Ce tour de force est déconseillé dans la
236  plupart des cas.</p>
237  <p>Nous trouvons également la méthode <code>getTransliterator</code> qui
238  retourne un objet <code>Transliterator</code>, ou <code>null</code> si cette
239  classe n'existe pas. Cette méthode prend en argument un identifiant de
240  translitération. Nous conseillons de
241  <a href="http://userguide.icu-project.org/transforms/general">lire la
242  documentation sur le translitérateur d'ICU</a> pour comprendre cet
243  identifiant. La méthode <code>transliterate</code> permet de translitérer la
244  chaîne courante à partir d'un identifiant et d'un index de début et de
245  fin. Elle fonctionne de la même façon que la méthode
246  <a href="http://php.net/transliterator.transliterate"><code>Transliterator::transliterate</code></a>.</p>
247
248  <p>Plus généralement, pour des changements d'<strong>encodage</strong> brut,
249  nous pouvons utiliser la méthode statique
250  <code>Hoa\Ustring\Ustring::transcode</code>, avec en premier argument une chaîne
251  de caractères, en deuxième argument l'encodage d'origine et en dernier
252  argument l'encodage final souhaité (par défaut UTF-8). Pour la liste des
253  encodages, il faut se reporter à l'extension
254  <a href="http://php.net/iconv"><code>iconv</code></a> ou entrer la commande
255  suivante dans un terminal :</p>
256  <pre><code class="language-php">$ iconv --list</code></pre>
257  <p>Pour savoir si une chaîne est encodée en UTF-8, nous pouvons utiliser la
258  méthode statique <code>Hoa\Ustring\Ustring::isUtf8</code> ; par exemple :</p>
259  <pre><code class="language-php">var_dump(
260    Hoa\Ustring\Ustring::isUtf8('a'),
261    Hoa\Ustring\Ustring::isUtf8(Hoa\Ustring\Ustring::transcode('a', 'UTF-8', 'UTF-16'))
262);
263
264/**
265 * Will output:
266 *     bool(true)
267 *     bool(false)
268 */</code></pre>
269  <p>Nous pouvons <strong>éclater</strong> la chaîne en plusieurs sous-chaînes
270  en utilisant la méthode <code>Hoa\Ustring\Ustring::split</code>. En premier
271  argument, nous avons une expression régulière (type
272  <a href="http://pcre.org/">PCRE</a>), puis un entier représentant le nombre
273  maximum d'éléments à retourner et enfin une combinaison de constantes. Ces
274  constantes sont les mêmes que celles de
275  <a href="http://php.net/preg_split"><code>preg_split</code></a>.</p>
276  <p>Par défaut, le deuxième argument vaut -1, qui symbolise l'infini, et le
277  dernier argument vaut <code>PREG_SPLIT_NO_EMPTY</code>. Ainsi, si nous
278  voulons obtenir tous les mots d'une chaîne, nous écrirons :</p>
279  <pre><code class="language-php">print_r($title->split('#\b|\s#'));
280
281/**
282 * Will output:
283 *     Array
284 *     (
285 *         [0] => Un
286 *         [1] => ete
287 *         [2] => brulant
288 *         [3] => sur
289 *         [4] => la
290 *         [5] => cote
291 *     )
292 */</code></pre>
293  <p>Si nous voulons <strong>itérer</strong> sur tous les
294  <strong>caractères</strong>, il est préférable d'exploiter l'interface
295  <code>IteratorAggregate</code>, soit la méthode
296  <code>Hoa\Ustring\Ustring::getIterator</code>. Voyons plutôt sur l'exemple en
297  arabe :</p>
298  <pre><code class="language-php">foreach ($arabic as $letter) {
299    echo $letter, "\n";
300}
301
302/**
303 * Will output:
304 *     أ
305 *     ح
306 *     ب
307 *     ك
308 */</code></pre>
309  <p>Nous remarquons que l'itération se fait suivant la direction du texte,
310  c'est à dire que le premier élément de l'itération est la première lettre de
311  la chaîne en partant du début.</p>
312  <p>Bien sûr, si nous voulons obtenir un tableau des caractères, nous pouvons
313  utiliser la fonction
314  <a href="http://php.net/iterator_to_array"><code>iterator_to_array</code></a>
315  de PHP :</p>
316  <pre><code class="language-php">print_r(iterator_to_array($arabic));
317
318/**
319 * Will output:
320 *     Array
321 *     (
322 *         [0] => أ
323 *         [1] => ح
324 *         [2] => ب
325 *         [3] => ك
326 *     )
327 */</code></pre>
328
329  <h3 id="Comparison_and_search" for="main-toc">Comparaison et recherche</h3>
330
331  <p>Les chaînes peuvent également être <strong>comparées</strong> entre elles
332  grâce à la méthode <code>Hoa\Ustring\Ustring::compare</code> :</p>
333  <pre><code class="language-php">$string = new Hoa\Ustring\Ustring('abc');
334var_dump(
335    $string->compare('wxyz')
336);
337
338/**
339 * Will output:
340 *     string(-1)
341 */</code></pre>
342  <p>Cette méthode retourne -1 si la chaîne initiale vient avant (par ordre
343  alphabétique), 0 si elle est identique et 1 si elle vient après. Si nous
344  voulons utiliser la pleine
345  puissance du mécanisme sous-jacent, nous pouvons appeler la méthode statique
346  <code>Hoa\Ustring\Ustring::getCollator</code> (si la classe
347  <a href="http://php.net/Collator"><code>Collator</code></a> existe, sinon
348  <code>Hoa\Ustring\Ustring::compare</code> utilisera une comparaison simple
349  octet par octets sans tenir compte d'autres paramètres). Ainsi, si nous
350  voulons trier un tableau de chaînes, nous écrirons plutôt :</p>
351  <pre><code class="language-php">$strings = array('c', 'Σ', 'd', 'x', 'α', 'a');
352Hoa\Ustring\Ustring::getCollator()->sort($strings);
353print_r($strings);
354
355/**
356 * Could output:
357 *     Array
358 *     (
359 *         [0] => a
360 *         [1] => c
361 *         [2] => d
362 *         [3] => x
363 *         [4] => α
364 *         [5] => Σ
365 *     )
366 */</code></pre>
367  <p>La comparaison entre deux chaînes dépend de la <strong>locale</strong>,
368  c'est à dire de la régionalisation du système, comme la langue, le pays, la
369  région etc. Nous pouvons utiliser <a href="@hack:chapter=Locale">la
370  bibliothèque <code>Hoa\Locale</code></a> pour modifier ces données, mais ce
371  n'est pas une dépendance de <code>Hoa\Ustring</code> pour autant.</p>
372  <p>Nous pouvons également savoir si une chaîne <strong>correspond</strong> à
373  un certain motif, toujours exprimé avec une expression régulière. Pour cela,
374  nous allons utiliser la méthode <code>Hoa\Ustring\Ustring::match</code>. Cette
375  méthode repose sur les fonctions
376  <a href="http://php.net/preg_match"><code>preg_match</code></a> et
377  <a href="http://php.net/preg_match_all"><code>preg_match_all</code></a> de
378  PHP, mais en modifiant les options du motif afin qu'il supporte Unicode. Nous
379  avons les paramètres suivants : le motif, une variable par référence pour
380  récupérer les captures, les <em lang="en">flags</em>, la position de début de
381  recherche (<em lang="en">offset</em>) et enfin un booléen indiquant si la
382  recherche est globale ou non (respectivement si nous devons utiliser
383  <code>preg_match_all</code> ou <code>preg_match</code>). Par défaut, la
384  recherche n'est pas globale.</p>
385  <p>Ainsi, nous allons vérifier que notre exemple en français contient bien
386  <code>aime</code> avec son complément d'objet direct :</p>
387  <pre><code class="language-php">$french->match('#(?:(?&amp;lt;direct_object>\w)[\'\b])aime#', $matches);
388var_dump($matches['direct_object']);
389
390/**
391 * Will output:
392 *     string(1) "t"
393 */</code></pre>
394  <p>Cette méthode retourne <code>false</code> si une erreur est survenue (par
395  exemple si le motif n'est pas correct), 0 si aucune correspondance n'a été
396  trouvée, le nombre de correspondances trouvées sinon.</p>
397  <p>Similairement, nous pouvons <strong>chercher</strong> et
398  <strong>remplacer</strong> des sous-chaînes par d'autres sous-chaînes suivant
399  un motif, toujours exprimé avec une expression régulière. Pour cela, nous
400  allons utiliser la méthode <code>Hoa\Ustring\Ustring::replace</code>. Cette
401  méthode repose sur les fonctions
402  <a href="http://php.net/preg_replace"><code>preg_replace</code></a> et
403  <a href="http://php.net/preg_replace_callback"><code>preg_replace_callback</code></a>
404  de PHP, mais toujours en modifiant les options du motif afin qu'il supporte
405  Unicode. En premier argument, nous trouvons le ou les motifs, en deuxième
406  argument, le ou les remplacements et en dernier argument la limite de
407  remplacements à faire. Si le remplacement est un <em lang="en">callable</em>,
408  alors la fonction <code>preg_replace_callback</code> sera utilisée.</p>
409  <p>Ainsi, nous allons modifier notre exemple français pour qu'il soit plus
410  poli :</p>
411  <pre><code class="language-php">$french->replace('#(?:\w[\'\b])(?&amp;lt;verb>aime)#', function ($matches) {
412    return 'vous ' . $matches['verb'];
413});
414
415echo $french;
416
417/**
418 * Will output:
419 *     Je vous aime
420 */</code></pre>
421  <p>La classe <code>Hoa\Ustring\Ustring</code> propose des constantes qui sont
422  des aliases de constantes PHP et qui permettent une meilleure lecture du
423  code:</p>
424  <ul>
425    <li><code>Hoa\Ustring\Ustring::WITHOUT_EMPTY</code>, alias de
426    <code>PREG_SPLIT_NO_EMPTY</code> ;</li>
427    <li><code>Hoa\Ustring\Ustring::WITH_DELIMITERS</code>, alias de
428    <code>PREG_SPLIT_DELIM_CAPTURE</code> ;</li>
429    <li><code>Hoa\Ustring\Ustring::WITH_OFFSET</code>, alias de
430    <code>PREG_OFFSET_CAPTURE</code> et
431    <code>PREG_SPLIT_OFFSET_CAPTURE</code> ;</li>
432    <li><code>Hoa\Ustring\Ustring::GROUP_BY_PATTERN</code>, alias de
433    <code>PREG_PATTERN_ORDER</code> ;</li>
434    <li><code>Hoa\Ustring\Ustring::GROUP_BY_TUPLE</code>, alias de
435    <code>PREG_SET_ORDER</code>.</li>
436  </ul>
437  <p>Comme ce sont des aliases stricts, nous pouvons écrire :</p>
438  <pre><code class="language-php">$string = new Hoa\Ustring\Ustring('abc1 defg2 hikl3 xyz4');
439$string->match(
440    '#(\w+)(\d)#',
441    $matches,
442    Hoa\Ustring\Ustring::WITH_OFFSET
443  | Hoa\Ustring\Ustring::GROUP_BY_TUPLE,
444    0,
445    true
446);</code></pre>
447
448  <h3 id="Characters" for="main-toc">Caractères</h3>
449
450  <p>La classe <code>Hoa\Ustring\Ustring</code> offre des méthodes statiques
451  travaillant sur un seul caractère Unicode. Nous avons déjà évoqué la méthode
452  <code>getCharDirection</code> qui permet de connaître la
453  <strong>direction</strong> d'un caractère. Nous trouvons aussi
454  <code>getCharWidth</code> qui calcule le <strong>nombre de colonnes</strong>
455  nécessaires pour l'affichage d'un seul caractère. Ainsi :</p>
456  <pre><code class="language-php">var_dump(
457    Hoa\Ustring\Ustring::getCharWidth(Hoa\Ustring\Ustring::fromCode(0x7f)),
458    Hoa\Ustring\Ustring::getCharWidth('a'),
459    Hoa\Ustring\Ustring::getCharWidth('㽠')
460);
461
462/**
463 * Will output:
464 *     int(-1)
465 *     int(1)
466 *     int(2)
467 */</code></pre>
468  <p>Cette méthode retourne -1 ou 0 si le caractère n'est pas
469  <strong>imprimable</strong> (par exemple si c'est un caractère de contrôle,
470  comme <code>0x7f</code> qui correspond à <code>DELETE</code>), 1 ou plus si
471  c'est un caractère qui peut être imprimé. Dans notre exemple, <code>㽠</code>
472  s'imprime sur 2 colonnes.</p>
473  <p>Pour plus de sémantique, nous avons accès à la méthode
474  <code>Hoa\Ustring\Ustring::isCharPrintable</code> qui permet de savoir si un
475  caractère est imprimable ou pas.</p>
476  <p>Si nous voulons calculer le nombre de colonnes pour tout une chaîne, il
477  faut utiliser la méthode <code>Hoa\Ustring\Ustring::getWidth</code>.
478  Ainsi :</p>
479  <pre><code class="language-php">var_dump(
480    $french->getWidth(),
481    $arabic->getWidth(),
482    $japanese->getWidth()
483);
484
485/**
486 * Will output:
487 *     int(9)
488 *     int(4)
489 *     int(18)
490 */</code></pre>
491  <p>Essayez dans un terminal avec une police <strong>mono-espacée</strong>.
492  Vous verrez que le japonais demande 18 colonnes pour s'afficher. Cette mesure
493  est très utile si nous voulons connaître la largeur d'une chaîne pour la
494  positionner correctement.</p>
495  <p>La méthode <code>getCharWidth</code> est différente de
496  <code>getWidth</code> car elle prend en compte des caractères de contrôles.
497  Elle est destinée à être utilisée, par exemple, avec des terminaux (voir
498  <a href="@hack:chapter=Console">la bibliothèque
499  <code>Hoa\Console</code></a>).</p>
500  <p>Enfin, si cette fois nous ne nous intéressons pas aux caractères Unicode
501  mais aux caractères <strong>machines</strong> <code>char</code> (soit 1
502  octet), nous avons une opération supplémentaire. La méthode
503  <code>Hoa\Ustring\Ustring::getBytesLength</code> va compter la
504  <strong>taille</strong> de la chaîne en octets :</p>
505  <pre><code class="language-php">var_dump(
506    $arabic->getBytesLength(),
507    $japanese->getBytesLength()
508);
509
510/**
511 * Will output:
512 *     int(8)
513 *     int(27)
514 */</code></pre>
515  <p>Si nous comparons ces résultats avec ceux de la méthode
516  <code>Hoa\Ustring\Ustring::count</code>, nous comprenons que les caractères
517  arabes sont encodés sur 2 octets alors que les caractères japonais sont
518  encodés sur 3 octets. Nous pouvons également obtenir un octet précis à l'aide
519  de la méthode <code>Hoa\Ustring\Ustring::getByteAt</code>. Encore une fois,
520  l'index n'est pas borné.</p>
521
522  <h3 id="Code-point" for="main-toc">Code-point</h3>
523
524  <p>Chaque caractère est représenté en machine par un entier, appelé
525  <strong>code-point</strong>. Pour obtenir le code-point d'un caractère, nous
526  pouvons utiliser la méthode statique <code>Hoa\Ustring\Ustring::toCode</code>,
527  et pour obtenir un caractère à partir d'un code, nous pouvons utiliser la
528  méthode statique <code>Hoa\Ustring\Ustring::fromCode</code>. Nous avons aussi
529  la méthode statique <code>Hoa\Ustring\Ustring::toBinaryCode</code> qui
530  retourne la représentation sous forme binaire d'un caractère. Prenons un
531  exemple :</p>
532  <pre><code class="language-php">var_dump(
533    Hoa\Ustring\Ustring::toCode('Σ'),
534    Hoa\Ustring\Ustring::toBinaryCode('Σ'),
535    Hoa\Ustring\Ustring::fromCode(0x1a9)
536);
537
538/**
539 * Will output:
540 *     int(931)
541 *     string(32) "1100111010100011"
542 *     string(2) "Σ"
543 */</code></pre>
544
545  <h2 id="Search_algorithms" for="main-toc">Algorithmes de recherche</h2>
546
547  <p>La bibliothèque <code>Hoa\Ustring</code> propose des algorithmes de
548  <strong>recherches</strong> sophistiquées sur les chaînes de caractères à
549  travers la classe <code>Hoa\Ustring\Search</code>.</p>
550  <p>Nous allons étudier l'algorithme
551  <code>Hoa\Ustring\Search::approximated</code> qui fait une recherche d'une
552  sous-chaîne dans une chaîne avec au maximum <strong><em>k</em>
553  différences</strong> (une différence étant une insertion, une délétion ou une
554  modification). Prenons un exemple classique avec une représentation
555  ADN : nous allons chercher toutes les sous-chaînes s'approchant de
556  <code>GATAA</code> à 1 différence près (au maximum) dans
557  <code>CAGATAAGAGAA</code>. Pour cela, nous allons donc écrire :</p>
558  <pre><code class="language-php">$x      = 'GATAA';
559$y      = 'CAGATAAGAGAA';
560$k      = 1;
561$search = Hoa\Ustring\Search::approximated($y, $x, $k);
562$n      = count($search);
563
564echo 'Try to match ', $x, ' in ', $y, ' with at most ', $k, ' difference(s):', "\n";
565echo $n, ' match(es) found:', "\n";
566
567foreach ($search as $position) {
568    echo '    • ', substr($y, $position['i'], $position['l'), "\n";
569}
570
571/**
572 * Will output:
573 *     Try to match GATAA in CAGATAAGAGAA with at most 1 difference(s):
574 *     4 match(es) found:
575 *         • AGATA
576 *         • GATAA
577 *         • ATAAG
578 *         • GAGAA
579 */</code></pre>
580  <p>Cette méthode retourne un tableau de tableaux. Chaque sous-tableau
581  représente un résultat et contient trois indexes : <code>i</code> pour la
582  position du premier caractère (octet) du résultat, <code>j</code> pour la
583  position du dernier caractère et <code>l</code> pour la taille du résultat
584  (tout simplement <code>j</code> - <code>i</code>).
585  Ainsi, nous pouvons calculer les résultats en utilisant notre chaîne initiale
586  (ici <code class="language-php">$y</code>) et ces indexes.</p>
587  <p>Avec notre exemple, nous avons quatre résultats. Le premier est
588  <code>AGATA</code>, soit <code>GATA<em>A</em></code> avec un caractère
589  déplacé, et <code>AGATA</code> existe bien dans
590  <code>C<em>AGATA</em>AGAGAA</code>. Le deuxième résultat est
591  <code>GATAA</code>, notre sous-chaîne, qui existe bel et bien dans
592  <code>CA<em>GATAA</em>GAGAA</code>. Le troisième résultat est
593  <code>ATAAG</code>, soit <code><em>G</em>ATAA</code> avec un caractère
594  déplacé, et <code>ATAAG</code> existe bien dans
595  <code>CAG<em>ATAAG</em>AGAA</code>. Enfin, le dernier résultat est
596  <code>GAGAA</code>, soit <code>GA<em>T</em>AA</code> avec un caractère
597  modifié, et <code>GAGAA</code> existe bien dans
598  <code>CAGATAA<em>GAGAA</em></code>.</p>
599  <p>Prenons un autre exemple, plus concret cette fois-ci. Nous allons
600  considérer la chaîne <code>--testIt --foobar --testThat --testAt</code> (qui
601  représente les options possibles d'une ligne de commande), et nous allons
602  chercher <code>--testot</code>, une option qu'aurait pu donner
603  l'utilisateur. Cette option n'existe pas telle quelle. Nous allons donc
604  utiliser notre algorithme de recherche avec 1 différence au maximum. Voyons
605  plutôt :</p>
606  <pre><code class="language-php">$x      = 'testot';
607$y      = '--testIt --foobar --testThat --testAt';
608$k      = 1;
609$search = Hoa\Ustring\Search::approximated($y, $x, $k);
610$n      = count($search);
611
612// …
613
614/**
615 * Will output:
616 *     Try to match testot in --testIt --foobar --testThat --testAt with at most 1 difference(s)
617 *     2 match(es) found:
618 *         • testIt
619 *         • testAt
620 */</code></pre>
621  <p>Les résultats <code>testIt</code> et <code>testAt</code> sont des vraies
622  options, donc nous pouvons les proposer à l'utilisateur. C'est un mécanisme
623  utilisé par <code>Hoa\Console</code> pour proposer des corrections à
624  l'utilisateur s'il se trompe.</p>
625
626  <h2 id="Conclusion" for="main-toc">Conclusion</h2>
627
628  <p>La bibliothèque <code>Hoa\Ustring</code> propose des facilités pour
629  manipuler des chaînes encodées au format Unicode, mais aussi pour effectuer
630  des recherches sophistiquées sur des chaînes.</p>
631
632</yield>
633</overlay>
634