[ create a new paste ] login | about

Link: http://codepad.org/7qrpNDSG    [ raw code | output | fork ]

k4st - PHP, pasted on May 23:
<?php

/**
 * Exception class for the XMLDocument class.
 */
class XMLDocumentException extends Exception { }

/**
 * A simple stack-based XML string constructor for creating XML.
 * @author Peter Goodman
 */
class XMLDocument {
    
    const TAG_CLOSING = 1,
          TAG_NON_CLOSING = 0;
    
    private $tags = array(),
            $indent = -1;
    
    /**
     * Construct the class and put in the base <xml> tag. This tag is non-
     * removable from the stack and isn't built in the usual way :) It's an
     * exception to the rule.
     */
    public function __construct() { 
        $this->push(' ');
        $xml =& $this->tags[$this->indent];
        
        $xml['children'] = -1;
        $xml['content'] = '<?xml version="1.0"?>';
        $this->indent = -1;
    }
    
    // destructor, obvious.
    public function __destruct() {
        unset($this->stack);
    }
    
    /**
     * Add some content to the current tag on the top of the stack.
     */
    public function hasContent($content = "") {
        
        $tag =& $this->tags[$this->indent+1];
        
        // wrap the content in a CDATA block if it contains tags
        if(preg_match("~[\<\>]~i", $content))
            $content = "<![CDATA[\n{$content}\n]]>";
        
        // what should the indent prefix be?
        $prefix = $this->indent > 0 ? str_repeat("\t", $this->indent) : '';
        
        // prefix the content
        $c = "";
        foreach(explode("\n", $content) as $line) {
            if(strlen($line) > 0) {
                $tag['content_num_lines']++;
                $c .= $prefix . $line;
            }
        }
        
        // only one line, lets remove the prefix
        if($tag['content_num_lines'] == 1)
            $c = substr($c, strlen($prefix));
        
        $tag['content'] .= $c;
        
        return $this;
    }
    
    /**
     * Push a tag onto the XML tag stack.
     */
    public function push($name = '', $type = TAG_CLOSING) {
        
        // we can't push no tag onto the tag stack
        if(empty($name) || !is_string($name))
            throw new XMLDocumentException("Tag name expected in push operation.");
        
        $this->indent++;
        $this->tags[] = array('name' => $name,
                              'closing' => (bool)$type,
                              'attr' => '',
                              'content' => '',
                              'children' => 0,
                              'content_num_lines' => 0);
        
        // get the parent tag and tell it that it has one more child. The
        // children is also incremented in the pop method; however, these
        // don't conflict because the lowest level child will have 0 children
        // and this will have already been called. Thus, 1 + 0 accurately
        // represents the number of children the node at the top of the stack
        // has when a leaf node is popped, and so it all bubbles up nicely.
        $parent =& $this->tags[$this->indent];
        $parent['children']++;
        
        return $this;
    }
    
    /**
     * Pop the current tag off of the tag stack.
     */
    public function pop($expect = NULL) {
        
        // we've closed all tags, this is a guard against popping the xml
        // tag off the stack
        if($this->indent < 0)
            throw new XMLDocumentException("No tags left to close.");
        
        $tag =& array_pop($this->tags);
        
        // did we expect to close a certain tag name and it's not the same one
        // that the class expects?
        if($expect !== NULL && $tag['name'] != $expect)
            throw new XMLDocumentException("Malformed XML. Expected [{$expect}] " 
                                          ."but found [{$tag['name']}].");
    
        // this builds the output from the inside out by putting the content
        // into the parent tag's content.
        $prefix = $this->indent > 0 ? str_repeat("\t", $this->indent) : '';
        $suffix = !$tag['closing'] ? ' /' : '';
        $content = "\n{$prefix}<". $tag['name'] . $tag['attr'] . $suffix .">";
        
        // do we need to build the contents of this tag?
        if($tag['closing']) {
            
            // write this tag over multiple lines
            if($tag['content_num_lines'] > 1 || $tag['children'] > 0)
                $content .= "{$prefix}". $tag['content'] ."\n{$prefix}";

            // write this tag over a single line
            else
                $content .= $tag['content'];
                
            $content .= '</'. $tag['name'] .">";
        }
        
        // add stuff into the parent and modify its content and children
        // counter
        $parent =& $this->tags[$this->indent];
        $parent['content'] .= $content;
        $parent['children'] += $tag['children'];
        
        $this->indent--;
        
        return $this;
    }
    
