| <?php | |
| /** | |
| * A script that reads xml documents without the need of an external library | |
| * Useful for reading configuration data or content data, that would otherwise | |
| * have been placed in a db | |
| * | |
| * known bugs/limitations : | |
| * + doesn't cope well with erraneous files - so be sure to validate | |
| * + a lot of the special tags like doctype etc. aren't handled well | |
| * + external doctype definitions are accepted though (but ignored) | |
| * + methods and behaviour doesn't follow the W3C DOM specification correctly, although mostly. | |
| * usage : | |
| * <code> | |
| * // open and read file | |
| * $xml =& new xmlParser($filename); | |
| * // parse document and return rootnode | |
| * $doc =& $xml->getDocument(); | |
| * </code> | |
| * | |
| * disclaimer : | |
| * this piece of code is freely usable by anyone. if it makes your life better, | |
| * remember me in your eveningprayer. if it makes your life worse, try doing it any | |
| * better yourself. | |
| * | |
| * @version 4. aug. 04 | |
| * @author troels@kyberfabrikken.dk | |
| * @download http://www.phpclasses.org/ | |
| * @package xmllib | |
| */ | |
| /** | |
| * A generic implementation of a DOM node. | |
| * Somehow follows the specs at w3c | |
| * @see http://www.w3.org/TR/1998/REC-DOM-Level-1-19981001/ | |
| * @package xmllib | |
| */ | |
| class Node | |
| { | |
| /** | |
| * Holds the attributes of the node | |
| * @var array | |
| * @access public | |
| */ | |
| var $attributes; | |
| /** | |
| * Holds the children of the node | |
| * @var array | |
| * @access private | |
| */ | |
| var $children; | |
| /** | |
| * The nodeName | |
| * @var string | |
| * @access private | |
| */ | |
| var $nodeName; | |
| /** | |
| * The nodeType | |
| * @var int | |
| * @access private | |
| */ | |
| var $nodeType; | |
| /** | |
| * Reference to parent node | |
| * @var reference | |
| * @access private | |
| */ | |
| var $parent; | |
| /** | |
| * Internally used by the parser | |
| * @var int | |
| * @access private | |
| */ | |
| var $_end; | |
| /** | |
| * Constructor | |
| * @param string $nodeName The name of the node to construct | |
| * @param int $nodeType The type of the node to construct. | |
| * @see createChild | |
| */ | |
| function Node($nodeName='node', $nodeType=1) | |
| { | |
| $this->nodeName = $nodeName; | |
| $this->children = array(); | |
| $this->attributes = array(); | |
| $this->parent = null; | |
| $this->nodeType = $nodeType; // 1=element | |
| } | |
| /** | |
| * Factorymethod. | |
| * Constructs a child node, and returns it. | |
| * @param string $nodeName The name of the node to construct | |
| * @param int $nodeType The type of the node to construct. | |
| * @return Node | |
| */ | |
| function &createChild($nodeName=null, $nodeType=1) | |
| { | |
| // create new instance of this class | |
| $params = ($nodeName == null) ? "();" : "(\$nodeName, \$nodeType);"; | |
| $n=null; | |
| eval("\$n =& new ".get_class($this).$params); | |
| $this->appendChild($n); | |
| return $n; | |
| } | |
| /** | |
| * @param Node $orphan The node to adopt | |
| * @deprecated | |
| * @see appendChild | |
| */ | |
| function adopt(&$orphan) | |
| { | |
| $this->appendChild($orphan); | |
| } | |
| /** | |
| * Assigns a node as child to current node | |
| * @param Node $orphan The node to adopt | |
| */ | |
| function appendChild(&$orphan) | |
| { | |
| $orphan->parent =& $this; | |
| $this->children[] =& $orphan; | |
| $this->children[count($this->children)-1]; | |
| } | |
| /** | |
| * Removes the child node indicated by oldChild from the list of children, and returns it. | |
| * @param Node $orphan The node to adopt | |
| * @returns Node | |
| */ | |
| function & removeChild(&$oldChild) | |
| { | |
| $_children = array(); | |
| $notFound = true; | |
| for ($i=0;$i<count($this->children);$i++) { | |
| if ($oldChild !== $this->children[$i]) { | |
| $_children[] =& $this->children[$i]; | |
| $notFound = false; | |
| } | |
| } | |
| if ($notFound) { | |
| trigger_error('oldChild is not a child of this node', E_USER_NOTICE); | |
| return null; | |
| } | |
| $this->children = $_children; | |
| $oldChild->parent = null; | |
| return $oldChild; | |
| } | |
| /** | |
| * Replaces the child node oldChild with newChild in the list of children, and returns | |
| * the oldChild node. If the newChild is already in the tree, it is first removed. | |
| * @param Node $newChild The new node to put in the child list. | |
| * @param Node $oldChild The node being replaced in the list. | |
| * @returns Node The node replaced. | |
| */ | |
| function & replaceChild(&$newChild, &$oldChild) | |
| { | |
| $notFound = true; | |
| for ($i=0;$i<count($this->children);$i++) { | |
| if ($oldChild !== $this->children[$i]) { | |
| $this->children[$i] = null; | |
| $this->children[$i] =& $newChild; | |
| $newChild->parent =& $this; | |
| $notFound = false; | |
| } | |
| } | |
| if ($notFound) { | |
| trigger_error('oldChild is not a child of this node', E_USER_NOTICE); | |
| return null; | |
| } | |
| $oldChild->parent = null; | |
| return $oldChild; | |
| } | |
| /** | |
| * Fetch the last childnode, or null | |
| * @param string $nodeName If supplied, only nodes of that nodeName will be returned | |
| * @return Node | |
| */ | |
| function &lastChild($nodeName=null) | |
| { | |
| if (count($this->children)==0) | |
| return null; | |
| if ($nodeName == null) | |
| return $this->children[count($this->children)-1]; | |
| for ($i=count($this->children);$i>=0;$i--) { | |
| $c =& $this->children[$i]; | |
| if (($nodeName == null) || ($c->nodeName == $nodeName)) | |
| return $c; | |
| } | |
| return null; | |
| } | |
| /** | |
| * Fetch the first childnode, or null | |
| * @param string $nodeName If supplied, only nodes of that nodeName will be returned | |
| * @return Node | |
| */ | |
| function &firstChild($nodeName=null) | |
| { | |
| if (count($this->children)==0) | |
| return null; | |
| if ($nodeName == null) | |
| return $this->children[0]; | |
| for ($i=0;$i<count($this->children);$i++) { | |
| $c =& $this->children[$i]; | |
| if (($nodeName == null) || ($c->nodeName() == $nodeName)) | |
| return $c; | |
| } | |
| return null; | |
| } | |
| /** | |
| * This is a convenience method to allow easy determination of whether a node has any children. | |
| * @see hasChildNodes | |
| * @return bool | |
| */ | |
| function hasChildren() | |
| { | |
| return (count($this->children)>0); | |
| } | |
| /** | |
| * This is a convenience method to allow easy determination of whether a node has any children. | |
| * @see hasChildren | |
| * @return bool | |
| */ | |
| function hasChildNodes() | |
| { | |
| return $this->hasChildren(); | |
| } | |
| /** | |
| * Fetch array of childnodes. | |
| * @param string $nodeName If supplied, only nodes of that nodeName will be returned | |
| * @param string $nodeType If supplied, only nodes of that nodeType will be returned | |
| * @see childNodes | |
| * @return array | |
| */ | |
| function children($nodeName=null, $nodeType=null) | |
| { | |
| $ret = array(); | |
| for ($i=0;$i<count($this->children);$i++) { | |
| $c =& $this->children[$i]; | |
| if ((($nodeName == null) || ($c->nodeName() == $nodeName)) && (($nodeType == null) || ($c->nodeType() == $nodeType))) | |
| $ret[count($ret)] =& $c; | |
| } | |
| return $ret; | |
| } | |
| /** | |
| * Alias for children() | |
| * @see children | |
| */ | |
| function childNodes($nodeName=null, $nodeType=null) | |
| { | |
| return $this->children($nodeName, $nodeType); | |
| } | |
| /** | |
| * Iterates through the nodes children, looking for the first node with specified id. | |
| * @return Node | |
| */ | |
| function &getElementById($id) | |
| { | |
| if (isset($this->attributes['id']) && $this->attributes['id'] == $id) | |
| return $this; | |
| for ($i=0;$i<count($this->children);$i++) { | |
| $node =& $this->children[$i]->getElementById($id); | |
| if (is_object($node)) | |
| return $node; | |
| } | |
| return null; | |
| } | |
| /** | |
| * Finds the greatest used id + 1 (next free id) | |
| * @return int | |
| */ | |
| function getCardinality() | |
| { | |
| $cardinal = 0; | |
| if (isset($this->attributes['id'])) | |
| $cardinal = $this->attributes['id'] + 1; | |
| for ($i=0;$i<count($this->children);$i++) { | |
| $tmp = $this->children[$i]->getCardinality(); | |
| if ($tmp > $cardinal) | |
| $cardinal = $tmp; | |
| } | |
| return $cardinal; | |
| } | |
| /** | |
| * If the returned value is null, the node is the topmost (root) | |
| * @deprecated | |
| * @see parentNode | |
| * @return Node | |
| */ | |
| function &parent() | |
| { | |
| return $this->parentNode(); | |
| } | |
| /** | |
| * If the returned value is null, the node is the topmost (root) | |
| * @return Node | |
| */ | |
| function &parentNode() | |
| { | |
| return $this->parent; | |
| } | |
| /** | |
| * Retrieves the name of the node | |
| * @note In older versions of xmllib, nodeName was (wrongly) called type. | |
| * @return string | |
| */ | |
| function nodeName() | |
| { | |
| return $this->nodeName; | |
| } | |
| /** | |
| * Retrieves the type of the node | |
| * @note In older versions of xmllib, nodeName was called type. | |
| * @return int | |
| */ | |
| function nodeType() | |
| { | |
| return $this->nodeType; | |
| } | |
| /** | |
| * Retrieves the named attribute. | |
| * @note It's legal to accees $this->attributes directly, but this presents a more clean way of accomplishing it. | |
| * @param string $name Name of the attribute to retrieve. | |
| * @return string | |
| */ | |
| function getAttribute($name) | |
| { | |
| return (isset($this->attributes[$name])) ? $this->attributes[$name] : null; | |
| } | |
| /** | |
| * Sets the value of the named attribute. | |
| * @param string $name Name of the attribute to retrieve. | |
| * @param string $value Value to assign to the node. Only scalar values are allowed. | |
| * @return bool | |
| */ | |
| function setAttribute($name, $value) | |
| { | |
| if (!is_scalar($value)) { | |
| trigger_error("Only scalar values are allowed as attribute values.", E_USER_WARNING); | |
| return false; | |
| } | |
| $this->attributes[$name] = $value; | |
| return true; | |
| } | |
| /** | |
| * Removes a named attribute | |
| * @param string $name Name of the attribute to retrieve. | |
| */ | |
| function removeAttribute($name) | |
| { | |
| $_attributes = array(); | |
| for ($i=0, $keys=array_keys($this->attributes); $i < count($keys); $i++) { | |
| if ($keys[$i] != $name) | |
| $_attributes[] = $this->attributes[$keys[$i]]; | |
| } | |
| $this->attributes =& $_attributes; | |
| } | |
| /** | |
| * Fetch the value of this node, depending on its type. | |
| * @return string | |
| */ | |
| function nodeValue() | |
| { | |
| if (isset($this->attributes['value'])) | |
| return $this->attributes['value']; | |
| if (count($this->children('#cdata-section')) == 1) { | |
| $n = $this->firstChild('#cdata-section'); | |
| return $n->data; | |
| } | |
| if (count($this->children('#text')) == 1) { | |
| $n = $this->firstChild('#text'); | |
| return $n->data; | |
| } | |
| return null; | |
| } | |
| /** | |
| * Escape html-entities for flash | |
| * For some reason flash doesn't seem to recognize the htmlentities | |
| * for singlequote, lesser than and greater than | |
| * @param string $str String to escape | |
| * @return string | |
| */ | |
| function flash_escape($str) | |
| { | |
| $translation_table = array(); | |
| $translation_table['&'] = '&'; | |
| $translation_table['"'] = '"'; | |
| $translation_table["'"] = '''; | |
| $translation_table['<'] = '<'; | |
| $translation_table['>'] = '>'; | |
| return strtr($str, $translation_table); | |
| } | |
| /** | |
| * Escapes string with different protocols | |
| * Allowed values for translation param are : | |
| * + htmlentities | |
| * + htmlspecialchars | |
| * + addslashes | |
| * + flash_escape | |
| * @param string $str String to escape | |
| * @param string $translation The mode to translate the string by | |
| * @return string | |
| */ | |
| function escape($str, $translation) | |
| { | |
| switch ($translation) { | |
| case 'flash_escape' : return Node::flash_escape($str); | |
| case 'addslashes' : return addslashes($str); | |
| case 'htmlspecialchars' : return htmlspecialchars($str); | |
| case 'htmlentities' : return htmlentities($str); | |
| } | |
| trigger_error("unknown translation : ".$translation, E_USER_WARNING); | |
| return $str; | |
| } | |
| /** | |
| * decodes c-style slashes and html-entities (unicode ones too) | |
| * @param string $str Encoded string | |
| * @return string | |
| */ | |
| function unescape($str) | |
| { | |
| $translation_table = array_flip(get_html_translation_table(HTML_ENTITIES)); | |
| $ret = strtr(stripslashes($str), $translation_table); | |
| return preg_replace('/&#(\d+);/me', "chr('\\1')",$ret); | |
| } | |
| /** | |
| * Recursively transforms node into string | |
| * @see escape | |
| * @param bool $add_formatting If true, blocks get indented | |
| * @param string $att_escape Translation to use. | |
| * @param string $str String to escape | |
| * @return string | |
| */ | |
| function toString($add_formatting=false, $att_escape='addslashes') | |
| { | |
| $indent = ""; | |
| $toadd = ""; | |
| $indent_minus_one = ""; | |
| if ($add_formatting) { | |
| $toadd = "\n"; | |
| for ($p = $this->parent(); $p != null; $p = $p->parent()) { | |
| if ($indent != "") | |
| $indent_minus_one .= "\t"; | |
| $indent .= "\t"; | |
| } | |
| } | |
| if ($this->nodeType() == 3) { | |
| return Node::escape($this->data, $att_escape); | |
| } else if ($this->nodeType() == 4) { | |
| if (strpos($this->data, '<![CDATA[') !== false) { | |
| trigger_error("illegal content. cdata-section can't contain the string : <![CDATA[", E_USER_WARNING); | |
| } | |
| if (strpos($this->data, ']]>') !== false) { | |
| trigger_error("illegal content. cdata-section can't contain the string : ]]>", E_USER_WARNING); | |
| } | |
| return $toadd.$indent."<![CDATA[".$this->data."]]>".$toadd.$indent_minus_one; | |
| } else if ($this->nodeType() == 8) { | |
| return $toadd.$indent."<!--".$this->data."-->".$toadd.$indent_minus_one; | |
| } else if ($this->nodeType() != 1) { | |
| trigger_error("unsupported nodetype : ".$this->nodeType(), E_USER_WARNING); | |
| } | |
| $a = ""; | |
| foreach ($this->attributes as $name => $value) { | |
| $value = Node::escape($value, $att_escape); | |
| $a .= " ".$name."=\"".$value."\""; | |
| } | |
| if ((count($this->children) == 0)) { | |
| return $indent."<".$this->nodeName().$a." />"; | |
| } | |
| $ret = $indent."<".$this->nodeName().$a.">"; | |
| if ((count($this->children(null,1)) == 0)) { | |
| // only text/cdata nodes contained | |
| foreach ($this->children as $child) { | |
| $ret .= $child->toString($add_formatting, $att_escape); | |
| } | |
| $ret .= "</".$this->nodeName().">"; | |
| } else { | |
| $ret .= $toadd; | |
| foreach ($this->children as $child) { | |
| $ret .= $child->toString($add_formatting, $att_escape); | |
| $ret .= $toadd; | |
| } | |
| $ret .= $indent."</".$this->nodeName().">"; | |
| } | |
| return $ret; | |
| } | |
| /** | |
| * Outputs document to browser, complete with header | |
| * Param encoding works with : | |
| * + UTF-8 | |
| * + ISO-8859-1 | |
| * but may work with other encodings too. | |
| * @param string $encoding The document encoding. | |
| */ | |
| function dump($encoding='utf-8') | |
| { | |
| header ("Content-Type: application/xml"); | |
| echo "<?xml version=\"1.0\" encoding=\"".$encoding."\" ?>\n"; | |
| if ($encoding == 'utf-8') { | |
| echo utf8_encode($this->toString(true, 'flash_escape')); | |
| } else { | |
| echo $this->toString(true, 'flash_escape'); | |
| } | |
| } | |
| /** | |
| * Writes document to file | |
| * @param string $filename The filename to write to | |
| * @param string $encoding The document encoding. | |
| */ | |
| function toFile($filename, $encoding='utf-8') | |
| { | |
| $content = "<?xml version=\"1.0\" encoding=\"".$encoding."\" ?>\n"; | |
| if ($encoding == 'utf-8') { | |
| $content .= utf8_encode($this->toString(true, 'flash_escape')); | |
| } else { | |
| $content .= $this->toString(true, 'flash_escape'); | |
| } | |
| $fp = fopen($filename,'wb'); | |
| fwrite($fp, $content); | |
| fclose($fp); | |
| } | |
| /** | |
| * Makes a html decorated version of the document. Usefull for debugging. | |
| * @see toString | |
| * @return string Document as html-formatted string | |
| */ | |
| function toHtml() | |
| { | |
| return nl2br(str_replace('<', '<strong><', str_replace('>', '></strong>', str_replace("\t",'    ',htmlentities($this->toString(true, 'htmlentities')))))); | |
| } | |
| /** | |
| * Translates the document into an associative array. | |
| * @param Node $node From where to start the recursion. You shouldn't use this. | |
| * @return array | |
| */ | |
| function toArray($node=null) | |
| { | |
| if ($node == null) $node = $this; | |
| if ((isset($node->attributes['type']) && ($node->attributes['type'] == 'array') || $node->nodeName() == 'array')) { | |
| $a = array(); | |
| $c =& $node->children(); | |
| for ($i=0; $i < count($c); $i++) { | |
| if (in_array($c[$i]->nodeType(), array(1,3,4))) { | |
| array_push($a, Node::toArray($c[$i])); | |
| } | |
| } | |
| return $a; | |
| } | |
| if (!$node->hasChildren()) { | |
| if (count($node->attributes)>0) | |
| return $node->attributes; | |
| return $node->nodeValue(); | |
| } | |
| $a = array(); | |
| $c =& $node->children(); | |
| for ($i=0; $i < count($c); $i++) { | |
| if (in_array($c[$i]->nodeType(), array(1,3,4))) { | |
| $v = $c[$i]->nodeValue(); | |
| if ($v !== null) | |
| $a[$c[$i]->nodeName()] = $v; | |
| else | |
| $a[$c[$i]->nodeName()] = Node::toArray($c[$i]); | |
| } | |
| } | |
| return $a; | |
| } | |
| } | |
| /** | |
| * The xml-parser. Used to parse a document into a tree of nodes. | |
| * @access public | |
| * @package xmllib | |
| */ | |
| class xmlParser | |
| { | |
| /** | |
| * @access private | |
| */ | |
| var $parse_stack; | |
| /** | |
| * @access private | |
| */ | |
| var $data; | |
| /** | |
| * @access private | |
| */ | |
| var $encoding; | |
| /** | |
| * @access private | |
| */ | |
| var $casefolding; | |
| /** | |
| * Constructor | |
| * @param string $filename If a filename is given, the document will be loaded and prepared | |
| * @param boolean $casefolding If true, uppercase nodenames and attributenames are converted to lowercase | |
| */ | |
| function xmlParser($filename=null, $casefolding=false) | |
| { | |
| $this->casefolding = $casefolding; | |
| if (is_string($filename)) { | |
| $this->loadFromFile($filename); | |
| } | |
| } | |
| /** | |
| * Loads and prepares a file | |
| * @param string $filename name of the file | |
| */ | |
| function loadFromFile($filename) | |
| { | |
| if (!function_exists('file_get_contents')) { | |
| $fp = fopen($filename,'rb'); | |
| $size = filesize($filename); | |
| $contents = fread($fp,$size); | |
| fclose($fp); | |
| } else $contents = file_get_contents($filename); | |
| $this->loadFromString($filename,$contents); | |
| } | |
| /** | |
| * Loads a xml-document and prepares the parsing | |
| * @param string $contents the document | |
| */ | |
| function loadFromString($filename,$contents) | |
| { | |
| $this->parse_stack = array(); | |
| // find xml opening tag | |
| $pos1 = strpos($contents, "<?xml"); | |
| if ($pos1 === false) { | |
| trigger_error ($filename . " is not valid xml", E_USER_ERROR); | |
| } | |
| // find xml opening tag terminator | |
| $pos2 = strpos($contents, "?>"); | |
| if ($pos2 === false) { | |
| trigger_error ($filename . " is not valid xml", E_USER_ERROR); | |
| } | |
| // find the encoding | |
| $xml_att = substr($contents, $pos1, $pos2); | |
| $pos3 = strpos($xml_att, "encoding=\""); | |
| if ($pos3 === false) { | |
| $this->encoding = "utf-8"; | |
| } else { | |
| $xml_att_enc = substr($xml_att, $pos3+10, $pos2); | |
| $pos4 = strpos($xml_att_enc, "\""); | |
| if ($pos3 === false) { | |
| } else { | |
| $this->encoding = strtolower(substr($xml_att_enc, 0, $pos4)); | |
| } | |
| } | |
| if ($this->encoding == "utf-8") { | |
| $contents = utf8_decode($contents); | |
| $pos2 = strpos($contents, "?>"); | |
| } | |
| // strip xml opening tag | |
| $this->data = substr($contents, $pos2+2); | |
| // find doctype tag if any | |
| $pos = strpos($this->data, "<!DOCTYPE"); | |
| if ($pos !== false) { | |
| // find doctype tag terminator | |
| $pos = strpos($this->data, ">"); | |
| if ($pos === false) { | |
| trigger_error ("xml parse error in ".$filename, E_USER_ERROR); | |
| } | |
| $this->data = substr($this->data, $pos+1); | |
| } | |
| } | |
| /** | |
| * Parses the loaded document | |
| * @returns Node The rootnode of the document | |
| */ | |
| function getDocument() | |
| { | |
| $tags = $this->parse_tags(); | |
| return $this->parse_tree($tags); | |
| } | |
| /** | |
| * @access private | |
| */ | |
| function stack_push($nodeName, $pos, $a_begin) | |
| { | |
| $nodeName = strtolower($nodeName); | |
| if (!isset($this->parse_stack[$nodeName]) || ($this->parse_stack[$nodeName] == null)) | |
| $this->parse_stack[$nodeName] = array(); | |
| $t = new Tag($nodeName); | |
| $t->begin = $pos; | |
| $t->a_begin = $a_begin; | |
| array_push($this->parse_stack[$nodeName], $t); | |
| } | |
| /** | |
| * @access private | |
| */ | |
| function stack_pop($nodeName, $pos) | |
| { | |
| $nodeName = strtolower($nodeName); | |
| if (($this->parse_stack[$nodeName] == null) || (count($this->parse_stack[$nodeName])==0)) | |
| trigger_error("xml_malformed unmatched closing tag [tag=".$nodeName." pos=".$pos."]", E_USER_ERROR); | |
| $t = array_pop($this->parse_stack[$nodeName]); | |
| $t->end = $pos; | |
| return $t; | |
| } | |
| /** | |
| * @access private | |
| */ | |
| function cmp($a, $b) | |
| { | |
| if ($a->begin == $b->begin) return 0; | |
| return ($a->begin > $b->begin) ? 1 : -1; | |
| } | |
| /** | |
| * eh .. a hack really ... but it works, so who cares | |
| * @access private | |
| */ | |
| function add_textnode($textnode_begin, $textnode_end) | |
| { | |
| $t = new Tag('#text'); | |
| $t->nodeType = 3; | |
| $t->begin = $textnode_begin+1; | |
| $t->end = $textnode_end-1; | |
| $t->data = Node::unescape(substr($this->data, $textnode_begin, $textnode_end - $textnode_begin)); | |
| $t->begin; | |
| return $t; | |
| } | |
| /** | |
| * @access private | |
| */ | |
| function parse_tags() | |
| { | |
| $valid = array( 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','-','_',':', | |
| 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'); | |
| $parse_tags = array(); | |
| $len = strlen($this->data); | |
| $textnode_begin = -1; | |
| for ($i=0;$i<$len;$i++) { | |
| $n = $i+1; | |
| if (substr($this->data, $i, 9) == '<![CDATA[') { | |
| if ($textnode_begin > -1) { | |
| array_push($parse_tags, $this->add_textnode($textnode_begin, $i)); | |
| $textnode_begin = -1; | |
| } | |
| $t = new Tag('#cdata-section'); | |
| $t->nodeType = 4; | |
| $t->begin = $i+9; | |
| for ($i=$t->begin;substr($this->data, $i, 3) != ']]>';$i++) ; | |
| $t->end = $i; | |
| $i=$i+2; | |
| $t->data = substr($this->data, $t->begin, $t->end - $t->begin); | |
| array_push($parse_tags, $t); | |
| } else if (substr($this->data, $i, 4) == '<!--') { | |
| if ($textnode_begin > -1) { | |
| array_push($parse_tags, $this->add_textnode($textnode_begin, $i)); | |
| $textnode_begin = -1; | |
| } | |
| $t = new Tag('#comment'); | |
| $t->nodeType = 8; | |
| $t->begin = $i+4; | |
| for ($i=$t->begin;substr($this->data, $i, 3) != '-->';$i++) ; | |
| $t->end = $i; | |
| $i=$i+2; | |
| $t->data = substr($this->data, $t->begin, $t->end - $t->begin); | |
| array_push($parse_tags, $t); | |
| } else if (($this->data{$i} == '<') && ($n<$len) && ($this->data{$n} == '/')) { | |
| if ($textnode_begin > -1) { | |
| array_push($parse_tags, $this->add_textnode($textnode_begin, $i)); | |
| $textnode_begin = -1; | |
| } | |
| // closing tag | |
| $n++; | |
| for ($c=$this->data{$n++},$nodeName=''; in_array($c, $valid); $c=$this->data{$n++}) $nodeName .= $c; | |
| array_push($parse_tags, $this->stack_pop($nodeName, $i-1)); | |
| $i = $n-1; | |
| } else if ($this->data{$i} == '<') { | |
| if ($textnode_begin > -1) { | |
| array_push($parse_tags, $this->add_textnode($textnode_begin, $i)); | |
| $textnode_begin = -1; | |
| } | |
| // opening tag | |
| for ($c=$this->data{$n++},$nodeName=''; in_array($c, $valid); $c=$this->data{$n++}) $nodeName .= $c; | |
| for ($a=$n--;($this->data{$n}!='>') && !(($this->data{$n-1}=='/') && ($this->data{$n}=='>')); $n++) { ; } // this part holds the attributes | |
| if ($this->casefolding) $nodeName = strtolower($nodeName); | |
| if ($this->data{$n-1} == '/') { | |
| // single-style closing | |
| $t = new Tag($nodeName); | |
| $t->begin = $n+1; | |
| $t->end = $n+1; | |
| $t->a_begin = $a; | |
| array_push($parse_tags, $t); | |
| } else { | |
| $this->stack_push($nodeName, $n+1, $a); | |
| } | |
| $i = $n; | |
| } else if ($textnode_begin == -1 && (ord($this->data{$i}) > 32)) { | |
| $textnode_begin = $i; | |
| } | |
| } | |
| usort($parse_tags, array('xmlParser', 'cmp')); | |
| return $parse_tags; | |
| } | |
| /** | |
| * @access private | |
| */ | |
| function loadAttributes(&$node, $data) | |
| { | |
| $escapes=0; | |
| if ($node->nodeType != 1) | |
| return ; | |
| $valid = array( 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','-','_',':', | |
| 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'); | |
| $len = strlen($data); | |
| $name = null; | |
| $value = null; | |
| for ($i=0;$i<$len;$i++) { | |
| if ($value === null) { // reading name | |
| if ($data{$i} == '=') { // end of name | |
| $value = ''; | |
| $escapes = 0; | |
| $i++; // skip opening quote | |
| } else if (in_array($data{$i}, $valid)){ // add to name | |
| if ($name === null) $name = $data{$i}; | |
| else $name .= $data{$i}; | |
| } | |
| } else { // reading value | |
| if ($data{$i} == "\\") | |
| $escapes++; | |
| if ($data{$i} == "\"" && ($escapes == 0)) { // closing quote | |
| if ($this->casefolding) $name = strtolower($name); | |
| $node->attributes[$name] = Node::unescape($value); | |
| $name = null; | |
| $value = null; | |
| } else { // add to value | |
| $value .= $data{$i}; | |
| } | |
| if ($data{$i-1} == "\\") | |
| $escapes--; | |
| } | |
| } | |
| } | |
| /** | |
| * @access private | |
| */ | |
| function parse_tree($tags) | |
| { | |
| for ($r=null, $i=0; $i<count($tags); $i++) { | |
| if ($r == null) { | |
| $r = $tags[$i]->toNode(); | |
| $r->_end = strlen($this->data); | |
| if ($tags[$i]->a_begin != $tags[$i]->begin) | |
| $this->loadAttributes(&$r, substr($this->data, $tags[$i]->a_begin, ($tags[$i]->begin - $tags[$i]->a_begin)-1)); | |
| $p =& $r; | |
| } else { | |
| $n = $tags[$i]->toNode(); | |
| if ($tags[$i]->a_begin != $tags[$i]->begin) | |
| $this->loadAttributes(&$n, substr($this->data, $tags[$i]->a_begin, ($tags[$i]->begin - $tags[$i]->a_begin)-1)); | |
| while ($p->_end < $tags[$i]->begin) | |
| $p =& $p->parent; | |
| $n->parent =& $p; | |
| array_push($p->children, $n); | |
| $p =& $p->lastChild(); | |
| } | |
| } | |
| return $r; | |
| } | |
| } | |
| /** | |
| * Used by the parser, as an intermediate before becomming a node | |
| * @access private | |
| * @package xmllib | |
| */ | |
| class Tag | |
| { | |
| var $nodeName; | |
| var $begin; | |
| var $end; | |
| var $a_begin; | |
| function Tag($nodeName) | |
| { | |
| $this->nodeName = $nodeName; | |
| $this->nodeType = 1; | |
| $this->data = null; | |
| } | |
| function toNode() | |
| { | |
| $n = new Node($this->nodeName); | |
| $n->nodeType = $this->nodeType; | |
| $n->_end = $this->end; | |
| $n->data = $this->data; | |
| return $n; | |
| } | |
| } | |
| ?> |