Fix #8052 - fixing pdf
[pear] / XML / Tree.php
1 <?php
2 //
3 // +----------------------------------------------------------------------+
4 // | PEAR :: XML_Tree                                                     |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1997-2003 The PHP Group                                |
7 // +----------------------------------------------------------------------+
8 // | This source file is subject to version 2.02 of the PHP license,      |
9 // | that is bundled with this package in the file LICENSE, and is        |
10 // | available at through the world-wide-web at                           |
11 // | http://www.php.net/license/2_02.txt.                                 |
12 // | If you did not receive a copy of the PHP license and are unable to   |
13 // | obtain it through the world-wide-web, please send a note to          |
14 // | license@php.net so we can mail you a copy immediately.               |
15 // +----------------------------------------------------------------------+
16 // | Authors: Bernd Römer <berndr@bonn.edu>                               |
17 // |          Sebastian Bergmann <sb@sebastian-bergmann.de>               |
18 // |          Tomas V.V.Cox <cox@idecnet.com>                             |
19 // |          Michele Manzato <michele.manzato@verona.miz.it>             |
20 // +----------------------------------------------------------------------+
21 //
22 // $Id: Tree.php,v 1.31 2003/09/18 10:51:34 cox Exp $
23 //
24
25 require_once 'XML/Parser.php';
26 require_once 'XML/Tree/Node.php';
27
28 /**
29 * PEAR::XML_Tree
30 *
31 * Purpose
32 *
33 *    Allows for the building of XML data structures
34 *    using a tree representation, without the need
35 *    for an extension like DOMXML.
36 *
37 * Example
38 *
39 *    $tree  = new XML_Tree;
40 *    $root =& $tree->addRoot('root');
41 *    $foo  =& $root->addChild('foo');
42 *
43 *    $tree->dump(true);
44 *
45 * @author  Bernd Römer <berndr@bonn.edu>
46 * @package XML
47 * @version $Version$ - 1.0
48 */
49 class XML_Tree extends XML_Parser
50 {
51     /**
52     * File Handle
53     *
54     * @var  resource
55     */
56     var $file = NULL;
57
58     /**
59     * Filename from which the XML_Tree was read
60     *
61     * @var  string
62     */
63     var $filename = '';
64
65     /**
66     * Namespace
67     *
68     * @var  array
69     */
70     var $namespace = array();
71
72     /**
73     * Root node of the XML tree
74     *
75     * @var  object XML_Tree_Node
76     */
77     var $root = NULL;
78
79     /**
80     * XML Version
81     *
82     * @var  string
83     */
84     var $version = '1.0';
85     
86     
87     var $obj = array();
88
89     /**
90     * Constructor
91     *
92     * @param  string  filename  Filename where to read the XML
93     * @param  string  version   XML Version to apply
94     */
95     function __construct($filename = '', $version = '1.0')
96     {
97         $this->filename = $filename;
98         $this->version  = $version;
99     }
100
101     /**
102     * Gets the root node
103     *
104     * @return object    Root XML_Tree_Node, or PEAR_Error if there isn't any root node.
105     *
106     * @access public
107     */
108     function getRoot()
109     {
110         if (!is_null($this->root)) {
111             return $this->root;
112         }
113         return $this->raiseError("No root");
114     }
115
116     /**
117     * Sets the root node of the XML tree.
118     *
119     * @param  string    name        Name of root element
120     *
121     * @return object XML_Tree_Node   Reference to the newly created root node
122     * @access public
123     */
124     function addRoot($name, $content = '', $attributes = array(), $lineno = null)
125     {
126         $this->root = new XML_Tree_Node($name, $content, $attributes, $lineno);
127         return $this->root;
128     }
129
130     /**
131     * Inserts a child/tree (child) into tree ($path,$pos) and maintains
132     * namespace integrity
133     *
134     * @param mixed      path            Path to parent node to add child (see
135     *                                   getNodeAt() for format)
136     * @param integer    pos             Position where to insert the new child.
137     *                                   0 < means |$pos| elements before the end,
138     *                                   e.g. -1 appends as last child.
139     * @param mixed      child           Child to insert (XML_Tree or XML_Tree_Node),
140     *                                   or name of child node
141     * @param string     content         Content (text) for the new node (only if
142     *                                   $child is the node name)
143     * @param array      attributes      Attribute-hash for new node
144     *
145     * @return object Reference to the inserted child (node), or PEAR_Error upon error
146     * @access public
147     * @see getNodeAt()
148     */
149     function insertChild($path, $pos, $child, $content = '', $attributes = array())
150     {
151         $parent = $this->getNodeAt($path);
152         if (PEAR::isError($parent)) {
153             return $parent;
154         }
155
156         $x = $parent->insertChild(null, $pos, $child, $content, $attributes);
157
158         if (!PEAR::isError($x)) {
159         // update namespace to maintain namespace integrity
160             $count = count($path);
161             foreach ($this->namespace as $key => $val) {
162                 if ((array_slice($val,0,$count)==$path) && ($val[$count]>=$pos)) {
163                     $this->namespace[$key][$count]++;
164                 }
165             }
166         }
167         return $x;
168     }
169
170     /*
171     * Removes a child node from tree and maintains namespace integrity
172     *
173     * @param array      path        Path to the parent of child to remove (see
174     *                               getNodeAt() for format)
175     * @param integer    pos         Position of child in parent children-list
176     *                               0 < means |$pos| elements before the end,
177     *                               e.g. -1 removes the last child.
178     *
179     * @return object    Parent XML_Tree_Node whose child was removed, or PEAR_Error upon error
180     * @access public
181     * @see getNodeAt()
182     */
183     function removeChild($path, $pos)
184     {
185         $parent = $this->getNodeAt($path);
186         if (PEAR::isError($parent)) {
187             return $parent;
188         }
189
190         $x = $parent->removeChild($pos);
191
192         if (!PEAR::isError($x)) {
193             // Update namespace to maintain namespace integrity
194             $count=count($path);
195             foreach($this->namespace as $key => $val) {
196                 if (array_slice($val,0,$count)==$path) {
197                     if ($val[$count]==$pos) {
198                         unset($this->namespace[$key]); break;
199                     }
200                     if ($val[$count]>$pos) {
201                         $this->namespace[$key][$count]--;
202                     }
203                 }
204             }
205         }
206
207         return $x;
208     }
209
210     /*
211     * Maps a XML file to a XML_Tree
212     *
213     * @return mixed The XML tree root (an XML_Tree_Node), or PEAR_Error upon error.
214     * @access public
215     */
216     function getTreeFromFile ()
217     {
218         $this->folding = false;
219         parent::__construct(null, 'event');
220         $err = $this->setInputFile($this->filename);
221         if (PEAR::isError($err)) {
222             return $err;
223         }
224         $this->cdata = null;
225         $err = $this->parse();
226         if (PEAR::isError($err)) {
227             return $err;
228         }
229         return $this->getRoot();
230     }
231
232     /*
233     * Maps an XML string to an XML_Tree.
234     *
235     * @return mixed The XML tree root (an XML_Tree_Node), or PEAR_Error upon error.
236     * @access public
237     */
238     function getTreeFromString($str)
239     {
240         $this->folding = false;
241         parent::__construct(null, 'event');
242         $this->cdata = null;
243         $err = $this->parseString($str);
244         if (PEAR::isError($err)) {
245             return $err;
246         }
247         return $this->getRoot();
248     }
249
250     /**
251     * Handler for the xml-data
252     * Used by XML_Parser::XML_Parser() when parsing an XML stream.
253     *
254     * @param mixed  xp          ignored
255     * @param string elem        name of the element
256     * @param array  attribs     attributes for the generated node
257     *
258     * @access private
259     */
260     function startHandler($xp, $elem,  $attribs)
261     {
262          $lineno = xml_get_current_line_number($this->parser);
263         // root elem
264         if (empty($this->i)) {
265             $this->obj[1] = $this->addRoot($elem, null, $attribs, $lineno);
266             $this->i = 2;
267         } else {
268             // mixed contents
269             if (!empty($this->cdata)) {
270                 $parent_id = $this->i - 1;
271                 $parent    = $this->obj[$parent_id];
272                 $parent->children[] = new XML_Tree_Node(null, $this->cdata, null, $lineno);
273             }
274             $obj_id = $this->i++;
275             $this->obj[$obj_id] = new XML_Tree_Node($elem, null, $attribs, $lineno);
276         }
277         $this->cdata = null;
278         return null;
279     }
280
281     /**
282     * Handler for the xml-data
283     * Used by XML_Parser::XML_Parser() when parsing an XML stream.
284     *
285     * @param mixed  xp          ignored
286     * @param string elem        name of the element
287     *
288     * @access private
289     */
290     function endHandler($elem)
291     {
292         $this->i--;
293         if ($this->i > 1) {
294             $obj_id =   $this->i;
295             // recover the node created in StartHandler
296             $node   = $this->obj[$obj_id];
297             // mixed contents
298             if (count($node->children) > 0) {
299                 if (trim($this->cdata)) {
300                     $node->children[] = new XML_Tree_Node(null, $this->cdata);
301                 }
302             } else {
303                 $node->setContent($this->cdata);
304             }
305             $parent_id =  ($this->i - 1);
306             $parent    = $this->obj[$parent_id];
307             // attach the node to its parent node children array
308             $parent->children[] = $node;
309         }
310         $this->cdata = null;
311         return null;
312     }
313
314     /*
315     * The xml character data handler
316     * Used by XML_Parser::XML_Parser() when parsing an XML stream.
317     *
318     * @param mixed  xp          ignored
319     * @param string data        PCDATA between tags
320     *
321     * @access private
322     */
323     function cdataHandler($xp, $data)
324     {
325         //var_dump($data);
326         if (trim($data) != '') {
327             $this->cdata .= $data;
328         }
329     }
330
331     /**
332     * Get a copy of this tree by cloning and all of its nodes, recursively.
333     *
334     * @return object XML_Tree copy of this node.
335     * @access public
336     */
337     function cloneTree()
338     {
339         $clone = new XML_Tree($this->filename, $this->version);
340         if (!is_null($this->root)) {
341             $clone->root = $this->root->cloneNode();
342         }
343
344         // clone all other vars
345         $temp = get_object_vars($this);
346         foreach($temp as $varname => $value) {
347             if (!in_array($varname,array('filename','version','root'))) {
348                 $clone->$varname=$value;
349             }
350         }
351         return $clone;
352     }
353
354     /**
355     * Print text representation of XML tree.
356     *
357     * @param bool xmlHeader     if true then generate also the leading XML
358     *                           'Content-type' header directive, e.g. for
359     *                           direct output to a web page.
360     *
361     * @access public
362     */
363     function dump($xmlHeader = false)
364     {
365         if ($xmlHeader) {
366             header('Content-type: text/xml');
367         }
368         echo $this->get();
369     }
370
371     /**
372     * Get text representation of XML tree.
373     *
374     * @return string  Text (XML) representation of the tree
375     * @access public
376     */
377     function get()
378     {
379         $out = '<?xml version="' . $this->version . "\"?>\n";
380         if (!is_null($this->root))
381             {
382             if(!is_object($this->root) || (get_class($this->root) != 'xml_tree_node'))
383                 return $this->raiseError("Bad XML root node");
384             $out .= $this->root->get();
385         }
386         return $out;
387     }
388
389     /**
390     * Get current namespace.
391     *
392     * @param  string  name  namespace
393     * @return string
394     *
395     * @access public
396     */
397     function getName($name) {
398         return $this->root->getElement($this->namespace[$name]);
399     }
400
401     /**
402     * Register a namespace.
403     *
404     * @param  string  $name namespace
405     * @param  string  $path path
406     *
407     * @access public
408     */
409     function registerName($name, $path) {
410         $this->namespace[$name] = $path;
411     }
412
413     /**
414     * Get a reference to a node. Node is searched by its 'path'.
415     *
416     * @param mixed  path  Path to node. Can be either a string (slash-separated
417     *                     children names) or an array (sequence of children names) both
418     *                     of them starting from node. Note that the first name in sequence
419     *                     must be the name of the document root.
420     * @return object    Reference to the XML_Tree_Node found, or PEAR_Error if
421     *                   the path does not exist. If more than one element matches
422     *                   then only the first match is returned.
423     * @access public
424     */
425     function getNodeAt($path)
426     {
427         if (is_null($this->root)){
428             return $this->raiseError("XML_Tree hasn't a root node");
429         }
430         if (is_string($path))
431             $path = explode("/", $path);
432         if (sizeof($path) == 0) {
433             return $this->raiseError("Path to node is empty");
434         }
435         $path1 = $path;
436         $rootName = array_shift($path1);
437         if ($this->root->name != $rootName) {
438             return $this->raiseError("Path does not match the document root");
439         }
440         $x =  $this->root->getNodeAt($path1);
441         if (!PEAR::isError($x)) {
442             return $x;
443         }
444         // No node with that name found
445         return $this->raiseError("Bad path to node: [".implode('/', $path)."]");
446     }
447
448     /**
449     * Gets all children that match a given tag name.
450     *
451     * @param  string    Tag name
452     *
453     * @return array     An array of Node objects of the children found,
454     *                   an empty array if none
455     * @access public
456     * @author Pierre-Alain Joye <paj@pearfr.org>
457     */
458     function getElementsByTagName($tagName)
459     {
460         if (empty($tagName)) {
461             return $this->raiseError('Empty tag name');
462         }
463         if (sizeof($this->children)==0) {
464             return null;
465         }
466         $result = array();
467         foreach ($this->children as $child) {
468             if ($child->name == $tagName) {
469                 $result[] = $child;
470             }
471         }
472         return $result;
473     }
474 }