Skip to content
Extraits de code Groupes Projets
Valider 22d2f964 rédigé par esj's avatar esj
Parcourir les fichiers

Il était écrit que la prochaine version de SPIP n'allait pas sortir avec un...

Il était écrit que la prochaine version de SPIP n'allait pas sortir avec un validateur limité. Je suis nul de ne pas y avoir pensé avant: pour vérifier les rares règles plus complexes que celle de la forme {{{(A|B|C|)*}}} il suffit de les compiler en une RegExp (ce qu'elles sont à peu près, il faut juste supprimer les virgules et normaliser l'usage des espaces) et appliquer celle-ci sur la suite des noms de balises rencontrées séparées par des espaces. Ainsi, la règle:
{{{
<!ELEMENT table
     (caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))>
}}}
se compile en 
{{{
/^((caption )?((col )*|(colgroup )*)(thead )?(tfoot )?((tbody )+|(tr )+))$/
}}}
appliquer preg_match dessus et, par exemple, "tr caption tr ", permettra e refuser cette construction ce qui n'était pas le cas auparavant.

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.
parent c0dcba2b
Aucune branche associée trouvée
Aucune étiquette associée trouvée
Aucune requête de fusion associée trouvée
...@@ -65,13 +65,32 @@ function charger_dtd($data) ...@@ -65,13 +65,32 @@ function charger_dtd($data)
spip_log("Racine $topelement dans $grammaire ($rotlvl)"); spip_log("Racine $topelement dans $grammaire ($rotlvl)");
$dtc = new DTC; $dtc = new DTC;
analyser_dtd($grammaire, $avail, $dtc); analyser_dtd($grammaire, $avail, $dtc);
$r = $dtc->regles; ksort($r);foreach($r as $l => $v) echo "$l '$v'<br />\n";
return $dtc; 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 // http://doc.spip.org/@analyser_dtd
function analyser_dtd($grammaire, $avail, &$dtc) function analyser_dtd($grammaire, $avail, &$dtc)
{ {
static $trace = array(); // pour debug static $trace = array(); // pour debug
$dtd = ''; $dtd = '';
...@@ -120,23 +139,44 @@ function analyser_dtd($grammaire, $avail, &$dtc) ...@@ -120,23 +139,44 @@ function analyser_dtd($grammaire, $avail, &$dtc)
} }
} }
// memoriser la regle de production de l'element // Dresser le tableau des filles potentielles de l'element
// et dresser le tableau de ses fils potentiels // pour traquer tres vite les illegitimes.
// pour traquer tres vite les balises filles 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)) { if (preg_match_all('/<!ELEMENT\s+(\S+)\s+([^>]*)>/', $dtd, $r, PREG_SET_ORDER)) {
foreach($r as $m) { foreach($r as $m) {
list(,$nom, $val) = $m; list(,$nom, $val) = $m;
$nom = expanserEntite($nom, $dtc->macros); $nom = expanserEntite($nom, $dtc->macros);
$val = expanserEntite($val, $dtc->macros); $val = compilerRegle(expanserEntite($val, $dtc->macros));
$dtc->regles[$nom]= $val; if (isset($dtc->elements[$nom])) {
$val = array_values(preg_split('/\W+/', $val,-1, PREG_SPLIT_NO_EMPTY)); spip_log("double definition de $nom dans la DTD");
$dtc->elements[$nom]= $val; return;
}
foreach ($val as $k) { $filles = array();
if (($k != 'EMPTY') AND ($k != 'ANY') AND ($k[0] != '#') AND ((!isset($dtc->peres[$k])) OR !in_array($nom, $dtc->peres[$k]))) if ($val == '(EMPTY )')
$dtc->peres[$k][]= $nom; $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 // tri pour presenter les suggestions de corrections
foreach ($dtc->peres as $k => $v) { foreach ($dtc->peres as $k => $v) {
...@@ -169,37 +209,34 @@ function analyser_dtd($grammaire, $avail, &$dtc) ...@@ -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"); 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 // http://doc.spip.org/@expanserEntite
function expanserEntite($val, $macros) function expanserEntite($val, $macros)
{ {
if (preg_match_all('/%([.\w]+);/', $val, $r, PREG_SET_ORDER)) { if (preg_match_all('/%([.\w]+);/', $val, $r, PREG_SET_ORDER)) {
foreach($r as $m) foreach($r as $m) {
$ent = $m[1];
// il peut valoir "" // il peut valoir ""
if (isset($macros[$m[1]])) if (isset($macros[$ent]))
$val = str_replace($m[0], $macros[$m[1]], $val); $val = str_replace($m[0], $macros[$ent], $val);
}
} }
return trim(preg_replace('/\s+/', ' ', $val)); return trim(preg_replace('/\s+/', ' ', $val));
} }
// http://doc.spip.org/@validerElement // http://doc.spip.org/@validerElement
function validerElement($phraseur, $name, $attrs) function validerElement($phraseur, $name, $attrs)
{ {
global $phraseur_xml; global $phraseur_xml;
if (!$phraseur_xml->dtc->elements) return;
if (!isset($phraseur_xml->dtc->elements[$name])) if (!isset($phraseur_xml->dtc->elements[$name]))
$phraseur_xml->err[]= " <b>$name</b>" $phraseur_xml->err[]= " <b>$name</b>"
. _L(' balise inconnue ') . _L(' balise inconnue ')
. coordonnees_erreur($phraseur); . 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 { else {
// controler les filles illegitimes, ca suffut
$depth = $phraseur_xml->depth; $depth = $phraseur_xml->depth;
$ouvrant = $phraseur_xml->ouvrant; $ouvrant = $phraseur_xml->ouvrant;
if (isset($ouvrant[$depth])) { if (isset($ouvrant[$depth])) {
...@@ -216,9 +253,14 @@ function validerElement($phraseur, $name, $attrs) ...@@ -216,9 +253,14 @@ function validerElement($phraseur, $name, $attrs)
. (!$bons_peres ? '' . (!$bons_peres ? ''
: (_L( '<p style="font-size: 80%"> mais de <b>') . $bons_peres . '</b></p>')) : (_L( '<p style="font-size: 80%"> mais de <b>') . $bons_peres . '</b></p>'))
. coordonnees_erreur($phraseur); . 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])) { if (isset($phraseur_xml->dtc->attributs[$name])) {
foreach ($phraseur_xml->dtc->attributs[$name] as $n => $v) foreach ($phraseur_xml->dtc->attributs[$name] as $n => $v)
{ if (($v[1] == '#REQUIRED') AND (!isset($attrs[$n]))) { if (($v[1] == '#REQUIRED') AND (!isset($attrs[$n])))
...@@ -352,7 +394,9 @@ function debutElement($phraseur, $name, $attrs) ...@@ -352,7 +394,9 @@ function debutElement($phraseur, $name, $attrs)
{ {
global $phraseur_xml; global $phraseur_xml;
validerElement($phraseur, $name, $attrs); if ($phraseur_xml->dtc->elements)
validerElement($phraseur, $name, $attrs);
xml_debutElement($phraseur, $name, $attrs); xml_debutElement($phraseur, $name, $attrs);
$depth = &$phraseur_xml->depth; $depth = &$phraseur_xml->depth;
$phraseur_xml->debuts[$depth] = strlen($phraseur_xml->res); $phraseur_xml->debuts[$depth] = strlen($phraseur_xml->res);
...@@ -374,33 +418,37 @@ function finElement($phraseur, $name) ...@@ -374,33 +418,37 @@ function finElement($phraseur, $name)
if ($ouv[0] != ' ') if ($ouv[0] != ' ')
$ouvrant[$depth] = ' ' . $ouv; $ouvrant[$depth] = ' ' . $ouv;
else $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]; $k = $phraseur_xml->debuts[$depth];
$regle = $phraseur_xml->dtc->regles[$name]; $regle = $phraseur_xml->dtc->regles[$name];
$vide = ($regle == 'EMPTY'); $vide = ($regle == 'EMPTY');
// controler que les balises devant etre vides le sont (ok), // controler que les balises devant etre vides le sont
// idem pour les nons vides (approximatif, car on ignore "|")
// Pour Xhtml 1.0, cette approximation suffit
if ($vide) { if ($vide) {
if ($n <> $k) if ($n <> ($k + $c))
$phraseur_xml->err[]= " <p><b>$name</b>" $phraseur_xml->err[]= " <p><b>$name</b>"
. _L(' balise non vide') . _L(' balise non vide')
. coordonnees_erreur($phraseur); . coordonnees_erreur($phraseur);
} elseif ($n == $k) { // pour les regles PCDATA ou iteration de disjonction, tout est fait
if (strpos($regle, '+')) $ok = false; } elseif ($regle AND ($regle != '*')) {
else { if ($regle == '+') {
if (!preg_match('/^\(.*(.)$/', $regle,$r)) // iteration de disjonction non vide: 1 balise au -
$ok = true; // DATA: ok. if ($n == $k) {
elseif ($r[1] != ')') $ok = true; // i.e. ? ou *: ok $phraseur_xml->err[]= " <p>\n<b>$name</b>"
elseif (preg_match('/[^?*)][)]*,/', $regle)) $ok= false; . _L(' balise vide')
else $ok= true; . coordonnees_erreur($phraseur);
} }
if( !$ok) { } else {
$phraseur_xml->err[]= " <p>\n<b>$name</b>" $f = $phraseur_xml->fratrie[substr($depth,2)];
. _L(' balise vide') if (!preg_match($regle, $f))
. coordonnees_erreur($phraseur); $phraseur_xml->err[]= " <p>\n<b>$name</b>"
. _L(' succession des fils incorrecte : <b>')
. $f
. '</b>'
. coordonnees_erreur($phraseur);
} }
} }
xml_finElement($phraseur, $name, $vide); xml_finElement($phraseur, $name, $vide);
} }
...@@ -460,6 +508,7 @@ function phraserTout($phraseur, $data) ...@@ -460,6 +508,7 @@ function phraserTout($phraseur, $data)
var $idrefs = array(); var $idrefs = array();
var $idrefss = array(); var $idrefss = array();
var $debuts = array(); var $debuts = array();
var $fratrie = array();
} }
// http://doc.spip.org/@inc_valider_xml_dist // http://doc.spip.org/@inc_valider_xml_dist
......
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter