| // +----------------------------------------------------------------------+ // // $Id$ /** * error code for invalid chars in XML name */ define("XML_UTIL_ERROR_INVALID_CHARS", 51); /** * error code for invalid chars in XML name */ define("XML_UTIL_ERROR_INVALID_START", 52); /** * error code for non-scalar tag content */ define("XML_UTIL_ERROR_NON_SCALAR_CONTENT", 60); /** * error code for missing tag name */ define("XML_UTIL_ERROR_NO_TAG_NAME", 61); /** * replace XML entities */ define("XML_UTIL_REPLACE_ENTITIES", 1); /** * embedd content in a CData Section */ define("XML_UTIL_CDATA_SECTION", 5); /** * do not replace entitites */ define("XML_UTIL_ENTITIES_NONE", 0); /** * replace all XML entitites * This setting will replace <, >, ", ' and & */ define("XML_UTIL_ENTITIES_XML", 1); /** * replace only required XML entitites * This setting will replace <, " and & */ define("XML_UTIL_ENTITIES_XML_REQUIRED", 2); /** * replace HTML entitites * @link http://www.php.net/htmlentities */ define("XML_UTIL_ENTITIES_HTML", 3); /** * Collapse all empty tags. */ define("XML_UTIL_COLLAPSE_ALL", 1); /** * Collapse only empty XHTML tags that have no end tag. */ define("XML_UTIL_COLLAPSE_XHTML_ONLY", 2); /** * utility class for working with XML documents * * @category XML * @package XML_Util * @version 1.1.0 * @author Stephan Schmidt */ class XML_Util { /** * return API version * * @access public * @static * @return string $version API version */ function apiVersion() { return '1.1'; } /** * replace XML entities * * With the optional second parameter, you may select, which * entities should be replaced. * * * require_once 'XML/Util.php'; * * // replace XML entites: * $string = XML_Util::replaceEntities("This string contains < & >."); * * * @access public * @static * @param string string where XML special chars should be replaced * @param integer setting for entities in attribute values (one of XML_UTIL_ENTITIES_XML, XML_UTIL_ENTITIES_XML_REQUIRED, XML_UTIL_ENTITIES_HTML) * @return string string with replaced chars * @see reverseEntities() */ function replaceEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML) { switch ($replaceEntities) { case XML_UTIL_ENTITIES_XML: return strtr($string,array( '&' => '&', '>' => '>', '<' => '<', '"' => '"', '\'' => ''' )); break; case XML_UTIL_ENTITIES_XML_REQUIRED: return strtr($string,array( '&' => '&', '<' => '<', '"' => '"' )); break; case XML_UTIL_ENTITIES_HTML: return htmlentities($string); break; } return $string; } /** * reverse XML entities * * With the optional second parameter, you may select, which * entities should be reversed. * * * require_once 'XML/Util.php'; * * // reverse XML entites: * $string = XML_Util::reverseEntities("This string contains < & >."); * * * @access public * @static * @param string string where XML special chars should be replaced * @param integer setting for entities in attribute values (one of XML_UTIL_ENTITIES_XML, XML_UTIL_ENTITIES_XML_REQUIRED, XML_UTIL_ENTITIES_HTML) * @return string string with replaced chars * @see replaceEntities() */ function reverseEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML) { switch ($replaceEntities) { case XML_UTIL_ENTITIES_XML: return strtr($string,array( '&' => '&', '>' => '>', '<' => '<', '"' => '"', ''' => '\'' )); break; case XML_UTIL_ENTITIES_XML_REQUIRED: return strtr($string,array( '&' => '&', '<' => '<', '"' => '"' )); break; case XML_UTIL_ENTITIES_HTML: $arr = array_flip(get_html_translation_table(HTML_ENTITIES)); return strtr($string, $arr); break; } return $string; } /** * build an xml declaration * * * require_once 'XML/Util.php'; * * // get an XML declaration: * $xmlDecl = XML_Util::getXMLDeclaration("1.0", "UTF-8", true); * * * @access public * @static * @param string $version xml version * @param string $encoding character encoding * @param boolean $standAlone document is standalone (or not) * @return string $decl xml declaration * @uses XML_Util::attributesToString() to serialize the attributes of the XML declaration */ function getXMLDeclaration($version = "1.0", $encoding = null, $standalone = null) { $attributes = array( "version" => $version, ); // add encoding if ($encoding !== null) { $attributes["encoding"] = $encoding; } // add standalone, if specified if ($standalone !== null) { $attributes["standalone"] = $standalone ? "yes" : "no"; } return sprintf("", XML_Util::attributesToString($attributes, false)); } /** * build a document type declaration * * * require_once 'XML/Util.php'; * * // get a doctype declaration: * $xmlDecl = XML_Util::getDocTypeDeclaration("rootTag","myDocType.dtd"); * * * @access public * @static * @param string $root name of the root tag * @param string $uri uri of the doctype definition (or array with uri and public id) * @param string $internalDtd internal dtd entries * @return string $decl doctype declaration * @since 0.2 */ function getDocTypeDeclaration($root, $uri = null, $internalDtd = null) { if (is_array($uri)) { $ref = sprintf( ' PUBLIC "%s" "%s"', $uri["id"], $uri["uri"] ); } elseif (!empty($uri)) { $ref = sprintf( ' SYSTEM "%s"', $uri ); } else { $ref = ""; } if (empty($internalDtd)) { return sprintf("", $root, $ref); } else { return sprintf("", $root, $ref, $internalDtd); } } /** * create string representation of an attribute list * * * require_once 'XML/Util.php'; * * // build an attribute string * $att = array( * "foo" => "bar", * "argh" => "tomato" * ); * * $attList = XML_Util::attributesToString($att); * * * @access public * @static * @param array $attributes attribute array * @param boolean|array $sort sort attribute list alphabetically, may also be an assoc array containing the keys 'sort', 'multiline', 'indent', 'linebreak' and 'entities' * @param boolean $multiline use linebreaks, if more than one attribute is given * @param string $indent string used for indentation of multiline attributes * @param string $linebreak string used for linebreaks of multiline attributes * @param integer $entities setting for entities in attribute values (one of XML_UTIL_ENTITIES_NONE, XML_UTIL_ENTITIES_XML, XML_UTIL_ENTITIES_XML_REQUIRED, XML_UTIL_ENTITIES_HTML) * @return string string representation of the attributes * @uses XML_Util::replaceEntities() to replace XML entities in attribute values * @todo allow sort also to be an options array */ function attributesToString($attributes, $sort = true, $multiline = false, $indent = ' ', $linebreak = "\n", $entities = XML_UTIL_ENTITIES_XML) { /** * second parameter may be an array */ if (is_array($sort)) { if (isset($sort['multiline'])) { $multiline = $sort['multiline']; } if (isset($sort['indent'])) { $indent = $sort['indent']; } if (isset($sort['linebreak'])) { $multiline = $sort['linebreak']; } if (isset($sort['entities'])) { $entities = $sort['entities']; } if (isset($sort['sort'])) { $sort = $sort['sort']; } else { $sort = true; } } $string = ''; if (is_array($attributes) && !empty($attributes)) { if ($sort) { ksort($attributes); } if( !$multiline || count($attributes) == 1) { foreach ($attributes as $key => $value) { if ($entities != XML_UTIL_ENTITIES_NONE) { if ($entities === XML_UTIL_CDATA_SECTION) { $entities = XML_UTIL_ENTITIES_XML; } $value = XML_Util::replaceEntities($value, $entities); } $string .= ' '.$key.'="'.$value.'"'; } } else { $first = true; foreach ($attributes as $key => $value) { if ($entities != XML_UTIL_ENTITIES_NONE) { $value = XML_Util::replaceEntities($value, $entities); } if ($first) { $string .= " ".$key.'="'.$value.'"'; $first = false; } else { $string .= $linebreak.$indent.$key.'="'.$value.'"'; } } } } return $string; } /** * Collapses empty tags. * * @access public * @static * @param string $xml XML * @param integer $mode Whether to collapse all empty tags (XML_UTIL_COLLAPSE_ALL) or only XHTML (XML_UTIL_COLLAPSE_XHTML_ONLY) ones. * @return string $xml XML */ function collapseEmptyTags($xml, $mode = XML_UTIL_COLLAPSE_ALL) { if ($mode == XML_UTIL_COLLAPSE_XHTML_ONLY) { return preg_replace( '/<(area|base|br|col|hr|img|input|link|meta|param)([^>]*)><\/\\1>/s', '<\\1\\2 />', $xml ); } else { return preg_replace( '/<(\w+)([^>]*)><\/\\1>/s', '<\\1\\2 />', $xml ); } } /** * create a tag * * This method will call XML_Util::createTagFromArray(), which * is more flexible. * * * require_once 'XML/Util.php'; * * // create an XML tag: * $tag = XML_Util::createTag("myNs:myTag", array("foo" => "bar"), "This is inside the tag", "http://www.w3c.org/myNs#"); * * * @access public * @static * @param string $qname qualified tagname (including namespace) * @param array $attributes array containg attributes * @param mixed $content * @param string $namespaceUri URI of the namespace * @param integer $replaceEntities whether to replace XML special chars in content, embedd it in a CData section or none of both * @param boolean $multiline whether to create a multiline tag where each attribute gets written to a single line * @param string $indent string used to indent attributes (_auto indents attributes so they start at the same column) * @param string $linebreak string used for linebreaks * @param boolean $sortAttributes Whether to sort the attributes or not * @return string $string XML tag * @see XML_Util::createTagFromArray() * @uses XML_Util::createTagFromArray() to create the tag */ function createTag($qname, $attributes = array(), $content = null, $namespaceUri = null, $replaceEntities = XML_UTIL_REPLACE_ENTITIES, $multiline = false, $indent = "_auto", $linebreak = "\n", $sortAttributes = true) { $tag = array( "qname" => $qname, "attributes" => $attributes ); // add tag content if ($content !== null) { $tag["content"] = $content; } // add namespace Uri if ($namespaceUri !== null) { $tag["namespaceUri"] = $namespaceUri; } return XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, $indent, $linebreak, $sortAttributes); } /** * create a tag from an array * this method awaits an array in the following format *
    * array(
    *  "qname"        => $qname         // qualified name of the tag
    *  "namespace"    => $namespace     // namespace prefix (optional, if qname is specified or no namespace)
    *  "localpart"    => $localpart,    // local part of the tagname (optional, if qname is specified)
    *  "attributes"   => array(),       // array containing all attributes (optional)
    *  "content"      => $content,      // tag content (optional)
    *  "namespaceUri" => $namespaceUri  // namespaceUri for the given namespace (optional)
    *   )
    * 
* * * require_once 'XML/Util.php'; * * $tag = array( * "qname" => "foo:bar", * "namespaceUri" => "http://foo.com", * "attributes" => array( "key" => "value", "argh" => "fruit&vegetable" ), * "content" => "I'm inside the tag", * ); * // creating a tag with qualified name and namespaceUri * $string = XML_Util::createTagFromArray($tag); * * * @access public * @static * @param array $tag tag definition * @param integer $replaceEntities whether to replace XML special chars in content, embedd it in a CData section or none of both * @param boolean $multiline whether to create a multiline tag where each attribute gets written to a single line * @param string $indent string used to indent attributes (_auto indents attributes so they start at the same column) * @param string $linebreak string used for linebreaks * @param boolean $sortAttributes Whether to sort the attributes or not * @return string $string XML tag * @see XML_Util::createTag() * @uses XML_Util::attributesToString() to serialize the attributes of the tag * @uses XML_Util::splitQualifiedName() to get local part and namespace of a qualified name */ function createTagFromArray($tag, $replaceEntities = XML_UTIL_REPLACE_ENTITIES, $multiline = false, $indent = "_auto", $linebreak = "\n", $sortAttributes = true) { if (isset($tag['content']) && !is_scalar($tag['content'])) { return XML_Util::raiseError( 'Supplied non-scalar value as tag content', XML_UTIL_ERROR_NON_SCALAR_CONTENT ); } if (!isset($tag['qname']) && !isset($tag['localPart'])) { return XML_Util::raiseError( 'You must either supply a qualified name (qname) or local tag name (localPart).', XML_UTIL_ERROR_NO_TAG_NAME ); } // if no attributes hav been set, use empty attributes if (!isset($tag["attributes"]) || !is_array($tag["attributes"])) { $tag["attributes"] = array(); } if (isset($tag['namespaces'])) { foreach ($tag['namespaces'] as $ns => $uri) { $tag['attributes']['xmlns:'.$ns] = $uri; } } // qualified name is not given if (!isset($tag["qname"])) { // check for namespace if (isset($tag["namespace"]) && !empty($tag["namespace"])) { $tag["qname"] = $tag["namespace"].":".$tag["localPart"]; } else { $tag["qname"] = $tag["localPart"]; } // namespace URI is set, but no namespace } elseif (isset($tag["namespaceUri"]) && !isset($tag["namespace"])) { $parts = XML_Util::splitQualifiedName($tag["qname"]); $tag["localPart"] = $parts["localPart"]; if (isset($parts["namespace"])) { $tag["namespace"] = $parts["namespace"]; } } if (isset($tag["namespaceUri"]) && !empty($tag["namespaceUri"])) { // is a namespace given if (isset($tag["namespace"]) && !empty($tag["namespace"])) { $tag["attributes"]["xmlns:".$tag["namespace"]] = $tag["namespaceUri"]; } else { // define this Uri as the default namespace $tag["attributes"]["xmlns"] = $tag["namespaceUri"]; } } // check for multiline attributes if ($multiline === true) { if ($indent === "_auto") { $indent = str_repeat(" ", (strlen($tag["qname"])+2)); } } // create attribute list $attList = XML_Util::attributesToString($tag['attributes'], $sortAttributes, $multiline, $indent, $linebreak, $replaceEntities ); if (!isset($tag['content']) || (string)$tag['content'] == '') { $tag = sprintf('<%s%s />', $tag['qname'], $attList); } else { switch ($replaceEntities) { case XML_UTIL_ENTITIES_NONE: break; case XML_UTIL_CDATA_SECTION: $tag['content'] = XML_Util::createCDataSection($tag['content']); break; default: $tag['content'] = XML_Util::replaceEntities($tag['content'], $replaceEntities); break; } $tag = sprintf('<%s%s>%s', $tag['qname'], $attList, $tag['content'], $tag['qname'] ); } return $tag; } /** * create a start element * * * require_once 'XML/Util.php'; * * // create an XML start element: * $tag = XML_Util::createStartElement("myNs:myTag", array("foo" => "bar") ,"http://www.w3c.org/myNs#"); * * * @access public * @static * @param string $qname qualified tagname (including namespace) * @param array $attributes array containg attributes * @param string $namespaceUri URI of the namespace * @param boolean $multiline whether to create a multiline tag where each attribute gets written to a single line * @param string $indent string used to indent attributes (_auto indents attributes so they start at the same column) * @param string $linebreak string used for linebreaks * @param boolean $sortAttributes Whether to sort the attributes or not * @return string $string XML start element * @see XML_Util::createEndElement(), XML_Util::createTag() */ function createStartElement($qname, $attributes = array(), $namespaceUri = null, $multiline = false, $indent = '_auto', $linebreak = "\n", $sortAttributes = true) { // if no attributes hav been set, use empty attributes if (!isset($attributes) || !is_array($attributes)) { $attributes = array(); } if ($namespaceUri != null) { $parts = XML_Util::splitQualifiedName($qname); } // check for multiline attributes if ($multiline === true) { if ($indent === "_auto") { $indent = str_repeat(" ", (strlen($qname)+2)); } } if ($namespaceUri != null) { // is a namespace given if (isset($parts["namespace"]) && !empty($parts["namespace"])) { $attributes["xmlns:".$parts["namespace"]] = $namespaceUri; } else { // define this Uri as the default namespace $attributes["xmlns"] = $namespaceUri; } } // create attribute list $attList = XML_Util::attributesToString($attributes, $sortAttributes, $multiline, $indent, $linebreak); $element = sprintf("<%s%s>", $qname, $attList); return $element; } /** * create an end element * * * require_once 'XML/Util.php'; * * // create an XML start element: * $tag = XML_Util::createEndElement("myNs:myTag"); * * * @access public * @static * @param string $qname qualified tagname (including namespace) * @return string $string XML end element * @see XML_Util::createStartElement(), XML_Util::createTag() */ function createEndElement($qname) { $element = sprintf("", $qname); return $element; } /** * create an XML comment * * * require_once 'XML/Util.php'; * * // create an XML start element: * $tag = XML_Util::createComment("I am a comment"); * * * @access public * @static * @param string $content content of the comment * @return string $comment XML comment */ function createComment($content) { $comment = sprintf("", $content); return $comment; } /** * create a CData section * * * require_once 'XML/Util.php'; * * // create a CData section * $tag = XML_Util::createCDataSection("I am content."); * * * @access public * @static * @param string $data data of the CData section * @return string $string CData section with content */ function createCDataSection($data) { return sprintf("", $data); } /** * split qualified name and return namespace and local part * * * require_once 'XML/Util.php'; * * // split qualified tag * $parts = XML_Util::splitQualifiedName("xslt:stylesheet"); * * the returned array will contain two elements: *
    * array(
    *       "namespace" => "xslt",
    *       "localPart" => "stylesheet"
    *      );
    * 
