Fix #8052 - fixing pdf
[pear] / XML / Tree / Node.php
1 <?php
2 // +----------------------------------------------------------------------+
3 // | PEAR :: XML_Tree                                                     |
4 // +----------------------------------------------------------------------+
5 // | Copyright (c) 1997-2003 The PHP Group                                |
6 // +----------------------------------------------------------------------+
7 // | This source file is subject to version 2.02 of the PHP license,      |
8 // | that is bundled with this package in the file LICENSE, and is        |
9 // | available at through the world-wide-web at                           |
10 // | http://www.php.net/license/2_02.txt.                                 |
11 // | If you did not receive a copy of the PHP license and are unable to   |
12 // | obtain it through the world-wide-web, please send a note to          |
13 // | license@php.net so we can mail you a copy immediately.               |
14 // +----------------------------------------------------------------------+
15 // | Authors: Bernd Römer <berndr@bonn.edu>                               |
16 // |          Sebastian Bergmann <sb@sebastian-bergmann.de>               |
17 // |          Christian Kühn <ck@chkuehn.de> (escape xml entities)        |
18 // |          Michele Manzato <michele.manzato@verona.miz.it>             |
19 // +----------------------------------------------------------------------+
20 //
21 // $Id: Node.php,v 1.18 2003/09/11 19:03:27 cox Exp $
22 //
23
24 /**
25 * PEAR::XML_Tree_Node
26 *
27 * @author  Bernd Römer <berndr@bonn.edu>
28 * @package XML_Tree
29 * @version 1.0  16-Aug-2001
30 */
31 #[AllowDynamicProperties] 
32
33 class XML_Tree_Node {
34     /**
35     * Attributes of this node
36     *
37     * @var  array
38     */
39     var $attributes;
40
41     /**
42     * Children of this node
43     *
44     * @var  array
45     */
46     var $children;
47
48     /**
49     * Content (text) of this node
50     *
51     * @var  string
52     */
53     var $content;
54
55     /**
56     * Name of the node
57     *
58     * @var  string
59     */
60     var $name;
61     
62     var $lineno;
63     /**
64     * Constructor
65     *
66     * @param  string    name            Node name
67     * @param  string    content         Node content (text)
68     * @param  array     attributes      Attribute-hash for the node
69     */
70     function __construct($name, $content = '', $attributes = array(), $lineno = null)
71     {
72         $this->name = $name;
73         if ($content !== null) {
74             $this->setContent($content);
75         }
76         $this->attributes = $attributes;
77         $this->children   = array();
78         $this->lineno     = $lineno;
79     }
80
81     /**
82     * Append a child node to this node, after all other nodes
83     *
84     * @param mixed      child           Child to insert (XML_Tree or XML_Tree_Node),
85     *                                   or name of child node
86     * @param string     content         Content (text) for the new node (only if
87     *                                   $child is the node name)
88     * @param array      attributes      Attribute-hash for new node
89     *
90     * @return object  reference to new child node
91     * @access public
92     */
93     function &addChild($child, $content = '', $attributes = array(), $lineno = null)
94     {
95         $index = sizeof($this->children);
96
97         if (is_object($child)) {
98             if (strtolower(get_class($child)) == 'xml_tree_node') {
99                 $this->children[$index] = $child;
100             }
101
102             if (strtolower(get_class($child)) == 'xml_tree' && isset($child->root)) {
103                 $this->children[$index] = $child->root->getElement();
104             }
105         } else {
106             $this->children[$index] = new XML_Tree_Node($child, $content, $attributes, $lineno);
107         }
108
109         return $this->children[$index];
110     }
111
112     /**
113     * Get a copy of this node by clone this node and all of its children,
114     * recursively.
115     *
116     * @return object    Reference to the cloned copy.
117     * @access public
118     */
119     function &cloneNode()
120     {
121         $clone = new XML_Tree_Node($this->name,$this->content,$this->attributes);
122
123         $max_child=count($this->children);
124         for($i=0;$i<$max_child;$i++) {
125             $clone->children[]=$this->children[$i]->cloneNode();
126         }
127
128         /* for future use....
129             // clone all other vars
130             $temp=get_object_vars($this);
131             foreach($temp as $varname => $value)
132                 if (!in_array($varname,array('name','content','attributes','children')))
133                     $clone->$varname=$value;
134         */
135
136         return $clone;
137     }
138
139     /**
140     * Inserts child ($child) to a specified child-position ($pos)
141     *
142     * @param mixed      path            Path to parent node to add child (see getNodeAt()
143     *                                   for format). If null child is inserted in the
144     *                                   current node.
145     * @param integer    pos             Position where to insert the new child.
146     *                                   0 < means |$pos| elements before the end,
147     *                                   e.g. -1 appends as last child.
148     * @param mixed      child           Child to insert (XML_Tree or XML_Tree_Node),
149     *                                   or name of child node
150     * @param string     content         Content (text) for the new node (only if
151     *                                   $child is the node name)
152     * @param array      attributes      Attribute-hash for new node
153     *
154     * @return Reference to the newly inserted node, or PEAR_Error upon insertion error.
155     * @access public
156     */
157     function &insertChild($path,$pos,&$child, $content = '', $attributes = array())
158     {
159         $parent =& $this->getNodeAt($path);
160         if (PEAR::isError($parent)) {
161             // $path was not found
162             return $parent;
163         } elseif ($parent != $this) {
164             // Insert at the node found
165             return $parent->insertChild(null, $pos, $child, $content, $attributes);
166         }
167
168         if (($pos < -count($this->children)) || ($pos > count($this->children))) {
169             return new PEAR_Error("Invalid insert position.");
170         }
171
172         if (is_object($child)) { // child is an object
173             // insert a single node
174             if (strtolower(get_class($child)) == 'xml_tree_node') {
175                 array_splice($this->children, $pos, 0, 'dummy');
176                 if ($pos < 0) {
177                     $pos = count($this->children) + $pos - 1;
178                 }
179                 $this->children[$pos] = &$child;
180             // insert a tree i.e insert root-element of tree
181             } elseif (strtolower(get_class($child)) == 'xml_tree' && isset($child->root)) {
182                 array_splice($this->children, $pos, 0, 'dummy');
183                 if ($pos < 0) {
184                     $pos = count($this->children) + $pos - 1;
185                 }
186                 $this->children[$pos] = $child->root;
187             } else {
188                 return new PEAR_Error("Bad node (must be a XML_Tree or an XML_Tree_Node)");
189             }
190         } else { // child offered is a string
191             array_splice($this->children, $pos, 0, 'dummy');
192             if ($pos < 0) {
193                 $pos = count($this->children) + $pos - 1;
194             }
195             $this->children[$pos] = new XML_Tree_Node($child, $content, $attributes);
196         }
197         return $this;
198     }
199
200     /**
201     * Removes child at a given position
202     *
203     * @param    integer     pos     position of child to remove in children-list.
204     *                               0 < means |$pos| elements before the end,
205     *                               e.g. -1 removes the last child.
206     *
207     * @return mixed     The removed node, or PEAR_Error upon removal error.
208     * @access public
209     */
210     function &removeChild($pos)
211     {
212         if (($pos < -count($this->children)) || ($pos >= count($this->children))) {
213             return new PEAR_Error("Invalid remove position.");
214         }
215
216         // Using array_splice() instead of a simple unset() to maintain index-integrity
217         return array_splice($this->children, $pos, 1);
218     }
219
220     /**
221     * Returns text representation of this node.
222     *
223     * @return  string   text (xml) representation of this node. Each tag is
224     *                   indented according to its level.
225     * @access public
226     */
227     function &get()
228     {
229         static $deep = -1;
230         static $do_ident = true;
231         $deep++;
232         if ($this->name !== null) {
233             $ident = str_repeat('  ', $deep);
234             if ($do_ident) {
235                 $out = $ident . '<' . $this->name;
236             } else {
237                 $out = '<' . $this->name;
238             }
239             foreach ($this->attributes as $name => $value) {
240                 $out .= ' ' . $name . '="' . $value . '"';
241             }
242
243             $out .= '>' . $this->content;
244
245             if (sizeof($this->children) > 0) {
246                 $out .= "\n";
247                 foreach ($this->children as $child) {
248                     $out .= $child->get();
249                 }
250             } else {
251                 $ident = '';
252             }
253             if ($do_ident) {
254                 $out .= $ident . '</' . $this->name . ">\n";
255             } else {
256                 $out .= '</' . $this->name . '>';
257             }
258             $do_ident = true;
259         } else {
260             $out = $this->content;
261             $do_ident = false;
262         }
263         $deep--;
264         return $out;
265     }
266
267     /**
268     * Get an attribute by its name.
269     *
270     * @param  string  $name     Name of attribute to retrieve
271     *
272     * @return string  attribute, or null if attribute is unset.
273     * @access public
274     */
275     function getAttribute($name)
276     {
277         if (isset($this->attributes[strtolower($name)])) {
278             return $this->attributes[strtolower($name)];
279         }
280         return null;
281     }
282
283     function hasAttribute($name)
284     {
285         return isset($this->attributes[strtolower($name)]);
286     }
287     
288     /**
289     * Sets an attribute for this node.
290     *
291     * @param  string    name        Name of attribute to set
292     * @param  string    value       Value of attribute
293     *
294     * @access public
295     */
296     function setAttribute($name, $value = '')
297     {
298         $this->attributes[strtolower($name)] = $value;
299     }
300
301     /**
302     * Unsets an attribute of this node.
303     *
304     * @param  string  $name     Name of attribute to unset
305     *
306     * @access public
307     */
308     function unsetAttribute($name)
309     {
310         if (isset($this->attributes[strtolower($name)])) {
311             unset($this->attributes[strtolower($name)]);
312         }
313     }
314
315     /**
316     * Sets the content for this node.
317     *
318     * @param  string    content     Node content to assign
319     *
320     * @access public
321     */
322     function setContent($content)
323     {
324         
325         $this->content = $this->encodeXmlEntities($content);
326        // var_dump(array($this->content, $content));
327     }
328
329     /**
330     * Gets an element by its 'path'.
331     *
332     * @param  array     path    path to element: sequence of indexes to the
333     *                           children. E.g. array(1, 2, 3) means "third
334     *                           child of second child of first child" of the node.
335     *
336     * @return object    reference to element found, or PEAR_Error if node can't
337     *                   be found.
338     * @access public
339     */
340     function &getElement($path)
341     {
342         if (!is_array($path)) {
343             $path = array($path);
344         }
345         if (sizeof($path) == 0) {
346             return $this;
347         }
348
349         $path1 = $path;
350         $next = array_shift($path1);
351         if (isset($this->children[$next])) {
352             $x =& $this->children[$next]->getElement($path1);
353             if (!PEAR::isError($x)) {
354                 return $x;
355             }
356         }
357
358         return new PEAR_Error("Bad path to node: [".implode('-', $path)."]");
359     }
360
361     /**
362     * Get a reference to a node by its 'path'.
363     *
364     * @param  mixed     path    Path to node. Can be either a string (slash-separated
365     *   children names) or an array (sequence of children names) both
366     *                           starting from this node. The first name in sequence
367     *   is a child name, not the name of this node.
368     *
369     * @return object    Reference to the XML_Tree_Node found, or PEAR_Error if
370     *                   the path does not match any node. Note that if more than
371     *                   one element matches then only the first matching node is
372     *                   returned.
373     * @access public
374     */
375     function &getNodeAt($path)
376     {
377         if (is_string($path))
378             $path = explode("/", $path);
379
380         if (sizeof($path) == 0) {
381             return $this;
382         }
383
384         $path1 = $path;
385         $next = array_shift($path1);
386
387         // Get the first children of this node whose name is '$next'
388         $child = null;
389         for ($i = 0; $i < count($this->children); $i++) {
390             if ($this->children[$i]->name == $next) {
391                 $child =& $this->children[$i];
392                 break;
393             }
394         }
395         if (!is_null($child)) {
396             $x =& $child->getNodeAt($path1);
397             if (!PEAR::isError($x)) {
398                 return $x;
399             }
400         }
401
402         // No node with that name found
403         return new PEAR_Error("Bad path to node: [".implode('/', $path)."]");
404     }
405
406     /**
407     * Escape XML entities.
408     *
409     * @param   string  xml      Text string to escape.
410     *
411     * @return  string  xml
412     * @access  public
413     */
414     function encodeXmlEntities($xml)
415     {
416          $xml =  preg_replace_callback("/[\x{0080}-\x{FFFF}]/u", function($v) {
417                          //   var_dump($v);
418                             return '&#'. mb_ord($v[0]).';';
419                             
420                                   //"'&#'.ord('\\1').';'"         
421                         },
422                         $xml
423                     );
424          //var_dump($xml);
425          $xml = str_replace(array('ü', 'Ü', 'ö',
426                                  'Ö', 'ä', 'Ä',
427                                  'ß', '<', '>',
428                                  '"', '\''
429                                 ),
430                            array('&#252;', '&#220;', '&#246;',
431                                  '&#214;', '&#228;', '&#196;',
432                                   '&#223;', '&lt;', '&gt;',
433                                   '&quot;', '&apos;'
434                                 ),
435                            $xml
436                           );
437        // var_dump($xml);
438         $xml =  preg_replace(array("/\&([a-z\d\#]+)\;/i",
439                                   "/\&/",
440                                   "/\#\|\|([a-z\d\#]+)\|\|\#/i",
441                                  ),
442                             array("#||\\1||#",
443                                   "&amp;",
444                                   "&\\1;",
445                                   
446                                  ),
447                             $xml
448         );
449  
450 /*
451              $xml =  preg_replace_callback("/[^a-zA-Z\d\s\<\>\&\;\.\:\=\"\-\/\%\?\!\'\(\)\[\]\{\}\$\#\+\,\@_]/u",
452                         function($v) {
453                              var_dump($v);
454                             return '&#'. mb_ord($v[0]).';';
455                             
456                                   //"'&#'.ord('\\1').';'"         
457                         },
458                         $xml
459                     );
460                     */
461 //var_dump($xml);             
462         return $xml;
463     }
464
465     /**
466     * Decode XML entities in a text string.
467     *
468     * @param   string  xml  Text to decode
469     *
470     * @return  string  Decoded text
471     * @access  public
472     */
473     function decodeXmlEntities($xml)
474     {
475         static $trans_tbl = null;
476         if (!$trans_tbl) {
477             $trans_tbl = get_html_translation_table(HTML_ENTITIES);
478             $trans_tbl = array_flip($trans_tbl);
479         }
480         for ($i = 1; $i <= 255; $i++) {
481             $ent = sprintf("&#%03d;", $i);
482             $ch = chr($i);
483             $xml = str_replace($ent, $ch, $xml);
484         }
485
486         return strtr($xml, $trans_tbl);
487     }
488
489
490     /**
491     * Print text representation of XML_Tree_Node.
492     *
493     * @access  public
494     */
495     function dump() {
496         echo $this->get();
497     }
498 }