[SPIP] +2.1.12
[velocampus/web/www.git] / www / ecrire / xml / valider.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2011 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
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 \***************************************************************************/
12
13 if (!defined('_ECRIRE_INC_VERSION')) return;
14
15 // Validateur XML en deux passes, fonde sur SAX pour la premiere
16 // Faudrait faire deux classes car pour la premiere passe
17 // on a les memes methodes et variables que l'indenteur
18
19 class ValidateurXML {
20
21 // http://doc.spip.org/@validerElement
22 function validerElement($phraseur, $name, $attrs)
23 {
24 if (!($p = isset($this->dtc->elements[$name]))) {
25 if ($p = strpos($name, ':')) {
26 $name = substr($name, $p+1);
27 $p = isset($this->dtc->elements[$name]);
28 }
29 if (!$p) {
30 coordonnees_erreur($this," <b>$name</b> "
31 . _T('zxml_inconnu_balise'));
32 return;
33 }
34 }
35 // controler les filles illegitimes, ca suffit
36 $depth = $this->depth;
37 $ouvrant = $this->ouvrant;
38 # spip_log("trouve $name apres " . $ouvrant[$depth]);
39 if (isset($ouvrant[$depth])) {
40 if (preg_match('/^\s*(\w+)/', $ouvrant[$depth], $r)) {
41 $pere = $r[1];
42 # spip_log("pere $pere");
43 if (isset($this->dtc->elements[$pere])) {
44 $fils = $this->dtc->elements[$pere];
45 # spip_log("rejeton $name fils " . @join(',',$fils));
46 if (!($p = @in_array($name, $fils))) {
47 if ($p = strpos($name, ':')) {
48 $p = substr($name, $p+1);
49 $p = @in_array($p, $fils);
50 }
51 }
52 if (!$p) {
53 $bons_peres = @join ('</b>, <b>', $this->dtc->peres[$name]);
54 coordonnees_erreur($this, " <b>$name</b> "
55 . _T('zxml_non_fils')
56 . ' <b>'
57 . $pere
58 . '</b>'
59 . (!$bons_peres ? ''
60 : ('<p style="font-size: 80%"> '._T('zxml_mais_de').' <b>'. $bons_peres . '</b></p>')));
61 } else if ($this->dtc->regles[$pere][0]=='/') {
62 $this->fratrie[substr($depth,2)].= "$name ";
63 }
64 }
65 }
66 }
67 // Init de la suite des balises a memoriser si regle difficile
68 if ($this->dtc->regles[$name][0]=='/')
69 $this->fratrie[$depth]='';
70 if (isset($this->dtc->attributs[$name])) {
71 foreach ($this->dtc->attributs[$name] as $n => $v)
72 { if (($v[1] == '#REQUIRED') AND (!isset($attrs[$n])))
73 coordonnees_erreur($this, " <b>$n</b>"
74 . '&nbsp;:&nbsp;'
75 . _T('zxml_obligatoire_attribut')
76 . " <b>$name</b>");
77 }
78 }
79 }
80
81 // http://doc.spip.org/@validerAttribut
82 function validerAttribut($phraseur, $name, $val, $bal)
83 {
84 // Si la balise est inconnue, eviter d'insister
85 if (!isset($this->dtc->attributs[$bal]))
86 return ;
87
88 $a = $this->dtc->attributs[$bal];
89 if (!isset($a[$name])) {
90 $bons = join(', ',array_keys($a));
91 if ($bons)
92 $bons = " title=' " .
93 _T('zxml_connus_attributs') .
94 '&nbsp;: ' .
95 $bons .
96 "'";
97 $bons .= " style='font-weight: bold'";
98 coordonnees_erreur($this, " <b>$name</b> "
99 . _T('zxml_inconnu_attribut').' '._T('zxml_de')
100 . " <a$bons>$bal</a> ("
101 . _T('zxml_survoler')
102 . ")");
103 } else{
104 $type = $a[$name][0];
105 if (!preg_match('/^\w+$/', $type))
106 $this->valider_motif($phraseur, $name, $val, $bal, $type);
107 else if (method_exists($this, $f = 'validerAttribut_' . $type))
108 $this->$f($phraseur, $name, $val, $bal);
109 # else spip_log("$type type d'attribut inconnu");
110 }
111 }
112
113 function validerAttribut_NMTOKEN($phraseur, $name, $val, $bal)
114 {
115 $this->valider_motif($phraseur, $name, $val, $bal, _REGEXP_NMTOKEN);
116 }
117
118 function validerAttribut_NMTOKENS($phraseur, $name, $val, $bal)
119 {
120 $this->valider_motif($phraseur, $name, $val, $bal, _REGEXP_NMTOKENS);
121 }
122
123 // http://doc.spip.org/@validerAttribut_ID
124 function validerAttribut_ID($phraseur, $name, $val, $bal)
125 {
126 if (isset($this->ids[$val])) {
127 list($l,$c) = $this->ids[$val];
128 coordonnees_erreur($this, " <p><b>$val</b> "
129 . _T('zxml_valeur_attribut')
130 . " <b>$name</b> "
131 . _T('zxml_de')
132 . " <b>$bal</b> "
133 . _T('zxml_vu')
134 . " (L$l,C$c)");
135 } else {
136 $this->valider_motif($phraseur, $name, $val, $bal, _REGEXP_ID);
137 $this->ids[$val] = array(xml_get_current_line_number($phraseur), xml_get_current_column_number($phraseur));
138 }
139 }
140
141 // http://doc.spip.org/@validerAttribut_IDREF
142 function validerAttribut_IDREF($phraseur, $name, $val, $bal)
143 {
144 $this->idrefs[] = array($val, xml_get_current_line_number($phraseur), xml_get_current_column_number($phraseur));
145 }
146
147 // http://doc.spip.org/@validerAttribut_IDREFS
148 function validerAttribut_IDREFS($phraseur, $name, $val, $bal)
149 {
150 $this->idrefss[] = array($val, xml_get_current_line_number($phraseur), xml_get_current_column_number($phraseur));
151 }
152
153 // http://doc.spip.org/@valider_motif
154 function valider_motif($phraseur, $name, $val, $bal, $motif)
155 {
156 if (!preg_match($motif, $val)) {
157 coordonnees_erreur($this, "<b>$val</b> "
158 . _T('zxml_valeur_attribut')
159 . " <b>$name</b> "
160 . _T('zxml_de')
161 . " <b>$bal</b> "
162 . _T('zxml_non_conforme')
163 . "</p><p>"
164 . "<b>" . $motif . "</b>");
165 }
166 }
167
168 // http://doc.spip.org/@valider_idref
169 function valider_idref($nom, $ligne, $col)
170 {
171 if (!isset($this->ids[$nom]))
172 $this->err[]= array(" <p><b>$nom</b> " . _T('zxml_inconnu_id'), $ligne, $col);
173 }
174
175 // http://doc.spip.org/@valider_passe2
176 function valider_passe2()
177 {
178 if (!$this->err) {
179 foreach ($this->idrefs as $idref) {
180 list($nom, $ligne, $col) = $idref;
181 $this->valider_idref($nom, $ligne, $col);
182 }
183 foreach ($this->idrefss as $idref) {
184 list($noms, $ligne, $col) = $idref;
185 foreach(preg_split('/\s+/', $noms) as $nom)
186 $this->valider_idref($nom, $ligne, $col);
187 }
188 }
189 }
190
191 // http://doc.spip.org/@debutElement
192 function debutElement($phraseur, $name, $attrs)
193 {
194 if ($this->dtc->elements)
195 $this->validerElement($phraseur, $name, $attrs);
196
197 if ($f = $this->process['debut']) $f($this, $name, $attrs);
198 $depth = $this->depth;
199 $this->debuts[$depth] = strlen($this->res);
200 foreach ($attrs as $k => $v) {
201 $this->validerAttribut($phraseur, $k, $v, $name);
202 }
203 }
204
205 // http://doc.spip.org/@finElement
206 function finElement($phraseur, $name)
207 {
208 $depth = $this->depth;
209 $contenu = $this->contenu;
210
211 $n = strlen($this->res);
212 $c = strlen(trim($contenu[$depth]));
213 $k = $this->debuts[$depth];
214
215 $regle = $this->dtc->regles[$name];
216 $vide = ($regle == 'EMPTY');
217 // controler que les balises devant etre vides le sont
218 if ($vide) {
219 if ($n <> ($k + $c))
220 coordonnees_erreur($this, " <p><b>$name</b> "
221 . _T('zxml_nonvide_balise'));
222 // pour les regles PCDATA ou iteration de disjonction, tout est fait
223 } elseif ($regle AND ($regle != '*')) {
224 if ($regle == '+') {
225 // iteration de disjonction non vide: 1 balise au -
226 if ($n == $k) {
227 coordonnees_erreur($this, "<p>\n<b>$name</b> "
228 . _T('zxml_vide_balise'));
229 }
230 } else {
231 $f = $this->fratrie[substr($depth,2)];
232 if (!preg_match($regle, $f)) {
233 coordonnees_erreur($this,
234 " <p>\n<b>$name</b> "
235 . _T('zxml_succession_fils_incorrecte')
236 . '&nbsp;: <b>'
237 . $f
238 . '</b>');
239 }
240 }
241
242 }
243 if ($f = $this->process['fin']) $f($this, $name, $vide);
244 }
245
246 // http://doc.spip.org/@textElement
247 function textElement($phraseur, $data)
248 {
249 if (trim($data)) {
250 $d = $this->depth;
251 $d = $this->ouvrant[$d];
252 preg_match('/^\s*(\S+)/', $d, $m);
253 if ($this->dtc->pcdata[$m[1]]) {
254 coordonnees_erreur($this, " <p><b>". $m[1] . "</b> "
255 . _T('zxml_nonvide_balise') // message a affiner
256 );
257 }
258 }
259 if ($f = $this->process['text']) $f($this, $data);
260 }
261
262 function piElement($phraseur, $target, $data)
263 {
264 if ($f = $this->process['pi']) $f($this, $target, $data);
265 }
266
267 // Denonciation des entitees XML inconnues
268 // Pour contourner le bug de conception de SAX qui ne signale pas si elles
269 // sont dans un attribut, les entites les plus frequentes ont ete
270 // transcodees au prealable (sauf & < > " que SAX traite correctement).
271 // On ne les verra donc pas passer a cette etape, contrairement a ce que
272 // le source de la page laisse legitimement supposer.
273
274 // http://doc.spip.org/@defautElement
275 function defaultElement($phraseur, $data)
276 {
277 if (!preg_match('/^<!--/', $data)
278 AND (preg_match_all('/&([^;]*)?/', $data, $r, PREG_SET_ORDER)))
279 foreach ($r as $m) {
280 list($t,$e) = $m;
281 if (!isset($this->dtc->entites[$e]))
282 coordonnees_erreur($this, " <b>$e</b> "
283 . _T('zxml_inconnu_entite')
284 . ' '
285 );
286 }
287 if ($f = $this->process['default']) $f($this, $data);
288 }
289
290 // http://doc.spip.org/@phraserTout
291 function phraserTout($phraseur, $data)
292 {
293 xml_parsestring($this, $data);
294
295 if (!$this->dtc OR preg_match(',^' . _MESSAGE_DOCTYPE . ',', $data)) {
296 $this->err[]= array('DOCTYPE ?', 0, 0);
297 } else {
298 $this->valider_passe2($this);
299 }
300 }
301
302 var $depth = "";
303 var $res = "";
304 var $err = array();
305 var $contenu = array();
306 var $ouvrant = array();
307 var $reperes = array();
308 var $process = array();
309 var $entete = '';
310 var $page = '';
311 var $dtc = NULL;
312 var $sax = NULL;
313
314 var $ids = array();
315 var $idrefs = array();
316 var $idrefss = array();
317 var $debuts = array();
318 var $fratrie = array();
319
320 }
321
322 // http://doc.spip.org/@emboite_texte
323 function emboite_texte($res, $fonc='',$self='')
324 {
325 include_spip('public/debusquer');
326
327 list($texte, $errs) = $res;
328 if (!$texte)
329 return array(ancre_texte('', array('','')), false);
330 if (!$errs)
331 return array(ancre_texte($texte, array('', '')), true);
332
333 if (!isset($GLOBALS['debug_objets'])) {
334
335 $colors = array('#e0e0f0', '#f8f8ff');
336 $encore = count_occ($errs);
337 $encore2 = array();
338 $fautifs = array();
339
340 $err = '<tr><th>'
341 . _T('numero')
342 . "</th><th>"
343 . _T('occurrence')
344 . "</th><th>"
345 . _T('ligne')
346 . "</th><th>"
347 . _T('colonne')
348 . "</th><th>"
349 . _T('erreur')
350 . "</th></tr>";
351
352 $i = 0;
353 $style = "style='text-align: right; padding-right: 5px'";
354 foreach($errs as $r) {
355 $i++;
356 list($msg, $ligne, $col) = $r;
357 #spip_log("$r = list($msg, $ligne, $col");
358 if (isset($encore2[$msg]))
359 $ref = ++$encore2[$msg];
360 else {$encore2[$msg] = $ref = 1;}
361 $err .= "<tr style='background-color: "
362 . $colors[$i%2]
363 . "'><td $style><a href='#debut_err'>"
364 . $i
365 . "</a></td><td $style>"
366 . "$ref/$encore[$msg]</td>"
367 . "<td $style><a href='#L"
368 . $ligne
369 . "' id='T$i'>"
370 . $ligne
371 . "</a></td><td $style>"
372 . $col
373 . "</td><td>$msg</td></tr>\n";
374 $fautifs[]= array($ligne, $col, $i, $msg);
375 }
376 $err = "<h2 style='text-align: center'>"
377 . $i
378 . "<a href='#fin_err'>"
379 . " "._T('erreur_texte')
380 . "</a></h2><table id='debut_err' style='width: 100%'>"
381 . $err
382 . " </table><a id='fin_err'></a>";
383 return array(ancre_texte($texte, $fautifs), $err);
384 } else {
385 list($msg, $fermant, $ouvrant) = $errs[0];
386 $rf = reference_boucle_debug($fermant, $fonc, $self);
387 $ro = reference_boucle_debug($ouvrant, $fonc, $self);
388 $err = $msg .
389 "<a href='#L" . $fermant . "'>$fermant</a>$rf<br />" .
390 "<a href='#L" . $ouvrant . "'>$ouvrant</a>$ro";
391 return array(ancre_texte($texte, array(array($ouvrant), array($fermant))), $err);
392 }
393 }
394
395 // http://doc.spip.org/@count_occ
396 function count_occ($regs)
397 {
398 $encore = array();
399 foreach($regs as $r) {
400 if (isset($encore[$r[0]]))
401 $encore[$r[0]]++;
402 else $encore[$r[0]] = 1;
403 }
404 return $encore;
405 }
406
407 // Retourne un tableau formee de la page analysee et du tableau des erreurs,
408 // ce dernier ayant comme entrees des sous-tableaux [message, ligne, colonne]
409
410 // http://doc.spip.org/@xml_valider_dist
411 function xml_valider_dist($page, $apply=false, $process=false)
412 {
413
414 $sax = charger_fonction('sax', 'xml');
415 $f = new ValidateurXML();
416 if (!is_array($process))
417 $process = array(
418 'debut' => 'xml_debutElement',
419 'fin' => 'xml_finElement',
420 'text' => 'xml_textElement',
421 'pi' => 'xml_piElement',
422 'default' => 'xml_defaultElement'
423 );
424
425 $f->process = $process;
426 $sax($page, $apply, $f);
427 $page = $f->err ? $f->page : $f->res;
428 return array($f->entete . $page, $f->err);
429 }
430 ?>