10.3. XML processing with PHP

PHP [76] is a popular server-side script language embedded in HTML that provides excellent string processing capabilities coupled with a good integration with a relational data-base most often MySQL, but other databases can also be accomodated. It features also many libraries for reading, creating and transforming XML data [77]. In this section, we will show how to use PHP for DOM, SAX and StAX parsing. We will use the same algorithms and program organization that we used in Java (Chapter 8 and Chapter 9). We will also describe how to transform XML data using XSL stylesheets and briefly show how to use the SimpleXML library that is useful for some simple cases.

10.3.1. DOM parsing using PHP

To show how to process an existing XML structure, we will use the compacting process that we programmed in Java in Section 8.1. In PHP, DOM parsing is achieved simply by creating an instance of the Dom class. This instance is a node and its node type is given by the value of its property nodeType: XML_ELEMENT_NODE or XML_TEXT_NODE. Information about a node is available through properties nodeName, attributes (an array whose keys are attribute names), childNodes (an array that can be iterated upon with foreach) or textContent for text nodes.

Example 10.15. [DOMCompact.php] Text compaction of the cellar book (Example 2.2) with PHP using the DOM model

Compare this program with Example 8.1.

  1 <?php
    function compact_($node,$indent){                                    (1)
        if ($node->nodeType==XML_ELEMENT_NODE) {                         (2)
            print $node->nodeName."[";
  5         $indent.=str_repeat(' ',strlen($node->nodeName)+1);
            $first=true;
            foreach ($node->attributes as $attrName => $attrValue) {     (3)
                if(!$first)print "\n$indent";
                print "@".$attrName."[".$attrValue->value."]";
 10             $first=false;
            }
            foreach ($node->childNodes as $child)                        (4)
                if($child->nodeType==XML_ELEMENT_NODE or 
                   strlen($child->textContent)>0 ){
 15                if(!$first)print "\n$indent";
                   compact_($child,$indent);
                   $first=false;                                         (5)
               }
            print "]";
 20     } else if ($node->nodeType==XML_TEXT_NODE)                       (6)
            print preg_replace("/ *\n? +/"," ",trim($node->textContent));
    }
    
    $dom = new DomDocument();                                            (7)
 25 $dom->preserveWhiteSpace = false;
    $dom->substituteEntities = true;
    $dom->load("php://stdin");
    compact_($dom->documentElement,"");                                  (8)
    print "\n";
 30 ?>

1

Prints the content of the XML node. Each line prefixed with an indentation, a string composed of the appropriate number of spaces.

2

If the node is an element, prints the name of the node followed by an opening bracket and adds the appropriate number of spaces to the current indentation.

3

Deals with attributes which are contained in an array over which we iterate. For each attribute, prints a @, its key and the corresponding value within square brackets. A new line is started for each attribute except the first.

4

Processes all children with a for-each loop.

5

If it is an element or a non-empty text node, recursively calls compact possibly changing line if it not the first child.

6

Processes a text node by printing it and normalizing internal newlines.

7

Parses the file by creating a new XML document from the content of the standard input. Sets parameters to that white space only nodes are not returned and that entities are replaced by their values before the XML processing.

8

Call the compacting process on the document node with an empty indentation string.


Because PHP programs are meant to be embedded in HTML, we now show how the HTML compacting process can be done.

Example 10.16. [compactHTML.php] HTML compaction of the cellar book (Example 2.2) with PHP using the DOM model

