Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

traitement xml : caracteres interdits ?

14 réponses
Avatar
Olivier Masson
Bonjour,

Il me semblait que l'utilisation de section CDATA permettait de
s'abstraire des problèmes de caractères utilisés.
Or, j'ai une erreur avec simple_xml_file, apparemment dûe à un caractère
étrange ($001D).
Je n'ai pas trouvé les limites de cette commande dans la doc php.
Avez-vous davantage d'infos ?
Merci.

10 réponses

1 2
Avatar
Olivier Miakinen
Bonjour Olivier,

Le 05/01/2011 23:03, Olivier Masson a écrit :

Il me semblait que l'utilisation de section CDATA permettait de
s'abstraire des problèmes de caractères utilisés.
Or, j'ai une erreur avec simple_xml_file, apparemment dûe à un caractère
étrange ($001D).
Je n'ai pas trouvé les limites de cette commande dans la doc php.
Avez-vous davantage d'infos ?



Ce que je comprends de CDATA, c'est qu'il permet de s'abstraire non pas
des problèmes de charset, mais des problèmes de caractères réservés (en
particulier « & » et « < »).

Il ne doit pas être un moyen d'inclure des séquences d'octets mal
formées ou « overlong » en UTF-8, ni des caractères invalides. Or
la liste de caractères Unicode autorisée dans un document XML est
la suivante :

