. ** ** it_xml.class - XML parser / object factory */ 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 * @param $p['forcearray'] xml tags to ALWAYS return as array * @param $p['safety'] 2 causes program abort with invalid xml, 1 (default) causes error report, 0 just returns false * @param $p['encoding'] Output character encoding (e.g. UTF-8, default: ISO-8859-1) * @param $p['prefix'] Optional prefix for class names * @param $p['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("", 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", 'safety' => 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 = '_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 = '_p['encoding'] . '"?>' . $xmldata; # decode illegal entities but protect semantically important ones $xmldata = html_entity_decode(preg_replace('/&(amp|lt|gt|#38|#60|#62|#x26|#x3C|#3E);/i', '&$1;', $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(array('title' => $this->error, 'body' => $xmldata)); 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($dummy_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($dummy_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($dummy_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" : ($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']; } } ?>