3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
6 * Copyright (c) 2001-2007 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
13 if (!defined("_ECRIRE_INC_VERSION")) return;
15 include_spip('xml/interfaces');
17 function charger_dtd($grammaire, $avail)
21 // L'analyseur retourne un booleen de reussite et modifie $dtc.
22 // Retourner vide en cas d'echec
23 if (!analyser_dtd($grammaire, $avail, $dtc)) return array();
25 // tri final pour presenter les suggestions de corrections
26 foreach ($dtc->peres
as $k => $v) {
31 spip_log("Analyser DTD $avail $grammaire (" . spip_timer('dtd') . ") " . count($dtc->macros
) . ' macros, ' . count($dtc->elements
) . ' elements, ' . count($dtc->attributs
) . " listes d'attributs, " . count($dtc->entites
) . " entites");
32 # $r = $dtc->regles; ksort($r);foreach($r as $l => $v) echo "<b>$l</b> '$v' ", join (', ',array_keys($dtc->attributs[$l])), "<br />\n";exit;
36 // Compiler une regle de production en une Regexp qu'on appliquera sur la
37 // suite des noms de balises separes par des espaces. Du coup:
38 // supprimer #PCDATA etc, ca ne sert pas pour le controle des balises;
39 // supprimer les virgules (les sequences sont implicites dans une Regexp)
40 // conserver | + * ? ( ) qui ont la meme signification en DTD et en Regexp;
41 // faire suivre chaque nom d'un espace (et supprimer les autres) ...
42 // et parentheser le tout pour que | + * ? s'applique dessus.
44 // http://doc.spip.org/@compilerRegle
45 function compilerRegle($val)
47 $x = str_replace('\s*()\s*','',
48 preg_replace('/\s*,\s*/','',
49 preg_replace('/(\w+)\s*/','(\1 )',
50 preg_replace('/\s*([(+*|])\s*/','\1',
51 preg_replace('/\s*#\w+\s*[,|]?\s*/','', $val)))));
56 // http://doc.spip.org/@analyser_dtd
57 function analyser_dtd($loc, $avail, &$dtc)
59 if ($avail == 'SYSTEM')
62 $file = sous_repertoire(_DIR_CACHE_XML
);
63 $file .= preg_replace('/[^\w.]/','_', $loc);
67 if (@is_readable($file)) {
68 lire_fichier($file, $dtd);
70 if ($avail == 'PUBLIC') {
71 include_spip('inc/distant');
72 if ($dtd = trim(recuperer_page($loc)))
73 ecrire_fichier($file, $dtd, true
);
78 spip_log("DTD '$loc' inaccessible");
80 } else spip_log("analyse de la DTD $loc ");
84 $r = analyser_dtd_lexeme($dtd, $dtc, $loc);
85 elseif ($dtd[1] != '!')
86 $r = analyser_dtd_pi($dtd, $dtc, $loc);
87 elseif ($dtd[2] == '[')
88 $r = analyser_dtd_data($dtd, $dtc, $loc);
89 else switch ($dtd[3]) {
90 case '%' : $r = analyser_dtd_data($dtd, $dtc, $loc); break;
91 case 'T' : $r = analyser_dtd_attlist($dtd, $dtc, $loc);break;
92 case 'L' : $r = analyser_dtd_element($dtd, $dtc, $loc);break;
93 case 'N' : $r = analyser_dtd_entity($dtd, $dtc, $loc);break;
94 case 'O' : $r = analyser_dtd_notation($dtd, $dtc, $loc);break;
95 case '-' : $r = analyser_dtd_comment($dtd, $dtc, $loc); break;
99 spip_log("erreur $r dans la DTD " . substr($dtd,0,80) . ".....");
107 // http://doc.spip.org/@analyser_dtd_comment
108 function analyser_dtd_comment($dtd, &$dtc, $grammaire){
109 // ejecter les commentaires, surtout quand ils contiennent du code.
110 // Option /s car sur plusieurs lignes parfois
112 if (!preg_match('/^<!--.*?-->\s*(.*)$/s',$dtd, $m))
117 // http://doc.spip.org/@analyser_dtd_pi
118 function analyser_dtd_pi($dtd, &$dtc, $grammaire){
119 if (!preg_match('/^<\?.*?>\s*(.*)$/s', $dtd, $m))
124 // http://doc.spip.org/@analyser_dtd_lexeme
125 function analyser_dtd_lexeme($dtd, &$dtc, $grammaire){
126 if (!preg_match(_REGEXP_ENTITY_DEF
,$dtd, $m))
130 $n = $dtc->macros
[$s];
132 // en cas d'inclusion, l'espace de nom est le meme
133 analyser_dtd($n[1], $n[0], $dtc);
136 return ltrim(substr($dtd,strlen($m[0])));
139 // il faudrait prevoir plusieurs niveaux d'inclusion.
140 // (Ruby en utilise mais l'erreur est transparente. Scandaleux coup de pot)
142 // http://doc.spip.org/@analyser_dtd_data
143 function analyser_dtd_data($dtd, &$dtc, $grammaire){
144 if (!preg_match('/^<!\[\s*%\s*([^;]*);\s*\[\s*(.*?)\]\]>\s*(.*)$/s',$dtd, $m))
146 if ($dtc->macros
[$m[1]] == 'INCLUDE')
147 $retour = $m[2] . $m[3];
148 else $retour = $m[3];
152 // http://doc.spip.org/@analyser_dtd_notation
153 function analyser_dtd_notation($dtd, &$dtc, $grammaire){
154 if (!preg_match('/^<!NOTATION.*?>\s*(.*)$/s',$dtd, $m))
156 spip_log("analyser_dtd_notation a ecrire");
160 // http://doc.spip.org/@analyser_dtd_entity
161 function analyser_dtd_entity($dtd, &$dtc, $grammaire)
163 if (!preg_match(_REGEXP_ENTITY_DECL
, $dtd, $m))
166 list($t, $term, $nom, $type, $val, $q, $c, $alt, $dtd) = $m;
168 if (isset($dtc->macros
[$nom]) AND $dtc->macros
[$nom])
170 if (isset($dtc->entites
[$nom]))
171 spip_log("redefinition de l'entite $nom");
173 $dtc->entites
[$nom] = expanserEntite($val, $dtc->macros
);
175 $dtc->macros
[$nom] = expanserEntite($val, $dtc->macros
);
177 $dtc->macros
[$nom] = expanserEntite($val, $dtc->macros
);
179 if (strpos($alt, '/') === false
)
180 $alt = preg_replace(',/[^/]+$,', '/', $grammaire)
182 $dtc->macros
[$nom] = array($type, $alt);
187 // Dresser le tableau des filles potentielles de l'element
188 // pour traquer tres vite les illegitimes.
189 // Si la regle a au moins une sequence (i.e. une virgule)
190 // ou n'est pas une itération (i.e. se termine par * ou +)
191 // en faire une RegExp qu'on appliquera aux balises rencontrees.
192 // Sinon, conserver seulement le type de l'iteration car la traque
193 // aura fait l'essentiel du controle sans memorisation des balises.
194 // Fin du controle en finElement
196 // http://doc.spip.org/@analyser_dtd_element
197 function analyser_dtd_element($dtd, &$dtc, $grammaire)
199 if (!preg_match('/^<!ELEMENT\s+(\S+)\s+([^>]*)>\s*(.*)$/s', $dtd, $m))
202 list(,$nom, $val, $dtd) = $m;
203 $nom = expanserEntite($nom, $dtc->macros
);
204 $val = compilerRegle(expanserEntite($val, $dtc->macros
));
205 if (isset($dtc->elements
[$nom])) {
206 spip_log("redefinition de l'element $nom dans la DTD");
210 if ($val == '(EMPTY )')
211 $dtc->regles
[$nom] = 'EMPTY';
212 elseif ($val == '(ANY )')
213 $dtc->regles
[$nom] = 'ANY';
215 $last = substr($val,-1);
216 if (preg_match('/ \w/', $val)
217 OR strpos('*+', $last) === false
)
218 $dtc->regles
[$nom] = "/^$val$/";
220 $dtc->regles
[$nom] = $last;
221 $filles = array_values(preg_split('/\W+/', $val,-1, PREG_SPLIT_NO_EMPTY
));
223 foreach ($filles as $k) {
224 if (!isset($dtc->peres
[$k]))
225 $dtc->peres
[$k] = array();
226 if (!in_array($nom, $dtc->peres
[$k]))
227 $dtc->peres
[$k][]= $nom;
230 $dtc->elements
[$nom]= $filles;
235 // http://doc.spip.org/@analyser_dtd_attlist
236 function analyser_dtd_attlist($dtd, &$dtc, $grammaire)
238 if (!preg_match('/^<!ATTLIST\s+(\S+)\s+([^>]*)>\s*(.*)/s', $dtd, $m))
241 list(,$nom, $val, $dtd) = $m;
242 $nom = expanserEntite($nom, $dtc->macros
);
243 $val = expanserEntite($val, $dtc->macros
);
244 if (!isset($dtc->attributs
[$nom]))
245 $dtc->attributs
[$nom] = array();
247 if (preg_match_all("/\s*(\S+)\s+(([(][^)]*[)])|(\S+))\s+([^\s']*)(\s*'[^']*')?/", $val, $r2, PREG_SET_ORDER
)) {
248 foreach($r2 as $m2) {
249 $v = preg_match('/^\w+$/', $m2[2]) ?
$m2[2]
250 : ('/^' . preg_replace('/\s+/', '', $m2[2]) . '$/');
251 $m21 = expanserEntite($m2[1], $dtc->macros
);
252 $m25 = expanserEntite($m2[5], $dtc->macros
);
253 $dtc->attributs
[$nom][$m21] = array($v, $m25);
261 // http://doc.spip.org/@expanserEntite
262 function expanserEntite($val, $macros)
264 if (preg_match_all(_REGEXP_ENTITY_USE
, $val, $r, PREG_SET_ORDER
)){
268 if (isset($macros[$ent]))
269 $val = str_replace($m[0], $macros[$ent], $val);
272 return trim(preg_replace('/\s+/', ' ', $val));