Compare this program with Example 5.7.

  1 <?php
    function tag($tag,$body,$attrs=""){                                  (1)
       return "<$tag".($attrs?" ":"")."$attrs>$body</$tag>";
    }
  5 
    function compact_($node){                                            (2)
        if ($node->nodeType==XML_ELEMENT_NODE) {                         (3)
            $res="";
            $attrs="";
 10         foreach ($node->attributes as $key => $value)                (4)
                $attrs.=" $key='$value->value'";
            $res.=tag("b",$node->nodeName)." ".$attrs;
            $children = $node->childNodes;                               (5)
            if($children->length>1){                                     (6)
 15             $cs="";
                foreach($children as $child)
                    $cs.=compact_($child);
                $res.=tag("ul",$cs);
            } else 
 20             $res.=compact_($children->item(0));                      (7)
            return tag("li",$res);
        } else if ($node->nodeType==XML_TEXT_NODE){
            return preg_replace("/ *\n? +/"," ",trim($node->nodeValue)); (8)
        }
 25 }
    
    $dom = new DomDocument();                                            (9)
    $dom->preserveWhiteSpace = false;
    $dom->substituteEntities = true;
 30 $file = $_GET['file'];
    $dom->load($file?$file:"php://stdin");
    ?>
    <html xmlns="http://www.w3.org/1999/xhtml">
         <head>
 35          <title>HTML compaction of "<?php print $_GET['file']?>"</title>
         </head>
         <body>
             <ul>
                 <?php print compact_($dom->documentElement,"")?>        (10)
 40          </ul>
         </body>
    </html>

1

Defines a function that returns a string comprised of the string of the body of a tag (second parameter) wrapped by the start and end tag named with the first parameter. The third argument give a string of attributes that are added to the start tag.

2

Returns a string corresponding to the content of the XML node.

3

If the node is an element, the name of the node is and its attributes are embedded in a b tag. The content of the attributes and the children nodes is embedded in a li tag.

4

Deals with attributes which are contained in an array over which we iterate. For each attribute, prints its name and the corresponding value separated by an equal sign. These attributes are combined in a single string which is added after the name of the node wrapped with a b tag.

5

Gets the list of all children.

6

If there are more than one child, accumulates the content of the children and wrap it within an ul tag.

7

In the case of one child, returns its content.

8

Processes a text node by normalizing its content.

9

Parses the file by creating a new XML document from the content of a file named by the HTTP file parameter passed in the url. If this value is not set, the standard input content will be used. Sets parameters to that white space only nodes are not returned and that entities are replaced by their values before the XML processing.

10

Calls the compacting process on the document node within an HTML structure forming the shape of HTML page. compact_ returns a string corresponding to the content of the file which is printed within the HTML template.


10.3.2. SAX parsing using PHP

In order to process an XML structure with the SAX approach, we will use a similar compacting process to the one we programmed in Java in Section 8.2. SAX parsing is achieved by calling the xml_parser_create function. This call return a reference to a parser to which we can set some options and then give an object in which are defined call-back functions that will be called during the parsing process.

Example 10.17. [SAXCompact.php] Text compaction of the cellar book (Example 2.2) with PHP using the SAX model

Compare this program with Example 8.3.

  1 <?php
    require 'CompactHandler.php';                                        (1)
    $handler = new CompactHandler();
    
  5 $parser = xml_parser_create("UTF-8");                                (2)
    xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, "UTF-8"); (3)
    xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
    xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); 
    
 10 xml_set_object($parser,$handler);                                    (4)
    xml_set_element_handler($parser, "startElement", "endElement");
    xml_set_character_data_handler($parser,"characters");
    
    $fp = fopen("php://stdin","r");                                      (5)
 15 while ($data = fread($fp, 4096)) {
        if (!xml_parse($parser, $data, feof($fp))) {
            die(sprintf("XML error: %s at line %d",
                        xml_error_string(xml_get_error_code($parser)),
                        xml_get_current_line_number($parser)));
 20     }
    }
    print "\n";
    xml_parser_free($parser);
    ?>

1

Makes sure that the appropriate CompactHandler class is present.

2

Creates an instance of the SAX parser that will parse an UTF-8 input. Set some options, in particular, make sure that tag names are not returned in upper-case.

4

Indicates to which object the parsing events of the CompactHandler class (described in Example 10.18) will be sent. Then gives the name of functions of this object that will be called when a start tag, an end tag and a character node will be parsed.

