Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Once upon a time ...

  • Loading branch information...
commit ad3ad7321c5feb8ed259a815df8e3e6b333eae55 0 parents
@GromNaN GromNaN authored
Showing with 3,631 additions and 0 deletions.
  1. +6 −0 .travis.yml
  2. +19 −0 LICENSE
  3. +77 −0 README.md
  4. +26 −0 phpunit.xml.dist
  5. +87 −0 src/Welldom/Attr.php
  6. +77 −0 src/Welldom/Comment.php
  7. +539 −0 src/Welldom/Document.php
  8. +57 −0 src/Welldom/DocumentFragment.php
  9. +329 −0 src/Welldom/Element.php
  10. +22 −0 src/Welldom/Exception/InvalidXpathException.php
  11. +30 −0 src/Welldom/NodeInterface.php
  12. +236 −0 src/Welldom/NodeList.php
  13. +73 −0 src/Welldom/Text.php
  14. +66 −0 src/Welldom/XmlErrorHandler.php
  15. +64 −0 src/Welldom/XmlSerializer.php
  16. +96 −0 src/Welldom/Xpath.php
  17. +186 −0 src/Welldom/XsltProcessor.php
  18. +60 −0 src/Welldom/XsltProcessorCollection.php
  19. +25 −0 src/autoload.php
  20. +45 −0 tests/Welldom/Tests/DocumentFragmentTest.php
  21. +430 −0 tests/Welldom/Tests/DocumentTest.php
  22. +198 −0 tests/Welldom/Tests/ElementTest.php
  23. +365 −0 tests/Welldom/Tests/NodeListTest.php
  24. +32 −0 tests/Welldom/Tests/TestCase.php
  25. +62 −0 tests/Welldom/Tests/XmlSerializerTest.php
  26. +92 −0 tests/Welldom/Tests/XpathTest.php
  27. +46 −0 tests/Welldom/Tests/XsltProcessorCollectionTest.php
  28. +152 −0 tests/Welldom/Tests/XsltProcessorTest.php
  29. +7 −0 tests/_files/frameworks-invalid.xsl
  30. +15 −0 tests/_files/frameworks-parameters.xsl
  31. +12 −0 tests/_files/frameworks.xsl
  32. +15 −0 tests/_files/invalid.xml
  33. +11 −0 tests/_files/invalid.xsl
  34. +47 −0 tests/_files/valid.xml
  35. +13 −0 tests/_files/valid.xsl
  36. +14 −0 tests/bootstrap.php
