diff --git a/ecrire/inc/valider_xml.php b/ecrire/inc/valider_xml.php index 56d0e6b7c644e27ede430a31503bc6316398ad54..ed86eb6229bef8914182a9997a6fee433302bf26 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