<?php
// magpie-look-alike
// Martin Spernau 11-2002 (martin at traumwind.de)
// trying to mimic the PHP native XML functions (expat)
class MagpieRSS {
    
    
# current_item - item currently being parsed
    # items - collection of parsed items
    # channel - hash of channel fields
    #
    
var $current_item    = array();
        var 
$items            = array();
    var 
$channel        = array();
    
    var 
$parent_field    = array('RDF');
    var 
$current_field    = '';
    var 
$current_namespace    = false;
        
        
// list of common HTML tags that are to be considered 'content'
        
var $ignoreTags = '<\/?(br|p|a|img|b|hr|font|i|cite|table|tr|td|ul|ol|dl|dt|dd|li|blockquote|em|strong)(\s+|>|\/)';
    
    function 
MagpieRSS ($source) {
                
// the REX based XML tokenizer comes here:
                
                /*
                * The following code was adopted from
                * (the Javascript and PHP impl of regex being more compatible)
                ' ################
                * REX/Javascript 1.0 
                * Robert D. Cameron "REX: XML Shallow Parsing with Regular Expressions",
                * Technical Report TR 1998-17, School of Computing Science, Simon Fraser 
                * University, November, 1998.
                * Copyright (c) 1998, Robert D. Cameron. 
                * The following code may be freely used and distributed provided that
                * this copyright and citation notice remains intact and that modifications
                * or additions are clearly identified.
                ' ################
                * 'port' to PHP by Martin Spernau 2002
                */
                // define the regex-parts globaly, for later re-use
                
$TextSE = "[^<]+";
                
$UntilHyphen = "[^-]*-";
                
$Until2Hyphens = "$UntilHyphen([^-]$UntilHyphen)*-";
                
$CommentCE = "$Until2Hyphens>?";
                
$UntilRSBs = "[^]]*]([^]]+])*]+";
                
$CDATA_CE = "$UntilRSBs([^]>]$UntilRSBs)*>";
                
$S = "[ \\n\\t\\r]+";
                
$NameStrt = "[A-Za-z_:]|[^\\x00-\\x7F]";
                
$NameChar = "[A-Za-z0-9_:.-]|[^\\x00-\\x7F]";
                
$Name = "($NameStrt)($NameChar)*";
                
$QuoteSE = "\"[^\"]*\"|'[^']*'";
                
$DT_IdentSE = "$S$Name($S($Name|$QuoteSE))*";
                
$MarkupDeclCE = "([^]\"'><]+|$QuoteSE)*>";
                
$S1 = "[\\n\\r\\t ]";
                
$UntilQMs = "[^?]*\\?+";
                
$PI_Tail = "\\?>|$S1$UntilQMs([^>?]$UntilQMs)*>";
                
$DT_ItemSE = "<(!(--$Until2Hyphens>|[^-]$MarkupDeclCE)|\\?$Name($PI_Tail))|%$Name;|$S";
                
$DocTypeCE = "$DT_IdentSE($S)?(\\[($DT_ItemSE)*]($S)?)?>?";
                
$DeclCE = "--($CommentCE)?|\\[CDATA\\[($CDATA_CE)?|DOCTYPE($DocTypeCE)?";
                
$PI_CE = "$Name($PI_Tail)?";
                
$EndTagCE = "$Name($S)?>?";
                
$AttValSE = "\"[^<\"]*\"|'[^<']*'";
                
$ElemTagCE = "$Name($S$Name($S)?=($S)?($AttValSE))*($S)?\/?>?";
                
$MarkupSPE = "<(!($DeclCE)?|\\?($PI_CE)?|\/($EndTagCE)?|($ElemTagCE)?)";
                
                
$XML_SPE = "$TextSE|$MarkupSPE";
                
preg_match_all("/$XML_SPE/", $source, $tokenList);
                
// done REX tokenizer

                
                // define opening tag and empty tag regexes
                
$OpeningTagRegex = "/<($Name)($S$Name($S)?=($S)?($AttValSE))*($S)?>/"; 
                
$EmptyTagRegex = "/<($Name)($S$Name($S)?=($S)?($AttValSE))*($S)?\/>/"; 
                
$EndTagRegex = "/<\/($Name)($S)?>/";
                
// Text regex
                
$TextRegex = "/^$TextSE$/";
                
                
// the actual 'parser'
                
while (list($count,$token)=each($tokenList[0])) {
                        if (
preg_match('/'.$this->ignoreTags.'/', $token)) {
                                
// echo "ignoreTag ".htmlspecialchars($token)."<br>\n";
                                
$this->cdata(1, $token); // we handle common HTML tags as content
                        
} elseif (preg_match($OpeningTagRegex, $token, $matches)) {
                                
$this->start_element(1, $matches[1], $attr); 
                        } elseif (
preg_match($EndTagRegex, $token, $matches)) {
                                
$this->end_element(1, $matches[1]);
                        } elseif (
preg_match($TextRegex, $token)) {
                                
$this->cdata(1, $token);
                        }
                }
        
    }
    
        
// the actual Magpie code, unmodified can work with the above
    
function start_element ($p, $element, &$attrs) {
        
$element     = strtolower( $element );
        
# check for a namespace, and split if found
        #
        
$namespace    = false;
        if ( 
strpos( $element, ':' ) ) {
            list(
$namespace, $element) = split( ':', $element, 2); 
        }
        
$this->current_field = $element;
        if ( 
$namespace and $namespace != 'rdf' ) {
            
$this->current_namespace = $namespace;
        }
        
        if ( 
$element == 'channel' ) {
            
array_unshift( $this->parent_field, 'channel' );
        }
        elseif ( 
$element == 'items' ) {
            
array_unshift( $this->parent_field, 'items' );
        }
        elseif ( 
$element == 'item' ) {
            
array_unshift( $this->parent_field, 'item' );
        }
        
        
    }
    