6 .travis.yml
@@ -0,0 +1,6 @@
+language: php
+php:
+ - 5.3
+ - 5.4
+
+script: phpunit
19 LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Groupe Express Roularta
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
77 README.md
@@ -0,0 +1,77 @@
+ExpDOM extension for PHP DOM
+============================
+
+*ExpDOM* try to ease the use of DOM to manipulate XML documents.
+
+Requirements
+------------
+* PHP 5.3+
+* PHPUnit 5.6+
+
+
+Installation
+------------
+
+The library follow the PSR-0 naming convention. To load the library,
+use any compliant class loader or simply require the `src/autoload.php` file.
+
+``` php
+require __DIR__ . '/path/to/expdom/src/autoload.php';
+```
+
+
+Usage
+-----
+
+Load an XML document:
+
+``` php
+<?php
+
+use Welldom\Document;
+
+$doc = new Document('UTF-8');
+
+if (false === $doc->load('data.xml')) {
+ $errors = $doc->getLastErrors();
+ throw new Exception("XML Loading error\n" . implode("\n", $errors));
+}
+
+// ... enjoy your DOM document
+```
+
+Find a node values using XPath:
+
+``` php
+$values = array(
+ 'title' => $doc->getNodeValue('//book/title'),
+ 'lang' => $doc->getNodeValue('//book/title/@lang', 'en'),
+);
+```
+
+Execute action on each node matching a given Xpath:
+
+``` php
+$doc->query('//foo/bar[@lang=de]')
+ ->remove();
+
+$doc->query('//foo/bar[@lang=english]/@lang')
+ ->setValue('en');
+```
+
+
+Run tests
+---------
+
+Run PHPUnit test suite:
+```
+phpunit
+```
+
+Quality metrics:
+```
+ant phpmd
+ant phpcs
+ant phpcpd
+ant phploc
+```
26 phpunit.xml.dist
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ processIsolation="false"
+ stopOnFailure="false"
+ syntaxCheck="false"
+ bootstrap="tests/bootstrap.php"
+ >
+
+ <testsuites>
+ <testsuite name="Welldom Test Suite">
+ <directory>./tests/</directory>
+ </testsuite>
+ </testsuites>
+
+ <filter>
+ <whitelist>
+ <directory>./src/Welldom/</directory>
+ </whitelist>
+ </filter>
+
+</phpunit>
87 src/Welldom/Attr.php
@@ -0,0 +1,87 @@
+<?php
+
+/*
+ * This file is part of the Welldom package.
+ *
+ * (c) Groupe Express Roularta
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Welldom;
+
+/**
+ * DOM Attribute extension.
+ *
+ * @author Jérôme Tamarelle <jerome@tamarelle.net>
+ */
+class Attr extends \DOMAttr implements NodeInterface
+{
+ /**
+ * Removes the attribute from its parent.
+ *
+ * @return \Welldom\Attr
+ */
+ public function remove()
+ {
+ $this->ownerElement->removeAttribute($this->nodeName);
+ }
+
+ /**
+ * Rename the attribute
+ *
+ * @return \Welldom\Attr The new attribute
+ */
+ public function rename($name)
+ {
+ $ownerElement = $this->ownerElement;
+ $ownerElement->removeAttributeNode($this);
+ $ownerElement->setAttribute($name, $this->value);
+
+ return $ownerElement->getAttribute($name);
+ }
+
+ /**
+ * Get attribute value
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Set attribute value
+ *
+ * @param string $value
+ * @return \Welldom\Attr
+ */
+ public function setValue($value)
+ {
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get attribute name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->nodeName;
+ }
+
+ /**
+ * Get XML representation
+ *
+ * @return string
+ */
+ public function getXml()
+ {
+ return $this->ownerDocument->saveXML($this);
+ }
+}
77 src/Welldom/Comment.php
@@ -0,0 +1,77 @@
+<?php
+
+/*
+ * This file is part of the Welldom package.
+ *
+ * (c) Groupe Express Roularta
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Welldom;
+
+/**
+ * DOM Comment extension.
+ *
+ * @author Jérôme Tamarelle <jerome@tamarelle.net>
+ */
+class Comment extends \DOMComment implements NodeInterface
+{
+ /**
+ * Removes the node from its parent.
+ *
+ * @return \Welldom\Comment
+ */
+ public function remove()
+ {
+ if (!$this->parentNode) {
+ return $this;
+ }
+
+ return $this->parentNode->removeChild($this);
+ }
+
+ /**
+ * Get node value
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Set node value
+ *
+ * @param string $value
+ * @return \Welldom\Comment
+ */
+ public function setValue($value)
+ {
+ $this->data = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get node name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->nodeName;
+ }
+
+ /**
+ * Get XML representation
+ *
+ * @return string
+ */
+ public function getXml()
+ {
+ return $this->ownerDocument->saveXML($this);
+ }
+}
539 src/Welldom/Document.php
@@ -0,0 +1,539 @@
+<?php
+
+/*
+ * This file is part of the Welldom package.
+ *
+ * (c) Groupe Express Roularta
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Welldom;
+
+use Welldom\Exception\InvalidXpathException;
+
+/**
+ * DOM Document extension.
+ *
+ * @author Jérôme Tamarelle <jerome@tamarelle.net>
+ */
+class Document extends \DOMDocument
+{
+ const DEFAULT_ENCODING = 'ISO-8859-15';
+ const DEFAULT_VERSION = '1.0';
+
+ /**
+ * @var \Welldom\Xpath
+ */
+ private $xpath = null;
+
+ /**
+ * Last loading/validation errors
+ *
+ * @var array
+ */
+ private $lastErrors = array();
+
+ /**
+ * Constructor
+ *
+ * @param string $encoding
+ * @param string $version
+ * @param array $streamOptions
+ */
+ public function __construct($encoding = null, $version = null)
+ {
+ if (null === $encoding) {
+ $encoding = static::DEFAULT_ENCODING;
+ }
+ if (null === $version) {
+ $version = static::DEFAULT_VERSION;
+ }
+ parent::__construct($version, $encoding);
+
+ $this->registerNodeClass('DOMDocument', get_called_class());
+ $this->registerNodeClass('DOMDocumentFragment', 'Welldom\DocumentFragment');
+ $this->registerNodeClass('DOMElement', 'Welldom\Element');
+ $this->registerNodeClass('DOMAttr', 'Welldom\Attr');
+ $this->registerNodeClass('DOMText', 'Welldom\Text');
+ $this->registerNodeClass('DOMComment', 'Welldom\Comment');
+
+ $this->preserveWhiteSpace = false;
+ $this->resolveExternals = true;
+ $this->substituteEntities = true;
+ }
+
+ /**
+ * Factory : create a document and load contents.
+ *
+ * @param string $source
+ * @param string $encoding
+ * @param string $version
+ * @return \Welldom\Document
+ */
+ static public function create($source, $encoding = null, $version = null)
+ {
+ $doc = new static($encoding, $version);
+ $doc->loadXML($source);
+
+ return $doc;
+ }
+
+ /**
+ * Factory.
+ * Create a document from a DOMNode.
+ *
+ * @param \Welldom\DOMNode $node
+ * @param bool $deep When false, $node subtree is not imported.
+ * @return \Welldom\Document
+ */
+ static public function createFromNode(\DOMNode $node, $deep = true)
+ {
+ $doc = new static($node->ownerDocument->encoding, $node->ownerDocument->version);
+ $doc->appendChild($doc->importNode($node, $deep));
+
+ return $doc;
+ }
+
+ /**
+ * Destructor
+ */
+ public function __destruct()
+ {
+ $this->xpath = null;
+ }
+
+ /**
+ * Get shared Xpath object
+ *
+ * @return \Welldom\Xpath
+ */
+ public function getXpath()
+ {
+ if (null === $this->xpath) {
+ $this->xpath = new Xpath($this);
+ }
+
+ return $this->xpath;
+ }
+
+ /**
+ * Get last XML loading errors
+ *
+ * @return array
+ */
+ public function getLastErrors()
+ {
+ return $this->lastErrors;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return boolean
+ */
+ public function load($filename, $options = LIBXML_COMPACT)
+ {
+ XmlErrorHandler::start();
+ $success = parent::load($filename, $options);
+ $this->lastErrors = XmlErrorHandler::getErrors();
+ XmlErrorHandler::clean();
+
+ return $success;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return boolean
+ */
+ public function loadXML($source, $options = LIBXML_COMPACT)
+ {
+ $source = $this->fixDoctype($source);
+
+ XmlErrorHandler::start();
+ $success = parent::loadXML($source, $options);
+ $this->lastErrors = XmlErrorHandler::getErrors();
+ XmlErrorHandler::clean();
+
+ return $success;
+ }
+
+ /**
+ * @param string Inner XML
+ */
+ public function getInnerXml()
+ {
+ return $this->documentElement ? $this->documentElement->getInnerXml() : '';
+ }
+
+ /**
+ * @return string XML
+ */
+ public function getXml()
+ {
+ return $this->documentElement ? $this->documentElement->getXml() : '';
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * The output encoding is the document encoding.
+ * $doc->encoding = 'iso-8859-15';
+ * $doc->saveXML($node);
+ */
+ public function saveXML(\DOMNode $node = null, int $options = null)
+ {
+ if (null === $node) {
+ return parent::saveXML(null, $options);
+ }
+
+ if ($node instanceof \DOMDocument) {
+ return $node->saveXML();
+ }
+
+ $xml = parent::saveXML($node, $options);
+
+ if ('UTF-8' === $encoding = strtoupper($this->encoding)) {
+ return $xml;
+ }
+
+ return mb_convert_encoding($xml, $encoding, 'UTF-8');
+ }
+
+ /**
+ * Convert to array
+ *
+ * @param boolean $renderRoot
+ * @return array
+ */
+ public function toArray($renderRoot = false)
+ {
+ $array = $this->documentElement->toArray(true);
+
+ if ($renderRoot) {
+ $array = array($this->nodeName => $array);
+ }
+
+ return $array;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return boolean
+ */
+ public function validate()
+ {
+ XmlErrorHandler::start();
+ $valid = parent::validate();
+ $this->lastErrors = XmlErrorHandler::getErrors();
+ XmlErrorHandler::clean();
+
+ return $valid;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return boolean
+ */
+ public function schemaValidate($filename)
+ {
+ XmlErrorHandler::start();
+ $valid = parent::schemaValidate($filename);
+ $this->lastErrors = XmlErrorHandler::getErrors();
+ XmlErrorHandler::clean();
+
+ return $valid;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return boolean
+ */
+ public function schemaValidateSource($source)
+ {
+ XmlErrorHandler::start();
+ $valid = parent::schemaValidateSource($source);
+ $this->lastErrors = XmlErrorHandler::getErrors();
+ XmlErrorHandler::clean();
+
+ return $valid;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return \Welldom\NodeList
+ */
+ public function getElementsByTagName($name)
+ {
+ return new NodeList(parent::getElementsByTagName($name));
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return mixed
+ */
+ public function evaluate($expression, $contextNode = null)
+ {
+ if (null === $contextNode) {
+ $contextNode = $this;
+ }
+
+ return $this->getXpath()->evaluate($expression, $contextNode);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return \Welldom\NodeList
+ */
+ public function query($expression, $contextNode = null)
+ {
+ if (null === $contextNode) {
+ $contextNode = $this;
+ }
+
+ return $this->getXpath()->query($expression, $contextNode);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return \Welldom\Element
+ */
+ public function queryOne($expression, $contextNode = null)
+ {
+ if (null === $contextNode) {
+ $contextNode = $this;
+ }
+
+ return $this->getXpath()->queryOne($expression, $contextNode);
+ }
+
+ /**
+ * Create node
+ *
+ * @param string $expression Xpath expression
+ * @param string $value
+ * @param array $array
+ * @return \Welldom\Element
+ */
+ public function createNode($expression, $value = null, array $attributes = null)
+ {
+ $current = ('/' === $expression{0}) ? $this : $this->documentElement;
+ $xpaths = explode('/', str_replace('//', '', $expression));
+
+ $i = 0;
+ foreach ($xpaths as $path) {
+ ++$i;
+ $nodes = $this->getXpath()->query($path, $current);
+
+ if ($nodes->getLength() > 1) {
+ throw new InvalidXpathException(sprintf('Sub-query part "%s" returned more than 1 element', $path));
+ }
+
+ if ($nodes->getLength() === 1) {
+ $current = $nodes->item(0);
+
+ continue;
+ }
+
+ if ($nodes->getLength() === 0) {
+ if ('@' === $path[0]) {
+ if ($i !== count($xpaths)) {
+ throw new InvalidXpathException(sprintf('Cannot create the node attribute "%s"', $path));
+ }
+
+ $path = substr($path, 1);
+ $current = $current->setAttribute($path, $value);
+
+ continue;
+ }
+
+ if ('comment()' === $path) {
+ if ($i !== count($xpaths)) {
+ throw new InvalidXpathException(sprintf('Cannot create the node comment'));
+ }
+
+ $current = $current->appendChild($this->createComment($value));
+
+ continue;
+ }
+
+ if ('text()' === $path) {
+ if ($i !== count($xpaths)) {
+ throw new InvalidXpathException(sprintf('Cannot create the node text'));
+ }
+
+ $current = $current->appendChild($this->createTextNode($value));
+
+ continue;
+ }
+
+ if (strpos($path, '[')) {
+ if (!preg_match('/^(\w*)\[@(\w*)="(\w*)"\]$/', $path, $matches)) {
+ throw new InvalidXpathException(sprintf('Cannot create sub-part query "%s"', $path));
+ }
+
+ $current = $current->appendChild($this->createElement($matches[1]));
+ $current->setAttribute($matches[2], $matches[3]);
+ } else {
+ $current = $current->appendChild($this->createElement($path));
+ }
+ }
+ }
+
+ if (null !== $value) {
+ $current->nodeValue = $value;
+ }
+
+ if ($attributes && XML_ELEMENT_NODE === $current->nodeType) {
+ $current->setAttributes($attributes);
+ }
+
+ return $current;
+ }
+
+ /**
+ * Get node
+ *
+ * @param string $expression
+ * @param boolean $create
+ * @return \Welldom\Element
+ */
+ public function getNode($expression, $create = false)
+ {
+ $node = $this->getXpath()->queryOne($expression);
+
+ if (false === $node && true === $create) {
+ $node = $this->createNode($expression);
+ }
+
+ return $node;
+ }
+
+ /**
+ * Get the value of a node
+ *
+ * @param string $expression Xpath expression
+ * @param mixed $default Default value
+ * @return mixed Node value
+ */
+ public function getNodeValue($expression, $default = false)
+ {
+ if (false === $node = $this->getXpath()->queryOne($expression)) {
+ return $default;
+ }
+
+ return $node->getValue();
+ }
+
+ /**
+ * Set the value of a node.
+ * Create the node if needed.
+ *
+ * @param string $expression
+ * @param string $value
+ * @return \Welldom\Node
+ */
+ public function setNodeValue($expression, $value = null)
+ {
+ if (false === $node = $this->getXpath($expression)->queryOne()) {
+ return false;
+ }
+
+ return $node->setValue($value);
+ }
+
+ /**
+ * Get values of a list of nodes
+ *
+ * @param string $expression Xpath expression
+ * @return array
+ */
+ public function getNodeListValues($expression)
+ {
+ return $this->getXpath()->query($expression)->getValue();
+ }
+
+ /**
+ * Get the Xml representation of a node
+ *
+ * @param string $expression Xpath expression
+ * @param bool $throwException Throws an exception if number of nodes found is not 1
+ * @return mixed Node value
+ */
+ public function getNodeXml($expression, $default = false)
+ {
+ if (false === $node = $this->getXpath()->queryOne($expression)) {
+ return $default;
+ }
+
+ return $node->getXml();
+ }
+
+ /**
+ * Get Xml representation of a list of nodes
+ *
+ * @param string $expression Xpath expression
+ * @return array Node Xml
+ */
+ public function getNodeListXml($expression)
+ {
+ return $this->getXpath()->query($expression)->getXml();
+ }
+
+ /**
+ * Get the Xml representation of an inner node
+ *
+ * @param string $expression
+ * @param string $default
+ * @return string
+ */
+ public function getNodeInnerXml($expression, $default = false)
+ {
+ if (false === $node = $this->getXpath()->queryOne($expression)) {
+ return $default;
+ }
+
+ return $node->getInnerXml();
+ }
+
+ /**
+ * Set the inner Xml of a node
+ *
+ * @param string $expression
+ * @param string $xml
+ * @param boolean $append
+ * @return \Welldom\Node
+ */
+ public function setNodeInnerXml($expression, $xml, $append = false)
+ {
+ if (false === $node = $this->createNode($expression)) {
+ return false;
+ }
+
+ return $node->setInnerXml($xml, $append);
+ }
+
+ /**
+ * Had doctype to the XML source if absent
+ *
+ * @param string $xml
+ * @return string
+ */
+ protected function fixDoctype($xml)
+ {
+ if ('<?xml' === substr($xml, 0, 5)) {
+ return $xml;
+ }
+
+ return sprintf('<?xml version="%s" encoding="%s" ?>', $this->version, $this->encoding) . $xml;
+ }
+}
57 src/Welldom/DocumentFragment.php
@@ -0,0 +1,57 @@
+<?php
+
+/*
+ * This file is part of the Welldom package.
+ *
+ * (c) Groupe Express Roularta
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Welldom;
+
+use Welldom\Exception\XmlLoadingException;
+
+/**
+ * DOM Document Fragment extension.
+ *
+ * @author Jérôme Tamarelle <jerome@tamarelle.net>
+ */
+class DocumentFragment extends \DOMDocumentFragment
+{
+ protected $lastErrors = array();
+
+ /**
+ * Get last XML loading errors.
+ *
+ * @return array
+ */
+ public function getLastErrors()
+ {
+ return $this->lastErrors;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function appendXml($xml)
+ {
+ XmlErrorHandler::start();
+ $success = parent::appendXml($xml);
+ $this->lastErrors = XmlErrorHandler::getErrors();
+ XmlErrorHandler::clean();
+
+ return $success;
+ }
+
+ /**
+ * Get element children
+ *
+ * @return \Welldom\NodeList
+ */
+ public function getChildNodes()
+ {
+ return new NodeList($this->childNodes);
+ }
+}
329 src/Welldom/Element.php
@@ -0,0 +1,329 @@
+<?php
+
+/*
+ * This file is part of the Welldom package.
+ *
+ * (c) Groupe Express Roularta
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Welldom;
+
+/**
+ * DOM Element extension.
+ *
+ * @author Jérôme Tamarelle <jerome@tamarelle.net>
+ */
+class Element extends \DOMElement
+{
+ /**
+ * @return \Welldom\Document
+ */
+ public function getDocument()
+ {
+ return $this->ownerDocument;
+ }
+
+ /**
+ * Get element tag name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->nodeName;
+ }
+
+ /**
+ * Replace the element by one with the given name
+ *
+ * @param string $nodeName
+ * @return \Welldom\Element
+ */
+ public function rename($nodeName)
+ {
+ if ($this->getName() == $nodeName) {
+ return $this;
+ }
+
+ $newNode = $this->ownerDocument->createElement($nodeName);
+
+ if ($this->attributes->length) {
+ foreach ($this->attributes as $attribute) {
+ $newNode->setAttribute($attribute->nodeName, $attribute->nodeValue);
+ }
+ }
+
+ while ($child = $this->firstChild) {
+ $newNode->appendChild($child);
+ }
+
+ if ($this->parentNode) {
+ $this->parentNode->replaceChild($newNode, $this);
+ }
+
+ return $newNode;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return \Welldom\NodeList
+ */
+ public function getElementsByTagName($name)
+ {
+ return new NodeList(parent::getElementsByTagName($name));
+ }
+
+ /**
+ * Get element children
+ *
+ * @return \Welldom\NodeList
+ */
+ public function getChildNodes()
+ {
+ return new NodeList($this->childNodes);
+ }
+
+ /**
+ * Remove the element from its parent.
+ *
+ * @return \Welldom\Element The removed element
+ */
+ public function remove()
+ {
+ if (!$this->parentNode) {
+ return $this;
+ }
+
+ return $this->parentNode->removeChild($this);
+ }
+
+
+ /**
+ * Remove nodes by node name
+ *
+ * @return array Remaining nodes names
+ */
+ public function removeChildNodes()
+ {
+ foreach ($this->childNodes as $childNode) {
+ $this->removeChild($childNode);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get element text value
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->nodeValue;
+ }
+
+ /**
+ * Set element text value
+ *
+ * @param string $value
+ * @return \Welldom\Element
+ */
+ public function setValue($value)
+ {
+ $this->nodeValue = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get all attributes as array
+ *
+ * @return array
+ */
+ public function getAttributes()
+ {
+ $attributes = array();
+ foreach ($this->attributes as $name => $attr) {
+ $attributes[$name] = $attr->getValue();
+ }
+
+ return $attributes;
+ }
+
+ /**
+ * Set many attributes from an associative array
+ *
+ * @param array $attributes
+ * @param bool $append If FALSE, previous attributes are removed
+ */
+ public function setAttributes(array $attributes, $append = true)
+ {
+ if (!$append) {
+ $this->removeAttributes();
+ }
+
+ foreach ($attributes as $name => $value) {
+ $this->setAttribute($name, $value);
+ }
+ }
+
+ /**
+ * Removes all attributes
+ *
+ * @return \Welldom\Element
+ */
+ public function removeAttributes()
+ {
+ while ($attr = $this->attributes->item(0)) {
+ $this->removeAttributeNode($attr);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get inner XML
+ *
+ * @return string
+ */
+ public function getInnerXml()
+ {
+ return $this->removeRootTag($this->getXml(), $this->tagName);
+ }
+
+ /**
+ * Set inner XML
+ *
+ * @param string $inner
+ * @param bool $append If TRUE, previous inner XML is kept
+ * @return \Welldom\Element
+ */
+ public function setInnerXml($inner, $append = false)
+ {
+ if (false === $append) {
+ $this->removeChildNodes();
+ }
+
+ if ($inner instanceof \DOMNode) {
+ $node = $inner;
+ if ($this->ownerDocument->isSameNode($node->ownerDocument)) {
+ $node = $node->cloneNode(false);
+ } else {
+ $node = $this->ownerDocument->importNode($node);
+ }
+ $this->appendChild($node);
+
+ } elseif ($inner instanceof \DOMNodeList || $inner instanceof NodeList) {
+ // We create a temp array because \DOMNodeList is a living object
+ $nodes = array();
+ foreach ($inner as $node) {
+ $nodes[] = $node;
+ }
+ foreach ($nodes as $node) {
+ $this->setInnerXml($node, true);
+ }
+
+ } elseif (!empty($inner)) {
+ $fragment = $this->ownerDocument->createDocumentFragment();
+ if (false === $fragment->appendXml($inner)) {
+ throw new \InvalidArgumentException(sprintf('Invalid XML: "%s"', $inner));
+ }
+ $this->appendChild($fragment);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get XML representation of the element
+ *
+ * @return string
+ */
+ public function getXml()
+ {
+ return $this->ownerDocument->saveXml($this);
+ }
+
+ /**
+ * Get array representation of the element
+ *
+ * @return array
+ */
+ public function toArray($renderRoot = false)
+ {
+ $arResult = array();
+
+ if (!$this->hasChildNodes()) {
+ if ($renderRoot) {
+ return array($this->nodeName => $this->nodeValue);
+ }
+
+ return $this->nodeValue;
+ }
+
+ foreach ($this->childNodes as $childNode) {
+ // how many of these child nodes do we have?
+ $childNodeList = $this->getElementsByTagName($childNode->nodeName); // count = 0
+ $childCount = 0;
+ // there are x number of childs in this node that have the same tag name
+ // however, we are only interested in the # of siblings with the same tag name
+ foreach ($childNodeList as $oNode) {
+ if ($oNode->parentNode->isSameNode($childNode->parentNode)) {
+ $childCount++;
+ }
+ }
+
+ if ($childNode instanceof self) {
+ $mValue = $childNode->toArray(true);
+ $mValue = is_array($mValue) ? $mValue[$childNode->nodeName] : $mValue;
+ } else {
+ $mValue = $childNode->getValue();
+ }
+
+ $sKey = $childNode->nodeName{0} == '#' ? 0 : $childNode->nodeName;
+
+ // this will give us a clue as to what the result structure should be
+ // how many of these child nodes do we have?
+ if ($childCount == 1) {
+ // only one child – make associative array
+ $arResult[$sKey] = $mValue;
+ } else if ($childCount > 1) {
+ // more than one child like this – make numeric array
+ $arResult[$sKey][] = $mValue;
+ } elseif ($childCount == 0) {
+ // no child records found, this is DOMText or DOMCDataSection
+ $arResult[$sKey] = $mValue;
+ }
+ }
+
+ // if the child is bar, the result will be array(bar)
+ // make the result just 'bar'
+ if (count($arResult) == 1 && isset($arResult[0]) && !is_array($arResult[0])) {
+ $arResult = $arResult[0];
+ }
+
+ if ($renderRoot) {
+ return array($this->nodeName => $arResult);
+ }
+
+ return $arResult;
+ }
+
+ /**
+ * Removes the root element from an XML string
+ *
+ * @ignore
+ * @param string $xml
+ * @param string $tagName
+ * @return string
+ */
+ protected function removeRootTag($xml, $tagName = '[:alnum:]+')
+ {
+ return preg_replace(array('/^<' . $tagName . '[^>]*>/', '/<\/' . $tagName . '>$/'), '', $xml);
+ }
+}
+
22 src/Welldom/Exception/InvalidXpathException.php
@@ -0,0 +1,22 @@
+<?php
+
+/*
+ * This file is part of the Welldom package.
+ *
+ * (c) Groupe Express Roularta
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Welldom\Exception;
+
+/**
+ * Exception thrown when the given Xpath has a syntax error.
+ *
+ * @author Jérôme Tamarelle <jerome@tamarelle.net>
+ */
+class InvalidXpathException extends \InvalidArgumentException
+{
+
+}
30 src/Welldom/NodeInterface.php
@@ -0,0 +1,30 @@
+<?php
+
+/*
+ * This file is part of the Welldom package.
+ *
+ * (c) Groupe Express Roularta
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Welldom;
+
+/**
+ * Interface DOM Node extensions.
+ *
+ * @author Jérôme Tamarelle <jerome@tamarelle.net>
+ */
+interface NodeInterface
+{
+ function remove();
+
+ function getValue();
+
+ function setValue($value);
+
+ function getName();
+
+ function getXml();
+}
236 src/Welldom/NodeList.php
@@ -0,0 +1,236 @@
+<?php
+
+/*
+ * This file is part of the Welldom package.
+ *
+ * (c) Groupe Express Roularta
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Welldom;
+
+/**
+ * DOM Node List extension. Batch actions on its items.
+ *
+ * @author Jérôme Tamarelle <jerome@tamarelle.net>
+ *
+ * @method array getNodePath() getNodePath()
+ * @method array getLineNo() getLineNo()
+ * @method array isSameNode() isSameNode(\DOMNode $node)
+ * @method array cloneNode() cloneNode(bool $deep)
+ * @method array hasAttributes() hasAttributes()
+ * @method array hasChildNodes() hasChildNodes()
+ */
+class NodeList implements NodeInterface, \IteratorAggregate
+{
+ /**
+ * @var \DOMNodeList
+ */
+ protected $nodeList;
+
+ /**
+ * Constructor
+ *
+ * @param \DOMNodeList $nodeList
+ */
+ public function __construct(\DOMNodeList $nodeList)
+ {
+ $this->nodeList = $nodeList;
+ }
+
+ /**
+ * Destructor
+ */
+ public function __destruct()
+ {
+ unset($this->nodeList);
+ }
+
+ /**
+ * @see \IteratorAggregate::getIterator()
+ *
+ * @return \DOMNodeList
+ */
+ public function getIterator()
+ {
+ return $this->nodeList;
+ }
+
+ /**
+ * Get an array of nodes currently in the NodeList.
+ * \DOMNodeList is a dynamic object ; its elements change when you manipulate the DOM.
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $nodes = array();
+ foreach ($this->nodeList as $node) {
+ $nodes[] = $node;
+ }
+
+ return $nodes;
+ }
+
+ /**
+ * @see \DOMNodeList::item()
+ *
+ * @return \Welldom\Element
+ */
+ public function item($index)
+ {
+ return $this->nodeList->item($index);
+ }
+
+ /**
+ * Count nodes
+ *
+ * @return int
+ */
+ public function getLength()
+ {
+ return $this->nodeList->length;
+ }
+
+ /**
+ * Remove each node.
+ *
+ * This NodeList is empty after calling this method.
+ *
+ * @return void
+ */
+ public function remove()
+ {
+ foreach ($this->toArray() as $node) {
+ $node->remove();
+ }
+ }
+
+ /**
+ * Rename each node.
+ *
+ * This NodeList is empty after calling this method.
+ * All nodes are removed and replaced by a new one.
+ *
+ * @param string $name
+ * @return void
+ */
+ public function rename($name)
+ {
+ foreach ($this->toArray() as $node) {
+ $node->rename($name);
+ }
+ }
+
+ /**
+ * Get each names
+ *
+ * @return array
+ */
+ public function getName()
+ {
+ $names = array();
+ foreach ($this->nodeList as $node) {
+ $names[] = $node->getName();
+ }
+
+ return $names;
+ }
+
+ /**
+ * Get values of each nodes
+ *
+ * @return array
+ */
+ public function getValue()
+ {
+ $values = array();
+ foreach ($this->nodeList as $node) {
+ $values[] = $node->getValue();
+ }
+
+ return $values;
+ }
+
+ /**
+ * Set value to each nodes
+ *
+ * @return \Welldom\NodeList
+ */
+ public function setValue($value)
+ {
+ foreach ($this->nodeList as $node) {
+ $node->setValue($value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get inner XML of each node
+ *
+ * @return array
+ */
+ public function getInnerXml()
+ {
+ $xml = array();
+ foreach ($this->nodeList as $node) {
+ if ($node instanceof Element) {
+ $xml[] = $node->getInnerXml();
+ }
+ }
+
+ return $xml;
+ }
+
+ /**
+ * Set inner XML to each node
+ *
+ * @param string $xml XML
+ * @return \Welldom\NodeList
+ */
+ public function setInnerXml($xml)
+ {
+ foreach ($this->nodeList as $node) {
+ if ($node instanceof Element) {
+ $node->setInnerXml($xml);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get XML of each node
+ *
+ * @return array
+ */
+ public function getXml()
+ {
+ $xml = array();
+ foreach ($this->nodeList as $node) {
+ $xml[] = $node->getXml();
+ }
+
+ return $xml;
+ }
+
+ /**
+ * Magic method, do not use :)
+ * The method is called on each node.
+ *
+ * @return array
+ */
+ public function __call($method, $arguments)
+ {
+ $arg = isset($arguments[0]) ? $arguments[0] : null;
+ $results = array();
+ foreach ($this->toArray() as $node) {
+ $results[] = $node->$method($arg);
+ }
+
+ return $results;
+ }
+}
73 src/Welldom/Text.php
@@ -0,0 +1,73 @@
+<?php
+
+/*
+ * This file is part of the Welldom package.
+ *
+ * (c) Groupe Express Roularta
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Welldom;
+
+/**
+ * DOM Text extension.
+ *
+ * @author Jérôme Tamarelle <jerome@tamarelle.net>
+ */
+class Text extends \DOMText implements NodeInterface
+{
+ /**
+ * Removes the node from its parent.
+ *
+ * @return \Welldom\Text
+ */
+ public function remove()
+ {
+ return $this->parentNode->removeChild($this);
+ }
+
+ /**
+ * Get node value
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->wholeText;
+ }
+
+ /**
+ * Set node value
+ *
+ * @param string $value
+ * @return \Welldom\Comment
+ */
+ public function setValue($value)
+ {
+ $this->data = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get node name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->nodeName;
+ }
+
+ /**
+ * Get XML representation
+ *
+ * @return string
+ */
+ public function getXml()
+ {
+ return $this->ownerDocument->saveXML($this);
+ }
+}
66 src/Welldom/XmlErrorHandler.php
@@ -0,0 +1,66 @@
+<?php
+
+/*
+ * This file is part of the Welldom package.
+ *
+ * (c) Groupe Express Roularta
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Welldom;
+
+/**
+ * XML Errors Handler is an utilitary class to catch libxml errors.
+ *
+ * @author Jérôme Tamarelle <jerome@tamarelle.net>
+ */
+class XmlErrorHandler
+{
+ static private $userDefinedUseInternalErrors;
+
+ /**
+ * Start error handling
+ */
+ static public function start()
+ {
+ if (null === self::$userDefinedUseInternalErrors) {
+ self::$userDefinedUseInternalErrors = libxml_use_internal_errors(true);
+ }
+ }
+
+ /**
+ * Get last errors
+ *
+ * @return array
+ */
+ static public function getErrors()
+ {
+ $errors = array();
+ foreach (libxml_get_errors() as $error) {
+ $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
+ LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
+ $error->code,
+ trim($error->message),
+ $error->file ? $error->file : 'n/a',
+ $error->line,
+ $error->column
+ );
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Clean error handling
+ */
+ static public function clean()
+ {
+ if (null !== self::$userDefinedUseInternalErrors) {
+ libxml_clear_errors();
+ libxml_use_internal_errors(self::$userDefinedUseInternalErrors);
+ self::$userDefinedUseInternalErrors = null;
+ }
+ }
+}
64 src/Welldom/XmlSerializer.php
@@ -0,0 +1,64 @@
+<?php
+
+/*
+ * This file is part of the Welldom package.
+ *
+ * (c) Groupe Express Roularta
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Welldom;
+
+/**
+ * Xml Serializer.
+ *
+ * @author Jérôme Tamarelle <jerome@tamarelle.net>
+ */
+class XmlSerializer
+{
+ /**
+ * Serialize an array into an XML
+ *
+ * @param array $array
+ * @return string
+ */
+ static public function arrayToXml(array $array)
+ {
+ $xml = '';
+ foreach ($array as $name => $content) {
+ if (is_array($content)) {
+ $content = self::arrayToXml($content);
+ }
+
+ $xml .= sprintf('<%s>%s</%s>', $name, $content, $name);
+ }
+
+ return $xml;
+ }
+
+ /**
+ * Unserialize an XML into an array.
+ * Attributes are node serialized.
+ *
+ * @param string $xml
+ * @return array
+ */
+ static public function xmlToArray($xml)
+ {
+ $pattern = '/^<(.*?)>(.*?)<\/\1>(.*)/';
+
+ if (preg_match($pattern, $xml)) {
+ $array = array();
+ while ($xml && preg_match($pattern, $xml, $matches)) {
+ $array[$matches[1]] = self::xmlToArray($matches[2]);
+ $xml = $matches[3];
+ }
+
+ return $array;
+ }
+
+ return $xml;
+ }
+}
96 src/Welldom/Xpath.php
@@ -0,0 +1,96 @@
+<?php
+
+/*
+ * This file is part of the Welldom package.
+ *
+ * (c) Groupe Express Roularta
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Welldom;
+
+use Welldom\Exception\InvalidXpathException;
+
+/**
+ * DOM Xpath extension.
+ *
+ * @author Jérôme Tamarelle <jerome@tamarelle.net>
+ */
+class Xpath extends \DOMXPath
+{
+ /**
+ * Evaluate Xpath expression.
+ *
+ * @param string $expression The XPath expression to execute.
+ * @param \DOMNode $contextnode
+ * @param bool $registerNodeNS
+ * @return mixed
+ * @throws \Welldom\Exception\InvalidXpathException
+ */
+ public function evaluate($expression, \DOMNode $contextnode = null, $registerNodeNS = true)
+ {
+ XmlErrorHandler::start();
+ $results = parent::evaluate($expression, $contextnode);
+ XmlErrorHandler::clean();
+
+ if (false === $results) {
+ throw new InvalidXpathException(sprintf('Invalid Xpath expression "%s".', $expression));
+ }
+
+ if ($results instanceof \DOMNodeList) {
+ return new NodeList($results);
+ }
+
+ return $results;
+ }
+
+ /**
+ * Query Xpath expression.
+ *
+ * @param string $expression The XPath expression to execute.
+ * @param \DOMNode $contextnode
+ * @param bool $registerNodeNS
+ * @return \Welldom\NodeList
+ * @throws \Welldom\Exception\InvalidXpathException
+ */
+ public function query($expression, \DOMNode $contextnode = null, $registerNodeNS = true)
+ {
+ XmlErrorHandler::start();
+ $results = parent::query($expression, $contextnode, $registerNodeNS);
+ XmlErrorHandler::clean();
+
+ if (false === $results) {
+ throw new InvalidXpathException(sprintf('Invalid Xpath expression "%s".', $expression));
+ }
+
+ return new NodeList($results);
+ }
+
+ /**
+ * Execute Xpath query and retrieve 1 result
+ *
+ * @param string $expression Xpath expression
+ * @param \DOMNode $contextnode
+ * @param bool $registerNodeNS
+ * @return \Welldom\Node The node found or FALSE if the number of results is not 1
+ * @throws \Welldom\Exception\InvalidXpathException
+ */
+ public function queryOne($expression, \DOMNode $contextnode = null, $registerNodeNS = true)
+ {
+ XmlErrorHandler::start();
+ $results = parent::query($expression, $contextnode, $registerNodeNS);
+ XmlErrorHandler::clean();
+
+ if (false === $results) {
+ throw new InvalidXpathException(sprintf('Invalid Xpath expression "%s".', $expression));
+ }
+
+ if (1 === $results->length) {
+ return $results->item(0);
+ }
+
+ return false;
+ }
+}
186 src/Welldom/XsltProcessor.php
@@ -0,0 +1,186 @@
+<?php
+
+/*
+ * This file is part of the Welldom package.
+ *
+ * (c) Groupe Express Roularta
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Welldom;
+
+/**
+ * DOM XsltProcessor extension.
+ *
+ * @author Jérôme Tamarelle <jerome@tamarelle.net>
+ */
+class XsltProcessor extends \XSLTProcessor
+{
+ /**
+ * @var array
+ */
+ protected $lastErrors = array();
+
+ /**
+ * @var array
+ */
+ protected $replacedParameters;
+
+ /**
+ * Constructor.
+ * Import the given XSL.
+ *
+ * @param string $filename XSL file path
+ */
+ public function __construct($filename)
+ {
+ XmlErrorHandler::start();
+
+ $xslDoc = new \DOMDocument();
+ $xslDoc->load($filename, LIBXML_NOCDATA | LIBXML_COMPACT);
+ $this->registerPHPFunctions();
+ $this->importStyleSheet($xslDoc);
+
+ $this->lastErrors = XmlErrorHandler::getErrors();
+ XmlErrorHandler::clean();
+ }
+
+ /**
+ * Transforms the given node using the XSL and returns the generated XML.
+ *
+ * @param \DOMNode $node
+ * @param array $parameters
+ * @return string The generated XML or FALSE in case of error
+ */
+ public function transformToXml(\DOMNode $node, array $parameters = array())
+ {
+ $this->preTransform($parameters);
+ $xml = parent::transformToXml($node);
+ $this->postTransform($parameters);
+
+ if (false === $xml) {
+ return false;
+ }
+
+ return rtrim($xml, "\r\n ");
+ }
+
+ /**
+ * Transforms the given node using the XSL and returns the generated DOM document.
+ *
+ * @param \DOMNode $node
+ * @param array $parameters
+ * @return \Welldom\Document The generated document or FALSE in case of error
+ */
+ public function transformToDoc(\DOMNode $node, array $parameters = array())
+ {
+ $this->preTransform($parameters);
+ $doc = parent::transformToDoc($node);
+ $this->postTransform();
+
+ if (false === $doc) {
+ return false;
+ }
+
+ // XSLTProcessor::transformToDoc() returns a DOMDocument, not \Welldom\Document
+ // @link https://bugs.php.net/bug.php?id=53693
+ return Document::createFromNode($doc->documentElement, true);
+ }
+
+ /**
+ * Transforms the given node using the XSL and saves the result to the given filename.
+ *
+ * @param \DOMNode $node
+ * @param string $filename
+ * @param array $parameters
+ * @return mixed The number of byte writter or FALSE in case of error
+ */
+ public function transformToUri(\DOMNode $node, $filename, array $parameters = array())
+ {
+ $this->preTransform($parameters);
+ $ret = parent::transformToUri($node, $filename);
+ $this->postTransform($parameters);
+
+ return $ret;
+ }
+
+ /**
+ * Set a list of parameters
+ *
+ * @param array $parameters Associative array (name, value)
+ * @return \Welldom\XsltProcessor
+ */
+ public function setParameters(array $parameters)
+ {
+ $this->setParameter('', $parameters);
+
+ return $this;
+ }
+
+ /**
+ * Removes a list of parameters.
+ *
+ * @param type $parameterNames List of names
+ * @return \Welldom\XsltProcessor
+ */
+ public function removeParameters(array $parameterNames)
+ {
+ foreach ($parameterNames as $name) {
+ $this->removeParameter('', $name);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get last transformation errors
+ *
+ * @return array
+ */
+ public function getLastErrors()
+ {
+ return $this->lastErrors;
+ }
+
+ /**
+ * Method called before any transformation.
+ * Handle errors and set parameters.
+ *
+ * @param array $parameters Transformation specific parameters
+ */
+ protected function preTransform(array $parameters)
+ {
+ if (0 !== count($parameters)) {
+ $this->replacedParameters = array();
+ foreach ($parameters as $name => $value) {
+ $this->replacedParameters[$name] = $this->getParameter('', $name);
+ $this->setParameter('', $name, $value);
+ }
+ }
+
+ XmlErrorHandler::start();
+ }
+
+ /**
+ * Method called after any transformation.
+ * Handle errors and reset parameters.
+ */
+ protected function postTransform()
+ {
+ $this->lastErrors = XmlErrorHandler::getErrors();
+ XmlErrorHandler::clean();
+
+ if (0 !== count($this->replacedParameters))
+ {
+ foreach ($this->replacedParameters as $name => $value) {
+ if (false === $value) {
+ $this->removeParameter('', $name);
+ } else {
+ $this->setParameter('', $name, $value);
+ }
+ }
+ }
+ }
+}
60 src/Welldom/XsltProcessorCollection.php
@@ -0,0 +1,60 @@
+<?php
+
+/*
+ * This file is part of the Welldom package.
+ *
+ * (c) Groupe Express Roularta
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Welldom;
+
+/**
+ * XSLT Processor repository.
+ *
+ * Creates and store them in memory to reduce calls to XsltProcessor::importStylesheet method.
+ *
+ * @author Jérôme Tamarelle <jerome@tamarelle.net>
+ */
+class XsltProcessorCollection
+{
+ /**
+ * @var array
+ */
+ static private $xsltProcessors = array();
+
+ /**
+ * Get an XSLT processor for the given XSL stylesheet.
+ *
+ * @param string $filename XSL file name
+ * @return \XSLTProcessor
+ */
+ static public function getXsltProcessor($filename)
+ {
+ if (false === $realpath = realpath($filename)) {
+ throw new \InvalidArgumentException(sprintf('XSL file "%s" does not exists', $filename));
+ }
+
+ if (!isset(self::$xsltProcessors[$realpath])) {
+ $xslt = new XsltProcessor($filename);
+
+ if ($errors = $xslt->getLastErrors()) {
+ throw new \DOMException("Error while loading XSL stylesheet: \n" . implode("\n", $errors));
+ }
+
+ self::$xsltProcessors[$realpath] = $xslt;
+ }
+
+ return self::$xsltProcessors[$realpath];
+ }
+
+ /**
+ * Unset all XsltProcessors.
+ */
+ public function free()
+ {
+ self::$xsltProcessors = array();
+ }
+}
25 src/autoload.php
@@ -0,0 +1,25 @@
+<?php
+/*
+ * This file is part of the Welldom package.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Simple autoloader that follow the PHP Standards Recommendation #0 (PSR-0)
+ * @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md for more informations.
+ *
+ * @author Jérôme Tamarelle <jerome@tamarelle.net>
+ */
+spl_autoload_register(function($className) {
+ $className = ltrim($className, '\\');
+ if (0 === strpos($className, 'Welldom')) {
+ $fileName = __DIR__ . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $className) . '.php';
+ if (is_file($fileName)) {
+ require $fileName;
+ return true;
+ }
+ }
+ return false;
+});
45 tests/Welldom/Tests/DocumentFragmentTest.php
@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of the Welldom package.
+ *
+ * (c) Groupe Express Roularta
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Welldom\Tests;
+
+/**
+ * @covers \Welldom\DocumentFragment
+ */
+class DocumentFragmentTest extends TestCase
+{
+ public function testAppendXmlError()
+ {
+ $doc = $this->createDocument('<foo />');
+ $fragment = $doc->createDocumentFragment();
+
+ $errors = $fragment->getLastErrors();
+ $this->assertInternalType('array', $errors);
+ $this->assertCount(0, $errors);
+
+ $success = $fragment->appendXML('<invalid att="v><test></invalid>');
+ $this->assertSame(false, $success);
+
+ $errors = $fragment->getLastErrors();
+ $this->assertInternalType('array', $errors);
+ $this->assertCount(5, $errors);
+ }
+
+ public function testGetChildNodes()
+ {
+ $doc = $this->createDocument('<foo />');
+ $fragment = $doc->createDocumentFragment();
+ $fragment->appendXML('<bar>bazbaz</bar>');
+ $nodeList = $fragment->getChildNodes();
+ $this->assertInstanceOf('\Welldom\NodeList', $nodeList);
+ }
+
+}
430 tests/Welldom/Tests/DocumentTest.php
@@ -0,0 +1,430 @@
+<?php
+
+/*
+ * This file is part of the Welldom package.
+ *
+ * (c) Groupe Express Roularta
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Welldom\Tests;
+
+/**
+ * @covers \Welldom\Document
+ */
+use Welldom\Document;
+
+class DocumentTest extends TestCase
+{
+ public function testConstructorEncoding()
+ {
+ $doc = Document::create('<foo />', 'UTF-8');
+
+ $expected = '<?xml version="1.0" encoding="UTF-8"?>'."\n".'<foo/>'."\n";
+ $this->assertEquals($expected, $doc->saveXml());
+
+ $doc->encoding = 'ISO-8859-15';
+ $expected = '<?xml version="1.0" encoding="ISO-8859-15"?>'."\n".'<foo/>'."\n";
+ $this->assertEquals($expected, $doc->saveXml());
+ }
+
+// ::createFromNode()
+
+ public function testCreateFromNode()
+ {
+ $doc = $this->createDocument('<foo><bar>richard</bar></foo>');
+ $node = $doc->documentElement->getChildNodes()->item(0);
+
+ $newDoc = Document::createFromNode($node);
+
+ $this->assertEquals('<bar>richard</bar>', $newDoc->getXml(), '->fromNode()');
+ }
+
+// ->load()
+
+ public function testLoad()
+ {
+ $doc = new Document();
+ $success = $doc->load(FILES_DIR . '/valid.xml');
+ $this->assertTrue($success, '->load() returns true');
+ }
+
+ public function testLoadError()
+ {
+ $doc = new Document();
+ $success = $doc->load(FILES_DIR . '/invalid.xml');
+ $this->assertFalse($success, '->load() returns false');
+ }
+
+// ->loadXML()
+
+ /**
+ * @covers \Welldom\Document::loadXML
+ */
+ public function testLoadXML()
+ {
+ $doc = new Document();
+ $success = $doc->loadXML('<foo><bar/></foo>');
+ $this->assertTrue($success, '->loadXML() returns true');
+ }
+
+ /**
+ * @covers \Welldom\Document::loadXML
+ */
+ public function testLoadXMLError()
+ {
+ $doc = new Document();
+ $this->assertEquals(array(), $doc->getLastErrors());
+
+ $this->assertFalse($doc->loadXML(''), '->loadXML() returns false with empty string');
+ $this->assertInternalType('array', $doc->getLastErrors());
+ $this->assertCount(1, $doc->getLastErrors());
+
+ $this->assertFalse($doc->loadXML('<foo><bar></foo a="1>'), '->loadXML() returns false with invalid XML');
+ $this->assertInternalType('array', $doc->getLastErrors());
+ $this->assertCount(3, $doc->getLastErrors());
+ }
+
+
+// ->getXmlWithoutDoctype()
+
+ public function testGetXml()
+ {
+ $xml = '<foo><bar/></foo>';
+ $doc = $this->createDocument($xml);
+ $this->assertEquals($xml, $doc->getXml());
+ }
+
+ public function testGetXmlEmpty()
+ {
+ $doc = new Document();
+ $this->assertEquals('', $doc->getXml());
+ }
+
+ public function testGetXmlAfterAnError()
+ {
+ $doc = new Document();
+ try {
+ $doc->loadXML('<invalid>');
+ $this->fail();
+ } catch (\Exception $e) {
+ $this->assertEquals('', $doc->getXml());
+ }
+ }
+
+// ->toArray()
+
+ /**
+ * @dataProvider dataForTestToArray
+ */
+ public function testToArray($xml, $root, $expected)
+ {
+ $array = Document::create($xml)->toArray($root);
+ $this->assertEquals($expected, $array, '->toArray()');
+ }
+
+ public function dataForTestToArray()
+ {
+ return array(
+ array(
+ '<bar>bazbaz</bar>',
+ false,
+ array('bar' => 'bazbaz')
+ ),