summaryrefslogtreecommitdiff
path: root/it_xml.class
diff options
context:
space:
mode:
authorChristian Schneider2007-10-11 00:39:30 +0000
committerChristian Schneider2007-10-11 00:39:30 +0000
commit35fe33f7364329dacf415c950bff01b6de9ef88e (patch)
treeb0e6b018b50038ca20266723c53750268f508df5 /it_xml.class
parent1f95711ff3e9697cd85a54545ab42e5fd3611317 (diff)
downloaditools-35fe33f7364329dacf415c950bff01b6de9ef88e.tar.gz
itools-35fe33f7364329dacf415c950bff01b6de9ef88e.tar.bz2
itools-35fe33f7364329dacf415c950bff01b6de9ef88e.zip
Populated release branch
Diffstat (limited to 'it_xml.class')
-rw-r--r--it_xml.class293
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'];
+}
+
+}
+
+?>