Strings can sometimes be complex, especially when they use
the Unicode
encoding format. The Hoa\String
library
provides several operations on UTF-8 strings.
When we manipulate strings, the Unicode
format establishes itself because of its compatibility with
historical formats (like ASCII) and its capacity to understand a
large range of characters and symbols for all cultures and
all regions in the world. PHP provides several tools to manipulate such
strings, like the following extensions:
mbstring
,
iconv
or also the excellent
intl
which is based on
ICU, the reference implementation of
Unicode. Unfortunately, sometimes we have to mix these extensions to achieve
our aims and at the cost of a certain complexity along with
a regrettable verbosity.
The Hoa\String
library answers to these issues by providing a
simple way to manipulate strings with
performance and efficiency in minds. It
also provides some evoluated algorithms to perform search
operations on strings.
The Hoa\String\String
class represents a
UTF-8 Unicode strings and allows to manipulate it easily.
This class implements the
ArrayAccess
,
Countable
and
IteratorAggregate
interfaces. We are going to use three examples in three different languages:
French, Arab and Japanese. Thus:
$french = new Hoa\String\String('Je t\'aime');
$arabic = new Hoa\String\String('أحبك');
$japanese = new Hoa\String\String('私はあなたを愛して');
Now, let's see what we can do on these three strings.
Let's start with elementary operations. If we would like
to count the number of characters (not bytes), we will use
the count
function. Thus:
var_dump(
count($french),
count($arabic),
count($japanese)
);
/**
* Will output:
* int(9)
* int(4)
* int(9)
*/
When we speak about text position, it is not suitable to speak about the
right or the left, but rather about a beginning or an
end, and based on the direction of writing.
We can know this direction thanks to the
Hoa\String\String::getDirection
method. It returns the value of
one of the following constants:
Hoa\String\String::LTR
, for left-to-right, if the text is
written from the left to the right,Hoa\String\String::RTL
, for right-to-left, if the text is
written from the right to the left.Let's observe the result with our examples:
var_dump(
$french->getDirection() === Hoa\String\String::LTR, // is left-to-right?
$arabic->getDirection() === Hoa\String\String::RTL, // is right-to-left?
$japanese->getDirection() === Hoa\String\String::LTR // is left-to-right?
);
/**
* Will output:
* bool(true)
* bool(true)
* bool(true)
*/
The result of this method is computed thanks to the
Hoa\String\String::getCharDirection
static method which computes
the direction of only one character.
If we would like to concatenate another string to the end
or to the beginning, we will respectively use the
Hoa\String\String::append
and
Hoa\String\String::prepend
methods. These methods, like most of
the ones which modifies the string, return the object itself, in order to
chain the calls. For instance:
echo $french->append('… et toi, m\'aimes-tu ?')->prepend('Mam\'zelle ! ');
/**
* Will output:
* Mam'zelle ! Je t'aime… et toi, m'aimes-tu ?
*/
We also have the Hoa\String\String::toLowerCase
and
Hoa\String\String::toUpperCase
methods to, respectively, set the
case of the string to lower or upper. For instance:
echo $french->toUpperCase();
/**
* Will output:
* MAM'ZELLE ! JE T'AIME… ET TOI, M'AIMES-TU ?
*/
We can also add characters to the beginning or to the end of the string to
reach a minimum length. This operation is frequently called
the padding (for historical reasons dating back to typewriters).
That's why we have the Hoa\String\String::pad
method which takes
three arguments: the minimum length, characters to add and a constant
indicating whether we have to add at the end or at the beginning of the string
(respectively Hoa\String\String::END
, by default, and
Hoa\String\String::BEGINNING
).
echo $arabic->pad(20, ' ');
/**
* Will output:
* أحبك
*/
A similar operation allows to remove, by default, spaces
at the beginning and at the end of the string thanks to the
Hoa\String\String::trim
method. For example, to retreive our
original Arabic string:
echo $arabic->trim();
/**
* Will output:
* أحبك
*/
If we would like to remove other characters, we can use its first argument
which must be a regular expression. Finally, its second argument allows to
specify from what side we would like to remove character: at the beginning, at
the end or both, still by using the Hoa\String\String::BEGINNING
and Hoa\String\String::END
constants.
If we would like to remove other characters, we can use its first argument
which must be a regular expression. Finally, its second argument allows to
specify the side where to remove characters: at the beginning, at the end or
both, still by using the Hoa\String\String::BEGINNING
and
Hoa\String\String::END
constants. We can combine these constants
to express “both sides”, which is the default value:
Hoa\String\String::BEGINNING |
Hoa\String\String::END
. For example, to remove all the numbers and the
spaces only at the end, we will write:
$arabic->trim('\s|\d', Hoa\String\String::END);
We can also reduce the string to a
sub-string by specifying the position of the first character
followed by the length of the sub-string to the
Hoa\String\String::reduce
method:
echo $french->reduce(3, 6)->reduce(2, 4);
/**
* Will output:
* aime
*/
If we would like to get a specific character, we can rely on the
ArrayAccess
interface. For instance, to get the first character
of each of our examples (from their original definitions):
var_dump(
$french[0],
$arabic[0],
$japanese[0]
);
/**
* Will output:
* string(1) "J"
* string(2) "أ"
* string(3) "私"
*/
If we would like the last character, we will use the -1 index. The index is not bounded to the length of the string. If the index exceeds this length, then a modulo will be applied.
We can also modify or remove a specific character with this method. For example:
$french->append(' ?');
$french[-1] = '!';
echo $french;
/**
* Will output:
* Je t'aime !
*/
Another very useful method is the ASCII transformation. Be careful, this is not always possible, according to your settings. For example:
$title = new Hoa\String\String('Un été brûlant sur la côte');
echo $title->toAscii();
/**
* Will output:
* Un ete brulant sur la cote
*/
We can also transform from Arabic or Japanese to ASCII. Symbols, like Mathemeticals symbols or emojis, are also transformed:
$emoji = new Hoa\String\String('I ❤ Unicode');
$maths = new Hoa\String\String('∀ i ∈ ℕ');
echo
$arabic->toAscii(), "\n",
$japanese->toAscii(), "\n",
$emoji->toAscii(), "\n",
$maths->toAscii(), "\n";
/**
* Will output:
* ahbk
* sihaanatawo aishite
* I (heavy black heart)️ Unicode
* (for all) i (element of) N
*/
In order this method to work correctly, the
intl
extension needs to be
present, so that the
Transliterator
class
is present. If it does not exist, the
Normalizer
class must
exist. If this class does not exist neither, the
Hoa\String\String::toAscii
method can still try a transformation,
but it is less efficient. To activate this last solution, true
must be passed as a single argument. This tour de force is
not recommended in most cases.
We also find the getTransliterator
method which returns a
Transliterator
object, or null
if this class does
not exist. This method takes a transliteration identifier as argument. We
suggest to read
the documentation about the transliterator of ICU to understand this
identifier. The transliterate
method allows to transliterate the
current string based on an identifier and a beginning index and an end
one. This method works the same way than the
Transliterator::transliterate
method.
More generally, to change the encoding format, we can use
the Hoa\String\String::transcode
static method, with a string as
first argument, the original encoding format as second argument and the
expected encoding format as third argument (UTF-8 by default). The get the
list of encoding formats, we have to refer to the
iconv
extension or to use the
following command line in a terminal:
$ iconv --list
To know if a string is encoded in UTF-8, we can use the
Hoa\String\String::isUtf8
static method; for instance:
var_dump(
Hoa\String\String::isUtf8('a'),
Hoa\String\String::isUtf8(Hoa\String\String::transcode('a', 'UTF-8', 'UTF-16'))
);
/**
* Will output:
* bool(true)
* bool(false)
*/
We can split the string into several sub-strings by using
the Hoa\String\String::split
method. As first argument, we have a
regular expression (of kind PCRE), then an
integer representing the maximum number of elements to return and finally a
combination of constants. These constants are the same as the ones of
preg_split
.
By default, the second argument is set to -1, which means infinity, and the
last argument is set to PREG_SPLIT_NO_EMPTY
. Thus, if we would
like to get all the words of a string, we will write:
print_r($title->split('#\b|\s#'));
/**
* Will output:
* Array
* (
* [0] => Un
* [1] => ete
* [2] => brulant
* [3] => sur
* [4] => la
* [5] => cote
* )
*/
If we would like to iterate over all the
characters, it is recommended to use the
IteratorAggregate
method, being the
Hoa\String\String::getIterator
method. Let's see on the Arabic
example:
foreach ($arabic as $letter) {
echo $letter, "\n";
}
/**
* Will output:
* أ
* ح
* ب
* ك
*/
We notice that the iteration is based on the text direction, it means that the first element of the iteration is the first letter of the string starting from the beginning.
Of course, if we would like to get an array of characters, we can use the
iterator_to_array
PHP function:
print_r(iterator_to_array($arabic));
/**
* Will output:
* Array
* (
* [0] => أ
* [1] => ح
* [2] => ب
* [3] => ك
* )
*/
Strings can also be compared thanks to the
Hoa\String\String::compare
method:
$string = new Hoa\String\String('abc');
var_dump(
$string->compare('wxyz')
);
/**
* Will output:
* string(-1)
*/
This methods returns -1 if the initial string comes before (in the
alphabetical order), 0 if it is identical and 1 if it comes after. If we
would like to use all the power of the underlying mechanism, we can call the
Hoa\String\String::getCollator
static method (if the
Collator
class exists, else
Hoa\String\String::compare
will use a simple byte to bytes
comparison without taking care of the other parameters). Thus, if we would
like to sort an array of strings, we will write:
$strings = array('c', 'Σ', 'd', 'x', 'α', 'a');
Hoa\String\String::getCollator()->sort($strings);
print_r($strings);
/**
* Could output:
* Array
* (
* [0] => a
* [1] => c
* [2] => d
* [3] => x
* [4] => α
* [5] => Σ
* )
*/
Comparison between two strings depends on the locale, it
means of the localization of the system, like the language, the country, the
region etc. We can use the
Hoa\Locale
library to modify
these data, but it's not a dependence of Hoa\String
.
We can also know if a string matches a certain pattern,
still expressed with a regular expression. To achieve that, we will use the
Hoa\String\String::match
method. This method relies on the
preg_match
and
preg_match_all
PHP
functions, but by modifying the pattern's options to ensure the Unicode
support. We have the following parameters: the pattern, a variable passed by
reference to collect the matches, flags, an offset and finally a boolean
indicating whether the search is global or not (respectively if we have to use
preg_match_all
or preg_match
). By default, the
search is not global.
Thus, we will check that our French example contains aime
with
a direct object complement:
$french->match('#(?:(?<direct_object>\w)[\'\b])aime#', $matches);
var_dump($matches['direct_object']);
/**
* Will output:
* string(1) "t"
*/
This method returns false
if an error is raised (for example
if the pattern is not correct), 0 if no match has been found, the number of
matches else.
Similarly, we can search and replace
sub-strings by other sub-strings based on a pattern, still expressed with a
regular expression. To achieve that, we will use the
Hoa\String\String::replace
method. This method uses the
preg_replace
and
preg_replace_callback
PHP functions, but still by modifying the pattern's options to ensure the
Unicode support. As first argument, we find one or more patterns, as second
argument, one or more replacements and as last argument the limit of
replacements to apply. If the replacement is a callable, then the
preg_replace_callback
function will be used.
Thus, we will modify our French example to be more polite:
$french->replace('#(?:\w[\'\b])(?<verb>aime)#', function ($matches) {
return 'vous ' . $matches['verb'];
});
echo $french;
/**
* Will output:
* Je vous aime
*/
The Hoa\String\String
class provides constants which are
aliases of existing PHP constants and ensure a better readability of the
code:
Hoa\String\String::WITHOUT_EMPTY
, alias of
PREG_SPLIT_NO_EMPTY
,Hoa\String\String::WITH_DELIMITERS
, alias of
PREG_SPLIT_DELIM_CAPTURE
,Hoa\String\String::WITH_OFFSET
, alias of
PREG_OFFSET_CAPTURE
and
PREG_SPLIT_OFFSET_CAPTURE
,Hoa\String\String::GROUP_BY_PATTERN
, alias of
PREG_PATTERN_ORDER
,Hoa\String\String::GROUP_BY_TUPLE
, alias of
PREG_SET_ORDER
.Because they are strict aliases, we can write:
$string = new Hoa\String\String('abc1 defg2 hikl3 xyz4');
$string->match(
'#(\w+)(\d)#',
$matches,
Hoa\String\String::WITH_OFFSET
| Hoa\String\String::GROUP_BY_TUPLE,
0,
true
);
The Hoa\String\String
class offers static methods working on a
single Unicode character. We have already mentionned the
getCharDirection
method which allows to know the
direction of a character. We also have the
getCharWidth
which counts the number of columns
necessary to print a single character. Thus:
var_dump(
Hoa\String\String::getCharWidth(Hoa\String\String::fromCode(0x7f)),
Hoa\String\String::getCharWidth('a'),
Hoa\String\String::getCharWidth('㽠')
);
/**
* Will output:
* int(-1)
* int(1)
* int(2)
*/
This method returns -1 or 0 if the character is not
printable (for instance, if this is a control character, like
0x7f
which corresponds to DELETE
), 1 or more if this
is a character that can be printed. In our example, 㽠
requires
2 columns to be printed.
To get more semantics, we have the
Hoa\String\String::isCharPrintable
method which allows to know
whether a character is printable or not.
If we would like to count the number of columns necessary for a whole
string, we have to use the Hoa\String\String::getWidth
method.
Thus:
var_dump(
$french->getWidth(),
$arabic->getWidth(),
$japanese->getWidth()
);
/**
* Will output:
* int(9)
* int(4)
* int(18)
*/
Try this in your terminal with a monospaced font. You will observe that Japanese requires 18 columns to be printed. This measure is very useful if we would like to know the length of a string to position it efficiently.
The getCharWidth
method is different of getWidth
because it includes control characters. This method is intended to be used,
for example, with terminals (please, see the
Hoa\Console
library).
Finally, if this time we are not interested by Unicode characters but
rather by machine characters char
(being
1 byte), we have an extra operation. The
Hoa\String\String::getBytesLength
method will count the
length of the string in bytes:
var_dump(
$arabic->getBytesLength(),
$japanese->getBytesLength()
);
/**
* Will output:
* int(8)
* int(27)
*/
If we compare these results with the ones of the
Hoa\String\String::count
method, we understand that the Arabic
characters are encoded with 2 bytes whereas Japanese characteres are encoded
with 3 bytes. We can also get a specific byte thanks to the
Hoa\String\String::getByteAt
method. Once again, the index is not
bounded.
Each character is represented by an integer, called a
code-point. To get the code-point of a character, we can
use the Hoa\String\String::toCode
static method, and to get a
character based on its code-point, we can use the
Hoa\String\String::fromCode
static method. We also have the
Hoa\String\String::toBinaryCode
method which returns the binary
representation of a character. Let's take an example:
var_dump(
Hoa\String\String::toCode('Σ'),
Hoa\String\String::toBinaryCode('Σ'),
Hoa\String\String::fromCode(0x1a9)
);
/**
* Will output:
* int(931)
* string(32) "1100111010100011"
* string(2) "Σ"
*/
The Hoa\String
library provides sophisticated
search algorithms on strings through the
Hoa\String\Search
class.
We will study the Hoa\String\Search::approximated
algorithm
which searches a sub-string in a string up to k
differences (a difference is an addition, a deletion or a
modification). Let's take the classical example of a DNA representation: We
will search all the sub-strings approximating GATAA
with
1 difference (maximum) in CAGATAAGAGAA
. So, we will write:
$x = 'GATAA';
$y = 'CAGATAAGAGAA';
$k = 1;
$search = Hoa\String\Search::approximated($y, $x, $k);
$n = count($search);
echo 'Try to match ', $x, ' in ', $y, ' with at most ', $k, ' difference(s):', "\n";
echo $n, ' match(es) found:', "\n";
foreach ($search as $position) {
echo ' • ', substr($y, $position['i'], $position['l'), "\n";
}
/**
* Will output:
* Try to match GATAA in CAGATAAGAGAA with at most 1 difference(s):
* 4 match(es) found:
* • AGATA
* • GATAA
* • ATAAG
* • GAGAA
*/
This methods returns an array of arrays. Each sub-array represents a result
and contains three indexes: i
for the position of the first
character (byte) of the result, j
for the position of the last
character and l
for the length of the result (simply
j
- i
). Thus, we can compute the results by using
our initial string (here $y
) and its
indexes.
With our example, we have four results. The first is AGATA
,
being GATAA
with one moved character, and
AGATA
exists in CAGATAAGAGAA
. The second
result is GATAA
, our sub-string, which well and truly exists in
CAGATAAGAGAA
. The third result is ATAAG
,
being GATAA
with one moved character, and
ATAAG
exists in CAGATAAGAGAA
. Finally, the
last result is GAGAA
, being GATAA
with one
modified character, and GAGAA
exists in
CAGATAAGAGAA
.
Another example, more concrete this time. We will consider the
--testIt --foobar --testThat --testAt
string (which represents
possible options of a command line), and we will search --testot
,
an option that should have been given by the user. This option does not exist
as it is. We will then use our search algorithm with at most 1 difference.
Let's see:
$x = 'testot';
$y = '--testIt --foobar --testThat --testAt';
$k = 1;
$search = Hoa\String\Search::approximated($y, $x, $k);
$n = count($search);
// …
/**
* Will output:
* Try to match testot in --testIt --foobar --testThat --testAt with at most 1 difference(s)
* 2 match(es) found:
* • testIt
* • testAt
*/
The testIt
and testAt
results are true options,
so we can suggest them to the user. This is a mechanism user by
Hoa\Console
to suggest corrections to the user in case of a
mistyping.
The Hoa\String
library provides facilities to manipulate
strings encoded with the Unicode format, but also to make sophisticated search
on strings.
Les chaînes de caractères peuvent parfois être complexes,
particulièrement lorsqu'elles utilisent l'encodage Unicode.
La bibliothèque Hoa\String
propose plusieurs opérations sur des
chaînes de caractères UTF-8.
Lorsque nous manipulons des chaînes de caractères, le format
Unicode s'impose par sa
compatibilité avec les formats de base historiques (comme
ASCII) et par sa grande capacité à comprendre une très large
plage de caractères et de symboles, pour toutes les cultures et toutes les
régions de notre monde. PHP propose plusieurs outils pour manipuler de telles
chaînes, comme les extensions
mbstring
,
iconv
ou encore l'excellente
intl
qui se base sur
ICU, l'implémentation de référence
d'Unicode. Malheureusement, il faut parfois mélanger ces extensions pour
arriver à nos fins et au prix d'une certaine complexité et
d'une verbosité regrettable.
La bibliothèque Hoa\String
répond à ces problématiques en
proposant une façon simple de manipuler des chaînes de
caractères, de manière performante et
efficace. Elle propose également des algorithmes évolués pour
des opérations de recherche sur des chaînes de
caractères.
La classe Hoa\String\String
représente une chaîne de
caractères Unicode UTF-8 et permet de la manipuler
facilement. Elle implémente les interfaces
ArrayAccess
,
Countable
et
IteratorAggregate
.
Nous allons utiliser trois exemples dans trois langues différentes : français,
arabe et japonais. Ainsi :
$french = new Hoa\String\String('Je t\'aime');
$arabic = new Hoa\String\String('أحبك');
$japanese = new Hoa\String\String('私はあなたを愛して');
Maintenant, voyons les opérations possibles sur ces trois chaînes.
Commençons par les opérations élémentaires. Si nous
voulons compter le nombre de caractères (et non pas
d'octets), nous allons utiliser la fonction
count
de PHP. Ainsi :
var_dump(
count($french),
count($arabic),
count($japanese)
);
/**
* Will output:
* int(9)
* int(4)
* int(9)
*/
Quand nous parlons de position sur un texte, il n'est pas adéquat de parler
de droite ou de gauche, mais plutôt de début ou de
fin, et cela à partir de la direction (sens
d'écriture) du texte. Nous pouvons connaître cette direction grâce à la
méthode Hoa\String\String::getDirection
. Elle retourne la valeur
d'une des constantes suivantes :
Hoa\String\String::LTR
, pour
left-to-right, si le texte s'écrit de gauche à
droite ;Hoa\String\String::RTL
, pour
right-to-left, si le texte s'écrit de droite à
gauche.Observons le résultat sur nos exemples :
var_dump(
$french->getDirection() === Hoa\String\String::LTR, // is left-to-right?
$arabic->getDirection() === Hoa\String\String::RTL, // is right-to-left?
$japanese->getDirection() === Hoa\String\String::LTR // is left-to-right?
);
/**
* Will output:
* bool(true)
* bool(true)
* bool(true)
*/
Le résultat de cette méthode est calculé grâce à la méthode statique
Hoa\String\String::getCharDirection
qui calcule la direction d'un
seul caractère.
Si nous voulons concaténer une autre chaîne à la fin ou au
début, nous utiliserons respectivement les méthodes
Hoa\String\String::append
et
Hoa\String\String::prepend
. Ces méthodes, comme la plupart de
celles qui modifient la chaîne, retournent l'objet lui-même, ce afin de
chaîner les appels. Par exemple :
echo $french->append('… et toi, m\'aimes-tu ?')->prepend('Mam\'zelle ! ');
/**
* Will output:
* Mam'zelle ! Je t'aime… et toi, m'aimes-tu ?
*/
Nous avons également les méthodes
Hoa\String\String::toLowerCase
et
Hoa\String\String::toUpperCase
pour, respectivement, mettre la
chaîne en minuscules ou en majuscules. Par
exemple :
echo $french->toUpperCase();
/**
* Will output:
* MAM'ZELLE ! JE T'AIME… ET TOI, M'AIMES-TU ?
*/
Nous pouvons aussi ajouter des caractères en début ou en fin de chaîne pour
atteindre une taille minimum. Cette opération est plus
couramment appelée le padding (pour des raisons historiques
remontant aux machines à écrire). C'est pourquoi nous trouvons la méthode
Hoa\String\String::pad
qui prend trois arguments : la taille
minimum, les caractères à ajouter et une constante indiquant si nous devons
ajouter en fin ou en début de chaîne (respectivement
Hoa\String\String::END
, par défaut, et
Hoa\String\String::BEGINNING
).
echo $arabic->pad(20, ' ');
/**
* Will output:
* أحبك
*/
Une opération similairement inverse permet de supprimer, par défaut, les
espaces en début et en fin de chaîne grâce à la méthode
Hoa\String\String::trim
. Par exemple, pour revenir à notre chaîne
arabe originale :
echo $arabic->trim();
/**
* Will output:
* أحبك
*/
Si nous voulons supprimer d'autres caractères, nous pouvons utiliser son
premier argument qui doit être une expression régulière. Enfin, son second
argument permet de préciser de quel côté nous voulons supprimer les
caractères : en début, en fin ou les deux, toujours en utilisant les
constantes Hoa\String\String::BEGINNING
et
Hoa\String\String::END
. Nous pouvons combiner ces constantes
pour exprimer « les deux côtés », ce qui est la valeur par défaut :
Hoa\String\String::BEGINNING |
Hoa\String\String::END
. Par exemple, pour supprimer tous les nombres et
les espaces uniquement à la fin, nous écrirons :
$arabic->trim('\s|\d', Hoa\String\String::END);
Nous pouvons également réduire la chaîne à une
sous-chaîne en précisant la position du premier caractère
puis la taille de la sous-chaîne à la méthode
Hoa\String\String::reduce
:
echo $french->reduce(3, 6)->reduce(2, 4);
/**
* Will output:
* aime
*/
Si nous voulons obtenir un caractère en particulier, nous pouvons exploiter
l'interface ArrayAccess
. Par exemple, pour obtenir le premier
caractère de chacun de nos exemples (en les reprenant depuis le début) :
var_dump(
$french[0],
$arabic[0],
$japanese[0]
);
/**
* Will output:
* string(1) "J"
* string(2) "أ"
* string(3) "私"
*/
Si nous voulons le dernier caractère, nous utiliserons l'index -1. L'index n'est pas borné à la taille de la chaîne. Si jamais l'index dépasse cette taille, alors un modulo sera appliqué.
Nous pouvons aussi modifier ou supprimer un caractère précis avec cette méthode. Par exemple :
$french->append(' ?');
$french[-1] = '!';
echo $french;
/**
* Will output:
* Je t'aime !
*/
Une autre méthode fort utile est la transformation en ASCII. Attention, ce n'est pas toujours possible, selon votre installation. Par exemple :
$title = new Hoa\String\String('Un été brûlant sur la côte');
echo $title->toAscii();
/**
* Will output:
* Un ete brulant sur la cote
*/
Nous pouvons aussi transformer de l'arabe ou du japonais vers de l'ASCII. Les symboles, comme les symboles Mathématiques ou les emojis, sont aussi transformés :
$emoji = new Hoa\String\String('I ❤ Unicode');
$maths = new Hoa\String\String('∀ i ∈ ℕ');
echo
$arabic->toAscii(), "\n",
$japanese->toAscii(), "\n",
$emoji->toAscii(), "\n",
$maths->toAscii(), "\n";
/**
* Will output:
* ahbk
* sihaanatawo aishite
* I (heavy black heart)️ Unicode
* (for all) i (element of) N
*/
Pour que cette méthode fonctionne correctement, il faut que l'extension
intl
soit présente, pour que la
classe Transliterator
existe. Si elle n'existe pas, la classe
Normalizer
doit exister.
Si cette classe n'existe pas non plus, la méthode
Hoa\String\String::toAscii
peut quand même essayer une
transformation mais moins efficace. Pour cela, il faut passer
true
en seul argument. Ce tour de force est déconseillé dans la
plupart des cas.
Nous trouvons également la méthode getTransliterator
qui
retourne un objet Transliterator
, ou null
si cette
classe n'existe pas. Cette méthode prend en argument un identifiant de
translitération. Nous conseillons de
lire la
documentation sur le translitérateur d'ICU pour comprendre cet
identifiant. La méthode transliterate
permet de translitérer la
chaîne courante à partir d'un identifiant et d'un index de début et de
fin. Elle fonctionne de la même façon que la méthode
Transliterator::transliterate
.
Plus généralement, pour des changements d'encodage brut,
nous pouvons utiliser la méthode statique
Hoa\String\String::transcode
, avec en premier argument une chaîne
de caractères, en deuxième argument l'encodage d'origine et en dernier
argument l'encodage final souhaité (par défaut UTF-8). Pour la liste des
encodages, il faut se reporter à l'extension
iconv
ou entrer la commande
suivante dans un terminal :
$ iconv --list
Pour savoir si une chaîne est encodée en UTF-8, nous pouvons utiliser la
méthode statique Hoa\String\String::isUtf8
; par exemple :
var_dump(
Hoa\String\String::isUtf8('a'),
Hoa\String\String::isUtf8(Hoa\String\String::transcode('a', 'UTF-8', 'UTF-16'))
);
/**
* Will output:
* bool(true)
* bool(false)
*/
Nous pouvons éclater la chaîne en plusieurs sous-chaînes
en utilisant la méthode Hoa\String\String::split
. En premier
argument, nous avons une expression régulière (type
PCRE), puis un entier représentant le nombre
maximum d'éléments à retourner et enfin une combinaison de constantes. Ces
constantes sont les mêmes que celles de
preg_split
.
Par défaut, le deuxième argument vaut -1, qui symbolise l'infini, et le
dernier argument vaut PREG_SPLIT_NO_EMPTY
. Ainsi, si nous
voulons obtenir tous les mots d'une chaîne, nous écrirons :
print_r($title->split('#\b|\s#'));
/**
* Will output:
* Array
* (
* [0] => Un
* [1] => ete
* [2] => brulant
* [3] => sur
* [4] => la
* [5] => cote
* )
*/
Si nous voulons itérer sur tous les
caractères, il est préférable d'exploiter l'interface
IteratorAggregate
, soit la méthode
Hoa\String\String::getIterator
. Voyons plutôt sur l'exemple en
arabe :
foreach ($arabic as $letter) {
echo $letter, "\n";
}
/**
* Will output:
* أ
* ح
* ب
* ك
*/
Nous remarquons que l'itération se fait suivant la direction du texte, c'est à dire que le premier élément de l'itération est la première lettre de la chaîne en partant du début.
Bien sûr, si nous voulons obtenir un tableau des caractères, nous pouvons
utiliser la fonction
iterator_to_array
de PHP :
print_r(iterator_to_array($arabic));
/**
* Will output:
* Array
* (
* [0] => أ
* [1] => ح
* [2] => ب
* [3] => ك
* )
*/
Les chaînes peuvent également être comparées entre elles
grâce à la méthode Hoa\String\String::compare
:
$string = new Hoa\String\String('abc');
var_dump(
$string->compare('wxyz')
);
/**
* Will output:
* string(-1)
*/
Cette méthode retourne -1 si la chaîne initiale vient avant (par ordre
alphabétique), 0 si elle est identique et 1 si elle vient après. Si nous
voulons utiliser la pleine
puissance du mécanisme sous-jacent, nous pouvons appeler la méthode statique
Hoa\String\String::getCollator
(si la classe
Collator
existe, sinon
Hoa\String\String::compare
utilisera une comparaison simple
octet par octets sans tenir compte d'autres paramètres). Ainsi, si nous
voulons trier un tableau de chaînes, nous écrirons plutôt :
$strings = array('c', 'Σ', 'd', 'x', 'α', 'a');
Hoa\String\String::getCollator()->sort($strings);
print_r($strings);
/**
* Could output:
* Array
* (
* [0] => a
* [1] => c
* [2] => d
* [3] => x
* [4] => α
* [5] => Σ
* )
*/
La comparaison entre deux chaînes dépend de la locale,
c'est à dire de la régionalisation du système, comme la langue, le pays, la
région etc. Nous pouvons utiliser la
bibliothèque Hoa\Locale
pour modifier ces données, mais ce
n'est pas une dépendance de Hoa\String
pour autant.
Nous pouvons également savoir si une chaîne correspond à
un certain motif, toujours exprimé avec une expression régulière. Pour cela,
nous allons utiliser la méthode Hoa\String\String::match
. Cette
méthode repose sur les fonctions
preg_match
et
preg_match_all
de
PHP, mais en modifiant les options du motif afin qu'il supporte Unicode. Nous
avons les paramètres suivants : le motif, une variable par référence pour
récupérer les captures, les flags, la position de début de
recherche (offset) et enfin un booléen indiquant si la
recherche est globale ou non (respectivement si nous devons utiliser
preg_match_all
ou preg_match
). Par défaut, la
recherche n'est pas globale.
Ainsi, nous allons vérifier que notre exemple en français contient bien
aime
avec son complément d'objet direct :
$french->match('#(?:(?<direct_object>\w)[\'\b])aime#', $matches);
var_dump($matches['direct_object']);
/**
* Will output:
* string(1) "t"
*/
Cette méthode retourne false
si une erreur est survenue (par
exemple si le motif n'est pas correct), 0 si aucune correspondance n'a été
trouvée, le nombre de correspondances trouvées sinon.
Similairement, nous pouvons chercher et
remplacer des sous-chaînes par d'autres sous-chaînes suivant
un motif, toujours exprimé avec une expression régulière. Pour cela, nous
allons utiliser la méthode Hoa\String\String::replace
. Cette
méthode repose sur les fonctions
preg_replace
et
preg_replace_callback
de PHP, mais toujours en modifiant les options du motif afin qu'il supporte
Unicode. En premier argument, nous trouvons le ou les motifs, en deuxième
argument, le ou les remplacements et en dernier argument la limite de
remplacements à faire. Si le remplacement est un callable,
alors la fonction preg_replace_callback
sera utilisée.
Ainsi, nous allons modifier notre exemple français pour qu'il soit plus poli :
$french->replace('#(?:\w[\'\b])(?<verb>aime)#', function ($matches) {
return 'vous ' . $matches['verb'];
});
echo $french;
/**
* Will output:
* Je vous aime
*/
La classe Hoa\String\String
propose des constantes qui sont des
aliases de constantes PHP et qui permettent une meilleure lecture du code:
Hoa\String\String::WITHOUT_EMPTY
, alias de
PREG_SPLIT_NO_EMPTY
;Hoa\String\String::WITH_DELIMITERS
, alias de
PREG_SPLIT_DELIM_CAPTURE
;Hoa\String\String::WITH_OFFSET
, alias de
PREG_OFFSET_CAPTURE
et
PREG_SPLIT_OFFSET_CAPTURE
;Hoa\String\String::GROUP_BY_PATTERN
, alias de
PREG_PATTERN_ORDER
;Hoa\String\String::GROUP_BY_TUPLE
, alias de
PREG_SET_ORDER
.Comme ce sont des aliases stricts, nous pouvons écrire :
$string = new Hoa\String\String('abc1 defg2 hikl3 xyz4');
$string->match(
'#(\w+)(\d)#',
$matches,
Hoa\String\String::WITH_OFFSET
| Hoa\String\String::GROUP_BY_TUPLE,
0,
true
);
La classe Hoa\String\String
offre des méthodes statiques
travaillant sur un seul caractère Unicode. Nous avons déjà évoqué la méthode
getCharDirection
qui permet de connaître la
direction d'un caractère. Nous trouvons aussi
getCharWidth
qui calcule le nombre de colonnes
nécessaires pour l'affichage d'un seul caractère. Ainsi :
var_dump(
Hoa\String\String::getCharWidth(Hoa\String\String::fromCode(0x7f)),
Hoa\String\String::getCharWidth('a'),
Hoa\String\String::getCharWidth('㽠')
);
/**
* Will output:
* int(-1)
* int(1)
* int(2)
*/
Cette méthode retourne -1 ou 0 si le caractère n'est pas
imprimable (par exemple si c'est un caractère de contrôle,
comme 0x7f
qui correspond à DELETE
), 1 ou plus si
c'est un caractère qui peut être imprimé. Dans notre exemple, 㽠
s'imprime sur 2 colonnes.
Pour plus de sémantique, nous avons accès à la méthode
Hoa\String\String::isCharPrintable
qui permet de savoir si un
caractère est imprimable ou pas.
Si nous voulons calculer le nombre de colonnes pour tout une chaîne, il
faut utiliser la méthode Hoa\String\String::getWidth
.
Ainsi :
var_dump(
$french->getWidth(),
$arabic->getWidth(),
$japanese->getWidth()
);
/**
* Will output:
* int(9)
* int(4)
* int(18)
*/
Essayez dans un terminal avec une police mono-espacée. Vous verrez que le japonais demande 18 colonnes pour s'afficher. Cette mesure est très utile si nous voulons connaître la largeur d'une chaîne pour la positionner correctement.
La méthode getCharWidth
est différente de
getWidth
car elle prend en compte des caractères de contrôles.
Elle est destinée à être utilisée, par exemple, avec des terminaux (voir
la bibliothèque
Hoa\Console
).
Enfin, si cette fois nous ne nous intéressons pas aux caractères Unicode
mais aux caractères machines char
(soit 1
octet), nous avons une opération supplémentaire. La méthode
Hoa\String\String::getBytesLength
va compter la
taille de la chaîne en octets :
var_dump(
$arabic->getBytesLength(),
$japanese->getBytesLength()
);
/**
* Will output:
* int(8)
* int(27)
*/
Si nous comparons ces résultats avec ceux de la méthode
Hoa\String\String::count
, nous comprenons que les caractères
arabes sont encodés sur 2 octets alors que les caractères japonais sont
encodés sur 3 octets. Nous pouvons également obtenir un octet précis à l'aide
de la méthode Hoa\String\String::getByteAt
. Encore une fois,
l'index n'est pas borné.
Chaque caractère est représenté en machine par un entier, appelé
code-point. Pour obtenir le code-point d'un caractère, nous
pouvons utiliser la méthode statique Hoa\String\String::toCode
,
et pour obtenir un caractère à partir d'un code, nous pouvons utiliser la
méthode statique Hoa\String\String::fromCode
. Nous avons aussi la
méthode statique Hoa\String\String::toBinaryCode
qui retourne la
représentation sous forme binaire d'un caractère. Prenons un exemple :
var_dump(
Hoa\String\String::toCode('Σ'),
Hoa\String\String::toBinaryCode('Σ'),
Hoa\String\String::fromCode(0x1a9)
);
/**
* Will output:
* int(931)
* string(32) "1100111010100011"
* string(2) "Σ"
*/
La bibliothèque Hoa\String
propose des algorithmes de
recherches sophistiquées sur les chaînes de caractères à
travers la classe Hoa\String\Search
.
Nous allons étudier l'algorithme
Hoa\String\Search::approximated
qui fait une recherche d'une
sous-chaîne dans une chaîne avec au maximum k
différences (une différence étant une insertion, une délétion ou une
modification). Prenons un exemple classique avec une représentation
ADN : nous allons chercher toutes les sous-chaînes s'approchant de
GATAA
à 1 différence près (au maximum) dans
CAGATAAGAGAA
. Pour cela, nous allons donc écrire :
$x = 'GATAA';
$y = 'CAGATAAGAGAA';
$k = 1;
$search = Hoa\String\Search::approximated($y, $x, $k);
$n = count($search);
echo 'Try to match ', $x, ' in ', $y, ' with at most ', $k, ' difference(s):', "\n";
echo $n, ' match(es) found:', "\n";
foreach ($search as $position) {
echo ' • ', substr($y, $position['i'], $position['l'), "\n";
}
/**
* Will output:
* Try to match GATAA in CAGATAAGAGAA with at most 1 difference(s):
* 4 match(es) found:
* • AGATA
* • GATAA
* • ATAAG
* • GAGAA
*/
Cette méthode retourne un tableau de tableaux. Chaque sous-tableau
représente un résultat et contient trois indexes : i
pour la
position du premier caractère (octet) du résultat, j
pour la
position du dernier caractère et l
pour la taille du résultat
(tout simplement j
- i
).
Ainsi, nous pouvons calculer les résultats en utilisant notre chaîne initiale
(ici $y
) et ces indexes.
Avec notre exemple, nous avons quatre résultats. Le premier est
AGATA
, soit GATAA
avec un caractère
déplacé, et AGATA
existe bien dans
CAGATAAGAGAA
. Le deuxième résultat est
GATAA
, notre sous-chaîne, qui existe bel et bien dans
CAGATAAGAGAA
. Le troisième résultat est
ATAAG
, soit GATAA
avec un caractère
déplacé, et ATAAG
existe bien dans
CAGATAAGAGAA
. Enfin, le dernier résultat est
GAGAA
, soit GATAA
avec un caractère
modifié, et GAGAA
existe bien dans
CAGATAAGAGAA
.
Prenons un autre exemple, plus concret cette fois-ci. Nous allons
considérer la chaîne --testIt --foobar --testThat --testAt
(qui
représente les options possibles d'une ligne de commande), et nous allons
chercher --testot
, une option qu'aurait pu donner
l'utilisateur. Cette option n'existe pas telle quelle. Nous allons donc
utiliser notre algorithme de recherche avec 1 différence au maximum. Voyons
plutôt :
$x = 'testot';
$y = '--testIt --foobar --testThat --testAt';
$k = 1;
$search = Hoa\String\Search::approximated($y, $x, $k);
$n = count($search);
// …
/**
* Will output:
* Try to match testot in --testIt --foobar --testThat --testAt with at most 1 difference(s)
* 2 match(es) found:
* • testIt
* • testAt
*/
Les résultats testIt
et testAt
sont des vraies
options, donc nous pouvons les proposer à l'utilisateur. C'est un mécanisme
utilisé par Hoa\Console
pour proposer des corrections à
l'utilisateur s'il se trompe.
La bibliothèque Hoa\String
propose des facilités pour
manipuler des chaînes encodées au format Unicode, mais aussi pour effectuer
des recherches sophistiquées sur des chaînes.