    /**
     * Append an attribute to the top tag in the stack. This is really just a
     * convenient shortcut.
     */
    public function __call($attr, array $args) {
    
        if(empty($attr))
            throw new XMLDocumentException("XML tag attribute must has a name.");
    
        $tag =& $this->tags[$this->indent+1];
        $tag['attr'] .= " {$attr}=\"". preg_replace('~"~', '\"', (string)$args[0]) ."\"";
        return $this;
    }

    /**
     * Secondary function for attributes if an attribute, for example, has a 
     * colon in its name or has the name push, pop, or one of the other names
     * of one of this class' functions.
     */
    public function hasAttribute($name = '', $val = '') {
        return $this->__call((string)$name, array((string)$val));
    }
    
    /**
     * Do the header call.
     */
    public function doHeader() {
        header("Content-Type: text/xml");
    }
    
    /**
     * Return the built XML string.
     */
    public function __toString() {
        
        // whoops, we're not done building the xml document yet!
        if($this->indent > 0)
            throw new XMLDocumentException("Cannot export malformed XML document.");
        
        return (string)$this->tags[0]['content'];
    }
}

$xml = new XMLDocument;
$xml->push('rss')->version("2.0")->encoding("UTF-8")
    ->push('channel')
    ->push('title')->hasContent("Feed title here")->pop()
    ->push('pubDate')->hasContent(date("D, d M Y H:i:s O"))->pop()
    ->push('link')->hasContent("http://ioreader.com")->pop()
    ->push('language')->hasContent("en-us")->pop();

// create the xml for each item
$xml->push('item')
    ->push('title')->hasContent("hello world")->pop()
    ->push('link')->hasContent("http://ioreader.com")->pop()
    ->push('description')->hasContent("this is a description")->pop()
    ->push('pubDate')->hasContent(date("D, d M Y H:i:s O"))->pop()
    ->push('guid')->hasContent(12345)->isPermaLink("false")->pop()
    ->pop('item');

// close remaining tags and output the xml
$xml->pop('channel')->pop('rss');
echo $xml;


Output:

Warning: preg_match(): Internal pcre_fullinfo() error -3 on line 47

Warning: preg_match(): Internal pcre_fullinfo() error -3 on line 47

Warning: preg_match(): Internal pcre_fullinfo() error -3 on line 47

Warning: preg_match(): Internal pcre_fullinfo() error -3 on line 47

Warning: preg_match(): Internal pcre_fullinfo() error -3 on line 47

Warning: preg_match(): Internal pcre_fullinfo() error -3 on line 47

Warning: preg_match(): Internal pcre_fullinfo() error -3 on line 47

Warning: preg_match(): Internal pcre_fullinfo() error -3 on line 47

Warning: preg_match(): Internal pcre_fullinfo() error -3 on line 47
<?xml version="1.0"?>
<rss version="2.0" encoding="UTF-8">
	<channel>	
		<title>Feed title here</title>
		<pubDate>Fri, 23 May 2008 23:04:09 +0000</pubDate>
		<link>http://ioreader.com</link>
		<language>en-us</language>
		<item>		
			<title>hello world</title>
			<link>http://ioreader.com</link>
			<description>this is a description</description>
			<pubDate>Fri, 23 May 2008 23:04:09 +0000</pubDate>
			<guid isPermaLink="false">12345</guid>
		</item>
	</channel>
</rss>


Create a new paste based on this one


Comments: