]>
Commit | Line | Data |
---|---|---|
a5eae6b7 MR |
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 |