PageRenderTime 33ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/inc/XMLDocument.php

https://gitlab.com/karora/awl
PHP | 332 lines | 165 code | 37 blank | 130 comment | 60 complexity | d89cdd5fa3ee10e535883aa98a61150e MD5 | raw file
  1. <?php
  2. /**
  3. * Handling of namespacing for XML documents
  4. *
  5. * @package awl
  6. * @subpackage XMLDocument
  7. * @author Andrew McMillan <andrew@morphoss.com>
  8. * @copyright Morphoss Ltd - http://www.morphoss.com/
  9. * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL version 3 or later
  10. *
  11. */
  12. require_once("XMLElement.php");
  13. /**
  14. * A class for XML Documents which will contain namespaced XML elements
  15. *
  16. * @package awl
  17. */
  18. class XMLDocument {
  19. /**#@+
  20. * @access private
  21. */
  22. /**
  23. * holds the namespaces which this document has been configured for.
  24. * @var namespaces
  25. */
  26. private $namespaces;
  27. /**
  28. * holds the prefixes which are shorthand for the namespaces.
  29. * @var prefixes
  30. */
  31. private $prefixes;
  32. /**
  33. * Holds the root document for the tree
  34. * @var root
  35. */
  36. private $root;
  37. /**
  38. * Simple XMLDocument constructor
  39. *
  40. * @param array $namespaces An array of 'namespace' => 'prefix' pairs, where the prefix is used as a short form for the namespace.
  41. */
  42. function __construct( $namespaces = null ) {
  43. $this->namespaces = array();
  44. $this->prefixes = array();
  45. if ( $namespaces != null ) {
  46. foreach( $namespaces AS $ns => $prefix ) {
  47. $this->namespaces[$ns] = $prefix;
  48. $this->prefixes[$prefix] = $prefix;
  49. }
  50. }
  51. $this->next_prefix = 0;
  52. }
  53. /**
  54. * Add a new namespace to the document, optionally specifying it's short prefix
  55. *
  56. * @param string $namespace The full namespace name to be added
  57. * @param string $prefix An optional short form for the namespace.
  58. */
  59. function AddNamespace( $namespace, $prefix = null ) {
  60. if ( !isset($this->namespaces[$namespace]) ) {
  61. if ( isset($prefix) && ($prefix == "" || isset($this->prefixes[$prefix])) ) $prefix = null;
  62. if ( $prefix == null ) {
  63. // Try and build a prefix based on the first alphabetic character of the last element of the namespace
  64. if ( preg_match('/^(.*):([^:]+)$/', $namespace, $matches) ) {
  65. $alpha = preg_replace( '/[^a-z]/i', '', $matches[2] );
  66. $prefix = strtoupper(substr($alpha,0,1));
  67. }
  68. else {
  69. $prefix = 'X';
  70. }
  71. $i = "";
  72. if ( isset($this->prefixes[$prefix]) ) {
  73. for ( $i=1; $i<10 && isset($this->prefixes["$prefix$i"]); $i++ ) {
  74. }
  75. }
  76. if ( isset($this->prefixes["$prefix$i"]) ) {
  77. dbg_error_log("ERROR", "Cannot find a free prefix for this namespace");
  78. exit;
  79. }
  80. $prefix = "$prefix$i";
  81. dbg_error_log("XMLDocument", "auto-assigning prefix of '%s' for ns of '%s'", $prefix, $namespace );
  82. }
  83. else if ( $prefix == "" || isset($this->prefixes[$prefix]) ) {
  84. dbg_error_log("ERROR", "Cannot assign the same prefix to two different namespaces");
  85. exit;
  86. }
  87. $this->prefixes[$prefix] = $prefix;
  88. $this->namespaces[$namespace] = $prefix;
  89. }
  90. else {
  91. if ( isset($this->namespaces[$namespace]) && $this->namespaces[$namespace] != $prefix ) {
  92. dbg_error_log("ERROR", "Cannot use the same namespace with two different prefixes");
  93. exit;
  94. }
  95. $this->prefixes[$prefix] = $prefix;
  96. $this->namespaces[$namespace] = $prefix;
  97. }
  98. }
  99. /**
  100. * Return the default namespace for this document
  101. */
  102. function DefaultNamespace() {
  103. foreach( $this->namespaces AS $k => $v ) {
  104. if ( $v == '' ) {
  105. return $k;
  106. }
  107. }
  108. return '';
  109. }
  110. /**
  111. * Return a tag with namespace stripped and replaced with a short form, and the ns added to the document.
  112. *
  113. */
  114. function GetXmlNsArray() {
  115. $ns = array();
  116. foreach( $this->namespaces AS $n => $p ) {
  117. if ( $p == "" ) $ns["xmlns"] = $n; else $ns["xmlns:$p"] = $n;
  118. }
  119. return $ns;
  120. }
  121. /**
  122. * Return a tag with namespace stripped and replaced with a short form, and the ns added to the document.
  123. *
  124. * @param string $in_tag The tag we want a namespace prefix on.
  125. * @param string $namespace The namespace we want it in (which will be parsed from $in_tag if not present
  126. * @param string $prefix The prefix we would like to use. Leave it out and one will be assigned.
  127. *
  128. * @return string The tag with a namespace prefix consistent with previous tags in this namespace.
  129. */
  130. function Tag( $in_tag, $namespace=null, $prefix=null ) {
  131. if ( $namespace == null ) {
  132. // Attempt to split out from namespace:tag
  133. if ( preg_match('/^(.*):([^:]+)$/', $in_tag, $matches) ) {
  134. $namespace = $matches[1];
  135. $tag = $matches[2];
  136. }
  137. else {
  138. // There is nothing we can do here
  139. return $in_tag;
  140. }
  141. }
  142. else {
  143. $tag = $in_tag;
  144. }
  145. if ( !isset($this->namespaces[$namespace]) ) {
  146. $this->AddNamespace( $namespace, $prefix );
  147. }
  148. $prefix = $this->namespaces[$namespace];
  149. return $prefix . ($prefix == "" ? "" : ":") . $tag;
  150. }
  151. static public $ns_dav = 'DAV:';
  152. static public $ns_caldav = 'urn:ietf:params:xml:ns:caldav';
  153. static public $ns_carddav = 'urn:ietf:params:xml:ns:carddav';
  154. static public $ns_calendarserver = 'http://calendarserver.org/ns/';
  155. /**
  156. * Special helper for namespaced tags.
  157. *
  158. * @param object $element The tag are adding a new namespaced element to
  159. * @param string $tag the tag name, possibly prefixed with the namespace
  160. * @param mixed $content The content of the tag
  161. * @param array $attributes An array of key/value pairs of attributes.
  162. * @param string $namespace The namespace for the tag
  163. *
  164. */
  165. function NSElement( &$element, $in_tag, $content=false, $attributes=false, $namespace=null ) {
  166. if ( $namespace == null && preg_match('/^(.*):([^:]+)$/', $in_tag, $matches) ) {
  167. $namespace = $matches[1];
  168. if ( preg_match('{^[A-Z][A-Z0-9]*$}', $namespace ) ) {
  169. throw new Exception("Dodgy looking namespace from '".$in_tag."'!");
  170. }
  171. $tag = $matches[2];
  172. }
  173. else {
  174. $tag = $in_tag;
  175. if ( isset($namespace) ) {
  176. $tag = str_replace($namespace.':', '', $tag);
  177. }
  178. }
  179. if ( isset($namespace) && !isset($this->namespaces[$namespace]) ) $this->AddNamespace( $namespace );
  180. return $element->NewElement( $tag, $content, $attributes, $namespace );
  181. }
  182. /**
  183. * Special helper for tags in the DAV: namespace.
  184. *
  185. * @param object $element The tag are adding a new namespaced element to
  186. * @param string $tag the tag name
  187. * @param mixed $content The content of the tag
  188. * @param array $attributes An array of key/value pairs of attributes.
  189. */
  190. function DAVElement( &$element, $tag, $content=false, $attributes=false ) {
  191. if ( !isset($this->namespaces[self::$ns_dav]) ) $this->AddNamespace( self::$ns_dav, '' );
  192. return $this->NSElement( $element, $tag, $content, $attributes, self::$ns_dav );
  193. }
  194. /**
  195. * Special helper for tags in the urn:ietf:params:xml:ns:caldav namespace.
  196. *
  197. * @param object $element The tag are adding a new namespaced element to
  198. * @param string $tag the tag name
  199. * @param mixed $content The content of the tag
  200. * @param array $attributes An array of key/value pairs of attributes.
  201. */
  202. function CalDAVElement( &$element, $tag, $content=false, $attributes=false ) {
  203. if ( !isset($this->namespaces[self::$ns_caldav]) ) $this->AddNamespace( self::$ns_caldav, 'C' );
  204. return $this->NSElement( $element, $tag, $content, $attributes, self::$ns_caldav );
  205. }
  206. /**
  207. * Special helper for tags in the urn:ietf:params:xml:ns:carddav namespace.
  208. *
  209. * @param object $element The tag are adding a new namespaced element to
  210. * @param string $tag the tag name
  211. * @param mixed $content The content of the tag
  212. * @param array $attributes An array of key/value pairs of attributes.
  213. */
  214. function CardDAVElement( &$element, $tag, $content=false, $attributes=false ) {
  215. if ( !isset($this->namespaces[self::$ns_carddav]) ) $this->AddNamespace( self::$ns_carddav, 'VC' );
  216. return $this->NSElement( $element, $tag, $content, $attributes, self::$ns_carddav );
  217. }
  218. /**
  219. * Special helper for tags in the urn:ietf:params:xml:ns:caldav namespace.
  220. *
  221. * @param object $element The tag are adding a new namespaced element to
  222. * @param string $tag the tag name
  223. * @param mixed $content The content of the tag
  224. * @param array $attributes An array of key/value pairs of attributes.
  225. */
  226. function CalendarserverElement( &$element, $tag, $content=false, $attributes=false ) {
  227. if ( !isset($this->namespaces[self::$ns_calendarserver]) ) $this->AddNamespace( self::$ns_calendarserver, 'A' );
  228. return $this->NSElement( $element, $tag, $content, $attributes, self::$ns_calendarserver );
  229. }
  230. /**
  231. * @param string $in_tag The tag name of the new element, possibly namespaced
  232. * @param mixed $content Either a string of content, or an array of sub-elements
  233. * @param array $attributes An array of attribute name/value pairs
  234. * @param array $xmlns An XML namespace specifier
  235. */
  236. function NewXMLElement( $in_tag, $content=false, $attributes=false, $xmlns=null ) {
  237. if ( $xmlns == null && preg_match('/^(.*):([^:]+)$/', $in_tag, $matches) ) {
  238. $xmlns = $matches[1];
  239. $tagname = $matches[2];
  240. }
  241. else {
  242. $tagname = $in_tag;
  243. }
  244. if ( isset($xmlns) && !isset($this->namespaces[$xmlns]) ) $this->AddNamespace( $xmlns );
  245. return new XMLElement($tagname, $content, $attributes, $xmlns );
  246. }
  247. /**
  248. * Render the document tree into (nicely formatted) XML
  249. *
  250. * @param mixed $root A root XMLElement or a tagname to create one with the remaining parameters.
  251. * @param mixed $content Either a string of content, or an array of sub-elements
  252. * @param array $attributes An array of attribute name/value pairs
  253. * @param array $xmlns An XML namespace specifier
  254. *
  255. * @return A rendered namespaced XML document.
  256. */
  257. function Render( $root, $content=false, $attributes=false, $xmlns=null ) {
  258. if ( is_object($root) ) {
  259. /** They handed us a pre-existing object. We'll just use it... */
  260. $this->root = $root;
  261. }
  262. else {
  263. /** We got a tag name, so we need to create the root element */
  264. $this->root = $this->NewXMLElement( $root, $content, $attributes, $xmlns );
  265. }
  266. /**
  267. * Add our namespace attributes here.
  268. */
  269. foreach( $this->namespaces AS $n => $p ) {
  270. $this->root->SetAttribute( 'xmlns'.($p == '' ? '' : ':') . $p, $n);
  271. }
  272. /** And render... */
  273. return $this->root->Render(0,'<?xml version="1.0" encoding="utf-8" ?>');
  274. }
  275. /**
  276. * Return a DAV::href XML element, or an array of them
  277. * @param mixed $url The URL (or array of URLs) to be wrapped in DAV::href tags
  278. *
  279. * @return XMLElement The newly created XMLElement object.
  280. */
  281. function href($url) {
  282. if ( is_array($url) ) {
  283. $set = array();
  284. foreach( $url AS $href ) {
  285. $set[] = $this->href( $href );
  286. }
  287. return $set;
  288. }
  289. if ( preg_match('[@+ ]',$url) ) {
  290. trace_bug('URL "%s" was not encoded before call to XMLDocument::href()', $url );
  291. $url = str_replace( '%2F', '/', rawurlencode($url));
  292. }
  293. return $this->NewXMLElement('href', $url, false, 'DAV:');
  294. }
  295. }