5

Starts the parsing process on the standard input. In order to save space, the parsing is sent to the parser in chunks of 4K until the the third parameter of xml_parse is true.


SAX parsing events in PHP are sent to an event handler object in which are defined some methods to deal with the parsing events. Unfortunately, the PHP SAX parser does not collapse all contiguous character nodes in a single one, so the character content must be accumulated in the $chars variable which is then output before dealing with a start or end tag.

Example 10.18. [CompactHandler.php] PHP SAX handler for text compacting an XML file such as that of Example 2.2

Compare this program with Example 8.4.

  1 <?php
    class CompactHandler {
        var $closed, $indent, $chars;                                    (1)
        
  5     function CompactHandler(){                                       (2)
            $this->closed=false;
            $this->indent=0;
            $this->chars="";
        }
 10  
        function startElement($parser, $localname, array $attributes){   (3)
            $this->flushChars();
            if ($this->closed) {
                print "\n".str_repeat(' ',$this->indent);
 15             $this->closed=false;
            }
            $this->indent+=1+strlen($localname);                         (4)
            print $localname."[";
            $first=true;
 20         foreach ($attributes as $attrName => $attrValue){            (5)
                if(!$first)print "\n".str_repeat(' ',$this->indent);
                print "@".$attrName."[".$attrValue."]";
                $first=false;
                $this->closed=true;
 25         }
        }
    
        function endElement($parser, $localname){                        (6)
            $this->flushChars();
 30         print "]";
            $this->closed=true;
            $this->indent-=1+strlen($localname);
        }
    
 35     function characters($parser, $text){                             (7)
            if(strlen(trim($text))>0){
                if ($this->closed) {
                    print "\n".str_repeat(' ',$this->indent);
                    $this->closed=false;
 40             }
                $this->chars=$this->chars.trim($text);
            }
        }
    
 45     function flushChars(){                                           (8)
            if (strlen($this->chars)>0) {
                if ($this->closed) {
                    print "\n".str_repeat(' ',$this->indent);
                    $this->closed=false;
 50             }
                print preg_replace("/ *\n? +/"," ",$this->chars);
                $this->closed=true;
                $this->chars="";
            } 
 55     }
    }
    ?>

1

Declares variables to save the state of the parsing processing between calls to parsing call-back methods.

2

Initializes two instance variables needed to output the element with the correct indentation and one to accumulate the character content of successive character nodes.

3

When a new element is started, outputs the content of last character nodes and finishes the current indentation if needed.

4

Updates the current indentation by adding the length of the element name.

5

Prints the first attribute on the same line and the others on the subsequent lines properly indented.

6

When an element finishes, outputs the contents of the last character nodes and closes the current bracket and updates the current indentation.

7

For a non-empty text node, ends current line if needed and adds its contents to the $chars string.

8

Ends the current indentation and output the normalized content of the $chars variables and resets it to an empty string.


10.3.3. StAX parsing using PHP

Compare this program with Example 8.5.

Pull parsing in PHP is done using an instance of the XMLReader class which defines a method to move the cursor forward in the XML file (read()) and attributes to test the content of the current token (nodetype, hasAttributes and name). Contrarily to the standards, the PHP pull parser does not return a start and end tag for an empty element, but it instead only a single element that can be checked with the isEmptyElement method.

