License updated to GPLv3

🧲 New features
Custom user role permissions
Employee edit form updated
Employee daily task list
Attendance and employee distribution charts on dashboard
Improvements to company structure and company assets module
Improved tables for displaying data in several modules
Faster data loading (specially for employee module)
Initials based profile pictures
Re-designed login page
Re-designed user profile page
Improvements to filtering
New REST endpoints for employee qualifications

🐛 Bug fixes
Fixed, issue with managers being able to create performance reviews for employees who are not their direct reports
Fixed, issues related to using full profile image instead of using smaller version of profile image
Changing third gender to other
Improvements and fixes for internal frontend data caching
This commit is contained in:
Thilina Pituwala
2020-10-31 19:02:37 +01:00
parent 86b8345505
commit b1df0037db
29343 changed files with 867614 additions and 2191082 deletions

View File

@@ -0,0 +1,9 @@
.buildpath
.project
.settings
/fDOMDocument-1.1.0.tgz
.idea
build
composer.lock
composer.phar
vendor/

View File

@@ -0,0 +1,34 @@
language: php
sudo: false
php:
- "5.3"
- "5.4"
- "5.5"
- "5.6"
- "7.0"
- "7.1"
- nightly
# - hhvm
matrix:
allow_failures:
- php: nightly
# - php: hhvm
fast_finish: true
cache:
directories:
- $HOME/.composer/cache
notifications:
email: false
before_script:
- composer self-update
- composer install
- composer require --dev "phpunit/phpunit ^4.8.35|^5.6|^6.0"
script:
- vendor/bin/phpunit --verbose

View File