* * @access public * @static * @param string $qname qualified tag name * @param string $defaultNs default namespace (optional) * @return array $parts array containing namespace and local part */ function splitQualifiedName($qname, $defaultNs = null) { if (strstr($qname, ':')) { $tmp = explode(":", $qname); return array( "namespace" => $tmp[0], "localPart" => $tmp[1] ); } return array( "namespace" => $defaultNs, "localPart" => $qname ); } /** * check, whether string is valid XML name * *

XML names are used for tagname, attribute names and various * other, lesser known entities.

*

An XML name may only consist of alphanumeric characters, * dashes, undescores and periods, and has to start with a letter * or an underscore. *

* * * require_once 'XML/Util.php'; * * // verify tag name * $result = XML_Util::isValidName("invalidTag?"); * if (XML_Util::isError($result)) { * print "Invalid XML name: " . $result->getMessage(); * } * * * @access public * @static * @param string $string string that should be checked * @return mixed $valid true, if string is a valid XML name, PEAR error otherwise * @todo support for other charsets */ function isValidName($string) { // check for invalid chars if (!preg_match('/^[[:alpha:]_]$/', $string{0})) { return XML_Util::raiseError('XML names may only start with letter or underscore', XML_UTIL_ERROR_INVALID_START); } // check for invalid chars if (!preg_match('/^([[:alpha:]_]([[:alnum:]\-\.]*)?:)?[[:alpha:]_]([[:alnum:]\_\-\.]+)?$/', $string)) { return XML_Util::raiseError('XML names may only contain alphanumeric chars, period, hyphen, colon and underscores', XML_UTIL_ERROR_INVALID_CHARS); } // XML name is valid return true; } /** * replacement for XML_Util::raiseError * * Avoids the necessity to always require * PEAR.php * * @access public * @param string error message * @param integer error code * @return object PEAR_Error */ function raiseError($msg, $code) { require_once 'PEAR.php'; return PEAR::raiseError($msg, $code); } } ?>