Overview

Packages

  • awl
    • AuthPlugin
    • AwlDatabase
    • Browser
    • classEditor
    • DataEntry
    • DataUpdate
    • EMail
    • iCalendar
    • MenuSet
    • PgQuery
    • Session
    • Translation
    • User
    • Utilities
    • Validation
    • vCalendar
    • vComponent
    • XMLDocument
    • XMLElement
  • None
  • PHP

Classes

  • XMLElement

Functions

  • BuildXMLTree
  • Overview
  • Package
  • Class
  • Tree
  • Deprecated
  • Todo
  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://www.gnu.org/licenses/lgpl-3.0.txt  GNU LGPL version 3 or later
 10: */
 11: 
 12: require_once('AWLUtilities.php');
 13: 
 14: /**
 15: * A class for XML elements which may have attributes, or contain
 16: * other XML sub-elements
 17: *
 18: * @package   awl
 19: */
 20: class XMLElement {
 21:   protected $tagname;
 22:   protected $xmlns;
 23:   protected $attributes;
 24:   protected $content;
 25:   protected $_parent;
 26: 
 27:   /**
 28:   * Constructor - nothing fancy as yet.
 29:   *
 30:   * @param string $tagname The tag name of the new element
 31:   * @param mixed $content Either a string of content, or an array of sub-elements
 32:   * @param array $attributes An array of attribute name/value pairs
 33:   * @param string $xmlns An XML namespace specifier
 34:   */
 35:   function __construct( $tagname, $content=false, $attributes=false, $xmlns=null ) {
 36:     $this->tagname=$tagname;
 37:     if ( gettype($content) == "object" ) {
 38:       // Subtree to be parented here
 39:       $this->content = array(&$content);
 40:     }
 41:     else {
 42:       // Array or text
 43:       $this->content = $content;
 44:     }
 45:     $this->attributes = $attributes;
 46:     if ( isset($xmlns) ) {
 47:       $this->xmlns = $xmlns;
 48:     }
 49:     else {
 50:       if ( preg_match( '{^(.*):([^:]*)$}', $tagname, $matches) ) {
 51:         $prefix = $matches[1];
 52:         $tag = $matches[2];
 53:         if ( isset($this->attributes['xmlns:'.$prefix]) ) {
 54:           $this->xmlns = $this->attributes['xmlns:'.$prefix];
 55:         }
 56:       }
 57:       else if ( isset($this->attributes['xmlns']) ) {
 58:         $this->xmlns = $this->attributes['xmlns'];
 59:       }
 60:     }
 61:   }
 62: 
 63: 
 64:   /**
 65:   * Count the number of elements
 66:   * @return int The number of elements
 67:   */
 68:   function CountElements( ) {
 69:     if ( $this->content === false ) return 0;
 70:     if ( is_array($this->content) ) return count($this->content);
 71:     if ( $this->content == '' ) return 0;
 72:     return 1;
 73:   }
 74: 
 75:   /**
 76:   * Set an element attribute to a value
 77:   *
 78:   * @param string The attribute name
 79:   * @param string The attribute value
 80:   */
 81:   function SetAttribute($k,$v) {
 82:     if ( gettype($this->attributes) != "array" ) $this->attributes = array();
 83:     $this->attributes[$k] = $v;
 84:     if ( strtolower($k) == 'xmlns' ) {
 85:       $this->xmlns = $v;
 86:     }
 87:   }
 88: 
 89:   /**
 90:   * Set the whole content to a value
 91:   *
 92:   * @param mixed The element content, which may be text, or an array of sub-elements
 93:   */
 94:   function SetContent($v) {
 95:     $this->content = $v;
 96:   }
 97: 
 98:   /**
 99:   * Accessor for the tag name
100:   *
101:   * @return string The tag name of the element
102:   */
103:   function GetTag() {
104:     return $this->tagname;
105:   }
106: 
107:   /**
108:   * Accessor for the full-namespaced tag name
109:   *
110:   * @return string The tag name of the element, prefixed by the namespace
111:   */
112:   function GetNSTag() {
113:     return (empty($this->xmlns) ? '' : $this->xmlns . ':') . $this->tagname;
114:   }
115: 
116:   /**
117:   * Accessor for a single attribute
118:   * @param string $attr The name of the attribute.
119:   * @return string The value of that attribute of the element
120:   */
121:   function GetAttribute( $attr ) {
122:     if ( $attr == 'xmlns' ) return $this->xmlns;
123:     if ( isset($this->attributes[$attr]) ) return $this->attributes[$attr];
124:     return null;
125:   }
126: 
127:   /**
128:   * Accessor for the attributes
129:   *
130:   * @return array The attributes of this element
131:   */
132:   function GetAttributes() {
133:     return $this->attributes;
134:   }
135: 
136:   /**
137:   * Accessor for the content
138:   *
139:   * @return array The content of this element
140:   */
141:   function GetContent() {
142:     return $this->content;
143:   }
144: 
145:   /**
146:   * Return an array of elements matching the specified tag, or all elements if no tag is supplied.
147:   * Unlike GetContent() this will always return an array.
148:   *
149:   * @return array The XMLElements within the tree which match this tag
150:   */
151:   function GetElements( $tag=null, $recursive=false ) {
152:     $elements = array();
153:     if ( gettype($this->content) == "array" ) {
154:       foreach( $this->content AS $k => $v ) {
155:         if ( empty($tag) || $v->GetNSTag() == $tag ) {
156:           $elements[] = $v;
157:         }
158:         if ( $recursive ) {
159:           $elements = $elements + $v->GetElements($tag,true);
160:         }
161:       }
162:     }
163:     else if ( empty($tag) || (isset($v->content->tagname) && $v->content->GetNSTag() == $tag) ) {
164:       $elements[] = $this->content;
165:     }
166:     return $elements;
167:   }
168: 
169: 
170:   /**
171:   * Return an array of elements matching the specified path
172:   *
173:   * @return array The XMLElements within the tree which match this tag
174:   */
175:   function GetPath( $path ) {
176:     $elements = array();
177:     // printf( "Querying within '%s' for path '%s'\n", $this->tagname, $path );
178:     if ( !preg_match( '#(/)?([^/]+)(/?.*)$#', $path, $matches ) ) return $elements;
179:     // printf( "Matches: %s -- %s -- %s\n", $matches[1], $matches[2], $matches[3] );
180:     if ( $matches[2] == '*' || $matches[2] == $this->GetNSTag()) {
181:       if ( $matches[3] == '' ) {
182:         /**
183:         * That is the full path
184:         */
185:         $elements[] = $this;
186:       }
187:       else if ( gettype($this->content) == "array" ) {
188:         /**
189:         * There is more to the path, so we recurse into that sub-part
190:         */
191:         foreach( $this->content AS $k => $v ) {
192:           $elements = array_merge( $elements, $v->GetPath($matches[3]) );
193:         }
194:       }
195:     }
196: 
197:     if ( $matches[1] != '/' && gettype($this->content) == "array" ) {
198:       /**
199:       * If our input $path was not rooted, we recurse further
200:       */
201:       foreach( $this->content AS $k => $v ) {
202:         $elements = array_merge( $elements, $v->GetPath($path) );
203:       }
204:     }
205:     // printf( "Found %d within '%s' for path '%s'\n", count($elements), $this->tagname, $path );
206:     return $elements;
207:   }
208: 
209: 
210:   /**
211:   * Add a sub-element
212:   *
213:   * @param object An XMLElement to be appended to the array of sub-elements
214:   */
215:   function AddSubTag(&$v) {
216:     if ( gettype($this->content) != "array" ) $this->content = array();
217:     $this->content[] =& $v;
218:     return count($this->content);
219:   }
220: 
221:   /**
222:   * Add a new sub-element
223:   *
224:   * @param string The tag name of the new element
225:   * @param mixed Either a string of content, or an array of sub-elements
226:   * @param array An array of attribute name/value pairs
227:   *
228:   * @return objectref A reference to the new XMLElement
229:   */
230:   function &NewElement( $tagname, $content=false, $attributes=false, $xmlns=null ) {
231:     if ( gettype($this->content) != "array" ) $this->content = array();
232:     $element = new XMLElement($tagname,$content,$attributes,$xmlns);
233:     $this->content[] =& $element;
234:     return $element;
235:   }
236: 
237: 
238:   /**
239:   * Render just the internal content
240:   *
241:   * @return string The content of this element, as a string without this element wrapping it.
242:   */
243:   function RenderContent($indent=0, $nslist=null, $force_xmlns=false ) {
244:     $r = "";
245:     if ( is_array($this->content) ) {
246:       /**
247:       * Render the sub-elements with a deeper indent level
248:       */
249:       $r .= "\n";
250:       foreach( $this->content AS $k => $v ) {
251:         if ( is_object($v) ) {
252:           $r .= $v->Render($indent+1, "", $nslist, $force_xmlns);
253:         }
254:       }
255:       $r .= substr("                        ",0,$indent);
256:     }
257:     else {
258:       /**
259:       * Render the content, with special characters escaped
260:       *
261:       */
262:       if(strpos($this->content, '<![CDATA[')===0 && strrpos($this->content, ']]>')===strlen($this->content)-3)
263:         $r .= '<![CDATA[' . str_replace(']]>', ']]]]><![CDATA[>', substr($this->content, 9, -3)) . ']]>';
264:       else if ( defined('ENT_XML1') && defined('ENT_DISALLOWED') )
265:         // Newer PHP versions allow specifying ENT_XML1, but default to ENT_HTML401.  Go figure.  #PHPWTF
266:         $r .= htmlspecialchars($this->content, ENT_NOQUOTES |  ENT_XML1 | ENT_DISALLOWED );
267:       // Need to work out exactly how to do this in PHP.
268:       // else if ( preg_match('{^[\t\n\r\x0020-\xD7FF\xE000-\xFFFD\x10000-\x10FFFF]+$}u', utf8ToUnicode($this->content)) )
269:       //   $r .= '<![CDATA[' . $this->content . ']]>';
270:       else
271:         // Older PHP versions default to ENT_XML1.
272:         $r .= htmlspecialchars($this->content, ENT_NOQUOTES );
273:     }
274:     return $r;
275:   }
276: 
277: 
278:   /**
279:   * Render the document tree into (nicely formatted) XML
280:   *
281:   * @param int The indenting level for the pretty formatting of the element
282:   */
283:   function Render($indent=0, $xmldef="", $nslist=null, $force_xmlns=false) {
284:     $r = ( $xmldef == "" ? "" : $xmldef."\n");
285: 
286:     $attr = "";
287:     $tagname = $this->tagname;
288:     $xmlns_done = false;
289:     if ( gettype($this->attributes) == "array" ) {
290:       /**
291:       * Render the element attribute values
292:       */
293:       foreach( $this->attributes AS $k => $v ) {
294:         if ( preg_match('#^xmlns(:?(.+))?$#', $k, $matches ) ) {
295: //          if ( $force_xmlns ) printf( "1: %s: %s\n", $this->tagname, $this->xmlns );
296:           if ( !isset($nslist) ) $nslist = array();
297:           $prefix = (isset($matches[2]) ? $matches[2] : '');
298:           if ( isset($nslist[$v]) && $nslist[$v] == $prefix ) continue; // No need to include in list as it's in a wrapping element
299:           $nslist[$v] = $prefix;
300:           if ( !isset($this->xmlns) ) $this->xmlns = $v;
301:           $xmlns_done = true;
302:         }
303:         $attr .= sprintf( ' %s="%s"', $k, htmlspecialchars($v) );
304:       }
305:     }
306:     if ( isset($this->xmlns) && isset($nslist[$this->xmlns]) && $nslist[$this->xmlns] != '' ) {
307: //      if ( $force_xmlns ) printf( "2: %s: %s\n", $this->tagname, $this->xmlns );
308:       $tagname = $nslist[$this->xmlns] . ':' . $tagname;
309:       if ( $force_xmlns ) $attr .= sprintf( ' xmlns="%s"', $this->xmlns);
310:     }
311:     else if ( isset($this->xmlns) && !isset($nslist[$this->xmlns]) && gettype($this->attributes) == 'array' && !isset($this->attributes[$this->xmlns]) ) {
312: //      if ( $force_xmlns ) printf( "3: %s: %s\n", $this->tagname, $this->xmlns );
313:       $attr .= sprintf( ' xmlns="%s"', $this->xmlns);
314:     }
315:     else if ( $force_xmlns && isset($this->xmlns) && ! $xmlns_done ) {
316: //      printf( "4: %s: %s\n", $this->tagname, $this->xmlns );
317:       $attr .= sprintf( ' xmlns="%s"', $this->xmlns);
318:     }
319: 
320:     $r .= substr("                        ",0,$indent) . '<' . $tagname . $attr;
321: 
322:     if ( (is_array($this->content) && count($this->content) > 0) || (!is_array($this->content) && strlen($this->content) > 0) ) {
323:       $r .= ">";
324:       $r .= $this->RenderContent($indent,$nslist,$force_xmlns);
325:       $r .= '</' . $tagname.">\n";
326:     }
327:     else {
328:       $r .= "/>\n";
329:     }
330:     return $r;
331:   }
332: 
333: 
334:   function __tostring() {
335:     return $this->Render();
336:   }
337: }
338: 
339: 
340: /**
341: * Rebuild an XML tree in our own style from the parsed XML tags using
342: * a tail-recursive approach.
343: *
344: * @param array $xmltags An array of XML tags we get from using the PHP XML parser
345: * @param intref &$start_from A pointer to our current integer offset into $xmltags
346: * @return mixed Either a single XMLElement, or an array of XMLElement objects.
347: */
348: function BuildXMLTree( $xmltags, &$start_from ) {
349:   $content = array();
350: 
351:   if ( !isset($start_from) ) $start_from = 0;
352: 
353:   for( $i=0; $i < 50000 && isset($xmltags[$start_from]); $i++) {
354:     $tagdata = $xmltags[$start_from++];
355:     if ( !isset($tagdata) || !isset($tagdata['tag']) || !isset($tagdata['type']) ) break;
356:     if ( $tagdata['type'] == "close" ) break;
357:     $xmlns = null;
358:     $tag = $tagdata['tag'];
359:     if ( preg_match( '{^(.*):([^:]*)$}', $tag, $matches) ) {
360:       $xmlns = $matches[1];
361:       $tag = $matches[2];
362:     }
363:     $attributes = ( isset($tagdata['attributes']) ? $tagdata['attributes'] : false );
364:     if ( $tagdata['type'] == "open" ) {
365:       $subtree = BuildXMLTree( $xmltags, $start_from );
366:       $content[] = new XMLElement($tag, $subtree, $attributes, $xmlns );
367:     }
368:     else if ( $tagdata['type'] == "complete" ) {
369:       $value = ( isset($tagdata['value']) ? $tagdata['value'] : false );
370:       $content[] = new XMLElement($tag, $value, $attributes, $xmlns );
371:     }
372:   }
373: 
374:   /**
375:   * If there is only one element, return it directly, otherwise return the
376:   * array of them
377:   */
378:   if ( count($content) == 1 ) {
379:     return $content[0];
380:   }
381:   return $content;
382: }
383: 
384: 
AWL API documentation generated by ApiGen 2.8.0