<?php /** * Zend Framework * * LICENSE * * This source file is subject to the new BSD license that is bundled * with this package in the file LICENSE.txt. * It is also available through the world-wide-web at this URL: * http://framework.zend.com/license/new-bsd * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to [email protected] so we can send you a copy immediately. * * @category Zend * @package Zend_Feed * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License * @version $Id$ */ /** * Wraps a DOMElement allowing for SimpleXML-like access to attributes. * * @category Zend * @package Zend_Feed * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ class Zend_Feed_Element implements ArrayAccess { /** * @var DOMElement */ protected $_element; /** * @var string Character encoding to utilize */ protected $_encoding = 'UTF-8'; /** * @var Zend_Feed_Element */ protected $_parentElement; /** * @var boolean */ protected $_appended = true; /** * Zend_Feed_Element constructor. * * @param DOMElement $element The DOM element we're encapsulating. * @return void */ public function __construct($element = null) { $this->_element = $element; } /** * Get a DOM representation of the element * * Returns the underlying DOM object, which can then be * manipulated with full DOM methods. * * @return DOMDocument */ public function getDOM() { return $this->_element; } /** * Update the object from a DOM element * * Take a DOMElement object, which may be originally from a call * to getDOM() or may be custom created, and use it as the * DOM tree for this Zend_Feed_Element. * * @param DOMElement $element * @return void */ public function setDOM(DOMElement $element) { $this->_element = $this->_element->ownerDocument->importNode($element, true); } /** * Set the parent element of this object to another * Zend_Feed_Element. * * @param Zend_Feed_Element $element * @return void */ public function setParent(Zend_Feed_Element $element) { $this->_parentElement = $element; $this->_appended = false; } /** * Appends this element to its parent if necessary. * * @return void */ protected function ensureAppended() { if (!$this->_appended) { $this->_parentElement->getDOM()->appendChild($this->_element); $this->_appended = true; $this->_parentElement->ensureAppended(); } } /** * Get an XML string representation of this element * * Returns a string of this element's XML, including the XML * prologue. * * @return string */ public function saveXml() { // Return a complete document including XML prologue. $doc = new DOMDocument($this->_element->ownerDocument->version, $this->_element->ownerDocument->actualEncoding); $doc->appendChild($doc->importNode($this->_element, true)); return $doc->saveXML(); } /** * Get the XML for only this element * * Returns a string of this element's XML without prologue. * * @return string */ public function saveXmlFragment() { return $this->_element->ownerDocument->saveXML($this->_element); } /** * Get encoding * * @return string */ public function getEncoding() { return $this->_encoding; } /** * Set encoding * * @param string $value Encoding to use * @return Zend_Feed_Element */ public function setEncoding($value) { $this->_encoding = (string) $value; return $this; } /** * Map variable access onto the underlying entry representation. * * Get-style access returns a Zend_Feed_Element representing the * child element accessed. To get string values, use method syntax * with the __call() overriding. * * @param string $var The property to access. * @return mixed */ public function __get($var) { $nodes = $this->_children($var); $length = count($nodes); if ($length == 1) { return new Zend_Feed_Element($nodes[0]); } elseif ($length > 1) { return array_map(create_function('$e', 'return new Zend_Feed_Element($e);'), $nodes); } else { // When creating anonymous nodes for __set chaining, don't // call appendChild() on them. Instead we pass the current // element to them as an extra reference; the child is // then responsible for appending itself when it is // actually set. This way "if ($foo->bar)" doesn't create // a phantom "bar" element in our tree. if (strpos($var, ':') !== false) { list($ns, $elt) = explode(':', $var, 2); $node = $this->_element->ownerDocument->createElementNS(Zend_Feed::lookupNamespace($ns), $elt); } else { $node = $this->_element->ownerDocument->createElement($var); } $node = new self($node); $node->setParent($this); return $node; } } /** * Map variable sets onto the underlying entry representation. * * @param string $var The property to change. * @param string $val The property's new value. * @return void * @throws Zend_Feed_Exception */ public function __set($var, $val) { $this->ensureAppended(); $nodes = $this->_children($var); if (!$nodes) { if (strpos($var, ':') !== false) { list($ns, $elt) = explode(':', $var, 2); $node = $this->_element->ownerDocument->createElementNS(Zend_Feed::lookupNamespace($ns), $var, htmlspecialchars($val, ENT_NOQUOTES, $this->getEncoding())); $this->_element->appendChild($node); } else { $node = $this->_element->ownerDocument->createElement($var, htmlspecialchars($val, ENT_NOQUOTES, $this->getEncoding())); $this->_element->appendChild($node); } } elseif (count($nodes) > 1) { /** * @see Zend_Feed_Exception */ require_once 'Zend/Feed/Exception.php'; throw new Zend_Feed_Exception('Cannot set the value of multiple tags simultaneously.'); } else { $nodes[0]->nodeValue = $val; } } /** * Map isset calls onto the underlying entry representation. * * @param string $var * @return boolean */ public function __isset($var) { // Look for access of the form {ns:var}. We don't use // _children() here because we can break out of the loop // immediately once we find something. if (strpos($var, ':') !== false) { list($ns, $elt) = explode(':', $var, 2); foreach ($this->_element->childNodes as $child) { if ($child->localName == $elt && $child->prefix == $ns) { return true; } } } else { foreach ($this->_element->childNodes as $child) { if ($child->localName == $var) { return true; } } } } /** * Get the value of an element with method syntax. * * Map method calls to get the string value of the requested * element. If there are multiple elements that match, this will * return an array of those objects. * * @param string $var The element to get the string value of. * @param mixed $unused This parameter is not used. * @return mixed The node's value, null, or an array of nodes. */ public function __call($var, $unused) { $nodes = $this->_children($var); if (!$nodes) { return null; } elseif (count($nodes) > 1) { return $nodes; } else { return $nodes[0]->nodeValue; } } /** * Remove all children matching $var. * * @param string $var * @return void */ public function __unset($var) { $nodes = $this->_children($var); foreach ($nodes as $node) { $parent = $node->parentNode; $parent->removeChild($node); } } /** * Returns the nodeValue of this element when this object is used * in a string context. * * @return string */ public function __toString() { return $this->_element->nodeValue; } /** * Finds children with tagnames matching $var * * Similar to SimpleXML's children() method. * * @param string $var Tagname to match, can be either namespace:tagName or just tagName. * @return array */ protected function _children($var) { $found = array(); // Look for access of the form {ns:var}. if (strpos($var, ':') !== false) { list($ns, $elt) = explode(':', $var, 2); foreach ($this->_element->childNodes as $child) { if ($child->localName == $elt && $child->prefix == $ns) { $found[] = $child; } } } else { foreach ($this->_element->childNodes as $child) { if ($child->localName == $var) { $found[] = $child; } } } return $found; } /** * Required by the ArrayAccess interface. * * @param string $offset * @return boolean */ public function offsetExists($offset) { if (strpos($offset, ':') !== false) { list($ns, $attr) = explode(':', $offset, 2); return $this->_element->hasAttributeNS(Zend_Feed::lookupNamespace($ns), $attr); } else { return $this->_element->hasAttribute($offset); } } /** * Required by the ArrayAccess interface. * * @param string $offset * @return string */ public function offsetGet($offset) { if (strpos($offset, ':') !== false) { list($ns, $attr) = explode(':', $offset, 2); return $this->_element->getAttributeNS(Zend_Feed::lookupNamespace($ns), $attr); } else { return $this->_element->getAttribute($offset); } } /** * Required by the ArrayAccess interface. * * @param string $offset * @param string $value * @return string */ public function offsetSet($offset, $value) { $this->ensureAppended(); if (strpos($offset, ':') !== false) { list($ns, $attr) = explode(':', $offset, 2); // DOMElement::setAttributeNS() requires $qualifiedName to have a prefix return $this->_element->setAttributeNS(Zend_Feed::lookupNamespace($ns), $offset, $value); } else { return $this->_element->setAttribute($offset, $value); } } /** * Required by the ArrayAccess interface. * * @param string $offset * @return boolean */ public function offsetUnset($offset) { if (strpos($offset, ':') !== false) { list($ns, $attr) = explode(':', $offset, 2); return $this->_element->removeAttributeNS(Zend_Feed::lookupNamespace($ns), $attr); } else { return $this->_element->removeAttribute($offset); } } }