3 * @version $Id: simplexml.php 1962 2009-06-03 11:17:48Z fishbone $
4 * @package Joomla.Framework
5 * @subpackage Utilities
6 * @copyright Copyright (C) 2005 - 2008 Open Source Matters. All rights reserved.
7 * @license GNU/GPL, see LICENSE.php
8 * Joomla! is free software. This version may have been modified pursuant
9 * to the GNU General Public License, and as distributed it includes or
10 * is derivative of works licensed under the GNU General Public License or
11 * other free or open source software licenses.
12 * See COPYRIGHT.php for copyright notices and details.
15 // Check to ensure this file is within the rest of the framework
16 //defined('JPATH_BASE') or die();
19 * SimpleXML implementation.
21 * The XML Parser extension (expat) is required to use JSimpleXML.
23 * The class provides a pure PHP4 implementation of the PHP5
24 * interface SimpleXML. As with PHP5's SimpleXML it is what it says:
25 * simple. Nevertheless, it is an easy way to deal with XML data,
26 * especially for read only access.
28 * Because it's not possible to use the PHP5 ArrayIterator interface
29 * with PHP4 there are some differences between this implementation
33 * <li>The access to the root node has to be explicit in
34 * JSimpleXML, not implicit as with PHP5. Write
35 * $xml->document->node instead of $xml->node</li>
36 * <li>You cannot acces CDATA using array syntax. Use the method data() instead</li>
37 * <li>You cannot access attributes directly with array syntax. use attributes()
39 * <li>Comments are ignored.</li>
40 * <li>Last and least, this is not as fast as PHP5 SimpleXML--it is pure PHP4.</li>
46 * <?xml version="1.0" encoding="utf-8" standalone="yes"?>
49 * <child gender="m">Tom Foo</child>
50 * <child gender="f">Tamara Bar</child>
56 * // read and write a document
57 * $xml = new JSimpleXML;
58 * $xml->loadFile('simple.xml');
59 * print $xml->document->toString();
61 * // access a given node's CDATA
62 * print $xml->root->node->child[0]->data(); // Tom Foo
64 * // access attributes
65 * $attr = $xml->root->node->child[1]->attributes();
66 * print $attr['gender']; // f
69 * foreach( $xml->root->node->children() as $child ) {
70 * print $child->data();
74 * Note: JSimpleXML cannot be used to access sophisticated XML doctypes
75 * using datatype ANY (e.g. XHTML). With a DOM implementation you can
78 * @package Joomla.Framework
79 * @subpackage Utilities
82 class JSimpleXML extends JObject
103 var $document = null;
106 * Current object depth
110 var $_stack = array();
118 function __construct($options = null)
120 if(! function_exists('xml_parser_create')) {
121 return false; //TODO throw warning
124 //Create the parser resource and make sure both versions of PHP autodetect the format.
125 // $this->_parser = xml_parser_create('');
126 $this->_parser = xml_parser_create(M3_ENCODING); // 日本語を処理
128 // check parser resource
129 xml_set_object($this->_parser, $this);
130 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, 0);
131 if( is_array($options) )
133 foreach( $options as $option => $value ) {
134 xml_parser_set_option($this->_parser, $option, $value);
139 xml_set_element_handler($this->_parser, '_startElement', '_endElement');
140 xml_set_character_data_handler($this->_parser, '_characterData');
144 * Interprets a string of XML into an object
146 * This function will take the well-formed xml string data and return an object of class
147 * JSimpleXMLElement with properties containing the data held within the xml document.
148 * If any errors occur, it returns FALSE.
150 * @param string Well-formed xml string data
151 * @param string currently ignored
152 * @return object JSimpleXMLElement
154 function loadString($string, $classname = null) {
155 $this->_parse($string);
160 * Interprets an XML file into an object
162 * This function will convert the well-formed XML document in the file specified by filename
163 * to an object of class JSimpleXMLElement. If any errors occur during file access or
164 * interpretation, the function returns FALSE.
166 * @param string Path to xml file containing a well-formed XML document
167 * @param string currently ignored
168 * @return boolean True if successful, false if file empty
170 function loadFile($path, $classname = null)
172 //Check to see of the path exists
173 if ( !file_exists( $path ) ) {
177 //Get the XML document loaded into a variable
178 $xml = trim( file_get_contents($path) );
191 * Get a JSimpleXMLElement object from a DOM node.
193 * This function takes a node of a DOM document and makes it into a JSimpleXML node.
194 * This new object can then be used as a native JSimpleXML element. If any errors occur,
197 * @param string DOM document
198 * @param string currently ignored
199 * @return object JSimpleXMLElement
201 function importDOM($node, $classname = null) {
209 * @return resource XML parser resource handle
211 function getParser() {
212 return $this->_parser;
219 * @param resource XML parser resource handle
221 function setParser($parser) {
222 $this->_parser = $parser;
226 * Start parsing an XML document
228 * Parses an XML document. The handlers for the configured events are called as many times as necessary.
230 * @param $xml string data to parse
232 function _parse($data = '')
235 if (!xml_parse($this->_parser, $data)) {
237 xml_get_error_code($this->_parser),
238 xml_get_current_line_number($this->_parser),
239 xml_get_current_column_number($this->_parser)
244 xml_parser_free($this->_parser);
248 * Handles an XML parsing error
251 * @param int $code XML Error Code
252 * @param int $line Line on which the error happened
253 * @param int $col Column on which the error happened
255 function _handleError($code, $line, $col)
257 //JError::raiseWarning( 'SOME_ERROR_CODE' , 'XML Parsing Error at '.$line.':'.$col.'. Error '.$code.': '.xml_error_string($code));
258 debug( 'SOME_ERROR_CODE: XML Parsing Error at '.$line.':'.$col.'. Error '.$code.': '.xml_error_string($code));
262 * Gets the reference to the current direct parent
266 function _getStackLocation()
269 foreach($this->_stack as $stack) {
270 $return .= $stack.'->';
273 return rtrim($return, '->');
277 * Handler function for the start of a tag
280 * @param resource $parser
281 * @param string $name
282 * @param array $attrs
284 function _startElement($parser, $name, $attrs = array())
286 //Check to see if tag is root-level
287 $count = count($this->_stack);
290 //If so, set the document as the current tag
291 $classname = get_class( $this ) . 'Element';
292 $this->document = new $classname($name, $attrs);
294 //And start out the stack with the document tag
295 $this->_stack = array('document');
297 //If it isn't root level, use the stack to find the parent
300 //Get the name which points to the current direct parent, relative to $this
301 $parent = $this->_getStackLocation();
304 eval('$this->'.$parent.'->addChild($name, $attrs, '.$count.');');
307 eval('$this->_stack[] = $name.\'[\'.(count($this->'.$parent.'->'.$name.') - 1).\']\';');
312 * Handler function for the end of a tag
315 * @param resource $parser
316 * @param string $name
318 function _endElement($parser, $name)
320 //Update stack by removing the end value from it as the parent
321 array_pop($this->_stack);
325 * Handler function for the character data within a tag
328 * @param resource $parser
329 * @param string $data
331 function _characterData($parser, $data)
333 //Get the reference to the current parent object
334 $tag = $this->_getStackLocation();
337 eval('$this->'.$tag.'->_data .= $data;');
345 * This object stores all of the direct children of itself in the $children array.
346 * They are also stored by type as arrays. So, if, for example, this tag had 2 <font>
347 * tags as children, there would be a class member called $font created as an array.
348 * $font[0] would be the first font tag, and $font[1] would be the second.
350 * To loop through all of the direct children of this object, the $children member
353 * To loop through all of the direct children of a specific tag for this object, it
354 * is probably easier to use the arrays of the specific tag names, as explained above.
356 * @package Joomla.Framework
357 * @subpackage Utilities
360 class JSimpleXMLElement extends JObject
363 * Array with the attributes of this XML element
367 var $_attributes = array();
370 * The name of the element
377 * The data the element contains
384 * Array of references to the objects of all direct children of this XML object
388 var $_children = array();
391 * The level of this XML element
398 * Constructor, sets up all the default values
400 * @param string $name
401 * @param array $attrs
402 * @param int $parents
403 * @return JSimpleXMLElement
405 function __construct($name, $attrs = array(), $level = 0)
407 //Make the keys of the attr array lower case, and store the value
408 $this->_attributes = array_change_key_case($attrs, CASE_LOWER);
410 //Make the name lower case and store the value
411 $this->_name = strtolower($name);
414 $this->_level = $level;
418 * Get the name of the element
428 * Get the an attribute of the element
430 * @param string $attribute The name of the attribute
433 * @return mixed If an attribute is given will return the attribute if it exist.
434 * If no attribute is given will return the complete attributes array
436 function attributes($attribute = null)
438 if(!isset($attribute)) {
439 return $this->_attributes;
442 return isset($this->_attributes[$attribute]) ? $this->_attributes[$attribute] : null;
446 * Get the data of the element
456 * Set the data of the element
459 * @param string $data
462 function setData($data) {
463 $this->_data = $data;
467 * Get the children of the element
472 function children() {
473 return $this->_children;
477 * Get the level of the element
483 return $this->_level;
487 * Adds an attribute to the element
489 * @param string $name
490 * @param array $attrs
492 function addAttribute($name, $value)
494 //add the attribute to the element, override if it already exists
495 $this->_attributes[$name] = $value;
499 * Removes an attribute from the element
501 * @param string $name
503 function removeAttribute($name)
505 unset($this->_attributes[$name]);
509 * Adds a direct child to the element
511 * @param string $name
512 * @param array $attrs
514 * @return JSimpleXMLElement The added child object
516 function &addChild($name, $attrs = array(), $level = null)
518 //If there is no array already set for the tag name being added,
519 //create an empty array for it
520 if(!isset($this->$name)) {
521 $this->$name = array();
524 // set the level if not already specified
525 if ($level == null) {
526 $level = ($this->_level + 1);
529 //Create the child object itself
530 $classname = get_class( $this );
531 $child = new $classname( $name, $attrs, $level );
533 //Add the reference of it to the end of an array member named for the elements name
534 $this->{$name}[] =& $child;
536 //Add the reference to the children array member
537 $this->_children[] =& $child;
539 //return the new child
543 function removeChild(&$child)
545 $name = $child->name();
546 for ($i=0,$n=count($this->_children);$i<$n;$i++)
548 if ($this->_children[$i] == $child) {
549 unset($this->_children[$i]);
552 for ($i=0,$n=count($this->{$name});$i<$n;$i++)
554 if ($this->{$name}[$i] == $child) {
555 unset($this->{$name}[$i]);
558 $this->_children = array_values($this->_children);
559 $this->{$name} = array_values($this->{$name});
564 * Get an element in the document by / separated path
566 * @param string $path The / separated path to the element
567 * @return object JSimpleXMLElement
569 function &getElementByPath($path)
573 $parts = explode('/', trim($path, '/'));
575 foreach ($parts as $node)
578 foreach ($tmp->_children as $child)
580 if ($child->_name == $node)
601 * traverses the tree calling the $callback( JSimpleXMLElement
602 * $this, mixed $args=array() ) function with each JSimpleXMLElement.
604 * @param string $callback function name
607 function map($callback, $args=array())
609 $callback($this, $args);
610 // Map to all children
611 if ($n = count($this->_children)) {
614 $this->_children[$i]->map($callback, $args);
620 * Return a well-formed XML string based on SimpleXML element
624 function toString($whitespace=true)
626 //Start a new line, indent by the number indicated in $this->level, add a <, and add the name of the tag
628 $out = "\n".str_repeat("\t", $this->_level).'<'.$this->_name;
630 $out = '<'.$this->_name;
633 //For each attribute, add attr="value"
634 foreach($this->_attributes as $attr => $value) {
635 $out .= ' '.$attr.'="'.htmlspecialchars($value).'"';
638 //If there are no children and it contains no data, end it off with a />
639 if (empty($this->_children) && empty($this->_data)) {
644 //If there are children
645 if(!empty($this->_children))
647 //Close off the start tag
650 //For each child, call the asXML function (this will ensure that all children are added recursively)
651 foreach($this->_children as $child)
652 $out .= $child->toString($whitespace);
654 //Add the newline and indentation to go along with the close tag
656 $out .= "\n".str_repeat("\t", $this->_level);
660 //If there is data, close off the start tag and add the data
661 elseif(!empty($this->_data))
662 $out .= '>'.htmlspecialchars($this->_data);
665 $out .= '</'.$this->_name.'>';
668 //Return the final output