[SPIP] v3.2.1-->v3.2.2
[lhc/web/www.git] / www / ecrire / xml / sax.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2019 *
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 include_spip('inc/charsets');
18 include_spip('xml/interfaces');
19
20 /**
21 * Encoder les entites
22 *
23 * @param string $texte
24 * @return string
25 */
26 function xml_entites_html($texte) {
27 if (!is_string($texte) or !$texte
28 or strpbrk($texte, "&\"'<>") == false
29 ) {
30 return $texte;
31 }
32
33 if (!function_exists('spip_htmlspecialchars')) {
34 include_spip("inc/filtres_mini");
35 }
36 $texte = spip_htmlspecialchars($texte, ENT_QUOTES);
37
38 return $texte;
39 }
40
41 // http://code.spip.net/@xml_debutElement
42 function xml_debutElement($phraseur, $name, $attrs) {
43 $depth = $phraseur->depth;
44
45 $t = isset($phraseur->ouvrant[$depth]) ? $phraseur->ouvrant[$depth] : ' ';
46 // espace initial signifie: deja integree au resultat
47 if ($t[0] != ' ') {
48 $phraseur->res .= '<' . $t . '>';
49 $phraseur->ouvrant[$depth] = ' ' . $t;
50 }
51 $t = $phraseur->contenu[$depth];
52 // n'indenter que s'il y a un separateur avant
53 $phraseur->res .= preg_replace("/[\n\t ]+$/", "\n$depth", $t);
54 $phraseur->contenu[$depth] = "";
55 $att = '';
56 $sep = ' ';
57 foreach ($attrs as $k => $v) {
58 $delim = strpos($v, "'") === false ? "'" : '"';
59 $val = xml_entites_html($v);
60 $att .= $sep . $k . "=" . $delim
61 . ($delim !== '"' ? str_replace('&quot;', '"', $val) : $val)
62 . $delim;
63 $sep = "\n $depth";
64 }
65 $phraseur->depth .= ' ';
66 $phraseur->contenu[$phraseur->depth] = "";
67 $phraseur->ouvrant[$phraseur->depth] = $name . $att;
68 $phraseur->reperes[$phraseur->depth] = xml_get_current_line_number($phraseur->sax);
69 }
70
71 // http://code.spip.net/@xml_finElement
72 function xml_finElement($phraseur, $name, $fusion_bal = false) {
73 $ouv = $phraseur->ouvrant[$phraseur->depth];
74
75 if ($ouv[0] != ' ') {
76 $phraseur->ouvrant[$phraseur->depth] = ' ' . $ouv;
77 } else {
78 $ouv = "";
79 }
80 $t = $phraseur->contenu[$phraseur->depth];
81 $phraseur->depth = substr($phraseur->depth, 2);
82 $t = preg_replace("/[\n\t ]+$/", "\n" . $phraseur->depth, $t);
83
84 // fusion <balise></balise> en <balise />.
85 // ATTENTION, certains clients http croient que fusion ==> pas d'atttributs
86 // en particulier pour les balises Script et A.
87 // en presence d'attributs ne le faire que si la DTD est dispo et d'accord
88 // (param fusion_bal)
89
90 if ($t || (($ouv != $name) and !$fusion_bal)) {
91 $phraseur->res .= ($ouv ? ('<' . $ouv . '>') : '') . $t . "</" . $name . ">";
92 } else {
93 $phraseur->res .= ($ouv ? ('<' . $ouv . ' />') : ("</" . $name . ">"));
94 }
95 }
96
97 // http://code.spip.net/@xml_textElement
98 function xml_textElement($phraseur, $data) {
99 $depth = $phraseur->depth;
100 $phraseur->contenu[$depth] .= preg_match('/^script/', $phraseur->ouvrant[$depth])
101 ? $data
102 : xml_entites_html($data);
103 }
104
105 function xml_piElement($phraseur, $target, $data) {
106 $depth = $phraseur->depth;
107
108 if (strtolower($target) != "php") {
109 $phraseur->contenu[$depth] .= $data;
110 } else {
111 ob_start();
112 eval($data);
113 $data = ob_get_contents();
114 ob_end_clean();
115 $phraseur->contenu[$depth] .= $data;
116 }
117 }
118
119
120 // http://code.spip.net/@xml_defautElement
121 function xml_defaultElement($phraseur, $data) {
122 $depth = $phraseur->depth;
123
124 if (!isset($phraseur->contenu[$depth])) {
125 $phraseur->contenu[$depth] = '';
126 }
127 $phraseur->contenu[$depth] .= $data;
128 }
129
130 // http://code.spip.net/@xml_parsestring
131 function xml_parsestring($phraseur, $data) {
132 $phraseur->contenu[$phraseur->depth] = '';
133
134 if (!xml_parse($phraseur->sax, $data, true)) {
135 coordonnees_erreur($phraseur,
136 xml_error_string(xml_get_error_code($phraseur->sax))
137 . "<br />\n" .
138 (!$phraseur->depth ? '' :
139 ('(' .
140 _T('erreur_balise_non_fermee') .
141 " <tt>" .
142 $phraseur->ouvrant[$phraseur->depth] .
143 "</tt> " .
144 _T('ligne') .
145 " " .
146 $phraseur->reperes[$phraseur->depth] .
147 ") <br />\n")));
148 }
149 }
150
151 // http://code.spip.net/@coordonnees_erreur
152 function coordonnees_erreur($phraseur, $msg) {
153 $entete_length = substr_count($phraseur->entete, "\n");
154 $phraseur->err[] = array(
155 $msg,
156 xml_get_current_line_number($phraseur->sax) + $entete_length,
157 xml_get_current_column_number($phraseur->sax)
158 );
159 }
160
161 // http://code.spip.net/@xml_sax_dist
162 function xml_sax_dist($page, $apply = false, $phraseur = null, $doctype = '', $charset = null) {
163 if (is_null($charset)) {
164 $charset = $GLOBALS['meta']['charset'];
165 }
166 if ($apply) {
167 ob_start();
168 if (is_array($apply)) {
169 $r = call_user_func_array($page, $apply);
170 } else {
171 $r = $page();
172 }
173 $page = ob_get_contents();
174 ob_end_clean();
175 // fonction sans aucun "echo", ca doit etre le resultat
176 if (!$page) {
177 $page = $r;
178 }
179 }
180
181 if (!$page) {
182 return '';
183 }
184 // charger la DTD et transcoder les entites,
185 // et escamoter le doctype que sax mange en php5 mais pas en php4
186 if (!$doctype) {
187 if (!$r = analyser_doctype($page)) {
188 $page = _MESSAGE_DOCTYPE . _DOCTYPE_ECRIRE
189 . preg_replace(_REGEXP_DOCTYPE, '', $page);
190 $r = analyser_doctype($page);
191 }
192 list($entete, $avail, $grammaire, $rotlvl) = array_pad($r, 4, null);
193 $page = substr($page, strlen($entete));
194 } else {
195 $avail = 'SYSTEM';
196 $grammaire = $doctype;
197 $rotlvl = basename($grammaire);
198 }
199
200 include_spip('xml/analyser_dtd');
201 $dtc = charger_dtd($grammaire, $avail, $rotlvl);
202 $page = sax_bug($page, $dtc, $charset);
203
204 // compatibilite Tidy espace public
205 if (!$phraseur) {
206 $indenter_xml = charger_fonction('indenter', 'xml');
207
208 return $indenter_xml($page, $apply);
209 }
210
211 $xml_parser = xml_parser_create($charset);
212
213 xml_set_element_handler($xml_parser,
214 array($phraseur, "debutElement"),
215 array($phraseur, "finElement"));
216
217 xml_set_character_data_handler($xml_parser,
218 array($phraseur, "textElement"));
219
220 xml_set_processing_instruction_handler($xml_parser,
221 array($phraseur, 'piElement'));
222
223 xml_set_default_handler($xml_parser,
224 array($phraseur, "defaultElement"));
225
226 xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, false);
227
228 $phraseur->sax = $xml_parser;
229 if (isset($entete)) {
230 $phraseur->entete = $entete;
231 }
232 $phraseur->page = $page;
233 $phraseur->dtc = $dtc;
234 $phraseur->phraserTout($xml_parser, $page);
235 xml_parser_free($xml_parser);
236 $phraseur->sax = '';
237
238 return $phraseur;
239 }
240
241 // SAX ne dit pas si une Entite est dans un attribut ou non.
242 // Les eliminer toutes sinon celles des attributs apparaissent en zone texte!
243 // Celles fondamentales pour la lecture (lt gt quot amp) sont conservees
244 // (d'ailleurs SAX ne les considere pas comme des entites dans un attribut)
245 // Si la DTD est dispo, on va chercher les entites dedans
246 // sinon on se rabat sur ce qu'en connait SPIP en standard.
247
248 // http://code.spip.net/@sax_bug
249 function sax_bug($data, $dtc, $charset = null) {
250 if (is_null($charset)) {
251 $charset = $GLOBALS['meta']['charset'];
252 }
253
254 if ($dtc) {
255 $trans = array();
256
257 foreach ($dtc->entites as $k => $v) {
258 if (!strpos(" amp lt gt quot ", $k)) {
259 $trans["&$k;"] = $v;
260 }
261 }
262 $data = strtr($data, $trans);
263 } else {
264 $data = html2unicode($data, true);
265 }
266
267 return unicode2charset($data, $charset);
268 }
269
270 // Retirer < ? xml... ? > et autre PI, ainsi que les commentaires en debut
271 // afin de reperer le Doctype et le decomposer selon:
272 // http://www.freebsd.org/doc/fr_FR.ISO8859-1/books/fdp-primer/sgml-primer-doctype-declaration.html
273 // Si pas de Doctype et premiere balise = RSS prendre la doctype RSS 0.91:
274 // les autres formats RSS n'ont pas de DTD,
275 // mais un XML Schema que SPIP ne fait pas encore lire.
276 // http://code.spip.net/@analyser_doctype
277 function analyser_doctype($data) {
278 if (!preg_match(_REGEXP_DOCTYPE, $data, $page)) {
279 if (preg_match(_REGEXP_XML, $data, $page)) {
280 list(, $entete, $topelement) = $page;
281 if ($topelement == 'rss') {
282 return array(
283 $entete,
284 'PUBLIC',
285 _DOCTYPE_RSS,
286 'rss-0.91.dtd'
287 );
288 } else {
289 $dtd = $topelement . '.dtd';
290 $f = find_in_path($dtd);
291 if (file_exists($f)) {
292 return array($entete, 'SYSTEM', $f, $dtd);
293 }
294 }
295 }
296 spip_log("Dtd pas vu pour " . substr($data, 0, 100));
297
298 return array();
299 }
300 list($entete, , $topelement, $avail, $suite) = $page;
301
302 if (!preg_match('/^"([^"]*)"\s*(.*)$/', $suite, $r)) {
303 if (!preg_match("/^'([^']*)'\s*(.*)$/", $suite, $r)) {
304 return array();
305 }
306 }
307 list(, $rotlvl, $suite) = $r;
308
309 if (!$suite) {
310 if ($avail != 'SYSTEM') {
311 return array();
312 }
313 $grammaire = $rotlvl;
314 $rotlvl = '';
315 } else {
316 if (!preg_match('/^"([^"]*)"\s*$/', $suite, $r)) {
317 if (!preg_match("/^'([^']*)'\s*$/", $suite, $r)) {
318 return array();
319 }
320 }
321
322 $grammaire = $r[1];
323 }
324
325 return array($entete, $avail, $grammaire, $rotlvl);
326 }