diff options
author | Christian Schneider | 2007-10-11 00:39:30 +0000 |
---|---|---|
committer | Christian Schneider | 2007-10-11 00:39:30 +0000 |
commit | 35fe33f7364329dacf415c950bff01b6de9ef88e (patch) | |
tree | b0e6b018b50038ca20266723c53750268f508df5 /it_xml.class | |
parent | 1f95711ff3e9697cd85a54545ab42e5fd3611317 (diff) | |
download | itools-35fe33f7364329dacf415c950bff01b6de9ef88e.tar.gz itools-35fe33f7364329dacf415c950bff01b6de9ef88e.tar.bz2 itools-35fe33f7364329dacf415c950bff01b6de9ef88e.zip |
Populated release branch
Diffstat (limited to 'it_xml.class')
-rw-r--r-- | it_xml.class | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/it_xml.class b/it_xml.class new file mode 100644 index 0000000..8cd386a --- /dev/null +++ b/it_xml.class @@ -0,0 +1,293 @@ +<?php +/* +** $Id$ +** +** it_xml.class - XML parser / object factory +** +** ITools - the Internet Tools Library +** +** Copyright (C) 1995-2005 by the ITools Authors. +** This program is free software; you can redistribute it and/or +** modify it under the terms of either the GNU General Public License +** or the GNU Lesser General Public License, as published by the Free +** Software Foundation. See http://www.gnu.org/licenses/ for details. +*/ + +class it_xml +{ +/* + * Add xml snippet as attribute tree to $this + * @param $xmldata XML string or filehandle (result from fopen) to parse + * @param $p associative array + * forcearray xml tags to ALWAYS return as array + * safety value 2 causes program abort with invalid xml, 1 causes error report + * encoding Output character encoding (e.g. UTF-8, default: ISO-8859-1) + * prefix Optional prefix for class names + * lowercase Lowercase all tag and attribute names + * @return XML object tree or null on failure + */ + +function it_xml($xmldata = "", $p = array()) +{ + if ($xmldata) + $this->from_xml($xmldata, $p); +} + +/** + * Factory method to return XML object tree from XML string. Params like constructor + * Example: $root = it_xml::create("<a><b id='2'/></a>", array("b")); + */ + +function create($xmldata, $p = array()) +{ + $xml = new it_xml; + + return $xml->from_xml($xmldata, array('factory' => true) + $p) ? $xml->_root : null; +} + +function from_xml($xmldata, $p) +{ + $this->_p = $p + array('encoding' => "ISO-8859-1"); + $this->_arrayforce = array_flip((array)$this->_p['forcearray']); + $this->_stack = array(); + unset($this->error); + $parser = xml_parser_create($this->_p['encoding']); + xml_set_object($parser, $this); + xml_set_element_handler($parser, "start_element", "end_element"); + xml_set_character_data_handler($parser, "character_data"); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); + xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $this->_p['encoding']); + + $result = true; + + if (is_resource($xmldata)) + { + if (!preg_match('/^<\?xml/', ($head = fread($xmldata, 1024 * 1024)))) # Prepend XML header for charset detection in PHP5 + $head = '<?xml version="1.0" encoding="' . $this->_p['encoding'] . '"?>' . $head; + + $result = xml_parse($parser, $head, false); + + while ($result && !feof($xmldata)) + $result = xml_parse($parser, fread($xmldata, 1024 * 1024), false); + + if ($result) + $result = xml_parse($parser, "", true); + } + else + { + # Add header for charset detection (PHP5) if no header/BOM + # See http://www.w3.org/TR/2006/REC-xml-20060816/#sec-guessing + if (!preg_match('/^(<\?xml|\xEF\xBB\xBF|\xFE\xFF|\xFF\xFE|\x00\x00\xFE\xFF|\x00\x00\xFF\xFE)/', $xmldata)) + $xmldata = '<?xml version="1.0" encoding="' . $this->_p['encoding'] . '"?>' . $xmldata; + + $result = xml_parse($parser, $xmldata); + } + + if (!$result) + $this->error .= sprintf("it_xml error: %s at line %d", xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser)); + + if ($this->error) + { + if ($this->_p['safety'] >= 2) + it::fatal($this->error); + else if ($this->_p['safety'] >= 1) + it::error($this->error); + + if ($this->_p['factory']) + $GLOBALS['IT_XML_ERROR'] = $this->error; + } + + unset($this->_arrayforce, $this->_p['safety'], $this->_p['factory'], $this->_stack); + xml_parser_free($parser); + + return empty($this->error); +} + +function consume($p) +{ + return false; +} + +/** + * Utility function which recursively flattens container into array + * @param with_attr if true, include attributes in result array + * @return Array with key/values-pairs for vals and optionally attrs of sub-objects + */ +function flatten($with_attr = false) +{ + $result = $with_attr ? (array)$this->attr : array(); + + foreach ($this->elements() as $key => $values) + { + if (is_array($values)) + { + $result[$key] = array(); + + foreach ($values as $value) + $result[$key][] = isset($value->val) || !get_object_vars($value) ? $value->val : $value->flatten($with_attr); + } + else + $result[$key] = isset($values->val) || !get_object_vars($values) ? $values->val : $values->flatten($with_attr); # don' take recursion if val is set or object is empty + } + + return $result; +} + +# Strip namespace and convert extra characters to _ +function _make_identifier($name) +{ + $id = preg_replace(array('/^.*:/', '/\W/'), array('', '_'), $name); + return $this->_p['lowercase'] ? strtolower($id) : $id; +} + +function start_element($parser, $name, $attrs) +{ + $name = $this->_p['prefix'] . $this->_make_identifier($name); + + if (!class_exists($name)) + eval("class $name extends it_xml {}"); # Extending the base class caused problems with tel_xmlentry + + if (!$this->_stack && !$this->_p['factory']) + array_unshift($this->_stack, &$this); + else + array_unshift($this->_stack, new $name); + + if (!is_a($this->_stack[0], "it_xml")) + $this->error .= "Class $name used in it_xml for tag $name does not extend it_xml: Name clash?\n"; + + if ($attrs) + { + foreach ($attrs as $key => $val) + $this->_stack[0]->attr[$this->_make_identifier($key)] = $val; + } +} + +function end_element($parser, $name) +{ + $name = $this->_make_identifier($name); + + if (!$this->_stack[0]->consume($this->_p)) + { + if (is_array($this->_stack[1]->$name)) + array_push($this->_stack[1]->$name, &$this->_stack[0]); + else if (isset($this->_stack[1]->$name)) + { + if (is_a($this->_stack[1]->$name, "it_xml")) + $this->_stack[1]->$name = array($this->_stack[1]->$name, &$this->_stack[0]); + else + $this->error .= "Variable $name used in it_xml for tag {$name} is already used in object: Name clash?\n"; + } + else if (isset($this->_arrayforce[$name])) + $this->_stack[1]->$name = array(&$this->_stack[0]); + else + $this->_stack[1]->$name =& $this->_stack[0]; + } + + $node = array_shift($this->_stack); + + if ($this->_p['factory']) + $this->_root = $node; +} + +function character_data($parser, $cdata) +{ + if (isset($this->_stack[0]->val) || (trim($cdata) !== "")) + $this->_stack[0]->val .= $cdata; +} + +/** + * Convert XML object tree back to string representation + * @param $p Associative array + * tag Name of root XML tag (otherwise determined by get_class) + * indent String to indent with (default: " ") + * lineseparator String to start new line with (default: "\n") + * prefix String to prefix class names with (default: "") + * sort value true causes to_xml() to output elements sorted + * @return XML string representation of this object + */ +function to_xml($p = array()) +{ + $p += array( + 'tag' => substr(get_class($this), strlen($p['prefix'])), + 'indent' => " ", + 'lineseparator' => "\n", + 'prefix' => "", + 'sort' => false, + 'stripempty' => false, # remove xml tags without content + ); + + $currenttag = $p['tag']; + $tag = $p['nextindentation'] . "<$currenttag"; + + if (empty($p['nextindentation'])) + $p['nextindentation'] = $p['lineseparator']; + + $currentindentation = $p['nextindentation']; + $p['nextindentation'] .= $p['indent']; + + foreach ((array)$this->attr as $key => $value) + $tag .= " $key=\"" . htmlspecialchars($value) . '"'; + + $vars = get_object_vars($this); + + if ($p['sort']) + ksort($vars); + + foreach ($vars as $key => $values) + { + $p['tag'] = $key; # Manually set as PHP4 lowercases get_class() + + if (!preg_match('/^(_|attr$)/', $key)) # Skip implementation detail fields + { + if (is_array($values)) + { + foreach ($values as $value) + { + if (is_a($value, "it_xml")) + { + $body .= $value->to_xml($p); + $indentation = $currentindentation; + } + } + } + else if (is_object($values)) + { + if (is_a($values, "it_xml")) + { + $body .= $values->to_xml($p); + $indentation = $currentindentation; + } + } + else if ($key == 'val') + $body .= htmlspecialchars($values); + } + } + + return isset($body) ? "$tag>$body$indentation</$currenttag>" : ($p['stripempty'] ? "" : "$tag/>"); +} + +/** + * Get array containing child elements (key value pairs) + * @return Array containing child elements + */ +function elements() +{ + $result = array(); + + foreach (get_object_vars($this) as $key => $value) + { + if (is_a($value, "it_xml") || (is_array($value) && is_a($value[0], "it_xml"))) + $result[$key] =& $this->$key; + } + + return $result; +} + +function error() +{ + return is_a($this, "it_xml") ? $this->error : $GLOBALS['IT_XML_ERROR']; +} + +} + +?> |