Discussion:
traitement xml : caracteres interdits ?
(trop ancien pour répondre)
Olivier Masson
2011-01-05 22:03:44 UTC
Permalink
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.
Olivier Miakinen
2011-01-05 22:32:27 UTC
Permalink
Bonjour Olivier,
Post by Olivier Masson
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>
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
Olivier Masson
2011-01-06 10:36:26 UTC
Permalink
Post by Olivier Miakinen
<cit. http://www.w3.org/TR/REC-xml/#charsets>
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 ?
Olivier Miakinen
2011-01-06 11:05:53 UTC
Permalink
Post by Olivier Miakinen
<cit. http://www.w3.org/TR/REC-xml/#charsets>
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.
Olivier Masson
2011-01-06 11:54:39 UTC
Permalink
Post by Olivier Miakinen
Voici juste quelques idées si jamais ça n'existait pas (je suppose que
- 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.
Olivier Miakinen
2011-01-06 15:13:03 UTC
Permalink
Post by Olivier Masson
Post by Olivier Miakinen
- 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
Pascal
2011-01-06 20:05:01 UTC
Permalink
Post by Olivier Miakinen
- 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
Olivier Masson
2011-01-07 15:50:31 UTC
Permalink
Post by Olivier Miakinen
Post by Olivier Masson
Post by Olivier Miakinen
- 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
- 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.
Olivier Miakinen
2011-01-07 16:32:45 UTC
Permalink
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.
Post by Pascal
Post by Olivier Miakinen
<cit. http://www.w3.org/TR/REC-xml/#charsets>
Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
[#x10000-#x10FFFF]
</cit.>
$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
Olivier Masson
2011-01-07 18:12:27 UTC
Permalink
Post by Olivier Miakinen
'/[^\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
'/[^\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,
Merci pour ces corrections.
Le |, c'est la belle erreur du "ou" que je colle inutilement...
Très bien pour les autres caractères à éliminer.
Par contre, pour ta dernière remarque, avec le + je devrais détecter
plusieurs caractères interdits de suite mais ils seront remplacés en
groupe par un seul espace, non ? Et c'est bien ce que je souhaite (avoir
une suite d'espace dans un texte n'est pas souhaitable.)
Olivier Miakinen
2011-01-07 18:30:32 UTC
Permalink
Post by Olivier Masson
Par contre, pour ta dernière remarque, avec le + je devrais détecter
plusieurs caractères interdits de suite mais ils seront remplacés en
groupe par un seul espace, non ? Et c'est bien ce que je souhaite (avoir
une suite d'espace dans un texte n'est pas souhaitable.)
Oui, dans ce cas le + se justifie bien.
Olivier Miakinen
2011-01-07 18:12:27 UTC
Permalink
Post by Olivier Miakinen
Post by Pascal
Post by Olivier Miakinen
<cit. http://www.w3.org/TR/REC-xml/#charsets>
Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
[#x10000-#x10FFFF]
</cit.>
$xml =
preg_replace('/[^\x{9}|\x{A}|\x{D}|\x{20}-\x{D7FF}|\x{E000}-\x{FFFD}|\x{10000}-\x{10FFFF}]+/u',
' ', $xmlString);
'/[^\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]+/u'
La définition de Char dans XML 1.1 au lieu de XML 1.0, associée à la
petite note sur fond gris, me permet de proposer une autre regexp.

<cit. http://www.w3.org/TR/xml11/#charsets>
Char ::= [#x1-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]

[...]

Note:

Document authors are encouraged to avoid "compatibility characters", as
defined in Unicode [Unicode]. The characters defined in the following
ranges are also discouraged. They are either control characters or
permanently undefined Unicode characters:

[#x1-#x8], [#xB-#xC], [#xE-#x1F], [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF],
[#x1FFFE-#x1FFFF], [#x2FFFE-#x2FFFF], [#x3FFFE-#x3FFFF],
[#x4FFFE-#x4FFFF], [#x5FFFE-#x5FFFF], [#x6FFFE-#x6FFFF],
[#x7FFFE-#x7FFFF], [#x8FFFE-#x8FFFF], [#x9FFFE-#x9FFFF],
[#xAFFFE-#xAFFFF], [#xBFFFE-#xBFFFF], [#xCFFFE-#xCFFFF],
[#xDFFFE-#xDFFFF], [#xEFFFE-#xEFFFF], [#xFFFFE-#xFFFFF],
[#x10FFFE-#x10FFFF].
</cit.>


D'où :


$EXCLUDE_CHAR =
/* Control characters (including U+0000 and U+0085) */
'\x{0}-\x{8}' . '\x{B}\x{C}' . '\x{E}-\x{1F}' . '\x{7F}-\x{9F}' .

/* Surrogates */
'\x{D800}-\x{DFFF}' .

/* Non characters within Arabic Presentation Forms-A*/
'\x{FDD0}-\x{FDEF}' . /* FDEF and not FDDF, see errata */

/* Non characters *FFFE and *FFFF */
'\x{FFFE}\x{FFFF}' . '\x{1FFFE}\x{1FFFF}' . '\x{2FFFE}\x{2FFFF}' .
'\x{3FFFE}\x{3FFFF}' . '\x{4FFFE}\x{4FFFF}' . '\x{5FFFE}\x{5FFFF}' .
'\x{6FFFE}\x{6FFFF}' . '\x{7FFFE}\x{7FFFF}' . '\x{8FFFE}\x{8FFFF}' .
'\x{9FFFE}\x{9FFFF}' . '\x{AFFFE}\x{AFFFF}' . '\x{BFFFE}\x{BFFFF}' .
'\x{CFFFE}\x{CFFFF}' . '\x{DFFFE}\x{DFFFF}' . '\x{EFFFE}\x{EFFFF}' .
'\x{FFFFE}\x{FFFFF}' . '\x{10FFFE}\x{10FFFF}';

$xml = preg_replace('/[' . $EXCLUDE_CHAR . ']/u', ' ', $xmlString);
Olivier Masson
2011-01-09 21:15:18 UTC
Permalink
Post by Olivier Miakinen
$EXCLUDE_CHAR =
/* Control characters (including U+0000 and U+0085) */
'\x{0}-\x{8}' . '\x{B}\x{C}' . '\x{E}-\x{1F}' . '\x{7F}-\x{9F}' .
/* Surrogates */
'\x{D800}-\x{DFFF}' .
/* Non characters within Arabic Presentation Forms-A*/
'\x{FDD0}-\x{FDEF}' . /* FDEF and not FDDF, see errata */
/* Non characters *FFFE and *FFFF */
'\x{FFFE}\x{FFFF}' . '\x{1FFFE}\x{1FFFF}' . '\x{2FFFE}\x{2FFFF}' .
'\x{3FFFE}\x{3FFFF}' . '\x{4FFFE}\x{4FFFF}' . '\x{5FFFE}\x{5FFFF}' .
'\x{6FFFE}\x{6FFFF}' . '\x{7FFFE}\x{7FFFF}' . '\x{8FFFE}\x{8FFFF}' .
'\x{9FFFE}\x{9FFFF}' . '\x{AFFFE}\x{AFFFF}' . '\x{BFFFE}\x{BFFFF}' .
'\x{CFFFE}\x{CFFFF}' . '\x{DFFFE}\x{DFFFF}' . '\x{EFFFE}\x{EFFFF}' .
'\x{FFFFE}\x{FFFFF}' . '\x{10FFFE}\x{10FFFF}';
$xml = preg_replace('/[' . $EXCLUDE_CHAR . ']/u', ' ', $xmlString);
Ça devient énorme (comprendre également que ça va bien au-delà de ma
préoccupation de départ) mais au moins c'est générique.
Merci pour ces recherches et ce résultat.

Olivier Miakinen
2011-01-06 11:05:53 UTC
Permalink
[diapublication avec suivi]
Post by Olivier Miakinen
<cit. http://www.w3.org/TR/REC-xml/#charsets>
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
Continuer la lecture sur narkive:
Loading...