4 * XML utilities for WebDAV
8 * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
9 * @author Evert Pot (http://www.rooftopsolutions.nl/)
10 * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
12 class Sabre_DAV_XMLUtil {
15 * Returns the 'clark notation' for an element.
17 * For example, and element encoded as:
18 * <b:myelem xmlns:b="http://www.example.org/" />
19 * will be returned as:
20 * {http://www.example.org}myelem
22 * This format is used throughout the SabreDAV sourcecode.
23 * Elements encoded with the urn:DAV namespace will
24 * be returned as if they were in the DAV: namespace. This is to avoid
25 * compatibility problems.
27 * This function will return null if a nodetype other than an Element is passed.
32 static function toClarkNotation(DOMNode $dom) {
34 if ($dom->nodeType !== XML_ELEMENT_NODE) return null;
36 // Mapping back to the real namespace, in case it was dav
37 if ($dom->namespaceURI=='urn:DAV') $ns = 'DAV:'; else $ns = $dom->namespaceURI;
39 // Mapping to clark notation
40 return '{' . $ns . '}' . $dom->localName;
45 * Parses a clark-notation string, and returns the namespace and element
48 * If the string was invalid, it will throw an InvalidArgumentException.
51 * @throws InvalidArgumentException
54 static function parseClarkNotation($str) {
56 if (!preg_match('/^{([^}]*)}(.*)$/',$str,$matches)) {
57 throw new InvalidArgumentException('\'' . $str . '\' is not a valid clark-notation formatted string');
68 * This method takes an XML document (as string) and converts all instances of the
69 * DAV: namespace to urn:DAV
71 * This is unfortunately needed, because the DAV: namespace violates the xml namespaces
72 * spec, and causes the DOM to throw errors
74 * @param string $xmlDocument
75 * @return array|string|null
77 static function convertDAVNamespace($xmlDocument) {
79 // This is used to map the DAV: namespace to urn:DAV. This is needed, because the DAV:
80 // namespace is actually a violation of the XML namespaces specification, and will cause errors
81 return preg_replace("/xmlns(:[A-Za-z0-9_]*)?=(\"|\')DAV:(\\2)/","xmlns\\1=\\2urn:DAV\\2",$xmlDocument);
86 * This method provides a generic way to load a DOMDocument for WebDAV use.
88 * This method throws a Sabre_DAV_Exception_BadRequest exception for any xml errors.
89 * It does not preserve whitespace, and it converts the DAV: namespace to urn:DAV.
92 * @throws Sabre_DAV_Exception_BadRequest
95 static function loadDOMDocument($xml) {
98 throw new Sabre_DAV_Exception_BadRequest('Empty XML document sent');
100 // The BitKinex client sends xml documents as UTF-16. PHP 5.3.1 (and presumably lower)
101 // does not support this, so we must intercept this and convert to UTF-8.
102 if (substr($xml,0,12) === "\x3c\x00\x3f\x00\x78\x00\x6d\x00\x6c\x00\x20\x00") {
104 // Note: the preceeding byte sequence is "<?xml" encoded as UTF_16, without the BOM.
105 $xml = iconv('UTF-16LE','UTF-8',$xml);
107 // Because the xml header might specify the encoding, we must also change this.
108 // This regex looks for the string encoding="UTF-16" and replaces it with
110 $xml = preg_replace('|<\?xml([^>]*)encoding="UTF-16"([^>]*)>|u','<?xml\1encoding="UTF-8"\2>',$xml);
114 // Retaining old error setting
115 $oldErrorSetting = libxml_use_internal_errors(true);
117 // Clearing any previous errors
118 libxml_clear_errors();
120 $dom = new DOMDocument();
121 $dom->loadXML(self::convertDAVNamespace($xml),LIBXML_NOWARNING | LIBXML_NOERROR);
123 // We don't generally care about any whitespace
124 $dom->preserveWhiteSpace = false;
126 if ($error = libxml_get_last_error()) {
127 libxml_clear_errors();
128 throw new Sabre_DAV_Exception_BadRequest('The request body had an invalid XML body. (message: ' . $error->message . ', errorcode: ' . $error->code . ', line: ' . $error->line . ')');
131 // Restoring old mechanism for error handling
132 if ($oldErrorSetting===false) libxml_use_internal_errors(false);
139 * Parses all WebDAV properties out of a DOM Element
141 * Generally WebDAV properties are enclosed in {DAV:}prop elements. This
142 * method helps by going through all these and pulling out the actual
143 * propertynames, making them array keys and making the property values,
144 * well.. the array values.
146 * If no value was given (self-closing element) null will be used as the
147 * value. This is used in for example PROPFIND requests.
149 * Complex values are supported through the propertyMap argument. The
150 * propertyMap should have the clark-notation properties as it's keys, and
151 * classnames as values.
153 * When any of these properties are found, the unserialize() method will be
154 * (statically) called. The result of this method is used as the value.
156 * @param DOMElement $parentNode
157 * @param array $propertyMap
160 static function parseProperties(DOMElement $parentNode, array $propertyMap = array()) {
163 foreach($parentNode->childNodes as $propNode) {
165 if (Sabre_DAV_XMLUtil::toClarkNotation($propNode)!=='{DAV:}prop') continue;
167 foreach($propNode->childNodes as $propNodeData) {
169 /* If there are no elements in here, we actually get 1 text node, this special case is dedicated to netdrive */
170 if ($propNodeData->nodeType != XML_ELEMENT_NODE) continue;
172 $propertyName = Sabre_DAV_XMLUtil::toClarkNotation($propNodeData);
173 if (isset($propertyMap[$propertyName])) {
174 $propList[$propertyName] = call_user_func(array($propertyMap[$propertyName],'unserialize'),$propNodeData);
176 $propList[$propertyName] = $propNodeData->textContent;