Le problème est que ’
décode vers le caractère Unicode U+0092, UTF-8 C2 92
connu sous le nom de PRIVATE USE TWO :
$ php test.php | xxd
0000000: 5374 616e c292 73 Stan..s
C'est-à-dire que cela ne se décode pas en une apostrophe habituelle.
html_entity_decode($string)
fonctionne car il ne décode pas réellement l'entité, puisque le jeu de caractères cible par défaut est latin-1, qui ne peut pas représenter ce caractère. Si vous spécifiez UTF-8 comme jeu de caractères cible, l'entité est réellement décodée.
La cible de cette entité est le jeu de caractères Windows-1252 :
echo iconv('cp1252', 'UTF-8', html_entity_decode('Stan’s', ENT_QUOTES, 'cp1252'));
Stan’s
Citation : Wikipedia :
Les références numériques font toujours référence aux points de code Unicode, quel que soit l'encodage de la page. Il est interdit d'utiliser des références numériques faisant référence à des caractères non définis de façon permanente et à des caractères de contrôle, à l'exception des caractères de saut de ligne, de tabulation et de retour chariot. En d'autres termes, les caractères situés dans les plages hexadécimales 00-08, 0B-0C, 0E-1F, 7F et 80-9F ne peuvent pas être utilisés dans un document HTML, même par référence. ™
par exemple, n'est pas autorisé. Toutefois, pour des raisons de rétrocompatibilité avec les premiers auteurs HTML et les navigateurs qui ignoraient cette restriction, les caractères bruts et les références numériques dans la plage 80-9F sont interprétés par certains navigateurs comme représentant les caractères affectés aux octets 80-9F dans le codage Windows-1252.
Vous avez donc affaire à des entités HTML anciennes, que PHP ne gère pas de la même manière que certains navigateurs. Vous pouvez vérifier si les entités décodées sont dans la plage spécifiée ci-dessus, que vous les transcoder/redécoder en Windows-1252, puis les convertir en UTF-8. Ou exiger de vos utilisateurs qu'ils transmettent du HTML valide.
Cette fonction doit traiter les entités HTML anciennes et ordinaires :
function legacy_html_entity_decode($str, $quotes = ENT_QUOTES, $charset = 'UTF-8') {
return preg_replace_callback('/&#(\d+);/', function ($m) use ($quotes, $charset) {
if (0x80 <= $m[1] && $m[1] <= 0x9F) {
return iconv('cp1252', $charset, html_entity_decode($m[0], $quotes, 'cp1252'));
}
return html_entity_decode($m[0], $quotes, $charset);
}, $str);
}