Example 10.19. [StAXCompact.php] Text compaction of the cellar book (Example 2.2) with PHP using the StAX model

  1 <?php
    function compact_($xmlsr,$indent){                                   (1)
        if($xmlsr->nodeType==XMLReader::ELEMENT){                        (2)
            print $xmlsr->name."[";
  5         $indent.=str_repeat(' ',strlen($xmlsr->name)+1);
            $first=true;
            if($xmlsr->hasAttributes){                                   (3)
                while($xmlsr->moveToNextAttribute()){
                    if(!$first)print "\n$indent";
 10                 print "@".$xmlsr->name."[".$xmlsr->value."]";
                    $first=false;
                }
            } // Warning: PHP does not return an END_ELEMENT on a empty tag
            if(!$xmlsr->isEmptyElement)                                  (4)
 15             while(true){
                    do {$xmlsr->read();} 
                    while ($xmlsr->nodeType==XMLReader::SIGNIFICANT_WHITESPACE);
                    if($xmlsr->nodeType==XMLReader::END_ELEMENT)break;
                    if($first)$first=false;
 20                 else print "\n$indent";
                    compact_($xmlsr,$indent);                            (5)
                }
            print "]";
        } else if ($xmlsr->nodeType==XMLReader::TEXT)
 25         print preg_replace("/ *\n? +/"," ",trim($xmlsr->value));     (6)
        else {
            die("STRANGE NODE:".$xmlsr->nodeType."\n");
        }
    }
 30 
    $xmlsr = new XMLReader();                                            (7)
    $xmlsr->open("php://stdin");
    $xmlsr->setParserProperty(XMLReader::SUBST_ENTITIES,true);
    while ($xmlsr->nodeType!=XMLReader::ELEMENT)$xmlsr->read();          (8)
 35 compact_($xmlsr,"");                                                 (9)
    print "\n";
    ?>

1

Method to compact from the current token. As there is already a compact method predefined in PHP, we changed the name of our method by adding a trailing underscore.

2

If it is a start element tag, outputs the name of the element followed by an opening bracket and update the current indentation.

3

Outputs each attribute name and value all indented except the first one. Attributes are obtained by iteration using the the moveToNextAttribute method.

4

Loops on children nodes that are not whitespace and compacts each of them with the correct indentation. Some care must be taken not to call this process on an empty tag (i.e. without any children node) because the PHP parser does not return the corresponding end tag.

5

Recursive call to the compacting process.

6

Prints the normalized character content.

7

Creates a new stream parser, indicates that it will parse the standard input and that entities must be substituted by the parse.

8

Ignores the tokens that come before the first element (e.g. processing instructions).

9

Calls the compacting process with the current token and then prints a newline to flush the content of the last line.


10.3.4. Creating an XML document using PHP

In order to demonstrate the creation of new XML document, we will parse the compact form produced in Section 10.3.1 or in Section 10.3.2 like we did in Chapter 9. We first need a way to access appropriate tokens corresponding to important signals in the input file. This is achieved by defining a CompactTokenizer class (Example 10.20) that will return opening and closing square brackets, at-signs and the rest as a single string. Newlines will also delimit tokens but will be ignored in the document processing. The file is processed by a series of calls to nextToken or skip. skip provides a useful redundancy check for tokens that are encountered in the input but are ignored for output.

Example 10.20. [CompactTokenizer.php] Specialized string scanner that returns tokens of compact form.