<cit. http://www.w3.org/TR/REC-xml/#charsets&gt;
Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
[#x10000-#x10FFFF]
</cit.>

Le caractère #x1D (séparateur de groupe en ascii) n'en fait pas partie.


Cordialement,
--
Olivier Miakinen
Avatar
Olivier Masson
Le 05/01/2011 23:32, Olivier Miakinen a écrit :

<cit. http://www.w3.org/TR/REC-xml/#charsets&gt;
Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
[#x10000-#x10FFFF]
</cit.>

Le caractère #x1D (séparateur de groupe en ascii) n'en fait pas partie.


Cordialement,



C'est vrai que j'aurais dû me référer en premier lieu à la doc du W3C,
pas de PHP.
Merci pour ta réponse.

Mais... comment ça se corrige ? Côté PHP, il n'y a rien qui puisse
permettre de filtrer ça rapidement ?
Avatar
Olivier Miakinen
Le 06/01/2011 11:36, Olivier Masson a écrit :

<cit. http://www.w3.org/TR/REC-xml/#charsets&gt;
Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
[#x10000-#x10FFFF]
</cit.>

Le caractère #x1D (séparateur de groupe en ascii) n'en fait pas partie.



[...] comment ça se corrige ? Côté PHP, il n'y a rien qui puisse
permettre de filtrer ça rapidement ?



Je ne connais pas de fonction qui fasse ça -- ce qui ne veut pas dire
que ça n'existe pas, juste que je n'en connais pas ; et comme en ce
moment mon proxy web rame que c'en est à pleurer, je ne peux pas
chercher dans la doc.

Voici juste quelques idées si jamais ça n'existait pas (je suppose que
ton Unicode est en UTF-8, adapter la réponse sinon) :
- essayer un iconv de UTF-8 vers UTF-8 avec IGNORE ;
- essayer avec preg_replace en mode UTF-8 ;
- ou tout simplement faire un preg_replace en mode monobyte, si les
seuls caractères problématiques sont ceux de #x00-#1F.
Avatar
Olivier Miakinen
[diapublication avec suivi]

Le 05/01/2011 23:32, j'écrivais sur fclp :

<cit. http://www.w3.org/TR/REC-xml/#charsets&gt;
Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
[#x10000-#x10FFFF]
</cit.>



Il est à noter que le code HTML de cette doc officielle du W3C va un
peu à contre-courant des recommandations habituelles concernant les
bonnes habitudes en HTML :

<table class="scrap" summary="Scrap"><tbody><tr valign="baseline"><td><a
name="NT-Char"
id="NT-Char">[2]&nbsp;&nbsp;&nbsp;</a></td><td><code>Char</code></td><td>&nbsp;&nbsp;&nbsp;::=&nbsp;&nbsp;&nbsp;</td><td><code>#x9
| #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
[#x10000-#x10FFFF]</code></td><td><i>/* any Unicode character, excluding
the surrogate blocks, FFFE, and FFFF. */</i></td></tr></tbody></table>

Le même un peu mis en forme :

<table class="scrap" summary="Scrap">
<tbody>
<tr valign="baseline">
<td><a name="NT-Char" id="NT-Char">[2]&nbsp;&nbsp;&nbsp;</a></td>
<td><code>Char</code></td>
<td>&nbsp;&nbsp;&nbsp;::=&nbsp;&nbsp;&nbsp;</td>
<td><code>#x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
[#x10000-#x10FFFF]</code></td>
<td><i>/* any Unicode character, excluding the surrogate blocks,
FFFE, and FFFF. */</i></td>
</tr>
</tbody>
</table>

:-D

[suivi positionné vers fr.comp.infosystemes.www.auteurs]

--
Olivier Miakinen
Avatar
Olivier Masson
Le 06/01/2011 12:05, Olivier Miakinen a écrit :

Le même un peu mis en forme :

<table class="scrap" summary="Scrap">
<tbody>
<tr valign="baseline">
<td><a name="NT-Char" id="NT-Char">[2]&nbsp;&nbsp;&nbsp;</a></td>
<td><code>Char</code></td>
<td>&nbsp;&nbsp;&nbsp;::=&nbsp;&nbsp;&nbsp;</td>
<td><code>#x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
[#x10000-#x10FFFF]</code></td>
<td><i>/* any Unicode character, excluding the surrogate blocks,
FFFE, and FFFF. */</i></td>
</tr>
</tbody>
</table>

:-D

[suivi positionné vers fr.comp.infosystemes.www.auteurs]




Oulala ! En effet, du valign, du name, du i, une table pas vraiment
justifiée... la honte :)
Avatar
Olivier Masson
Le 06/01/2011 12:05, Olivier Miakinen a écrit :

Voici juste quelques idées si jamais ça n'existait pas (je suppose que
ton Unicode est en UTF-8, adapter la réponse sinon) :
- essayer un iconv de UTF-8 vers UTF-8 avec IGNORE ;
- essayer avec preg_replace en mode UTF-8 ;
- ou tout simplement faire un preg_replace en mode monobyte, si les
seuls caractères problématiques sont ceux de #x00-#1F.



Merci, iconv me semble une bonne solution.
Je n'ai pas la main sur le fichier a traiter, donc a priori pas idée de
ce qu'il pourrait contenir comme étrangetés.
Avatar
Olivier Miakinen
Le 06/01/2011 12:54, Olivier Masson a écrit :

- essayer un iconv de UTF-8 vers UTF-8 avec IGNORE ;



Merci, iconv me semble une bonne solution.



L'as-tu testé ? Cela pourrait fonctionner, mais ça pourrait tout aussi
bien ne rien donner, pour l'une ou l'autre des multiples raisons
suivantes :
- une optimisation faisant que la conversion UTF-8 vers UTF-8 ne fait
tout simplement rien ;
- un refus de traiter les caractères invalides (ce qui est différent
de ne pas pouvoir les transcrire dans un autre charset) ;
- considérer que le caractère U+001D est valide dans Unicode, bien qu'il
ne le soit pas pour XML ;
- traiter le IGNORE en laissant les caractères inchangés au lieu de les
supprimer ;
- et peut-être d'autres encore.

Bref, moi je n'ai pas testé mais je n'ai qu'une confiance limitée dans
le résultat.


Cordialement,
--
Olivier Miakinen
Avatar
Pascal
Le 06/01/2011 16:13, Olivier Miakinen a écrit :
- essayer un iconv de UTF-8 vers UTF-8 avec IGNORE ;







Apparemment "inconv" ne résout pas le problème.
J'ai même essayé une conversion en UTF-32, puis retour en UTF-8, idem.
Pas de solution non plus avec "mb_convert_encoding()", d'après mes essais.

Par contre, je crois avoir trouvé une solution avec la bibliothèque Tidy
(libtidy), qui traite aussi les documents XML.

Réf. Tidy: [http://tidy.sourceforge.net/]
Réf. PHP : [http://fr.php.net/manual/en/book.tidy.php]

Voici le script correspondant à mon essai :

<?php

header("Content-Type: text/plain; charset=utf-8");

$xmlString = <<<XML
<?xml version="1.0" encoding="UTF-8" ?>
<root>
<title>Example of <![CDATA[Special Chars: ()(ᴀ)]]></title>
</root>
XML;

$tidyConfig = array(
'input-xml' => TRUE,
'output-xml' => TRUE
);
$xmlString = tidy_repair_string($xmlString, $tidyConfig);

$xml = simplexml_load_string($xmlString);
echo $xml->title;

?>

Je ne sais pas ce que donneront, à l'affichage dans le newsgroup, les
caractères que j'ai placé entre parenthèses après "Special Chars".
Pour information, ils correspondent aux codes #x1D et #x1D00.


--
Cordialement,
Pascal
Avatar
Olivier Masson
Le 06/01/2011 16:13, Olivier Miakinen a écrit :
Le 06/01/2011 12:54, Olivier Masson a écrit :

- essayer un iconv de UTF-8 vers UTF-8 avec IGNORE ;



Merci, iconv me semble une bonne solution.



L'as-tu testé ? Cela pourrait fonctionner, mais ça pourrait tout aussi
bien ne rien donner, pour l'une ou l'autre des multiples raisons
suivantes :
- une optimisation faisant que la conversion UTF-8 vers UTF-8 ne fait
tout simplement rien ;
- un refus de traiter les caractères invalides (ce qui est différent
de ne pas pouvoir les transcrire dans un autre charset) ;
- considérer que le caractère U+001D est valide dans Unicode, bien qu'il
ne le soit pas pour XML ;
- traiter le IGNORE en laissant les caractères inchangés au lieu de les
supprimer ;
- et peut-être d'autres encore.

Bref, moi je n'ai pas testé mais je n'ai qu'une confiance limitée dans
le résultat.


Cordialement,



Résultat des courses :

Pour un fichier de 300ko, les temps de traitement (seconde) :
Tidy : 0.108
iconv : 0.008
preg : 0.031

Maintenant qui fonctionne ?

- *Tidy* : ok, MAIS j'ai toujours eu tendance à détester tidy,
probablement parce que je ne le connais pas suffisamment et que sa
config par défaut massacre tout. Attention Pascal, il manque toutefois
un traitement utf8 à ton exemple.

Code utilisé :
$tidyConfig = array(
'input-xml' => TRUE,
'output-xml' => TRUE,
'input-encoding' => UTF8,
'output-encoding' => UTF8
);
$xml = tidy_repair_string($xmlString, $tidyConfig);

Comme je n'ai pas besoin d'autres nettoyages, tidy en fait trop par
défaut (il faudrait donc mettre toute la config à FALSE)


- *iconv* : ko, il laisse en place ce qui ne va pas (mais c'est normal
finalement puisque, comme tu le dis justement, U+001D est valide).

Code utilisé :
$xml = iconv("UTF-8", "UTF-8//IGNORE", $xmlString);


- *preg* : ok et, finalement, c'est la solution que je retiens puisque
c'est suffisamment rapide et, surtout, ça me permet de remplacer par un
espace plutôt que de supprimer ce qui pose problème (et c'est un
comportement souhaitable dans mon cas).

Code utilisé :
$xml =
preg_replace('/[^x{9}|x{A}|x{D}|x{20}-x{D7FF}|x{E000}-x{FFFD}|x{10000}-x{10FFFF}]+/u',
' ', $xmlString);


Merci pour vos interventions.
Avatar
Olivier Miakinen
Bonjour, et merci pour ce retour d'expérience si détaillé, incluant le
code testé à chaque fois. Ce genre de chose est très utile pour ne pas
répéter les mêmes erreurs.

Le 07/01/2011 16:50, Olivier Masson a écrit :

<cit. http://www.w3.org/TR/REC-xml/#charsets&gt;
Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
[#x10000-#x10FFFF]
</cit.>



Code utilisé :
$xml =
preg_replace('/[^x{9}|x{A}|x{D}|x{20}-x{D7FF}|x{E000}-x{FFFD}|x{10000}-x{10FFFF}]+/u',
' ', $xmlString);



Note que, en ayant regroupé les termes de l'alternative dans une seule
classe de caractères, tu te retrouves à tester six fois de suite le
caractère '|' !
Plus simple (et moins source d'erreur) :
'/[^x{9}x{A}x{D}x{20}-x{D7FF}x{E000}-x{FFFD}x{10000}-x{10FFFF}]+/u'

Par ailleurs, tu aurais peut-être intérêt à éliminer aussi les
caractères de commande #x7f et #x80 à #x9f, ces derniers étant
parfois produits par une mauvaise conversion de CP1252 en UTF-8 :
'/[^x{9}x{A}x{D}x{20}-x{7E}x{A0}-x{D7FF}x{E000}-x{FFFD}x{10000}-x{10FFFF}]+/u'

Tu peux éventuellement rajouter le #x85 qui est dans la norme, mais vu
que -- à ce que j'ai compris -- il est là pour permettre à EBCDIC d'être
compatible binaire avec UTF-8 (85 = espace), ça doit être plutôt une
bonne chose de le remplacer par une vraie espace ! D'autant que #x85
existe dans CP1252 et représente un caractère fréquent (les points de
suspension).

Enfin, si tu veux être à même de détecter plusieurs caractères
interdits de suite, et de remplacer chacun d'eux par une espace,
tu peux retirer le '+' de la regexp.

Cordialement,
--
Olivier Miakinen
1 2