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 // +----------------------------------------------------------------------+
22 // $Id: Tree.php,v 1.31 2003/09/18 10:51:34 cox Exp $
25 require_once 'XML/Parser.php';
26 require_once 'XML/Tree/Node.php';
33 * Allows for the building of XML data structures
34 * using a tree representation, without the need
35 * for an extension like DOMXML.
39 * $tree = new XML_Tree;
40 * $root =& $tree->addRoot('root');
41 * $foo =& $root->addChild('foo');
45 * @author Bernd Römer <berndr@bonn.edu>
47 * @version $Version$ - 1.0
49 class XML_Tree extends XML_Parser
59 * Filename from which the XML_Tree was read
70 var $namespace = array();
73 * Root node of the XML tree
75 * @var object XML_Tree_Node
92 * @param string filename Filename where to read the XML
93 * @param string version XML Version to apply
95 function __construct($filename = '', $version = '1.0')
97 $this->filename = $filename;
98 $this->version = $version;
104 * @return object Root XML_Tree_Node, or PEAR_Error if there isn't any root node.
110 if (!is_null($this->root)) {
113 return $this->raiseError("No root");
117 * Sets the root node of the XML tree.
119 * @param string name Name of root element
121 * @return object XML_Tree_Node Reference to the newly created root node
124 function addRoot($name, $content = '', $attributes = array(), $lineno = null)
126 $this->root = new XML_Tree_Node($name, $content, $attributes, $lineno);
131 * Inserts a child/tree (child) into tree ($path,$pos) and maintains
132 * namespace integrity
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
145 * @return object Reference to the inserted child (node), or PEAR_Error upon error
149 function insertChild($path, $pos, $child, $content = '', $attributes = array())
151 $parent = $this->getNodeAt($path);
152 if (PEAR::isError($parent)) {
156 $x = $parent->insertChild(null, $pos, $child, $content, $attributes);
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]++;
171 * Removes a child node from tree and maintains namespace integrity
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.
179 * @return object Parent XML_Tree_Node whose child was removed, or PEAR_Error upon error
183 function removeChild($path, $pos)
185 $parent = $this->getNodeAt($path);
186 if (PEAR::isError($parent)) {
190 $x = $parent->removeChild($pos);
192 if (!PEAR::isError($x)) {
193 // Update namespace to maintain namespace integrity
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;
200 if ($val[$count]>$pos) {
201 $this->namespace[$key][$count]--;
211 * Maps a XML file to a XML_Tree
213 * @return mixed The XML tree root (an XML_Tree_Node), or PEAR_Error upon error.
216 function getTreeFromFile ()
218 $this->folding = false;
219 parent::__construct(null, 'event');
220 $err = $this->setInputFile($this->filename);
221 if (PEAR::isError($err)) {
225 $err = $this->parse();
226 if (PEAR::isError($err)) {
229 return $this->getRoot();
233 * Maps an XML string to an XML_Tree.
235 * @return mixed The XML tree root (an XML_Tree_Node), or PEAR_Error upon error.
238 function getTreeFromString($str)
240 $this->folding = false;
241 parent::__construct(null, 'event');
243 $err = $this->parseString($str);
244 if (PEAR::isError($err)) {
247 return $this->getRoot();
251 * Handler for the xml-data
252 * Used by XML_Parser::XML_Parser() when parsing an XML stream.
254 * @param mixed xp ignored
255 * @param string elem name of the element
256 * @param array attribs attributes for the generated node
260 function startHandler($xp, $elem, $attribs)
262 $lineno = xml_get_current_line_number($this->parser);
264 if (empty($this->i)) {
265 $this->obj[1] = $this->addRoot($elem, null, $attribs, $lineno);
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);
274 $obj_id = $this->i++;
275 $this->obj[$obj_id] = new XML_Tree_Node($elem, null, $attribs, $lineno);
282 * Handler for the xml-data
283 * Used by XML_Parser::XML_Parser() when parsing an XML stream.
285 * @param mixed xp ignored
286 * @param string elem name of the element
290 function endHandler($elem)
295 // recover the node created in StartHandler
296 $node = $this->obj[$obj_id];
298 if (count($node->children) > 0) {
299 if (trim($this->cdata)) {
300 $node->children[] = new XML_Tree_Node(null, $this->cdata);
303 $node->setContent($this->cdata);
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;
315 * The xml character data handler
316 * Used by XML_Parser::XML_Parser() when parsing an XML stream.
318 * @param mixed xp ignored
319 * @param string data PCDATA between tags
323 function cdataHandler($xp, $data)
326 if (trim($data) != '') {
327 $this->cdata .= $data;
332 * Get a copy of this tree by cloning and all of its nodes, recursively.
334 * @return object XML_Tree copy of this node.
339 $clone = new XML_Tree($this->filename, $this->version);
340 if (!is_null($this->root)) {
341 $clone->root = $this->root->cloneNode();
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;
355 * Print text representation of XML tree.
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.
363 function dump($xmlHeader = false)
366 header('Content-type: text/xml');
372 * Get text representation of XML tree.
374 * @return string Text (XML) representation of the tree
379 $out = '<?xml version="' . $this->version . "\"?>\n";
380 if (!is_null($this->root))
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();
390 * Get current namespace.
392 * @param string name namespace
397 function getName($name) {
398 return $this->root->getElement($this->namespace[$name]);
402 * Register a namespace.
404 * @param string $name namespace
405 * @param string $path path
409 function registerName($name, $path) {
410 $this->namespace[$name] = $path;
414 * Get a reference to a node. Node is searched by its 'path'.
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.
425 function getNodeAt($path)
427 if (is_null($this->root)){
428 return $this->raiseError("XML_Tree hasn't a root node");
430 if (is_string($path))
431 $path = explode("/", $path);
432 if (sizeof($path) == 0) {
433 return $this->raiseError("Path to node is empty");
436 $rootName = array_shift($path1);
437 if ($this->root->name != $rootName) {
438 return $this->raiseError("Path does not match the document root");
440 $x = $this->root->getNodeAt($path1);
441 if (!PEAR::isError($x)) {
444 // No node with that name found
445 return $this->raiseError("Bad path to node: [".implode('/', $path)."]");
449 * Gets all children that match a given tag name.
451 * @param string Tag name
453 * @return array An array of Node objects of the children found,
454 * an empty array if none
456 * @author Pierre-Alain Joye <paj@pearfr.org>
458 function getElementsByTagName($tagName)
460 if (empty($tagName)) {
461 return $this->raiseError('Empty tag name');
463 if (sizeof($this->children)==0) {
467 foreach ($this->children as $child) {
468 if ($child->name == $tagName) {