@@ -0,0 +1,31 @@
fDOMDocument
Copyright (c) 2010-2012 Arne Blankerts <arne@blankerts.de>
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Arne Blankerts nor the names of contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,161 @@
fDOMDocument
============
The classes contained within this repository extend the standard DOM to use exceptions at
all occasions of errors instead of PHP warnings or notices. They also add various custom methods
and shortcuts for convenience and to simplify the usage of DOM.
[![Build Status](https://travis-ci.org/theseer/fDOMDocument.png)](https://travis-ci.org/theseer/fDOMDocument)
Requirements
------------
PHP: 5.3.3 (5.3.0-5.3.2 had serious issues with spl stacked autoloaders)
Extensions: dom, libxml
Installation
------------
Apart from cloning this repository, fDOMDocument can be installed using by any of the following methods.
##### Composer
As fDOMDocument is a library and does not provide any cli tools, you can only add it to your own project:
{
"require": {
"theseer/fdomdocument": "^1.6"
}
}
##### YUM/DNF (Fedora / Redhat / CentOS)
The following command will install fDOMDocument via its RPM package:
sudo yum install php-theseer-fDOMDocument
Usage
-----
fDOMDocument is designed as a drop in replacement for DOMDocument. You can either use the composer generated
autoloader or the provided one.
Usage Samples
-------------
<?php
require '/path/to/autoload.php';
$dom = new TheSeer\fDOM\fDOMDocument();
try {
$dom->loadXML('<?xml version="1.0" ?><root><child name="foo" /></root>');
} catch (fDOMException $e) {
die($e);
}
$child = $dom->queryOne('//child');
print_r($child->getAttribute('name'));
print_r($child->getAttribute('missing','DefaultValue'));
?>
Changelog
---------
##### Release 1.6.6
* Merge PRs 33+34: Add support for parameter "asTextNode" to fDOMElement::appendElement().
fDOMElement::appendElementNS() and fDOMElement::appendElementPrefix
##### Releaes 1.6.5
* Revert git exports limitations as they cause unwanted side effects
##### Releaes 1.6.4
* Merge PR 31 to optimize travis builds and git exports (Thanks to @willemstuursma)
##### Releaes 1.6.3
* Merge PR 29 to fix issues with PHP 7.2
##### Release 1.6.2
* Handle empty string warings from PHP
##### Release 1.6.1
* Added Workaround for [HHVM Issue #5412](https://github.com/facebook/hhvm/issues/5412)
##### Release 1.6.0
* Added ```createElement*``` to ```fDOMEmenet``` and ```fDOMDocumentFragment``` as shortcuts
* Added ```appendElement*``` to ```fDOMDocumentFragment``` as shortcuts
* Enhanced the exception messages of save errors with filenames to contain the filename
* Fixed fDomDocumentFragment::__toString to actually work
* Updated / Added some tests
##### Release 1.5.0
* Added ```select``` to ```fDOMDocument```,```fDOMElement``` and ```fDOMNode``` to support
CSS Selectors in favor of XPath only to find nodes
* Added ```query``` and ```queryOne``` forwardes to ```fDOMNode```
##### Release 1.4.3
* Added ```saveXML``` and ```saveHTML``` to ```fDOMNode``` and ```fDOMElement``` as a
shortcut to calling those methods on the ownerDocument
##### Release 1.4.2
* Added ```__toString``` support to ```fDOMNode```, ```fDOMElement```, ```fDOMDocument``` and ```fDOMDocumentFragment```
##### Release 1.4.1
* Removed unused Interface ```fDOMNodeInterface``` from code base
##### Release 1.4.0
* Added XPathQuery helper object, allowing for a prepared statement alike API around XPath
##### Release 1.3.2
* Added ```__clone``` method to reset domxpath object when domdocument gets cloned (Thanks to Markus Ineichen for pointing it out)
##### Release 1.3.1
* PHP 5.3 compatibility: changed interal behavior for incompatible changes from PHP 5.3 to 5.4 (Thanks to Jens Graefe for pointing it out)
##### Release 1.3.0
* Added appendTextNode method (Thanks to Markus Ineichen)
* Added appendElement / appendElementNS to DOMDocument to support documentElement "creation" (Thanks to Markus Ineichen)
* Overwrite createElement / createElementNS to throw exception on error
* Removed fDOMFilter code: Unmaintained and broken in its current form
* Added (static) Flag for fDOMException to globally enable full exception message
* Added Unit tests
##### Release 1.2.4
* PHP 5.4 compatibilty: added support for optional options bitmask on additional methods
##### Release 1.2.3
* Cleanup code style to adhere coding standard
* Added entity support for Attributes
* Added phpcs file to make coding standard public
##### Release 1.2.2
* Fix Exception to not overwrite final methods of \Exception
##### Release 1.2.1
* Changed fDOMDocument to be no longer final, use lsb to lookup actual class in constructor.
This should fix test/mock issues.
##### Release 1.2.0
* Changed fException to be more compatible with standard exceptions by adding a switch to get full info by getMessage()
* Merged setAttributes() and setAttributesNS() methods from Andreas
* Fixed internal registerNamespace variable mixup
##### Release 1.1.0
* Renamed files to mimic classname cases
* Fixed inSameDocument to support DOMDocument as well as DOMNodes
* Added fDOMXPath class providing queryOne(), qoute() and prepare()
* Adjusted forwarders in fDOMDocument to make use of new object
* Fixed various return values to statically return true for compatibility with original API
* Applied Workaround to fix potential problems with lost references to instances of fDOMDocument
* Support registerPHPFunctions
* Bump Copyright
* Added missing docblocks
##### Release 1.0.2
* Indenting and typo fixes, minor bugfixes
##### Release 1.0.1
* Bugfix: typehints corrected
##### Release 1.0.0
* Initial release

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by PHP Project Wizard (PPW) 1.0.4 on Fri Mar 11 16:37:31 CET 2011 -->
<project name="fDOMDocument" default="build" basedir=".">
<property name="source" value="src"/>
<target name="clean" description="Clean up and create artifact directories">
<delete dir="${basedir}/build/api"/>
<delete dir="${basedir}/build/code-browser"/>
<delete dir="${basedir}/build/coverage"/>
<delete dir="${basedir}/build/logs"/>
<delete dir="${basedir}/build/pdepend"/>
<mkdir dir="${basedir}/build/api"/>
<mkdir dir="${basedir}/build/code-browser"/>
<mkdir dir="${basedir}/build/coverage"/>
<mkdir dir="${basedir}/build/logs"/>
<mkdir dir="${basedir}/build/pdepend"/>
</target>
<target name="phpab">
<exec executable="phpab">
<arg line='-o src/autoload.php src' />
</exec>
</target>
<target name="phpunit" description="Run unit tests using PHPUnit and generates junit.xml and clover.xml">
<exec executable="phpunit" failonerror="true"/>
</target>
<target name="parallelTasks" description="Run the pdepend, phpmd, phpcpd, phpcs, phpdoc and phploc tasks in parallel using a maximum of 2 threads.">
<parallel threadCount="2">
<sequential>
<antcall target="pdepend"/>
<antcall target="phpmd"/>
</sequential>
<antcall target="phpcpd"/>
<antcall target="phpcs"/>
<antcall target="phploc"/>
</parallel>
</target>
<target name="pdepend" description="Generate jdepend.xml and software metrics charts using PHP_Depend">
<exec executable="pdepend">
<arg line="--jdepend-xml=${basedir}/build/logs/jdepend.xml
--jdepend-chart=${basedir}/build/pdepend/dependencies.svg
--overview-pyramid=${basedir}/build/pdepend/overview-pyramid.svg
${source}" />
</exec>
</target>
<target name="phpmd" description="Generate pmd.xml using PHPMD">
<exec executable="phpmd">
<arg line="${source}
xml
codesize,design,naming,unusedcode
--reportfile ${basedir}/build/logs/pmd.xml" />
</exec>
</target>
<target name="phpcpd" description="Generate pmd-cpd.xml using PHPCPD">
<exec executable="phpcpd">
<arg line="--log-pmd ${basedir}/build/logs/pmd-cpd.xml ${source}" />
</exec>
</target>
<target name="phploc" description="Generate phploc.csv">
<exec executable="phploc">
<arg line="--log-csv ${basedir}/build/logs/phploc.csv ${source}" />
</exec>
</target>
<target name="phpcs" description="Generate checkstyle.xml using PHP_CodeSniffer">
<exec executable="phpcs" output="/dev/null">
<arg line="--report=checkstyle
--report-file=${basedir}/build/logs/checkstyle.xml
--standard=phpcs.xml
${source}" />
</exec>
</target>
<target name="phpcb" description="Aggregate tool output with PHP_CodeBrowser">
<exec executable="phpcb">
<arg line="--log ${basedir}/build/logs
--source ${source}
--output ${basedir}/build/code-browser" />
</exec>
</target>
<target name="build" depends="clean,parallelTasks,phpunit,phpcb"/>
</project>

View File

@@ -0,0 +1,26 @@
{
"name": "theseer/fdomdocument",
"description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.",
"homepage": "https://github.com/theseer/fDOMDocument",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Arne Blankerts",
"email": "arne@blankerts.de",
"role": "lead"
}
],
"support": {
"issues": "https://github.com/theseer/fDOMDocument/issues"
},
"require": {
"php": ">=5.3.3",
"ext-dom": "*",
"lib-libxml": "*"
},
"autoload": {
"classmap": [
"src/"
]
}
}

View File

@@ -0,0 +1,62 @@
%define _pearDir /usr/share/pear/
%define _sourcedir src/
Summary: fDOMDocument - An Extension to PHP's standard DOM to add various convenience methods and exceptions by default
Name: fDOMDocument
Version: 1.1.0
Release: 1
Group: System Environment/Libraries
License: Arne Blankerts
Vendor: Arne Blankerts
URL: https://github.com/theseer/
#Source:
Provides: fDOMDocument-%{version}%{release}
BuildRoot: %{_tmppath}/%{name}-%{version}%{release}-root-%(%{__id_u} -n)
BuildArch: noarch
Requires: php-common, php-dom
%description
fDOMDocument - An Extension to PHP's standard DOM to add various convenience methods and exceptions by default
%install
[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
install -m 755 -d $RPM_BUILD_ROOT%{_pearDir}TheSeer
install -m 755 -d $RPM_BUILD_ROOT%{_pearDir}TheSeer/fDOMDocument
install -m 755 -d $RPM_BUILD_ROOT%{_pearDir}TheSeer/fDOMDocument/fDOMFilter
cp %{_sourcedir}fDOMDocument.php $RPM_BUILD_ROOT%{_pearDir}TheSeer/fDOMDocument/fDOMDocument.php
cp %{_sourcedir}fDOMDocumentFragment.php $RPM_BUILD_ROOT%{_pearDir}TheSeer/fDOMDocument/fDOMDocumentFragment.php
cp %{_sourcedir}fDOMElement.php $RPM_BUILD_ROOT%{_pearDir}TheSeer/fDOMDocument/fDOMElement.php
cp %{_sourcedir}fDOMException.php $RPM_BUILD_ROOT%{_pearDir}TheSeer/fDOMDocument/fDOMException.php
cp %{_sourcedir}fDOMFilter.php $RPM_BUILD_ROOT%{_pearDir}TheSeer/fDOMDocument/fDOMFilter.php
cp %{_sourcedir}fDOMNode.php $RPM_BUILD_ROOT%{_pearDir}TheSeer/fDOMDocument/fDOMNode.php
cp %{_sourcedir}fDOMXPath.php $RPM_BUILD_ROOT%{_pearDir}TheSeer/fDOMDocument/fDOMXPath.php
cp %{_sourcedir}fDOMFilter/xhtml.php $RPM_BUILD_ROOT%{_pearDir}TheSeer/fDOMDocument/fDOMFilter/xhtml.php
phpab -o $RPM_BUILD_ROOT%{_pearDir}TheSeer/fDOMDocument/autoload.php TheSeer/fDOMDocument
%post
%files
%defattr(-,root,root)
%dir %{_pearDir}TheSeer/fDOMDocument
%dir %{_pearDir}TheSeer/fDOMDocument/fDOMFilter
%{_pearDir}TheSeer/fDOMDocument/*
%{_pearDir}TheSeer/fDOMDocument/fDOMFilter/*
%{_pearDir}TheSeer/fDOMDocument/autoload.php
%{_pearDir}TheSeer/fDOMDocument/fDOMDocument.php
%{_pearDir}TheSeer/fDOMDocument/fDOMDocumentFragment.php
%{_pearDir}TheSeer/fDOMDocument/fDOMElement.php
%{_pearDir}TheSeer/fDOMDocument/fDOMException.php
%{_pearDir}TheSeer/fDOMDocument/fDOMFilter.php
%{_pearDir}TheSeer/fDOMDocument/fDOMNode.php
%{_pearDir}TheSeer/fDOMDocument/fDOMXPath.php
%{_pearDir}TheSeer/fDOMDocument/fDOMFilter/xhtml.php
%changelog
* Tue Jul 04 2011 Maik 'M4ikT' Thieme <maik.thieme@gmail.com> 1.1.0
- Initial package release

View File

@@ -0,0 +1,40 @@
<?xml version="1.0"?>
<ruleset name="Arne">
<description>Arne Blankerts' coding standard</description>
<rule ref="Generic.CodeAnalysis.ForLoopShouldBeWhileLoop"/>
<rule ref="Generic.CodeAnalysis.ForLoopWithTestFunctionCall"/>
<rule ref="Generic.CodeAnalysis.JumbledIncrementer"/>
<rule ref="Generic.CodeAnalysis.UnconditionalIfStatement"/>
<rule ref="Generic.CodeAnalysis.UnnecessaryFinalModifier"/>
<rule ref="Generic.CodeAnalysis.UselessOverridingMethod"/>
<rule ref="Generic.Commenting.Todo"/>
<rule ref="Generic.ControlStructures.InlineControlStructure"/>
<rule ref="Generic.Files.LineEndings">
<properties>
<property name="eolChar" value="\n"/>
</properties>
</rule>
<rule ref="Generic.Formatting.DisallowMultipleStatements"/>
<rule ref="Generic.Formatting.NoSpaceAfterCast"/>
<rule ref="Generic.Functions.OpeningFunctionBraceKernighanRitchie" />
<rule ref="Generic.Functions.FunctionCallArgumentSpacing" />
<rule ref="PEAR.Functions.ValidDefaultValue"/>
<rule ref="Generic.NamingConventions.ConstructorName"/>
<!-- <rule ref="PEAR.NamingConventions.ValidClassName"/>-->
<rule ref="Generic.PHP.DisallowShortOpenTag"/>
<rule ref="Generic.PHP.NoSilencedErrors"/>
<rule ref="Generic.WhiteSpace.DisallowTabIndent"/>
<!-- Namespace handling broken, so disabled -->
<!--<rule ref="Generic.WhiteSpace.ScopeIndent"/>-->
<rule ref="Squiz.WhiteSpace.SuperfluousWhitespace"/>
</ruleset>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
verbose="true"
bootstrap="src/autoload.php">
<testsuites>
<testsuite name="fDOMDocument">
<directory suffix=".test.php">tests</directory>
</testsuite>
</testsuites>
<logging>
<log type="coverage-html" target="build/coverage" title="fDOMDocument"
charset="UTF-8" yui="true" highlight="true"
lowUpperBound="35" highLowerBound="70"/>
<log type="coverage-clover" target="build/logs/clover.xml"/>
<log type="junit" target="build/logs/junit.xml" logIncompleteSkipped="false"/>
</logging>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
<exclude>
<directory suffix=".php">src/bootstrap</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,205 @@
<?php
/**
* Copyright (c) 2010-2017 Arne Blankerts <arne@blankerts.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Arne Blankerts nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @link http://github.com/theseer/fdomdocument
*
*/
namespace TheSeer\fDOM {
/**
* Class XPathQuery
*
* @package TheSeer\fDOM
*/
class XPathQuery {
/**
* @var string
*/
private $query;
/**
* Key-value Map for bound values
*
* @var array
*/
private $values = array();
/**
* @param string $query
*/
public function __construct($query) {
$this->setQuery($query);
}
/**
* Set Query.
*
* @param string $query
*/
private function setQuery($query) {
$this->query = $query;
$res = preg_match_all('/(:(\w*))/', $query, $matches);
if ($res > 0) {
$this->values = array_fill_keys($matches[2], '');
}
}
/**
* Returns keys.
*
* @return array
*/
public function getKeys() {
return array_keys($this->values);
}
/**
* Bind value to key.
*
* @param string $key
* @param string $value
*
* @throws XPathQueryException
*/
public function bind($key, $value) {
if (!array_key_exists($key, $this->values)) {
throw new XPathQueryException("'$key' not found in query'", XPathQueryException::KeyNotFound );
}
$this->values[$key] = $value;
}
/**
* Generate query.
*
* @param \DOMNode $ctx
* @param array $values
*
* @return string
*/
public function generate(\DOMNode $ctx, array $values = NULL) {
return $this->buildQuery($this->getXPathObjectFor($ctx), $values);
}
/**
* Evaluate Query.
*
* @param \DOMNode $ctx
* @param array $values
* @param bool $registerNodeNS
*
* @throws fDOMException
*
* @return mixed
*/
public function evaluate(\DOMNode $ctx, array $values = NULL, $registerNodeNS = TRUE) {
$xp = $this->getXPathObjectFor($ctx);
return $xp->evaluate($this->buildQuery($xp, $values), $ctx, $registerNodeNS);
}
/**
* Execute Query.
*
* @param \DOMNode $ctx
* @param array $values
* @param bool $registerNodeNS
*
* @throws fDOMException
*
* @return mixed
*/
public function query(\DOMNode $ctx, array $values = NULL, $registerNodeNS = TRUE) {
$xp = $this->getXPathObjectFor($ctx);
return $xp->evaluate($this->buildQuery($xp, $values), $ctx, $registerNodeNS);
}
/**
* Execute Query and return first result.
*
* @param \DOMNode $ctx
* @param array $values
* @param bool $registerNodeNS
*
* @return \DOMNode
*/
public function queryOne(\DOMNode $ctx, array $values = NULL, $registerNodeNS = TRUE) {
$xp = $this->getXPathObjectFor($ctx);
return $xp->queryOne($this->buildQuery($xp, $values), $ctx, $registerNodeNS);
}
/**
* Return xPath for node
*
* @param \DOMNode $ctx
*
* @throws fDOMException
*
* @return fDOMXPath
*/
private function getXPathObjectFor(\DOMNode $ctx) {
$dom = $ctx instanceof \DOMDocument ? $ctx : $ctx->ownerDocument;
if ($dom instanceOf fDOMDocument) {
return $dom->getDOMXPath();
}
return new fDOMXPath($dom);
}
/**
* Build query using values.
*
* @param fDOMXPath $xp
* @param array $values
*
* @throws XPathQueryException
*
* @return string
*/
private function buildQuery(fDOMXPath $xp, array $values = NULL) {
$backup = $this->values;
if (is_array($values) && count($values) > 0) {
foreach($values as $k => $v) {
$this->bind($k, $v);
}
}
$query = $xp->prepare($this->query, $this->values);
$this->values = $backup;
return $query;
}
}
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* Copyright (c) 2010-2017 Arne Blankerts <arne@blankerts.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Arne Blankerts nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @link http://github.com/theseer/fdomdocument
*
*/
namespace TheSeer\fDOM {
class XPathQueryException extends \Exception {
const KeyNotFound = 1;
}
}

View File

@@ -0,0 +1,32 @@
<?php
// @codingStandardsIgnoreFile
// @codeCoverageIgnoreStart
// this is an autogenerated file - do not edit
spl_autoload_register(
function($class) {
static $classes = null;
if ($classes === null) {
$classes = array(
'theseer\\fdom\\css\\dollarequalrule' => '/css/DollarEqualRule.php',
'theseer\\fdom\\css\\notrule' => '/css/NotRule.php',
'theseer\\fdom\\css\\nthchildrule' => '/css/NthChildRule.php',
'theseer\\fdom\\css\\regexrule' => '/css/RegexRule.php',
'theseer\\fdom\\css\\ruleinterface' => '/css/RuleInterface.php',
'theseer\\fdom\\css\\translator' => '/css/Translator.php',
'theseer\\fdom\\fdomdocument' => '/fDOMDocument.php',
'theseer\\fdom\\fdomdocumentfragment' => '/fDOMDocumentFragment.php',
'theseer\\fdom\\fdomelement' => '/fDOMElement.php',
'theseer\\fdom\\fdomexception' => '/fDOMException.php',
'theseer\\fdom\\fdomnode' => '/fDOMNode.php',
'theseer\\fdom\\fdomxpath' => '/fDOMXPath.php',
'theseer\\fdom\\xpathquery' => '/XPathQuery.php',
'theseer\\fdom\\xpathqueryexception' => '/XPathQueryException.php'
);
}
$cn = strtolower($class);
if (isset($classes[$cn])) {
require __DIR__ . $classes[$cn];
}
}
);
// @codeCoverageIgnoreEnd

View File

@@ -0,0 +1,31 @@
<?php
namespace TheSeer\fDOM\CSS {
class DollarEqualRule implements RuleInterface {
/**
* @param $selector
*
* @return string
*/
public function apply($selector) {
return preg_replace_callback(
'/\[([a-zA-Z0-9\_\-]+)\$=([^\]]+)\]/',
array($this, 'callback'),
$selector
);
}
/**
* Build query from matches.
*
* @param array $matches
*
* @return string
*/
private function callback(array $matches) {
return '[substring(@' . $matches[1] . ',string-length(@' . $matches[1] . ')-' . (strlen($matches[2]) - 3) . ')=' . $matches[1] . ']';
}
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace TheSeer\fDOM\CSS {
class NotRule implements RuleInterface {
/**
* @var Translator
*/
private $translator;
/**
* @param Translator $translator
*/
public function __construct(Translator $translator) {
$this->translator = $translator;
}
/**
* @param $selector
*
* @return string
*/
public function apply($selector) {
return preg_replace_callback(
'/([a-zA-Z0-9\_\-\*]+):not\(([^\)]*)\)/',
array($this, 'callback'),
$selector
);
}
/**
* @param array $matches
*
* @return string
*/
private function callback(array $matches) {
$subresult = preg_replace(
'/^[^\[]+\[([^\]]*)\].*$/',
'$1',
$this->translator->translate($matches[2])
);
return $matches[1] . '[not(' . $subresult . ')]';
}
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace TheSeer\fDOM\CSS {
class NthChildRule implements RuleInterface {
/**
* @param $selector
*
* @return string
*/
public function apply($selector) {
return preg_replace_callback(
'/([a-zA-Z0-9\_\-\*]+):nth-child\(([^\)]*)\)/',
array($this, 'callback'),
$selector
);
}
/**
* @param array $matches
*
* @return string
*/
private function callback(array $matches) {
switch($matches[2]){
case 'n': {
return $matches[1];
}
case 'even': {
return '*[position() mod 2=0 and position()>=0]/self::' . $matches[1];
}
case 'odd': {
return $matches[1] . '[(count(preceding-sibling::*) + 1) mod 2=1]';
}
default: {
$b = !isset($matches[2]) || empty($matches[2]) ? '0' : $matches[2];
$b = preg_replace('/^([0-9]*)n.*?([0-9]*)$/', '$1+$2', $b);
$b = explode('+', $b);
if (!isset($b[1])) {
$b[1] = '0';
}
return '*[(position()-' . $b[1] . ') mod ' . $b[0] . '=0 and position()>=' . $b[1] . ']/self::' . $matches[1];
}
}
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace TheSeer\fDOM\CSS {
class RegexRule implements RuleInterface {
/**
* @var string
*/
private $regex;
/**
* @var string
*/
private $replacement;
/**
* @param string $regex
* @param string $replacement
*/
public function __construct($regex, $replacement) {
$this->regex = $regex;
$this->replacement = $replacement;
}
/**
* @param $selector
*
* @return string
*/
public function apply($selector) {
return preg_replace($this->regex, $this->replacement, $selector);
}
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace TheSeer\fDOM\CSS {
interface RuleInterface {
/**
* @param $selector
*
* @return string
*/
public function apply($selector);
}
}

View File

@@ -0,0 +1,158 @@
<?php
/**
* Copyright (c) 2010-2017 Arne Blankerts <arne@blankerts.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Arne Blankerts nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @link http://github.com/theseer/fdomdocument
*
*/
namespace TheSeer\fDOM\CSS {
/**
* Class Translator
*
* The regular expressions used in this class are heavily inspired by and mostly adopted from
* the css2xpath.js code by Andrea Giammarchi (http://code.google.com/p/css2xpath/).
* The JavaScript version (css2xpath.js) is licensed under the MIT License
*
*/
class Translator {
/**
* @var array
*/
private $rules;
/**
* @param string $selector A CSS Selector string
*
* @return string
*/
public function translate($selector) {
foreach($this->getRules() as $rule) {
/** @var RuleInterface $rule */
$selector = $rule->apply($selector);
}
return '//' . $selector;
}
/**
* @return array
*/
private function getRules() {
if ($this->rules != NULL) {
return $this->rules;
}
$this->rules = array(
// prefix|name
new RegexRule('/([a-zA-Z0-9\_\-\*]+)\|([a-zA-Z0-9\_\-\*]+)/', '$1:$2'),
// add @ for attribs
new RegexRule("/\[([^\]~\$\*\^\|\!]+)(=[^\]]+)?\]/", '[@$1$2]'),
// multiple queries
new RegexRule("/\s*,\s*/", '|'),
// , + ~ >
new RegexRule("/\s*(\+|~|>)\s*/", '$1'),
//* ~ + >
new RegexRule("/([a-zA-Z0-9\_\-\*])~([a-zA-Z0-9\_\-\*])/", '$1/following-sibling::$2'),
new RegexRule("/([a-zA-Z0-9\_\-\*])\+([a-zA-Z0-9\_\-\*])/", '$1/following-sibling::*[1]/self::$2'),
new RegexRule("/([a-zA-Z0-9\_\-\*])>([a-zA-Z0-9\_\-\*])/", '$1/$2'),
// all unescaped stuff escaped
new RegexRule("/\[([^=]+)=([^'|'][^\]]*)\]/", '[$1="$2"]'),
// all descendant or self to //
new RegexRule("/(^|[^a-zA-Z0-9\_\-\*])(#|\.)([a-zA-Z0-9\_\-]+)/", '$1*$2$3'),
new RegexRule("/([\>\+\|\~\,\s])([a-zA-Z\*]+)/", '$1//$2'),
new RegexRule("/\s+\/\//", '//'),
// :first-child
new RegexRule("/([a-zA-Z0-9\_\-\*]+):first-child/", '*[1]/self::$1'),
// :last-child
new RegexRule("/([a-zA-Z0-9\_\-\*]+):last-child/", '$1[not(following-sibling::*)]'),
// :only-child
new RegexRule("/([a-zA-Z0-9\_\-\*]+):only-child/", '*[last()=1]/self::$1'),
// :empty
new RegexRule("/([a-zA-Z0-9\_\-\*]+):empty/", '$1[not(*) and not(normalize-space())]'),
// :not
new NotRule($this),
// :nth-child
new NthChildRule(),
// :contains(selectors)
new RegexRule('/:contains\(([^\)]*)\)/', '[contains(string(.),"$1")]'),
// |= attrib
new RegexRule("/\[([a-zA-Z0-9\_\-]+)\|=([^\]]+)\]/", '[@$1=$2 or starts-with(@$1,concat($2,"-"))]'),
// *= attrib
new RegexRule("/\[([a-zA-Z0-9\_\-]+)\*=([^\]]+)\]/", '[contains(@$1,$2)]'),
// ~= attrib
new RegexRule("/\[([a-zA-Z0-9\_\-]+)~=([^\]]+)\]/", '[contains(concat(" ",normalize-space(@$1)," "),concat(" ",$2," "))]'),
// ^= attrib
new RegexRule("/\[([a-zA-Z0-9\_\-]+)\^=([^\]]+)\]/", '[starts-with(@$1,$2)]'),
// $= attrib
new DollarEqualRule(),
// != attrib
new RegexRule("/\[([a-zA-Z0-9\_\-]+)\!=([^\]]+)\]/", '[not(@$1) or @$1!=$2]'),
// ids and classes
new RegexRule("/#([a-zA-Z0-9\_\-]+)/", '[@id="$1"]'),
new RegexRule("/\.([a-zA-Z0-9\_\-]+)/", '[contains(concat(" ",normalize-space(@class)," ")," $1 ")]'),
// normalize multiple filters
new RegexRule("/\]\[([^\]]+)/", ' and ($1)')
);
return $this->rules;
}
}
}

View File

@@ -0,0 +1,625 @@
<?php
/**
* Copyright (c) 2010-2017 Arne Blankerts <arne@blankerts.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Arne Blankerts nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @link http://github.com/theseer/fdomdocument
*
*/
namespace TheSeer\fDOM {
use TheSeer\fDOM\CSS\Translator;
/**
* fDOMDocument extension to PHP's DOMDocument.
* This class adds various convenience methods to simplify APIs
* It is set to final since further extending it would even more
* break the Object structure after use of registerNodeClass.
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @access public
* @property fDOMDocument $ownerDocument
*
*/
class fDOMDocument extends \DOMDocument {
/**
* XPath Object instance
*
* @var fDOMXPath
*/
private $xp = NULL;
/**
* List of registered prefixes and their namespace uri
* @var array
*/
private $prefixes = array();
/**
* Extended DOMDocument constructor
*
* @param string $version XML Version, should be 1.0
* @param string $encoding Encoding, defaults to utf-8
* @param array $streamOptions optional stream options array
*
* @return fDOMDocument
*/
public function __construct($version = '1.0', $encoding = 'utf-8', $streamOptions = NULL) {
if (!is_null($streamOptions)) {
$this->setStreamContext($streamOptions);
}
libxml_use_internal_errors(TRUE);
$rc = parent::__construct($version, $encoding);
$this->registerNodeClasses();
return $rc;
}
/**
* Reset XPath object so the clone gets a new instance when needed
*/
public function __clone() {
$this->registerNodeClasses();
$this->xp = new fDOMXPath($this);
foreach($this->prefixes as $prefix => $uri) {
$this->xp->registerNamespace($prefix, $uri);
}
}
/**
* @return string
*/
public function __toString() {
return $this->C14N();
}
/**
* Set Stream context options
*
* @param array $options Stream context options
*
* @return boolean true on success, false on failure
*/
public function setStreamContext(array $options) {
if (!count($options)) {
return FALSE;
}
$context = stream_context_create($options);
libxml_set_streams_context($context);
return TRUE;
}
/**
* Wrapper to DOMDocument load with exception handling
* Returns true on success to satisfy the compatibilty of the original DOM Api
*
* @param string $fname File to load
* @param int|null $options LibXML Flags to pass
*
* @throws fDOMException
*
* @return bool|mixed
*/
public function load($fname, $options = LIBXML_NONET) {
if ($fname === '') {
throw new fDOMException('empty filename is not allowed', fDOMException::ParseError);
}
$this->xp = NULL;
$tmp = parent :: load($fname, $options);
if (!$tmp || libxml_get_last_error()) {
throw new fDOMException("loading file '$fname' failed.", fDOMException::LoadError);
}
$this->registerNodeClasses();
return TRUE;
}
/**
* Wrapper to DOMDocument loadXML with exception handling
* Returns true on success to satisfy the compatibilty of the original DOM Api
*
* @param string $source XML source code
* @param integer $options LibXML option flags
*
* @throws fDOMException
*
* @return boolean
*/
public function loadXML($source, $options = LIBXML_NONET) {
if ($source === '') {
throw new fDOMException('empty string not allowed', fDOMException::ParseError);
}
$this->xp = NULL;
$tmp = parent :: loadXML($source, $options);
if (!$tmp || libxml_get_last_error()) {
throw new fDOMException('parsing string failed', fDOMException::ParseError);
}
$this->registerNodeClasses();
return TRUE;
}
/**
* Wrapper to DOMDocument loadHTMLFile with exception handling.
* Returns true on success to satisfy the compatibilty of the original DOM Api
*
* @param string $fname html file to load
* @param integer $options Options bitmask (@see DOMDocument::loadHTMLFile)
*
* @throws fDOMException
*
* @return boolean
*/
public function loadHTMLFile($fname, $options = NULL) {
if ($fname === '') {
throw new fDOMException('empty filename is not allowed', fDOMException::ParseError);
}
$this->xp = NULL;
if (version_compare(PHP_VERSION, '5.4.0', '<')) {
if ($options !== NULL) {
throw new fDOMException('Passing options requires PHP 5.4.0+', fDOMException::LoadError);
}
$tmp = parent :: loadHTMLFile($fname);
} else {
$tmp = parent :: loadHTMLFile($fname, $options);
}
if (!$tmp || libxml_get_last_error()) {
throw new fDOMException("loading html file '$fname' failed", fDOMException::LoadError);
}
$this->registerNodeClasses();
return TRUE;
}
/**
* Wrapper to DOMDocument loadHTML with exception handling
* Returns true on success to satisfy the compatibilty of the original DOM Api
*
* @param string $source html source code
* @param integer $options Options bitmask (@see DOMDocument::loadHTML)
*
* @throws fDOMException
*
* @return boolean
*/
public function loadHTML($source, $options = NULL) {
if ($source === '') {
throw new fDOMException('empty string not allowed', fDOMException::ParseError);
}
$this->xp = NULL;
if (version_compare(PHP_VERSION, '5.4.0', '<')) {
if ($options !== NULL) {
throw new fDOMException('Passing options requires PHP 5.4.0+', fDOMException::LoadError);
}
$tmp = parent :: loadHTML($source);
} else {
$tmp = parent :: loadHTML($source, $options);
}
if (!$tmp || libxml_get_last_error()) {
throw new fDOMException('parsing html string failed', fDOMException::ParseError);
}
$this->registerNodeClasses();
return TRUE;
}
/**
* Wrapper to DOMDocument::save with exception handling
*
* @param string $filename filename to save to
* @param integer $options Options bitmask (@see DOMDocument::save)
*
* @throws fDOMException
*
* @return integer bytes saved
*/
public function save($filename, $options = NULL) {
$tmp = parent::save($filename, $options);
if (!$tmp) {
throw new fDOMException("Saving XML to file '$filename' failed", fDOMException::SaveError);
}
return $tmp;
}
/**
* Wrapper to DOMDocument::saveHTML with exception handling
*
* @param \DOMNode|null $node Context DOMNode (optional)
*
* @throws fDOMException
*
* @return string html content
*/
public function saveHTML(\DOMNode $node = NULL) {
if (version_compare(PHP_VERSION, '5.3.6', '<') && $node !== NULL) {
throw new fDOMException('Passing a context node requires PHP 5.3.6+', fDOMException::SaveError);
}
$tmp = parent::saveHTML($node);
if (!$tmp) {
throw new fDOMException('Serializing to HTML failed', fDOMException::SaveError);
}
return $tmp;
}
/**
* Wrapper to DOMDocument::saveHTMLfile with exception handling
*
* @param string $filename filename to save to
* @param integer $options Options bitmask (@see DOMDocument::saveHTMLFile)
*
* @throws fDOMException
*
* @return integer bytes saved
*/
public function saveHTMLFile($filename, $options = NULL) {
$tmp = parent::saveHTMLFile($filename, $options);
if (!$tmp) {
throw new fDOMException("Saving HTML to file '$filename' failed", fDOMException::SaveError);
}
return $tmp;
}
/**
* Wrapper to DOMDocument::saveXML with exception handling
*
* @param \DOMNode $node node to start serializing at
* @param integer $options options flags as bitmask
*
* @throws fDOMException
*
* @return string serialized XML
*/
public function saveXML(\DOMNode $node = NULL, $options = NULL) {
try {
$tmp = parent::saveXML($node, $options);
if (!$tmp) {
throw new fDOMException('Serializing to XML failed', fDOMException::SaveError);
}
return $tmp;
} catch (\Exception $e) {
if (!$e instanceof fDOMException) {
throw new fDOMException($e->getMessage(), fDOMException::SaveError, $e);
}
throw $e;
}
}
/**
* get Instance of DOMXPath Object for current DOM
*
* @throws fDOMException
*
* @return fDOMXPath
*/
public function getDOMXPath() {
if (is_null($this->xp)) {
$this->xp = new fDOMXPath($this);
}
if (!$this->xp) {
throw new fDOMException('creating DOMXPath object failed.', fDOMException::NoDOMXPath);
}
return $this->xp;
}
/**
* Convert a given DOMNodeList into a DOMFragment
*
* @param \DOMNodeList $list The Nodelist to process
* @param boolean $move Signale if nodes are to be moved into fragment or not
*
* @return fDOMDocumentFragment
*/
public function nodeList2Fragment(\DOMNodeList $list, $move=FALSE) {
$frag = $this->createDocumentFragment();
/** @var fDOMNode $node */
foreach($list as $node) {
$frag->appendChild($move ? $node : $node->cloneNode(TRUE));
}
return $this->ensureIntance($frag);
}
/**
* Perform an xpath query
*
* @param String $q query string containing xpath
* @param \DOMNode|null $ctx (optional) Context DOMNode
* @param boolean $registerNodeNS Register flag pass through
*
* @return \DOMNodeList
*/
public function query($q, \DOMNode $ctx = NULL, $registerNodeNS = TRUE) {
if (is_null($this->xp)) {
$this->getDOMXPath();
}
return $this->xp->evaluate($q, $ctx, $registerNodeNS);
}
/**
* Perform an xpath query and return only the 1st match
*
* @param String $q query string containing xpath
* @param \DOMNode $ctx (optional) Context DOMNode
* @param boolean $registerNodeNS Register flag pass thru
*
* @return fDOMNode
*/
public function queryOne($q, \DOMNode $ctx = NULL, $registerNodeNS = TRUE) {
if (is_null($this->xp)) {
$this->getDOMXPath();
}
return $this->xp->queryOne($q, $ctx, $registerNodeNS);
}
/**
* Forwarder to fDOMXPath's prepare method allowing for easy and secure
* placeholder replacement comparable to sql's prepared statements
* .
* @param string $xpath String containing xpath with :placeholder markup
* @param array $valueMap array containing keys (:placeholder) and value pairs to be quoted
*
* @return string
*/
public function prepareQuery($xpath, array $valueMap) {
if (is_null($this->xp)) {
$this->getDOMXPath();
}
return $this->xp->prepare($xpath, $valueMap);
}
/**
* Use a CSS Level 3 Selector string to query select nodes
*
* @param string $selector A CSS Level 3 Selector string
* @param \DOMNode $ctx
* @param bool $registerNodeNS
*
* @return \DOMNodeList
*/
public function select($selector, \DOMNode $ctx = NULL, $registerNodeNS = TRUE) {
$translator = new Translator();
$xpath = $translator->translate($selector);
if ($ctx !== NULL) {
$xpath = '.' . $xpath;
}
return $this->query($xpath, $ctx, $registerNodeNS);
}
/**
* Forward to DOMXPath->registerNamespace()
*
* @param string $prefix The prefix to use
* @param string $uri The uri to assign to this prefix
*
* @throws fDOMException
*
* @return void
*/
public function registerNamespace($prefix, $uri) {
if (is_null($this->xp)) {
$this->getDOMXPath();
}
if (!$this->xp->registerNamespace($prefix, $uri)) {
throw new fDOMException("Registering namespace '$uri' with prefix '$prefix' failed.", fDOMException::RegistrationFailed);
}
$this->prefixes[$prefix] = $uri;
}
/**
* Forward to DOMXPath->registerPHPFunctions()
*
* @param mixed $restrict array of function names or string with functionname to restrict callabilty to
*
* @throws fDOMException
*
* @return void
*/
public function registerPHPFunctions($restrict = NULL) {
if (is_null($this->xp)) {
$this->getDOMXPath();
}
$this->xp->registerPHPFunctions($restrict);
if (libxml_get_last_error()) {
throw new fDOMException("Registering php functions failed.", fDOMException::RegistrationFailed);
}
}
/**
* Create a new element in namespace defined by given prefix
*
* @param string $prefix Namespace prefix for node to create
* @param string $name Name of not element to create
* @param string $content Optional content to be set
* @param bool $asTextNode Create content as textNode rather then setting nodeValue
*
* @throws fDOMException
*
* @return fDOMElement Reference to created fDOMElement
*/
public function createElementPrefix($prefix, $name, $content = NULL, $asTextNode = FALSE) {
if (!isset($this->prefixes[$prefix])) {
throw new fDOMException("'$prefix' not bound", fDOMException::UnboundPrefix);
}
return $this->createElementNS($this->prefixes[$prefix], $prefix.':'.$name, $content, $asTextNode);
}
/**
* Create a new fDOMElement and return it, optionally set content
*
* @param string $name Name of node to create
* @param null $content Content to set (optional)
* @param bool $asTextnode Create content as textNode rather then setting nodeValue
*
* @throws fDOMException
*
* @return fDOMElement Reference to created fDOMElement
*/
public function createElement($name, $content = NULL, $asTextnode = FALSE) {
try {
$node = parent::createElement($name);
if (!$node) {
throw new fDOMException("Creating element with name '$name' failed", fDOMException::NameInvalid);
}
if ($content !== NULL) {
if ($asTextnode) {
$node->appendChild($this->createTextnode($content));
} else {
$node->nodeValue = $content;
}
if (libxml_get_errors()) {
throw new fDOMException("Setting content value failed", fDOMException::SetFailedError);
}
}
return $this->ensureIntance($node);
} catch (\DOMException $e) {
throw new fDOMException("Creating elemnt with name '$name' failed", 0, $e);
}
}
/**
* Create a new fDOMElement within given namespace and return it
*
* @param string $namespace Namespace URI for node to create
* @param string $name Name of node to create
* @param string $content Content to set (optional)
* @param bool $asTextNode Create content as textNode rather then setting nodeValue
*
* @throws fDOMException
*
* @return fDOMElement
*/
public function createElementNS($namespace, $name, $content = NULL, $asTextNode = FALSE) {
$node = parent::createElementNS($namespace, $name);
if (!$node) {
throw new fDOMException("Creating element with name '$name' failed", fDOMException::NameInvalid);
}
if ($content !== NULL) {
if ($asTextNode) {
$node->appendChild($this->createTextnode($content));
} else {
$node->nodeValue = $content;
}
if (libxml_get_errors()) {
throw new fDOMException("Setting content value failed", fDOMException::SetFailedError);
}
}
return $this->ensureIntance($node);
}
/**
* @return fDOMDocumentFragment
*/
public function createDocumentFragment() {
return $this->ensureIntance(parent::createDocumentFragment());
}
/**
* Check if the given node is in the same document
*
* @param \DOMNode $node Node to compare with
*
* @return boolean true on match, false if they differ
*
*/
public function inSameDocument(\DOMNode $node) {
if ($node instanceof \DOMDocument) {
return $this->isSameNode($node);
}
return $this->isSameNode($node->ownerDocument);
}
/**
* Create a new element and append it as documentElement
*
* @param string $name Name of not element to create
* @param string $content Optional content to be set
* @param bool $asTextNode
*
* @return fDOMElement Reference to created fDOMElement
*/
public function appendElement($name, $content = NULL, $asTextNode = FALSE) {
return $this->appendChild(
$this->createElement($name, $content, $asTextNode)
);
}
/**
* Create a new element in given namespace and append it as documentElement
*
* @param string $ns Namespace of node to create
* @param string $name Name of not element to create
* @param string $content Optional content to be set
* @param bool $asTextNode
*
* @return fDOMElement Reference to created fDOMElement
*/
public function appendElementNS($ns, $name, $content = NULL, $asTextNode = FALSE) {
return $this->appendChild(
$this->createElementNS($ns, $name, $content, $asTextNode)
);
}
/**
* This is a workaround for hhvm's broken registerNodeClass handling
* (https://github.com/facebook/hhvm/issues/1848)
*
* @param \DOMNode $node
*
* @return \DOMNode
*/
private function ensureIntance(\DOMNode $node) {
if ($node instanceof fDOMNode || $node instanceof fDOMElement || $node instanceof fDOMDocumentFragment) {
return $node;
}
return $this->importNode($node, TRUE);
}
/**
* Register replacements
*
* Called from constructor and, as a workaround for (https://github.com/facebook/hhvm/issues/5412),
* after load(), loadXML(), loadHTML() and loadHTMLFile()
*/
private function registerNodeClasses() {
$this->registerNodeClass('DOMDocument', get_called_class());
$this->registerNodeClass('DOMNode', 'TheSeer\fDOM\fDOMNode');
$this->registerNodeClass('DOMElement', 'TheSeer\fDOM\fDOMElement');
$this->registerNodeClass('DOMDocumentFragment', 'TheSeer\fDOM\fDOMDocumentFragment');
}
} // fDOMDocument
}

View File

@@ -0,0 +1,191 @@
<?php
/**
* Copyright (c) 2010-2017 Arne Blankerts <arne@blankerts.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Arne Blankerts nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @link http://github.com/theseer/fdomdocument
*
*/
namespace TheSeer\fDOM {
/**
* fDOMDocumentFragment
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @access public
* @property fDOMDocument $ownerDocument
*
*/
class fDOMDocumentFragment extends \DOMDocumentFragment {
/**
* @return string
*/
public function __toString() {
return $this->ownerDocument->saveXML($this);
}
/**
* Wrapper to standard method with exception support
*
* @param string $str Data string to parse and append
*
* @throws fDOMException
*
* @return bool true on success
*/
public function appendXML($str) {
if (!parent::appendXML($str)) {
throw new fDOMException('Appending xml string failed', fDOMException::ParseError);
}
return true;
}
/**
* Create a new element and append it
*
* @param string $name Name of not element to create
* @param string $content Optional content to be set
*
* @return fDOMElement Reference to created fDOMElement
*/
public function appendElement($name, $content = null) {
$node = $this->ownerDocument->createElement($name, $content);
$this->appendChild($node);
return $node;
}
/**
* Create a new element in given namespace and append it
*
* @param string $ns Namespace of node to create
* @param string $name Name of not element to create
* @param string $content Optional content to be set
*
* @return fDOMElement Reference to created fDOMElement
*/
public function appendElementNS($ns, $name, $content = null) {
$node = $this->ownerDocument->createElementNS($ns, $name, $content);
$this->appendChild($node);
return $node;
}
/**
* Create a new element in given namespace and append it
*
* @param string $prefix Namespace prefix for node to create
* @param string $name Name of not element to create
* @param string $content Optional content to be set
* @param bool $asTextnode Create content as textNode rather then setting nodeValue
*
* @return fDOMElement Reference to created fDOMElement
*/
public function appendElementPrefix($prefix, $name, $content = null, $asTextnode = FALSE) {
$node = $this->ownerDocument->createElementPrefix($prefix, $name, $content, $asTextnode);
$this->appendChild($node);
return $node;
}
/**
* Create a new text node and append it
*
* @param string $content Text content to be added
*
* @return \DOMText
*/
public function appendTextNode($content) {
$text = $this->ownerDocument->createTextNode($content);
$this->appendChild($text);
return $text;
}
/**
* Check if the given node is in the same document
*
* @param \DOMNode $node Node to compare with
*
* @return boolean true on match, false if they differ
*
*/
public function inSameDocument(\DOMNode $node) {
return $this->ownerDocument->inSameDocument($node);
}
/**
* Forward to fDomDocument->query()
*
* @param string $q XPath to use
* @param \DOMNode $ctx \DOMNode to overwrite context
* @param boolean $registerNodeNS Register flag pass thru
*
* @return \DomNodeList
*/
public function query($q, \DOMNode $ctx = null, $registerNodeNS = true) {
return $this->ownerDocument->query($q, $ctx ? $ctx : $this, $registerNodeNS);
}
/**
* Forward to fDomDocument->queryOne()
*
* @param string $q XPath to use
* @param \DOMNode $ctx (optional) \DOMNode to overwrite context
* @param boolean $registerNodeNS Register flag pass thru
*
* @return mixed
*/
public function queryOne($q, \DOMNode $ctx = null, $registerNodeNS = true) {
return $this->ownerDocument->queryOne($q, $ctx ? $ctx : $this, $registerNodeNS);
}
/**
* Forward to fDomDocument->select()
*
* @param string $selector A CSS Level 3 Selector string
* @param \DOMNode $ctx
* @param bool $registerNodeNS
*
* @return \DOMNodeList
*/
public function select($selector, \DOMNode $ctx = null, $registerNodeNS = true) {
return $this->ownerDocument->select($selector, $ctx ? $ctx : $this, $registerNodeNS);
}
} // fDOMDocumentFragment
}

View File

@@ -0,0 +1,392 @@
<?php
/**
* Copyright (c) 2010-2017 Arne Blankerts <arne@blankerts.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Arne Blankerts nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @link http://github.com/theseer/fdomdocument
*
*/
namespace TheSeer\fDOM {
/**
* fDomElement
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @access public
* @property fDOMDocument $ownerDocument
*
*/
class fDOMElement extends \DOMElement {
/**
* @return string
*/
public function __toString() {
return $this->C14N();
}
/**
* Forward to fDomDocument->query()
*
* @param string $q XPath to use
* @param \DOMNode $ctx \DOMNode to overwrite context
* @param boolean $registerNodeNS Register flag pass thru
*
* @return \DomNodeList
*/
public function query($q, \DOMNode $ctx = null, $registerNodeNS = true) {
return $this->ownerDocument->query($q, $ctx ? $ctx : $this, $registerNodeNS);
}
/**
* Forward to fDomDocument->queryOne()
*
* @param string $q XPath to use
* @param \DOMNode $ctx (optional) \DOMNode to overwrite context
* @param boolean $registerNodeNS Register flag pass thru
*
* @return mixed
*/
public function queryOne($q, \DOMNode $ctx = null, $registerNodeNS = true) {
return $this->ownerDocument->queryOne($q, $ctx ? $ctx : $this, $registerNodeNS);
}
/**
* Forward to fDomDocument->select()
*
* @param string $selector A CSS Level 3 Selector string
* @param \DOMNode $ctx
* @param bool $registerNodeNS
*
* @return \DOMNodeList
*/
public function select($selector, \DOMNode $ctx = null, $registerNodeNS = true) {
return $this->ownerDocument->select($selector, $ctx, $registerNodeNS);
}
/**
* Parse and append XML String to node
*
* @param String $str string to process
*
* @return fDomDocumentFragment Reference to the created Fragment
*/
public function appendXML($str) {
$frag = $this->ownerDocument->createDocumentFragment();
$frag->appendXML($str);
$this->appendChild($frag);
return $frag;
}
/**
* Create a new element and append it
*
* @param string $name Name of not element to create
* @param string $content Optional content to be set
* @param bool $asTextnode Create content as textNode rather then setting nodeValue
*
* @return fDOMElement Reference to created fDOMElement
*/
public function appendElement($name, $content = null, $asTextnode = FALSE) {
$node = $this->ownerDocument->createElement($name, $content, $asTextnode);
$this->appendChild($node);
return $node;
}
/**
* Create a new element in given namespace and append it
*
* @param string $ns Namespace of node to create
* @param string $name Name of not element to create
* @param string $content Optional content to be set
* @param bool $asTextnode Create content as textNode rather then setting nodeValue
*
* @return fDOMElement Reference to created fDOMElement
*/
public function appendElementNS($ns, $name, $content = null, $asTextnode = FALSE) {
$node = $this->ownerDocument->createElementNS($ns, $name, $content, $asTextnode);
$this->appendChild($node);
return $node;
}
/**
* Create a new element in given namespace and append it
*
* @param string $prefix Namespace prefix for node to create
* @param string $name Name of not element to create
* @param string $content Optional content to be set
* @param bool $asTextnode Create content as textNode rather then setting nodeValue
*
* @return fDOMElement Reference to created fDOMElement
*/
public function appendElementPrefix($prefix, $name, $content = null, $asTextnode = FALSE) {
$node = $this->ownerDocument->createElementPrefix($prefix, $name, $content, $asTextnode);
$this->appendChild($node);
return $node;
}
/**
* Create a new text node and append it
*
* @param string $content Text content to be added
*
* @return \DOMText
*/
public function appendTextNode($content) {
$text = $this->ownerDocument->createTextNode($content);
$this->appendChild($text);
return $text;
}
/**
* Create a new fDOMElement
*
* @see fDOMDocument::createElement
*
* @param string $name
* @param string $content
* @param bool $asTextnode
*
* @return fDOMElement
*/
public function createElement($name, $content = NULL, $asTextnode = FALSE) {
return $this->ownerDocument->createElement($name, $content, $asTextnode);
}
/**
* Create a new fDOMElement in namespace defined by prefix
*
* @see fDOMDocument::createElementPrefix
*
* @param string $prefix
* @param string $name
* @param string $content
* @param bool $asTextNode
*
* @return fDOMElement
*/
public function createElementPrefix($prefix, $name, $content = NULL, $asTextNode = FALSE) {
return $this->ownerDocument->createElementPrefix($prefix, $name, $content, $asTextNode);
}
/**
* Create a new fDOMElement within given namespace and return it
*
* @param string $namespace Namespace URI for node to create
* @param string $name Name of node to create
* @param null $content Content to set (optional)
* @param bool $asTextNode Create content as textNode rather then setting nodeValue
*
* @throws fDOMException
*
* @return fDOMElement
*/
public function createElementNS($namespace, $name, $content = NULL, $asTextNode = FALSE) {
return $this->ownerDocument->createElementNS($namespace, $name, $content, $asTextNode);
}
/**
* Wrapper to DomElement->getAttribute with default value option
*
* Note: A set but emptry attribute does NOT trigger use of the default
*
* @param string $attr Attribute to access
* @param string $default Default value to use if the attribute is not set
*
* @return string
*/
public function getAttribute($attr, $default='') {
return $this->hasAttribute($attr) ? parent::getAttribute($attr) : $default;
}
/**
* Wrapper to DomElement->getAttributeNS with default value option
*
* Note: A set but empty attribute does NOT trigger use of the default
*
* @param string $ns Namespace of attribute
* @param string $attr Attribute to access
* @param string $default Default value to use if the attribute is not set
*
* @return string
*/
public function getAttributeNS($ns, $attr, $default='') {
return $this->hasAttributeNS($ns, $attr) ? parent::getAttributeNS($ns, $attr) : $default;
}
/**
* Wrapper to DOMElement::setAttribute with additional entities support
*
* @param string $attr Attribute name to set
* @param string $value Value to set attribute to
* @param bool $keepEntities Flag to signale if entities should be kept
*
* @throws fDOMException
*
* @return \DOMAttr
*
* @see DOMElement::setAttribute()
*/
public function setAttribute($attr, $value, $keepEntities=false) {
if ($keepEntities === true) {
$attrNode = $this->ownerDocument->createAttribute($attr);
if (!$attrNode) {
throw new fDOMException("Setting attribute '$attr' failed.", fDOMException::SetFailedError);
}
$attrNode->value = $value;
$this->appendChild($attrNode);
return $attrNode;
}
return parent::setAttribute($attr, $value);
}
/**
* Wrapper to namespace aware DOMElement::setAttributeNS with additional entities support
*
* @param string $ns namespace attribute should be in
* @param string $attr Attribute name to set
* @param string $value Value to set attribute to
* @param bool $keepEntities Flag to signale if entities should be kept
*
* @throws fDOMException
*
* @return \DOMAttr|null
* @see DOMElement::setAttribute()
*/
public function setAttributeNS($ns, $attr, $value, $keepEntities=false) {
if ($keepEntities === true) {
$attrNode = $this->ownerDocument->createAttributeNS($ns, $attr);
if (!$attrNode) {
throw new fDOMException("Setting attribute '$attr' failed.", fDOMException::SetFailedError);
}
$attrNode->value = $value;
$this->appendChild($attrNode);
return $attrNode;
}
return parent::setAttributeNS($ns, $attr, $value);
}
/**
* Helper to add multiple attributes to an element
*
* @param array $attr Attributes to add as key-value pair
* @param bool $keepEntities Flag wether to keep entities
*
* @return array List with references to created DOMAttr
*/
public function setAttributes(array $attr, $keepEntities=false) {
$attList = array();
foreach($attr as $name => $value) {
$attList[] = $this->setAttribute($name, $value, $keepEntities);
}
return $attList;
}
/**
* Helper to add multiple attributes with the given namespace and prefix
*
* @param string $ns Namespace of attribute
* @param string $prefix Namespace prefix for attribute to create
* @param array $attr Attributes to add
* @param bool $keepEntities Flag wether to keep entities
*
* @return void
*/
public function setAttributesNS($ns, $prefix, array $attr, $keepEntities=false) {
foreach($attr as $name => $value) {
$this->setAttributeNS($ns, $prefix.':'.$name, $value, $keepEntities);
}
}
/**
* Helper method to get children by name
*
* @param string $tagName tagname to search for
*
* @return \DOMNodeList
*/
public function getChildrenByTagName($tagName) {
return $this->query("*[local-name()='$tagName']");
}
/**
* Helper method to get children by name and namespace
*
* @param string $ns namespace nodes have to be in
* @param string $tagName tagname to search for
*
* @return \DOMNodeList
*/
public function getChildrenByTagNameNS($ns, $tagName) {
return $this->query("*[local-name()='$tagName' and namespace-uri()='$ns']");
}
/**
* Check if the given node is in the same document
*
* @param \DomNode $node Node to compare with
*
* @return boolean true on match, false if they differ
*
*/
public function inSameDocument(\DomNode $node) {
return $this->ownerDocument->inSameDocument($node);
}
/**
* Wrapper to DomDocument::saveXML() with current node as context
*
* @return string
*/
public function saveXML() {
return $this->ownerDocument->saveXML($this);
}
/**
* Wrapper to DomDocument::saveHTML() with current node as context
*
* @return string
*/
public function saveHTML() {
return $this->ownerDocument->saveHTML($this);
}
} // fDOMElement
}

View File

@@ -0,0 +1,187 @@
<?php
/**
* Copyright (c) 2010-2017 Arne Blankerts <arne@blankerts.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Arne Blankerts nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
* @license BSD License
*/
namespace TheSeer\fDOM {
/**
* fDOMException
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @access public
*
*/
class fDOMException extends \Exception {
const LoadError = 1;
const ParseError = 2;
const SaveError = 3;
const QueryError = 4;
const RegistrationFailed = 5;
const NoDOMXPath = 6;
const UnboundPrefix = 7;
const SetFailedError = 8;
const NameInvalid = 9;
/**
* List of libxml error objects
*
* @var array
*/
private $errorList;
/**
* Full Error message
*
* @var string
*/
private $fullMessage = null;
/**
* Short Error Message
*
* @var string
*/
private $shortMessage = null;
private static $fullMesageMode = true;
/**
* Constructor
*
* @param string $message Exception message
* @param integer $code Exception code
* @param \Exception $chain optional chained exception
*
*/
public function __construct($message, $code = 0, \Exception $chain = NULL) {
$this->shortMessage = $message;
$this->errorList = libxml_get_errors();
libxml_clear_errors();
parent :: __construct($message, $code, $chain);
$this->fullMessage = $message."\n\n";
foreach ($this->errorList as $error) {
// hack, skip "attempt to load external pseudo error"
if ($error->code=='1543') {
continue;
}
if (empty($error->file)) {
$this->fullMessage .= '[XML-STRING] ';
} else {
$this->fullMessage .= '['.$error->file.'] ';
}
$this->fullMessage .= '[Line: '.$error->line.' - Column: '.$error->column.'] ';
switch ($error->level) {
case LIBXML_ERR_WARNING:
$this->fullMessage .= "Warning $error->code: ";
break;
case LIBXML_ERR_ERROR:
$this->fullMessage .= "Error $error->code: ";
break;
case LIBXML_ERR_FATAL:
$this->fullMessage .= "Fatal Error $error->code: ";
break;
}
$this->fullMessage .= str_replace("\n", '', $error->message)."\n";
if (self::$fullMesageMode) {
$this->message = $this->fullMessage;
}
}
}
/**
* Accessor to fullMessage
*
* @return string
*/
public function getFullMessage() {
return $this->fullMessage;
}
/**
* Access to shortMessage
*
* @return string
*/
public function getShortMessage() {
return $this->shortMessage;
}
/**
* Accessor to errorList objets
*
* @return array
*/
public function getErrorList() {
return $this->errorList;
}
/**
* Toggle wehter getMessage() should return full or only exception message
*
* @param boolean $full Flag to enable or disable full message output
*
* @return void
*/
public function toggleFullMessage($full = true) {
$this->message = $full ? $this->fullMessage : $this->shortMessage;
}
/**
* Magic method for string context
*
* @return string
*/
public function __toString() {
return $this->fullMessage;
}
} // fDOMException
}

View File

@@ -0,0 +1,184 @@
<?php
/**
* Copyright (c) 2010-2017 Arne Blankerts <arne@blankerts.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Arne Blankerts nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @link http://github.com/theseer/fdomdocument
*
*/
namespace TheSeer\fDOM {
/**
* fDomNode
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @access public
* @property fDOMDocument $ownerDocument
*
*/
class fDOMNode extends \DOMNode {
/**
* @return string
*/
public function __toString() {
return $this->C14N();
}
/**
* Create a new fDOMElement
*
* @see fDOMDocument::createElement
*
* @param string $name
* @param string $content
* @param bool $asTextnode
*
* @return fDOMElement
*/
public function createElement($name, $content = NULL, $asTextnode = FALSE) {
return $this->ownerDocument->createElement($name, $content, $asTextnode);
}
/**
* Create a new fDOMElement in namespace defined by prefix
*
* @see fDOMDocument::createElementPrefix
*
* @param string $prefix
* @param string $name
* @param string $content
* @param bool $asTextNode
*
* @return fDOMElement
*/
public function createElementPrefix($prefix, $name, $content = NULL, $asTextNode = FALSE) {
return $this->ownerDocument->createElementPrefix($prefix, $name, $content, $asTextNode);
}
/**
* Create a new fDOMElement within given namespace and return it
*
* @param string $namespace Namespace URI for node to create
* @param string $name Name of node to create
* @param null $content Content to set (optional)
* @param bool $asTextNode Create content as textNode rather then setting nodeValue
*
* @throws fDOMException
*
* @return fDOMElement
*/
public function createElementNS($namespace, $name, $content = NULL, $asTextNode = FALSE) {
return $this->ownerDocument->createElementNS($namespace, $name, $content, $asTextNode);
}
/**
* Forward to fDomDocument->query()
*
* @param string $q XPath to use
* @param \DOMNode $ctx \DOMNode to overwrite context
* @param boolean $registerNodeNS Register flag pass thru
*
* @return \DomNodeList
*/
public function query($q, \DOMNode $ctx = null, $registerNodeNS = true) {
return $this->ownerDocument->query($q, $ctx ? $ctx : $this, $registerNodeNS);
}
/**
* Forward to fDomDocument->queryOne()
*
* @param string $q XPath to use
* @param \DOMNode $ctx (optional) \DOMNode to overwrite context
* @param boolean $registerNodeNS Register flag pass thru
*
* @return mixed
*/
public function queryOne($q, \DOMNode $ctx = null, $registerNodeNS = true) {
return $this->ownerDocument->queryOne($q, $ctx ? $ctx : $this, $registerNodeNS);
}
/**
* Forward to fDomDocument->select()
*
* @param string $selector A CSS Level 3 Selector string
* @param \DOMNode $ctx
* @param bool $registerNodeNS
*
* @return \DOMNodeList
*/
public function select($selector, \DOMNode $ctx = null, $registerNodeNS = true) {
return $this->ownerDocument->select($selector, $ctx, $registerNodeNS);
}
/**
* Check if the given node is in the same document
*
* @param \DomNode $node Node to compare with
*
* @return boolean true on match, false if they differ
*
*/
public function inSameDocument(\DOMNode $node) {
return $this->ownerDocument->inSameDocument($node);
}
/**
* Wrapper to DomDocument::saveXML() with current node as context
*
* @return string
*/
public function saveXML() {
return $this->ownerDocument->saveXML($this);
}
/**
* Wrapper to DomDocument::saveHTML() with current node as context
*
* @return string
*/
public function saveHTML() {
return $this->ownerDocument->saveHTML($this);
}
} // fDOMNode
}

View File

@@ -0,0 +1,159 @@
<?php
/**
* Copyright (c) 2010-2017 Arne Blankerts <arne@blankerts.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Arne Blankerts nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @link http://github.com/theseer/fdomdocument
*
*/
namespace TheSeer\fDOM {
/**
* fDOMXPath extension to PHP's DOMXPath.
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @access public
*
*/
class fDOMXPath extends \DOMXPath {
/**
* @var \DOMDocument
*/
protected $doc;
/**
* @param \DOMDocument $doc
*/
public function __construct(\DOMDocument $doc) {
parent::__construct($doc);
$this->doc = $doc;
}
/**
* @param string $xpath
* @param array $valueMap
*
* @return string
*/
public function prepare($xpath, array $valueMap) {
if (count($valueMap)==0) {
return $xpath;
}
foreach($valueMap as $key => $value) {
$xpath = str_replace(':'.$key, $this->quote($value), $xpath);
}
return $xpath;
}
/**
* @param string $q
* @param \DOMNode $ctx
* @param bool $registerNodeNS
*
* @throws fDOMException
*
* @return \DOMNodeList
*/
public function query($q, \DOMNode $ctx = null, $registerNodeNS = true) {
libxml_clear_errors();
if (version_compare(PHP_VERSION, '5.3.3', '<') || strpos(PHP_VERSION, 'hiphop') || strpos(PHP_VERSION, 'hhvm')) {
$rc = parent::query($q, ($ctx instanceof \DOMNode) ? $ctx : $this->doc->documentElement);
} else {
$rc = parent::query($q, ($ctx instanceof \DOMNode) ? $ctx : $this->doc->documentElement, $registerNodeNS);
}
if (libxml_get_last_error()) {
throw new fDOMException('evaluating xpath expression failed.', fDOMException::QueryError);
}
return $rc;
}
/**
* @param string $q
* @param \DOMNode $ctx
* @param bool $registerNodeNS
*
* @throws fDOMException
*
* @return mixed
*/
public function evaluate($q, \DOMNode $ctx = null, $registerNodeNS = true) {
libxml_clear_errors();
if (version_compare(PHP_VERSION, '5.3.3', '<') || strpos(PHP_VERSION, 'hiphop') || strpos(PHP_VERSION, 'hhvm')) {
$rc = parent::evaluate($q, ($ctx instanceof \DOMNode) ? $ctx : $this->doc->documentElement);
} else {
$rc = parent::evaluate($q, ($ctx instanceof \DOMNode) ? $ctx : $this->doc->documentElement, $registerNodeNS);
}
if (libxml_get_last_error()) {
throw new fDOMException('evaluating xpath expression failed.', fDOMException::QueryError);
}
return $rc;
}
/**
* @param string $q
* @param \DOMNode $ctx
* @param bool $registerNodeNS
*
* @throws fDOMException
*
* @return \DOMNode|mixed
*/
public function queryOne($q, \DOMNode $ctx = null, $registerNodeNS = true) {
$rc = $this->evaluate($q, $ctx, $registerNodeNS);
if ($rc instanceof \DOMNodelist) {
return $rc->item(0);
}
return $rc;
}
/**
* @param string $str
*
* @return string
*/
public function quote($str) {
if (strpos($str, '"') === false) {
return '"'.$str.'"';
}
$parts = explode('"', $str);
return 'concat("' . join('",\'"\',"', $parts).'")';
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace TheSeer\fDOM\Tests {
use TheSeer\fDOM\CSS\Translator;
class TranslatorTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provider
*/
public function testTranslatingCssSelectorReturnsCorrectXPath($selector, $xpath) {
$translator = new Translator();
$this->assertEquals($xpath, $translator->translate($selector));
}
public function provider() {
return array(
array("div", '//div'),
array("body div", '//body//div'),
array("div p", '//div//p'),
array("div > p", '//div/p'),
array("div + p", '//div/following-sibling::*[1]/self::p'),
array("div ~ p", '//div/following-sibling::p'),
array("div[class^=exa][class$=mple]", '//div[starts-with(@class,"exa") and (substring(@class,string-length(@class)-3)=class)]'),
array("div p a", '//div//p//a'),
array("div, p, a", '//div|//p|//a'),
array(".note", '//*[contains(concat(" ",normalize-space(@class)," ")," note ")]'),
array("div.example", '//div[contains(concat(" ",normalize-space(@class)," ")," example ")]'),
array("ul .tocline2", '//ul//*[contains(concat(" ",normalize-space(@class)," ")," tocline2 ")]'),
array("div.example, div.note", '//div[contains(concat(" ",normalize-space(@class)," ")," example ")]|//div[contains(concat(" ",normalize-space(@class)," ")," note ")]'),
array("#title", '//*[@id="title"]'),
array("h1#title", '//h1[@id="title"]'),
array("div #title", '//div//*[@id="title"]'),
array("ul.toc li.tocline2", '//ul[contains(concat(" ",normalize-space(@class)," ")," toc ")]//li[contains(concat(" ",normalize-space(@class)," ")," tocline2 ")]'),
array("ul.toc > li.tocline2", '//ul[contains(concat(" ",normalize-space(@class)," ")," toc ")]/li[contains(concat(" ",normalize-space(@class)," ")," tocline2 ")]'),
array("h1#title + div > p", '//h1[@id="title"]/following-sibling::*[1]/self::div/p'),
array("h1[id]:contains(Selectors)", '//h1[@id and (contains(string(.),"Selectors"))]'),
array("a[href][lang][class]", '//a[@href and (@lang) and (@class)]'),
array("div[class]", '//div[@class]'),
array("div[class=example]", '//div[@class="example"]'),
array("div[class^=exa]", '//div[starts-with(@class,"exa")]'),
array("div[class$=mple]", '//div[substring(@class,string-length(@class)-3)=class]'),
array("div[class*=e]", '//div[contains(@class,"e")]'),
array("div[class|=dialog]", '//div[@class="dialog" or starts-with(@class,concat("dialog","-"))]'),
array("div[class!=made_up]", '//div[not(@class) or @class!="made_up"]'),
array("div[class~=example]", '//div[contains(concat(" ",normalize-space(@class)," "),concat(" ","example"," "))]'),
array("div:not(.example)", '//div[not(contains(concat(" ",normalize-space(@class)," ")," example "))]'),
array("p:contains(selectors)", '//p[contains(string(.),"selectors")]'),
array("p:nth-child(even)", '//*[position() mod 2=0 and position()>=0]/self::p'),
array("p:nth-child(2n)", '//*[(position()-) mod 2=0 and position()>=]/self::p'),
array("p:nth-child(odd)", '//p[(count(preceding-sibling::*) + 1) mod 2=1]'),
array("p:nth-child(2n+1)", '//*[(position()-1) mod 2=0 and position()>=1]/self::p'),
array("p:nth-child(n)", '//p'),
array("p:only-child", '//*[last()=1]/self::p'),
array("p:last-child", '//p[not(following-sibling::*)]'),
array("p:first-child", '//*[1]/self::p'),
array("foo|bar", '//foo:bar')
);
}
}
}

View File

@@ -0,0 +1,124 @@
<?php
/**
* Copyright (c) 2010-2017 Arne Blankerts <arne@blankerts.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Arne Blankerts nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @link http://github.com/theseer/fdomdocument
*
*/
namespace TheSeer\fDOM\Tests {
use TheSeer\fDOM\XPathQuery;
use TheSeer\fDOM\fDOMDocument;
class XPathQueryTest extends \PHPUnit\Framework\TestCase {
private $dom;
protected function setUp() {
$this->dom = new fDOMDocument();
$this->dom->loadXML('<?xml version="1.0" ?><root attr="value" />');
}
public function testFindingKeysInQueryWorks() {
$xp = new XPathQuery(':key');
$this->assertEquals(array('key'), $xp->getKeys());
}
/**
* @expectedException TheSeer\fDOM\XPathQueryException
*/
public function testTryingToBindNonExistingKeyThrowsException() {
$xp = new XPathQuery(':key');
$xp->bind('other', 123);
}
public function testBoundValueForKeyGetsApplied() {
$xp = new XPathQuery(':key');
$xp->bind('key', 123);
$this->assertEquals('"123"', $xp->generate($this->dom));
}
public function testAppliedValueForKeyIsUsedOnQueryAndReturnsNode() {
$xp = new XPathQuery('//*[@attr = :key]');
$xp->bind('key', 'value');
$res = $xp->query($this->dom);
$this->assertInstanceOf('\DOMNodelist', $res);
$this->assertEquals(1, $res->length);
$this->assertInstanceOf('\DOMNode', $res->item(0));
}
public function testOverwriteValueOnQuery() {
$xp = new XPathQuery('//*[@attr = :key]');
$xp->bind('key', 'first');
$res = $xp->query($this->dom, array('key' => 'value'));
$this->assertEquals(1, $res->length);
$this->assertInstanceOf('\DOMNode', $res->item(0));
}
public function testAppliedValueForKeyIsUsedOnEvaluateAndReturnsNode() {
$xp = new XPathQuery('//*[@attr = :key]');
$xp->bind('key', 'value');
$res = $xp->evaluate($this->dom);
$this->assertInstanceOf('\DOMNodelist', $res);
$this->assertEquals(1, $res->length);
$this->assertInstanceOf('\DOMNode', $res->item(0));
}
public function testOverwriteValueOnEvaluate() {
$xp = new XPathQuery('//*[@attr = :key]');
$xp->bind('key', 'first');
$res = $xp->evaluate($this->dom, array('key' => 'value'));
$this->assertEquals(1, $res->length);
$this->assertInstanceOf('\DOMNode', $res->item(0));
}
public function testCallToQueryOneReturnsOneNode() {
$xp = new XPathQuery('//*[@attr]');
$res = $xp->queryOne($this->dom);
$this->assertInstanceOf('\DOMNode', $res);
}
public function testQueryCanBeRunWithStandardDomDocument() {
$xp = new XPathQuery('/');
$res = $xp->query(new \DomDocument());
$this->assertInstanceOf('\DOMNodelist', $res);
}
}
}

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" ?>
<broken>
<!-- This is broken for testing reasons - do not fix ;) ->

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" ?>
<root>
<child attr="abc" id="test">
<child attr="other" />
</child>
</root>

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" ?>
<root>&undefined;</root>

View File

@@ -0,0 +1,2 @@
<?xml version="1.0"?>
<test/>

View File

@@ -0,0 +1,355 @@
<?php
/**
* Copyright (c) 2010-2017 Arne Blankerts <arne@blankerts.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Arne Blankerts nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @link http://github.com/theseer/fdomdocument
*
*/
namespace TheSeer\fDOM\Tests {
use TheSeer\fDOM\fDOMDocument;
/**
*
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
*/
class fDOMDocumentTest extends \PHPUnit\Framework\TestCase {
/**
* @var fDOMDocument
*/
private $dom;
public function setUp() {
$this->dom = new fDOMDocument();
}
public function testloadingXMLStringWorks() {
$this->dom->loadXML('<?xml version="1.0" ?><test />');
$this->assertInstanceOf('TheSeer\fDOM\fDOMElement', $this->dom->documentElement);
}
public function testloadingXMLFileWorks() {
$this->dom->load(__DIR__ . '/_data/valid.xml');
$this->assertInstanceOf('TheSeer\fDOM\fDOMElement', $this->dom->documentElement);
}
public function testGetDomXPathReturnsXPathObject() {
$this->dom->loadXML('<?xml version="1.0" ?><test />');
$this->assertInstanceOf('TheSeer\fDOM\fDOMXpath', $this->dom->getDomXPath());
}
/**
* @expectedException \TheSeer\fDOM\fDOMException
*/
public function testAttemptingToLoadAnXMLStringWithAnUndefinedEntityThrowsException() {
$this->dom->loadXML('<?xml version="1.0" ?><root>&undefined;</root>');
}
/**
* @expectedException \TheSeer\fDOM\fDOMException
*/
public function testAttemptingToLoadAnEmptyXMLStringThrowsException() {
$this->dom->loadXML('');
}
/**
* @expectedException \TheSeer\fDOM\fDOMException
*/
public function testAttemptingToLoadWithEmptyFilenameThrowsException() {
$this->dom->load('');
}
/**
* @expectedException \TheSeer\fDOM\fDOMException
*/
public function testAttemptingToLoadHTMLWithAnEmptyFilenameThrowsException() {
$this->dom->loadHTMLFile('');
}
/**
* @expectedException \TheSeer\fDOM\fDOMException
*/
public function testAttemptingToLoadHMLWithAnEmptyStringThrowsException() {
$this->dom->loadHTML('');
}
/**
* @expectedException \TheSeer\fDOM\fDOMException
*/
public function testloadingInvalidXMLStringThrowsException() {
$this->dom->loadXML('<?xml version="1.0" ?><broken>');
}
/**
* @expectedException \TheSeer\fDOM\fDOMException
*/
public function testTryingToLoadNonExistingFileThrowsException() {
$this->dom->load('_does_not_exist.xml');
}
/**
* @expectedException \TheSeer\fDOM\fDOMException
*/
public function testloadingBrokenXMLFileThrowsException() {
$this->dom->load(__DIR__ . '/_data/broken.xml');
}
/**
* @expectedException \TheSeer\fDOM\fDOMException
*/
public function testAttemptingToLoadAnXMLFileWithAnUndefinedEntityThrowsException() {
$this->dom->load(__DIR__ . '/_data/undefentity.xml');
}
/**
* @covers \TheSeer\fDOM\fDOMDocument::query
*/
public function testQueryReturnsNodeList() {
$this->dom->load(__DIR__ . '/_data/valid.xml');
$list = $this->dom->query('/test');
$this->assertInstanceOf('DomNodelist', $list);
$this->assertEquals(1, $list->length);
}
/**
* @covers \TheSeer\fDOM\fDOMDocument::queryOne
*/
public function testQueryOneReturnsElement() {
$this->dom->load(__DIR__ . '/_data/valid.xml');
$node = $this->dom->queryOne('/test');
$this->assertInstanceOf('TheSeer\fDOM\fDOMElement', $node);
}
public function testSaveXMLReturnsCorrectXMLString() {
$xml = file_get_contents(__DIR__ . '/_data/valid.xml');
$this->dom->loadXML($xml);
$this->assertEquals($xml, $this->dom->saveXML());
}
/**
* @expectedException \TheSeer\fDOM\fDOMException
*/
public function testSaveXMLThrowsExceptionWithReferenceToNodeFromOtherDocument() {
$dom = new fDOMDocument();
$this->dom->saveXML($dom->createElement('foo'));
}
/**
* @covers \TheSeer\fDOM\fDOMDocument::nodeList2FragMent
*/
public function testTransformNodeListToFragmentWorks() {
$this->dom->loadXML('<?xml version="1.0" ?><root><node1/><node2 /></root>');
$frag = $this->dom->nodeList2Fragment($this->dom->query('/root/*'));
$this->assertInstanceOf('TheSeer\fDOM\fDOMDocumentFragment', $frag);
$this->assertEquals(2, $frag->childNodes->length);
}
public function testPrepareQueryReturnsValidXPathString() {
$values = array('key' => 'the "value" of \'values\'');
$xpath = '//some[@value = :key]';
$result = $this->dom->prepareQuery($xpath, $values);
$this->assertEquals('//some[@value = concat("the ",\'"\',"value",\'"\'," of \'values\'")]', $result);
}
public function testRegisteringANamespaceWithPrefixWorks() {
$this->dom->registerNamespace('test', 'test:uri');
$this->assertAttributeEquals(array('test' => 'test:uri'), 'prefixes', $this->dom);
}
/**
* @expectedException \TheSeer\fDOM\fDOMException
*/
public function testCreatingElementWithInvalidNameThrowsException() {
$node = $this->dom->createElement('in valid');
}
public function testCreatingElementWithoutText() {
$node = $this->dom->createElement('name');
$this->assertInstanceOf('TheSeer\fDOM\fDOMElement', $node);
$this->assertEquals('name', $node->nodeName);
}
/**
* @covers \TheSeer\fDOM\fDOMDocument::createElementPrefix
*/
public function testCreatingNewElementByprefix() {
$this->dom->registerNamespace('test', 'test:uri');
$node = $this->dom->createElementPrefix('test', 'node');
$this->assertEquals('test:uri', $node->namespaceURI);
}
/**
* @covers \TheSeer\fDOM\fDOMDocument::createElementPrefix
* @expectedException \TheSeer\fDOM\fDOMException
*/
public function testTryingToCreateNewElementByprefixWithUndefinedPrefixThrowsException() {
$this->dom->createElementPrefix('test', 'node');
}
public function testSettingContentUnescapedForNewElementRemainsIntact() {
$node = $this->dom->createElement('test', "test &amp; demo");
$this->assertInstanceOf('TheSeer\fDOM\fDOMElement', $node);
$this->assertEquals('test & demo', $node->nodeValue);
}
/**
* @expectedException \TheSeer\fDOM\fDOMException
*/
public function testSettingContentUnescapedForNewElementThrowsExceptionOnInvalidEntity() {
$node = $this->dom->createElement('test', "test & demo");
}
public function testSettingContentAsTextNodeForNewElementEncodesEntities() {
$node = $this->dom->createElement('test', "test &amp; demo", TRUE);
$this->assertEquals('test &amp; demo', $node->nodeValue);
}
public function testSettingContentUnescapedForNewElementWithNamespaceRemainsIntact() {
$node = $this->dom->createElementNS('test:uri', 'test', "test &amp; demo");
$this->assertInstanceOf('TheSeer\fDOM\fDOMElement', $node);
$this->assertEquals('test & demo', $node->nodeValue);
}
/**
* @expectedException \TheSeer\fDOM\fDOMException
*/
public function testSettingContentUnescapedForNewElementWithNamespaceThrowsExceptionOnInvalidEntity() {
$node = $this->dom->createElementNS('test:uri', 'test', "test & demo");
}
public function testSettingContentAsTextNodeForNewElementWithNamespaceEncodesEntities() {
$node = $this->dom->createElementNS('test:uri', 'test', "test &amp; demo", TRUE);
$this->assertInstanceOf('TheSeer\fDOM\fDOMElement', $node);
$this->assertEquals('test &amp; demo', $node->nodeValue);
}
/**
* @covers \TheSeer\fDOM\fDOMDocument::queryOne
*/
public function testThatTwoNodesAreIdentifiedAsBeingInTheSameDocument() {
$this->dom->loadXML('<?xml version="1.0" ?><root><node /></root>');
$node = $this->dom->queryOne('//node');
$this->assertTrue($this->dom->inSameDocument($node));
}
/**
* @covers \TheSeer\fDOM\fDOMDocument::inSameDocument
*/
public function testThatANodeFromADifferentDocumentIsNotConsideredAsInSameDocument() {
$dom = new fDOMDocument();
$node = $dom->createElement('foo');
$this->dom->loadXML('<?xml version="1.0" ?><root />');
$this->assertFalse($this->dom->documentElement->inSameDocument($node));
}
/**
* @covers \TheSeer\fDOM\fDOMDocument::inSameDocument
*/
public function testInSameDocumentWorksForDOMDocument() {
$dom = new fDOMDocument();
$this->assertFalse($this->dom->inSameDocument($dom));
}
public function testAppendElementCreatesANewNodeAndAttachesIt() {
$node = $this->dom->appendElement('test');
$this->assertSame($node, $this->dom->documentElement);
}
public function testAppendElementNSCreatesANewNodeAndAttachesIt() {
$node = $this->dom->appendElementNS('test:uri', 'test');
$this->assertSame($node, $this->dom->documentElement);
}
/**
* @covers \TheSeer\fDOM\fDOMDocument::__clone
*/
public function testCloningTriggersCreationOfNewDOMXPathInstance() {
$this->dom->loadXML('<?xml version="1.0" ?><test />');
$xp1 = $this->dom->getDOMXPath();
$clone = clone $this->dom;
$xp2 = $clone->getDOMXPath();
$this->assertNotSame($xp2, $xp1);
}
/**
* @covers \TheSeer\fDOM\fDOMDocument::__clone
*/
public function testRegisteredNamespacePrefixesGetCopiedToClonedDocument() {
$this->dom->loadXML('<?xml version="1.0" ?><foo:test xmlns:foo="test:uri" />');
$this->dom->registerNamespace('foo', 'test:uri');
$clone = clone $this->dom;
$node = $clone->queryOne('//foo:test');
$this->assertSame($clone->documentElement, $node);
}
/**
* https://github.com/theseer/fDOMDocument/issues/15
*/
public function testQueryReturnsNodeFromClonedDocument() {
$this->dom->loadXML('<?xml version="1.0" ?><test />');
$clone = clone $this->dom;
$node = $clone->queryOne('/test');
$this->assertNotSame($this->dom->documentElement, $node);
}
public function testCSSSelectorReturnsCorrectNodes() {
$this->dom->load(__DIR__ . '/_data/selector.xml');
$result = $this->dom->select('child');
$this->assertEquals(2, $result->length);
$this->assertEquals('child', $result->item(0)->nodeName);
$this->assertEquals('child', $result->item(1)->nodeName);
}
public function testCSSSelectorHonorsContextNode() {
$this->dom->load(__DIR__ . '/_data/selector.xml');
$ctx = $this->dom->getElementsByTagName('child')->item(0);
$result = $this->dom->select('child', $ctx);
$this->assertEquals(1, $result->length);
$this->assertEquals('child', $result->item(0)->nodeName);
$this->assertEquals('other', $result->item(0)->getAttribute('attr'));
}
}
}

View File

@@ -0,0 +1,137 @@
<?php
/**
* Copyright (c) 2010-2017 Arne Blankerts <arne@blankerts.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Arne Blankerts nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @link http://github.com/theseer/fdomdocument
*
*/
namespace TheSeer\fDOM\Tests {
use TheSeer\fDOM\fDOMDocument;
use TheSeer\fDOM\fDOMDocumentFragment;
/**
*
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
*/
class fDOMDocumentFragmentTest extends \PHPUnit\Framework\TestCase {
/**
* @var fDOMDocument
*/
private $dom;
/**
* @var fDOMDocumentFragment
*/
private $frag;
public function setUp() {
$this->dom = new fDOMDocument();
$this->frag = $this->dom->createDocumentFragment();
}
public function testAppendedXMLGetsAddedAndIsParsedAsXML() {
$this->frag->appendXML('<some />');
$this->assertEquals('some', $this->frag->firstChild->nodeName);
}
/**
* @expectedException \TheSeer\fDOM\fDOMException
*/
public function testTryingToAppendInvalidXMLToFragmentThrowsException() {
$this->frag->appendXML('<foo');
}
public function testCheckingInSameDocumentReturnsTrueOnNodeFromFragment() {
$this->frag->appendXML('<some />');
$this->assertTrue($this->frag->inSameDocument($this->frag->firstChild));
}
public function testAppendingANewElement() {
$node = $this->frag->appendElement('append', 'text');
$this->assertInstanceOf('TheSeer\fDOM\fDOMElement', $node);
$this->assertEquals(1, $this->frag->query('count(append)'));
$this->assertEquals('text', $node->nodeValue);
}
public function testAppendingANewElementWithinANamespace() {
$node = $this->frag->appendElementNS('test:uri', 'append', 'text');
$this->dom->registerNamespace('t', 'test:uri');
$this->assertInstanceOf('TheSeer\fDOM\fDOMElement', $node);
$this->assertEquals(1, $this->frag->query('count(t:append)'));
$this->assertEquals('text', $node->nodeValue);
}
public function testAppendingANewElementWithinANamespaceByPrefix() {
$this->dom->registerNamespace('t', 'test:uri');
$node = $this->frag->appendElementPrefix('t', 'append', 'text');
$this->assertInstanceOf('TheSeer\fDOM\fDOMElement', $node);
$this->assertEquals(1, $this->frag->query('count(t:append)'));
$this->assertEquals('text', $node->nodeValue);
}
public function testAppendingANewElementWithinANamespaceAsTextNodeByPrefix() {
$this->dom->registerNamespace('t', 'test:uri');
$node = $this->frag->appendElementPrefix('t', 'append', 'test & demo', true);
$this->assertInstanceOf('TheSeer\fDOM\fDOMElement', $node);
$this->assertEquals(1, $this->frag->query('count(t:append)'));
$this->assertEquals('test & demo', $node->nodeValue);
}
public function testAppendingATextAsTextnode() {
$node = $this->frag->appendTextNode('test & demo');
$found = $this->frag->queryOne('text()');
$this->assertSame($node, $found);
$this->assertEquals('test & demo', $node->nodeValue);
}
public function testCSSSelectorReturnsCorrectNodes() {
$node = $this->frag->appendElement('append', 'text');
$result = $this->frag->select('append');
$this->assertSame($node, $result->item(0));
$this->assertEquals(1, $result->length);
}
public function testToStringReturnsSerializedXMLString() {
$this->frag->appendElement('append', 'text');
$this->assertEquals('<append>text</append>', (string)$this->frag);
}
}
}

View File

@@ -0,0 +1,231 @@
<?php
/**
* Copyright (c) 2010-2017 Arne Blankerts <arne@blankerts.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Arne Blankerts nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @link http://github.com/theseer/fdomdocument
*
*/
namespace TheSeer\fDOM\Tests {
use TheSeer\fDOM\fDOMDocument;
use TheSeer\fDOM\fDOMElement;
/**
*
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
*/
class fDOMElementTest extends \PHPUnit\Framework\TestCase {
/**
* @var fDOMDocument
*/
private $dom;
/**
* @var fDOMElement
*/
private $node;
public function setUp() {
$this->dom = new fDOMDocument();
$this->dom->loadXML('<?xml version="1.0" ?><root><node><child/></node><node /></root>');
$this->node = $this->dom->documentElement;
}
/**
* The query is a forwarder to the DOMDocument, so just checking if the forwarding works is enough
*/
public function testQueryReturnsNodelist() {
$list = $this->node->query('//node');
$this->assertInstanceOf('DOMNodelist', $list);
$this->assertEquals(2, $list->length);
}
/**
* The query is a forwarder to the DOMDocument, so just checking if the forwarding works is enough
*/
public function testQueryOneReturnsNode() {
$node = $this->node->queryOne('//root');
$this->assertSame($this->node, $node);
}
public function testAppendingAnXMLStringCreatesAFragment() {
$frag = $this->node->appendXML('<append />');
$this->assertInstanceOf('TheSeer\fDOM\fDOMDocumentFragment', $frag);
$this->assertEquals(1, $this->node->query('count(append)'));
}
public function testAppendingANewElement() {
$node = $this->node->appendElement('append', 'text');
$this->assertInstanceOf('TheSeer\fDOM\fDOMElement', $node);
$this->assertEquals(1, $this->node->query('count(append)'));
$this->assertEquals('text', $node->nodeValue);
}
public function testAppendingANewElementAsTextNode() {
$node = $this->node->appendElement('append', 'test & demo', true);
$this->assertInstanceOf('TheSeer\fDOM\fDOMElement', $node);
$this->assertEquals(1, $this->node->query('count(append)'));
$this->assertEquals('test & demo', $node->nodeValue);
}
public function testAppendingANewElementWithinANamespace() {
$node = $this->node->appendElementNS('test:uri', 'append', 'text');
$this->dom->registerNamespace('t','test:uri');
$this->assertInstanceOf('TheSeer\fDOM\fDOMElement', $node);
$this->assertEquals(1, $this->node->query('count(t:append)'));
$this->assertEquals('text', $node->nodeValue);
}
public function testAppendingANewElementWithinANamespaceAsTextNode() {
$node = $this->node->appendElementNS('test:uri', 'append', 'test & demo', true);
$this->dom->registerNamespace('t','test:uri');
$this->assertInstanceOf('TheSeer\fDOM\fDOMElement', $node);
$this->assertEquals(1, $this->node->query('count(t:append)'));
$this->assertEquals('test & demo', $node->nodeValue);
}
public function testAppendingANewElementWithinANamespaceByPrefix() {
$this->dom->registerNamespace('t','test:uri');
$node = $this->node->appendElementPrefix('t', 'append', 'text');
$this->assertInstanceOf('TheSeer\fDOM\fDOMElement', $node);
$this->assertEquals(1, $this->node->query('count(t:append)'));
$this->assertEquals('text', $node->nodeValue);
}
public function testAppendingANewElementWithinANamespaceAsTextNodeByPrefix() {
$this->dom->registerNamespace('t','test:uri');
$node = $this->node->appendElementPrefix('t', 'append', 'test & demo', true);
$this->assertInstanceOf('TheSeer\fDOM\fDOMElement', $node);
$this->assertEquals(1, $this->node->query('count(t:append)'));
$this->assertEquals('test & demo', $node->nodeValue);
}
public function testAppendingATextAsTextnode() {
$node = $this->node->appendTextNode('test & demo');
$found = $this->node->queryOne('text()');
$this->assertSame($node, $found);
$this->assertEquals('test & demo', $node->nodeValue);
}
public function testGettingNonExistingAttributeReturnsDefaultValue() {
$res = $this->node->getAttribute('missing','default');
$this->assertEquals('default', $res);
}
public function testGettingNonExistingAttributeWithNamespaceReturnsDefaultValue() {
$res = $this->node->getAttributeNS('some:uri', 'missing','default');
$this->assertEquals('default', $res);
}
public function testSettingAttributeValueWithPlainValue() {
$attr = $this->node->setAttribute('test', 'value');
$this->assertTrue($this->node->hasAttribute('test'));
$this->assertInstanceOf('DOMAttr', $attr);
$this->assertEquals('value', $this->node->getAttribute('test'));
}
public function testSettingAttributeValueWithEntitiesEncodesProperly() {
$attr = $this->node->setAttribute('test', '&amp;');
$this->assertTrue($this->node->hasAttribute('test'));
$this->assertInstanceOf('DOMAttr', $attr);
$this->assertEquals('&amp;', $this->node->getAttribute('test'));
}
public function testSettingAttributeValueWithEntitiesAndKeepEntitiesEnabledDoesNotEncode() {
$attr = $this->node->setAttribute('test', '&amp;', true);
$this->assertTrue($this->node->hasAttribute('test'));
$this->assertInstanceOf('DOMAttr', $attr);
$this->assertEquals('&', $this->node->getAttribute('test'));
}
public function testSettingNamespacedAttributeValueWithPlainValue() {
$this->node->setAttributeNS('some:uri','s:attr', 'value');
$this->assertTrue($this->node->hasAttributeNS('some:uri','attr'));
$this->assertEquals('value', $this->node->getAttributeNS('some:uri','attr'));
}
public function testSettingNamespacedAttributeValueWithEntitiesEncodesProperly() {
$this->node->setAttributeNS('test:uri', 't:test', '&amp;');
$this->assertTrue($this->node->hasAttributeNS('test:uri', 'test'));
$this->assertEquals('&amp;', $this->node->getAttributeNS('test:uri','test'));
}
public function testSettingNamespacedAttributeValueWithEntitiesAndKeepEntitiesEnabledDoesNotEncode() {
$attr = $this->node->setAttributeNS('test:uri', 't:test', '&amp;', true);
$this->assertTrue($this->node->hasAttributeNS('test:uri', 'test'));
$this->assertInstanceOf('DOMAttr', $attr);
$this->assertEquals('&', $this->node->getAttributeNS('test:uri', 'test'));
}
public function testSettingMultipleAttributesFromArray() {
$attrs = array('a1' => 'v1', 'a2' => 'v2');
$this->node->setAttributes($attrs);
$this->assertEquals('v1', $this->node->getAttribute('a1'));
$this->assertEquals('v2', $this->node->getAttribute('a2'));
}
public function testSettingMultipleAttributesWithNamespaceFromArray() {
$attrs = array('a1' => 'v1', 'a2' => 'v2');
$this->node->setAttributesNS('some:uri','s', $attrs);
$this->assertEquals('v1', $this->node->getAttributeNS('some:uri', 'a1'));
$this->assertEquals('v2', $this->node->getAttributeNS('some:uri', 'a2'));
}
public function testGetChildrenByTagnameReturnsCorrectNodelist() {
$list = $this->node->getChildrenByTagName('node');
$this->assertInstanceOf('DOMNodeList', $list);
$this->assertEquals(2, $list->length);
}
public function testGetChildrenByTagnameNSReturnsCorrectNodelist() {
$this->dom->loadXML('<?xml version="1.0" ?><root xmlns="test:uri"><node><child/></node><node /></root>');
$this->node = $this->dom->documentElement;
$list = $this->node->getChildrenByTagNameNS('test:uri', 'node');
$this->assertInstanceOf('DOMNodeList', $list);
$this->assertEquals(2, $list->length);
}
public function testInSameDocumentForwardsToOwnerDocumentAndReturnsCorrectValue() {
$rc = $this->node->inSameDocument($this->node->firstChild);
$this->assertTrue($rc);
}
}
}

View File

@@ -0,0 +1,114 @@
<?php
/**
* Copyright (c) 2010-2017 Arne Blankerts <arne@blankerts.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Arne Blankerts nor the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER ORCONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @category PHP
* @package TheSeer\fDOM
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @link http://github.com/theseer/fdomdocument
*
*/
namespace TheSeer\fDOM\Tests {
use TheSeer\fDOM\fDOMDocument;
use TheSeer\fDOM\fDOMXPath;
/**
*
* @author Arne Blankerts <arne@blankerts.de>
* @copyright Arne Blankerts <arne@blankerts.de>, All rights reserved.
*/
class fDOMXPathTest extends \PHPUnit\Framework\TestCase {
/**
* @var TheSeer\fDOM\fDOMDocument
*/
private $dom;
/**
* @var TheSeer\fDOM\fDOMXPath
*/
private $xp;
public function setUp() {
$this->dom = new fDOMDocument();
$this->dom->loadXML('<?xml version="1.0" ?><root><node attr="foo" /></root>');
$this->xp = $this->dom->getDOMXPath();
}
/**
* @covers TheSeer\fDOM\fDOMXPath::query
* @expectedException TheSeer\fDOM\fDOMException
*/
public function testExecutingAQueryWithInvalidXPathThrowsException() {
$this->xp->query('//[invalid');
}
public function testQueryReturnsNodeList() {
$res = $this->xp->query('//*');
$this->assertInstanceOf('DOMNodeList', $res);
$this->assertEquals(2, $res->length);
}
/**
* @covers TheSeer\fDOM\fDOMXPath::evaluate
* @expectedException TheSeer\fDOM\fDOMException
*/
public function testExecutingAQueryWithEvaluateWithInvalidXPathThrowsException() {
$this->xp->evaluate('//[invalid');
}
/**
* @covers TheSeer\fDOM\fDOMXPath::quote
*/
public function testPrepareReturnsStraightStringOnPlainText() {
$this->assertEquals('"test"', $this->xp->quote('test'));
}
public function testQueryOneReturnsANode() {
$this->assertSame($this->dom->documentElement, $this->xp->queryOne('//root'));
}
public function testPrepareReturnsUnmodifiedXPathOnEmptyArray() {
$xpath = 'test';
$this->assertEquals($xpath, $this->xp->prepare($xpath, array()));
}
public function testQueryOneReturnsValueOnNonNodeQuery() {
$this->assertEquals('1', $this->xp->queryOne('count(//root)'));
}
}
}

View File

@@ -5,3 +5,4 @@
/tools
/vendor
/build
/.phpunit.result.cache

View File

@@ -17,11 +17,12 @@ install:
script: ./tools/phpunit
php:
- 5.6
- 7.0
- 7.1
- 7.0snapshot
- 7.1snapshot
- 7.2
- 7.3
- master
matrix:

View File

@@ -0,0 +1,32 @@
# Changelog
All notable changes to Tokenizer are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
## [1.1.2] - 2019-04-04
### Changed
* Reverted PHPUnit 8 test update to stay PHP 7.0 compliant
## [1.1.1] - 2019-04-03
### Fixed
* [#1](https://github.com/theseer/tokenizer/issues/1): Empty file causes invalid array read
### Changed
* Tests should now be PHPUnit 8 compliant
## [1.1.0] - 2017-04-07
### Added
* Allow use of custom namespace for XML serialization
## [1.0.0] - 2017-04-05
Initial Release

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<phive xmlns="https://phar.io/phive">
<phar name="phpunit" version="^6.0" installed="6.0.10" location="./tools/phpunit"/>
<phar name="phpab" version="^1.23.0" installed="1.23.0" location="./tools/phpab"/>
<phar name="phpunit" version="^6.0" installed="6.5.14" location="./tools/phpunit" copy="false"/>
<phar name="phpab" version="^1.23.0" installed="1.25.3" location="./tools/phpab" copy="false"/>
</phive>

View File

@@ -41,6 +41,11 @@ class Tokenizer {
public function parse(string $source): TokenCollection {
$result = new TokenCollection();
if ($source === '') {
return $result;
}
$tokens = token_get_all($source);
$lastToken = new Token(

View File

@@ -57,12 +57,15 @@ class XMLSerializer {
$this->writer->startDocument();
$this->writer->startElement('source');
$this->writer->writeAttribute('xmlns', $this->xmlns->asString());
$this->writer->startElement('line');
$this->writer->writeAttribute('no', '1');
$this->previousToken = $tokens[0];
foreach ($tokens as $token) {
$this->addToken($token);
if (count($tokens) > 0) {
$this->writer->startElement('line');
$this->writer->writeAttribute('no', '1');
$this->previousToken = $tokens[0];
foreach ($tokens as $token) {
$this->addToken($token);
}
}
$this->writer->endElement();

View File

@@ -40,4 +40,11 @@ class XMLSerializerTest extends TestCase {
$this->assertEquals($expected, $serializer->toXML($this->tokens));
}
public function testEmptyCollectionCreatesEmptyDocument() {
$expected = file_get_contents(__DIR__ . '/_files/empty.xml');
$serializer = new XMLSerializer();
$this->assertEquals($expected, $serializer->toXML((new TokenCollection())));
}
}

View File

@@ -0,0 +1,2 @@
<?xml version="1.0"?>
<source xmlns="https://github.com/theseer/tokenizer"/>