loader ajax

This commit is contained in:
2019-10-06 01:13:05 +02:00
parent 8a48a6ac0f
commit ba59f31741
355 changed files with 11286 additions and 707 deletions
+4 -2
View File
@@ -23,8 +23,10 @@ user/data/*
!user/data/.*
user/plugins/*
!user/plugins/.*
user/themes/*
!user/themes/.*
user/themes/quark/
!user/themes/quark/.*
user/pages/*
!user/pages/.*
user/localhost/config/security.yaml
user/config/security.yaml
+5
View File
@@ -73,3 +73,8 @@ RewriteRule ^(LICENSE\.txt|composer\.lock|composer\.json|\.htaccess)$ error [F]
Options -Indexes
DirectoryIndex index.php index.html index.htm
# End - Prevent Browsing and Set Default Resources
# Redirect http://figureslibres.kevintessier.net/ https://figureslibres.kevintessier.net/
Redirect 301 /sitemap /sitemap.xml
Binary file not shown.
@@ -0,0 +1,48 @@
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# =========================
# Operating System Files
# =========================
# OSX
# =========================
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
.php_cs.cache
@@ -0,0 +1,58 @@
# v1.1.1
## 29-04-2019
1. [](#bugfix)
- Content-processing
# v1.1.1-beta-1
## 11-04-2019
1. [](#bugfix)
- Content-processing
# v1.1.0
## 14-02-2019
1. [](#improved)
- Allow per-page options
- Bump vendor-dependency
- Code-cleanup
# v1.0.3
## 28-02-2017
1. [](#improved)
- Use selectize in blueprint
- Code cleanup
- PSR-1 and PSR-2 compliance
- Change plugin icon
- Update readme
2. [](#new)
- Document class and methods
3. [](#bugfix)
- Fix changelog format
# v1.0.2
## 09-01-2017
1. [](#bugfix)
- Fixed blueprint for admin interface
# v1.0.1
## 03-01-2017
1. [](#bugfix)
- Restricted operations to non-admin pages only
# v1.0.0
## 14-05-2016
1. [](#new)
- Initial release
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Ole Vik
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.
@@ -0,0 +1,108 @@
# [Grav](http://getgrav.org/) Image Srcset Plugin
Adds a `srcset`-attribute to `img`-elements to allow for responsive images in Markdown. This allows [modern browsers](http://caniuse.com/#feat=srcset) to load the image which best fits within the viewport, based on available images and sizes, essentially choosing the best image to reduce loading times (see [RespImg](https://responsiveimages.org/)).
Thus, on a small mobile screen this would load a much smaller image than on a large desktop. From this:
```html
<img title="Street view from the east" alt="Street view" src="street.jpg" />
```
To this:
```html
<img
title="Street view from the east"
alt="Street view"
src="street.jpg"
srcset="
street-320.jpg 320w,
street-480.jpg 480w,
street-640.jpg 640w,
street-960.jpg 960w,
street-1280.jpg 1280w,
street-1600.jpg 1600w,
street-1920.jpg 1920w,
street-2240.jpg 2240w
"
sizes="100vw"
/>
```
This is only applied to image-elements generated from Markdown. Depends on [PHP Html Parser v1.7.0](https://github.com/paquettg/php-html-parser/) for DOM parsing and manipulation of `srcset` and `sizes`.
# Installation and Configuration
1. Download the zip version of [this repository](https://github.com/OleVik/grav-plugin-imgsrcset) and unzip it under `/your/site/grav/user/plugins`.
2. Rename the folder to `imgsrcset`.
You should now have all the plugin files under
/your/site/grav/user/plugins/imgsrcset
The plugin is enabled by default, and can be disabled by copying `user/plugins/imgsrcset/imgsrcset.yaml` into `user/config/plugins/imgsrcset.yaml` and setting `enabled: false`. For a simple Twig-integration see [this gist](https://gist.github.com/OleVik/a7604215f127763b71bd8b8788d45cfd).
**Note**: The plugin needs Twig to be processed first, so be sure to set `twig_first` to `true` in `system.yaml`, like this:
```
pages:
twig_first: true
```
## Generating images
This plugin does **not** leverage Grav's media caching mechanisms, it simply circumvenes the need for caching by assuming that images are generated outside of Grav. This is necessary because Grav currently uses the Gregwar library, which relies on PHP's GD-module for image manipulation, and it handles large or many images poorly - indeed it tends to crash both caching and Grav itself. Thus by creating the images outside of this system the same quality and automation is achieved.
**For an example of generating responsive images with NodeJS and Gulp see [this gist](https://gist.github.com/OleVik/f2c8b51a7153743b13607072c27cf8d2).**
## Widths
The `widths` setting is a YAML sequence wherein each integer represents the width of the image, defaulting to:
```yaml
- 320
- 480
- 640
- 960
- 1280
- 1600
- 1920
- 2240
```
## Sizes
The `sizes` setting is a YAML string defining the [sizes](https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-sizes)-attribute, defaulting to:
```yaml
sizes: "100vw"
```
## Images
The plugin expects the images to be in the same folder as the source image. So, for the case of `street.jpg`, the page folder should contain:
```bash
street.jpg
street-320.jpg
street-480.jpg
street-640.jpg
street-960.jpg
street-1280.jpg
street-1600.jpg
street-1920.jpg
street-2240.jpg
```
## Per Page Options
Configuration-options may also be set on an individual Page, using the following FrontMatter:
```yaml
imgsrcset:
enabled: true
widths: widths
sizes: sizes
```
MIT License 2019 by [Ole Vik](http://github.com/olevik).
@@ -0,0 +1,42 @@
name: Image Srcset
version: 1.1.1
testing: true
description: "Adds a srcset-attribute to img-elements to allow for responsive images in Markdown."
icon: picture-o
author:
name: Ole Vik
email: git@olevik.me
url: http://olevik.me
homepage: https://github.com/olevik/grav-plugin-imgsrcset
keywords: responsive, srcset
bugs: https://github.com/olevik/grav-plugin-imgsrcset/issues
license: MIT
form:
validation: loose
fields:
enabled:
type: toggle
label: Plugin Status
highlight: 1
default: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
widths:
type: selectize
label: Widths
description: Determines the available image sizes. Comma-separated list of widths.
classes: fancy
validate:
type: commalist
sizes:
type: text
size: x-small
label: Sizes
help: Determines the Sizes-attribute.
validate:
type: text
min: 1
@@ -0,0 +1,5 @@
{
"require": {
"paquettg/php-html-parser": "^2.0.2"
}
}
@@ -0,0 +1,114 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "f38b95fb20b42face451f5eb678baf3e",
"packages": [
{
"name": "paquettg/php-html-parser",
"version": "2.0.2",
"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"
},
"type": "library",
"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"
],
"time": "2019-02-10T01:35:49+00:00"
},
{
"name": "paquettg/string-encode",
"version": "1.0.1",
"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"
},
"type": "library",
"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"
],
"time": "2018-12-21T02:25:09+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
@@ -0,0 +1,84 @@
<?php
/**
* ImgSrcset Plugin
*
* PHP version 7
*
* @category Extensions
* @package Grav
* @subpackage ImgSrcset
* @author Ole Vik <git@olevik.net>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link https://github.com/OleVik/grav-plugin-imgsrcset
*/
namespace Grav\Plugin;
use Grav\Common\Data;
use Grav\Common\Plugin;
use Grav\Common\Grav;
use Grav\Common\Page\Page;
use RocketTheme\Toolbox\Event\Event;
use PHPHtmlParser\Dom;
/**
* Adds a srcset-attribute to img-elements to allow for responsive images in Markdown
*
* Class ImgSrcsetPlugin
*
* @category Extensions
* @package Grav\Plugin
* @author Ole Vik <git@olevik.net>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link https://github.com/OleVik/grav-plugin-imgsrcset
*/
class ImgSrcsetPlugin extends Plugin
{
/**
* Register events with Grav
*
* @return array
*/
public static function getSubscribedEvents()
{
return [
'onPageContentProcessed' => ['onPageContentProcessed', 0]
];
}
/**
* Iterates over images in page content and rewrites paths
*
* @return void
*/
public function onPageContentProcessed()
{
if ($this->isAdmin()) {
return;
}
$config = (array) $this->config->get('plugins.imgsrcset');
$page = $this->grav['page'];
$config = $this->mergeConfig($page);
if ($config['enabled']) {
include __DIR__ . '/vendor/autoload.php';
$dom = new Dom;
$dom->load($page->getRawContent());
$images = $dom->find('img');
$widths = $config['widths'];
foreach ($images as $image) {
$file = pathinfo($image->getAttribute('src'));
$dirname = $file['dirname'];
$filename = $file['filename'];
$extension = $file['extension'];
$srcsets = '';
foreach ($widths as $width) {
$srcsets .= $dirname.'/'.$filename.'-'.$width.'.'.$extension.' '.$width.'w, ';
}
$srcsets = rtrim($srcsets, ", ");
$image->setAttribute('srcset', $srcsets);
$image->setAttribute('sizes', $config['sizes']);
}
$page->setRawContent($dom->outerHtml);
}
}
}
@@ -0,0 +1,11 @@
enabled: true
widths:
- 320
- 480
- 640
- 960
- 1280
- 1600
- 1920
- 2240
sizes: "100vw"
@@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInite863a36850238c27fe275e4df5c3a50c::getLoader();
@@ -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;
}
@@ -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.
@@ -0,0 +1,9 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);
@@ -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'),
);
@@ -0,0 +1,9 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);
@@ -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;
}
}
@@ -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);
}
}
@@ -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"
]
}
]
@@ -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
@@ -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
@@ -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
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
@@ -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 = '&#10;';
}
$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;
}
}
@@ -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;
}
}
@@ -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;
}
@@ -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);
}
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -0,0 +1,13 @@
<?php
namespace PHPHtmlParser\Dom;
/**
* Class LeafNode
*
* @package PHPHtmlParser
*/
abstract class LeafNode extends AbstractNode
{
}
@@ -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();
}
}
@@ -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.'>';
}
}
@@ -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('&#10;', "\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;
}
}
@@ -0,0 +1,12 @@
<?php
namespace PHPHtmlParser\Exceptions;
/**
* Class ChildNotFoundException
*
* @package PHPHtmlParser\Exceptions
*/
final class ChildNotFoundException extends \Exception
{
}
@@ -0,0 +1,11 @@
<?php
namespace PHPHtmlParser\Exceptions;
/**
* Class CircularException
*
* @package PHPHtmlParser\Exceptions
*/
final class CircularException extends \Exception
{
}
@@ -0,0 +1,11 @@
<?php
namespace PHPHtmlParser\Exceptions;
/**
* Class CurlException
*
* @package PHPHtmlParser\Exceptions
*/
class CurlException extends \Exception
{
}
@@ -0,0 +1,11 @@
<?php
namespace PHPHtmlParser\Exceptions;
/**
* Class EmptyCollectionException
*
* @package PHPHtmlParser\Exceptions
*/
final class EmptyCollectionException extends \Exception
{
}
@@ -0,0 +1,11 @@
<?php
namespace PHPHtmlParser\Exceptions;
/**
* Class NotLoadedException
*
* @package PHPHtmlParser\Exceptions
*/
final class NotLoadedException extends \Exception
{
}
@@ -0,0 +1,11 @@
<?php
namespace PHPHtmlParser\Exceptions;
/**
* Class ParentNotFoundException
*
* @package PHPHtmlParser\Exceptions
*/
final class ParentNotFoundException extends \Exception
{
}
@@ -0,0 +1,11 @@
<?php
namespace PHPHtmlParser\Exceptions;
/**
* Class StrictException
*
* @package PHPHtmlParser\Exceptions
*/
final class StrictException extends \Exception
{
}
@@ -0,0 +1,11 @@
<?php
namespace PHPHtmlParser\Exceptions;
/**
* Class UnknownChildTypeException
*
* @package PHPHtmlParser\Exceptions
*/
final class UnknownChildTypeException extends \Exception
{
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -0,0 +1,7 @@
<?php
namespace PHPHtmlParser\Selector;
interface ParserInterface
{
public function parseSelectorString(string $selector): array;
}
@@ -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;
}
}
@@ -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;
}
}
@@ -0,0 +1,14 @@
language: php
php:
- 7.1
- 7.2
- 7.3
install:
- composer self-update
- composer install
script:
phpunit
@@ -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`)
@@ -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.
@@ -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"
}
@@ -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');
@@ -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>
@@ -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;
}
}
@@ -0,0 +1,4 @@
<?php
namespace stringEncode;
class Exception extends \Exception {}
@@ -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']);
}
}
+11
View File
@@ -0,0 +1,11 @@
enabled: true
widths:
- 320
- 480
- 640
- 960
- 1280
- 1600
- 1920
- 2240
sizes: 100vw
+2
View File
@@ -0,0 +1,2 @@
enabled: true
lazy_img_class: lazy
+7
View File
@@ -0,0 +1,7 @@
enabled: true
route: /sitemap
changefreq: daily
priority: !!float 1
ignores:
- /collectif
- /commanditaires/acadie
+14 -29
View File
@@ -1,18 +1,13 @@
absolute_urls: false
timezone: ''
default_locale: null
param_sep: ':'
wrapped_site: false
reverse_proxy_setup: false
force_ssl: false
force_lowercase_urls: true
custom_base_url: ''
username_regex: '^[a-z0-9_-]{3,16}$'
pwd_regex: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}'
intl_enabled: true
languages:
supported: { }
default_lang: null
include_default_lang: true
pages_fallback_only: false
translations: true
@@ -31,7 +26,6 @@ pages:
list:
count: 20
dateformat:
default: null
short: 'jS M Y'
long: 'F jS \a\t g:ia'
publish_dates: true
@@ -59,14 +53,12 @@ pages:
- json
- rss
- atom
append_url_extension: ''
expires: 604800
cache_control: null
last_modified: false
etag: false
vary_accept_encoding: false
redirect_default_route: false
redirect_default_code: 302
redirect_default_code: '302'
redirect_trailing_slash: true
ignore_files:
- .DS_Store
@@ -74,7 +66,6 @@ pages:
- .git
- .idea
ignore_hidden: true
hide_empty_folders: false
url_taxonomy_filters: true
frontmatter:
process_twig: false
@@ -82,7 +73,7 @@ pages:
- form
- forms
cache:
enabled: true
enabled: false
check:
method: file
driver: auto
@@ -90,16 +81,14 @@ cache:
purge_at: '0 4 * * *'
clear_at: '0 3 * * *'
clear_job_type: standard
clear_images_by_default: true
clear_images_by_default: false
cli_compatibility: false
lifetime: 604800
lifetime: 0
gzip: false
allow_webserver_gzip: false
redis:
socket: false
twig:
cache: true
debug: true
cache: false
debug: false
auto_reload: true
autoescape: false
undefined_functions: true
@@ -107,20 +96,20 @@ twig:
umask_fix: false
assets:
css_pipeline: false
css_pipeline_include_externals: true
css_pipeline_before_excludes: true
css_minify: true
css_pipeline_include_externals: false
css_pipeline_before_excludes: false
css_minify: false
css_minify_windows: false
css_rewrite: true
css_rewrite: false
js_pipeline: false
js_pipeline_include_externals: true
js_pipeline_before_excludes: true
js_minify: true
js_pipeline_include_externals: false
js_pipeline_before_excludes: false
js_minify: false
enable_asset_timestamp: false
collections:
jquery: 'system://assets/jquery/jquery-2.x.min.js'
errors:
display: true
display: 1
log: true
log:
handler: file
@@ -140,8 +129,6 @@ images:
seofriendly: false
media:
enable_media_timestamp: false
unsupported_inline_types: { }
allowed_fallback_types: { }
auto_metadata_exif: false
upload_limit: 2097152
session:
@@ -153,10 +140,8 @@ session:
secure: false
httponly: true
split: true
path: null
gpm:
releases: stable
proxy_url: null
method: auto
verify_peer: true
official_gpm_only: true
-7
View File
@@ -1,7 +0,0 @@
---
title: Home
body_classes: 'title-center title-h1h2'
---
**Figures Libres** est un collectif né de la rencontre de deux démarches citoyennes : porter des messages d'utilité [publique](/projets/publique?id=publique), [sociale](/projets/sociale?id=sociale) et [culturelle](/projets/culturelle?id=culturelle) ; Œuvrer sur logiciels libres pour créer sans consommer et libérer l'information. Nous sommes graphistes, développeurs, designers, photographes, artistes, enseignant⋅e⋅s, militant⋅e⋅s… et croisons nos pratiques et compétences pour avancer plus loin à plusieurs. Convaincu⋅e⋅s que la création est un outil démocratique, nous partageons les convictions de nos commanditaires. Nous préférons maîtriser nos outils, plutôt que l'inverse, les adapter à nos envies et à vos besoins. Comme nous sommes joueur⋅euse⋅s et utopistes nous aimons les construire avec vous et les partager avec tous. Votre projet sera un site, une affiche, une campagne, une application, un livre, une exposition, une manifestation… tant mieux ! nous n'aimons pas les réponses toutes faites ; mais bien faites.
-13
View File
@@ -1,13 +0,0 @@
---
title: Projets
content:
items:
- '@self.children'
limit: 0
order:
by: date
dir: desc
pagination: true
url_taxonomy_filters: true
---
-13
View File
@@ -1,13 +0,0 @@
---
title: Culturelle
content:
items:
- '@self.children'
limit: 0
order:
by: date
dir: desc
pagination: true
url_taxonomy_filters: true
---
Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 KiB

@@ -1,19 +0,0 @@
---
date: '02-01-2018 20:51'
title: 'LEncyclopédie de la Parole'
media_order: 1.jpg
taxonomy:
category:
- 'Site web'
tag:
- 'Encyclopédie de la Parole'
- 'Echelle 1:1'
---
Refonte du siite internet
LEncyclopédie de la parole est un collectif né en 2007 aux Laboratoires dAubervilliers. Depuis plus de 10 ans, elle récolte des enregistrements de paroles quelle met en regard au sein dune collection ordonnée par phénomènes (Compression, Plis, Alternance, Mélodie, Cadence, Projection, Résidus..). En regard également sous forme de performances, de spectacles, danalyses et de jeux.
Lensemble de la collection est publié sur une plate-forme internet dédiée, dont la navigation permet la consultation de plus de 1000 sons répertoriés.
Un travail dorfèvre de Bachir Soussi-Chiadmi pour le développement, accompagné de Maud Boyer et de Kevin Tessier pour la refonte graphique.
www.encyclopediedelaparole.org
Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

@@ -1,17 +0,0 @@
---
date: '30-05-2018 19:27'
title: Ethica
media_order: OSC_2810.jpg
taxonomy:
category:
- 'Site web'
tag:
- 'Patrick Fontana'
---
Site web et application
Conception dun site et dune application qui propose une version numérique et augmentée de l’Éthique de Spinoza. Grâce à une visualisation inédite du texte, lapplication Ethica rend visible le réseau démonstratif de l’œuvre. Lutilisateur peut voir et lire librement le texte, lexplorer de façon simple et intuitive.
http://ethica-spinoza.net/fr
http://app.ethica-spinoza.net/fr/connections
Création graphique Kevin Tessier et Bachir Soussi Chiadmi ; Développement du site vitrine Kevin Tessier ; développement de lapplication Bachir Soussi Chiadmi
Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

@@ -1,20 +0,0 @@
---
date: '11-07-2018 20:51'
title: 'Les spectacles de lEncyclopédie'
media_order: '1.jpg,2.jpg,3.jpg,4.jpg'
taxonomy:
category:
- Éditions
tag:
- 'Encyclopédie de la Parole'
- 'Echelle 1:1'
---
Dépliant de présentation
Encyclopédie de la Parole / Echelle 1:1
Dépliant de présentation des productions de lEncyclopédie de la parole, édité à loccasion du lancement de la nouvelle plate-forme à la Gaité Lyrique.
Création et maquette par Kevin Tessier
Fabriqué par nos soins sous Scribus et Gimp pour la retouche image.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

@@ -1,12 +0,0 @@
---
date: '06-06-2017 19:08'
title: 'Obscurity of Light'
media_order: 'OSC_2861.jpg,OSC_2873.jpg,OSC_2884.jpg,OSC_2909.jpg,OSC_2913.jpg'
taxonomy:
category:
- Éditions
tag:
- 'Rémy Gauche'
---
Pochette de lalbum
Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

@@ -1,16 +0,0 @@
---
date: '06-07-2017 20:53'
title: 'Tous, des sang-mêlés'
media_order: 'OSC_2566.jpg,OSC_2539.jpg,OSC_2703_01.jpg,OSC_2614_01.jpg,OSC_2618_01.jpg,OSC_2625_02.jpg,OSC_2649.jpg,OSC_2672.jpg'
taxonomy:
category:
- Éditions
tag:
- 'MAC VAL'
---
[…] Nous sommes tous des passants, des migrants, des métis, des hybrides, des étrangers, des constructions, des êtres en relation. Tous, des sang-mêlés.
Louvrage met en lumière lexposition à travers cinq carnets de tailles et de couleurs différentes, dont les titrages mélangent plusieurs polices de caractères. La diversité des formats et des couleurs exprime la notion de différence, de singularité et de multi culturalité, sujets transversaux de lexposition. Cet objet modulable, multipliant les approches, génère plusieurs lectures différentes.
Une expérience éditoriale menée par Maud Boyer pour le design, Bachir Soussi-Chiadmi et Kevin Tessier pour le développement du HTML2Print
Fabriqué par nos soins sous Scribus pour la maquette de principe puis développée en HTML2Print
@@ -1,13 +0,0 @@
---
title: 'Logo du Ceras'
taxonomy:
category:
- 'Identité visuelle'
- 'Site web'
---
Identité visuelle
Refonte de lidentité du CERAS (Centre de Recherche et dAction Sociales). Le centre regroupe plusieurs missions : la revue Projet, la Doctrine Sociale de l’Église et la formation. Une identité actualisée, pensée pour être déclinée sur les différentes missions. Site de la Doctrine Sociale et brochure des formations développés sur cette nouvelle identité.
www.doctrine-sociale-catholique.fr
Création graphique de Maud Boyer et Sandrine Ripoll en collaboration avec lagence Aouka pour le développement du site.
-13
View File
@@ -1,13 +0,0 @@
---
title: Publique
content:
items:
- '@self.children'
limit: 0
order:
by: date
dir: desc
pagination: true
url_taxonomy_filters: true
---
Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 549 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 560 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 515 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 583 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 570 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 388 KiB

Some files were not shown because too many files have changed in this diff Show More