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 // +----------------------------------------------------------------------+
21 // $Id: Node.php,v 1.18 2003/09/11 19:03:27 cox Exp $
27 * @author Bernd Römer <berndr@bonn.edu>
29 * @version 1.0 16-Aug-2001
33 * Attributes of this node
40 * Children of this node
47 * Content (text) of this node
63 * @param string name Node name
64 * @param string content Node content (text)
65 * @param array attributes Attribute-hash for the node
67 function XML_Tree_Node($name, $content = '', $attributes = array(), $lineno = null)
70 $this->setContent($content);
71 $this->attributes = $attributes;
72 $this->children = array();
73 $this->lineno = $lineno;
77 * Append a child node to this node, after all other nodes
79 * @param mixed child Child to insert (XML_Tree or XML_Tree_Node),
80 * or name of child node
81 * @param string content Content (text) for the new node (only if
82 * $child is the node name)
83 * @param array attributes Attribute-hash for new node
85 * @return object reference to new child node
88 function &addChild($child, $content = '', $attributes = array(), $lineno = null)
90 $index = sizeof($this->children);
92 if (is_object($child)) {
93 if (strtolower(get_class($child)) == 'xml_tree_node') {
94 $this->children[$index] = $child;
97 if (strtolower(get_class($child)) == 'xml_tree' && isset($child->root)) {
98 $this->children[$index] = $child->root->getElement();
101 $this->children[$index] = new XML_Tree_Node($child, $content, $attributes, $lineno);
104 return $this->children[$index];
108 * Get a copy of this node by clone this node and all of its children,
111 * @return object Reference to the cloned copy.
114 function &cloneNode()
116 $clone = new XML_Tree_Node($this->name,$this->content,$this->attributes);
118 $max_child=count($this->children);
119 for($i=0;$i<$max_child;$i++) {
120 $clone->children[]=$this->children[$i]->cloneNode();
123 /* for future use....
124 // clone all other vars
125 $temp=get_object_vars($this);
126 foreach($temp as $varname => $value)
127 if (!in_array($varname,array('name','content','attributes','children')))
128 $clone->$varname=$value;
135 * Inserts child ($child) to a specified child-position ($pos)
137 * @param mixed path Path to parent node to add child (see getNodeAt()
138 * for format). If null child is inserted in the
140 * @param integer pos Position where to insert the new child.
141 * 0 < means |$pos| elements before the end,
142 * e.g. -1 appends as last child.
143 * @param mixed child Child to insert (XML_Tree or XML_Tree_Node),
144 * or name of child node
145 * @param string content Content (text) for the new node (only if
146 * $child is the node name)
147 * @param array attributes Attribute-hash for new node
149 * @return Reference to the newly inserted node, or PEAR_Error upon insertion error.
152 function &insertChild($path,$pos,&$child, $content = '', $attributes = array())
154 $parent =& $this->getNodeAt($path);
155 if (PEAR::isError($parent)) {
156 // $path was not found
158 } elseif ($parent != $this) {
159 // Insert at the node found
160 return $parent->insertChild(null, $pos, $child, $content, $attributes);
163 if (($pos < -count($this->children)) || ($pos > count($this->children))) {
164 return new PEAR_Error("Invalid insert position.");
167 if (is_object($child)) { // child is an object
168 // insert a single node
169 if (strtolower(get_class($child)) == 'xml_tree_node') {
170 array_splice($this->children, $pos, 0, 'dummy');
172 $pos = count($this->children) + $pos - 1;
174 $this->children[$pos] = &$child;
175 // insert a tree i.e insert root-element of tree
176 } elseif (strtolower(get_class($child)) == 'xml_tree' && isset($child->root)) {
177 array_splice($this->children, $pos, 0, 'dummy');
179 $pos = count($this->children) + $pos - 1;
181 $this->children[$pos] = $child->root;
183 return new PEAR_Error("Bad node (must be a XML_Tree or an XML_Tree_Node)");
185 } else { // child offered is a string
186 array_splice($this->children, $pos, 0, 'dummy');
188 $pos = count($this->children) + $pos - 1;
190 $this->children[$pos] = new XML_Tree_Node($child, $content, $attributes);
196 * Removes child at a given position
198 * @param integer pos position of child to remove in children-list.
199 * 0 < means |$pos| elements before the end,
200 * e.g. -1 removes the last child.
202 * @return mixed The removed node, or PEAR_Error upon removal error.
205 function &removeChild($pos)
207 if (($pos < -count($this->children)) || ($pos >= count($this->children))) {
208 return new PEAR_Error("Invalid remove position.");
211 // Using array_splice() instead of a simple unset() to maintain index-integrity
212 return array_splice($this->children, $pos, 1);
216 * Returns text representation of this node.
218 * @return string text (xml) representation of this node. Each tag is
219 * indented according to its level.
225 static $do_ident = true;
227 if ($this->name !== null) {
228 $ident = str_repeat(' ', $deep);
230 $out = $ident . '<' . $this->name;
232 $out = '<' . $this->name;
234 foreach ($this->attributes as $name => $value) {
235 $out .= ' ' . $name . '="' . $value . '"';
238 $out .= '>' . $this->content;
240 if (sizeof($this->children) > 0) {
242 foreach ($this->children as $child) {
243 $out .= $child->get();
249 $out .= $ident . '</' . $this->name . ">\n";
251 $out .= '</' . $this->name . '>';
255 $out = $this->content;
263 * Get an attribute by its name.
265 * @param string $name Name of attribute to retrieve
267 * @return string attribute, or null if attribute is unset.
270 function getAttribute($name)
272 if (isset($this->attributes[strtolower($name)])) {
273 return $this->attributes[strtolower($name)];
279 * Sets an attribute for this node.
281 * @param string name Name of attribute to set
282 * @param string value Value of attribute
286 function setAttribute($name, $value = '')
288 $this->attributes[strtolower($name)] = $value;
292 * Unsets an attribute of this node.
294 * @param string $name Name of attribute to unset
298 function unsetAttribute($name)
300 if (isset($this->attributes[strtolower($name)])) {
301 unset($this->attributes[strtolower($name)]);
306 * Sets the content for this node.
308 * @param string content Node content to assign
312 function setContent(&$content)
314 $this->content = $this->encodeXmlEntities($content);
318 * Gets an element by its 'path'.
320 * @param array path path to element: sequence of indexes to the
321 * children. E.g. array(1, 2, 3) means "third
322 * child of second child of first child" of the node.
324 * @return object reference to element found, or PEAR_Error if node can't
328 function &getElement($path)
330 if (!is_array($path)) {
331 $path = array($path);
333 if (sizeof($path) == 0) {
338 $next = array_shift($path1);
339 if (isset($this->children[$next])) {
340 $x =& $this->children[$next]->getElement($path1);
341 if (!PEAR::isError($x)) {
346 return new PEAR_Error("Bad path to node: [".implode('-', $path)."]");
350 * Get a reference to a node by its 'path'.
352 * @param mixed path Path to node. Can be either a string (slash-separated
353 * children names) or an array (sequence of children names) both
354 * starting from this node. The first name in sequence
355 * is a child name, not the name of this node.
357 * @return object Reference to the XML_Tree_Node found, or PEAR_Error if
358 * the path does not match any node. Note that if more than
359 * one element matches then only the first matching node is
363 function &getNodeAt($path)
365 if (is_string($path))
366 $path = explode("/", $path);
368 if (sizeof($path) == 0) {
373 $next = array_shift($path1);
375 // Get the first children of this node whose name is '$next'
377 for ($i = 0; $i < count($this->children); $i++) {
378 if ($this->children[$i]->name == $next) {
379 $child =& $this->children[$i];
383 if (!is_null($child)) {
384 $x =& $child->getNodeAt($path1);
385 if (!PEAR::isError($x)) {
390 // No node with that name found
391 return new PEAR_Error("Bad path to node: [".implode('/', $path)."]");
395 * Escape XML entities.
397 * @param string xml Text string to escape.
402 function encodeXmlEntities($xml)
404 $xml = str_replace(array('ü', 'Ü', 'ö',
409 array('ü', 'Ü', 'ö',
410 'Ö', 'ä', 'Ä',
411 'ß', '<', '>',
416 // fixme - remove the /e!!!
417 $xml = @preg_replace(array("/\&([a-z\d\#]+)\;/i",
419 "/\#\|\|([a-z\d\#]+)\|\|\#/i",
420 "/([^a-zA-Z\d\s\<\>\&\;\.\:\=\"\-\/\%\?\!\'\(\)\[\]\{\}\$\#\+\,\@_])/e"
425 "'&#'.ord('\\1').';'"
434 * Decode XML entities in a text string.
436 * @param string xml Text to decode
438 * @return string Decoded text
441 function decodeXmlEntities($xml)
443 static $trans_tbl = null;
445 $trans_tbl = get_html_translation_table(HTML_ENTITIES);
446 $trans_tbl = array_flip($trans_tbl);
448 for ($i = 1; $i <= 255; $i++) {
449 $ent = sprintf("&#%03d;", $i);
451 $xml = str_replace($ent, $ch, $xml);
454 return strtr($xml, $trans_tbl);
459 * Print text representation of XML_Tree_Node.