    function 
end_element ($p, $element) {
        
$element = strtolower($element);
        if ( 
$element == 'item' ) {
            
$this->items[] = $this->current_item;
            
$this->current_item = array();
            
array_shift( $this->parent_field );
        }
        elseif ( 
$element == 'channel' or $element == 'items' ) {
            
array_shift( $this->parent_field );
        }
        
        
$this->current_field = '';
        
$this->current_namespace = false;
    }
    
    function 
cdata ($p, $text) {
        
# skip item, channel, items first time we see them
        #
        
if ( $this->parent_field[0] == $this->current_field or
             ! 
$this->current_field ) {
            return;
        }
        elseif ( 
$this->parent_field[0] == 'channel') {
            if ( 
$this->current_namespace ) {
                
$this->channel[ $this->current_namespace ][ $this->current_field ] .= $text;
            }
            else {
                
$this->channel[ $this->current_field ] .= $text;
            }
        
        }
        elseif ( 
$this->parent_field[0] == 'item' ) {
            if ( 
$this->current_namespace ) {
                
$this->current_item[ $this->current_namespace ][ $this->current_field ] .= $text;
            }
            else {
                
$this->current_item[ $this->current_field ] .= $text;
            }
        }
    }
        
    
# debugging functions
    #
    
function show_list () {
        echo 
"<ol>\n";
        foreach (
$this->items as $item) {
            echo 
"<li>", $this->show_item( $item );
        }
        echo 
"</ol>";
    }
    
    function 
show_channel () {
        echo 
"channel:<br>";
        echo 
"<ul>";
        while ( list(
$key, $value) = each( $this->channel ) ) {
            echo 
"<li> $key: $value";
        }
        echo 
"</ul>";
    }
    
    function 
show_item ($item) {
        echo 
"item: $item[title]";
        echo 
"<ul>";
        while ( list(
$key, $value) = each($item) ) {
            if ( 
is_array($value) ) {
                echo 
"<br><b>$key</b>";
                echo 
"<ul>";
                while ( list( 
$ns_key, $ns_value) = each( $value ) ) {
                    echo 
"<li>$ns_key: $ns_value";
                }
                echo 
"</ul>";
            }
            else {
                echo 
"<li> $key: $value";
            }
        }
        echo 
"</ul>";
    }
    
} 
# end class RSS
?>