├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── .php-cs-fixer.dist.php ├── README.md ├── composer.json ├── lib ├── Document.php ├── Element.php ├── Exception │ └── InvalidQueryException.php ├── XPath.php └── XPathAware.php ├── phpstan.neon ├── phpunit.xml.dist └── tests └── Unit ├── DocumentTest.php ├── ElementTest.php └── XPathTest.php /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - 'master' 8 | 9 | env: 10 | fail-fast: true 11 | TZ: "Europe/Paris" 12 | REQUIRED_PHP_EXTENSIONS: "dom" 13 | 14 | jobs: 15 | composer-validate: 16 | name: "Composer validate (${{ matrix.php-version }})" 17 | 18 | runs-on: "ubuntu-latest" 19 | 20 | strategy: 21 | matrix: 22 | php-version: 23 | - '7.3' 24 | 25 | steps: 26 | - 27 | name: "Checkout code" 28 | uses: "actions/checkout@v2" 29 | 30 | - 31 | name: "Install PHP" 32 | uses: "shivammathur/setup-php@v2" 33 | with: 34 | coverage: "none" 35 | php-version: "${{ matrix.php-version }}" 36 | tools: composer:v2 37 | 38 | - 39 | name: "Validate composer.json" 40 | run: "composer validate --strict --no-check-lock" 41 | 42 | php-cs-fixer: 43 | needs: 44 | - "composer-validate" 45 | 46 | name: "PHP-CS-Fixer (${{ matrix.php-version }})" 47 | 48 | runs-on: "ubuntu-latest" 49 | 50 | strategy: 51 | matrix: 52 | php-version: 53 | - '7.4' 54 | 55 | steps: 56 | - 57 | name: "Checkout code" 58 | uses: "actions/checkout@v2" 59 | 60 | - 61 | name: "Install PHP" 62 | uses: "shivammathur/setup-php@v2" 63 | with: 64 | coverage: "none" 65 | extensions: "${{ env.REQUIRED_PHP_EXTENSIONS }}" 66 | php-version: "${{ matrix.php-version }}" 67 | tools: composer:v2 68 | 69 | - 70 | name: "Composer install" 71 | uses: "ramsey/composer-install@v1" 72 | with: 73 | composer-options: "--no-scripts" 74 | 75 | - 76 | name: "Run friendsofphp/php-cs-fixer" 77 | run: "vendor/bin/php-cs-fixer fix --dry-run --diff --verbose" 78 | 79 | phpstan: 80 | needs: 81 | - "composer-validate" 82 | 83 | name: "PHPStan (${{ matrix.php-version }})" 84 | 85 | runs-on: "ubuntu-latest" 86 | 87 | strategy: 88 | matrix: 89 | php-version: 90 | - '7.4' 91 | 92 | steps: 93 | - 94 | name: "Checkout code" 95 | uses: "actions/checkout@v2" 96 | 97 | - 98 | name: "Install PHP" 99 | uses: "shivammathur/setup-php@v2" 100 | with: 101 | coverage: "none" 102 | extensions: "${{ env.REQUIRED_PHP_EXTENSIONS }}" 103 | php-version: "${{ matrix.php-version }}" 104 | tools: composer:v2 105 | 106 | - 107 | name: "Composer install" 108 | uses: "ramsey/composer-install@v1" 109 | with: 110 | composer-options: "--no-scripts" 111 | 112 | - 113 | name: "Run phpstan/phpstan" 114 | run: "vendor/bin/phpstan analyse --level=7 lib" 115 | 116 | tests: 117 | needs: 118 | - "composer-validate" 119 | 120 | name: "PHP ${{ matrix.php-version }} + ${{ matrix.dependency }}" 121 | 122 | runs-on: ubuntu-latest 123 | 124 | continue-on-error: ${{ matrix.allow-failures }} 125 | 126 | strategy: 127 | matrix: 128 | php-version: 129 | - '7.3' 130 | - '7.4' 131 | - '8.0' 132 | dependency: 133 | - 'lowest' 134 | - 'highest' 135 | with-examples: ['yes'] 136 | allow-failures: [false] 137 | include: 138 | - php-version: '7.3' 139 | dependency: 'lowest' 140 | with-examples: 'no' 141 | allow-failures: false 142 | coverage: xdebug 143 | - php-version: '8.1' 144 | dependency: 'highest' 145 | with-examples: 'no' 146 | allow-failures: true 147 | coverage: xdebug 148 | 149 | steps: 150 | - name: "Checkout code" 151 | uses: actions/checkout@v2.3.3 152 | 153 | - name: "Install PHP with extensions" 154 | uses: shivammathur/setup-php@2.7.0 155 | with: 156 | extensions: "${{ env.REQUIRED_PHP_EXTENSIONS }}" 157 | php-version: ${{ matrix.php-version }} 158 | tools: composer:v2 159 | 160 | - name: "Add PHPUnit matcher" 161 | run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 162 | 163 | - name: "Remove friendsofphp/php-cs-fixer" 164 | run: composer remove --dev friendsofphp/php-cs-fixer --no-update 165 | 166 | - name: "Composer install" 167 | uses: "ramsey/composer-install@v1" 168 | with: 169 | dependency-versions: "${{ matrix.dependency }}" 170 | 171 | - name: PHP Info 172 | run: php --version 173 | 174 | - name: "Run tests with PHPUnit" 175 | run: vendor/bin/phpunit --verbose 176 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | .phpunit.result.cache 4 | .php-cs-fixer.cache 5 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | in([ 8 | __DIR__ . '/lib', 9 | __DIR__ . '/tests', 10 | ]) 11 | ; 12 | 13 | return (new Config()) 14 | ->setRiskyAllowed(true) 15 | ->setRules([ 16 | '@PSR12' => true, 17 | 'void_return' => true, 18 | 'binary_operator_spaces' => [ 19 | 'operators' => [ 20 | '=>' => null 21 | ], 22 | ], 23 | 'blank_line_before_statement' => [ 24 | 'statements' => [ 25 | 'break', 26 | 'continue', 27 | 'declare', 28 | 'default', 29 | 'do', 30 | 'exit', 31 | 'for', 32 | 'foreach', 33 | 'goto', 34 | 'if', 35 | 'include', 36 | 'include_once', 37 | 'require', 38 | 'require_once', 39 | 'return', 40 | 'switch', 41 | 'throw', 42 | 'try', 43 | 'while', 44 | 'yield', 45 | ], 46 | ], 47 | 'concat_space' => false, 48 | 'no_unused_imports' => true, 49 | 'php_unit_set_up_tear_down_visibility' => true, 50 | 'phpdoc_align' => [], 51 | 'phpdoc_indent' => false, 52 | 'phpdoc_separation' => true, 53 | 'no_superfluous_phpdoc_tags' => [ 54 | 'allow_mixed' => true 55 | ], 56 | 'fully_qualified_strict_types' => true, 57 | ]) 58 | ->setFinder($finder) 59 | ; 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DOM 2 | === 3 | 4 | **This library is abandoned** 5 | 6 | [![CI](https://github.com/phpbench/dom/actions/workflows/ci.yaml/badge.svg)](https://github.com/phpbench/dom/actions/workflows/ci.yaml) 7 | 8 | This library provides a wrapper for the PHP DOM library which makes your life 9 | easier. 10 | 11 | It wraps the `\DOMDocument`, `\DOMElement` and `\DOMXpath` classes and 12 | throws *exceptions*. 13 | 14 | Example: 15 | 16 | ```php 17 | $dom = new Document(); 18 | $element = $dom->createRoot('example'); 19 | $element->appendChild('boo', 'hello'); 20 | $element->appendChild('baz', 'world'); 21 | 22 | echo $dom->dump(); 23 | // 24 | // 25 | // hello 26 | // world 27 | // 28 | 29 | $element->appendElement('number', 5); 30 | $element->appendElement('number', 10); 31 | 32 | echo $element->evaluate('sum(./number)'); // 15 33 | 34 | $nodeList = $element->query('./number'); 35 | 36 | echo $nodeList->length; // 2 37 | ``` 38 | 39 | Document 40 | -------- 41 | 42 | The `PhpBench\Dom\Document` class wraps the `\DOMDocument` class and replaces the 43 | `\DOMElement` class with the `PhpBench\Dom\Element` class. 44 | 45 | It implements the `XPathAware` interface. 46 | 47 | - `createRoot($name, $value = null)`: Create and return a new root node with `$name` and optional 48 | `$value`. 49 | - `query($query, $context = null)`: Execute a given XPath query on the 50 | document. 51 | - `queryOne($query, $context = null)`: Execute a given XPath query on the 52 | document and return the first element or `NULL`. 53 | - `evaluate($query, $context = null)`: Evaluate the given XPath expression. 54 | - `dump()`: Return a formatted string representation of the document. 55 | 56 | Element 57 | ------- 58 | 59 | Wraps the `\DOMElement` class and is used by default when you instantiate a 60 | `PhpBench\Dom\Document` class. 61 | 62 | It implements the `XPathAware` interface. 63 | 64 | - `appendElement($name $value)`: Create and return an element with name 65 | `$name` and value `$value`. 66 | - `query`, `queryOne` and `evaluate`: As with Document but will use the context of this element by 67 | default. 68 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phpbench/dom", 3 | "description": "DOM wrapper to simplify working with the PHP DOM implementation", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Daniel Leech", 8 | "email": "daniel@dantleech.com" 9 | } 10 | ], 11 | "require": { 12 | "php": "^7.3||^8.0", 13 | "ext-dom": "*" 14 | }, 15 | "require-dev": { 16 | "friendsofphp/php-cs-fixer": "^3.14", 17 | "phpunit/phpunit": "^8.0||^9.0", 18 | "phpstan/phpstan": "^1.10" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "PhpBench\\Dom\\": "lib/" 23 | } 24 | }, 25 | "autoload-dev": { 26 | "psr-4": { 27 | "PhpBench\\Dom\\Tests\\": "tests/" 28 | } 29 | }, 30 | "extra": { 31 | "branch-alias": { 32 | "dev-master": "1.0-dev" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/Document.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace PhpBench\Dom; 13 | 14 | use DOMNode; 15 | use DOMNodeList; 16 | use RuntimeException; 17 | 18 | /** 19 | * Wrapper for the \DOMDocument class. 20 | */ 21 | class Document extends \DOMDocument implements XPathAware 22 | { 23 | /** 24 | * @var XPath|null 25 | */ 26 | private $xpath; 27 | 28 | /** 29 | * @param string $version 30 | * @param string $encoding 31 | */ 32 | public function __construct($version = '1.0', $encoding = null) 33 | { 34 | if ($encoding) { 35 | parent::__construct($version, $encoding); 36 | } else { 37 | parent::__construct($version); 38 | } 39 | $this->registerNodeClass('DOMElement', 'PhpBench\Dom\Element'); 40 | } 41 | 42 | /** 43 | * Create and return a root DOM element. 44 | * 45 | * @param string $name 46 | * 47 | */ 48 | public function createRoot($name): Element 49 | { 50 | $element = $this->appendChild(new Element($name)); 51 | assert($element instanceof Element); 52 | 53 | return $element; 54 | } 55 | 56 | /** 57 | * Return the XPath object bound to this document. 58 | * 59 | */ 60 | public function xpath(): XPath 61 | { 62 | if ($this->xpath) { 63 | return $this->xpath; 64 | } 65 | 66 | $this->xpath = new XPath($this); 67 | 68 | return $this->xpath; 69 | } 70 | 71 | /** 72 | * @return DOMNodeList 73 | */ 74 | public function query($query, DOMNode $context = null): DOMNodeList 75 | { 76 | return $this->xpath()->query($query, $context); 77 | } 78 | 79 | /** 80 | * {@inheritdoc} 81 | */ 82 | public function queryOne($query, DOMNode $context = null): ?Element 83 | { 84 | return $this->xpath()->queryOne($query, $context); 85 | } 86 | 87 | /** 88 | * @return mixed 89 | */ 90 | public function evaluate($expression, DOMNode $context = null) 91 | { 92 | return $this->xpath()->evaluate($expression, $context); 93 | } 94 | 95 | /** 96 | * Return a formatted string representation of the document. 97 | * 98 | */ 99 | public function dump(): string 100 | { 101 | $this->formatOutput = true; 102 | $result = $this->saveXML(); 103 | $this->formatOutput = false; 104 | 105 | if (false === $result) { 106 | throw new RuntimeException('Could not dump XML'); 107 | } 108 | 109 | return $result; 110 | } 111 | 112 | public function duplicate(): Document 113 | { 114 | $dom = new self(); 115 | 116 | if ($this->firstChild) { 117 | $firstChild = $dom->importNode($this->firstChild, true); 118 | $dom->appendChild($firstChild); 119 | } 120 | 121 | 122 | return $dom; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/Element.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace PhpBench\Dom; 13 | 14 | use DOMNode; 15 | use DOMNodeList; 16 | 17 | /** 18 | * Wrapper for the \DOMElement class. 19 | */ 20 | class Element extends \DOMElement implements XPathAware 21 | { 22 | /** 23 | * Create and append a text-node with the given name and value. 24 | */ 25 | public function appendTextNode(string $name, ?string $value): Element 26 | { 27 | $el = new self($name); 28 | $element = $this->appendChild($el); 29 | assert($element instanceof Element); 30 | 31 | $element->appendChild( 32 | $this->owner()->createTextNode($value ?? '') 33 | ); 34 | 35 | return $element; 36 | } 37 | 38 | /** 39 | * Create and append an element with the given name and optionally given value. 40 | * 41 | * Note: The value will not be escaped. Use DOMDocument::createTextNode() to create a text node with escaping support. 42 | */ 43 | public function appendElement(string $name, ?string $value = null): Element 44 | { 45 | $element = $this->appendChild(new self($name, $value)); 46 | assert($element instanceof Element); 47 | 48 | return $element; 49 | } 50 | 51 | /** 52 | * @return DOMNodeList 53 | */ 54 | public function query($xpath, DOMNode $context = null): DOMNodeList 55 | { 56 | return $this->owner()->xpath()->query($xpath, $context ?: $this); 57 | } 58 | 59 | public function queryOne($xpath, DOMNode $context = null): ?Element 60 | { 61 | return $this->owner()->xpath()->queryOne($xpath, $context ?: $this); 62 | } 63 | 64 | /** 65 | * @return mixed 66 | */ 67 | public function evaluate($expression, DOMNode $context = null) 68 | { 69 | return $this->owner()->xpath()->evaluate($expression, $context ?: $this); 70 | } 71 | 72 | /** 73 | * Dump the current node 74 | */ 75 | public function dump(): string 76 | { 77 | $document = new Document(); 78 | $document->appendChild($document->importNode($this, true)); 79 | 80 | return $document->dump(); 81 | } 82 | 83 | private function owner(): Document 84 | { 85 | $owner = $this->ownerDocument; 86 | assert($owner instanceof Document); 87 | 88 | return $owner; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/Exception/InvalidQueryException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace PhpBench\Dom\Exception; 13 | 14 | class InvalidQueryException extends \InvalidArgumentException 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /lib/XPath.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace PhpBench\Dom; 13 | 14 | use DOMNode; 15 | use DOMNodeList; 16 | use RuntimeException; 17 | 18 | /** 19 | * Wrapper for the \DOMXPath class. 20 | */ 21 | class XPath extends \DOMXPath 22 | { 23 | /** 24 | * {@inheritdoc} 25 | * 26 | * @param mixed $contextnode 27 | * @param bool $registerNodeNS 28 | */ 29 | #[\ReturnTypeWillChange] 30 | public function evaluate($expression, $contextnode = null, $registerNodeNS = true) 31 | { 32 | $result = $this->execute('evaluate', 'expression', $expression, $contextnode, $registerNodeNS); 33 | 34 | return $result; 35 | } 36 | 37 | /** 38 | * @param bool $registerNodeNS 39 | * @param mixed $contextnode 40 | * 41 | * @return DOMNodeList 42 | */ 43 | #[\ReturnTypeWillChange] 44 | public function query($expression, $contextnode = null, $registerNodeNS = true): DOMNodeList 45 | { 46 | $list = $this->execute('query', 'query', $expression, $contextnode, $registerNodeNS); 47 | 48 | if (!$list instanceof DOMNodeList) { 49 | throw new RuntimeException(sprintf('Expected XPAth expression to return DOMNodeList, got "%s"', is_object($list) ? get_class($list) : gettype($list))); 50 | } 51 | 52 | return $list; 53 | } 54 | 55 | public function queryOne(string $expr, DOMNode $contextEl = null, bool $registerNodeNs = false): ?Element 56 | { 57 | $nodeList = $this->query($expr, $contextEl, $registerNodeNs); 58 | 59 | if (0 === $nodeList->length) { 60 | return null; 61 | } 62 | 63 | $node = $nodeList->item(0); 64 | 65 | if (!$node instanceof Element) { 66 | throw new RuntimeException(sprintf( 67 | 'Expected "%s" but got "%s"', 68 | Element::class, 69 | $node ? get_class($node) : gettype($node) 70 | )); 71 | } 72 | 73 | return $node; 74 | } 75 | 76 | /** 77 | * Execute the given xpath method and cactch any errors. 78 | * 79 | * @param mixed $contextEl 80 | * 81 | * @return mixed 82 | */ 83 | #[\ReturnTypeWillChange] 84 | private function execute(string $method, string $context, string $query, $contextEl = null, bool $registerNodeNs = false) 85 | { 86 | libxml_use_internal_errors(true); 87 | 88 | $value = @parent::$method($query, $contextEl, $registerNodeNs); 89 | 90 | $xmlErrors = libxml_get_errors(); 91 | 92 | if ($xmlErrors) { 93 | $errors = []; 94 | 95 | foreach ($xmlErrors as $xmlError) { 96 | $errors[] = sprintf('[%s] %s', $xmlError->code, $xmlError->message); 97 | } 98 | libxml_clear_errors(); 99 | 100 | throw new Exception\InvalidQueryException(sprintf( 101 | 'Errors encountered when evaluating XPath %s "%s": %s%s', 102 | $context, 103 | $query, 104 | PHP_EOL, 105 | implode(PHP_EOL, $errors) 106 | )); 107 | } 108 | 109 | libxml_use_internal_errors(false); 110 | 111 | return $value; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/XPathAware.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace PhpBench\Dom; 13 | 14 | use DOMNode; 15 | use DOMNodeList; 16 | 17 | interface XPathAware 18 | { 19 | /** 20 | * Perform an xpath query on this document, optionally with 21 | * the given context node. 22 | * 23 | * If this interface is applied to an Element, then the element 24 | * should be used as the context if no context is given. 25 | * 26 | * @param string $query 27 | * @param \DOMNode $context 28 | * 29 | * @return DOMNodeList 30 | */ 31 | public function query($query, DOMNode $context = null); 32 | 33 | /** 34 | * As with XPathAware::query but return a single node or NULL if no node was found. 35 | * 36 | * @param string $query 37 | * @param \DOMNode $context 38 | * 39 | * @return Element|null 40 | */ 41 | public function queryOne($query, DOMNode $context = null); 42 | 43 | /** 44 | * Evaluate an XPath expression on this document, optionally 45 | * with the given context node. 46 | * 47 | * If this interface is applied to an Element, then the element 48 | * should be used as the context if no context is given. 49 | * 50 | * @param string $expression 51 | * @param \DOMNode $context 52 | * 53 | * @return mixed 54 | */ 55 | public function evaluate($expression, DOMNode $context = null); 56 | } 57 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: max 3 | paths: 4 | - lib 5 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | ./tests 12 | 13 | 14 | 15 | 16 | 17 | . 18 | 19 | vendor/ 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/Unit/DocumentTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace PhpBench\Dom\Tests\Unit; 13 | 14 | use PhpBench\Dom\Document; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | class DocumentTest extends TestCase 18 | { 19 | /** 20 | * @var Document 21 | */ 22 | private $document; 23 | 24 | protected function setUp(): void 25 | { 26 | $this->document = new Document(1.0); 27 | } 28 | 29 | /** 30 | * It should perform an XPath query. 31 | */ 32 | public function testQuery(): void 33 | { 34 | $this->document->loadXml($this->getXml()); 35 | $nodeList = $this->document->query('//record'); 36 | $this->assertInstanceOf('DOMNodeList', $nodeList); 37 | $this->assertEquals(2, $nodeList->length); 38 | } 39 | 40 | /** 41 | * It should evaluate an XPath expression. 42 | */ 43 | public function testEvaluate(): void 44 | { 45 | $this->document->loadXml($this->getXml()); 46 | $result = $this->document->evaluate('count(//record)'); 47 | $this->assertEquals(2, $result); 48 | } 49 | 50 | /** 51 | * It should create a root element. 52 | */ 53 | public function testCreateRoot(): void 54 | { 55 | $this->document->createRoot('hello'); 56 | $this->assertStringContainsString('', $this->document->saveXml()); 57 | } 58 | 59 | /** 60 | * It should return a formatted string representation of the document. 61 | */ 62 | public function testDump(): void 63 | { 64 | $this->document->loadXml($this->getXml()); 65 | $this->assertEquals( 66 | trim($this->getXml()), 67 | trim($this->document->dump()) 68 | ); 69 | } 70 | 71 | /** 72 | * It should provide a duplicate version of itself. 73 | */ 74 | public function testDuplicate(): void 75 | { 76 | $this->document->loadXml($this->getXml()); 77 | $duplicate = $this->document->duplicate(); 78 | $this->assertNotsame($this->document, $duplicate); 79 | $this->assertNotsame($this->document->firstChild, $duplicate->firstChild); 80 | $this->assertNotsame($this->document->firstChild->firstChild, $duplicate->firstChild->firstChild); 81 | } 82 | 83 | 84 | private function getXml() 85 | { 86 | $xml = << 88 | 89 | 90 | Hello 91 | 92 | 93 | World 94 | 95 | 96 | EOT; 97 | 98 | return $xml; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tests/Unit/ElementTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace PhpBench\Dom\Tests\Unit; 13 | 14 | use PhpBench\Dom\Document; 15 | use PHPUnit\Framework\TestCase; 16 | 17 | class ElementTest extends TestCase 18 | { 19 | private $element; 20 | private $document; 21 | 22 | protected function setUp(): void 23 | { 24 | $this->document = new Document(); 25 | $this->element = $this->document->createRoot('test'); 26 | } 27 | 28 | /** 29 | * It should create and append a child element. 30 | */ 31 | public function testAppendElement(): void 32 | { 33 | $element = $this->element->appendElement('hello'); 34 | $result = $this->document->evaluate('count(//hello)'); 35 | $this->assertInstanceOf('PhpBench\Dom\Element', $element); 36 | $this->assertEquals(1, $result); 37 | } 38 | 39 | /** 40 | * It should create and append text. 41 | */ 42 | public function testAppendTextNode(): void 43 | { 44 | $element = $this->element->appendTextNode('hello', 'fix&foxy'); 45 | $result = $this->document->evaluate('count(//hello)'); 46 | $this->assertInstanceOf('PhpBench\Dom\Element', $element); 47 | $this->assertEquals(1, $result); 48 | } 49 | 50 | /** 51 | * It should exeucte an XPath query. 52 | */ 53 | public function testQuery(): void 54 | { 55 | $boo = $this->element->appendElement('boo'); 56 | $nodeList = $this->element->query('.//*'); 57 | $this->assertInstanceOf('DOMNodeList', $nodeList); 58 | $this->assertEquals(1, $nodeList->length); 59 | $nodeList = $boo->query('.//*'); 60 | $this->assertEquals(0, $nodeList->length); 61 | } 62 | 63 | /** 64 | * It should evaluate an XPath expression. 65 | */ 66 | public function testEvaluate(): void 67 | { 68 | $boo = $this->element->appendElement('boo'); 69 | $count = $this->element->evaluate('count(.//*)'); 70 | $this->assertEquals(1, $count); 71 | $count = $boo->evaluate('count(.//*)'); 72 | $this->assertEquals(0, $count); 73 | } 74 | 75 | /** 76 | * It should query for one element. 77 | */ 78 | public function testQueryOne(): void 79 | { 80 | $boo = $this->element->appendElement('boo'); 81 | $node = $this->element->queryOne('./boo'); 82 | $this->assertSame($boo, $node); 83 | } 84 | 85 | /** 86 | * It should return null if one element is queried for an it none exist. 87 | */ 88 | public function testQueryOneNone(): void 89 | { 90 | $node = $this->element->queryOne('./boo'); 91 | $this->assertNull($node); 92 | } 93 | 94 | /** 95 | * It should return the XML contained in the node. 96 | */ 97 | public function testDumpNode(): void 98 | { 99 | $this->element->appendElement('boo'); 100 | $dump = $this->element->dump(); 101 | 102 | $this->assertEquals(<< 104 | 105 | 106 | 107 | 108 | EOT 109 | , $dump); 110 | } 111 | 112 | private function getXml() 113 | { 114 | $xml = << 116 | 117 | 118 | Hello 119 | 120 | 121 | World 122 | 123 | 124 | EOT; 125 | 126 | return $xml; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /tests/Unit/XPathTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace PhpBench\Dom\Tests\Unit; 13 | 14 | use PhpBench\Dom\Document; 15 | use PhpBench\Dom\Exception\InvalidQueryException; 16 | use PHPUnit\Framework\TestCase; 17 | 18 | class XPathTest extends TestCase 19 | { 20 | /** 21 | * It should throw an exception if the xpath query is invalid. 22 | */ 23 | public function testQueryException(): void 24 | { 25 | $this->expectException(InvalidQueryException::class); 26 | $this->getDocument()->query('//article[noexistfunc() = "as"]'); 27 | } 28 | 29 | /** 30 | * It should NOT throw an exception if the expression evaluates as false. 31 | */ 32 | public function testEvaluateFalse(): void 33 | { 34 | $result = $this->getDocument()->evaluate('boolean(count(//foo))'); 35 | $this->assertFalse($result); 36 | } 37 | 38 | private function getDocument() 39 | { 40 | $xml = << 42 | 43 |
44 | Morning 45 |
46 |
47 | Afternoon 48 |
49 |
50 | EOT; 51 | 52 | $document = new Document(); 53 | $document->loadXml($xml); 54 | 55 | return $document; 56 | } 57 | } 58 | --------------------------------------------------------------------------------