<?php /** * @package Warp Theme Framework * @author YOOtheme http://www.yootheme.com * @copyright Copyright (C) YOOtheme GmbH * @license http://www.gnu.org/licenses/gpl.html GNU/GPL */ namespace Warp\Dom; /** * DOM element class with extended attributes and functions. */ class Element extends \DOMElement { public function first($query) { if ($matches = $this->find($query)) { if ($matches->length) { return $matches->item(0); } } return null; } public function find($query) { return $this->query(CssSelector::toXPath($query, 'descendant::')); } public function query($expression) { return $this->ownerDocument->xpath()->query($expression, $this); } public function parent() { return $this->parentNode; } public function next() { $sibling = $this->nextSibling; do { if ($sibling->nodeType == XML_ELEMENT_NODE) { break; } } while ($sibling = $sibling->nextSibling); return $sibling; } public function prev() { $sibling = $this->previousSibling; do { if ($sibling->nodeType == XML_ELEMENT_NODE) { break; } } while ($sibling = $sibling->previousSibling); return $sibling; } public function hasChildren() { return $this->hasChildNodes(); } public function children($query = null) { $children = array(); if (!$this->hasChildren()) { return $children; } if ($query == null) { foreach ($this->childNodes as $child) { if ($child->nodeType == XML_ELEMENT_NODE) { $children[] = $child; } } return $children; } return $this->query(CssSelector::toXPath($query, 'child::')); } public function removeChildren() { while ($child = $this->firstChild) { $this->removeChild($child); } return $this; } public function before($data) { $data = $this->prepareInsert($data); $this->parentNode->insertBefore($data, $this); return $this; } public function after($data) { $data = $this->prepareInsert($data); if (isset($this->nextSibling)) { $this->parentNode->insertBefore($data, $this->nextSibling); } else { $this->parentNode->appendChild($data); } return $this; } public function prepend($data) { $data = $this->prepareInsert($data); if (isset($data)) { if ($this->hasChildren()) { $this->insertBefore($data, $this->firstChild); } else { $this->appendChild($data); } } return $this; } public function append($data) { $data = $this->prepareInsert($data); if (isset($data)) { $this->appendChild($data); } return $this; } public function replaceWith($data) { $data = $this->prepareInsert($data); if (isset($data)) { $this->parent()->replaceChild($data, $this); } return $data; } public function wrap($data) { $data = $this->prepareInsert($data); if (empty($data)) { return $this; } self::wrapNode($this, $data); return $this; } public function text($text = null) { if (isset($text)) { $this->removeChildren(); $this->appendChild($this->ownerDocument->createTextNode($text)); return $this; } return $this->textContent; } public function html($markup = null) { if (isset($markup)) { $this->removeChildren(); $this->append($markup); return $this; } // fix selfclosing tags if (false !== $html = $this->ownerDocument->saveXML($this)) { $html = preg_replace('#(<(div|span|i)(\s[^>]*)?)/>#i', '$1></$2>', $html); } return $html; } public function tag() { return $this->tagName; } public function val($value = null) { if (isset($value)) { return $this->attr('value', $value); } return $this->attr('value'); } public function hasClass($class) { return self::attrClass('has', $this, $class); } public function addClass($class) { return self::attrClass('add', $this, $class); } public function removeClass($class) { return self::attrClass('remove', $this, $class); } public function toggleClass($class) { return self::attrClass('toggle', $this, $class); } public function attr($name = null, $value = null) { if (is_null($name)) { $attributes = array(); foreach ($this->attributes as $name => $node) { $attributes[$name] = $node->value; } return $attributes; } if (isset($value)) { $this->setAttribute($name, $value); return $this; } return $this->getAttribute($name); } public function removeAttr($name) { $this->removeAttribute($name); return $this; } protected function prepareInsert($item) { if (empty($item)) { return; } if (is_string($item)) { $item = Entities::replaceAllEntities($item); $frag = $this->ownerDocument->createDocumentFragment(); try { $frag->appendXML($item); } catch (Exception $e) {} return $frag; } if ($item instanceof \DOMNode) { if ($item->ownerDocument !== $this->ownerDocument) { return $this->ownerDocument->importNode($item, true); } return $item; } } protected static function wrapNode(\DOMNode $node, \DOMNode $wrapper) { if ($wrapper->hasChildNodes()) { $deepest = self::deepestNode($wrapper); $wrapper = $deepest[0]; } $parent = $node->parentNode; $parent->insertBefore($wrapper, $node); $wrapper->appendChild($parent->removeChild($node)); } protected static function deepestNode(\DOMNode $node, $depth = 0, $current = null, &$deepest = null) { if (!isset($current)) $current = array($node); if (!isset($deepest)) $deepest = $depth; if ($node->hasChildNodes()) { foreach ($node->childNodes as $child) { if ($child->nodeType === XML_ELEMENT_NODE) { $current = self::deepestNode($child, $depth + 1, $current, $deepest); } } } elseif ($depth > $deepest) { $current = array($node); $deepest = $depth; } elseif ($depth === $deepest) { $current[] = $node; } return $current; } protected static function attrClass($action, \DOMNode $node, $class) { $classes = $node->getAttribute('class'); $found = stripos($classes, $class) !== false && in_array(strtolower($class), explode(' ', strtolower($classes))); if ($action == 'has') { return $found; } if ($action == 'toggle') { $action = $found ? 'remove' : 'add'; } if ($action == 'add' && !$found) { $node->setAttribute('class', trim(preg_replace('/\s{2,}/i', ' ', $classes.' '.$class))); } if ($action == 'remove' && $found) { $classes = trim(preg_replace('/\s{2,}/i', ' ', preg_replace('/(^|\s)'.preg_quote($class, '/').'(?:\s|$)/i', ' ', $classes))); if ($classes !== '') { $node->setAttribute('class', $classes); } else { $node->removeAttribute('class'); } } return $node; } }