changed git call from https to git readonly
[atutor.git] / mods / wiki / plugins / lib / xml.php
1 <?php
2 /*
3    Data-oriented XML parsing code.
4 */
5
6
7 #-- simplification wrapper around XML parser -------------------------------
8 #  namespace-aware, folds URIs down to expected xmlnamespace prefixes;
9 #  this is mostly handled internally, not by php-xml
10 #
11 class easy_xml {
12
13    var $xp;
14    var $xml;
15
16    #-- general xmlns= mappings -> we don't want to deal with URIs
17    #   but get shortened abbreviations instead (and not unpredictable ones)
18    var $xmlns = array(
19       "http://www.w3.org/1999/02/22-rdf-syntax-ns#" => "rdf",
20       "http://purl.org/dc/elements/1.1/" => "dc",
21       "http://purl.org/rss/1.0/modules/wiki/" => "rss-wiki",
22       "http://purl.org/rss/1.0/" => "rss",   // (at least its similar enough)
23       "http://purl.org/atom/ns#" => "atom",
24       "DAV:" => "dav",
25 #<eee># "urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" => "ms-dav-time",
26       "http://xmlns.com/foaf/0.1/" => "foaf",
27    );
28    #-- final xmlns= qualifier down-mapping,
29    #   think of this as "local xmlns:tag renaming"
30    var $xmlns2;
31
32    #-- tagname mappings
33    var $map = array(
34       //"xmlns-prefix:weird-tag-name" => "desired",
35    );
36
37    #-- tag name stack
38    var $current, $parent, $stack=array();
39
40
41    #-- prepare parser
42    function easy_xml($xml="", $charset=NULL, $targetcoding="ISO-8859-1") {
43
44       #-- init (no "_ns" parser, because we can handle that better)
45       $this->xp = xml_parser_create($charset);
46       xml_parser_set_option($this->xp, XML_OPTION_CASE_FOLDING, false);
47       xml_parser_set_option($this->xp, XML_OPTION_SKIP_WHITE, true);
48       xml_parser_set_option($this->xp, XML_OPTION_TARGET_ENCODING, $targetcoding);
49       $this->xml = &$xml;
50
51       #-- handlers
52       xml_set_character_data_handler($this->xp, array(&$this,"cdata"));
53       xml_set_element_handler($this->xp, array(&$this,"start"), array(&$this,"end"));
54       
55       #-- we take care of namespaces ourselves
56       $this->xmlns2 = array();
57    }
58
59
60    #-- action 
61    function parse($more_xml="") {
62       $r = xml_parse($this->xp, trim($this->xml . $more_xml), $_is_final=TRUE);
63       if ($e = xml_get_error_code($this->xp)) {
64          trigger_error("XML error #$e: " . xml_error_string($e), E_WARNING);
65       }
66       unset($this->xml);
67       unset($this->stack);
68       unset($this->current);
69       unset($this->parent);
70       unset($this->l);
71       unset($this->map);
72       unset($this->as_content);
73       unset($this->as_list);
74       $r = xml_parser_free($this->xp) && ($r);
75       unset($this->xp);
76       return($r);
77    }
78
79
80    #-- map ugly XMLNS urls to known identifiers/internal representation
81    function tag($tag) {
82       while (($l = strrpos($tag, ":"))
83          and ($prefix = substr($tag, 0, $l))
84          and (isset($this->xmlns2[$prefix])) )
85       {
86          $ns = $this->xmlns2[$prefix];
87          if ($ns == $prefix) {
88             break;
89          }
90          
91          #-- strip known xmlns qualifier/moniker
92          $tag = substr($tag, $l+1);
93          if ($ns) {
94             $tag = $ns . ":" . $tag;
95          }
96          else break;
97       }
98
99       #-- and also rewrite to preferred tag names
100       if (isset($this->map[$tag])) {
101          $tag = $this->map[$tag];
102       }
103       
104       return($tag);
105    }
106
107
108    #-- log <opening> tags
109    function start($xp, &$tag, &$attr) {
110       #-- normalize attributes and discover namespaces
111       if ($attr) {
112          $a = array();
113          foreach ($attr as $i=>$v) {
114             if (strncmp($i, "xmlns:", 6) == 0) {
115                $this->xmlns($xp, substr($i, 6), $v);
116             }
117             else {
118                $i = $this->tag($i);
119                $a[$i] = $v;
120             }
121          }
122          $attr = $a;
123       }
124
125       #-- normalize tag names
126       $tag = $this->tag($tag);
127       
128       #-- track where we are
129       if ($this->current) {
130          $this->stack[] = $this->current;
131       }
132       $this->parent = $this->current;
133       $this->current = $tag;
134    }
135
136
137    #-- track </end> tags
138    function end($xp, &$tag) {
139       $tag = $this->tag($tag);
140       $this->current = $this->parent;
141       $this->parent = array_pop($this->stack);
142    }
143
144
145    #-- data extr
146    function cdata($xp, $data) {
147    }
148
149
150    #-- we handle namespaces ourselves to decipher silly URIs and
151    #   get rid of uncommon prefixes
152    function xmlns($xp, $short, $uri) {
153       #-- setup back-mapping to OUR preferred xmlnamespace abbr
154       if ($desired = $this->xmlns[$uri]) {
155          if (($short != $desired)
156          and (isset($this->xmlns2["rw:"][$short])
157           or !isset($this->xmlns2[$short]))  )
158          {
159             $this->xmlns2[$short] = $desired;
160             $this->xmlns2["rw:"][$short] = 1;  // mark as overwritable entry
161          }
162          // prevents 1:1-conversions
163       }
164       #-- keep it
165       else {
166          $this->xmlns[$uri] = $short;  // log
167        //  $this->xmlns2[$short] = $short;
168       }
169    }
170
171
172 }//class
173
174
175
176 #-- simple data/array XML file --------------------------------------------
177 #  can decode only two-level data containers
178 class easy_xml_data extends easy_xml {
179
180    #-- which elements always to expect multiple times (= becomes list)
181    var $as_list = array();
182    
183    #-- which attributes to transmove into cdata
184    var $as_content = array();
185
186
187    #-- append string data to collection
188    function cdata($xp, $data) {
189       if (trim($data)) {
190          $this->l[$this->parent][$this->current] = $data;
191       }
192    }
193
194
195    #-- extract single blocks from array collection list
196    function end($xp, $tag) {
197
198       parent::end($xp, $tag);
199
200       if (isset($this->l[$tag])) {
201          #-- make list
202          if (isset($this->{$tag}) || in_array($tag, $this->as_list)) {
203             #-- convert into list
204             if (isset($this->{$tag}) && !isset($this->{$tag}[0])) {
205                $this->{$tag} = array(
206                   0 => $this->{$tag}
207                );
208             }
209             $this->{$tag}[] = $this->l[$tag];
210          }
211          #-- no list (asis)
212          else {
213             $this->{$tag} = $this->l[$tag];
214          }
215          unset($this->l[$tag]);
216       }
217    }
218
219
220    #-- converts certain expected tag attributes into cdata
221    function start($xp, &$tag, &$attr) {
222       parent::start($xp, $tag, $attr);
223       foreach ($attr as $i=>$content) {
224          if ($this->as_content[$tag]==$i) {
225             $this->cdata($xp, $content);
226          }
227       }
228    }
229
230 }//class
231
232
233
234 #-- special simplifications for RSS- and Atom- XML files --------------------
235 class easy_xml_rss extends easy_xml_data {
236
237    #-- add a few mappings to make RSS and Atom look similar
238    function easy_xml_rss($xml, $cs=NULL, $tc="ISO-8859-1") {
239       parent::easy_xml($xml, $cs, $tc);
240       // kill some of _our_ namespace prefixes
241       $this->xmlns2 += array(
242 //         "rss-wiki" => "",
243          "rss" => "",
244          "atom" => "",
245          "dc" => "",
246       );
247       // rename tags (mainly for Atom)
248       $this->map += array(
249          // atom:
250          "feed" => "channel",
251          "entry" => "item",
252          "content" => "description",
253          "id" => "guid",
254          // dc:
255          "date" => "pubDate",
256       );
257       // tag attributes to auto-convert into content
258       $this->as_content += array(
259          "link"=>"href",
260       );
261       // always make list-array of items
262       $this->as_list = array(
263          "item",
264       );
265    }
266 }//class
267
268
269
270
271 #-- decode into structs (fast, but annoying to work with) ------------------
272 class ewiki_xml_fast {
273
274    var $data;
275    var $tags;
276
277    #-- do
278    function parse() {
279       xml_parse_into_struct($this->xp, $this->xml, $this->data, $this->tags);
280       
281       #-- fix xmlns, tag names
282       $data = &$this->data;
283       $tags = &$this->tags;
284       foreach ($data as $i=>$d) {
285          if ($new = $this->tag($data[$i]["tag"])) {
286             $data[$i]["tag"] = $new;
287          }
288          if (isset($data[$i]["attributes"])) {
289             foreach ($data[$i]["attributes"] as $key=>$val) {
290                if ($new = ewiki_short_xmlns($key, $xmlns)) {
291                   unset($data[$i]["attributes"][$key]);
292                   $data[$i]["attributes"][$new] = $val;
293                }
294             }
295          }
296       }
297       foreach ($tags as $key=>$val) {
298          if ($new = $this->tag($key)) {
299             unset($tags[$key]);
300             $tags[$new] = $val;
301          }
302       }
303
304       unset($this->xml);
305    }
306 }//class
307
308
309 ?>