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
89 * @param string filename Filename where to read the XML
90 * @param string version XML Version to apply
92 function __construct($filename = '', $version = '1.0')
94 $this->filename = $filename;
95 $this->version = $version;
101 * @return object Root XML_Tree_Node, or PEAR_Error if there isn't any root node.
107 if (!is_null($this->root)) {
110 return $this->raiseError("No root");
114 * Sets the root node of the XML tree.
116 * @param string name Name of root element
118 * @return object XML_Tree_Node Reference to the newly created root node
121 function addRoot($name, $content = '', $attributes = array(), $lineno = null)
123 $this->root = new XML_Tree_Node($name, $content, $attributes, $lineno);
128 * Inserts a child/tree (child) into tree ($path,$pos) and maintains
129 * namespace integrity
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
142 * @return object Reference to the inserted child (node), or PEAR_Error upon error
146 function insertChild($path, $pos, $child, $content = '', $attributes = array())
148 $parent = $this->getNodeAt($path);
149 if (PEAR::isError($parent)) {
153 $x = $parent->insertChild(null, $pos, $child, $content, $attributes);
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]++;
168 * Removes a child node from tree and maintains namespace integrity
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.
176 * @return object Parent XML_Tree_Node whose child was removed, or PEAR_Error upon error
180 function removeChild($path, $pos)
182 $parent = $this->getNodeAt($path);
183 if (PEAR::isError($parent)) {
187 $x = $parent->removeChild($pos);
189 if (!PEAR::isError($x)) {
190 // Update namespace to maintain namespace integrity
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;
197 if ($val[$count]>$pos) {
198 $this->namespace[$key][$count]--;
208 * Maps a XML file to a XML_Tree
210 * @return mixed The XML tree root (an XML_Tree_Node), or PEAR_Error upon error.
213 function getTreeFromFile ()
215 $this->folding = false;
216 $this->XML_Parser(null, 'event');
217 $err = $this->setInputFile($this->filename);
218 if (PEAR::isError($err)) {
222 $err = $this->parse();
223 if (PEAR::isError($err)) {
226 return $this->getRoot();
230 * Maps an XML string to an XML_Tree.
232 * @return mixed The XML tree root (an XML_Tree_Node), or PEAR_Error upon error.
235 function getTreeFromString($str)
237 $this->folding = false;
238 $this->XML_Parser(null, 'event');
240 $err = $this->parseString($str);
241 if (PEAR::isError($err)) {
244 return $this->getRoot();
248 * Handler for the xml-data
249 * Used by XML_Parser::XML_Parser() when parsing an XML stream.
251 * @param mixed xp ignored
252 * @param string elem name of the element
253 * @param array attribs attributes for the generated node
257 function startHandler($xp, $elem, &$attribs)
259 $lineno = xml_get_current_line_number($xp);
261 if (!isset($this->i)) {
262 $this->obj1 = $this->addRoot($elem, null, $attribs, $lineno);
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);
271 $obj_id = 'obj' . $this->i++;
272 $this->$obj_id = new XML_Tree_Node($elem, null, $attribs, $lineno);
279 * Handler for the xml-data
280 * Used by XML_Parser::XML_Parser() when parsing an XML stream.
282 * @param mixed xp ignored
283 * @param string elem name of the element
287 function endHandler($xp, $elem)
291 $obj_id = 'obj' . $this->i;
292 // recover the node created in StartHandler
293 $node = $this->$obj_id;
295 if (count($node->children) > 0) {
296 if (trim($this->cdata)) {
297 $node->children[] = new XML_Tree_Node(null, $this->cdata);
300 $node->setContent($this->cdata);
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;
312 * The xml character data handler
313 * Used by XML_Parser::XML_Parser() when parsing an XML stream.
315 * @param mixed xp ignored
316 * @param string data PCDATA between tags
320 function cdataHandler($xp, $data)
322 if (trim($data) != '') {
323 $this->cdata .= $data;
328 * Get a copy of this tree by cloning and all of its nodes, recursively.
330 * @return object XML_Tree copy of this node.
335 $clone = new XML_Tree($this->filename, $this->version);
336 if (!is_null($this->root)) {
337 $clone->root = $this->root->cloneNode();
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;
351 * Print text representation of XML tree.
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.
359 function dump($xmlHeader = false)
362 header('Content-type: text/xml');
368 * Get text representation of XML tree.
370 * @return string Text (XML) representation of the tree
375 $out = '<?xml version="' . $this->version . "\"?>\n";
376 if (!is_null($this->root))
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();
386 * Get current namespace.
388 * @param string name namespace
393 function getName($name) {
394 return $this->root->getElement($this->namespace[$name]);
398 * Register a namespace.
400 * @param string $name namespace
401 * @param string $path path
405 function registerName($name, $path) {
406 $this->namespace[$name] = $path;
410 * Get a reference to a node. Node is searched by its 'path'.
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.
421 function getNodeAt($path)
423 if (is_null($this->root)){
424 return $this->raiseError("XML_Tree hasn't a root node");
426 if (is_string($path))
427 $path = explode("/", $path);
428 if (sizeof($path) == 0) {
429 return $this->raiseError("Path to node is empty");
432 $rootName = array_shift($path1);
433 if ($this->root->name != $rootName) {
434 return $this->raiseError("Path does not match the document root");
436 $x = $this->root->getNodeAt($path1);
437 if (!PEAR::isError($x)) {
440 // No node with that name found
441 return $this->raiseError("Bad path to node: [".implode('/', $path)."]");
445 * Gets all children that match a given tag name.
447 * @param string Tag name
449 * @return array An array of Node objects of the children found,
450 * an empty array if none
452 * @author Pierre-Alain Joye <paj@pearfr.org>
454 function getElementsByTagName($tagName)
456 if (empty($tagName)) {
457 return $this->raiseError('Empty tag name');
459 if (sizeof($this->children)==0) {
463 foreach ($this->children as $child) {
464 if ($child->name == $tagName) {