Compare this program with Example 9.1.

  1 <?php
    class CompactTokenizer {                                             (1)
        var $index, $tokens;
        
  5     function CompactTokenizer($file){                                (2)
            global $index,$tokens;
            $tokens = preg_split("/\n|(\[|\]|@)/",
                                 file_get_contents($file),
                                 -1,PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
 10         $index = 0;
         }
        
        function getToken(){                                             (3)
            global $tokens,$index;
 15         $token = $index<count($tokens)?$tokens[$index]:false;
            return $token;
        }
        
        function nextToken(){                                            (4)
 20         global $tokens,$index;
            do {
                $index=$index+1;
                $token = $this->getToken();
                if($token==false)break;
 25             $token=trim($token);
            } while (strlen($token)==0);
            return $token;
        }
        
 30     function skip($sym){                                             (5)
            if($this->getToken()==$sym)
                return $this->nextToken();
            die("skip:$sym expected but ".$this->getToken()." found. index=$index");
        }
 35 }
    ?>

1

Defines the CompactTokenizer class with two instance variables $tokens an array to keep all tokens and $index to indicate the current token.

2

Initializes the $tokens array in which the string of the whole file is split according to a regular expression; the last parameter sets flags so that no empty string tokens are of the preg_split method is a combination of flags to indicate that the delimiters are also returned as tokens and that no empty string are returned as tokens. The index of the current token is set to the first element of the array

3

Check that the index of the current token is within the bounds of the array and set it to false otherwise. Return it.

4

Gets the next token but skip those containing only whitespace. It returns the value of the current token.

5

Checks that the current token is the same as the one given as parameter and retrieves the next one. If the current token does not match, then stop the program by indicating the expected token and the one found with the content of the current line.


In PHP, the node creation and child addition is using DOM methods createElement(), appendChild() and createTextNode(). The attributes of a node are added using the setAttribute() method.

Example 10.21. [DOMExpand.php] PHP compact form parsing to create an XML document

A sample input for this program is Example 5.8 , which should yield Example 2.2. Compare this program with Example 9.2.

  1 <?php 
    require 'CompactTokenizer.php';                                      (1)
    
    function expand($elem){                                              (2)
  5     global $st,$dom;
        $st->skip('[');
        while($st->getToken()=='@'){   // process attributes             (3)
            $attName=$st->skip("@");
            $st->nextToken();
 10         $elem->setAttribute("$attName",$st->skip("["));              (4)
            $st->nextToken();
            $st->skip("]");
        }
        while ($st->getToken()!=false && $st->getToken()!="]") {         (5)
 15         $s = trim($st->getToken()); // process children
            $st->nextToken();
            if($st->getToken()=="[")
                expand($elem->appendChild($dom->createElement($s)));     (6)
            else
 20             $elem->appendChild($dom->createTextNode($s));            (7)
        }
        $st->skip("]");
    }
    
 25 $st = new CompactTokenizer('php://stdin');                           (8)
    $dom = new DOMDocument();                                            (9)
    $rootname = $st->getToken();                                         (10)
    while($st->nextToken()!='[')
        $rootname=$st->getToken();
 30 
    expand($dom->appendChild($dom->createElement($rootname)));           (11)
    
    $dom->formatOutput=true;                                             (12)
    print $dom->saveXML();
 35 ?>

1

Ensures that the CompactTokenizer (Example 10.20) is loaded.

2

Adds the content of the file corresponding to the children of the current node to the $elem element.

3

Because all attribute names start with an @, loops while the current token is equal to @. The name of the attribute is saved and the following opening square bracket is skipped, the value is kept and the ] is skipped.

4

Adding an attribute is done by calling the setAttribute method of the current element.

5

All children are then processed in turn until a closing square bracket is encountered. s is the current token, which is either a child element name (if followed by an opening square bracket) or the content of a text node.

6

A new element named s is created and its children are filled in by a recursive call to expand.

7

A text node is added as the last child of the current element.

8

Creates the tokenizer on the standard input.

9

Initializes a new XML document.

10

The name of the root is the first token immediately followed by an opening square bracket.

11

Creates the root node which is filled in by a call to expand.

12

Serializes the document on the standard output. Sets a flag so that the XML is properly indented. Serialization is done using the saveXML method of the document node.


10.3.5. Other means of dealing with XML documents using PHP

PHP also allows other ways of dealing with XML files, the simplest of which is to apply an XSLT stylesheet on a loaded DOM structure. Currently, only XSLT 1.0 stylesheets can be used without having to link with external Java classes using a PHP/Java bridge. The following example shows how to perform the transformation defined by the Example 5.9 stylesheet. As the PHP transformation is performed by xsltproc that does not deal very well with entities, for this case, we had to modify our original stylesheet Example 5.9 by replacing all entities references by their values. The resulting XSLT compact-php.xsl is not given here but it is available on the companion web site.

