Your IP : 3.145.152.146
<?php
/**
* @author Niels A.D.
* @author Todd Burry <todd@vanillaforums.com>
* @copyright 2010 Niels A.D., 2014 Todd Burry
* @license http://opensource.org/licenses/LGPL-2.1 LGPL-2.1
* @package pQuery
*/
namespace pagelayerQuery;
/**
* Holds (x)html/xml tag information like tag name, attributes,
* parent, children, self close, etc.
*
*/
class DomNode implements IQuery {
/**
* Element Node, used for regular elements
*/
const NODE_ELEMENT = 0;
/**
* Text Node
*/
const NODE_TEXT = 1;
/**
* Comment Node
*/
const NODE_COMMENT = 2;
/**
* Conditional Node (<![if]> <![endif])
*/
const NODE_CONDITIONAL = 3;
/**
* CDATA Node (<![CDATA[]]>
*/
const NODE_CDATA = 4;
/**
* Doctype Node
*/
const NODE_DOCTYPE = 5;
/**
* XML Node, used for tags that start with ?, like <?xml and <?php
*/
const NODE_XML = 6;
/**
* ASP Node
*/
const NODE_ASP = 7;
#php4 Compatibility with PHP4, this gets changed to a regular var in release tool
#static $NODE_TYPE = self::NODE_ELEMENT;
#php4e
#php5
/**
* Node type of class
*/
const NODE_TYPE = self::NODE_ELEMENT;
#php5e
/**
* Name of the selector class
* @var string
* @see select()
*/
var $selectClass = 'pagelayerQuery\\HtmlSelector';
/**
* Name of the parser class
* @var string
* @see setOuterText()
* @see setInnerText()
*/
var $parserClass = 'pagelayerQuery\\Html5Parser';
/**
* Name of the class used for {@link addChild()}
* @var string
*/
var $childClass = __CLASS__;
/**
* Name of the class used for {@link addText()}
* @var string
*/
var $childClass_Text = 'pagelayerQuery\\TextNode';
/**
* Name of the class used for {@link addComment()}
* @var string
*/
var $childClass_Comment = 'pagelayerQuery\\CommentNode';
/**
* Name of the class used for {@link addContional()}
* @var string
*/
var $childClass_Conditional = 'pagelayerQuery\\ConditionalTagNode';
/**
* Name of the class used for {@link addCDATA()}
* @var string
*/
var $childClass_CDATA = 'pagelayerQuery\\CdataNode';
/**
* Name of the class used for {@link addDoctype()}
* @var string
*/
var $childClass_Doctype = 'pagelayerQuery\\DoctypeNode';
/**
* Name of the class used for {@link addXML()}
* @var string
*/
var $childClass_XML = 'pagelayerQuery\\XmlNode';
/**
* Name of the class used for {@link addASP()}
* @var string
*/
var $childClass_ASP = 'pagelayerQuery\\AspEmbeddedNode';
/**
* Parent node, null if none
* @var DomNode
* @see changeParent()
*/
var $parent = null;
/**
* Attributes of node
* @var array
* @internal array('attribute' => 'value')
* @internal Public for faster access!
* @see getAttribute()
* @see setAttribute()
* @access private
*/
var $attributes = array();
/**
* Namespace info for attributes
* @var array
* @internal array('tag' => array(array('ns', 'tag', 'ns:tag', index)))
* @internal Public for easy outside modifications!
* @see findAttribute()
* @access private
*/
var $attributes_ns = null;
/**
* Array of child nodes
* @var array
* @internal Public for faster access!
* @see childCount()
* @see getChild()
* @see addChild()
* @see deleteChild()
* @access private
*/
var $children = array();
/**
* Full tag name (including namespace)
* @var string
* @see getTagName()
* @see getNamespace()
*/
var $tag = '';
/**
* Namespace info for tag
* @var array
* @internal array('namespace', 'tag')
* @internal Public for easy outside modifications!
* @access private
*/
var $tag_ns = null;
/**
* Is node a self closing node? No closing tag if true.
* @var bool
*/
var $self_close = false;
/**
* If self close, then this will be used to close the tag
* @var string
* @see $self_close
*/
var $self_close_str = ' /';
/**
* Use short tags for attributes? If true, then attributes
* with values equal to the attribute name will not output
* the value, e.g. selected="selected" will be selected.
* @var bool
*/
var $attribute_shorttag = false;
/**
* Function map used for the selector filter
* @var array
* @internal array('root' => 'filter_root') will cause the
* selector to call $this->filter_root at :root
* @access private
*/
var $filter_map = array(
'root' => 'filter_root',
'nth-child' => 'filter_nchild',
'eq' => 'filter_nchild', //jquery (naming) compatibility
'gt' => 'filter_gt',
'lt' => 'filter_lt',
'nth-last-child' => 'filter_nlastchild',
'nth-of-type' => 'filter_ntype',
'nth-last-of-type' => 'filter_nlastype',
'odd' => 'filter_odd',
'even' => 'filter_even',
'every' => 'filter_every',
'first-child' => 'filter_first',
'last-child' => 'filter_last',
'first-of-type' => 'filter_firsttype',
'last-of-type' => 'filter_lasttype',
'only-child' => 'filter_onlychild',
'only-of-type' => 'filter_onlytype',
'empty' => 'filter_empty',
'not-empty' => 'filter_notempty',
'has-text' => 'filter_hastext',
'no-text' => 'filter_notext',
'lang' => 'filter_lang',
'contains' => 'filter_contains',
'has' => 'filter_has',
'not' => 'filter_not',
'element' => 'filter_element',
'text' => 'filter_text',
'comment' => 'filter_comment',
'checked' => 'filter_checked',
'selected' => 'filter_selected',
);
/**
* Class constructor
* @param string|array $tag Name of the tag, or array with taginfo (array(
* 'tag_name' => 'tag',
* 'self_close' => false,
* 'attributes' => array('attribute' => 'value')))
* @param DomNode $parent Parent of node, null if none
*/
function __construct($tag, $parent) {
$this->parent = $parent;
if (is_string($tag)) {
$this->tag = $tag;
} else {
$this->tag = $tag['tag_name'];
$this->self_close = $tag['self_close'];
$this->attributes = $tag['attributes'];
}
}
#php4 PHP4 class constructor compatibility
#function DomNode($tag, $parent) {return $this->__construct($tag, $parent);}
#php4e
/**
* Class destructor
* @access private
*/
function __destruct() {
$this->delete();
}
/**
* Class toString, outputs {@link $tag}
* @return string
* @access private
*/
function __toString() {
return (($this->tag === '~root~') ? $this->toString(true, true, 1) : $this->tag);
}
/**
* Class magic get method, outputs {@link getAttribute()}
* @return string
* @access private
*/
function __get($attribute) {
return $this->getAttribute($attribute);
}
/**
* Class magic set method, performs {@link setAttribute()}
* @access private
*/
function __set($attribute, $value) {
$this->setAttribute($attribute, $value);
}
/**
* Class magic isset method, returns {@link hasAttribute()}
* @return bool
* @access private
*/
function __isset($attribute) {
return $this->hasAttribute($attribute);
}
/**
* Class magic unset method, performs {@link deleteAttribute()}
* @access private
*/
function __unset($attribute) {
return $this->deleteAttribute($attribute);
}
/**
* Class magic invoke method, performs {@link query()}.
* @param string $query The css query to run on the nodes.
* @return \pQuery
*/
function __invoke($query = '*') {
return $this->query($query);
}
/**
* Returns place in document
* @return string
*/
function dumpLocation() {
return (($this->parent) ? (($p = $this->parent->dumpLocation()) ? $p.' > ' : '').$this->tag.'('.$this->typeIndex().')' : '');
}
/**
* Returns all the attributes and their values
* @return string
* @access private
*/
protected function toString_attributes() {
$s = '';
foreach($this->attributes as $a => $v) {
$s .= ' '.$a;
if ((!$this->attribute_shorttag) || ($v !== $a)) {
$quote = '"';//(strpos($v, '"') === false) ? '"' : "'";
$v = str_replace('"', '"', $v);
$s .= '='.$quote.$v.$quote;
}
}
return $s;
}
/**
* Returns the content of the node (child tags and text)
* @param bool $attributes Print attributes of child tags
* @param bool|int $recursive How many sublevels of childtags to print. True for all.
* @param bool $content_only Only print text, false will print tags too.
* @return string
* @access private
*/
protected function toString_content($attributes = true, $recursive = true, $content_only = false) {
$s = '';
foreach($this->children as $c) {
$s .= $c->toString($attributes, $recursive, $content_only);
}
return $s;
}
/**
* Returns the node as string
* @param bool $attributes Print attributes (of child tags)
* @param bool|int $recursive How many sub-levels of child tags to print. True for all.
* @param bool|int $content_only Only print text, false will print tags too.
* @return string
*/
function toString($attributes = true, $recursive = true, $content_only = false) {
if ($content_only) {
if (is_int($content_only)) {
--$content_only;
}
return $this->toString_content($attributes, $recursive, $content_only);
}
$s = '<'.$this->tag;
if ($attributes) {
$s .= $this->toString_attributes();
}
if ($this->self_close) {
$s .= $this->self_close_str.'>';
} else {
$s .= '>';
if($recursive) {
$s .= $this->toString_content($attributes);
}
$s .= '</'.$this->tag.'>';
}
return $s;
}
/**
* Similar to JavaScript outerText, will return full (html formatted) node
* @return string
*/
function getOuterText() {
return $this->toString();
}
/**
* Similar to JavaScript outerText, will replace node (and child nodes) with new text
* @param string $text
* @param HtmlParserBase $parser Null to auto create instance
* @return bool|array True on succeed, array with errors on failure
*/
function setOuterText($text, $parser = null) {
if (trim($text)) {
$index = $this->index();
if ($parser === null) {
$parser = new $this->parserClass();
}
$parser->setDoc($text);
$parser->parse_all();
$parser->root->moveChildren($this->parent, $index);
}
$this->delete();
return (($parser && $parser->errors) ? $parser->errors : true);
}
/**
* Return html code of node
* @internal jquery (naming) compatibility
* @param string|null $value The value to set or null to get the value.
* @see toString()
* @return string
*/
function html($value = null) {
if ($value !== null) {
$this->setInnerText($value);
}
return $this->getInnerText();
}
/**
* Similar to JavaScript innerText, will return (html formatted) content
* @return string
*/
function getInnerText() {
return $this->toString(true, true, 1);
}
/**
* Similar to JavaScript innerText, will replace child nodes with new text
* @param string $text
* @param HtmlParserBase $parser Null to auto create instance
* @return bool|array True on succeed, array with errors on failure
*/
function setInnerText($text, $parser = null) {
$this->clear();
if (trim($text)) {
if ($parser === null) {
$parser = new $this->parserClass();
}
$parser->root =& $this;
$parser->setDoc($text);
$parser->parse_all();
}
return (($parser && $parser->errors) ? $parser->errors : true);
}
/**
* Similar to JavaScript plainText, will return text in node (and subnodes)
* @return string
*/
function getPlainText() {
return preg_replace('`\s+`', ' ', $this->toString(true, true, true));
}
/**
* Return plaintext taking document encoding into account
* @return string
*/
function getPlainTextUTF8() {
$txt = $this->toString(true, true, true);
$enc = $this->getEncoding();
if ($enc !== false) {
$txt = mb_convert_encoding($txt, 'UTF-8', $enc);
}
return preg_replace('`\s+`', ' ', $txt);
}
/**
* Similar to JavaScript plainText, will replace child nodes with new text (literal)
* @param string $text
*/
function setPlainText($text) {
$this->clear();
if (trim($text)) {
$this->addText($text);
}
}
/**
* Delete node from parent and clear node
*/
function delete() {
if (($p = $this->parent) !== null) {
$this->parent = null;
$p->deleteChild($this);
} else {
$this->clear();
}
}
/**
* Detach node from parent
* @param bool $move_children_up Only detach current node and replace it with child nodes
* @internal jquery (naming) compatibility
* @see delete()
*/
function detach($move_children_up = false) {
if (($p = $this->parent) !== null) {
$index = $this->index();
$this->parent = null;
if ($move_children_up) {
$this->moveChildren($p, $index);
}
$p->deleteChild($this, true);
}
}
/**
* Deletes all child nodes from node
*/
function clear() {
foreach($this->children as $c) {
$c->parent = null;
$c->delete();
}
$this->children = array();
}
/**
* Get top parent
* @return DomNode Root, null if node has no parent
*/
function getRoot() {
$r = $this->parent;
$n = ($r === null) ? null : $r->parent;
while ($n !== null) {
$r = $n;
$n = $r->parent;
}
return $r;
}
/**
* Change parent
* @param null|DomNode $to New parent, null if none
* @param false|int $index Add child to parent if not present at index, false to not add, negative to count from end, null to append
*/
#php4
#function changeParent($to, &$index) {
#php4e
#php5
function changeParent($to, &$index = null) {
#php5e
if ($this->parent !== null) {
$this->parent->deleteChild($this, true);
}
$this->parent = $to;
if ($index !== false) {
$new_index = $this->index();
if (!(is_int($new_index) && ($new_index >= 0))) {
$this->parent->addChild($this, $index);
}
}
}
/**
* Find out if node has (a certain) parent
* @param DomNode|string $tag Match against parent, string to match tag, object to fully match node, null to return if node has parent
* @param bool $recursive
* @return bool
*/
function hasParent($tag = null, $recursive = false) {
if ($this->parent !== null) {
if ($tag === null) {
return true;
} elseif (is_string($tag)) {
return (($this->parent->tag === $tag) || ($recursive && $this->parent->hasParent($tag)));
} elseif (is_object($tag)) {
return (($this->parent === $tag) || ($recursive && $this->parent->hasParent($tag)));
}
}
return false;
}
/**
* Find out if node is parent of a certain tag
* @param DomNode|string $tag Match against parent, string to match tag, object to fully match node
* @param bool $recursive
* @return bool
* @see hasParent()
*/
function isParent($tag, $recursive = false) {
return ($this->hasParent($tag, $recursive) === ($tag !== null));
}
/**
* Find out if node is text
* @return bool
*/
function isText() {
return false;
}
/**
* Find out if node is comment
* @return bool
*/
function isComment() {
return false;
}
/**
* Find out if node is text or comment node
* @return bool
*/
function isTextOrComment() {
return false;
}
/**
* Move node to other node
* @param DomNode $to New parent, null if none
* @param int $new_index Add child to parent at index if not present, null to not add, negative to count from end
* @internal Performs {@link changeParent()}
*/
#php4
#function move($to, &$new_index) {
#php4e
#php5
function move($to, &$new_index = -1) {
#php5e
$this->changeParent($to, $new_index);
}
/**
* Move child nodes to other node
* @param DomNode $to New parent, null if none
* @param int $new_index Add child to new node at index if not present, null to not add, negative to count from end
* @param int $start Index from child node where to start wrapping, 0 for first element
* @param int $end Index from child node where to end wrapping, -1 for last element
*/
#php4
#function moveChildren($to, &$new_index, $start = 0, $end = -1) {
#php4e
#php5
function moveChildren($to, &$new_index = -1, $start = 0, $end = -1) {
#php5e
if ($end < 0) {
$end += count($this->children);
}
for ($i = $start; $i <= $end; $i++) {
$this->children[$start]->changeParent($to, $new_index);
}
}
/**
* Index of node in parent
* @param bool $count_all True to count all tags, false to ignore text and comments
* @return int -1 if not found
*/
function index($count_all = true) {
if (!$this->parent) {
return -1;
} elseif ($count_all) {
return $this->parent->findChild($this);
} else{
$index = -1;
//foreach($this->parent->children as &$c) {
// if (!$c->isTextOrComment()) {
// ++$index;
// }
// if ($c === $this) {
// return $index;
// }
//}
foreach(array_keys($this->parent->children) as $k) {
if (!$this->parent->children[$k]->isTextOrComment()) {
++$index;
}
if ($this->parent->children[$k] === $this) {
return $index;
}
}
return -1;
}
}
/**
* Change index of node in parent
* @param int $index New index
*/
function setIndex($index) {
if ($this->parent) {
if ($index > $this->index()) {
--$index;
}
$this->delete();
$this->parent->addChild($this, $index);
}
}
/**
* Index of all similar nodes in parent
* @return int -1 if not found
*/
function typeIndex() {
if (!$this->parent) {
return -1;
} else {
$index = -1;
//foreach($this->parent->children as &$c) {
// if (strcasecmp($this->tag, $c->tag) === 0) {
// ++$index;
// }
// if ($c === $this) {
// return $index;
// }
//}
foreach(array_keys($this->parent->children) as $k) {
if (strcasecmp($this->tag, $this->parent->children[$k]->tag) === 0) {
++$index;
}
if ($this->parent->children[$k] === $this) {
return $index;
}
}
return -1;
}
}
/**
* Calculate indent of node (number of parent tags - 1)
* @return int
*/
function indent() {
return (($this->parent) ? $this->parent->indent() + 1 : -1);
}
/**
* Get sibling node
* @param int $offset Offset from current node
* @return DomNode Null if not found
*/
function getSibling($offset = 1) {
$index = $this->index() + $offset;
if (($index >= 0) && ($index < $this->parent->childCount())) {
return $this->parent->getChild($index);
} else {
return null;
}
}
/**
* Get node next to current
* @param bool $skip_text_comments
* @return DomNode Null if not found
* @see getSibling()
* @see getPreviousSibling()
*/
function getNextSibling($skip_text_comments = true) {
$offset = 1;
while (($n = $this->getSibling($offset)) !== null) {
if ($skip_text_comments && ($n->tag[0] === '~')) {
++$offset;
} else {
break;
}
}
return $n;
}
/**
* Get node previous to current
* @param bool $skip_text_comments
* @return DomNode Null if not found
* @see getSibling()
* @see getNextSibling()
*/
function getPreviousSibling($skip_text_comments = true) {
$offset = -1;
while (($n = $this->getSibling($offset)) !== null) {
if ($skip_text_comments && ($n->tag[0] === '~')) {
--$offset;
} else {
break;
}
}
return $n;
}
/**
* Get namespace of node
* @return string
* @see setNamespace()
*/
function getNamespace() {
if ($this->tag_ns === null) {
$a = explode(':', $this->tag, 2);
if (empty($a[1])) {
$this->tag_ns = array('', $a[0]);
} else {
$this->tag_ns = array($a[0], $a[1]);
}
}
return $this->tag_ns[0];
}
/**
* Set namespace of node
* @param string $ns
* @see getNamespace()
*/
function setNamespace($ns) {
if ($this->getNamespace() !== $ns) {
$this->tag_ns[0] = $ns;
$this->tag = $ns.':'.$this->tag_ns[1];
}
}
/**
* Get tagname of node (without namespace)
* @return string
* @see setTag()
*/
function getTag() {
if ($this->tag_ns === null) {
$this->getNamespace();
}
return $this->tag_ns[1];
}
/**
* Set tag (with or without namespace)
* @param string $tag
* @param bool $with_ns Does $tag include namespace?
* @see getTag()
*/
function setTag($tag, $with_ns = false) {
$with_ns = $with_ns || (strpos($tag, ':') !== false);
if ($with_ns) {
$this->tag = $tag;
$this->tag_ns = null;
} elseif ($this->getTag() !== $tag) {
$this->tag_ns[1] = $tag;
$this->tag = (($this->tag_ns[0]) ? $this->tag_ns[0].':' : '').$tag;
}
}
/**
* Try to determine the encoding of the current tag
* @return string|bool False if encoding could not be found
*/
function getEncoding() {
$root = $this->getRoot();
if ($root !== null) {
if ($enc = $root->select('meta[charset]', 0, true, true)) {
return $enc->getAttribute("charset");
} elseif ($enc = $root->select('"?xml"[encoding]', 0, true, true)) {
return $enc->getAttribute("encoding");
} elseif ($enc = $root->select('meta[content*="charset="]', 0, true, true)) {
$enc = $enc->getAttribute("content");
return substr($enc, strpos($enc, "charset=")+8);
}
}
return false;
}
/**
* Number of children in node
* @param bool $ignore_text_comments Ignore text/comments with calculation
* @return int
*/
function childCount($ignore_text_comments = false) {
if (!$ignore_text_comments) {
return count($this->children);
} else{
$count = 0;
//foreach($this->children as &$c) {
// if (!$c->isTextOrComment()) {
// ++$count;
// }
//}
foreach(array_keys($this->children) as $k) {
if (!$this->children[$k]->isTextOrComment()) {
++$count;
}
}
return $count;
}
}
/**
* Find node in children
* @param DomNode $child
* @return int False if not found
*/
function findChild($child) {
return array_search($child, $this->children, true);
}
/**
* Checks if node has another node as child
* @param DomNode $child
* @return bool
*/
function hasChild($child) {
return ((bool) findChild($child));
}
/**
* Get childnode
* @param int|DomNode $child Index, negative to count from end
* @param bool $ignore_text_comments Ignore text/comments with index calculation
* @return DomNode
*/
function &getChild($child, $ignore_text_comments = false) {
if (!is_int($child)) {
$child = $this->findChild($child);
} elseif ($child < 0) {
$child += $this->childCount($ignore_text_comments);
}
if ($ignore_text_comments) {
$count = 0;
$last = null;
//foreach($this->children as &$c) {
// if (!$c->isTextOrComment()) {
// if ($count++ === $child) {
// return $c;
// }
// $last = $c;
// }
//}
foreach(array_keys($this->children) as $k) {
if (!$this->children[$k]->isTextOrComment()) {
if ($count++ === $child) {
return $this->children[$k];
}
$last = $this->children[$k];
}
}
return (($child > $count) ? $last : null);
} else {
return $this->children[$child];
}
}
/**
* Add child node
* @param string|DomNode $tag Tag name or object
* @param int $offset Position to insert node, negative to count from end, null to append
* @return DomNode Added node
*/
#php4
#function &addChild($tag, &$offset) {
#php4e
#php5
function &addChild($tag, &$offset = null) {
#php5e
if (is_array($tag)) {
$tag = new $this->childClass($tag, $this);
} elseif (is_string($tag)) {
$nodes = $this->createNodes($tag);
$tag = array_shift($nodes);
if ($tag && $tag->parent !== $this) {
$index = false;
$tag->changeParent($this, $index);
}
} elseif (is_object($tag) && $tag->parent !== $this) {
$index = false; //Needs to be passed by ref
$tag->changeParent($this, $index);
}
if (is_int($offset) && ($offset < count($this->children)) && ($offset !== -1)) {
if ($offset < 0) {
$offset += count($this->children);
}
array_splice($this->children, $offset++, 0, array(&$tag));
} else {
$this->children[] =& $tag;
}
return $tag;
}
/**
* First child node
* @param bool $ignore_text_comments Ignore text/comments with index calculation
* @return DomNode
*/
function &firstChild($ignore_text_comments = false) {
return $this->getChild(0, $ignore_text_comments);
}
/**
* Last child node
* @param bool $ignore_text_comments Ignore text/comments with index calculation
* @return DomNode
*/
function &lastChild($ignore_text_comments = false) {
return $this->getChild(-1, $ignore_text_comments);
}
/**
* Insert childnode
* @param string|DomNode $tag Tagname or object
* @param int $offset Position to insert node, negative to count from end, null to append
* @return DomNode Added node
* @see addChild();
*/
function &insertChild($tag, $index) {
return $this->addChild($tag, $index);
}
/**
* Add text node
* @param string $text
* @param int $offset Position to insert node, negative to count from end, null to append
* @return DomNode Added node
* @see addChild();
*/
#php4
#function &addText($text, &$offset) {
#php4e
#php5
function &addText($text, &$offset = null) {
#php5e
return $this->addChild(new $this->childClass_Text($this, $text), $offset);
}
/**
* Add comment node
* @param string $text
* @param int $offset Position to insert node, negative to count from end, null to append
* @return DomNode Added node
* @see addChild();
*/
#php4
#function &addComment($text, &$offset) {
#php4e
#php5
function &addComment($text, &$offset = null) {
#php5e
return $this->addChild(new $this->childClass_Comment($this, $text), $offset);
}
/**
* Add conditional node
* @param string $condition
* @param bool True for <!--[if, false for <![if
* @param int $offset Position to insert node, negative to count from end, null to append
* @return DomNode Added node
* @see addChild();
*/
#php4
#function &addConditional($condition, $hidden = true, &$offset) {
#php4e
#php5
function &addConditional($condition, $hidden = true, &$offset = null) {
#php5e
return $this->addChild(new $this->childClass_Conditional($this, $condition, $hidden), $offset);
}
/**
* Add CDATA node
* @param string $text
* @param int $offset Position to insert node, negative to count from end, null to append
* @return DomNode Added node
* @see addChild();
*/
#php4
#function &addCDATA($text, &$offset) {
#php4e
#php5
function &addCDATA($text, &$offset = null) {
#php5e
return $this->addChild(new $this->childClass_CDATA($this, $text), $offset);
}
/**
* Add doctype node
* @param string $dtd
* @param int $offset Position to insert node, negative to count from end, null to append
* @return DomNode Added node
* @see addChild();
*/
#php4
#function &addDoctype($dtd, &$offset) {
#php4e
#php5
function &addDoctype($dtd, &$offset = null) {
#php5e
return $this->addChild(new $this->childClass_Doctype($this, $dtd), $offset);
}
/**
* Add xml node
* @param string $tag Tag name after "?", e.g. "php" or "xml"
* @param string $text
* @param array $attributes Array of attributes (array('attribute' => 'value'))
* @param int $offset Position to insert node, negative to count from end, null to append
* @return DomNode Added node
* @see addChild();
*/
#php4
#function &addXML($tag = 'xml', $text = '', $attributes = array(), &$offset) {
#php4e
#php5
function &addXML($tag = 'xml', $text = '', $attributes = array(), &$offset = null) {
#php5e
return $this->addChild(new $this->childClass_XML($this, $tag, $text, $attributes), $offset);
}
/**
* Add ASP node
* @param string $tag Tag name after "%"
* @param string $text
* @param array $attributes Array of attributes (array('attribute' => 'value'))
* @param int $offset Position to insert node, negative to count from end, null to append
* @return DomNode Added node
* @see addChild();
*/
#php4
#function &addASP($tag = '', $text = '', $attributes = array(), &$offset) {
#php4e
#php5
function &addASP($tag = '', $text = '', $attributes = array(), &$offset = null) {
#php5e
return $this->addChild(new $this->childClass_ASP($this, $tag, $text, $attributes), $offset);
}
/**
* Delete a child node
* @param int|DomNode $child Child(index) to delete, negative to count from end
* @param bool $soft_delete False to call {@link delete()} from child
*/
function deleteChild($child, $soft_delete = false) {
if (is_object($child)) {
$child = $this->findChild($child);
} elseif ($child < 0) {
$child += count($this->children);
}
if (!$soft_delete) {
$this->children[$child]->delete();
}
unset($this->children[$child]);
//Rebuild indices
$tmp = array();
//foreach($this->children as &$c) {
// $tmp[] =& $c;
//}
foreach(array_keys($this->children) as $k) {
$tmp[] =& $this->children[$k];
}
$this->children = $tmp;
}
/**
* Wrap node
* @param string|DomNode $node Wrapping node, string to create new element node
* @param int $wrap_index Index to insert current node in wrapping node, -1 to append
* @param int $node_index Index to insert wrapping node, null to keep at same position
* @return DomNode Wrapping node
*/
function wrap($node, $wrap_index = -1, $node_index = null) {
if ($node_index === null) {
$node_index = $this->index();
}
if (!is_object($node)) {
$node = $this->parent->addChild($node, $node_index);
} elseif ($node->parent !== $this->parent) {
$node->changeParent($this->parent, $node_index);
}
$this->changeParent($node, $wrap_index);
return $node;
}
/**
* Wrap child nodes
* @param string|DomNode $node Wrapping node, string to create new element node
* @param int $start Index from child node where to start wrapping, 0 for first element
* @param int $end Index from child node where to end wrapping, -1 for last element
* @param int $wrap_index Index to insert in wrapping node, -1 to append
* @param int $node_index Index to insert current node, null to keep at same position
* @return DomNode Wrapping node
*/
function wrapInner($node, $start = 0, $end = -1, $wrap_index = -1, $node_index = null) {
if ($end < 0) {
$end += count($this->children);
}
if ($node_index === null) {
$node_index = $end + 1;
}
if (!is_object($node)) {
$node = $this->addChild($node, $node_index);
} elseif ($node->parent !== $this) {
$node->changeParent($this->parent, $node_index);
}
$this->moveChildren($node, $wrap_index, $start, $end);
return $node;
}
/**
* Number of attributes
* @return int
*/
function attributeCount() {
return count($this->attributes);
}
/**
* Find attribute using namespace, name or both
* @param string|int $attr Negative int to count from end
* @param string $compare "namespace", "name" or "total"
* @param bool $case_sensitive Compare with case sensitivity
* @return array array('ns', 'attr', 'ns:attr', index)
* @access private
*/
protected function findAttribute($attr, $compare = 'total', $case_sensitive = false) {
if (is_int($attr)) {
if ($attr < 0) {
$attr += count($this->attributes);
}
$keys = array_keys($this->attributes);
return $this->findAttribute($keys[$attr], 'total', true);
} else if ($compare === 'total') {
$b = explode(':', $attr, 2);
if ($case_sensitive) {
$t =& $this->attributes;
} else {
$t = array_change_key_case($this->attributes);
$attr = strtolower($attr);
}
if (isset($t[$attr])) {
$index = 0;
foreach($this->attributes as $a => $v) {
if (($v === $t[$attr]) && (strcasecmp($a, $attr) === 0)) {
$attr = $a;
$b = explode(':', $attr, 2);
break;
}
++$index;
}
if (empty($b[1])) {
return array(array('', $b[0], $attr, $index));
} else {
return array(array($b[0], $b[1], $attr, $index));
}
} else {
return false;
}
} else {
if ($this->attributes_ns === null) {
$index = 0;
foreach($this->attributes as $a => $v) {
$b = explode(':', $a, 2);
if (empty($b[1])) {
$this->attributes_ns[$b[0]][] = array('', $b[0], $a, $index);
} else {
$this->attributes_ns[$b[1]][] = array($b[0], $b[1], $a, $index);
}
++$index;
}
}
if ($case_sensitive) {
$t =& $this->attributes_ns;
} else {
$t = array_change_key_case($this->attributes_ns);
$attr = strtolower($attr);
}
if ($compare === 'namespace') {
$res = array();
foreach($t as $ar) {
foreach($ar as $a) {
if ($a[0] === $attr) {
$res[] = $a;
}
}
}
return $res;
} elseif ($compare === 'name') {
return ((isset($t[$attr])) ? $t[$attr] : false);
} else {
trigger_error('Unknown comparison mode');
}
}
}
/**
* Checks if node has attribute
* @param string|int$attr Negative int to count from end
* @param string $compare Find node using "namespace", "name" or "total"
* @param bool $case_sensitive Compare with case sensitivity
* @return bool
*/
function hasAttribute($attr, $compare = 'total', $case_sensitive = false) {
return ((bool) $this->findAttribute($attr, $compare, $case_sensitive));
}
/**
* Gets namespace of attribute(s)
* @param string|int $attr Negative int to count from end
* @param string $compare Find node using "namespace", "name" or "total"
* @param bool $case_sensitive Compare with case sensitivity
* @return string|array False if not found
*/
function getAttributeNS($attr, $compare = 'name', $case_sensitive = false) {
$f = $this->findAttribute($attr, $compare, $case_sensitive);
if (is_array($f) && $f) {
if (count($f) === 1) {
return $this->attributes[$f[0][0]];
} else {
$res = array();
foreach($f as $a) {
$res[] = $a[0];
}
return $res;
}
} else {
return false;
}
}
/**
* Sets namespace of attribute(s)
* @param string|int $attr Negative int to count from end
* @param string $namespace
* @param string $compare Find node using "namespace", "name" or "total"
* @param bool $case_sensitive Compare with case sensitivity
* @return bool
*/
function setAttributeNS($attr, $namespace, $compare = 'name', $case_sensitive = false) {
$f = $this->findAttribute($attr, $compare, $case_sensitive);
if (is_array($f) && $f) {
if ($namespace) {
$namespace .= ':';
}
foreach($f as $a) {
$val = $this->attributes[$a[2]];
unset($this->attributes[$a[2]]);
$this->attributes[$namespace.$a[1]] = $val;
}
$this->attributes_ns = null;
return true;
} else {
return false;
}
}
/**
* Gets value(s) of attribute(s)
* @param string|int $attr Negative int to count from end
* @param string $compare Find node using "namespace", "name" or "total"
* @param bool $case_sensitive Compare with case sensitivity
* @return string|array
*/
function getAttribute($attr, $compare = 'total', $case_sensitive = false) {
$f = $this->findAttribute($attr, $compare, $case_sensitive);
if (is_array($f) && $f){
if (count($f) === 1) {
return $this->attributes[$f[0][2]];
} else {
$res = array();
foreach($f as $a) {
$res[] = $this->attributes[$a[2]];
}
return $res;
}
} else {
return null;
}
}
/**
* Sets value(s) of attribute(s)
* @param string|int $attr Negative int to count from end
* @param string $compare Find node using "namespace", "name" or "total"
* @param bool $case_sensitive Compare with case sensitivity
*/
function setAttribute($attr, $val, $compare = 'total', $case_sensitive = false) {
if ($val === null) {
return $this->deleteAttribute($attr, $compare, $case_sensitive);
}
$f = $this->findAttribute($attr, $compare, $case_sensitive);
if (is_array($f) && $f) {
foreach($f as $a) {
$this->attributes[$a[2]] = (string) $val;
}
} else {
$this->attributes[$attr] = (string) $val;
}
}
/**
* Add new attribute
* @param string $attr
* @param string $val
*/
function addAttribute($attr, $val) {
$this->setAttribute($attr, $val, 'total', true);
}
/**
* Delete attribute(s)
* @param string|int $attr Negative int to count from end
* @param string $compare Find node using "namespace", "name" or "total"
* @param bool $case_sensitive Compare with case sensitivity
*/
function deleteAttribute($attr, $compare = 'total', $case_sensitive = false) {
$f = $this->findAttribute($attr, $compare, $case_sensitive);
if (is_array($f) && $f) {
foreach($f as $a) {
unset($this->attributes[$a[2]]);
if ($this->attributes_ns !== null) {
unset($this->attributes_ns[$a[1]]);
}
}
}
}
/**
* Determine if node has a certain class
* @param string $className
* @return bool
*/
function hasClass($className) {
return ($className && preg_match('`\b'.preg_quote($className).'\b`si', $this->class));
}
/**
* Add new class(es)
* @param string|array $className
*/
function addClass($className) {
if (!is_array($className)) {
$className = array($className);
}
$class = isset($this->class) ? $this->class : '';
foreach ($className as $c) {
if (!(preg_match('`\b'.preg_quote($c).'\b`si', $class) > 0)) {
$class .= ' '.$c;
}
}
$this->class = trim($class);
}
/**
* Remove clas(ses)
* @param string|array $className
*/
function removeClass($className) {
if (!is_array($className)) {
$className = array($className);
}
$class = $this->class;
foreach ($className as $c) {
$class = preg_replace('`\b'.preg_quote($c).'\b`si', '', $class);
}
if ($class) {
$this->class = $class;
} else {
unset($this->class);
}
}
/**
* Finds children using a callback function
* @param callable $callback Function($node) that returns a bool
* @param bool|int $recursive Check recursively
* @param bool $check_self Include this node in search?
* @return array
*/
function getChildrenByCallback($callback, $recursive = true, $check_self = false) {
$count = $this->childCount();
if ($check_self && $callback($this)) {
$res = array($this);
} else {
$res = array();
}
if ($count > 0) {
if (is_int($recursive)) {
$recursive = (($recursive > 1) ? $recursive - 1 : false);
}
for ($i = 0; $i < $count; $i++) {
if ($callback($this->children[$i])) {
$res[] = $this->children[$i];
}
if ($recursive) {
$res = array_merge($res, $this->children[$i]->getChildrenByCallback($callback, $recursive));
}
}
}
return $res;
}
/**
* Finds children using the {$link match()} function
* @param $conditions See {$link match()}
* @param $custom_filters See {$link match()}
* @param bool|int $recursive Check recursively
* @param bool $check_self Include this node in search?
* @return array
*/
function getChildrenByMatch($conditions, $recursive = true, $check_self = false, $custom_filters = array()) {
$count = $this->childCount();
if ($check_self && $this->match($conditions, true, $custom_filters)) {
$res = array($this);
} else {
$res = array();
}
if ($count > 0) {
if (is_int($recursive)) {
$recursive = (($recursive > 1) ? $recursive - 1 : false);
}
for ($i = 0; $i < $count; $i++) {
if ($this->children[$i]->match($conditions, true, $custom_filters)) {
$res[] = $this->children[$i];
}
if ($recursive) {
$res = array_merge($res, $this->children[$i]->getChildrenByMatch($conditions, $recursive, false, $custom_filters));
}
}
}
return $res;
}
/**
* Checks if tag matches certain conditions
* @param array $tags array('tag1', 'tag2') or array(array(
* 'tag' => 'tag1',
* 'operator' => 'or'/'and',
* 'compare' => 'total'/'namespace'/'name',
* 'case_sensitive' => true))
* @return bool
* @internal Used by selector class
* @see match()
* @access private
*/
protected function match_tags($tags) {
$res = false;
foreach($tags as $tag => $match) {
if (!is_array($match)) {
$match = array(
'match' => $match,
'operator' => 'or',
'compare' => 'total',
'case_sensitive' => false
);
} else {
if (is_int($tag)) {
$tag = $match['tag'];
}
if (!isset($match['match'])) {
$match['match'] = true;
}
if (!isset($match['operator'])) {
$match['operator'] = 'or';
}
if (!isset($match['compare'])) {
$match['compare'] = 'total';
}
if (!isset($match['case_sensitive'])) {
$match['case_sensitive'] = false;
}
}
if (($match['operator'] === 'and') && (!$res)) {
return false;
} elseif (!($res && ($match['operator'] === 'or'))) {
if ($match['compare'] === 'total') {
$a = $this->tag;
} elseif ($match['compare'] === 'namespace') {
$a = $this->getNamespace();
} elseif ($match['compare'] === 'name') {
$a = $this->getTag();
}
if ($match['case_sensitive']) {
$res = (($a === $tag) === $match['match']);
} else {
$res = ((strcasecmp($a, $tag) === 0) === $match['match']);
}
}
}
return $res;
}
/**
* Checks if attributes match certain conditions
* @param array $attributes array('attr' => 'val') or array(array(
* 'operator_value' => 'equals'/'='/'contains_regex'/etc
* 'attribute' => 'attr',
* 'value' => 'val',
* 'match' => true,
* 'operator_result' => 'or'/'and',
* 'compare' => 'total'/'namespace'/'name',
* 'case_sensitive' => true))
* @return bool
* @internal Used by selector class
* @see match()
* @access private
*/
protected function match_attributes($attributes) {
$res = false;
foreach($attributes as $attribute => $match) {
if (!is_array($match)) {
$match = array(
'operator_value' => 'equals',
'value' => $match,
'match' => true,
'operator_result' => 'or',
'compare' => 'total',
'case_sensitive' => false
);
} else {
if (is_int($attribute)) {
$attribute = $match['attribute'];
}
if (!isset($match['match'])) {
$match['match'] = true;
}
if (!isset($match['operator_result'])) {
$match['operator_result'] = 'or';
}
if (!isset($match['compare'])) {
$match['compare'] = 'total';
}
if (!isset($match['case_sensitive'])) {
$match['case_sensitive'] = false;
}
}
if (is_string($match['value']) && (!$match['case_sensitive'])) {
$match['value'] = strtolower($match['value']);
}
if (($match['operator_result'] === 'and') && (!$res)) {
return false;
} elseif (!($res && ($match['operator_result'] === 'or'))) {
$possibles = $this->findAttribute($attribute, $match['compare'], $match['case_sensitive']);
$has = (is_array($possibles) && $possibles);
$res = (($match['value'] === $has) || (($match['match'] === false) && ($has === $match['match'])));
if ((!$res) && $has && is_string($match['value'])) {
foreach($possibles as $a) {
$val = $this->attributes[$a[2]];
if (is_string($val) && (!$match['case_sensitive'])) {
$val = strtolower($val);
}
switch($match['operator_value']) {
case '%=':
case 'contains_regex':
$res = ((preg_match('`'.$match['value'].'`s', $val) > 0) === $match['match']);
if ($res) break 1; else break 2;
case '|=':
case 'contains_prefix':
$res = ((preg_match('`\b'.preg_quote($match['value']).'[\-\s]`s', $val) > 0) === $match['match']);
if ($res) break 1; else break 2;
case '~=':
case 'contains_word':
$res = ((preg_match('`\s'.preg_quote($match['value']).'\s`s', " $val ") > 0) === $match['match']);
if ($res) break 1; else break 2;
case '*=':
case 'contains':
$res = ((strpos($val, $match['value']) !== false) === $match['match']);
if ($res) break 1; else break 2;
case '$=':
case 'ends_with':
$res = ((substr($val, -strlen($match['value'])) === $match['value']) === $match['match']);
if ($res) break 1; else break 2;
case '^=':
case 'starts_with':
$res = ((substr($val, 0, strlen($match['value'])) === $match['value']) === $match['match']);
if ($res) break 1; else break 2;
case '!=':
case 'not_equal':
$res = (($val !== $match['value']) === $match['match']);
if ($res) break 1; else break 2;
case '=':
case 'equals':
$res = (($val === $match['value']) === $match['match']);
if ($res) break 1; else break 2;
case '>=':
case 'bigger_than':
$res = (($val >= $match['value']) === $match['match']);
if ($res) break 1; else break 2;
case '<=':
case 'smaller_than':
$res = (($val >= $match['value']) === $match['match']);
if ($res) break 1; else break 2;
default:
trigger_error('Unknown operator "'.$match['operator_value'].'" to match attributes!');
return false;
}
}
}
}
}
return $res;
}
/**
* Checks if node matches certain filters
* @param array $tags array(array(
* 'filter' => 'last-child',
* 'params' => '123'))
* @param array $custom_filters Custom map next to {@link $filter_map}
* @return bool
* @internal Used by selector class
* @see match()
* @access private
*/
protected function match_filters($conditions, $custom_filters = array()) {
foreach($conditions as $c) {
$c['filter'] = strtolower($c['filter']);
if (isset($this->filter_map[$c['filter']])) {
if (!$this->{$this->filter_map[$c['filter']]}($c['params'])) {
return false;
}
} elseif (isset($custom_filters[$c['filter']])) {
if (!call_user_func($custom_filters[$c['filter']], $this, $c['params'])) {
return false;
}
} else {
trigger_error('Unknown filter "'.$c['filter'].'"!');
return false;
}
}
return true;
}
/**
* Checks if node matches certain conditions
* @param array $tags array('tags' => array(tag_conditions), 'attributes' => array(attr_conditions), 'filters' => array(filter_conditions))
* @param array $match Should conditions evaluate to true?
* @param array $custom_filters Custom map next to {@link $filter_map}
* @return bool
* @internal Used by selector class
* @see match_tags();
* @see match_attributes();
* @see match_filters();
* @access private
*/
function match($conditions, $match = true, $custom_filters = array()) {
$t = isset($conditions['tags']);
$a = isset($conditions['attributes']);
$f = isset($conditions['filters']);
if (!($t || $a || $f)) {
if (is_array($conditions) && $conditions) {
foreach($conditions as $c) {
if ($this->match($c, $match)) {
return true;
}
}
}
return false;
} else {
if (($t && (!$this->match_tags($conditions['tags']))) === $match) {
return false;
}
if (($a && (!$this->match_attributes($conditions['attributes']))) === $match) {
return false;
}
if (($f && (!$this->match_filters($conditions['filters'], $custom_filters))) === $match) {
return false;
}
return true;
}
}
/**
* Finds children that match a certain attribute
* @param string $attribute
* @param string $value
* @param string $mode Compare mode, "equals", "|=", "contains_regex", etc.
* @param string $compare "total"/"namespace"/"name"
* @param bool|int $recursive
* @return array
*/
function getChildrenByAttribute($attribute, $value, $mode = 'equals', $compare = 'total', $recursive = true) {
if ($this->childCount() < 1) {
return array();
}
$mode = explode(' ', strtolower($mode));
$match = ((isset($mode[1]) && ($mode[1] === 'not')) ? 'false' : 'true');
return $this->getChildrenByMatch(
array(
'attributes' => array(
$attribute => array(
'operator_value' => $mode[0],
'value' => $value,
'match' => $match,
'compare' => $compare
)
)
),
$recursive
);
}
/**
* Finds children that match a certain tag
* @param string $tag
* @param string $compare "total"/"namespace"/"name"
* @param bool|int $recursive
* @return array
*/
function getChildrenByTag($tag, $compare = 'total', $recursive = true) {
if ($this->childCount() < 1) {
return array();
}
$tag = explode(' ', strtolower($tag));
$match = ((isset($tag[1]) && ($tag[1] === 'not')) ? 'false' : 'true');
return $this->getChildrenByMatch(
array(
'tags' => array(
$tag[0] => array(
'match' => $match,
'compare' => $compare
)
)
),
$recursive
);
}
/**
* Finds all children using ID attribute
* @param string $id
* @param bool|int $recursive
* @return array
*/
function getChildrenByID($id, $recursive = true) {
return $this->getChildrenByAttribute('id', $id, 'equals', 'total', $recursive);
}
/**
* Finds all children using class attribute
* @param string $class
* @param bool|int $recursive
* @return array
*/
function getChildrenByClass($class, $recursive = true) {
return $this->getChildrenByAttribute('class', $class, 'equals', 'total', $recursive);
}
/**
* Finds all children using name attribute
* @param string $name
* @param bool|int $recursive
* @return array
*/
function getChildrenByName($name, $recursive = true) {
return $this->getChildrenByAttribute('name', $name, 'equals', 'total', $recursive);
}
/**
* Performs a css query on the node.
* @param string $query
* @return IQuery Returns the matching nodes from the query.
*/
public function query($query = '*') {
$select = $this->select($query);
$result = new \pagelayerQuery((array)$select);
return $result;
}
/**
* Performs css query on node
* @param string $query
* @param int|bool $index True to return node instead of array if only 1 match,
* false to return array, int to return match at index, negative int to count from end
* @param bool|int $recursive
* @param bool $check_self Include this node in search or only search child nodes
* @return DomNode[]|DomNode Returns an array of matching {@link DomNode} objects
* or a single {@link DomNode} if `$index` is not false.
*/
function select($query = '*', $index = false, $recursive = true, $check_self = false) {
$s = new $this->selectClass($this, $query, $check_self, $recursive);
$res = $s->result;
unset($s);
if (is_array($res) && ($index === true) && (count($res) === 1)) {
return $res[0];
} elseif (is_int($index) && is_array($res)) {
if ($index < 0) {
$index += count($res);
}
return ($index < count($res)) ? $res[$index] : null;
} else {
return $res;
}
}
/**
* Checks if node matches css query filter ":root"
* @return bool
* @see match()
* @access private
*/
protected function filter_root() {
return (strtolower($this->tag) === 'html');
}
/**
* Checks if node matches css query filter ":nth-child(n)"
* @param string $n 1-based index
* @return bool
* @see match()
* @access private
*/
protected function filter_nchild($n) {
return ($this->index(false)+1 === (int) $n);
}
/**
* Checks if node matches css query filter ":gt(n)"
* @param string $n 0-based index
* @return bool
* @see match()
* @access private
*/
protected function filter_gt($n) {
return ($this->index(false) > (int) $n);
}
/**
* Checks if node matches css query filter ":lt(n)"
* @param string $n 0-based index
* @return bool
* @see match()
* @access private
*/
protected function filter_lt($n) {
return ($this->index(false) < (int) $n);
}
/**
* Checks if node matches css query filter ":nth-last-child(n)"
* @param string $n 1-based index
* @return bool
* @see match()
* @access private
*/
protected function filter_nlastchild($n) {
if ($this->parent === null) {
return false;
} else {
return ($this->parent->childCount(true) - $this->index(false) === (int) $n);
}
}
/**
* Checks if node matches css query filter ":nth-of-type(n)"
* @param string $n 1-based index
* @return bool
* @see match()
* @access private
*/
protected function filter_ntype($n) {
return ($this->typeIndex()+1 === (int) $n);
}
/**
* Checks if node matches css query filter ":nth-last-of-type(n)"
* @param string $n 1-based index
* @return bool
* @see match()
* @access private
*/
protected function filter_nlastype($n) {
if ($this->parent === null) {
return false;
} else {
return (count($this->parent->getChildrenByTag($this->tag, 'total', false)) - $this->typeIndex() === (int) $n);
}
}
/**
* Checks if node matches css query filter ":odd"
* @return bool
* @see match()
* @access private
*/
protected function filter_odd() {
return (($this->index(false) & 1) === 1);
}
/**
* Checks if node matches css query filter ":even"
* @return bool
* @see match()
* @access private
*/
protected function filter_even() {
return (($this->index(false) & 1) === 0);
}
/**
* Checks if node matches css query filter ":every(n)"
* @return bool
* @see match()
* @access private
*/
protected function filter_every($n) {
return (($this->index(false) % (int) $n) === 0);
}
/**
* Checks if node matches css query filter ":first"
* @return bool
* @see match()
* @access private
*/
protected function filter_first() {
return ($this->index(false) === 0);
}
/**
* Checks if node matches css query filter ":last"
* @return bool
* @see match()
* @access private
*/
protected function filter_last() {
if ($this->parent === null) {
return false;
} else {
return ($this->parent->childCount(true) - 1 === $this->index(false));
}
}
/**
* Checks if node matches css query filter ":first-of-type"
* @return bool
* @see match()
* @access private
*/
protected function filter_firsttype() {
return ($this->typeIndex() === 0);
}
/**
* Checks if node matches css query filter ":last-of-type"
* @return bool
* @see match()
* @access private
*/
protected function filter_lasttype() {
if ($this->parent === null) {
return false;
} else {
return (count($this->parent->getChildrenByTag($this->tag, 'total', false)) - 1 === $this->typeIndex());
}
}
/**
* Checks if node matches css query filter ":only-child"
* @return bool
* @see match()
* @access private
*/
protected function filter_onlychild() {
if ($this->parent === null) {
return false;
} else {
return ($this->parent->childCount(true) === 1);
}
}
/**
* Checks if node matches css query filter ":only-of-type"
* @return bool
* @see match()
* @access private
*/
protected function filter_onlytype() {
if ($this->parent === null) {
return false;
} else {
return (count($this->parent->getChildrenByTag($this->tag, 'total', false)) === 1);
}
}
/**
* Checks if node matches css query filter ":empty"
* @return bool
* @see match()
* @access private
*/
protected function filter_empty() {
return ($this->childCount() === 0);
}
/**
* Checks if node matches css query filter ":not-empty"
* @return bool
* @see match()
* @access private
*/
protected function filter_notempty() {
return ($this->childCount() !== 0);
}
/**
* Checks if node matches css query filter ":has-text"
* @return bool
* @see match()
* @access private
*/
protected function filter_hastext() {
return ($this->getPlainText() !== '');
}
/**
* Checks if node matches css query filter ":no-text"
* @return bool
* @see match()
* @access private
*/
protected function filter_notext() {
return ($this->getPlainText() === '');
}
/**
* Checks if node matches css query filter ":lang(s)"
* @param string $lang
* @return bool
* @see match()
* @access private
*/
protected function filter_lang($lang) {
return ($this->lang === $lang);
}
/**
* Checks if node matches css query filter ":contains(s)"
* @param string $text
* @return bool
* @see match()
* @access private
*/
protected function filter_contains($text) {
return (strpos($this->getPlainTextUTF8(), $text) !== false);
}
/**
* Checks if node matches css query filter ":has(s)"
* @param string $selector
* @return bool
* @see match()
* @access private
*/
protected function filter_has($selector) {
$s = $this->select((string) $selector, false);
return (is_array($s) && (count($s) > 0));
}
/**
* Checks if node matches css query filter ":not(s)"
* @param string $selector
* @return bool
* @see match()
* @access private
*/
protected function filter_not($selector) {
$s = $this->select((string) $selector, false, true, true);
return ((!is_array($s)) || (array_search($this, $s, true) === false));
}
/**
* Checks if node matches css query filter ":element"
* @return bool
* @see match()
* @access private
*/
protected function filter_element() {
return true;
}
/**
* Checks if node matches css query filter ":text"
* @return bool
* @see match()
* @access private
*/
protected function filter_text() {
return false;
}
/**
* Checks if a node matches css query filter ":checked"
* @return bool
* @see match()
*/
protected function filter_checked() {
$attr = $this->getAttribute('checked');
if (is_array($attr))
$attr = reset($attr);
return strcasecmp($attr, 'checked') === 0;
}
/**
* Checks if node matches css query filter ":comment"
* @return bool
* @see match()
* @access private
*/
protected function filter_comment() {
return false;
}
/**
* Checks if a node matches css query filter ":selected"
* @return bool
* @see match()
*/
protected function filter_selected() {
$attr = $this->getAttribute('selected');
if (is_array($attr))
$attr = reset($attr);
return strcasecmp($attr, 'selected') === 0;
}
public function after($content) {
$offset = $this->index() + 1;
$parent = $this->parent;
$nodes = $this->createNodes($content);
foreach ($nodes as $node) {
$node->changeParent($parent, $offset);
}
return $this;
}
/**
* Create a {@link DomNode} from its string representation.
* @param string|DomNode $content
* @return DomNode
*/
protected function createNode($content) {
$nodes = $this->createNodes($content);
return reset($nodes);
}
/**
* Create an array of {@link DomNode} objects from their string representation.
* @param string|DomNode $content
* @return DomNode[]
*/
protected function createNodes($content) {
if (is_string($content)) {
if (strpos($content, ' ') === false) {
$nodes = array(new $this->childClass($content, $this));
} else {
$node = new $this->parserClass($content);
$nodes = $node->root->children;
}
} else {
$nodes = (array)$content;
}
return $nodes;
}
public function append($content) {
$nodes = $this->createNodes($content);
foreach ($nodes as $node) {
$node->changeParent($this);
}
return $this;
}
public function attr($name, $value = null) {
if ($value === null)
return $this->getAttribute($name);
$this->setAttribute($name, $value);
return $this;
}
public function before($content) {
$offset = $this->index();
$parent = $this->parent;
$nodes = $this->createNodes($content);
foreach ($nodes as $node) {
$node->changeParent($parent, $offset);
}
return $this;
}
public function count(): int {
return 1;
}
// public function css($name, $value = null) {
//
// }
public function prepend($content = null) {
$offset = 0;
$parent = $this;
$nodes = $this->createNodes($content);
foreach ($nodes as $node) {
$node->changeParent($parent, $offset);
}
return $this;
}
public function prop($name, $value = null) {
switch (strtolower($name)) {
case 'checked':
case 'disabled':
case 'selected':
if ($value !== null) {
if ($value) {
$this->attr($name, $name);
} else {
$this->removeAttr($name);
}
return $this;
}
return $this->attr($name) == $name;
case 'tagname':
return $this->tagName($value);
}
// The property is not supported, degrade gracefully
if ($value === null)
return $this;
else
return null;
}
public function remove($selector = null) {
if ($selector == null) {
$this->delete();
} else {
$nodes = (array)$this->select($selector);
foreach ($nodes as $node) {
$node->delete();
}
}
}
public function removeAttr($name) {
$this->deleteAttribute($name);
return $this;
}
function replaceWith($content) {
$node_index = $this->index();
// Add the new node.
$node = $this->createNode($content);
$node->changeParent($this->parent, $node_index);
// Remove this node.
$this->remove();
return $node;
}
/**
* @param type $value
* @return string|DomNode
*/
public function tagName($value = null) {
if ($value !== null) {
$this->setTag($value);
return $this;
}
return $this->getTag();
}
public function text($value = null) {
if ($value === null)
return $this->getPlainText();
$this->setPlainText($value);
return $this;
}
public function toggleClass($classname, $switch = null) {
if ($switch === true) {
$this->addClass($classname);
} elseif ($switch === false) {
$this->removeClass($classname);
} else {
if ($this->hasClass($classname))
$this->removeClass($classname);
else
$this->addClass($classname);
}
return $this;
}
public function unwrap() {
$this->parent->detach(true);
return $this;
}
public function val($value = null) {
switch (strtolower($this->tag)) {
case 'select':
if ($value === null) {
// Return the value of a selected child.
return $this->query('option:selected')->attr('value');
} else {
// Select the option with the right value and deselect the others.
foreach ($this->query('option') as $option) {
if ($option->attr('value') == $value) {
$option->attr('selected', 'selected');
} else {
$option->removeAttr('selected');
}
}
return $this;
}
case 'textarea':
if ($value === null) {
// Return the contents of the textarea.
return $this->getInnerText();
} else {
// Set the contents of the textarea.
$this->setInnerText($value);
return $this;
}
case 'input':
switch (strtolower($this->getAttribute('type'))) {
case 'checkbox':
if ($value === null)
return $this->prop('checked') ? $this->getAttribute('value') : null;
else {
if (!$value) {
$this->deleteAttribute('checked');
} else {
$this->setAttribute('value', $value);
$this->setAttribute('checked', 'checked');
}
return $this;
}
}
}
// Other node types can just get/set the value attribute.
if ($value !== null) {
$this->setAttribute('value', $value);
return $this;
}
return $this->getAttribute('value');
}
}
/**
* Node subclass for text
*/
class TextNode extends DomNode {
#php4 Compatibility with PHP4, this gets changed to a regular var in release tool
#static $NODE_TYPE = self::NODE_TEXT;
#php4e
#php5
const NODE_TYPE = self::NODE_TEXT;
#php5e
var $tag = '~text~';
/**
* @var string
*/
var $text = '';
/**
* Class constructor
* @param DomNode $parent
* @param string $text
*/
function __construct($parent, $text = '') {
$this->parent = $parent;
$this->text = $text;
}
#php4 PHP4 class constructor compatibility
#function TextNode($parent, $text = '') {return $this->__construct($parent, $text);}
#php4e
function isText() {return true;}
function isTextOrComment() {return true;}
protected function filter_element() {return false;}
protected function filter_text() {return true;}
function toString_attributes() {return '';}
function toString_content($attributes = true, $recursive = true, $content_only = false) {return $this->text;}
function toString($attributes = true, $recursive = true, $content_only = false) {return $this->text;}
/**
* {@inheritdoc}
*/
public function text($value = null) {
if ($value !== null) {
$this->text = $value;
return $this;
}
return $this->text;
}
/**
* {@inheritdoc}
*/
public function html($value = null) {
if ($value !== null) {
$this->text = $value;
return $this;
}
return $this->text;
}
}
/**
* Node subclass for comments
*/
class CommentNode extends DomNode {
#php4 Compatibility with PHP4, this gets changed to a regular var in release tool
#static $NODE_TYPE = self::NODE_COMMENT;
#php4e
#php5
const NODE_TYPE = self::NODE_COMMENT;
#php5e
var $tag = '~comment~';
/**
* @var string
*/
var $text = '';
/**
* Class constructor
* @param DomNode $parent
* @param string $text
*/
function __construct($parent, $text = '') {
$this->parent = $parent;
$this->text = $text;
}
#php4 PHP4 class constructor compatibility
#function CommentNode($parent, $text = '') {return $this->__construct($parent, $text);}
#php4e
function isComment() {return true;}
function isTextOrComment() {return true;}
protected function filter_element() {return false;}
protected function filter_comment() {return true;}
function toString_attributes() {return '';}
function toString_content($attributes = true, $recursive = true, $content_only = false) {return $this->text;}
function toString($attributes = true, $recursive = true, $content_only = false) {return '<!--'.$this->text.'-->';}
}
/**
* Node subclass for conditional tags
*/
class ConditionalTagNode extends DomNode {
#php4 Compatibility with PHP4, this gets changed to a regular var in release tool
#static $NODE_TYPE = self::NODE_CONDITIONAL;
#php4e
#php5
const NODE_TYPE = self::NODE_CONDITIONAL;
#php5e
var $tag = '~conditional~';
/**
* @var string
*/
var $condition = '';
/**
* Class constructor
* @param DomNode $parent
* @param string $condition e.g. "if IE"
* @param bool $hidden <!--[if if true, <![if if false
*/
function __construct($parent, $condition = '', $hidden = true) {
$this->parent = $parent;
$this->hidden = $hidden;
$this->condition = $condition;
}
#php4 PHP4 class constructor compatibility
#function ConditionalTagNode($parent, $condition = '', $hidden = true) {return $this->__construct($parent, $condition, $hidden);}
#php4e
protected function filter_element() {return false;}
function toString_attributes() {return '';}
function toString($attributes = true, $recursive = true, $content_only = false) {
if ($content_only) {
if (is_int($content_only)) {
--$content_only;
}
return $this->toString_content($attributes, $recursive, $content_only);
}
$s = '<!'.(($this->hidden) ? '--' : '').'['.$this->condition.']>';
if($recursive) {
$s .= $this->toString_content($attributes);
}
$s .= '<![endif]'.(($this->hidden) ? '--' : '').'>';
return $s;
}
}
/**
* Node subclass for CDATA tags
*/
class CdataNode extends DomNode {
#php4 Compatibility with PHP4, this gets changed to a regular var in release tool
#static $NODE_TYPE = self::NODE_CDATA;
#php4e
#php5
const NODE_TYPE = self::NODE_CDATA;
#php5e
var $tag = '~cdata~';
/**
* @var string
*/
var $text = '';
/**
* Class constructor
* @param DomNode $parent
* @param string $text
*/
function __construct($parent, $text = '') {
$this->parent = $parent;
$this->text = $text;
}
#php4 PHP4 class constructor compatibility
#function CdataNode($parent, $text = '') {return $this->__construct($parent, $text);}
#php4e
protected function filter_element() {return false;}
function toString_attributes() {return '';}
function toString_content($attributes = true, $recursive = true, $content_only = false) {return $this->text;}
function toString($attributes = true, $recursive = true, $content_only = false) {return '<![CDATA['.$this->text.']]>';}
}
/**
* Node subclass for doctype tags
*/
class DoctypeNode extends DomNode {
#php4 Compatibility with PHP4, this gets changed to a regular var in release tool
#static $NODE_TYPE = self::NODE_DOCTYPE;
#php4e
#php5
const NODE_TYPE = self::NODE_DOCTYPE;
#php5e
var $tag = '!DOCTYPE';
/**
* @var string
*/
var $dtd = '';
/**
* Class constructor
* @param DomNode $parent
* @param string $dtd
*/
function __construct($parent, $dtd = '') {
$this->parent = $parent;
$this->dtd = $dtd;
}
#php4 PHP4 class constructor compatibility
#function DoctypeNode($parent, $dtd = '') {return $this->__construct($parent, $dtd);}
#php4e
protected function filter_element() {return false;}
function toString_attributes() {return '';}
function toString_content($attributes = true, $recursive = true, $content_only = false) {return $this->text;}
function toString($attributes = true, $recursive = true, $content_only = false) {return '<'.$this->tag.' '.$this->dtd.'>';}
}
/**
* Node subclass for embedded tags like xml, php and asp
*/
class EmbeddedNode extends DomNode {
/**
* @var string
* @internal specific char for tags, like ? for php and % for asp
* @access private
*/
var $tag_char = '';
/**
* @var string
*/
var $text = '';
/**
* Class constructor
* @param DomNode $parent
* @param string $tag_char {@link $tag_char}
* @param string $tag {@link $tag}
* @param string $text
* @param array $attributes array('attr' => 'val')
*/
function __construct($parent, $tag_char = '', $tag = '', $text = '', $attributes = array()) {
$this->parent = $parent;
$this->tag_char = $tag_char;
if ($tag[0] !== $this->tag_char) {
$tag = $this->tag_char.$tag;
}
$this->tag = $tag;
$this->text = $text;
$this->attributes = $attributes;
$this->self_close_str = $tag_char;
}
#php4 PHP4 class constructor compatibility
#function EmbeddedNode($parent, $tag_char = '', $tag = '', $text = '', $attributes = array()) {return $this->__construct($parent, $tag_char, $tag, $text, $attributes);}
#php4e
protected function filter_element() {return false;}
function toString($attributes = true, $recursive = true, $content_only = false) {
$s = '<'.$this->tag;
if ($attributes) {
$s .= $this->toString_attributes();
}
$s .= $this->text.$this->self_close_str.'>';
return $s;
}
}
/**
* Node subclass for "?" tags, like php and xml
*/
class XmlNode extends EmbeddedNode {
#php4 Compatibility with PHP4, this gets changed to a regular var in release tool
#static $NODE_TYPE = self::NODE_XML;
#php4e
#php5
const NODE_TYPE = self::NODE_XML;
#php5e
/**
* Class constructor
* @param DomNode $parent
* @param string $tag {@link $tag}
* @param string $text
* @param array $attributes array('attr' => 'val')
*/
function __construct($parent, $tag = 'xml', $text = '', $attributes = array()) {
return parent::__construct($parent, '?', $tag, $text, $attributes);
}
#php4 PHP4 class constructor compatibility
#function XmlNode($parent, $tag = 'xml', $text = '', $attributes = array()) {return $this->__construct($parent, $tag, $text, $attributes);}
#php4e
}
/**
* Node subclass for asp tags
*/
class AspEmbeddedNode extends EmbeddedNode {
#php4 Compatibility with PHP4, this gets changed to a regular var in release tool
#static $NODE_TYPE = self::NODE_ASP;
#php4e
#php5
const NODE_TYPE = self::NODE_ASP;
#php5e
/**
* Class constructor
* @param DomNode $parent
* @param string $tag {@link $tag}
* @param string $text
* @param array $attributes array('attr' => 'val')
*/
function __construct($parent, $tag = '', $text = '', $attributes = array()) {
return parent::__construct($parent, '%', $tag, $text, $attributes);
}
#php4 PHP4 class constructor compatibility
#function AspEmbeddedNode($parent, $tag = '', $text = '', $attributes = array()) {return $this->__construct($parent, $tag, $text, $attributes);}
#php4e
}
?>