[SPIP][PLUGINS] v3.0-->v3.2
[lhc/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-2017 *
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')) {
14 return;
15 }
16
17 /**
18 * Validateur XML en deux passes, fondé sur SAX pour la première
19 *
20 * @note
21 * Faudrait faire deux classes car pour la première passe
22 * on a les memes methodes et variables que l'indenteur
23 **/
24 class ValidateurXML {
25
26 // http://code.spip.net/@validerElement
27 public function validerElement($phraseur, $name, $attrs) {
28 if (!($p = isset($this->dtc->elements[$name]))) {
29 if ($p = strpos($name, ':')) {
30 $name = substr($name, $p + 1);
31 $p = isset($this->dtc->elements[$name]);
32 }
33 if (!$p) {
34 coordonnees_erreur($this, " <b>$name</b>&nbsp;: "
35 . _T('zxml_inconnu_balise'));
36
37 return;
38 }
39 }
40 // controler les filles illegitimes, ca suffit
41 $depth = $this->depth;
42 $ouvrant = $this->ouvrant;
43 #spip_log("trouve $name apres " . $ouvrant[$depth]);
44 if (isset($ouvrant[$depth])) {
45 if (preg_match('/^\s*(\w+)/', $ouvrant[$depth], $r)) {
46 $pere = $r[1];
47 #spip_log("pere $pere");
48 if (isset($this->dtc->elements[$pere])) {
49 $fils = $this->dtc->elements[$pere];
50 #spip_log("rejeton $name fils " . @join(',',$fils));
51 if (!($p = @in_array($name, $fils))) {
52 if ($p = strpos($name, ':')) {
53 $p = substr($name, $p + 1);
54 $p = @in_array($p, $fils);
55 }
56 }
57 if (!$p) {
58 $bons_peres = @join('</b>, <b>', $this->dtc->peres[$name]);
59 coordonnees_erreur($this, " <b>$name</b> "
60 . _T('zxml_non_fils')
61 . ' <b>'
62 . $pere
63 . '</b>'
64 . (!$bons_peres ? ''
65 : ('<p style="font-size: 80%"> ' . _T('zxml_mais_de') . ' <b>' . $bons_peres . '</b></p>')));
66 } elseif ($this->dtc->regles[$pere][0] == '/') {
67 $frat = substr($depth, 2);
68 if (!isset($this->fratrie[$frat])) {
69 $this->fratrie[$frat] = '';
70 }
71 $this->fratrie[$frat] .= "$name ";
72 }
73 }
74 }
75 }
76 // Init de la suite des balises a memoriser si regle difficile
77 if ($this->dtc->regles[$name] and $this->dtc->regles[$name][0] == '/') {
78 $this->fratrie[$depth] = '';
79 }
80 if (isset($this->dtc->attributs[$name])) {
81 foreach ($this->dtc->attributs[$name] as $n => $v) {
82 if (($v[1] == '#REQUIRED') and (!isset($attrs[$n]))) {
83 coordonnees_erreur($this, " <b>$n</b>"
84 . '&nbsp;:&nbsp;'
85 . _T('zxml_obligatoire_attribut')
86 . " <b>$name</b>");
87 }
88 }
89 }
90 }
91
92 // http://code.spip.net/@validerAttribut
93 public function validerAttribut($phraseur, $name, $val, $bal) {
94 // Si la balise est inconnue, eviter d'insister
95 if (!isset($this->dtc->attributs[$bal])) {
96 return;
97 }
98
99 $a = $this->dtc->attributs[$bal];
100 if (!isset($a[$name])) {
101 $bons = join(', ', array_keys($a));
102 if ($bons) {
103 $bons = " title=' " .
104 _T('zxml_connus_attributs') .
105 '&nbsp;: ' .
106 $bons .
107 "'";
108 }
109 $bons .= " style='font-weight: bold'";
110 coordonnees_erreur($this, " <b>$name</b> "
111 . _T('zxml_inconnu_attribut') . ' ' . _T('zxml_de')
112 . " <a$bons>$bal</a> ("
113 . _T('zxml_survoler')
114 . ")");
115 } else {
116 $type = $a[$name][0];
117 if (!preg_match('/^\w+$/', $type)) {
118 $this->valider_motif($phraseur, $name, $val, $bal, $type);
119 } else {
120 if (method_exists($this, $f = 'validerAttribut_' . $type)) {
121 $this->$f($phraseur, $name, $val, $bal);
122 }
123 }
124 # else spip_log("$type type d'attribut inconnu");
125 }
126 }
127
128 public function validerAttribut_NMTOKEN($phraseur, $name, $val, $bal) {
129 $this->valider_motif($phraseur, $name, $val, $bal, _REGEXP_NMTOKEN);
130 }
131
132 public function validerAttribut_NMTOKENS($phraseur, $name, $val, $bal) {
133 $this->valider_motif($phraseur, $name, $val, $bal, _REGEXP_NMTOKENS);
134 }
135
136 // http://code.spip.net/@validerAttribut_ID
137 public function validerAttribut_ID($phraseur, $name, $val, $bal) {
138 if (isset($this->ids[$val])) {
139 list($l, $c) = $this->ids[$val];
140 coordonnees_erreur($this, " <p><b>$val</b> "
141 . _T('zxml_valeur_attribut')
142 . " <b>$name</b> "
143 . _T('zxml_de')
144 . " <b>$bal</b> "
145 . _T('zxml_vu')
146 . " (L$l,C$c)");
147 } else {
148 $this->valider_motif($phraseur, $name, $val, $bal, _REGEXP_ID);
149 $this->ids[$val] = array(xml_get_current_line_number($phraseur), xml_get_current_column_number($phraseur));
150 }
151 }
152
153 // http://code.spip.net/@validerAttribut_IDREF
154 public function validerAttribut_IDREF($phraseur, $name, $val, $bal) {
155 $this->idrefs[] = array($val, xml_get_current_line_number($phraseur), xml_get_current_column_number($phraseur));
156 }
157
158 // http://code.spip.net/@validerAttribut_IDREFS
159 public function validerAttribut_IDREFS($phraseur, $name, $val, $bal) {
160 $this->idrefss[] = array($val, xml_get_current_line_number($phraseur), xml_get_current_column_number($phraseur));
161 }
162
163 // http://code.spip.net/@valider_motif
164 public function valider_motif($phraseur, $name, $val, $bal, $motif) {
165 if (!preg_match($motif, $val)) {
166 coordonnees_erreur($this, "<b>$val</b> "
167 . _T('zxml_valeur_attribut')
168 . " <b>$name</b> "
169 . _T('zxml_de')
170 . " <b>$bal</b> "
171 . _T('zxml_non_conforme')
172 . "</p><p>"
173 . "<b>" . $motif . "</b>");
174 }
175 }
176
177 // http://code.spip.net/@valider_idref
178 public function valider_idref($nom, $ligne, $col) {
179 if (!isset($this->ids[$nom])) {
180 $this->err[] = array(" <p><b>$nom</b> " . _T('zxml_inconnu_id'), $ligne, $col);
181 }
182 }
183
184 // http://code.spip.net/@valider_passe2
185 public function valider_passe2() {
186 if (!$this->err) {
187 foreach ($this->idrefs as $idref) {
188 list($nom, $ligne, $col) = $idref;
189 $this->valider_idref($nom, $ligne, $col);
190 }
191 foreach ($this->idrefss as $idref) {
192 list($noms, $ligne, $col) = $idref;
193 foreach (preg_split('/\s+/', $noms) as $nom) {
194 $this->valider_idref($nom, $ligne, $col);
195 }
196 }
197 }
198 }
199
200 // http://code.spip.net/@debutElement
201 public function debutElement($phraseur, $name, $attrs) {
202 if ($this->dtc->elements) {
203 $this->validerElement($phraseur, $name, $attrs);
204 }
205
206 if ($f = $this->process['debut']) {
207 $f($this, $name, $attrs);
208 }
209 $depth = $this->depth;
210 $this->debuts[$depth] = strlen($this->res);
211 foreach ($attrs as $k => $v) {
212 $this->validerAttribut($phraseur, $k, $v, $name);
213 }
214 }
215
216 // http://code.spip.net/@finElement
217 public function finElement($phraseur, $name) {
218 $depth = $this->depth;
219 $contenu = $this->contenu;
220
221 $n = strlen($this->res);
222 $c = strlen(trim($contenu[$depth]));
223 $k = $this->debuts[$depth];
224
225 $regle = isset($this->dtc->regles[$name]) ? $this->dtc->regles[$name] : false;
226 $vide = ($regle == 'EMPTY');
227 // controler que les balises devant etre vides le sont
228 if ($vide) {
229 if ($n <> ($k + $c)) {
230 coordonnees_erreur($this, " <p><b>$name</b> " . _T('zxml_nonvide_balise'));
231 }
232 // pour les regles PCDATA ou iteration de disjonction, tout est fait
233 } elseif ($regle and ($regle != '*')) {
234 if ($regle == '+') {
235 // iteration de disjonction non vide: 1 balise au -
236 if ($n == $k) {
237 coordonnees_erreur($this, "<p>\n<b>$name</b> "
238 . _T('zxml_vide_balise'));
239 }
240 } else {
241 $f = isset($this->fratrie[substr($depth, 2)]) ? $this->fratrie[substr($depth, 2)] : null;
242 if (is_null($f) or !preg_match($regle, $f)) {
243 coordonnees_erreur($this,
244 " <p>\n<b>$name</b> "
245 . _T('zxml_succession_fils_incorrecte')
246 . '&nbsp;: <b>'
247 . $f
248 . '</b>');
249 }
250 }
251
252 }
253 if ($f = $this->process['fin']) {
254 $f($this, $name, $vide);
255 }
256 }
257
258 // http://code.spip.net/@textElement
259 public function textElement($phraseur, $data) {
260 if (trim($data)) {
261 $d = $this->depth;
262 $d = $this->ouvrant[$d];
263 preg_match('/^\s*(\S+)/', $d, $m);
264 if (isset($this->dtc->pcdata[$m[1]]) and ($this->dtc->pcdata[$m[1]])) {
265 coordonnees_erreur($this, " <p><b>" . $m[1] . "</b> "
266 . _T('zxml_nonvide_balise') // message a affiner
267 );
268 }
269 }
270 if ($f = $this->process['text']) {
271 $f($this, $data);
272 }
273 }
274
275 public function piElement($phraseur, $target, $data) {
276 if ($f = $this->process['pi']) {
277 $f($this, $target, $data);
278 }
279 }
280
281 // Denonciation des entitees XML inconnues
282 // Pour contourner le bug de conception de SAX qui ne signale pas si elles
283 // sont dans un attribut, les entites les plus frequentes ont ete
284 // transcodees au prealable (sauf & < > " que SAX traite correctement).
285 // On ne les verra donc pas passer a cette etape, contrairement a ce que
286 // le source de la page laisse legitimement supposer.
287
288 // http://code.spip.net/@defautElement
289 public function defaultElement($phraseur, $data) {
290 if (!preg_match('/^<!--/', $data)
291 and (preg_match_all('/&([^;]*)?/', $data, $r, PREG_SET_ORDER))
292 ) {
293 foreach ($r as $m) {
294 list($t, $e) = $m;
295 if (!isset($this->dtc->entites[$e])) {
296 coordonnees_erreur($this, " <b>$e</b> "
297 . _T('zxml_inconnu_entite')
298 . ' '
299 );
300 }
301 }
302 }
303 if (isset($this->process['default']) and ($f = $this->process['default'])) {
304 $f($this, $data);
305 }
306 }
307
308 // http://code.spip.net/@phraserTout
309 public function phraserTout($phraseur, $data) {
310 xml_parsestring($this, $data);
311
312 if (!$this->dtc or preg_match(',^' . _MESSAGE_DOCTYPE . ',', $data)) {
313 $this->err[] = array('DOCTYPE ?', 0, 0);
314 } else {
315 $this->valider_passe2($this);
316 }
317 }
318
319 /**
320 * Constructeur
321 *
322 * @param array $process ?
323 **/
324 public function __construct($process = array()) {
325 if (is_array($process)) {
326 $this->process = $process;
327 }
328 }
329
330 public $ids = array();
331 public $idrefs = array();
332 public $idrefss = array();
333 public $debuts = array();
334 public $fratrie = array();
335
336 public $dtc = null;
337 public $sax = null;
338 public $depth = "";
339 public $entete = '';
340 public $page = '';
341 public $res = "";
342 public $err = array();
343 public $contenu = array();
344 public $ouvrant = array();
345 public $reperes = array();
346 public $process = array(
347 'debut' => 'xml_debutElement',
348 'fin' => 'xml_finElement',
349 'text' => 'xml_textElement',
350 'pi' => 'xml_piElement',
351 'default' => 'xml_defaultElement'
352 );
353 }
354
355
356 /**
357 * Retourne une structure ValidateurXML, dont le champ "err" est un tableau
358 * ayant comme entrees des sous-tableaux [message, ligne, colonne]
359 *
360 **/
361 function xml_valider_dist($page, $apply = false, $process = false, $doctype = '', $charset = null) {
362 $f = new ValidateurXML($process);
363 $sax = charger_fonction('sax', 'xml');
364
365 return $sax($page, $apply, $f, $doctype, $charset);
366 }