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