rlm@3: rlm@3: * @copyright Copyright (c) 2005-2007, Adam A. Flynn rlm@3: * rlm@3: * @version 1.3.0 rlm@3: */ rlm@3: class XMLParser rlm@3: { rlm@3: /** rlm@3: * The XML parser rlm@3: * rlm@3: * @var resource rlm@3: */ rlm@3: var $parser; rlm@3: rlm@3: /** rlm@3: * The XML document rlm@3: * rlm@3: * @var string rlm@3: */ rlm@3: var $xml; rlm@3: rlm@3: /** rlm@3: * Document tag rlm@3: * rlm@3: * @var object rlm@3: */ rlm@3: var $document; rlm@3: rlm@3: /** rlm@3: * Current object depth rlm@3: * rlm@3: * @var array rlm@3: */ rlm@3: var $stack; rlm@3: /** rlm@3: * Whether or not to replace dashes and colons in tag rlm@3: * names with underscores. rlm@3: * rlm@3: * @var bool rlm@3: */ rlm@3: var $cleanTagNames; rlm@3: rlm@3: rlm@3: /** rlm@3: * Constructor. Loads XML document. rlm@3: * rlm@3: * @param string $xml The string of the XML document rlm@3: * @return XMLParser rlm@3: */ rlm@3: function XMLParser($xml = '', $cleanTagNames = true) rlm@3: { rlm@3: //Load XML document rlm@3: $this->xml = $xml; rlm@3: rlm@3: // Set stack to an array rlm@3: $this->stack = array(); rlm@3: rlm@3: //Set whether or not to clean tag names rlm@3: $this->cleanTagNames = $cleanTagNames; rlm@3: } rlm@3: rlm@3: /** rlm@3: * Initiates and runs PHP's XML parser rlm@3: */ rlm@3: function Parse() rlm@3: { rlm@3: //Create the parser resource rlm@3: $this->parser = xml_parser_create(); rlm@3: rlm@3: //Set the handlers rlm@3: xml_set_object($this->parser, $this); rlm@3: xml_set_element_handler($this->parser, 'StartElement', 'EndElement'); rlm@3: xml_set_character_data_handler($this->parser, 'CharacterData'); rlm@3: rlm@3: //Error handling rlm@3: if (!xml_parse($this->parser, $this->xml)) rlm@3: $this->HandleError(xml_get_error_code($this->parser), xml_get_current_line_number($this->parser), xml_get_current_column_number($this->parser)); rlm@3: rlm@3: //Free the parser rlm@3: xml_parser_free($this->parser); rlm@3: } rlm@3: rlm@3: /** rlm@3: * Handles an XML parsing error rlm@3: * rlm@3: * @param int $code XML Error Code rlm@3: * @param int $line Line on which the error happened rlm@3: * @param int $col Column on which the error happened rlm@3: */ rlm@3: function HandleError($code, $line, $col) rlm@3: { rlm@3: trigger_error('XML Parsing Error at '.$line.':'.$col.'. Error '.$code.': '.xml_error_string($code)); rlm@3: } rlm@3: rlm@3: rlm@3: /** rlm@3: * Gets the XML output of the PHP structure within $this->document rlm@3: * rlm@3: * @return string rlm@3: */ rlm@3: function GenerateXML() rlm@3: { rlm@3: return $this->document->GetXML(); rlm@3: } rlm@3: rlm@3: /** rlm@3: * Gets the reference to the current direct parent rlm@3: * rlm@3: * @return object rlm@3: */ rlm@3: function GetStackLocation() rlm@3: { rlm@3: $return = ''; rlm@3: rlm@3: foreach($this->stack as $stack) rlm@3: $return .= $stack.'->'; rlm@3: rlm@3: return rtrim($return, '->'); rlm@3: } rlm@3: rlm@3: /** rlm@3: * Handler function for the start of a tag rlm@3: * rlm@3: * @param resource $parser rlm@3: * @param string $name rlm@3: * @param array $attrs rlm@3: */ rlm@3: function StartElement($parser, $name, $attrs = array()) rlm@3: { rlm@3: //Make the name of the tag lower case rlm@3: $name = strtolower($name); rlm@3: rlm@3: //Check to see if tag is root-level rlm@3: if (count($this->stack) == 0) rlm@3: { rlm@3: //If so, set the document as the current tag rlm@3: $this->document = new XMLTag($name, $attrs); rlm@3: rlm@3: //And start out the stack with the document tag rlm@3: $this->stack = array('document'); rlm@3: } rlm@3: //If it isn't root level, use the stack to find the parent rlm@3: else rlm@3: { rlm@3: //Get the name which points to the current direct parent, relative to $this rlm@3: $parent = $this->GetStackLocation(); rlm@3: rlm@3: //Add the child rlm@3: eval('$this->'.$parent.'->AddChild($name, $attrs, '.count($this->stack).', $this->cleanTagNames);'); rlm@3: rlm@3: //If the cleanTagName feature is on, replace colons and dashes with underscores rlm@3: if($this->cleanTagNames) rlm@3: $name = str_replace(array(':', '-'), '_', $name); rlm@3: rlm@3: rlm@3: //Update the stack rlm@3: eval('$this->stack[] = $name.\'[\'.(count($this->'.$parent.'->'.$name.') - 1).\']\';'); rlm@3: } rlm@3: } rlm@3: rlm@3: /** rlm@3: * Handler function for the end of a tag rlm@3: * rlm@3: * @param resource $parser rlm@3: * @param string $name rlm@3: */ rlm@3: function EndElement($parser, $name) rlm@3: { rlm@3: //Update stack by removing the end value from it as the parent rlm@3: array_pop($this->stack); rlm@3: } rlm@3: rlm@3: /** rlm@3: * Handler function for the character data within a tag rlm@3: * rlm@3: * @param resource $parser rlm@3: * @param string $data rlm@3: */ rlm@3: function CharacterData($parser, $data) rlm@3: { rlm@3: //Get the reference to the current parent object rlm@3: $tag = $this->GetStackLocation(); rlm@3: rlm@3: //Assign data to it rlm@3: eval('$this->'.$tag.'->tagData .= trim($data);'); rlm@3: } rlm@3: } rlm@3: rlm@3: rlm@3: /** rlm@3: * XML Tag Object (php4) rlm@3: * rlm@3: * This object stores all of the direct children of itself in the $children array. They are also stored by rlm@3: * type as arrays. So, if, for example, this tag had 2 tags as children, there would be a class member rlm@3: * called $font created as an array. $font[0] would be the first font tag, and $font[1] would be the second. rlm@3: * rlm@3: * To loop through all of the direct children of this object, the $children member should be used. rlm@3: * rlm@3: * To loop through all of the direct children of a specific tag for this object, it is probably easier rlm@3: * to use the arrays of the specific tag names, as explained above. rlm@3: * rlm@3: * @author Adam A. Flynn rlm@3: * @copyright Copyright (c) 2005-2007, Adam A. Flynn rlm@3: * rlm@3: * @version 1.3.0 rlm@3: */ rlm@3: class XMLTag rlm@3: { rlm@3: /** rlm@3: * Array with the attributes of this XML tag rlm@3: * rlm@3: * @var array rlm@3: */ rlm@3: var $tagAttrs; rlm@3: rlm@3: /** rlm@3: * The name of the tag rlm@3: * rlm@3: * @var string rlm@3: */ rlm@3: var $tagName; rlm@3: rlm@3: /** rlm@3: * The data the tag contains rlm@3: * rlm@3: * So, if the tag doesn't contain child tags, and just contains a string, it would go here rlm@3: * rlm@3: * @var string rlm@3: */ rlm@3: var $tagData; rlm@3: rlm@3: /** rlm@3: * Array of references to the objects of all direct children of this XML object rlm@3: * rlm@3: * @var array rlm@3: */ rlm@3: var $tagChildren; rlm@3: rlm@3: /** rlm@3: * The number of parents this XML object has (number of levels from this tag to the root tag) rlm@3: * rlm@3: * Used presently only to set the number of tabs when outputting XML rlm@3: * rlm@3: * @var int rlm@3: */ rlm@3: var $tagParents; rlm@3: rlm@3: /** rlm@3: * Constructor, sets up all the default values rlm@3: * rlm@3: * @param string $name rlm@3: * @param array $attrs rlm@3: * @param int $parents rlm@3: * @return XMLTag rlm@3: */ rlm@3: function XMLTag($name, $attrs = array(), $parents = 0) rlm@3: { rlm@3: //Make the keys of the attr array lower case, and store the value rlm@3: $this->tagAttrs = array_change_key_case($attrs, CASE_LOWER); rlm@3: rlm@3: //Make the name lower case and store the value rlm@3: $this->tagName = strtolower($name); rlm@3: rlm@3: //Set the number of parents rlm@3: $this->tagParents = $parents; rlm@3: rlm@3: //Set the types for children and data rlm@3: $this->tagChildren = array(); rlm@3: $this->tagData = ''; rlm@3: } rlm@3: rlm@3: /** rlm@3: * Adds a direct child to this object rlm@3: * rlm@3: * @param string $name rlm@3: * @param array $attrs rlm@3: * @param int $parents rlm@3: * @param bool $cleanTagName rlm@3: */ rlm@3: function AddChild($name, $attrs, $parents, $cleanTagName = true) rlm@3: { rlm@3: //If the tag is a reserved name, output an error rlm@3: if(in_array($name, array('tagChildren', 'tagAttrs', 'tagParents', 'tagData', 'tagName'))) rlm@3: { rlm@3: trigger_error('You have used a reserved name as the name of an XML tag. Please consult the documentation (http://www.criticaldevelopment.net/xml/) and rename the tag named "'.$name.'" to something other than a reserved name.', E_USER_ERROR); rlm@3: rlm@3: return; rlm@3: } rlm@3: rlm@3: //Create the child object itself rlm@3: $child = new XMLTag($name, $attrs, $parents); rlm@3: rlm@3: //If the cleanTagName feature is on, replace colons and dashes with underscores rlm@3: if($cleanTagName) rlm@3: $name = str_replace(array(':', '-'), '_', $name); rlm@3: rlm@3: //Toss up a notice if someone's trying to to use a colon or dash in a tag name rlm@3: elseif(strstr($name, ':') || strstr($name, '-')) rlm@3: trigger_error('Your tag named "'.$name.'" contains either a dash or a colon. Neither of these characters are friendly with PHP variable names, and, as such, they cannot be accessed and will cause the parser to not work. You must enable the cleanTagName feature (pass true as the second argument of the XMLParser constructor). For more details, see http://www.criticaldevelopment.net/xml/', E_USER_ERROR); rlm@3: rlm@3: //If there is no array already set for the tag name being added, rlm@3: //create an empty array for it rlm@3: if(!isset($this->$name)) rlm@3: $this->$name = array(); rlm@3: rlm@3: //Add the reference of it to the end of an array member named for the tag's name rlm@3: $this->{$name}[] =& $child; rlm@3: rlm@3: //Add the reference to the children array member rlm@3: $this->tagChildren[] =& $child; rlm@3: } rlm@3: rlm@3: /** rlm@3: * Returns the string of the XML document which would be generated from this object rlm@3: * rlm@3: * This function works recursively, so it gets the XML of itself and all of its children, which rlm@3: * in turn gets the XML of all their children, which in turn gets the XML of all thier children, rlm@3: * and so on. So, if you call GetXML from the document root object, it will return a string for rlm@3: * the XML of the entire document. rlm@3: * rlm@3: * This function does not, however, return a DTD or an XML version/encoding tag. That should be rlm@3: * handled by XMLParser::GetXML() rlm@3: * rlm@3: * @return string rlm@3: */ rlm@3: function GetXML() rlm@3: { rlm@3: //Start a new line, indent by the number indicated in $this->parents, add a <, and add the name of the tag rlm@3: $out = "\n".str_repeat("\t", $this->tagParents).'<'.$this->tagName; rlm@3: rlm@3: //For each attribute, add attr="value" rlm@3: foreach($this->tagAttrs as $attr => $value) rlm@3: $out .= ' '.$attr.'="'.$value.'"'; rlm@3: rlm@3: //If there are no children and it contains no data, end it off with a /> rlm@3: if(empty($this->tagChildren) && empty($this->tagData)) rlm@3: $out .= " />"; rlm@3: rlm@3: //Otherwise... rlm@3: else rlm@3: { rlm@3: //If there are children rlm@3: if(!empty($this->tagChildren)) rlm@3: { rlm@3: //Close off the start tag rlm@3: $out .= '>'; rlm@3: rlm@3: //For each child, call the GetXML function (this will ensure that all children are added recursively) rlm@3: foreach($this->tagChildren as $child) rlm@3: { rlm@3: if(is_object($child)) rlm@3: $out .= $child->GetXML(); rlm@3: } rlm@3: rlm@3: //Add the newline and indentation to go along with the close tag rlm@3: $out .= "\n".str_repeat("\t", $this->tagParents); rlm@3: } rlm@3: rlm@3: //If there is data, close off the start tag and add the data rlm@3: elseif(!empty($this->tagData)) rlm@3: $out .= '>'.$this->tagData; rlm@3: rlm@3: //Add the end tag rlm@3: $out .= 'tagName.'>'; rlm@3: } rlm@3: rlm@3: //Return the final output rlm@3: return $out; rlm@3: } rlm@3: rlm@3: /** rlm@3: * Deletes this tag's child with a name of $childName and an index rlm@3: * of $childIndex rlm@3: * rlm@3: * @param string $childName rlm@3: * @param int $childIndex rlm@3: */ rlm@3: function Delete($childName, $childIndex = 0) rlm@3: { rlm@3: //Delete all of the children of that child rlm@3: $this->{$childName}[$childIndex]->DeleteChildren(); rlm@3: rlm@3: //Destroy the child's value rlm@3: $this->{$childName}[$childIndex] = null; rlm@3: rlm@3: //Remove the child's name from the named array rlm@3: unset($this->{$childName}[$childIndex]); rlm@3: rlm@3: //Loop through the tagChildren array and remove any null rlm@3: //values left behind from the above operation rlm@3: for($x = 0; $x < count($this->tagChildren); $x ++) rlm@3: { rlm@3: if(is_null($this->tagChildren[$x])) rlm@3: unset($this->tagChildren[$x]); rlm@3: } rlm@3: } rlm@3: rlm@3: /** rlm@3: * Removes all of the children of this tag in both name and value rlm@3: */ rlm@3: function DeleteChildren() rlm@3: { rlm@3: //Loop through all child tags rlm@3: for($x = 0; $x < count($this->tagChildren); $x ++) rlm@3: { rlm@3: //Do this recursively rlm@3: $this->tagChildren[$x]->DeleteChildren(); rlm@3: rlm@3: //Delete the name and value rlm@3: $this->tagChildren[$x] = null; rlm@3: unset($this->tagChildren[$x]); rlm@3: } rlm@3: } rlm@3: } rlm@3: ?>