loader ajax
This commit is contained in:
+7
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInite863a36850238c27fe275e4df5c3a50c::getLoader();
|
||||
Vendored
+445
@@ -0,0 +1,445 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see http://www.php-fig.org/psr/psr-0/
|
||||
* @see http://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
// PSR-4
|
||||
private $prefixLengthsPsr4 = array();
|
||||
private $prefixDirsPsr4 = array();
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
private $prefixesPsr0 = array();
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
private $useIncludePath = false;
|
||||
private $classMap = array();
|
||||
private $classMapAuthoritative = false;
|
||||
private $missingClasses = array();
|
||||
private $apcuPrefix;
|
||||
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', $this->prefixesPsr0);
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $classMap Class to filename map
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 base directories
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return bool|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath.'\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
$length = $this->prefixLengthsPsr4[$first][$search];
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*/
|
||||
function includeFile($file)
|
||||
{
|
||||
include $file;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'stringEncode' => array($vendorDir . '/paquettg/string-encode/src'),
|
||||
'PHPHtmlParser' => array($vendorDir . '/paquettg/php-html-parser/src'),
|
||||
);
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInite863a36850238c27fe275e4df5c3a50c
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInite863a36850238c27fe275e4df5c3a50c', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInite863a36850238c27fe275e4df5c3a50c', 'loadClassLoader'));
|
||||
|
||||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
||||
if ($useStaticLoader) {
|
||||
require_once __DIR__ . '/autoload_static.php';
|
||||
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInite863a36850238c27fe275e4df5c3a50c::getInitializer($loader));
|
||||
} else {
|
||||
$map = require __DIR__ . '/autoload_namespaces.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
$loader->set($namespace, $path);
|
||||
}
|
||||
|
||||
$map = require __DIR__ . '/autoload_psr4.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
$loader->setPsr4($namespace, $path);
|
||||
}
|
||||
|
||||
$classMap = require __DIR__ . '/autoload_classmap.php';
|
||||
if ($classMap) {
|
||||
$loader->addClassMap($classMap);
|
||||
}
|
||||
}
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInite863a36850238c27fe275e4df5c3a50c
|
||||
{
|
||||
public static $prefixesPsr0 = array (
|
||||
's' =>
|
||||
array (
|
||||
'stringEncode' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/paquettg/string-encode/src',
|
||||
),
|
||||
),
|
||||
'P' =>
|
||||
array (
|
||||
'PHPHtmlParser' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/paquettg/php-html-parser/src',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixesPsr0 = ComposerStaticInite863a36850238c27fe275e4df5c3a50c::$prefixesPsr0;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
[
|
||||
{
|
||||
"name": "paquettg/string-encode",
|
||||
"version": "1.0.1",
|
||||
"version_normalized": "1.0.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paquettg/string-encoder.git",
|
||||
"reference": "a8708e9fac9d5ddfc8fc2aac6004e2cd05d80fee"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paquettg/string-encoder/zipball/a8708e9fac9d5ddfc8fc2aac6004e2cd05d80fee",
|
||||
"reference": "a8708e9fac9d5ddfc8fc2aac6004e2cd05d80fee",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.5.1"
|
||||
},
|
||||
"time": "2018-12-21T02:25:09+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"stringEncode": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gilles Paquette",
|
||||
"email": "paquettg@gmail.com",
|
||||
"homepage": "http://gillespaquette.ca"
|
||||
}
|
||||
],
|
||||
"description": "Facilitating the process of altering string encoding in PHP.",
|
||||
"homepage": "https://github.com/paquettg/string-encoder",
|
||||
"keywords": [
|
||||
"charset",
|
||||
"encoding",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "paquettg/php-html-parser",
|
||||
"version": "2.0.2",
|
||||
"version_normalized": "2.0.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paquettg/php-html-parser.git",
|
||||
"reference": "77e4a44b0916690b4300fe9abf98fd05bbba48f0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paquettg/php-html-parser/zipball/77e4a44b0916690b4300fe9abf98fd05bbba48f0",
|
||||
"reference": "77e4a44b0916690b4300fe9abf98fd05bbba48f0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"paquettg/string-encode": "~1.0.0",
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.2",
|
||||
"php-coveralls/php-coveralls": "^2.1",
|
||||
"phpunit/phpunit": "^7.5.1"
|
||||
},
|
||||
"time": "2019-02-10T01:35:49+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"PHPHtmlParser": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gilles Paquette",
|
||||
"email": "paquettg@gmail.com",
|
||||
"homepage": "http://gillespaquette.ca"
|
||||
}
|
||||
],
|
||||
"description": "An HTML DOM parser. It allows you to manipulate HTML. Find tags on an HTML page with selectors just like jQuery.",
|
||||
"homepage": "https://github.com/paquettg/php-html-parser",
|
||||
"keywords": [
|
||||
"dom",
|
||||
"html",
|
||||
"parser"
|
||||
]
|
||||
}
|
||||
]
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/tests export-ignore
|
||||
/.scrutinizar.yml export-ignore
|
||||
/.travis.yml export-ignore
|
||||
/.gitignore export-ignore
|
||||
/CHANGELOG.md export-ignore
|
||||
/CONTRIBUTING.md export-ignore
|
||||
/LICENSE.md export-ignore
|
||||
/README.md export-ignore
|
||||
/phpunit.php export-ignore
|
||||
/phpunit.xml export-ignore
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
filter:
|
||||
paths: [src/*]
|
||||
excluded_paths: [tests/*]
|
||||
checks:
|
||||
php:
|
||||
code_rating: true
|
||||
remove_extra_empty_lines: true
|
||||
remove_php_closing_tag: true
|
||||
remove_trailing_whitespace: true
|
||||
fix_use_statements:
|
||||
remove_unused: true
|
||||
preserve_multiple: false
|
||||
preserve_blanklines: true
|
||||
order_alphabetically: true
|
||||
fix_php_opening_tag: true
|
||||
fix_linefeed: true
|
||||
fix_line_ending: true
|
||||
fix_identation_4spaces: true
|
||||
fix_doc_comments: true
|
||||
tools:
|
||||
external_code_coverage:
|
||||
timeout: 600
|
||||
runs: 3
|
||||
php_code_coverage: false
|
||||
php_code_sniffer:
|
||||
config:
|
||||
standard: PSR2
|
||||
filter:
|
||||
paths: ['src']
|
||||
php_loc:
|
||||
enabled: true
|
||||
excluded_dirs: [vendor, test]
|
||||
php_cpd:
|
||||
enabled: true
|
||||
excluded_dirs: [vendor, test]
|
||||
build:
|
||||
nodes:
|
||||
analysis:
|
||||
tests:
|
||||
override:
|
||||
- php-scrutinizer-run
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "paquettg/php-html-parser",
|
||||
"type": "library",
|
||||
"version": "2.0.2",
|
||||
"description": "An HTML DOM parser. It allows you to manipulate HTML. Find tags on an HTML page with selectors just like jQuery.",
|
||||
"keywords": ["html", "dom", "parser"],
|
||||
"homepage": "https://github.com/paquettg/php-html-parser",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gilles Paquette",
|
||||
"email": "paquettg@gmail.com",
|
||||
"homepage": "http://gillespaquette.ca"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1",
|
||||
"ext-mbstring": "*",
|
||||
"paquettg/string-encode": "~1.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.5.1",
|
||||
"mockery/mockery": "^1.2",
|
||||
"php-coveralls/php-coveralls": "^2.1"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"PHPHtmlParser": "src/"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser;
|
||||
|
||||
/**
|
||||
* Class Content
|
||||
*
|
||||
* @package PHPHtmlParser
|
||||
*/
|
||||
class Content
|
||||
{
|
||||
|
||||
/**
|
||||
* The content string.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* The size of the content.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $size;
|
||||
|
||||
/**
|
||||
* The current position we are in the content.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $pos;
|
||||
|
||||
/**
|
||||
* The following 4 strings are tags that are important to us.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $blank = " \t\r\n";
|
||||
protected $equal = ' =/>';
|
||||
protected $slash = " />\r\n\t";
|
||||
protected $attr = ' >';
|
||||
|
||||
/**
|
||||
* Content constructor.
|
||||
*
|
||||
* @param string $content
|
||||
*/
|
||||
public function __construct(string $content = '')
|
||||
{
|
||||
$this->content = $content;
|
||||
$this->size = strlen($content);
|
||||
$this->pos = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current position of the content.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPosition(): int
|
||||
{
|
||||
return $this->pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current character we are at.
|
||||
*
|
||||
* @param int $char
|
||||
* @return string
|
||||
*/
|
||||
public function char(int $char = null): string
|
||||
{
|
||||
$pos = $this->pos;
|
||||
if ( ! is_null($char)) {
|
||||
$pos = $char;
|
||||
}
|
||||
|
||||
if ( ! isset($this->content[$pos])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->content[$pos];
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the current position forward.
|
||||
*
|
||||
* @param int $count
|
||||
* @return Content
|
||||
* @chainable
|
||||
*/
|
||||
public function fastForward(int $count): Content
|
||||
{
|
||||
$this->pos += $count;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the current position backward.
|
||||
*
|
||||
* @param int $count
|
||||
* @return Content
|
||||
* @chainable
|
||||
*/
|
||||
public function rewind(int $count): Content
|
||||
{
|
||||
$this->pos -= $count;
|
||||
if ($this->pos < 0) {
|
||||
$this->pos = 0;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the content until we find the given string.
|
||||
*
|
||||
* @param string $string
|
||||
* @param bool $char
|
||||
* @param bool $escape
|
||||
* @return string
|
||||
*/
|
||||
public function copyUntil(string $string, bool $char = false, bool $escape = false): string
|
||||
{
|
||||
if ($this->pos >= $this->size) {
|
||||
// nothing left
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($escape) {
|
||||
$position = $this->pos;
|
||||
$found = false;
|
||||
while ( ! $found) {
|
||||
$position = strpos($this->content, $string, $position);
|
||||
if ($position === false) {
|
||||
// reached the end
|
||||
$found = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->char($position - 1) == '\\') {
|
||||
// this character is escaped
|
||||
++$position;
|
||||
continue;
|
||||
}
|
||||
|
||||
$found = true;
|
||||
}
|
||||
} elseif ($char) {
|
||||
$position = strcspn($this->content, $string, $this->pos);
|
||||
$position += $this->pos;
|
||||
} else {
|
||||
$position = strpos($this->content, $string, $this->pos);
|
||||
}
|
||||
|
||||
if ($position === false) {
|
||||
// could not find character, just return the remaining of the content
|
||||
$return = substr($this->content, $this->pos, $this->size - $this->pos);
|
||||
$this->pos = $this->size;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
if ($position == $this->pos) {
|
||||
// we are at the right place
|
||||
return '';
|
||||
}
|
||||
|
||||
$return = substr($this->content, $this->pos, $position - $this->pos);
|
||||
// set the new position
|
||||
$this->pos = $position;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the content until the string is found and return it
|
||||
* unless the 'unless' is found in the substring.
|
||||
*
|
||||
* @param string $string
|
||||
* @param string $unless
|
||||
* @return string
|
||||
*/
|
||||
public function copyUntilUnless(string $string, string $unless)
|
||||
{
|
||||
$lastPos = $this->pos;
|
||||
$this->fastForward(1);
|
||||
$foundString = $this->copyUntil($string, true, true);
|
||||
|
||||
$position = strcspn($foundString, $unless);
|
||||
if ($position == strlen($foundString)) {
|
||||
return $string.$foundString;
|
||||
}
|
||||
// rewind changes and return nothing
|
||||
$this->pos = $lastPos;
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the content until it reaches the token string.,
|
||||
*
|
||||
* @param string $token
|
||||
* @param bool $char
|
||||
* @param bool $escape
|
||||
* @return string
|
||||
* @uses $this->copyUntil()
|
||||
*/
|
||||
public function copyByToken(string $token, bool $char = false, bool $escape = false)
|
||||
{
|
||||
$string = $this->$token;
|
||||
|
||||
return $this->copyUntil($string, $char, $escape);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip a given set of characters.
|
||||
*
|
||||
* @param string $string
|
||||
* @param bool $copy
|
||||
* @return Content|string
|
||||
*/
|
||||
public function skip(string $string, bool $copy = false)
|
||||
{
|
||||
$len = strspn($this->content, $string, $this->pos);
|
||||
|
||||
// make it chainable if they don't want a copy
|
||||
$return = $this;
|
||||
if ($copy) {
|
||||
$return = substr($this->content, $this->pos, $len);
|
||||
}
|
||||
|
||||
// update the position
|
||||
$this->pos += $len;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip a given token of pre-defined characters.
|
||||
*
|
||||
* @param string $token
|
||||
* @param bool $copy
|
||||
* @return Content|string
|
||||
* @uses $this->skip()
|
||||
*/
|
||||
public function skipByToken(string $token, bool $copy = false)
|
||||
{
|
||||
$string = $this->$token;
|
||||
|
||||
return $this->skip($string, $copy);
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser;
|
||||
|
||||
use PHPHtmlParser\Exceptions\CurlException;
|
||||
|
||||
/**
|
||||
* Class Curl
|
||||
*
|
||||
* @package PHPHtmlParser
|
||||
*/
|
||||
class Curl implements CurlInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* A simple curl implementation to get the content of the url.
|
||||
*
|
||||
* @param string $url
|
||||
* @return string
|
||||
* @throws CurlException
|
||||
*/
|
||||
public function get(string $url): string
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
|
||||
if ( ! ini_get('open_basedir')) {
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36');
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
|
||||
$content = curl_exec($ch);
|
||||
if ($content === false) {
|
||||
// there was a problem
|
||||
$error = curl_error($ch);
|
||||
throw new CurlException('Error retrieving "'.$url.'" ('.$error.')');
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser;
|
||||
|
||||
/**
|
||||
* Interface CurlInterface
|
||||
*
|
||||
* @package PHPHtmlParser
|
||||
*/
|
||||
interface CurlInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* This method should return the content of the url in a string
|
||||
*
|
||||
* @param string $url
|
||||
* @return string
|
||||
*/
|
||||
public function get(string $url): string;
|
||||
}
|
||||
+782
@@ -0,0 +1,782 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser;
|
||||
|
||||
use PHPHtmlParser\Dom\AbstractNode;
|
||||
use PHPHtmlParser\Dom\HtmlNode;
|
||||
use PHPHtmlParser\Dom\TextNode;
|
||||
use PHPHtmlParser\Exceptions\NotLoadedException;
|
||||
use PHPHtmlParser\Exceptions\StrictException;
|
||||
use stringEncode\Encode;
|
||||
|
||||
/**
|
||||
* Class Dom
|
||||
*
|
||||
* @package PHPHtmlParser
|
||||
*/
|
||||
class Dom
|
||||
{
|
||||
|
||||
/**
|
||||
* The charset we would like the output to be in.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultCharset = 'UTF-8';
|
||||
|
||||
/**
|
||||
* Contains the root node of this dom tree.
|
||||
*
|
||||
* @var HtmlNode
|
||||
*/
|
||||
public $root;
|
||||
|
||||
/**
|
||||
* The raw version of the document string.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $raw;
|
||||
|
||||
/**
|
||||
* The document string.
|
||||
*
|
||||
* @var Content
|
||||
*/
|
||||
protected $content = null;
|
||||
|
||||
/**
|
||||
* The original file size of the document.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $rawSize;
|
||||
|
||||
/**
|
||||
* The size of the document after it is cleaned.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $size;
|
||||
|
||||
/**
|
||||
* A global options array to be used by all load calls.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $globalOptions = [];
|
||||
|
||||
/**
|
||||
* A persistent option object to be used for all options in the
|
||||
* parsing of the file.
|
||||
*
|
||||
* @var Options
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* A list of tags which will always be self closing
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $selfClosing = [
|
||||
'area',
|
||||
'base',
|
||||
'basefont',
|
||||
'br',
|
||||
'col',
|
||||
'embed',
|
||||
'hr',
|
||||
'img',
|
||||
'input',
|
||||
'keygen',
|
||||
'link',
|
||||
'meta',
|
||||
'param',
|
||||
'source',
|
||||
'spacer',
|
||||
'track',
|
||||
'wbr'
|
||||
];
|
||||
|
||||
/**
|
||||
* A list of tags where there should be no /> at the end (html5 style)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $noSlash = [];
|
||||
|
||||
/**
|
||||
* Returns the inner html of the root node.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->root->innerHtml();
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple wrapper around the root node.
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
return $this->root->$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load the dom from any resource, string, file, or URL.
|
||||
*
|
||||
* @param string $str
|
||||
* @param array $options
|
||||
* @return Dom
|
||||
* @chainable
|
||||
*/
|
||||
public function load(string $str, array $options = []): Dom
|
||||
{
|
||||
AbstractNode::resetCount();
|
||||
// check if it's a file
|
||||
if (strpos($str, "\n") === false && is_file($str)) {
|
||||
return $this->loadFromFile($str, $options);
|
||||
}
|
||||
// check if it's a url
|
||||
if (preg_match("/^https?:\/\//i", $str)) {
|
||||
return $this->loadFromUrl($str, $options);
|
||||
}
|
||||
|
||||
return $this->loadStr($str, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the dom from a document file/url
|
||||
*
|
||||
* @param string $file
|
||||
* @param array $options
|
||||
* @return Dom
|
||||
* @chainable
|
||||
*/
|
||||
public function loadFromFile(string $file, array $options = []): Dom
|
||||
{
|
||||
return $this->loadStr(file_get_contents($file), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a curl interface implementation to attempt to load
|
||||
* the content from a url.
|
||||
*
|
||||
* @param string $url
|
||||
* @param array $options
|
||||
* @param CurlInterface $curl
|
||||
* @return Dom
|
||||
* @chainable
|
||||
*/
|
||||
public function loadFromUrl(string $url, array $options = [], CurlInterface $curl = null): Dom
|
||||
{
|
||||
if (is_null($curl)) {
|
||||
// use the default curl interface
|
||||
$curl = new Curl;
|
||||
}
|
||||
$content = $curl->get($url);
|
||||
|
||||
return $this->loadStr($content, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsers the html of the given string. Used for load(), loadFromFile(),
|
||||
* and loadFromUrl().
|
||||
*
|
||||
* @param string $str
|
||||
* @param array $option
|
||||
* @return Dom
|
||||
* @chainable
|
||||
*/
|
||||
public function loadStr(string $str, array $option = []): Dom
|
||||
{
|
||||
$this->options = new Options;
|
||||
$this->options->setOptions($this->globalOptions)
|
||||
->setOptions($option);
|
||||
|
||||
$this->rawSize = strlen($str);
|
||||
$this->raw = $str;
|
||||
|
||||
$html = $this->clean($str);
|
||||
|
||||
$this->size = strlen($str);
|
||||
$this->content = new Content($html);
|
||||
|
||||
$this->parse();
|
||||
$this->detectCharset();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a global options array to be used by all load calls.
|
||||
*
|
||||
* @param array $options
|
||||
* @return Dom
|
||||
* @chainable
|
||||
*/
|
||||
public function setOptions(array $options): Dom
|
||||
{
|
||||
$this->globalOptions = $options;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find elements by css selector on the root node.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param int $nth
|
||||
* @return mixed
|
||||
*/
|
||||
public function find(string $selector, int $nth = null)
|
||||
{
|
||||
$this->isLoaded();
|
||||
|
||||
return $this->root->find($selector, $nth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find element by Id on the root node
|
||||
*
|
||||
* @param int $id
|
||||
* @return mixed
|
||||
*/
|
||||
public function findById(int $id)
|
||||
{
|
||||
$this->isLoaded();
|
||||
|
||||
return $this->root->findById($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the tag (or tags in an array) to the list of tags that will always
|
||||
* be self closing.
|
||||
*
|
||||
* @param string|array $tag
|
||||
* @return Dom
|
||||
* @chainable
|
||||
*/
|
||||
public function addSelfClosingTag($tag): Dom
|
||||
{
|
||||
if ( ! is_array($tag)) {
|
||||
$tag = [$tag];
|
||||
}
|
||||
foreach ($tag as $value) {
|
||||
$this->selfClosing[] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the tag (or tags in an array) from the list of tags that will
|
||||
* always be self closing.
|
||||
*
|
||||
* @param string|array $tag
|
||||
* @return Dom
|
||||
* @chainable
|
||||
*/
|
||||
public function removeSelfClosingTag($tag): Dom
|
||||
{
|
||||
if ( ! is_array($tag)) {
|
||||
$tag = [$tag];
|
||||
}
|
||||
$this->selfClosing = array_diff($this->selfClosing, $tag);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of self closing tags to empty.
|
||||
*
|
||||
* @return Dom
|
||||
* @chainable
|
||||
*/
|
||||
public function clearSelfClosingTags(): Dom
|
||||
{
|
||||
$this->selfClosing = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a tag to the list of self closing tags that should not have a trailing slash
|
||||
*
|
||||
* @param $tag
|
||||
* @return Dom
|
||||
* @chainable
|
||||
*/
|
||||
public function addNoSlashTag($tag): Dom
|
||||
{
|
||||
if ( ! is_array($tag)) {
|
||||
$tag = [$tag];
|
||||
}
|
||||
foreach ($tag as $value) {
|
||||
$this->noSlash[] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a tag from the list of no-slash tags.
|
||||
*
|
||||
* @param $tag
|
||||
* @return Dom
|
||||
* @chainable
|
||||
*/
|
||||
public function removeNoSlashTag($tag): Dom
|
||||
{
|
||||
if ( ! is_array($tag)) {
|
||||
$tag = [$tag];
|
||||
}
|
||||
$this->noSlash = array_diff($this->noSlash, $tag);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties the list of no-slash tags.
|
||||
*
|
||||
* @return Dom
|
||||
* @chainable
|
||||
*/
|
||||
public function clearNoSlashTags(): Dom
|
||||
{
|
||||
$this->noSlash = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple wrapper function that returns the first child.
|
||||
*
|
||||
* @return \PHPHtmlParser\Dom\AbstractNode
|
||||
*/
|
||||
public function firstChild(): \PHPHtmlParser\Dom\AbstractNode
|
||||
{
|
||||
$this->isLoaded();
|
||||
|
||||
return $this->root->firstChild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple wrapper function that returns the last child.
|
||||
*
|
||||
* @return \PHPHtmlParser\Dom\AbstractNode
|
||||
*/
|
||||
public function lastChild(): \PHPHtmlParser\Dom\AbstractNode
|
||||
{
|
||||
$this->isLoaded();
|
||||
|
||||
return $this->root->lastChild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple wrapper function that returns count of child elements
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function countChildren(): int
|
||||
{
|
||||
$this->isLoaded();
|
||||
|
||||
return $this->root->countChildren();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of children
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChildren(): array
|
||||
{
|
||||
$this->isLoaded();
|
||||
|
||||
return $this->root->getChildren();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if node have children nodes
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasChildren(): bool
|
||||
{
|
||||
$this->isLoaded();
|
||||
|
||||
return $this->root->hasChildren();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple wrapper function that returns an element by the
|
||||
* id.
|
||||
*
|
||||
* @param string $id
|
||||
* @return \PHPHtmlParser\Dom\AbstractNode|null
|
||||
*/
|
||||
public function getElementById($id)
|
||||
{
|
||||
$this->isLoaded();
|
||||
|
||||
return $this->find('#'.$id, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple wrapper function that returns all elements by
|
||||
* tag name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function getElementsByTag(string $name)
|
||||
{
|
||||
$this->isLoaded();
|
||||
|
||||
return $this->find($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple wrapper function that returns all elements by
|
||||
* class name.
|
||||
*
|
||||
* @param string $class
|
||||
* @return mixed
|
||||
*/
|
||||
public function getElementsByClass(string $class)
|
||||
{
|
||||
$this->isLoaded();
|
||||
|
||||
return $this->find('.'.$class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the load methods have been called.
|
||||
*
|
||||
* @throws NotLoadedException
|
||||
*/
|
||||
protected function isLoaded(): void
|
||||
{
|
||||
if (is_null($this->content)) {
|
||||
throw new NotLoadedException('Content is not loaded!');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans the html of any none-html information.
|
||||
*
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
protected function clean(string $str): string
|
||||
{
|
||||
if ($this->options->get('cleanupInput') != true) {
|
||||
// skip entire cleanup step
|
||||
return $str;
|
||||
}
|
||||
|
||||
// remove white space before closing tags
|
||||
$str = mb_eregi_replace("'\s+>", "'>", $str);
|
||||
$str = mb_eregi_replace('"\s+>', '">', $str);
|
||||
|
||||
// clean out the \n\r
|
||||
$replace = ' ';
|
||||
if ($this->options->get('preserveLineBreaks')) {
|
||||
$replace = ' ';
|
||||
}
|
||||
$str = str_replace(["\r\n", "\r", "\n"], $replace, $str);
|
||||
|
||||
// strip the doctype
|
||||
$str = mb_eregi_replace("<!doctype(.*?)>", '', $str);
|
||||
|
||||
// strip out comments
|
||||
$str = mb_eregi_replace("<!--(.*?)-->", '', $str);
|
||||
|
||||
// strip out cdata
|
||||
$str = mb_eregi_replace("<!\[CDATA\[(.*?)\]\]>", '', $str);
|
||||
|
||||
// strip out <script> tags
|
||||
if ($this->options->get('removeScripts') == true) {
|
||||
$str = mb_eregi_replace("<\s*script[^>]*[^/]>(.*?)<\s*/\s*script\s*>", '', $str);
|
||||
$str = mb_eregi_replace("<\s*script\s*>(.*?)<\s*/\s*script\s*>", '', $str);
|
||||
}
|
||||
|
||||
// strip out <style> tags
|
||||
if ($this->options->get('removeStyles') == true) {
|
||||
$str = mb_eregi_replace("<\s*style[^>]*[^/]>(.*?)<\s*/\s*style\s*>", '', $str);
|
||||
$str = mb_eregi_replace("<\s*style\s*>(.*?)<\s*/\s*style\s*>", '', $str);
|
||||
}
|
||||
|
||||
// strip out server side scripts
|
||||
if ($this->options->get('serverSideScriptis') == true){
|
||||
$str = mb_eregi_replace("(<\?)(.*?)(\?>)", '', $str);
|
||||
}
|
||||
|
||||
// strip smarty scripts
|
||||
$str = mb_eregi_replace("(\{\w)(.*?)(\})", '', $str);
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse the html in content.
|
||||
*/
|
||||
protected function parse(): void
|
||||
{
|
||||
// add the root node
|
||||
$this->root = new HtmlNode('root');
|
||||
$activeNode = $this->root;
|
||||
while ( ! is_null($activeNode)) {
|
||||
$str = $this->content->copyUntil('<');
|
||||
if ($str == '') {
|
||||
$info = $this->parseTag();
|
||||
if ( ! $info['status']) {
|
||||
// we are done here
|
||||
$activeNode = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if it was a closing tag
|
||||
if ($info['closing']) {
|
||||
$foundOpeningTag = true;
|
||||
$originalNode = $activeNode;
|
||||
while ($activeNode->getTag()->name() != $info['tag']) {
|
||||
$activeNode = $activeNode->getParent();
|
||||
if (is_null($activeNode)) {
|
||||
// we could not find opening tag
|
||||
$activeNode = $originalNode;
|
||||
$foundOpeningTag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($foundOpeningTag) {
|
||||
$activeNode = $activeNode->getParent();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! isset($info['node'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var AbstractNode $node */
|
||||
$node = $info['node'];
|
||||
$activeNode->addChild($node);
|
||||
|
||||
// check if node is self closing
|
||||
if ( ! $node->getTag()->isSelfClosing()) {
|
||||
$activeNode = $node;
|
||||
}
|
||||
} else if ($this->options->whitespaceTextNode ||
|
||||
trim($str) != ''
|
||||
) {
|
||||
// we found text we care about
|
||||
$textNode = new TextNode($str, $this->options->removeDoubleSpace);
|
||||
$activeNode->addChild($textNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to parse a tag out of the content.
|
||||
*
|
||||
* @return array
|
||||
* @throws StrictException
|
||||
*/
|
||||
protected function parseTag(): array
|
||||
{
|
||||
$return = [
|
||||
'status' => false,
|
||||
'closing' => false,
|
||||
'node' => null,
|
||||
];
|
||||
if ($this->content->char() != '<') {
|
||||
// we are not at the beginning of a tag
|
||||
return $return;
|
||||
}
|
||||
|
||||
// check if this is a closing tag
|
||||
if ($this->content->fastForward(1)->char() == '/') {
|
||||
// end tag
|
||||
$tag = $this->content->fastForward(1)
|
||||
->copyByToken('slash', true);
|
||||
// move to end of tag
|
||||
$this->content->copyUntil('>');
|
||||
$this->content->fastForward(1);
|
||||
|
||||
// check if this closing tag counts
|
||||
$tag = strtolower($tag);
|
||||
if (in_array($tag, $this->selfClosing)) {
|
||||
$return['status'] = true;
|
||||
|
||||
return $return;
|
||||
} else {
|
||||
$return['status'] = true;
|
||||
$return['closing'] = true;
|
||||
$return['tag'] = strtolower($tag);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
$tag = strtolower($this->content->copyByToken('slash', true));
|
||||
$node = new HtmlNode($tag);
|
||||
|
||||
// attributes
|
||||
while ($this->content->char() != '>' &&
|
||||
$this->content->char() != '/') {
|
||||
$space = $this->content->skipByToken('blank', true);
|
||||
if (empty($space)) {
|
||||
$this->content->fastForward(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $this->content->copyByToken('equal', true);
|
||||
if ($name == '/') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (empty($name)) {
|
||||
$this->content->skipByToken('blank');
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->content->skipByToken('blank');
|
||||
if ($this->content->char() == '=') {
|
||||
$attr = [];
|
||||
$this->content->fastForward(1)
|
||||
->skipByToken('blank');
|
||||
switch ($this->content->char()) {
|
||||
case '"':
|
||||
$attr['doubleQuote'] = true;
|
||||
$this->content->fastForward(1);
|
||||
$string = $this->content->copyUntil('"', true, true);
|
||||
do {
|
||||
$moreString = $this->content->copyUntilUnless('"', '=>');
|
||||
$string .= $moreString;
|
||||
} while ( ! empty($moreString));
|
||||
$attr['value'] = $string;
|
||||
$this->content->fastForward(1);
|
||||
$node->getTag()->$name = $attr;
|
||||
break;
|
||||
case "'":
|
||||
$attr['doubleQuote'] = false;
|
||||
$this->content->fastForward(1);
|
||||
$string = $this->content->copyUntil("'", true, true);
|
||||
do {
|
||||
$moreString = $this->content->copyUntilUnless("'", '=>');
|
||||
$string .= $moreString;
|
||||
} while ( ! empty($moreString));
|
||||
$attr['value'] = $string;
|
||||
$this->content->fastForward(1);
|
||||
$node->getTag()->$name = $attr;
|
||||
break;
|
||||
default:
|
||||
$attr['doubleQuote'] = true;
|
||||
$attr['value'] = $this->content->copyByToken('attr', true);
|
||||
$node->getTag()->$name = $attr;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// no value attribute
|
||||
if ($this->options->strict) {
|
||||
// can't have this in strict html
|
||||
$character = $this->content->getPosition();
|
||||
throw new StrictException("Tag '$tag' has an attribute '$name' with out a value! (character #$character)");
|
||||
}
|
||||
$node->getTag()->$name = [
|
||||
'value' => null,
|
||||
'doubleQuote' => true,
|
||||
];
|
||||
if ($this->content->char() != '>') {
|
||||
$this->content->rewind(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->content->skipByToken('blank');
|
||||
if ($this->content->char() == '/') {
|
||||
// self closing tag
|
||||
$node->getTag()->selfClosing();
|
||||
$this->content->fastForward(1);
|
||||
} elseif (in_array($tag, $this->selfClosing)) {
|
||||
|
||||
// Should be a self closing tag, check if we are strict
|
||||
if ($this->options->strict) {
|
||||
$character = $this->content->getPosition();
|
||||
throw new StrictException("Tag '$tag' is not self closing! (character #$character)");
|
||||
}
|
||||
|
||||
// We force self closing on this tag.
|
||||
$node->getTag()->selfClosing();
|
||||
|
||||
// Should this tag use a trailing slash?
|
||||
if(in_array($tag, $this->noSlash))
|
||||
{
|
||||
$node->getTag()->noTrailingSlash();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->content->fastForward(1);
|
||||
|
||||
$return['status'] = true;
|
||||
$return['node'] = $node;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to detect the charset that the html was sent in.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function detectCharset(): bool
|
||||
{
|
||||
// set the default
|
||||
$encode = new Encode;
|
||||
$encode->from($this->defaultCharset);
|
||||
$encode->to($this->defaultCharset);
|
||||
|
||||
if ( ! is_null($this->options->enforceEncoding)) {
|
||||
// they want to enforce the given encoding
|
||||
$encode->from($this->options->enforceEncoding);
|
||||
$encode->to($this->options->enforceEncoding);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$meta = $this->root->find('meta[http-equiv=Content-Type]', 0);
|
||||
if (is_null($meta)) {
|
||||
// could not find meta tag
|
||||
$this->root->propagateEncoding($encode);
|
||||
|
||||
return false;
|
||||
}
|
||||
$content = $meta->content;
|
||||
if (empty($content)) {
|
||||
// could not find content
|
||||
$this->root->propagateEncoding($encode);
|
||||
|
||||
return false;
|
||||
}
|
||||
$matches = [];
|
||||
if (preg_match('/charset=(.+)/', $content, $matches)) {
|
||||
$encode->from(trim($matches[1]));
|
||||
$this->root->propagateEncoding($encode);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// no charset found
|
||||
$this->root->propagateEncoding($encode);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+505
@@ -0,0 +1,505 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Dom;
|
||||
|
||||
use PHPHtmlParser\Exceptions\CircularException;
|
||||
use PHPHtmlParser\Exceptions\ParentNotFoundException;
|
||||
use PHPHtmlParser\Exceptions\ChildNotFoundException;
|
||||
use PHPHtmlParser\Selector\Selector;
|
||||
use PHPHtmlParser\Selector\Parser as SelectorParser;
|
||||
use stringEncode\Encode;
|
||||
use PHPHtmlParser\Finder;
|
||||
|
||||
/**
|
||||
* Dom node object.
|
||||
*
|
||||
* @property string outerhtml
|
||||
* @property string innerhtml
|
||||
* @property string text
|
||||
* @property int prev
|
||||
* @property int next
|
||||
* @property \PHPHtmlParser\Dom\Tag tag
|
||||
* @property InnerNode parent
|
||||
*/
|
||||
abstract class AbstractNode
|
||||
{
|
||||
private static $count = 0;
|
||||
/**
|
||||
* Contains the tag name/type
|
||||
*
|
||||
* @var \PHPHtmlParser\Dom\Tag
|
||||
*/
|
||||
protected $tag;
|
||||
|
||||
/**
|
||||
* Contains a list of attributes on this tag.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $attr = [];
|
||||
|
||||
/**
|
||||
* Contains the parent Node.
|
||||
*
|
||||
* @var InnerNode
|
||||
*/
|
||||
protected $parent = null;
|
||||
|
||||
/**
|
||||
* The unique id of the class. Given by PHP.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The encoding class used to encode strings.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $encode;
|
||||
|
||||
/**
|
||||
* An array of all the children.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $children = [];
|
||||
|
||||
/**
|
||||
* Creates a unique id for this node.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->id = self::$count;
|
||||
self::$count++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic get method for attributes and certain methods.
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get(string $key)
|
||||
{
|
||||
// check attribute first
|
||||
if ( ! is_null($this->getAttribute($key))) {
|
||||
return $this->getAttribute($key);
|
||||
}
|
||||
switch (strtolower($key)) {
|
||||
case 'outerhtml':
|
||||
return $this->outerHtml();
|
||||
case 'innerhtml':
|
||||
return $this->innerHtml();
|
||||
case 'text':
|
||||
return $this->text();
|
||||
case 'tag':
|
||||
return $this->getTag();
|
||||
case 'parent':
|
||||
return $this->getParent();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to clear out any object references.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->tag = null;
|
||||
$this->attr = [];
|
||||
$this->parent = null;
|
||||
$this->children = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply calls the outer text method.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->outerHtml();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset node counter
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function resetCount()
|
||||
{
|
||||
self::$count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of this object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function id(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent of node.
|
||||
*
|
||||
* @return AbstractNode
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parent node.
|
||||
*
|
||||
* @param InnerNode $parent
|
||||
* @return AbstractNode
|
||||
* @throws CircularException
|
||||
* @chainable
|
||||
*/
|
||||
public function setParent(InnerNode $parent): AbstractNode
|
||||
{
|
||||
// remove from old parent
|
||||
if ( ! is_null($this->parent)) {
|
||||
if ($this->parent->id() == $parent->id()) {
|
||||
// already the parent
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->parent->removeChild($this->id);
|
||||
}
|
||||
|
||||
$this->parent = $parent;
|
||||
|
||||
// assign child to parent
|
||||
$this->parent->addChild($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes this node and all its children from the
|
||||
* DOM tree.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
if ( ! is_null($this->parent)) {
|
||||
$this->parent->removeChild($this->id);
|
||||
}
|
||||
$this->parent->clear();
|
||||
$this->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the encoding class to this node.
|
||||
*
|
||||
* @param Encode $encode
|
||||
* @return void
|
||||
*/
|
||||
public function propagateEncoding(Encode $encode)
|
||||
{
|
||||
$this->encode = $encode;
|
||||
$this->tag->setEncoding($encode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given node id is an ancestor of
|
||||
* the current node.
|
||||
*
|
||||
* @param int $id
|
||||
* @return bool
|
||||
*/
|
||||
public function isAncestor(int $id): Bool
|
||||
{
|
||||
if ( ! is_null($this->getAncestor($id))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to get an ancestor node by the given id.
|
||||
*
|
||||
* @param int $id
|
||||
* @return null|AbstractNode
|
||||
*/
|
||||
public function getAncestor(int $id)
|
||||
{
|
||||
if ( ! is_null($this->parent)) {
|
||||
if ($this->parent->id() == $id) {
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
return $this->parent->getAncestor($id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current node has a next sibling.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasNextSibling(): bool
|
||||
{
|
||||
try
|
||||
{
|
||||
$this->nextSibling();
|
||||
|
||||
// sibling found, return true;
|
||||
return true;
|
||||
}
|
||||
catch (ParentNotFoundException $e)
|
||||
{
|
||||
// no parent, no next sibling
|
||||
return false;
|
||||
}
|
||||
catch (ChildNotFoundException $e)
|
||||
{
|
||||
// no sibling found
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to get the next sibling.
|
||||
*
|
||||
* @return AbstractNode
|
||||
* @throws ParentNotFoundException
|
||||
*/
|
||||
public function nextSibling(): AbstractNode
|
||||
{
|
||||
if (is_null($this->parent)) {
|
||||
throw new ParentNotFoundException('Parent is not set for this node.');
|
||||
}
|
||||
|
||||
return $this->parent->nextChild($this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to get the previous sibling
|
||||
*
|
||||
* @return AbstractNode
|
||||
* @throws ParentNotFoundException
|
||||
*/
|
||||
public function previousSibling(): AbstractNode
|
||||
{
|
||||
if (is_null($this->parent)) {
|
||||
throw new ParentNotFoundException('Parent is not set for this node.');
|
||||
}
|
||||
|
||||
return $this->parent->previousChild($this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tag object of this node.
|
||||
*
|
||||
* @return Tag
|
||||
*/
|
||||
public function getTag(): Tag
|
||||
{
|
||||
return $this->tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper method that simply calls the getAttribute method
|
||||
* on the tag of this node.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAttributes(): array
|
||||
{
|
||||
$attributes = $this->tag->getAttributes();
|
||||
foreach ($attributes as $name => $info) {
|
||||
$attributes[$name] = $info['value'];
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper method that simply calls the getAttribute method
|
||||
* on the tag of this node.
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAttribute(string $key)
|
||||
{
|
||||
$attribute = $this->tag->getAttribute($key);
|
||||
if ( ! is_null($attribute)) {
|
||||
$attribute = $attribute['value'];
|
||||
}
|
||||
|
||||
return $attribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper method that simply calls the hasAttribute method
|
||||
* on the tag of this node.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAttribute(string $key): bool
|
||||
{
|
||||
return $this->tag->hasAttribute($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper method that simply calls the setAttribute method
|
||||
* on the tag of this node.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string|null $value
|
||||
* @return AbstractNode
|
||||
* @chainable
|
||||
*/
|
||||
public function setAttribute(string $key, $value): AbstractNode
|
||||
{
|
||||
$this->tag->setAttribute($key, $value);
|
||||
|
||||
//clear any cache
|
||||
$this->clear();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper method that simply calls the removeAttribute method
|
||||
* on the tag of this node.
|
||||
*
|
||||
* @param string $key
|
||||
* @return void
|
||||
*/
|
||||
public function removeAttribute(string $key): void
|
||||
{
|
||||
$this->tag->removeAttribute($key);
|
||||
|
||||
//clear any cache
|
||||
$this->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper method that simply calls the removeAllAttributes
|
||||
* method on the tag of this node.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeAllAttributes(): void
|
||||
{
|
||||
$this->tag->removeAllAttributes();
|
||||
|
||||
//clear any cache
|
||||
$this->clear();
|
||||
}
|
||||
/**
|
||||
* Function to locate a specific ancestor tag in the path to the root.
|
||||
*
|
||||
* @param string $tag
|
||||
* @return AbstractNode
|
||||
* @throws ParentNotFoundException
|
||||
*/
|
||||
public function ancestorByTag(string $tag): AbstractNode
|
||||
{
|
||||
// Start by including ourselves in the comparison.
|
||||
$node = $this;
|
||||
|
||||
while ( ! is_null($node)) {
|
||||
if ($node->tag->name() == $tag) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
$node = $node->getParent();
|
||||
}
|
||||
|
||||
throw new ParentNotFoundException('Could not find an ancestor with "'.$tag.'" tag');
|
||||
}
|
||||
|
||||
/**
|
||||
* Find elements by css selector
|
||||
*
|
||||
* @param string $selector
|
||||
* @param int $nth
|
||||
* @return mixed
|
||||
*/
|
||||
public function find(string $selector, int $nth = null)
|
||||
{
|
||||
$selector = new Selector($selector, new SelectorParser());
|
||||
$nodes = $selector->find($this);
|
||||
|
||||
if ( ! is_null($nth)) {
|
||||
// return nth-element or array
|
||||
if (isset($nodes[$nth])) {
|
||||
return $nodes[$nth];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find node by id
|
||||
*
|
||||
* @param int $id
|
||||
* @return bool|AbstractNode
|
||||
*/
|
||||
public function findById(int $id)
|
||||
{
|
||||
$finder= new Finder($id);
|
||||
|
||||
return $finder->find($this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the inner html of this node.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function innerHtml(): string;
|
||||
|
||||
/**
|
||||
* Gets the html of this node, including it's own
|
||||
* tag.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function outerHtml(): string;
|
||||
|
||||
/**
|
||||
* Gets the text of this node (if there is any text).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function text(): string;
|
||||
|
||||
/**
|
||||
* Call this when something in the node tree has changed. Like a child has been added
|
||||
* or a parent has been changed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function clear(): void;
|
||||
|
||||
/**
|
||||
* Check is node type textNode
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isTextNode(): bool
|
||||
{
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Dom;
|
||||
|
||||
use Countable;
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
|
||||
/**
|
||||
* Dom node object which will allow users to use it as
|
||||
* an array.
|
||||
*/
|
||||
abstract class ArrayNode extends AbstractNode implements IteratorAggregate, Countable
|
||||
{
|
||||
|
||||
/**
|
||||
* Gets the iterator
|
||||
*
|
||||
* @return ArrayIterator
|
||||
*/
|
||||
public function getIterator(): ArrayIterator
|
||||
{
|
||||
return new ArrayIterator($this->getIteratorArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the count of the iterator array.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->getIteratorArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array to be used the the iterator.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function getIteratorArray(): array;
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Dom;
|
||||
|
||||
use ArrayAccess;
|
||||
use ArrayIterator;
|
||||
use Countable;
|
||||
use IteratorAggregate;
|
||||
use PHPHtmlParser\Exceptions\EmptyCollectionException;
|
||||
|
||||
/**
|
||||
* Class Collection
|
||||
*
|
||||
* @package PHPHtmlParser\Dom
|
||||
*/
|
||||
class Collection implements IteratorAggregate, ArrayAccess, Countable
|
||||
{
|
||||
|
||||
/**
|
||||
* The collection of Nodes.
|
||||
*
|
||||
* @param array
|
||||
*/
|
||||
protected $collection = [];
|
||||
|
||||
/**
|
||||
* Attempts to call the method on the first node in
|
||||
* the collection.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
* @throws EmptyCollectionException
|
||||
*/
|
||||
public function __call(string $method, array $arguments)
|
||||
{
|
||||
$node = reset($this->collection);
|
||||
if ($node instanceof AbstractNode) {
|
||||
return call_user_func_array([$node, $method], $arguments);
|
||||
} else {
|
||||
throw new EmptyCollectionException('The collection does not contain any Nodes.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to apply the magic get to the first node
|
||||
* in the collection.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @return mixed
|
||||
* @throws EmptyCollectionException
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
$node = reset($this->collection);
|
||||
if ($node instanceof AbstractNode) {
|
||||
return $node->$key;
|
||||
} else {
|
||||
throw new EmptyCollectionException('The collection does not contain any Nodes.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the magic string method to the first node in
|
||||
* the collection.
|
||||
*
|
||||
* @return string
|
||||
* @throws EmptyCollectionException
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
$node = reset($this->collection);
|
||||
if ($node instanceof AbstractNode) {
|
||||
return (string)$node;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the count of the collection.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator for the collection.
|
||||
*
|
||||
* @return ArrayIterator
|
||||
*/
|
||||
public function getIterator(): ArrayIterator
|
||||
{
|
||||
return new ArrayIterator($this->collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an attribute by the given offset
|
||||
*
|
||||
* @param mixed $offset
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function offsetSet($offset, $value): void
|
||||
{
|
||||
if (is_null($offset)) {
|
||||
$this->collection[] = $value;
|
||||
} else {
|
||||
$this->collection[$offset] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an offset exists.
|
||||
*
|
||||
* @param mixed $offset
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset): bool
|
||||
{
|
||||
return isset($this->collection[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset a collection Node.
|
||||
*
|
||||
* @param mixed $offset
|
||||
*/
|
||||
public function offsetUnset($offset): void
|
||||
{
|
||||
unset($this->collection[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node at the given offset, or null
|
||||
*
|
||||
* @param mixed $offset
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return isset($this->collection[$offset]) ? $this->collection[$offset] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this collection as an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to jQuery "each" method. Calls the callback with each
|
||||
* Node in this collection.
|
||||
*
|
||||
* @param callable $callback
|
||||
*/
|
||||
public function each(callable $callback)
|
||||
{
|
||||
foreach ($this->collection as $key => $value) {
|
||||
$callback($value, $key);
|
||||
}
|
||||
}
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Dom;
|
||||
|
||||
use PHPHtmlParser\Exceptions\UnknownChildTypeException;
|
||||
use PHPHtmlParser\Exceptions\ChildNotFoundException;
|
||||
|
||||
/**
|
||||
* Class HtmlNode
|
||||
*
|
||||
* @package PHPHtmlParser\Dom
|
||||
*/
|
||||
class HtmlNode extends InnerNode
|
||||
{
|
||||
|
||||
/**
|
||||
* Remembers what the innerHtml was if it was scanned previously.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $innerHtml = null;
|
||||
|
||||
/**
|
||||
* Remembers what the outerHtml was if it was scanned previously.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $outerHtml = null;
|
||||
|
||||
/**
|
||||
* Remembers what the text was if it was scanned previously.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $text = null;
|
||||
|
||||
/**
|
||||
* Remembers what the text was when we looked into all our
|
||||
* children nodes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $textWithChildren = null;
|
||||
|
||||
/**
|
||||
* Sets up the tag of this node.
|
||||
*
|
||||
* @param string|Tag $tag
|
||||
*/
|
||||
public function __construct($tag)
|
||||
{
|
||||
if ( ! $tag instanceof Tag) {
|
||||
$tag = new Tag($tag);
|
||||
}
|
||||
$this->tag = $tag;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the inner html of this node.
|
||||
*
|
||||
* @return string
|
||||
* @throws UnknownChildTypeException
|
||||
*/
|
||||
public function innerHtml(): string
|
||||
{
|
||||
if ( ! $this->hasChildren()) {
|
||||
// no children
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( ! is_null($this->innerHtml)) {
|
||||
// we already know the result.
|
||||
return $this->innerHtml;
|
||||
}
|
||||
|
||||
$child = $this->firstChild();
|
||||
$string = '';
|
||||
|
||||
// continue to loop until we are out of children
|
||||
while ( ! is_null($child)) {
|
||||
if ($child instanceof TextNode) {
|
||||
$string .= $child->text();
|
||||
} elseif ($child instanceof HtmlNode) {
|
||||
$string .= $child->outerHtml();
|
||||
} else {
|
||||
throw new UnknownChildTypeException('Unknown child type "'.get_class($child).'" found in node');
|
||||
}
|
||||
|
||||
try {
|
||||
$child = $this->nextChild($child->id());
|
||||
} catch (ChildNotFoundException $e) {
|
||||
// no more children
|
||||
$child = null;
|
||||
}
|
||||
}
|
||||
|
||||
// remember the results
|
||||
$this->innerHtml = $string;
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the html of this node, including it's own
|
||||
* tag.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function outerHtml(): string
|
||||
{
|
||||
// special handling for root
|
||||
if ($this->tag->name() == 'root') {
|
||||
return $this->innerHtml();
|
||||
}
|
||||
|
||||
if ( ! is_null($this->outerHtml)) {
|
||||
// we already know the results.
|
||||
return $this->outerHtml;
|
||||
}
|
||||
|
||||
$return = $this->tag->makeOpeningTag();
|
||||
if ($this->tag->isSelfClosing()) {
|
||||
// ignore any children... there should not be any though
|
||||
return $return;
|
||||
}
|
||||
|
||||
// get the inner html
|
||||
$return .= $this->innerHtml();
|
||||
|
||||
// add closing tag
|
||||
$return .= $this->tag->makeClosingTag();
|
||||
|
||||
// remember the results
|
||||
$this->outerHtml = $return;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the text of this node (if there is any text). Or get all the text
|
||||
* in this node, including children.
|
||||
*
|
||||
* @param bool $lookInChildren
|
||||
* @return string
|
||||
*/
|
||||
public function text(bool $lookInChildren = false): string
|
||||
{
|
||||
if ($lookInChildren) {
|
||||
if ( ! is_null($this->textWithChildren)) {
|
||||
// we already know the results.
|
||||
return $this->textWithChildren;
|
||||
}
|
||||
} elseif ( ! is_null($this->text)) {
|
||||
// we already know the results.
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
// find out if this node has any text children
|
||||
$text = '';
|
||||
foreach ($this->children as $child) {
|
||||
/** @var AbstractNode $node */
|
||||
$node = $child['node'];
|
||||
if ($node instanceof TextNode) {
|
||||
$text .= $child['node']->text;
|
||||
} elseif ($lookInChildren &&
|
||||
$node instanceof HtmlNode
|
||||
) {
|
||||
$text .= $node->text($lookInChildren);
|
||||
}
|
||||
}
|
||||
|
||||
// remember our result
|
||||
if ($lookInChildren) {
|
||||
$this->textWithChildren = $text;
|
||||
} else {
|
||||
$this->text = $text;
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this when something in the node tree has changed. Like a child has been added
|
||||
* or a parent has been changed.
|
||||
*/
|
||||
protected function clear(): void
|
||||
{
|
||||
$this->innerHtml = null;
|
||||
$this->outerHtml = null;
|
||||
$this->text = null;
|
||||
$this->textWithChildren = null;
|
||||
|
||||
if (is_null($this->parent) === false) {
|
||||
$this->parent->clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all children of this html node.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getIteratorArray(): array
|
||||
{
|
||||
return $this->getChildren();
|
||||
}
|
||||
}
|
||||
+442
@@ -0,0 +1,442 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Dom;
|
||||
|
||||
use PHPHtmlParser\Exceptions\ChildNotFoundException;
|
||||
use PHPHtmlParser\Exceptions\CircularException;
|
||||
use stringEncode\Encode;
|
||||
|
||||
/**
|
||||
* Inner node of the html tree, might have children.
|
||||
*
|
||||
* @package PHPHtmlParser\Dom
|
||||
*/
|
||||
abstract class InnerNode extends ArrayNode
|
||||
{
|
||||
|
||||
/**
|
||||
* An array of all the children.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $children = [];
|
||||
|
||||
/**
|
||||
* Sets the encoding class to this node and propagates it
|
||||
* to all its children.
|
||||
*
|
||||
* @param Encode $encode
|
||||
* @return void
|
||||
*/
|
||||
public function propagateEncoding(Encode $encode): void
|
||||
{
|
||||
$this->encode = $encode;
|
||||
$this->tag->setEncoding($encode);
|
||||
// check children
|
||||
foreach ($this->children as $id => $child) {
|
||||
/** @var AbstractNode $node */
|
||||
$node = $child['node'];
|
||||
$node->propagateEncoding($encode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this node has children.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasChildren(): bool
|
||||
{
|
||||
return ! empty($this->children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the child by id.
|
||||
*
|
||||
* @param int $id
|
||||
* @return AbstractNode
|
||||
* @throws ChildNotFoundException
|
||||
*/
|
||||
public function getChild(int $id): AbstractNode
|
||||
{
|
||||
if ( ! isset($this->children[$id])) {
|
||||
throw new ChildNotFoundException("Child '$id' not found in this node.");
|
||||
}
|
||||
|
||||
return $this->children[$id]['node'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new array of child nodes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChildren(): array
|
||||
{
|
||||
$nodes = [];
|
||||
try {
|
||||
$child = $this->firstChild();
|
||||
do {
|
||||
$nodes[] = $child;
|
||||
$child = $this->nextChild($child->id());
|
||||
} while ( ! is_null($child));
|
||||
} catch (ChildNotFoundException $e) {
|
||||
// we are done looking for children
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts children
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function countChildren(): int
|
||||
{
|
||||
return count($this->children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a child node to this node and returns the id of the child for this
|
||||
* parent.
|
||||
*
|
||||
* @param AbstractNode $child
|
||||
* @param Int $before
|
||||
* @return bool
|
||||
* @throws CircularException
|
||||
*/
|
||||
public function addChild(AbstractNode $child, int $before = -1): bool
|
||||
{
|
||||
$key = null;
|
||||
|
||||
// check integrity
|
||||
if ($this->isAncestor($child->id())) {
|
||||
throw new CircularException('Can not add child. It is my ancestor.');
|
||||
}
|
||||
|
||||
// check if child is itself
|
||||
if ($child->id() == $this->id) {
|
||||
throw new CircularException('Can not set itself as a child.');
|
||||
}
|
||||
|
||||
$next = null;
|
||||
|
||||
if ($this->hasChildren()) {
|
||||
if (isset($this->children[$child->id()])) {
|
||||
// we already have this child
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($before >= 0) {
|
||||
if (!isset($this->children[$before])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$key = $this->children[$before]['prev'];
|
||||
|
||||
if($key){
|
||||
$this->children[$key]['next'] = $child->id();
|
||||
}
|
||||
|
||||
$this->children[$before]['prev'] = $child->id();
|
||||
$next = $before;
|
||||
} else {
|
||||
$sibling = $this->lastChild();
|
||||
$key = $sibling->id();
|
||||
|
||||
$this->children[$key]['next'] = $child->id();
|
||||
}
|
||||
}
|
||||
|
||||
$keys = array_keys($this->children);
|
||||
|
||||
$insert = [
|
||||
'node' => $child,
|
||||
'next' => $next,
|
||||
'prev' => $key,
|
||||
];
|
||||
|
||||
$index = $key ? (array_search($key, $keys, true) + 1) : 0;
|
||||
array_splice($keys, $index, 0, $child->id());
|
||||
|
||||
$children = array_values($this->children);
|
||||
array_splice($children, $index, 0, [$insert]);
|
||||
|
||||
// add the child
|
||||
$this->children = array_combine($keys, $children);
|
||||
|
||||
// tell child I am the new parent
|
||||
$child->setParent($this);
|
||||
|
||||
//clear any cache
|
||||
$this->clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert element before child with provided id
|
||||
*
|
||||
* @param AbstractNode $child
|
||||
* @param int $id
|
||||
* @return bool
|
||||
*/
|
||||
public function insertBefore(AbstractNode $child, int $id): bool
|
||||
{
|
||||
return $this->addChild($child, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert element before after with provided id
|
||||
*
|
||||
* @param AbstractNode $child
|
||||
* @param int $id
|
||||
* @return bool
|
||||
*/
|
||||
public function insertAfter(AbstractNode $child, int $id): bool
|
||||
{
|
||||
if (!isset($this->children[$id])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->children[$id]['next']) {
|
||||
return $this->addChild($child, $this->children[$id]['next']);
|
||||
}
|
||||
|
||||
// clear cache
|
||||
$this->clear();
|
||||
|
||||
return $this->addChild($child);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the child by id.
|
||||
*
|
||||
* @param int $id
|
||||
* @return InnerNode
|
||||
* @chainable
|
||||
*/
|
||||
public function removeChild(int $id): InnerNode
|
||||
{
|
||||
if ( ! isset($this->children[$id])) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
// handle moving next and previous assignments.
|
||||
$next = $this->children[$id]['next'];
|
||||
$prev = $this->children[$id]['prev'];
|
||||
if ( ! is_null($next)) {
|
||||
$this->children[$next]['prev'] = $prev;
|
||||
}
|
||||
if ( ! is_null($prev)) {
|
||||
$this->children[$prev]['next'] = $next;
|
||||
}
|
||||
|
||||
// remove the child
|
||||
unset($this->children[$id]);
|
||||
|
||||
//clear any cache
|
||||
$this->clear();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if has next Child
|
||||
*
|
||||
* @param int $id
|
||||
* @return mixed
|
||||
*/
|
||||
public function hasNextChild(int $id)
|
||||
{
|
||||
$child= $this->getChild($id);
|
||||
return $this->children[$child->id()]['next'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to get the next child.
|
||||
*
|
||||
* @param int $id
|
||||
* @return AbstractNode
|
||||
* @uses $this->getChild()
|
||||
* @throws ChildNotFoundException
|
||||
*/
|
||||
public function nextChild(int $id): AbstractNode
|
||||
{
|
||||
$child = $this->getChild($id);
|
||||
$next = $this->children[$child->id()]['next'];
|
||||
if (is_null($next)) {
|
||||
throw new ChildNotFoundException("Child '$id' next not found in this node.");
|
||||
}
|
||||
|
||||
return $this->getChild($next);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to get the previous child.
|
||||
*
|
||||
* @param int $id
|
||||
* @return AbstractNode
|
||||
* @uses $this->getChild()
|
||||
* @throws ChildNotFoundException
|
||||
*/
|
||||
public function previousChild(int $id): AbstractNode
|
||||
{
|
||||
$child = $this->getchild($id);
|
||||
$next = $this->children[$child->id()]['prev'];
|
||||
if (is_null($next)) {
|
||||
throw new ChildNotFoundException("Child '$id' previous not found in this node.");
|
||||
}
|
||||
|
||||
return $this->getChild($next);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given node id is a child of the
|
||||
* current node.
|
||||
*
|
||||
* @param int $id
|
||||
* @return bool
|
||||
*/
|
||||
public function isChild(int $id): bool
|
||||
{
|
||||
foreach ($this->children as $childId => $child) {
|
||||
if ($id == $childId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the child with id $childId and replace it with the new child
|
||||
* $newChild.
|
||||
*
|
||||
* @param int $childId
|
||||
* @param AbstractNode $newChild
|
||||
* @throws ChildNotFoundException
|
||||
* @return void
|
||||
*/
|
||||
public function replaceChild(int $childId, AbstractNode $newChild): void
|
||||
{
|
||||
$oldChild = $this->children[$childId];
|
||||
|
||||
$newChild->prev = $oldChild['prev'];
|
||||
$newChild->next = $oldChild['next'];
|
||||
|
||||
$keys = array_keys($this->children);
|
||||
$index = array_search($childId, $keys, true);
|
||||
$keys[$index] = $newChild->id();
|
||||
$this->children = array_combine($keys, $this->children);
|
||||
$this->children[$newChild->id()] = array(
|
||||
'prev' => $oldChild['prev'],
|
||||
'node' => $newChild,
|
||||
'next' => $oldChild['next']
|
||||
);
|
||||
|
||||
// chnge previous child id to new child
|
||||
if ($oldChild['prev'] && isset($this->children[$newChild->prev])) {
|
||||
$this->children[$oldChild['prev']]['next'] = $newChild->id();
|
||||
}
|
||||
|
||||
// change next child id to new child
|
||||
if ($oldChild['next'] && isset($this->children[$newChild->next])) {
|
||||
$this->children[$oldChild['next']]['prev'] = $newChild->id();
|
||||
}
|
||||
|
||||
// remove old child
|
||||
unset($this->children[$childId]);
|
||||
|
||||
// clean out cache
|
||||
$this->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to return the first child.
|
||||
*
|
||||
* @return AbstractNode
|
||||
* @uses $this->getChild()
|
||||
* @throws ChildNotFoundException
|
||||
*/
|
||||
public function firstChild(): AbstractNode
|
||||
{
|
||||
if (count($this->children) == 0) {
|
||||
// no children
|
||||
throw new ChildNotFoundException("No children found in node.");
|
||||
}
|
||||
|
||||
reset($this->children);
|
||||
$key = (int) key($this->children);
|
||||
|
||||
return $this->getChild($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to get the last child.
|
||||
*
|
||||
* @return AbstractNode
|
||||
* @uses $this->getChild()
|
||||
* @throws ChildNotFoundException
|
||||
*/
|
||||
public function lastChild(): AbstractNode
|
||||
{
|
||||
if (count($this->children) == 0) {
|
||||
// no children
|
||||
throw new ChildNotFoundException("No children found in node.");
|
||||
}
|
||||
|
||||
end($this->children);
|
||||
$key = key($this->children);
|
||||
|
||||
return $this->getChild($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given node id is a descendant of the
|
||||
* current node.
|
||||
*
|
||||
* @param int $id
|
||||
* @return bool
|
||||
*/
|
||||
public function isDescendant(int $id): bool
|
||||
{
|
||||
if ($this->isChild($id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($this->children as $childId => $child) {
|
||||
/** @var InnerNode $node */
|
||||
$node = $child['node'];
|
||||
if ($node instanceof InnerNode &&
|
||||
$node->hasChildren() &&
|
||||
$node->isDescendant($id)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parent node.
|
||||
*
|
||||
* @param InnerNode $parent
|
||||
* @return AbstractNode
|
||||
* @throws CircularException
|
||||
* @chainable
|
||||
*/
|
||||
public function setParent(InnerNode $parent): AbstractNode
|
||||
{
|
||||
// check integrity
|
||||
if ($this->isDescendant($parent->id())) {
|
||||
throw new CircularException('Can not add descendant "'.$parent->id().'" as my parent.');
|
||||
}
|
||||
|
||||
// clear cache
|
||||
$this->clear();
|
||||
|
||||
return parent::setParent($parent);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Dom;
|
||||
|
||||
|
||||
/**
|
||||
* Class LeafNode
|
||||
*
|
||||
* @package PHPHtmlParser
|
||||
*/
|
||||
abstract class LeafNode extends AbstractNode
|
||||
{
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Dom;
|
||||
|
||||
/**
|
||||
* This mock object is used solely for testing the abstract
|
||||
* class Node with out any potential side effects caused
|
||||
* by testing a supper class of Node.
|
||||
*
|
||||
* This object is not to be used for any other reason.
|
||||
*/
|
||||
class MockNode extends InnerNode
|
||||
{
|
||||
|
||||
/**
|
||||
* Mock of innner html.
|
||||
*/
|
||||
public function innerHtml(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock of outer html.
|
||||
*/
|
||||
public function outerHtml(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock of text.
|
||||
*/
|
||||
public function text(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear content of this node
|
||||
*/
|
||||
protected function clear(): void
|
||||
{
|
||||
$this->innerHtml = null;
|
||||
$this->outerHtml = null;
|
||||
$this->text = null;
|
||||
if (is_null($this->parent) === false) {
|
||||
$this->parent->clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all children of this html node.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getIteratorArray(): array
|
||||
{
|
||||
return $this->getChildren();
|
||||
}
|
||||
}
|
||||
+351
@@ -0,0 +1,351 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Dom;
|
||||
|
||||
use PHPHtmlParser\Dom;
|
||||
use stringEncode\Encode;
|
||||
|
||||
/**
|
||||
* Class Tag
|
||||
*
|
||||
* @package PHPHtmlParser\Dom
|
||||
*/
|
||||
class Tag
|
||||
{
|
||||
|
||||
/**
|
||||
* The name of the tag.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* The attributes of the tag.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $attr = [];
|
||||
|
||||
/**
|
||||
* Is this tag self closing.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $selfClosing = false;
|
||||
|
||||
/**
|
||||
* If self-closing, will this use a trailing slash. />
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $trailingSlash = true;
|
||||
|
||||
/**
|
||||
* Tag noise
|
||||
*/
|
||||
protected $noise = '';
|
||||
|
||||
/**
|
||||
* The encoding class to... encode the tags
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $encode = null;
|
||||
|
||||
/**
|
||||
* Sets up the tag with a name.
|
||||
*
|
||||
* @param $name
|
||||
*/
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to get any of the attributes.
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
return $this->getAttribute($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to set any attribute.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __set($key, $value)
|
||||
{
|
||||
$this->setAttribute($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this tag.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tag to be self closing.
|
||||
*
|
||||
* @return Tag
|
||||
* @chainable
|
||||
*/
|
||||
public function selfClosing(): Tag
|
||||
{
|
||||
$this->selfClosing = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the tag to not use a trailing slash.
|
||||
*
|
||||
* @return Tag
|
||||
* @chainable
|
||||
*/
|
||||
public function noTrailingSlash(): Tag
|
||||
{
|
||||
$this->trailingSlash = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the tag is self closing.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSelfClosing(): bool
|
||||
{
|
||||
return $this->selfClosing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the encoding type to be used.
|
||||
*
|
||||
* @param Encode $encode
|
||||
* @return void
|
||||
*/
|
||||
public function setEncoding(Encode $encode): void
|
||||
{
|
||||
$this->encode = $encode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the noise for this tag (if any)
|
||||
*
|
||||
* @param string $noise
|
||||
* @return Tag
|
||||
* @chainable
|
||||
*/
|
||||
public function noise(string $noise): Tag
|
||||
{
|
||||
$this->noise = $noise;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an attribute for this tag.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string|array $value
|
||||
* @return Tag
|
||||
* @chainable
|
||||
*/
|
||||
public function setAttribute(string $key, $value): Tag
|
||||
{
|
||||
$key = strtolower($key);
|
||||
if ( ! is_array($value)) {
|
||||
$value = [
|
||||
'value' => $value,
|
||||
'doubleQuote' => true,
|
||||
];
|
||||
}
|
||||
$this->attr[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set inline style attribute value.
|
||||
*
|
||||
* @param mixed $attr_key
|
||||
* @param mixed $attr_value
|
||||
*/
|
||||
public function setStyleAttributeValue($attr_key, $attr_value): void
|
||||
{
|
||||
|
||||
$style_array = $this->getStyleAttributeArray();
|
||||
$style_array[$attr_key] = $attr_value;
|
||||
|
||||
$style_string = '';
|
||||
foreach ($style_array as $key => $value) {
|
||||
$style_string .= $key . ':' . $value . ';';
|
||||
}
|
||||
|
||||
$this->setAttribute('style', $style_string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get style attribute in array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getStyleAttributeArray(): array
|
||||
{
|
||||
$value = $this->getAttribute('style')['value'];
|
||||
|
||||
if ($value === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$value = explode(';', substr(trim($value), 0, -1));
|
||||
$result = [];
|
||||
foreach ($value as $attr) {
|
||||
$attr = explode(':', $attr);
|
||||
$result[$attr[0]] = $attr[1];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Removes an attribute from this tag.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @return void
|
||||
*/
|
||||
public function removeAttribute($key)
|
||||
{
|
||||
$key = strtolower($key);
|
||||
unset($this->attr[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all attributes on this tag.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeAllAttributes()
|
||||
{
|
||||
$this->attr = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the attributes for this tag
|
||||
*
|
||||
* @param array $attr
|
||||
* @return $this
|
||||
*/
|
||||
public function setAttributes(array $attr)
|
||||
{
|
||||
foreach ($attr as $key => $value) {
|
||||
$this->setAttribute($key, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all attributes of this tag.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
$return = [];
|
||||
foreach ($this->attr as $attr => $info) {
|
||||
$return[$attr] = $this->getAttribute($attr);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an attribute by the key
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAttribute(string $key)
|
||||
{
|
||||
if ( ! isset($this->attr[$key])) {
|
||||
return null;
|
||||
}
|
||||
$value = $this->attr[$key]['value'];
|
||||
if (is_string($value) && ! is_null($this->encode)) {
|
||||
// convert charset
|
||||
$this->attr[$key]['value'] = $this->encode->convert($value);
|
||||
}
|
||||
|
||||
return $this->attr[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if node has attribute
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAttribute(string $key)
|
||||
{
|
||||
return isset($this->attr[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the opening tag for this object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function makeOpeningTag()
|
||||
{
|
||||
$return = '<'.$this->name;
|
||||
|
||||
// add the attributes
|
||||
foreach ($this->attr as $key => $info) {
|
||||
$info = $this->getAttribute($key);
|
||||
$val = $info['value'];
|
||||
if (is_null($val)) {
|
||||
$return .= ' '.$key;
|
||||
} elseif ($info['doubleQuote']) {
|
||||
$return .= ' '.$key.'="'.$val.'"';
|
||||
} else {
|
||||
$return .= ' '.$key.'=\''.$val.'\'';
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->selfClosing && $this->trailingSlash) {
|
||||
return $return.' />';
|
||||
} else {
|
||||
return $return.'>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the closing tag for this object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function makeClosingTag()
|
||||
{
|
||||
if ($this->selfClosing) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '</'.$this->name.'>';
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Dom;
|
||||
|
||||
/**
|
||||
* Class TextNode
|
||||
*
|
||||
* @package PHPHtmlParser\Dom
|
||||
*/
|
||||
class TextNode extends LeafNode
|
||||
{
|
||||
|
||||
/**
|
||||
* This is a text node.
|
||||
*
|
||||
* @var Tag
|
||||
*/
|
||||
protected $tag;
|
||||
|
||||
/**
|
||||
* This is the text in this node.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $text;
|
||||
|
||||
/**
|
||||
* This is the converted version of the text.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $convertedText = null;
|
||||
|
||||
/**
|
||||
* Sets the text for this node.
|
||||
*
|
||||
* @param string $text
|
||||
* @param bool $removeDoubleSpace
|
||||
*/
|
||||
public function __construct(string $text, $removeDoubleSpace = true)
|
||||
{
|
||||
if ($removeDoubleSpace) {
|
||||
// remove double spaces
|
||||
$text = mb_ereg_replace('\s+', ' ', $text);
|
||||
}
|
||||
|
||||
// restore line breaks
|
||||
$text = str_replace(' ', "\n", $text);
|
||||
|
||||
$this->text = $text;
|
||||
$this->tag = new Tag('text');
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text of this node.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function text(): string
|
||||
{
|
||||
// convert charset
|
||||
if ( ! is_null($this->encode)) {
|
||||
if ( ! is_null($this->convertedText)) {
|
||||
// we already know the converted value
|
||||
return $this->convertedText;
|
||||
}
|
||||
$text = $this->encode->convert($this->text);
|
||||
|
||||
// remember the conversion
|
||||
$this->convertedText = $text;
|
||||
|
||||
return $text;
|
||||
} else {
|
||||
return $this->text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text for this node.
|
||||
*
|
||||
* @var string $text
|
||||
* @return void
|
||||
*/
|
||||
public function setText(string $text): void
|
||||
{
|
||||
$this->text = $text;
|
||||
|
||||
if ( ! is_null($this->encode)) {
|
||||
$text = $this->encode->convert($text);
|
||||
|
||||
// remember the conversion
|
||||
$this->convertedText = $text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This node has no html, just return the text.
|
||||
*
|
||||
* @return string
|
||||
* @uses $this->text()
|
||||
*/
|
||||
public function innerHtml(): string
|
||||
{
|
||||
return $this->text();
|
||||
}
|
||||
|
||||
/**
|
||||
* This node has no html, just return the text.
|
||||
*
|
||||
* @return string
|
||||
* @uses $this->text()
|
||||
*/
|
||||
public function outerHtml(): string
|
||||
{
|
||||
return $this->text();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this when something in the node tree has changed. Like a child has been added
|
||||
* or a parent has been changed.
|
||||
*/
|
||||
protected function clear(): void
|
||||
{
|
||||
$this->convertedText = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current node is a text node.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isTextNode(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Exceptions;
|
||||
|
||||
/**
|
||||
* Class ChildNotFoundException
|
||||
*
|
||||
* @package PHPHtmlParser\Exceptions
|
||||
*/
|
||||
final class ChildNotFoundException extends \Exception
|
||||
{
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Exceptions;
|
||||
|
||||
/**
|
||||
* Class CircularException
|
||||
*
|
||||
* @package PHPHtmlParser\Exceptions
|
||||
*/
|
||||
final class CircularException extends \Exception
|
||||
{
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Exceptions;
|
||||
|
||||
/**
|
||||
* Class CurlException
|
||||
*
|
||||
* @package PHPHtmlParser\Exceptions
|
||||
*/
|
||||
class CurlException extends \Exception
|
||||
{
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Exceptions;
|
||||
|
||||
/**
|
||||
* Class EmptyCollectionException
|
||||
*
|
||||
* @package PHPHtmlParser\Exceptions
|
||||
*/
|
||||
final class EmptyCollectionException extends \Exception
|
||||
{
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Exceptions;
|
||||
|
||||
/**
|
||||
* Class NotLoadedException
|
||||
*
|
||||
* @package PHPHtmlParser\Exceptions
|
||||
*/
|
||||
final class NotLoadedException extends \Exception
|
||||
{
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Exceptions;
|
||||
|
||||
/**
|
||||
* Class ParentNotFoundException
|
||||
*
|
||||
* @package PHPHtmlParser\Exceptions
|
||||
*/
|
||||
final class ParentNotFoundException extends \Exception
|
||||
{
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Exceptions;
|
||||
|
||||
/**
|
||||
* Class StrictException
|
||||
*
|
||||
* @package PHPHtmlParser\Exceptions
|
||||
*/
|
||||
final class StrictException extends \Exception
|
||||
{
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Exceptions;
|
||||
|
||||
/**
|
||||
* Class UnknownChildTypeException
|
||||
*
|
||||
* @package PHPHtmlParser\Exceptions
|
||||
*/
|
||||
final class UnknownChildTypeException extends \Exception
|
||||
{
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace PHPHtmlParser;
|
||||
|
||||
use PHPHtmlParser\Dom\AbstractNode;
|
||||
use PHPHtmlParser\Dom\InnerNode;
|
||||
|
||||
class Finder
|
||||
{
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* Finder constructor.
|
||||
* @param $id
|
||||
*/
|
||||
public function __construct($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Find node in tree by id
|
||||
*
|
||||
* @param AbstractNode $node
|
||||
* @return bool|AbstractNode
|
||||
*/
|
||||
public function find(AbstractNode $node)
|
||||
{
|
||||
if (!$node->id() && $node instanceof InnerNode) {
|
||||
return $this->find($node->firstChild());
|
||||
}
|
||||
|
||||
if ($node->id() == $this->id) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
if ($node->hasNextSibling()) {
|
||||
$nextSibling = $node->nextSibling();
|
||||
if ($nextSibling->id() == $this->id) {
|
||||
return $nextSibling;
|
||||
}
|
||||
if ($nextSibling->id() > $this->id) {
|
||||
return $this->find($node->firstChild());
|
||||
}
|
||||
if ($nextSibling->id() < $this->id) {
|
||||
return $this->find($nextSibling);
|
||||
}
|
||||
} else if (!$node->isTextNode()) {
|
||||
return $this->find($node->firstChild());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser;
|
||||
|
||||
/**
|
||||
* Class Options
|
||||
*
|
||||
* @package PHPHtmlParser
|
||||
* @property bool whitespaceTextNode
|
||||
* @property bool strict
|
||||
* @property string|null enforceEncoding
|
||||
* @property bool cleanupInput
|
||||
* @property bool removeScripts
|
||||
* @property bool removeStyles
|
||||
* @property bool preserveLineBreaks
|
||||
* @property bool removeDoubleSpace
|
||||
*/
|
||||
class Options
|
||||
{
|
||||
|
||||
/**
|
||||
* The default options array
|
||||
*
|
||||
* @param array
|
||||
*/
|
||||
protected $defaults = [
|
||||
'whitespaceTextNode' => true,
|
||||
'strict' => false,
|
||||
'enforceEncoding' => null,
|
||||
'cleanupInput' => true,
|
||||
'removeScripts' => true,
|
||||
'removeStyles' => true,
|
||||
'preserveLineBreaks' => false,
|
||||
'removeDoubleSpace' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* The list of all current options set.
|
||||
*
|
||||
* @param array
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* Sets the default options in the options array
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->options = $this->defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* A magic get to call the get() method.
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
* @uses $this->get()
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
return $this->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new options param to override the current option array.
|
||||
*
|
||||
* @param array $options
|
||||
* @return Options
|
||||
* @chainable
|
||||
*/
|
||||
public function setOptions(array $options): Options
|
||||
{
|
||||
foreach ($options as $key => $option) {
|
||||
$this->options[$key] = $option;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value associated to the key, or null if the key is not
|
||||
* found.
|
||||
*
|
||||
* @param string
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $key)
|
||||
{
|
||||
if (isset($this->options[$key])) {
|
||||
return $this->options[$key];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Selector;
|
||||
|
||||
/**
|
||||
* This is the parser for the selctor.
|
||||
*
|
||||
*
|
||||
*/
|
||||
class Parser implements ParserInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Pattern of CSS selectors, modified from 'mootools'
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pattern = "/([\w\-:\*>]*)(?:\#([\w\-]+)|\.([\w\-]+))?(?:\[@?(!?[\w\-:]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is";
|
||||
|
||||
/**
|
||||
* Parses the selector string
|
||||
*
|
||||
* @param string $selector
|
||||
*/
|
||||
public function parseSelectorString(string $selector): array
|
||||
{
|
||||
$selectors = [];
|
||||
|
||||
$matches = [];
|
||||
preg_match_all($this->pattern, trim($selector).' ', $matches, PREG_SET_ORDER);
|
||||
|
||||
// skip tbody
|
||||
$result = [];
|
||||
foreach ($matches as $match) {
|
||||
// default values
|
||||
$tag = strtolower(trim($match[1]));
|
||||
$operator = '=';
|
||||
$key = null;
|
||||
$value = null;
|
||||
$noKey = false;
|
||||
$alterNext = false;
|
||||
|
||||
// check for elements that alter the behavior of the next element
|
||||
if ($tag == '>') {
|
||||
$alterNext = true;
|
||||
}
|
||||
|
||||
// check for id selector
|
||||
if ( ! empty($match[2])) {
|
||||
$key = 'id';
|
||||
$value = $match[2];
|
||||
}
|
||||
|
||||
// check for class selector
|
||||
if ( ! empty($match[3])) {
|
||||
$key = 'class';
|
||||
$value = $match[3];
|
||||
}
|
||||
|
||||
// and final attribute selector
|
||||
if ( ! empty($match[4])) {
|
||||
$key = strtolower($match[4]);
|
||||
}
|
||||
if ( ! empty($match[5])) {
|
||||
$operator = $match[5];
|
||||
}
|
||||
if ( ! empty($match[6])) {
|
||||
$value = $match[6];
|
||||
}
|
||||
|
||||
// check for elements that do not have a specified attribute
|
||||
if (isset($key[0]) && $key[0] == '!') {
|
||||
$key = substr($key, 1);
|
||||
$noKey = true;
|
||||
}
|
||||
|
||||
$result[] = [
|
||||
'tag' => $tag,
|
||||
'key' => $key,
|
||||
'value' => $value,
|
||||
'operator' => $operator,
|
||||
'noKey' => $noKey,
|
||||
'alterNext' => $alterNext,
|
||||
];
|
||||
if (trim($match[7]) == ',') {
|
||||
$selectors[] = $result;
|
||||
$result = [];
|
||||
}
|
||||
}
|
||||
|
||||
// save last results
|
||||
if (count($result) > 0) {
|
||||
$selectors[] = $result;
|
||||
}
|
||||
|
||||
return $selectors;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Selector;
|
||||
|
||||
interface ParserInterface
|
||||
{
|
||||
public function parseSelectorString(string $selector): array;
|
||||
}
|
||||
+338
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser\Selector;
|
||||
|
||||
use PHPHtmlParser\Dom\AbstractNode;
|
||||
use PHPHtmlParser\Dom\Collection;
|
||||
use PHPHtmlParser\Dom\InnerNode;
|
||||
use PHPHtmlParser\Dom\LeafNode;
|
||||
use PHPHtmlParser\Exceptions\ChildNotFoundException;
|
||||
|
||||
/**
|
||||
* Class Selector
|
||||
*
|
||||
* @package PHPHtmlParser
|
||||
*/
|
||||
class Selector
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $selectors = [];
|
||||
|
||||
/**
|
||||
* Constructs with the selector string
|
||||
*
|
||||
* @param string $selector
|
||||
*/
|
||||
public function __construct(string $selector, ParserInterface $parser)
|
||||
{
|
||||
$this->selectors = $parser->parseSelectorString($selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the selectors that where found in __construct
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSelectors()
|
||||
{
|
||||
return $this->selectors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find the selectors starting from the given
|
||||
* node object.
|
||||
*
|
||||
* @param AbstractNode $node
|
||||
* @return Collection
|
||||
*/
|
||||
public function find(AbstractNode $node): Collection
|
||||
{
|
||||
$results = new Collection;
|
||||
foreach ($this->selectors as $selector) {
|
||||
$nodes = [$node];
|
||||
if (count($selector) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$options = [];
|
||||
foreach ($selector as $rule) {
|
||||
if ($rule['alterNext']) {
|
||||
$options[] = $this->alterNext($rule);
|
||||
continue;
|
||||
}
|
||||
$nodes = $this->seek($nodes, $rule, $options);
|
||||
// clear the options
|
||||
$options = [];
|
||||
}
|
||||
|
||||
// this is the final set of nodes
|
||||
foreach ($nodes as $result) {
|
||||
$results[] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to find all children that match the rule
|
||||
* given.
|
||||
*
|
||||
* @param array $nodes
|
||||
* @param array $rule
|
||||
* @param array $options
|
||||
* @return array
|
||||
* @recursive
|
||||
*/
|
||||
protected function seek(array $nodes, array $rule, array $options): array
|
||||
{
|
||||
// XPath index
|
||||
if (array_key_exists('tag', $rule) &&
|
||||
array_key_exists('key', $rule) &&
|
||||
is_numeric($rule['key'])
|
||||
) {
|
||||
$count = 0;
|
||||
/** @var AbstractNode $node */
|
||||
foreach ($nodes as $node) {
|
||||
if ($rule['tag'] == '*' ||
|
||||
$rule['tag'] == $node->getTag()->name()
|
||||
) {
|
||||
++$count;
|
||||
if ($count == $rule['key']) {
|
||||
// found the node we wanted
|
||||
return [$node];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$options = $this->flattenOptions($options);
|
||||
|
||||
$return = [];
|
||||
/** @var InnerNode $node */
|
||||
foreach ($nodes as $node) {
|
||||
// check if we are a leaf
|
||||
if ($node instanceof LeafNode ||
|
||||
! $node->hasChildren()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$children = [];
|
||||
$child = $node->firstChild();
|
||||
while ( ! is_null($child)) {
|
||||
// wild card, grab all
|
||||
if ($rule['tag'] == '*' && is_null($rule['key'])) {
|
||||
$return[] = $child;
|
||||
$child = $this->getNextChild($node, $child);
|
||||
continue;
|
||||
}
|
||||
|
||||
$pass = $this->checkTag($rule, $child);
|
||||
if ($pass && ! is_null($rule['key'])) {
|
||||
$pass = $this->checkKey($rule, $child);
|
||||
}
|
||||
if ($pass && ! is_null($rule['key']) &&
|
||||
! is_null($rule['value']) && $rule['value'] != '*'
|
||||
) {
|
||||
$pass = $this->checkComparison($rule, $child);
|
||||
}
|
||||
|
||||
if ($pass) {
|
||||
// it passed all checks
|
||||
$return[] = $child;
|
||||
} else {
|
||||
// this child failed to be matched
|
||||
if ($child instanceof InnerNode &&
|
||||
$child->hasChildren()
|
||||
) {
|
||||
// we still want to check its children
|
||||
$children[] = $child;
|
||||
}
|
||||
}
|
||||
|
||||
$child = $this->getNextChild($node, $child);
|
||||
}
|
||||
|
||||
if (( ! isset($options['checkGrandChildren']) ||
|
||||
$options['checkGrandChildren'])
|
||||
&& count($children) > 0
|
||||
) {
|
||||
// we have children that failed but are not leaves.
|
||||
$matches = $this->seek($children, $rule, $options);
|
||||
foreach ($matches as $match) {
|
||||
$return[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to match the given arguments with the given operator.
|
||||
*
|
||||
* @param string $operator
|
||||
* @param string $pattern
|
||||
* @param string $value
|
||||
* @return bool
|
||||
*/
|
||||
protected function match(string $operator, string $pattern, string $value): bool
|
||||
{
|
||||
$value = strtolower($value);
|
||||
$pattern = strtolower($pattern);
|
||||
switch ($operator) {
|
||||
case '=':
|
||||
return $value === $pattern;
|
||||
case '!=':
|
||||
return $value !== $pattern;
|
||||
case '^=':
|
||||
return preg_match('/^'.preg_quote($pattern, '/').'/', $value) == 1;
|
||||
case '$=':
|
||||
return preg_match('/'.preg_quote($pattern, '/').'$/', $value) == 1;
|
||||
case '*=':
|
||||
if ($pattern[0] == '/') {
|
||||
return preg_match($pattern, $value) == 1;
|
||||
}
|
||||
|
||||
return preg_match("/".$pattern."/i", $value) == 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to figure out what the alteration will be for
|
||||
* the next element.
|
||||
*
|
||||
* @param array $rule
|
||||
* @return array
|
||||
*/
|
||||
protected function alterNext(array $rule): array
|
||||
{
|
||||
$options = [];
|
||||
if ($rule['tag'] == '>') {
|
||||
$options['checkGrandChildren'] = false;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens the option array.
|
||||
*
|
||||
* @param array $optionsArray
|
||||
* @return array
|
||||
*/
|
||||
protected function flattenOptions(array $optionsArray)
|
||||
{
|
||||
$options = [];
|
||||
foreach ($optionsArray as $optionArray) {
|
||||
foreach ($optionArray as $key => $option) {
|
||||
$options[$key] = $option;
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next child or null if no more children.
|
||||
*
|
||||
* @param AbstractNode $node
|
||||
* @param AbstractNode $currentChild
|
||||
* @return AbstractNode|null
|
||||
*/
|
||||
protected function getNextChild(AbstractNode $node, AbstractNode $currentChild)
|
||||
{
|
||||
try {
|
||||
// get next child
|
||||
$child = $node->nextChild($currentChild->id());
|
||||
} catch (ChildNotFoundException $e) {
|
||||
// no more children
|
||||
$child = null;
|
||||
}
|
||||
|
||||
return $child;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks tag condition from rules against node.
|
||||
*
|
||||
* @param array $rule
|
||||
* @param AbstractNode $node
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkTag(array $rule, AbstractNode $node): bool
|
||||
{
|
||||
if ( ! empty($rule['tag']) && $rule['tag'] != $node->getTag()->name() &&
|
||||
$rule['tag'] != '*'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks key condition from rules against node.
|
||||
*
|
||||
* @param array $rule
|
||||
* @param AbstractNode $node
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkKey(array $rule, AbstractNode $node): bool
|
||||
{
|
||||
if ($rule['noKey']) {
|
||||
if ( ! is_null($node->getAttribute($rule['key']))) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if ($rule['key'] != 'plaintext' && !$node->hasAttribute($rule['key'])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks comparison condition from rules against node.
|
||||
*
|
||||
* @param array $rule
|
||||
* @param AbstractNode $node
|
||||
* @return bool
|
||||
*/
|
||||
public function checkComparison(array $rule, AbstractNode $node): bool
|
||||
{
|
||||
if ($rule['key'] == 'plaintext') {
|
||||
// plaintext search
|
||||
$nodeValue = $node->text();
|
||||
} else {
|
||||
// normal search
|
||||
$nodeValue = $node->getAttribute($rule['key']);
|
||||
}
|
||||
|
||||
$check = $this->match($rule['operator'], $rule['value'], $nodeValue);
|
||||
|
||||
// handle multiple classes
|
||||
if ( ! $check && $rule['key'] == 'class') {
|
||||
$nodeClasses = explode(' ', $node->getAttribute('class'));
|
||||
foreach ($nodeClasses as $class) {
|
||||
if ( ! empty($class)) {
|
||||
$check = $this->match($rule['operator'], $rule['value'], $class);
|
||||
}
|
||||
if ($check) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $check;
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
namespace PHPHtmlParser;
|
||||
|
||||
use PHPHtmlParser\Exceptions\NotLoadedException;
|
||||
|
||||
/**
|
||||
* Class StaticDom
|
||||
*
|
||||
* @package PHPHtmlParser
|
||||
*/
|
||||
final class StaticDom
|
||||
{
|
||||
|
||||
private static $dom = null;
|
||||
|
||||
/**
|
||||
* Attempts to call the given method on the most recent created dom
|
||||
* from bellow.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $arguments
|
||||
* @throws NotLoadedException
|
||||
* @return mixed
|
||||
*/
|
||||
public static function __callStatic(string $method, array $arguments)
|
||||
{
|
||||
if (self::$dom instanceof Dom) {
|
||||
return call_user_func_array([self::$dom, $method], $arguments);
|
||||
} else {
|
||||
throw new NotLoadedException('The dom is not loaded. Can not call a dom method.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to mount the static facade. The facade allows you to use
|
||||
* this object as a $className.
|
||||
*
|
||||
* @param string $className
|
||||
* @param Dom $dom
|
||||
* @return bool
|
||||
*/
|
||||
public static function mount(string $className = 'Dom', Dom $dom = null): bool
|
||||
{
|
||||
if (class_exists($className)) {
|
||||
return false;
|
||||
}
|
||||
class_alias(__CLASS__, $className);
|
||||
if ($dom instanceof Dom) {
|
||||
self::$dom = $dom;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new dom object and calls load() on the
|
||||
* new object.
|
||||
*
|
||||
* @param string $str
|
||||
* @return Dom
|
||||
*/
|
||||
public static function load(string $str): Dom
|
||||
{
|
||||
$dom = new Dom;
|
||||
self::$dom = $dom;
|
||||
|
||||
return $dom->load($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new dom object and calls loadFromFile() on the
|
||||
* new object.
|
||||
*
|
||||
* @param string $file
|
||||
* @return Dom
|
||||
*/
|
||||
public static function loadFromFile(string $file): Dom
|
||||
{
|
||||
$dom = new Dom;
|
||||
self::$dom = $dom;
|
||||
|
||||
return $dom->loadFromFile($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new dom object and calls loadFromUrl() on the
|
||||
* new object.
|
||||
*
|
||||
* @param string $url
|
||||
* @param array $options
|
||||
* @param CurlInterface $curl
|
||||
* @return Dom
|
||||
*/
|
||||
public static function loadFromUrl(string $url, array $options = [], CurlInterface $curl = null): Dom
|
||||
{
|
||||
$dom = new Dom;
|
||||
self::$dom = $dom;
|
||||
if (is_null($curl)) {
|
||||
// use the default curl interface
|
||||
$curl = new Curl;
|
||||
}
|
||||
|
||||
return $dom->loadFromUrl($url, $options, $curl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the $dom variable to null.
|
||||
*/
|
||||
public static function unload(): void
|
||||
{
|
||||
self::$dom = null;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
|
||||
install:
|
||||
- composer self-update
|
||||
- composer install
|
||||
|
||||
script:
|
||||
phpunit
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# PHPHtmlParser Contribution Guide
|
||||
|
||||
This page contains guidelines for contributing to the PHPHtmlParser package. Please review these guidelines before submitting any puLl requests to the package.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
The pull request process differs for new features and bugs. Before sending a pull request for a new feature, you should first create an issue with `[Proposal]` in the title. The proposal should describe the new feature, as well as implementation ideas. The proposal will then be reviewed and either approved or denied. Once a proposal is approved, a pull request may be created implementing the new feature. Pull requests which do not follow this guideline will be closed immediately.
|
||||
|
||||
Pull requests for bugs may be sent without creating any proposal issue. If you believe that you know of a solution for a bug that has been filed on Github, please leave a comment detailing your proposed fix.
|
||||
|
||||
### Feature Requests
|
||||
|
||||
If you have an idea for a new feature you would like to see added to the package, you may create an issue on Github with `[Request]` in the title. The feature request will then be reviewed.
|
||||
|
||||
## Coding Guidelines
|
||||
|
||||
We follow the [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) autoloading standard and take heavily from the [PSR-1](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md) coding standards. In addition to these standards, below is a list of other coding standards that should be followed:
|
||||
|
||||
- Class opening `{` should be on the same line as the class name.
|
||||
- Function and control structure opening `{` should be on a separate line.
|
||||
- Interface names are suffixed with `Interface` (`FooInterface`)
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
String Encode
|
||||
==========================
|
||||
|
||||
Version 1.0.1
|
||||
|
||||
String Encode is a simple PHP wrapper package to facilitate the encoding of strings in different charsets.
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
This package can be found on [packagist](https://packagist.org/packages/paquettg/stringencode) and is best loaded using [composer](http://getcomposer.org/). It does require php 7.1 or higher, so keep that in consideration.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
This is a really simple package so there is not much to say about it. The following is just about the only usage for this package at the moment.
|
||||
|
||||
```php
|
||||
use stringEncode\Encode;
|
||||
|
||||
$str = "Calendrier de l'avent façon Necta!"
|
||||
$encode = new Encode;
|
||||
$encode->detect($str);
|
||||
$newstr = $encode->convert($str);
|
||||
echo $newstr; // "Calendrier de l'avent façon Necta!" in UTF-8 encoding (default)
|
||||
```
|
||||
|
||||
As you can see, it is a very simple encoding converter.
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "paquettg/string-encode",
|
||||
"type": "library",
|
||||
"description": "Facilitating the process of altering string encoding in PHP.",
|
||||
"version": "1.0.1",
|
||||
"keywords": ["encoding", "charset", "string"],
|
||||
"homepage": "https://github.com/paquettg/string-encoder",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gilles Paquette",
|
||||
"email": "paquettg@gmail.com",
|
||||
"homepage": "http://gillespaquette.ca"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.5.1"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"stringEncode": "src/"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Register The Composer Auto Loader
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Composer provides a convenient, automatically generated class loader
|
||||
| for our application. We just need to utilize it! We'll require it
|
||||
| into the script here so that we do not have to worry about the
|
||||
| loading of any our classes "manually". Feels great to relax.
|
||||
|
|
||||
*/
|
||||
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Set The Default Timezone
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here we will set the default timezone for PHP. PHP is notoriously mean
|
||||
| if the timezone is not explicitly set. This will be used by each of
|
||||
| the PHP date and date-time functions throughout the application.
|
||||
|
|
||||
*/
|
||||
|
||||
date_default_timezone_set('UTC');
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
bootstrap="phpunit.php"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Repository Test Suite">
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist addUncoveredFilesFromWhitelist="false">
|
||||
<directory suffix=".php">src</directory>
|
||||
<exclude>
|
||||
<directory suffix=".php">vendor</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
namespace stringEncode;
|
||||
|
||||
class Encode {
|
||||
|
||||
/**
|
||||
* The encoding that the string is currently in.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $from;
|
||||
|
||||
/**
|
||||
* The encoding that we would like the string to be in.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $to;
|
||||
|
||||
/**
|
||||
* Sets the default charsets for thie package.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// default from encoding
|
||||
$this->from = 'CP1252';
|
||||
|
||||
// default to encoding
|
||||
$this->to = 'UTF-8';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the charset that we will be converting to.
|
||||
*
|
||||
* @param string $charset
|
||||
* @chainable
|
||||
*/
|
||||
public function to($charset)
|
||||
{
|
||||
$this->to = strtoupper($charset);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the charset that we will be converting from.
|
||||
*
|
||||
* @param string $charset
|
||||
* @chainable
|
||||
*/
|
||||
public function from($charset)
|
||||
{
|
||||
$this->from = strtoupper($charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the to and from charset that we will be using.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function charset()
|
||||
{
|
||||
return [
|
||||
'from' => $this->from,
|
||||
'to' => $this->to,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to detect the encoding of the given string from the encodingList.
|
||||
*
|
||||
* @param string $str
|
||||
* @param array $encodingList
|
||||
* @return bool
|
||||
*/
|
||||
public function detect($str, $encodingList = ['UTF-8', 'CP1252'])
|
||||
{
|
||||
$charset = mb_detect_encoding($str, $encodingList);
|
||||
if ($charset === false)
|
||||
{
|
||||
// could not detect charset
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->from = $charset;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to convert the string to the proper charset.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function convert($str)
|
||||
{
|
||||
if ($this->from != $this->to)
|
||||
{
|
||||
$str = iconv($this->from, $this->to, $str);
|
||||
}
|
||||
|
||||
if ($str === false)
|
||||
{
|
||||
// the convertion was a failure
|
||||
throw new Exception('The convertion from "'.$this->from.'" to "'.$this->to.'" was a failure.');
|
||||
}
|
||||
|
||||
// deal with BOM issue for utf-8 text
|
||||
if ($this->to == 'UTF-8')
|
||||
{
|
||||
if (substr($str, 0, 3) == "\xef\xbb\xbf")
|
||||
{
|
||||
$str = substr($str, 3);
|
||||
}
|
||||
if (substr($str, -3, 3) == "\xef\xbb\xbf")
|
||||
{
|
||||
$str = substr($str, 0, -3);
|
||||
}
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
namespace stringEncode;
|
||||
|
||||
class Exception extends \Exception {}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use stringEncode\Encode;
|
||||
|
||||
class ContentTest extends TestCase {
|
||||
|
||||
public function testTo()
|
||||
{
|
||||
$encode = new Encode;
|
||||
$encode->to('ISO-8859-1');
|
||||
$this->assertEquals('ISO-8859-1', $encode->charset()['to']);
|
||||
}
|
||||
|
||||
public function testFrom()
|
||||
{
|
||||
$encode = new Encode;
|
||||
$encode->from('ISO-8859-1');
|
||||
$this->assertEquals('ISO-8859-1', $encode->charset()['from']);
|
||||
}
|
||||
|
||||
public function testDetect()
|
||||
{
|
||||
$encode = new Encode;
|
||||
$encode->detect('Calendrier de l\'avent façon Necta!');
|
||||
$this->assertEquals('UTF-8', $encode->charset()['from']);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user