Example 10.22. [XSLcompact.php] PHP compaction of an XML file using an XSLT stylesheet.

Application of an XSLT stylesheet on an XML file. Here we use a modified version Example 5.9 to produce a compact version of an XML file.

  1 <?php
    $xml = new DOMDocument();                                            (1)
    $xml->load("php://stdin");
    
  5 $xsl = new DOMDocument();                                            (2)
    $xsl->load('compact-php.xsl');
    
    $proc = new XSLTProcessor();                                         (3)
    $proc->importStyleSheet($xsl);
 10 $proc -> setParameter(null, 'name','value'); // unused in this example
    
    print $proc->transformToXML($xml);                                   (4)
    ?>

1

Initializes the DOM structure for the instance file to transform and loads the file, here we deal with the standard input.

2

Initializes the DOM structure for the transformation stylesheet and loads it.

3

Configures the transformation processor with the stylesheet and sets some parameters (unused here)

4

Serializes the output of the transformer on the instance file.


For simple reading and manipulation of information of an XML file, one might consider the SimpleXML extension which, upon loading an XML file, translates its the tree structure into a PHP object, an instance of the SimpleXMLElement class, that can be processed using the usual property selectors and array iterators. As in PHP arrays are zero-based contrarily to XPath in which elements are numbered starting at 1, some care must be given when translating the expressions from one to the other. It is also possible to use XPath expressions to select elements in this structure. Methods are provided to get the name of an element, its attributes and its children nodes. Although it is possible to deal with namespaces, their use is error-prone. The type of object created by SimpleXML is particular, so its uses should be kept for simple operations and extraction of XML data. When an SimpleXMLElement instance is compared or combined with other variables such as integers or strings, it must be casted into the appropriate type before being used. Moreover, SimpleXML has no provision for mixed content, so for generality the DOM approach should be preferred, but it can be useful in some cases.

Example 10.23 shows the use of SimpleXMLExpressions to access some information in Example 2.2. The expressions correspond to the examples given in Example 4.1.

Example 10.23. [SimpleXMLPath.php] SimpleXML file loading followed by PHP SimpleXML expressions

