From 22d2f96406397279814ce9a2d68857ea3e525929 Mon Sep 17 00:00:00 2001 From: "Committo,Ergo:sum" <esj@rezo.net> Date: Sun, 7 Jan 2007 15:12:46 +0000 Subject: [PATCH] =?UTF-8?q?Il=20=C3=A9tait=20=C3=A9crit=20que=20la=20proch?= =?UTF-8?q?aine=20version=20de=20SPIP=20n'allait=20pas=20sortir=20avec=20u?= =?UTF-8?q?n=20validateur=20limit=C3=A9.=20Je=20suis=20nul=20de=20ne=20pas?= =?UTF-8?q?=20y=20avoir=20pens=C3=A9=20avant:=20pour=20v=C3=A9rifier=20les?= =?UTF-8?q?=20rares=20r=C3=A8gles=20plus=20complexes=20que=20celle=20de=20?= =?UTF-8?q?la=20forme=20{{{(A|B|C|)*}}}=20il=20suffit=20de=20les=20compile?= =?UTF-8?q?r=20en=20une=20RegExp=20(ce=20qu'elles=20sont=20=C3=A0=20peu=20?= =?UTF-8?q?pr=C3=A8s,=20il=20faut=20juste=20supprimer=20les=20virgules=20e?= =?UTF-8?q?t=20normaliser=20l'usage=20des=20espaces)=20et=20appliquer=20ce?= =?UTF-8?q?lle-ci=20sur=20la=20suite=20des=20noms=20de=20balises=20rencont?= =?UTF-8?q?r=C3=A9es=20s=C3=A9par=C3=A9es=20par=20des=20espaces.=20Ainsi,?= =?UTF-8?q?=20la=20r=C3=A8gle:=20{{{=20<!ELEMENT=20table=20=20=20=20=20=20?= =?UTF-8?q?(caption=3F,=20(col*|colgroup*),=20thead=3F,=20tfoot=3F,=20(tbo?= =?UTF-8?q?dy+|tr+))>=20}}}=20se=20compile=20en=20{{{=20/^((caption=20)=3F?= =?UTF-8?q?((col=20)*|(colgroup=20)*)(thead=20)=3F(tfoot=20)=3F((tbody=20)?= =?UTF-8?q?+|(tr=20)+))$/=20}}}=20appliquer=20preg=5Fmatch=20dessus=20et,?= =?UTF-8?q?=20par=20exemple,=20"tr=20caption=20tr=20",=20permettra=20e=20r?= =?UTF-8?q?efuser=20cette=20construction=20ce=20qui=20n'=C3=A9tait=20pas?= =?UTF-8?q?=20le=20cas=20auparavant.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La règle sur head, que la DTD énonce en 2 coups: {{{ <!ENTITY % head.misc "(script|style|meta|link|object|isindex)*"> <!ELEMENT head (%head.misc;, ((title, %head.misc;, (base, %head.misc;)?) | (base, %head.misc;, (title, %head.misc;))))> }}} est compilée en: {{{ /^(((script )|(style )|(meta )|(link )|(object ))*(((title )((script )|(style )|(meta )|(link )|(object ))*((base )((script )|(style )|(meta )|(link )|(object ))*)?)|((base )((script )|(style )|(meta )|(link )|(object ))*((title )((script )|(style )|(meta )|(link )|(object ))*))))$/ }}} ce qui assure qu'il y aura une et une seule balise Title et au plus une balise Base. Seul défaut de cette stratégie hyper efficace: le message d'erreur mentionnera comme ligne fautive celle contenant la balise fermant la suite problématique, sans plus de précision. Cela dit ce n'est de toutes façons pas possible d'etre plus précis dans le cas général. --- ecrire/inc/valider_xml.php | 137 +++++++++++++++++++++++++------------ 1 file changed, 93 insertions(+), 44 deletions(-) diff --git a/ecrire/inc/valider_xml.php b/ecrire/inc/valider_xml.php index 56d0e6b7c6..ed86eb6229 100644 --- a/ecrire/inc/valider_xml.php +++ b/ecrire/inc/valider_xml.php @@ -65,13 +65,32 @@ function charger_dtd($data) spip_log("Racine $topelement dans $grammaire ($rotlvl)"); $dtc = new DTC; analyser_dtd($grammaire, $avail, $dtc); +$r = $dtc->regles; ksort($r);foreach($r as $l => $v) echo "$l '$v'<br />\n"; return $dtc; } +// Compiler une regle de production en une Regexp qu'on appliquera sur la +// suite des noms de balises separes par des espaces. Du coup: +// supprimer #PCDATA etc, ca ne sert pas pour le controle des balises; +// supprimer les virgules (les sequences sont implicites dans une Regexp) +// conserver | + * ? ( ) qui ont la meme signification en DTD et en Regexp; +// faire suivre chaque nom d'un espace (et supprimer les autres) ... +// et parentheser le tout pour que | + * ? s'applique dessus. + +function compilerRegle($val) +{ + $x = str_replace('()','', + preg_replace('/\s*,\s*/','', + preg_replace('/(\w+)\s*/','(\1 )', + preg_replace('/\s*\|\s*/','|', + preg_replace('/#\w+\s*[,|]?\s*/','', $val))))); + return $x; +} + + // http://doc.spip.org/@analyser_dtd function analyser_dtd($grammaire, $avail, &$dtc) { - static $trace = array(); // pour debug $dtd = ''; @@ -120,23 +139,44 @@ function analyser_dtd($grammaire, $avail, &$dtc) } } - // memoriser la regle de production de l'element - // et dresser le tableau de ses fils potentiels - // pour traquer tres vite les balises filles illegitimes - + // Dresser le tableau des filles potentielles de l'element + // pour traquer tres vite les illegitimes. + // Si la regle a au moins une sequence (i.e. une virgule) + // ou n'est pas une it�ration (i.e. se termine par * ou +) + // en faire une RegExp qu'on appliquera aux balises rencontrees. + // Sinon, conserver seulement le type de l'iteration car la traque + // aura fait l'essentiel du controle sans memorisation des balises. + // Fin du controle en finElement if (preg_match_all('/<!ELEMENT\s+(\S+)\s+([^>]*)>/', $dtd, $r, PREG_SET_ORDER)) { foreach($r as $m) { list(,$nom, $val) = $m; $nom = expanserEntite($nom, $dtc->macros); - $val = expanserEntite($val, $dtc->macros); - $dtc->regles[$nom]= $val; - $val = array_values(preg_split('/\W+/', $val,-1, PREG_SPLIT_NO_EMPTY)); - $dtc->elements[$nom]= $val; - - foreach ($val as $k) { - if (($k != 'EMPTY') AND ($k != 'ANY') AND ($k[0] != '#') AND ((!isset($dtc->peres[$k])) OR !in_array($nom, $dtc->peres[$k]))) - $dtc->peres[$k][]= $nom; + $val = compilerRegle(expanserEntite($val, $dtc->macros)); + if (isset($dtc->elements[$nom])) { + spip_log("double definition de $nom dans la DTD"); + return; + } + $filles = array(); + if ($val == '(EMPTY )') + $dtc->regles[$nom] = 'EMPTY'; + elseif ($val == '(ANY )') + $dtc->regles[$nom] = 'ANY'; + else { + $last = substr($val,-1); + if (preg_match('/ \w/', $val) + OR strpos('*+', $last) === false) + $dtc->regles[$nom] = "/^$val$/"; + else + $dtc->regles[$nom] = $last; + $filles = array_values(preg_split('/\W+/', $val,-1, PREG_SPLIT_NO_EMPTY)); + + foreach ($filles as $k) { + if ((!isset($dtc->peres[$k])) + OR !in_array($nom, $dtc->peres[$k])) + $dtc->peres[$k][]= $nom; + } } + $dtc->elements[$nom]= $filles; } // tri pour presenter les suggestions de corrections foreach ($dtc->peres as $k => $v) { @@ -169,37 +209,34 @@ function analyser_dtd($grammaire, $avail, &$dtc) spip_log("DTD $avail $grammaire ". strlen($dtd) . ' octets ' . count($dtc->macros) . ' macros, ' . count($dtc->elements) . ' elements, ' . count($trace) . " types diff�rents d'attributs " . count($dtc->entites) . " entites"); } + // http://doc.spip.org/@expanserEntite function expanserEntite($val, $macros) { if (preg_match_all('/%([.\w]+);/', $val, $r, PREG_SET_ORDER)) { - foreach($r as $m) + foreach($r as $m) { + $ent = $m[1]; // il peut valoir "" - if (isset($macros[$m[1]])) - $val = str_replace($m[0], $macros[$m[1]], $val); + if (isset($macros[$ent])) + $val = str_replace($m[0], $macros[$ent], $val); + } } return trim(preg_replace('/\s+/', ' ', $val)); } + // http://doc.spip.org/@validerElement function validerElement($phraseur, $name, $attrs) { global $phraseur_xml; - if (!$phraseur_xml->dtc->elements) return; - if (!isset($phraseur_xml->dtc->elements[$name])) $phraseur_xml->err[]= " <b>$name</b>" . _L(' balise inconnue ') . coordonnees_erreur($phraseur); - // controler les filles illegitimes, mais pas le droit d'ainesse - // (i.e. l'ordre de succession indique par une virgule dans la regle: - // le cas est rare, du coup interet/temps-de-calcul tend vers 0) - // Pour XHTML 1.0 il n'y a que <html>, <head> et <table>. - // Mais a-t-on jamais mis head apres body et caption apres tr ? - // Quant a Head, c'est juste pour avoir 1 seul title et 1 seul base. else { + // controler les filles illegitimes, ca suffut $depth = $phraseur_xml->depth; $ouvrant = $phraseur_xml->ouvrant; if (isset($ouvrant[$depth])) { @@ -216,9 +253,14 @@ function validerElement($phraseur, $name, $attrs) . (!$bons_peres ? '' : (_L( '<p style="font-size: 80%"> mais de <b>') . $bons_peres . '</b></p>')) . coordonnees_erreur($phraseur); - } + } else if ($phraseur_xml->dtc->regles[$pere][0]=='/') { + $phraseur_xml->fratrie[substr($depth,2)].= "$name "; + } } } + // Init de la suite des balises a memoriser si regle difficile + if ($phraseur_xml->dtc->regles[$name][0]=='/') + $phraseur_xml->fratrie[$depth]=''; if (isset($phraseur_xml->dtc->attributs[$name])) { foreach ($phraseur_xml->dtc->attributs[$name] as $n => $v) { if (($v[1] == '#REQUIRED') AND (!isset($attrs[$n]))) @@ -352,7 +394,9 @@ function debutElement($phraseur, $name, $attrs) { global $phraseur_xml; - validerElement($phraseur, $name, $attrs); + if ($phraseur_xml->dtc->elements) + validerElement($phraseur, $name, $attrs); + xml_debutElement($phraseur, $name, $attrs); $depth = &$phraseur_xml->depth; $phraseur_xml->debuts[$depth] = strlen($phraseur_xml->res); @@ -374,33 +418,37 @@ function finElement($phraseur, $name) if ($ouv[0] != ' ') $ouvrant[$depth] = ' ' . $ouv; else $ouv= ""; - $n = strlen($phraseur_xml->res) + strlen(trim($contenu[$depth])); + $n = strlen($phraseur_xml->res); + $c = strlen(trim($contenu[$depth])); $k = $phraseur_xml->debuts[$depth]; $regle = $phraseur_xml->dtc->regles[$name]; $vide = ($regle == 'EMPTY'); - // controler que les balises devant etre vides le sont (ok), - // idem pour les nons vides (approximatif, car on ignore "|") - // Pour Xhtml 1.0, cette approximation suffit + // controler que les balises devant etre vides le sont if ($vide) { - if ($n <> $k) + if ($n <> ($k + $c)) $phraseur_xml->err[]= " <p><b>$name</b>" . _L(' balise non vide') . coordonnees_erreur($phraseur); - } elseif ($n == $k) { - if (strpos($regle, '+')) $ok = false; - else { - if (!preg_match('/^\(.*(.)$/', $regle,$r)) - $ok = true; // DATA: ok. - elseif ($r[1] != ')') $ok = true; // i.e. ? ou *: ok - elseif (preg_match('/[^?*)][)]*,/', $regle)) $ok= false; - else $ok= true; - } - if( !$ok) { - $phraseur_xml->err[]= " <p>\n<b>$name</b>" - . _L(' balise vide') - . coordonnees_erreur($phraseur); + // pour les regles PCDATA ou iteration de disjonction, tout est fait + } elseif ($regle AND ($regle != '*')) { + if ($regle == '+') { + // iteration de disjonction non vide: 1 balise au - + if ($n == $k) { + $phraseur_xml->err[]= " <p>\n<b>$name</b>" + . _L(' balise vide') + . coordonnees_erreur($phraseur); + } + } else { + $f = $phraseur_xml->fratrie[substr($depth,2)]; + if (!preg_match($regle, $f)) + $phraseur_xml->err[]= " <p>\n<b>$name</b>" + . _L(' succession des fils incorrecte : <b>') + . $f + . '</b>' + . coordonnees_erreur($phraseur); } + } xml_finElement($phraseur, $name, $vide); } @@ -460,6 +508,7 @@ function phraserTout($phraseur, $data) var $idrefs = array(); var $idrefss = array(); var $debuts = array(); + var $fratrie = array(); } // http://doc.spip.org/@inc_valider_xml_dist -- GitLab