<?php


# This script formats a xml file according to some rules. These rules 
# are processed in the function that xml_set_character_data_handler 
# defines. We have to remember, in which element we are, and furthermore, 
# which ancestor elements the current element has. 

/** gm 04/10/03 - Made changes:
    - renamed to ConfigParse
    - added ConfigParse::$error, and changed ConfigParse::ConfigParse() to use that instead of die()
    - No longer addeds node for ending element  ( </element> )
    - removed fancy printing crap
    - added method ConfigParse::getElement() and ConfigParse::getElements()
    - bugfix- added ConfigParse::$_lastEnd, to not add cdata right after a </element>
      this means that in the case of: ... </element1> CDATA </element2> the CDATA will
      be ignored.  Note: this could also be changed to add the CDATA to <element2>'s node
    - changed Node::add_data so it doesn't always add a space at the start
    - removed Node::$type variable
    - added ConfigParse::getElementData and ConfigParse:getElementAttrib
    
    - renamed back to XML()
    - pass xml data instead of filename
*/



class Node 
    var 
$name
    var 
$attributes
    var 
$ancestors "/";
    var 
$data

    function 
Node($tree) { 
        
$this->name array_pop($tree); 
        
$this->ancestors .= implode("/"$tree); 
    } 

    function 
add_data($value) { 
        
$this->data = (strlen($this->data) > $this->data.' '.$value $value); 
    } 

    function 
get_type() { 
        if (
strlen($this->data) > 0) { 
            return 
"with CDATA";
        } else { 
            return 
"without CDATA";
        } 
    } 

    function 
level() { 
        if (
$this->ancestors == "/") return 0
      if (
preg_match_all("/(\/{1})/"$this->ancestors$result,PREG_PATTERN_ORDER)) { 
        return (
count($result[0])); 
      } else { 
        return 
0;
        }
    }

    function 
has_attributes() {
        return (
is_array($this->attributes));
    }

    function 
print_name() {
        return 
"$this->name";
    }

    function 
is_child($node) {
        
$result preg_match("/^$ancestors/"$node->ancestors$match);
        if (
$node->ancestors == $this->ancestors$result false;
        return 
$result;
    }
}

class 
XML {
    var 
$tree = array();
    var 
$nodes = array();
    var 
$PIs;
    var 
$error;
    var 
$_lastEnd;

    function 
XML($data) {
    
$this->_error false;
        
$xml_parser xml_parser_create();
        
xml_set_object($xml_parser,$this);
        
xml_set_element_handler($xml_parser"startElement""endElement");
        
xml_set_character_data_handler($xml_parser"characterData");
        
xml_set_processing_instruction_handler ($xml_parser"process_instruction");
                
# Why should one want to use case-folding with XML? XML is case-sensitiv, I think this is nonsense
        
xml_parser_set_option($xml_parserXML_OPTION_CASE_FOLDINGfalse);

        if (!
xml_parse($xml_parser$datatrue)) {
             
$this->error sprintf("XML error: %s at line %d",
                                    
xml_error_string(xml_get_error_code($xml_parser)),
                                    
xml_get_current_line_number($xml_parser));
    }
    }

    function 
startElement($parser$name$attribs) { 
        
# Adding the additional element to the tree, including attributes 
        
$this->tree[] = $name

        
$node = new Node($this->tree); 
        while (list(
$k$v) = each($attribs)) { 
            
$node->attributes[$k] = $v
        } 
        
$this->nodes[] = $node;
    
$this->_lastEnd false
    } 

    function 
endElement($parser$name) { 
        
# Adding a new element, describing the end of the tag 
        # But only, if the Tag has CDATA in it! 
         
        # Check 
        
if (count($this->nodes) >= 1) { 
            
$prev_node $this->nodes[count($this->nodes)-1]; 
            if (
strlen($prev_node->data) > || $prev_node->name != $name) {
                
$this->tree[count($this->tree)-1] = "/".$this->tree[count($this->tree)-1];
         
        
// gm 04/10/03 - dont add element for end node
                //$this->nodes[] = new Node($this->tree, NULL); 
            
} else { 
                
# Adding a slash to the end of the prev_node 
                
$prev_node->name $prev_node->name."/";
                
$this->nodes[count($this->nodes)-1]->name $this->nodes[count($this->nodes)-1]->name."/" ;
            } 
        } 

        
# Removing the element from the tree 
        
array_pop($this->tree); 
    
$this->_lastEnd true
    } 

    function 
characterData($parser$data) { 
        
$data ltrim($data); 
        if ((
$data != "") && (!$this->_lastEnd)) {
        
$this->nodes[count($this->nodes)-1]->add_data($data);
    } 
    } 
     
    function 
process_instruction($parser$target$data) { 
        if (
preg_match("/xml:stylesheet/"$target$match) && preg_match("/type=\"text\/xsl\"/"$data$match)) { 
            
preg_match("/href=\"(.+)\"/i"$data$this->PIs); 
#            print "<b>found xls pi: $PIs[1]</b><br>\n" 
        

    } 
    
    
// gets the element specified by path (first match)
    // path is in the format "/Container/Of/Element"
    // if the element is single (ie, "<something />") then include a trailing / ("/Container/Of/Element/")
    
function &getElement($path) {
        
// see if this is a single element (ie, <something /> ), if so, remove
    // trailing / so as not to mess up explode() etc
        
$slash false;
        if (
$path[strlen($path)-1] == "/") {
        
$path substr($path0, -1);
        
$slash true;
    }
    
        
// change into "/Container/Of" and "Element"
        
$temp explode("/"$path);
    
$name array_pop($temp).($slash "/" ""); // add slash back, if it needs it
    
$path implode("/"$temp);
        
        foreach (
$this->nodes as $node) {
            if (
$node->ancestors == $path) {
        if (
$node->name == $name) {
                return 
$node;
        }
        }
    }
    return 
false;
    }
    
    
// get the CDATA for an element
    
function getElementData($path) {
    
$node $this->getElement($path);
    if (
$node) {
        return 
$node->data;
    }
    }
    
    
// get a specific attribute for an element
    // NOTE: if calling this multiple times for the same element, its better to use getElement()
    // and access the attributes directly (otherwise you're seaching through the nodes many times)
    
function getElementAttrib($path$attrib) {
    
$node $this->getElement($path);
    if (isset(
$node->attributes[$attrib])) {
        return 
$node->attributes[$attrib];
    }
    return 
false;
    }

    
// gets the elements specified by path (into an array)
    // path is in the format "/Container/Of/Element"
    
function getElements($path) {
        
$return = array();
    
        foreach (
$this->nodes as $node) {
            if (
$node->ancestors == $path) {
            
$return[] = $node;
        }
    }
    
    return 
$return;
    }


?>