]> git.datanom.net - webcal.git/blob - caldav/awl/XMLElement.php
Initial upload
[webcal.git] / caldav / awl / XMLElement.php
1 <?php
2 /**
3 * A class to assist with construction of XML documents
4 *
5 * @package awl
6 * @subpackage XMLElement
7 * @author Andrew McMillan <andrew@mcmillan.net.nz>
8 * @copyright Catalyst .Net Ltd, Morphoss Ltd <http://www.morphoss.com/>
9 * @license http://gnu.org/copyleft/gpl.html GNU GPL v2
10 */
11 require_once("AWLUtilities.php");
12
13 /**
14 * A class for XML elements which may have attributes, or contain
15 * other XML sub-elements
16 *
17 * @package awl
18 */
19 class XMLElement {
20 var $tagname;
21 var $xmlns;
22 var $attributes;
23 var $content;
24 var $_parent;
25
26 /**
27 * Constructor - nothing fancy as yet.
28 *
29 * @param string $tagname The tag name of the new element
30 * @param mixed $content Either a string of content, or an array of sub-elements
31 * @param array $attributes An array of attribute name/value pairs
32 * @param array $xmlns An XML namespace specifier
33 */
34 function XMLElement( $tagname, $content=false, $attributes=false, $xmlns=null ) {
35 $this->tagname=$tagname;
36 if ( gettype($content) == "object" ) {
37 // Subtree to be parented here
38 $this->content = array(&$content);
39 }
40 else {
41 // Array or text
42 $this->content = $content;
43 }
44 $this->attributes = $attributes;
45 if ( isset($this->attributes['xmlns']) ) { // Oversimplification to be removed
46 $this->xmlns = $this->attributes['xmlns'];
47 }
48 if ( isset($xmlns) ) {
49 $this->xmlns = $xmlns;
50 }
51 }
52
53
54 /**
55 * Count the number of elements
56 * @return int The number of elements
57 */
58 function CountElements( ) {
59 if ( $this->content === false ) return 0;
60 if ( is_array($this->content) ) return count($this->content);
61 if ( $this->content == '' ) return 0;
62 return 1;
63 }
64
65 /**
66 * Set an element attribute to a value
67 *
68 * @param string The attribute name
69 * @param string The attribute value
70 */
71 function SetAttribute($k,$v) {
72 if ( gettype($this->attributes) != "array" ) $this->attributes = array();
73 $this->attributes[$k] = $v;
74 if ( strtolower($k) == 'xmlns' ) {
75 $this->xmlns = $v;
76 }
77 }
78
79 /**
80 * Set the whole content to a value
81 *
82 * @param mixed The element content, which may be text, or an array of sub-elements
83 */
84 function SetContent($v) {
85 $this->content = $v;
86 }
87
88 /**
89 * Accessor for the tag name
90 *
91 * @return string The tag name of the element
92 */
93 function GetTag() {
94 return $this->tagname;
95 }
96
97 /**
98 * Accessor for the full-namespaced tag name
99 *
100 * @return string The tag name of the element, prefixed by the namespace
101 */
102 function GetNSTag() {
103 return $this->xmlns . ':' . $this->tagname;
104 }
105
106 /**
107 * Accessor for a single attribute
108 * @param string $attr The name of the attribute.
109 * @return string The value of that attribute of the element
110 */
111 function GetAttribute( $attr ) {
112 if ( isset($this->attributes[$attr]) ) return $this->attributes[$attr];
113 }
114
115 /**
116 * Accessor for the attributes
117 *
118 * @return array The attributes of this element
119 */
120 function GetAttributes() {
121 return $this->attributes;
122 }
123
124 /**
125 * Accessor for the content
126 *
127 * @return array The content of this element
128 */
129 function GetContent() {
130 return $this->content;
131 }
132
133 /**
134 * Return an array of elements matching the specified tag
135 *
136 * @return array The XMLElements within the tree which match this tag
137 */
138 function GetElements( $tag, $recursive=false ) {
139 $elements = array();
140 if ( gettype($this->content) == "array" ) {
141 foreach( $this->content AS $k => $v ) {
142 if ( $v->tagname == $tag ) {
143 $elements[] = $v;
144 }
145 if ( $recursive ) {
146 $elements = $elements + $v->GetElements($tag,true);
147 }
148 }
149 }
150 return $elements;
151 }
152
153
154 /**
155 * Return an array of elements matching the specified path
156 *
157 * @return array The XMLElements within the tree which match this tag
158 */
159 function GetPath( $path ) {
160 $elements = array();
161 // printf( "Querying within '%s' for path '%s'\n", $this->tagname, $path );
162 if ( !preg_match( '#(/)?([^/]+)(/?.*)$#', $path, $matches ) ) return $elements;
163 // printf( "Matches: %s -- %s -- %s\n", $matches[1], $matches[2], $matches[3] );
164 if ( $matches[2] == '*' || strtolower($matches[2]) == strtolower($this->tagname) ) {
165 if ( $matches[3] == '' ) {
166 /**
167 * That is the full path
168 */
169 $elements[] = $this;
170 }
171 else if ( gettype($this->content) == "array" ) {
172 /**
173 * There is more to the path, so we recurse into that sub-part
174 */
175 foreach( $this->content AS $k => $v ) {
176 $elements = array_merge( $elements, $v->GetPath($matches[3]) );
177 }
178 }
179 }
180
181 if ( $matches[1] != '/' && gettype($this->content) == "array" ) {
182 /**
183 * If our input $path was not rooted, we recurse further
184 */
185 foreach( $this->content AS $k => $v ) {
186 $elements = array_merge( $elements, $v->GetPath($path) );
187 }
188 }
189 // printf( "Found %d within '%s' for path '%s'\n", count($elements), $this->tagname, $path );
190 return $elements;
191 }
192
193
194 /**
195 * Add a sub-element
196 *
197 * @param object An XMLElement to be appended to the array of sub-elements
198 */
199 function AddSubTag(&$v) {
200 if ( gettype($this->content) != "array" ) $this->content = array();
201 $this->content[] =& $v;
202 return count($this->content);
203 }
204
205 /**
206 * Add a new sub-element
207 *
208 * @param string The tag name of the new element
209 * @param mixed Either a string of content, or an array of sub-elements
210 * @param array An array of attribute name/value pairs
211 *
212 * @return objectref A reference to the new XMLElement
213 */
214 function &NewElement( $tagname, $content=false, $attributes=false, $xmlns=null ) {
215 if ( gettype($this->content) != "array" ) $this->content = array();
216 $element =/*&*/ new XMLElement($tagname,$content,$attributes,$xmlns);
217 $this->content[] =/*&*/ $element;
218 return $element;
219 }
220
221
222 /**
223 * Render just the internal content
224 *
225 * @return string The content of this element, as a string without this element wrapping it.
226 */
227 function RenderContent($indent=0, $nslist=null ) {
228 $r = "";
229 if ( is_array($this->content) ) {
230 /**
231 * Render the sub-elements with a deeper indent level
232 */
233 $r .= "\n";
234 foreach( $this->content AS $k => $v ) {
235 if ( is_object($v) ) {
236 $r .= $v->Render($indent+1, "", $nslist);
237 }
238 }
239 $r .= substr(" ",0,$indent);
240 }
241 else {
242 /**
243 * Render the content, with special characters escaped
244 *
245 */
246 $r .= htmlspecialchars($this->content, ENT_NOQUOTES );
247 }
248 return $r;
249 }
250
251
252 /**
253 * Render the document tree into (nicely formatted) XML
254 *
255 * @param int The indenting level for the pretty formatting of the element
256 */
257 function Render($indent=0, $xmldef="", $nslist=null) {
258 $r = ( $xmldef == "" ? "" : $xmldef."\n");
259
260 $attr = "";
261 $tagname = $this->tagname;
262 if ( gettype($this->attributes) == "array" ) {
263 /**
264 * Render the element attribute values
265 */
266 foreach( $this->attributes AS $k => $v ) {
267 if ( preg_match('#^xmlns(:?(.+))?$#', $k, $matches ) ) {
268 if ( !isset($nslist) ) $nslist = array();
269 $prefix = (isset($matches[2]) ? $matches[2] : '');
270 if ( isset($nslist[$v]) && $nslist[$v] == $prefix ) continue; // No need to include in list as it's in a wrapping element
271 $nslist[$v] = $prefix;
272 if ( !isset($this->xmlns) ) $this->xmlns = $v;
273 }
274 $attr .= sprintf( ' %s="%s"', $k, htmlspecialchars($v) );
275 }
276 }
277 if ( isset($this->xmlns) && isset($nslist[$this->xmlns]) && $nslist[$this->xmlns] != '' ) {
278 $tagname = $nslist[$this->xmlns] . ':' . $tagname;
279 }
280
281 $r .= substr(" ",0,$indent) . '<' . $tagname . $attr;
282
283 if ( (is_array($this->content) && count($this->content) > 0) || (!is_array($this->content) && strlen($this->content) > 0) ) {
284 $r .= ">";
285 $r .= $this->RenderContent($indent,$nslist);
286 $r .= '</' . $tagname.">\n";
287 }
288 else {
289 $r .= "/>\n";
290 }
291 return $r;
292 }
293 }
294
295
296 /**
297 * Rebuild an XML tree in our own style from the parsed XML tags using
298 * a tail-recursive approach.
299 *
300 * @param array $xmltags An array of XML tags we get from using the PHP XML parser
301 * @param intref &$start_from A pointer to our current integer offset into $xmltags
302 * @return mixed Either a single XMLElement, or an array of XMLElement objects.
303 */
304 function BuildXMLTree( $xmltags, &$start_from ) {
305 $content = array();
306
307 if ( !isset($start_from) ) $start_from = 0;
308
309 for( $i=0; $i < 50000 && isset($xmltags[$start_from]); $i++) {
310 $tagdata = $xmltags[$start_from++];
311 if ( !isset($tagdata) || !isset($tagdata['tag']) || !isset($tagdata['type']) ) break;
312 if ( $tagdata['type'] == "close" ) break;
313 $attributes = ( isset($tagdata['attributes']) ? $tagdata['attributes'] : false );
314 if ( $tagdata['type'] == "open" ) {
315 $subtree = BuildXMLTree( $xmltags, $start_from );
316 $content[] = new XMLElement($tagdata['tag'], $subtree, $attributes );
317 }
318 else if ( $tagdata['type'] == "complete" ) {
319 $value = ( isset($tagdata['value']) ? $tagdata['value'] : false );
320 $content[] = new XMLElement($tagdata['tag'], $value, $attributes );
321 }
322 }
323
324 /**
325 * If there is only one element, return it directly, otherwise return the
326 * array of them
327 */
328 if ( count($content) == 1 ) {
329 return $content[0];
330 }
331 return $content;
332 }
333
This page took 0.09399 seconds and 6 git commands to generate.