SimpleXML file loading followed by PHP expressions corresponding to the XPath expressions of Example 4.1.

  1 <?php
    $cellarbook = simplexml_load_file("CellarBook.xml");                 (1)
    
    p("/cellar-book/owner",                                              (2)
  5   $cellarbook->owner);
    p("/cellar-book/cellar/wine[quantity<2]",
      $cellarbook->xpath("/cellar-book/cellar/wine[quantity<2]"));
    p("/cellar-book/cellar/wine[1]",
      $cellarbook->cellar->wine[0]);
 10 p("//postal-code/..",
      $cellarbook->xpath("//postal-code/..")); 
    p("/cellar-book/owner/street",
      $cellarbook->owner->street); 
    foreach ($cellarbook->cellar->wine as $w) {
 15     p("//wine/@code",$w["code"]);
    }
    foreach ($cellarbook->children("http://www.iro.umontreal.ca/lapalme/wine-catalog")->
                  children() as $w){p("//cat:wine/@code",$w["code"]); 
    }
 20 p("/cellar-book/cellar/wine[1]/comment",
      $cellarbook->cellar->wine[0]->comment);
    $sum=0;
    foreach($cellarbook->cellar->wine as $w){
         $sum+=(int)$w->quantity;
 25 }
    p("sum(/cellar-book/cellar/wine/quantity)",$sum);
    
    print("for \$w in //wine return 
        concat(\$w/quantity,':',//cat:wine/@code[.=\$w/@code]/../@name)
 30 ");
    foreach($cellarbook->cellar->wine as $w){
        foreach ($cellarbook->
                    children("http://www.iro.umontreal.ca/lapalme/wine-catalog")
                       ->children() as $catw){
 35         if((string)$w["code"] == (string)$catw["code"]) 
                print($w->quantity.":".$catw["name"]."\n");
        }
    }
    print("---------------\n");
 40 
    $cheapFrenchWines=array();
    foreach ($cellarbook->children("http://www.iro.umontreal.ca/lapalme/wine-catalog")
                  ->children() as $catw){
          if((string)$catw->origin->country=="France" && (int)$catw->price<20)
 45             array_push($cheapFrenchWines,$catw["name"]);
    }
    p("//cat:wine[cat:origin/cat:country='France' and cat:price&amp;lt;20]",
      $cheapFrenchWines);
    
 50 function p($xpath,$sxo){                                             (3)
        print("$xpath\n");
        var_dump($sxo);
        print("---------------\n");
    }
 55 
    ?>

1

Loads the file given as parameter and returns the root element.

2

List of example expressions. As comparison, the corresponding XPath is given as a string, followed by the SimpleXML expression which it printed within the p method defined below.

3

Prints the string with the XPath expression, the structure returned by the evaluation of the expression. var_dump is used because it indicates the precise type of the expression. A separator line is printed to delimit each example.


In order to illustrate the manipulation of SimpleXML structures, we give in Example 10.24 a version to produce a compact version of an XML file. We simply parse (load) the file and the SimpleXML structure is built in memory. It is then a simple matter of traversing this structure to produce a compact version of the file.As a SimpleXMLElement instance has only one slot for its character content, it cannot cope correctly with mixed content elements. Some information is lost in this process. For example, all the text nodes in the food-pairing element of the first wine in the catalog will be concatenated as a single text node. When the SimpleXML is printed in compact form, then first the text child is printed and then the element children afterwards.

Example 10.24. [SimpleXMLCompact.php] PHP compaction of an XML file using a SimpleXML.

Compaction of an XML file by first creating a SimpleXML object and the traversing it with standard PHP iterators to create a compact version of the file.

  1 <?php
    function compact_($sxelem,$indent){                                  (1)
        print $sxelem->getName()."[";                                    (2)
        $indent.=str_repeat(' ',strlen($sxelem->getName())+1);
  5     $first=true;
        foreach ($sxelem->attributes() as $attname => $attvalue) {       (3)
            if(!$first)print "\n$indent";
            print "@{$attname}[$attvalue]";
            $first=false;
 10     }
        $text = trim($sxelem);                                           (4)
        if(strlen($text)){
            print preg_replace("/ *\n? +/"," ",$text);
            $first=false;
 15     }
        $children = $sxelem->children();                                 (5)
        foreach ($children as $child){
           if(!$first)print "\n$indent";
           compact_($child,$indent);                                     (6)
 20        $first=false;
        }       
        print "]";
    }
      
 25 $dom = simplexml_load_file('php://stdin');                           (7)
    compact_($dom,"");                                                   (8)
    print "\n";
    ?>

1

Function to compact a simple xml object (first parameter) with each new line preceded by an indentation given by the second parameter. As compact is a predefined function in PHP for dealing with arrays, we rename the function to compact_.

2

Prints the name of the element followed by an open bracket and updates the indentation by adding spaces corresponding to the number of characters in the element name.

3

Prints the array of attributes on different lines except for the first. The name of the attribute is preceded by @ and the value is put within square brackets.

4

If there is text content, prints it. The text is normalized by replacing contiguous spaces and new lines by a single space. Spaces at the start and end are removed first. Beware that the content is the concatenation of all child text nodes, so this is problematic in the case of mixed content elements.

5

if the element has children, compacts them, a new line is output if it is not the first child.

6

Calls compact_ recursively.

7

Reads an XML instance file, here the standard input and keeps a reference on the root node.

8

Calls the compacting function on the root element with an empty indentation.


Example 10.25 follows the same structure as Example 10.21 to expand a compact form into a SimpleXMLElement structure. It uses the tokenizer of Example 10.20 to read the file. It recursively creates the XML structure as it processes the file. As the PHP library does not provide an indented form of the XML string, we provide one here as another illustration of SimpleXMLElement use.

Example 10.25. [SimpleXMLExpand.php] PHP compact form parsing to create a SimpleXMLElement document

A sample input for this program is Example 5.8 , which should yield a file equivalent to Example 2.2 except for the mixed content elements. Compare this program with Example 9.2.

  1 <?php 
    require 'CompactTokenizer.php';                                      (1)
    
    function expand($elem){                                              (2)
  5     global $st;
        $st->skip('[');
        while($st->getToken()=='@'){   // process attributes             (3)
            $attName=$st->skip("@");
            $st->nextToken();
 10         $elem->addAttribute("$attName",$st->skip("["));              (4)
            $st->nextToken();
            $st->skip("]");
        }
        while ($st->getToken()!=false && $st->getToken()!="]") {         (5)
 15         $s = trim($st->getToken()); // process children
            $st->nextToken();
            if($st->getToken()=="[") {
                expand($elem->addChild($s));                             (6)
             } else 
 20             $elem[0]=$s; // add a text node                          (7)
        }
        $st->skip("]");
    }
    
 25 $st = new CompactTokenizer('php://stdin');                           (8)
    $rootname = $st->getToken();                                         (9)
    while($st->nextToken()!='['){
        $rootname=$st->getToken();
    }
 30 
    $sxelem = new SimpleXMLElement("<$rootname/>");                      (10)
    expand($sxelem);                                                     (11)
    
    print ppXML($sxelem,"");                                             (12)
 35 
    function ppXML($sxelem,$indent){                                     (13)
        $res="";
        $name = $sxelem->getName();
        $res.="$indent<$name";
 40     if(count($sxelem->attributes()>0))                               (14)
            foreach ($sxelem->attributes() as $attname => $attvalue)
                $res.=" ".$attname.'="'.$attvalue.'"';
        $text = trim((string) $sxelem);
        if($sxelem->count()==0 && strlen($text) == 0)
 45         $res.="/>\n";                                                (15)
        else if($sxelem->count()==0) 
            $res.=">$text</$name>\n";                                    (16)
        else {
            $res.=">$text\n";                                            (17)
 50         foreach ($sxelem->children() as $child)
                $res.=ppXML($child,$indent."   ");
            $res.="$indent</$name>\n";
        }
        return $res;    
 55 }
    
    ?>

1

Ensures that the CompactTokenizer (Example 10.20) is loaded.

2

Adds the content of the file corresponding to the children of the current node to the $elem element.

3

Because all attribute names start with an @, we loop while the current token is equal to @. The name of the attribute is saved and the following opening square bracket is skipped, the value is kept and the ] is skipped.

4

Adding an attribute is done by calling the setAttribute method of the current element.

5

All children are then processed in turn until a closing square bracket is encountered. s is the current token, which is either a child element name (if followed by an opening square bracket) or the content of a text node.

6

A new element named s is created and its children are filled in by a recursive call to expand.

7

A text node is added as the child of the current element. As there is no explicit method to specify text content, we assign the text content to the first array position of the SimpleXMLElement. This has the unfortunate side-effect of removing the previous child nodes in the case of mixed content.

8

Creates the tokenizer on the standard input.

9

The name of the root is the first token immediately followed by an opening square bracket.

10

Creates the root node as a SimpleXMLElement.

11

The root node which is filled in by a call to expand.

12

Serializes the document on the standard output by calling the ppXML method defined below. It is similar to the asXML method of the SimpleXMLELement but it returns an indented string.

13

Recursive function to create and indented string for the current element. The $indent parameter is a string which is output before the start of each new line. The output string is built in the $res variable.

14

Outputs the name of element followed by its attributes on a single line.

15

If there is no text content and no children nodes, closes the current tag.

16

If there are not children node, outputs the text and closes the tag.

17

Close the current tag followed by possible text. Recursively process each children and concatenates their result in the current string output.