d7b7abfa9cc7e6b67b39d529de592dfc3c1e0f6a
[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     * Constructor
88     *
89     * @param  string  filename  Filename where to read the XML
90     * @param  string  version   XML Version to apply
91     */
92     function __construct($filename = '', $version = '1.0')
93     {
94         $this->filename = $filename;
95         $this->version  = $version;
96     }
97
98     /**
99     * Gets the root node
100     *
101     * @return object    Root XML_Tree_Node, or PEAR_Error if there isn't any root node.
102     *
103     * @access public
104     */
105     function getRoot()
106     {
107         if (!is_null($this->root)) {
108             return $this->root;
109         }
110         return $this->raiseError("No root");
111     }
112
113     /**
114     * Sets the root node of the XML tree.
115     *
116     * @param  string    name        Name of root element
117     *
118     * @return object XML_Tree_Node   Reference to the newly created root node
119     * @access public
120     */
121     function addRoot($name, $content = '', $attributes = array(), $lineno = null)
122     {
123         $this->root = new XML_Tree_Node($name, $content, $attributes, $lineno);
124         return $this->root;
125     }
126
127     /**
128     * Inserts a child/tree (child) into tree ($path,$pos) and maintains
129     * namespace integrity
130     *
131     * @param mixed      path            Path to parent node to add child (see
132     *                                   getNodeAt() for format)
133     * @param integer    pos             Position where to insert the new child.
134     *                                   0 < means |$pos| elements before the end,
135     *                                   e.g. -1 appends as last child.
136     * @param mixed      child           Child to insert (XML_Tree or XML_Tree_Node),
137     *                                   or name of child node
138     * @param string     content         Content (text) for the new node (only if
139     *                                   $child is the node name)
140     * @param array      attributes      Attribute-hash for new node
141     *
142     * @return object Reference to the inserted child (node), or PEAR_Error upon error
143     * @access public
144     * @see getNodeAt()
145     */
146     function insertChild($path, $pos, $child, $content = '', $attributes = array())
147     {
148         $parent = $this->getNodeAt($path);
149         if (PEAR::isError($parent)) {
150             return $parent;
151         }
152
153         $x = $parent->insertChild(null, $pos, $child, $content, $attributes);
154
155         if (!PEAR::isError($x)) {
156         // update namespace to maintain namespace integrity
157             $count = count($path);
158             foreach ($this->namespace as $key => $val) {
159                 if ((array_slice($val,0,$count)==$path) && ($val[$count]>=$pos)) {
160                     $this->namespace[$key][$count]++;
161                 }
162             }
163         }
164         return $x;
165     }
166
167     /*
168     * Removes a child node from tree and maintains namespace integrity
169     *
170     * @param array      path        Path to the parent of child to remove (see
171     *                               getNodeAt() for format)
172     * @param integer    pos         Position of child in parent children-list
173     *                               0 < means |$pos| elements before the end,
174     *                               e.g. -1 removes the last child.
175     *
176     * @return object    Parent XML_Tree_Node whose child was removed, or PEAR_Error upon error
177     * @access public
178     * @see getNodeAt()
179     */
180     function removeChild($path, $pos)
181     {
182         $parent = $this->getNodeAt($path);
183         if (PEAR::isError($parent)) {
184             return $parent;
185         }
186
187         $x = $parent->removeChild($pos);
188
189         if (!PEAR::isError($x)) {
190             // Update namespace to maintain namespace integrity
191             $count=count($path);
192             foreach($this->namespace as $key => $val) {
193                 if (array_slice($val,0,$count)==$path) {
194                     if ($val[$count]==$pos) {
195                         unset($this->namespace[$key]); break;
196                     }
197                     if ($val[$count]>$pos) {
198                         $this->namespace[$key][$count]--;
199                     }
200                 }
201             }
202         }
203
204         return $x;
205     }
206
207     /*
208     * Maps a XML file to a XML_Tree
209     *
210     * @return mixed The XML tree root (an XML_Tree_Node), or PEAR_Error upon error.
211     * @access public
212     */
213     function getTreeFromFile ()
214     {
215         $this->folding = false;
216         $this->XML_Parser(null, 'event');
217         $err = $this->setInputFile($this->filename);
218         if (PEAR::isError($err)) {
219             return $err;
220         }
221         $this->cdata = null;
222         $err = $this->parse();
223         if (PEAR::isError($err)) {
224             return $err;
225         }
226         return $this->getRoot();
227     }
228
229     /*
230     * Maps an XML string to an XML_Tree.
231     *
232     * @return mixed The XML tree root (an XML_Tree_Node), or PEAR_Error upon error.
233     * @access public
234     */
235     function getTreeFromString($str)
236     {
237         $this->folding = false;
238         $this->XML_Parser(null, 'event');
239         $this->cdata = null;
240         $err = $this->parseString($str);
241         if (PEAR::isError($err)) {
242             return $err;
243         }
244         return $this->getRoot();
245     }
246
247     /**
248     * Handler for the xml-data
249     * Used by XML_Parser::XML_Parser() when parsing an XML stream.
250     *
251     * @param mixed  xp          ignored
252     * @param string elem        name of the element
253     * @param array  attribs     attributes for the generated node
254     *
255     * @access private
256     */
257     function startHandler($xp, $elem, &$attribs)
258     {
259         $lineno = xml_get_current_line_number($xp);
260         // root elem
261         if (!isset($this->i)) {
262             $this->obj1 = $this->addRoot($elem, null, $attribs, $lineno);
263             $this->i = 2;
264         } else {
265             // mixed contents
266             if (!empty($this->cdata)) {
267                 $parent_id = 'obj' . ($this->i - 1);
268                 $parent    = $this->$parent_id;
269                 $parent->children[] = new XML_Tree_Node(null, $this->cdata, null, $lineno);
270             }
271             $obj_id = 'obj' . $this->i++;
272             $this->$obj_id = new XML_Tree_Node($elem, null, $attribs, $lineno);
273         }
274         $this->cdata = null;
275         return null;
276     }
277
278     /**
279     * Handler for the xml-data
280     * Used by XML_Parser::XML_Parser() when parsing an XML stream.
281     *
282     * @param mixed  xp          ignored
283     * @param string elem        name of the element
284     *
285     * @access private
286     */
287     function endHandler($xp, $elem)
288     {
289         $this->i--;
290         if ($this->i > 1) {
291             $obj_id = 'obj' . $this->i;
292             // recover the node created in StartHandler
293             $node   = $this->$obj_id;
294             // mixed contents
295             if (count($node->children) > 0) {
296                 if (trim($this->cdata)) {
297                     $node->children[] = new XML_Tree_Node(null, $this->cdata);
298                 }
299             } else {
300                 $node->setContent($this->cdata);
301             }
302             $parent_id = 'obj' . ($this->i - 1);
303             $parent    = $this->$parent_id;
304             // attach the node to its parent node children array
305             $parent->children[] = $node;
306         }
307         $this->cdata = null;
308         return null;
309     }
310
311     /*
312     * The xml character data handler
313     * Used by XML_Parser::XML_Parser() when parsing an XML stream.
314     *
315     * @param mixed  xp          ignored
316     * @param string data        PCDATA between tags
317     *
318     * @access private
319     */
320     function cdataHandler($xp, $data)
321     {
322         if (trim($data) != '') {
323             $this->cdata .= $data;
324         }
325     }
326
327     /**
328     * Get a copy of this tree by cloning and all of its nodes, recursively.
329     *
330     * @return object XML_Tree copy of this node.
331     * @access public
332     */
333     function cloneTree()
334     {
335         $clone = new XML_Tree($this->filename, $this->version);
336         if (!is_null($this->root)) {
337             $clone->root = $this->root->cloneNode();
338         }
339
340         // clone all other vars
341         $temp = get_object_vars($this);
342         foreach($temp as $varname => $value) {
343             if (!in_array($varname,array('filename','version','root'))) {
344                 $clone->$varname=$value;
345             }
346         }
347         return $clone;
348     }
349
350     /**
351     * Print text representation of XML tree.
352     *
353     * @param bool xmlHeader     if true then generate also the leading XML
354     *                           'Content-type' header directive, e.g. for
355     *                           direct output to a web page.
356     *
357     * @access public
358     */
359     function dump($xmlHeader = false)
360     {
361         if ($xmlHeader) {
362             header('Content-type: text/xml');
363         }
364         echo $this->get();
365     }
366
367     /**
368     * Get text representation of XML tree.
369     *
370     * @return string  Text (XML) representation of the tree
371     * @access public
372     */
373     function get()
374     {
375         $out = '<?xml version="' . $this->version . "\"?>\n";
376         if (!is_null($this->root))
377             {
378             if(!is_object($this->root) || (get_class($this->root) != 'xml_tree_node'))
379                 return $this->raiseError("Bad XML root node");
380             $out .= $this->root->get();
381         }
382         return $out;
383     }
384
385     /**
386     * Get current namespace.
387     *
388     * @param  string  name  namespace
389     * @return string
390     *
391     * @access public
392     */
393     function getName($name) {
394         return $this->root->getElement($this->namespace[$name]);
395     }
396
397     /**
398     * Register a namespace.
399     *
400     * @param  string  $name namespace
401     * @param  string  $path path
402     *
403     * @access public
404     */
405     function registerName($name, $path) {
406         $this->namespace[$name] = $path;
407     }
408
409     /**
410     * Get a reference to a node. Node is searched by its 'path'.
411     *
412     * @param mixed  path  Path to node. Can be either a string (slash-separated
413     *                     children names) or an array (sequence of children names) both
414     *                     of them starting from node. Note that the first name in sequence
415     *                     must be the name of the document root.
416     * @return object    Reference to the XML_Tree_Node found, or PEAR_Error if
417     *                   the path does not exist. If more than one element matches
418     *                   then only the first match is returned.
419     * @access public
420     */
421     function getNodeAt($path)
422     {
423         if (is_null($this->root)){
424             return $this->raiseError("XML_Tree hasn't a root node");
425         }
426         if (is_string($path))
427             $path = explode("/", $path);
428         if (sizeof($path) == 0) {
429             return $this->raiseError("Path to node is empty");
430         }
431         $path1 = $path;
432         $rootName = array_shift($path1);
433         if ($this->root->name != $rootName) {
434             return $this->raiseError("Path does not match the document root");
435         }
436         $x =  $this->root->getNodeAt($path1);
437         if (!PEAR::isError($x)) {
438             return $x;
439         }
440         // No node with that name found
441         return $this->raiseError("Bad path to node: [".implode('/', $path)."]");
442     }
443
444     /**
445     * Gets all children that match a given tag name.
446     *
447     * @param  string    Tag name
448     *
449     * @return array     An array of Node objects of the children found,
450     *                   an empty array if none
451     * @access public
452     * @author Pierre-Alain Joye <paj@pearfr.org>
453     */
454     function getElementsByTagName($tagName)
455     {
456         if (empty($tagName)) {
457             return $this->raiseError('Empty tag name');
458         }
459         if (sizeof($this->children)==0) {
460             return null;
461         }
462         $result = array();
463         foreach ($this->children as $child) {
464             if ($child->name == $tagName) {
465                 $result[] = $child;
466             }
467         }
468         return $result;
469     }
470 }
471 ?>