diff --git a/ecrire/inc/utils.php b/ecrire/inc/utils.php index ad3aad60df0651647f8473a83c683c7c07a22cac..5a733b78e71b1877ed1c897c150457e24120e66c 100644 --- a/ecrire/inc/utils.php +++ b/ecrire/inc/utils.php @@ -992,8 +992,8 @@ function spip_initialisation($pi=NULL, $pa=NULL, $ti=NULL, $ta=NULL) { define('_DOCTYPE_ECRIRE', // "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN' 'http://www.w3.org/TR/html4/loose.dtd'>\n"); "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"); - // "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>\n"); - + // "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>\n"); + // "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.1 //EN' 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'>\n"); define('_DOCTYPE_AIDE', "<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01 Frameset//EN' 'http://www.w3.org/TR/1999/REC-html401-19991224/frameset.dtd'>"); diff --git a/ecrire/inc/valider_xml.php b/ecrire/inc/valider_xml.php index 21a9cdcda1d6e16e114a75b77b13e7c77846c85e..d126b3d5af1107cec1c885e55ddb2f1c30f5a9c4 100644 --- a/ecrire/inc/valider_xml.php +++ b/ecrire/inc/valider_xml.php @@ -65,7 +65,14 @@ 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"; + + // tri final pour presenter les suggestions de corrections + foreach ($dtc->peres as $k => $v) { + asort($v); + $dtc->peres[$k] = $v; + } + +# $r = $dtc->regles; ksort($r);foreach($r as $l => $v) echo "<b>$l</b> '$v' ", join (', ',array_keys($dtc->attributs[$l])), "<br />\n"; return $dtc; } @@ -80,134 +87,204 @@ function charger_dtd($data) // http://doc.spip.org/@compilerRegle function compilerRegle($val) { - $x = str_replace('()','', + $x = str_replace('\s*()\s*','', preg_replace('/\s*,\s*/','', preg_replace('/(\w+)\s*/','(\1 )', - preg_replace('/\s*\|\s*/','|', - preg_replace('/#\w+\s*[,|]?\s*/','', $val))))); + preg_replace('/\s*([(+*|])\s*/','\1', + preg_replace('/\s*#\w+\s*[,|]?\s*/','', $val))))); return $x; } // http://doc.spip.org/@analyser_dtd -function analyser_dtd($grammaire, $avail, &$dtc) +function analyser_dtd($loc, $avail, &$dtc) { - static $trace = array(); // pour debug - - $dtd = ''; if ($avail == 'SYSTEM') - $file = $grammaire; - else + $file = $loc; + else { $file = sous_repertoire(_DIR_DTD); - $file .= preg_replace('/[^\w.]/','_', $grammaire); + $file .= preg_replace('/[^\w.]/','_', $loc); + } + $dtd = ''; if (@is_readable($file)) { lire_fichier($file, $dtd); } else { if ($avail == 'PUBLIC') { include_spip('inc/distant'); - if ($dtd = recuperer_page($grammaire)) + if ($dtd = recuperer_page($loc)) ecrire_fichier($file, $dtd); } } - if (!$dtd) { - spip_log("DTD $grammaire inaccessible"); + + if (!$dtd = ltrim($dtd)) { + spip_log("DTD $loc inaccessible"); return array(); } + while ($dtd) { + if ($dtd[0] != '<') + $r = analyser_dtd_lexeme($dtd, $dtc, $loc); + elseif ($dtd[1] != '!') + $r = analyser_dtd_pi($dtd, $dtc, $loc); + else switch ($dtd[3]) { + case '%' : $r = analyser_dtd_data($dtd, $dtc, $loc); break; + case 'T' : $r = analyser_dtd_attlist($dtd, $dtc, $loc);break; + case 'L' : $r = analyser_dtd_element($dtd, $dtc, $loc);break; + case 'N' : $r = analyser_dtd_entity($dtd, $dtc, $loc);break; + case 'O' : $r = analyser_dtd_notation($dtd, $dtc, $loc);break; + case '-' : $r = analyser_dtd_comment($dtd, $dtc, $loc); break; + default: $r = -1; + } + + if (!is_string($r)) { + spip_log("erreur $r dans la DTD " . substr($dtd,0,256) . "....."); + return array(); + } + $dtd = $r; + + } + spip_log("DTD $avail $loc " . count($dtc->macros) . ' macros, ' . count($dtc->elements) . ' elements, ' . count($dtc->attributs) . " listes d'attributs, " . count($dtc->entites) . " entites"); +} + +function analyser_dtd_comment($dtd, &$dtc, $grammaire){ // ejecter les commentaires, surtout quand ils contiennent du code. // Option /s car sur plusieurs lignes parfois - $dtd = preg_replace('/<!--.*?-->/s','',$dtd); + if (!preg_match('/^<!--.*?-->\s*(.*)$/s',$dtd, $m)) + return -6; + return $m[1]; +} + +function analyser_dtd_pi($dtd, &$dtc, $grammaire){ + if (!preg_match('/^<\?.*?>\s*(.*)$/s', $dtd, $m)) + return -10; + return $m[1]; +} + +function analyser_dtd_lexeme($dtd, &$dtc, $grammaire){ + if (!preg_match('/^%([\w;.-]+);\s*(.*)$/s', $dtd, $m)) + return -9; + + list(,$s, $dtd) = $m; + $n = $dtc->macros[$s]; + if (is_array($n)) { + // en cas d'inclusion, l'espace de nom est le meme + analyser_dtd($n[1], $n[0], $dtc); + } + return $dtd; +} + +// il faudrait prevoir plusieurs niveaux d'inclusion. +// (Ruby en utilise mais l'erreur est transparente. Scandaleux coup de pot) + +function analyser_dtd_data($dtd, &$dtc, $grammaire){ + if (!preg_match('/^<!\[%([^;]*);\s*\[\s*(.*?)\]\]>\s*(.*)$/s',$dtd, $m)) + return -6; + if ($dtc->macros[$m[1]] == 'INCLUDE') + $retour = $m[2] . $m[3]; + else $retour = $m[3]; + return $retour; +} + +function analyser_dtd_notation($dtd, &$dtc, $grammaire){ + if (!preg_match('/^<!NOTATION.*?>\s*(.*)$/s',$dtd, $m)) + return -8; + spip_log("analyser_dtd_notation a ecrire"); + return $m[1]; +} - if (preg_match_all('/<!ENTITY\s+(%?)\s*([\w;.-]+)\s+(PUBLIC|SYSTEM)?\s*"([^"]*)"\s*("([^"]*)")?\s*>/', $dtd, $r, PREG_SET_ORDER)) { - foreach($r as $m) { - list($t, $term, $nom, $type, $val, $q, $alt) = $m; - if ($type AND $alt) { - // valeur par defaut de $alt obscure. A etudier. - if (strpos($alt, '/') === false) +function analyser_dtd_entity($dtd, &$dtc, $grammaire) +{ + if (!preg_match('/^<!ENTITY\s+(%?)\s*([\w;.-]+)\s+(PUBLIC|SYSTEM|INCLUDE|IGNORE)?\s*"([^"]*)"\s*("([^"]*)")?\s*>\s*(.*)$/s', $dtd, $m)) + return -2; + + list($t, $term, $nom, $type, $val, $q, $alt, $dtd) = $m; + if (!$term) + $dtc->entites[$nom] = $val; + elseif (!$type) + $dtc->macros[$nom] = expanserEntite($val, $dtc->macros); + elseif (!$alt) + $dtc->macros[$nom] = $alt; + else { + if (strpos($alt, '/') === false) $alt = preg_replace(',/[^/]+$,', '/', $grammaire) . ($alt ? $alt : "loose.dtd") ; - // en cas d'inclusion, l'espace de nom est le meme - analyser_dtd($alt, $type, $dtc); - } - elseif (!$term) { - $dtc->entites[$nom] = $val; - } - else { - $dtc->macros[$nom] = expanserEntite($val, $dtc->macros) ; - } - } - } + $dtc->macros[$nom] = array($type, $alt); + } - // 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 = 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; - } + return $dtd; +} + +// 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 + +function analyser_dtd_element($dtd, &$dtc, $grammaire) +{ + if (!preg_match('/^<!ELEMENT\s+(\S+)\s+([^>]*)>\s*(.*)$/s', $dtd, $m)) + return -3; + + list(,$nom, $val, $dtd) = $m; + $nom = expanserEntite($nom, $dtc->macros); + $val = compilerRegle(expanserEntite($val, $dtc->macros)); + if (isset($dtc->elements[$nom])) { + spip_log("double definition de $nom dans la DTD"); + return -4; + } + $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])) + $dtc->peres[$k] = array(); + if (!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) { - asort($v); - $dtc->peres[$k] = $v; - } } + $dtc->elements[$nom]= $filles; + return $dtd; +} - if (preg_match_all('/<!ATTLIST\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); - $att = array(); - if (preg_match_all("/\s*(\S+)\s+(([(][^)]*[)])|(\S+))\s+(\S+)(\s*'[^']*')?/", $val, $r2, PREG_SET_ORDER)) { +function analyser_dtd_attlist($dtd, &$dtc, $grammaire) +{ + if (!preg_match('/^<!ATTLIST\s+(\S+)\s+([^>]*)>\s*(.*)/s', $dtd, $m)) + return -5; + + list(,$nom, $val, $dtd) = $m; + $nom = expanserEntite($nom, $dtc->macros); + $val = expanserEntite($val, $dtc->macros); + if (!isset($dtc->attributs[$nom])) + $dtc->attributs[$nom] = array(); + + if (preg_match_all("/\s*(\S+)\s+(([(][^)]*[)])|(\S+))\s+(\S+)(\s*'[^']*')?/", $val, $r2, PREG_SET_ORDER)) { foreach($r2 as $m2) { $v = preg_match('/^\w+$/', $m2[2]) ? $m2[2] : ('/^' . preg_replace('/\s+/', '', $m2[2]) . '$/'); $m21 = expanserEntite($m2[1], $dtc->macros); $m25 = expanserEntite($m2[5], $dtc->macros); - $trace[$v] = 1; - $att[$m21] = array($v, $m25); + $dtc->attributs[$nom][$m21] = array($v, $m25); } - } - $dtc->attributs[$nom] = $att; - } } - 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"); + return $dtd; } @@ -294,6 +371,7 @@ function validerAttribut($phraseur, $name, $val, $bal) $bons . "'"; $bons .= " style='font-weight: bold'"; + $phraseur_xml->err[]= " <b>$name</b>" . _L(' attribut inconnu de ') . "<a$bons>$bal</a>" @@ -462,6 +540,15 @@ function textElement($phraseur, $data) function PiElement($phraseur, $target, $data) { xml_PiElement($phraseur, $target, $data);} +// Denonciation des entitees XML inconnues +// Pour contourner le bug de conception de SAX qui ne signale pas si elles +// sont dans un attribut, les entites les plus frequentes ont ete +// transcodees par html2unicode au prealable. +// On ne les verra donc pas passer a cette etape, contrairement a ce que +// le source de la page laisse legitimement supposer. +// Il faudrait en fait transcoder toutes les entites (sauf & < > ") +// pour leur eviter ce bug. + // http://doc.spip.org/@defautElement function defautElement($phraseur, $data) { @@ -476,6 +563,7 @@ function defautElement($phraseur, $data) . _L(' entite inconnue ') . coordonnees_erreur($phraseur); } + xml_defautElement($phraseur, $data); }