@@ -28,124 +28,124 @@ |
||
28 | 28 | class ExtensionRegistry |
29 | 29 | { |
30 | 30 | |
31 | - /** |
|
32 | - * Internal flag indicating whether or not the registry should |
|
33 | - * be used for automatic extension loading. If this is false, then |
|
34 | - * implementations should not automatically load extensions. |
|
35 | - */ |
|
36 | - public static $useRegistry = true; |
|
37 | - /** |
|
38 | - * The extension registry. This should consist of an array of class |
|
39 | - * names. |
|
40 | - */ |
|
41 | - protected static $extensionRegistry = []; |
|
42 | - protected static $extensionMethodRegistry = []; |
|
31 | + /** |
|
32 | + * Internal flag indicating whether or not the registry should |
|
33 | + * be used for automatic extension loading. If this is false, then |
|
34 | + * implementations should not automatically load extensions. |
|
35 | + */ |
|
36 | + public static $useRegistry = true; |
|
37 | + /** |
|
38 | + * The extension registry. This should consist of an array of class |
|
39 | + * names. |
|
40 | + */ |
|
41 | + protected static $extensionRegistry = []; |
|
42 | + protected static $extensionMethodRegistry = []; |
|
43 | 43 | |
44 | - /** |
|
45 | - * Extend a Query with the given extension class. |
|
46 | - */ |
|
47 | - public static function extend($classname) |
|
48 | - { |
|
49 | - self::$extensionRegistry[] = $classname; |
|
50 | - $class = new ReflectionClass($classname); |
|
51 | - $methods = $class->getMethods(); |
|
52 | - foreach ($methods as $method) { |
|
53 | - self::$extensionMethodRegistry[$method->getName()] = $classname; |
|
54 | - } |
|
55 | - } |
|
44 | + /** |
|
45 | + * Extend a Query with the given extension class. |
|
46 | + */ |
|
47 | + public static function extend($classname) |
|
48 | + { |
|
49 | + self::$extensionRegistry[] = $classname; |
|
50 | + $class = new ReflectionClass($classname); |
|
51 | + $methods = $class->getMethods(); |
|
52 | + foreach ($methods as $method) { |
|
53 | + self::$extensionMethodRegistry[$method->getName()] = $classname; |
|
54 | + } |
|
55 | + } |
|
56 | 56 | |
57 | - /** |
|
58 | - * Check to see if a method is known. |
|
59 | - * This checks to see if the given method name belongs to one of the |
|
60 | - * registered extensions. If it does, then this will return TRUE. |
|
61 | - * |
|
62 | - * @param string $name |
|
63 | - * The name of the method to search for. |
|
64 | - * |
|
65 | - * @return boolean |
|
66 | - * TRUE if the method exists, false otherwise. |
|
67 | - */ |
|
68 | - public static function hasMethod($name) |
|
69 | - { |
|
70 | - return isset(self::$extensionMethodRegistry[$name]); |
|
71 | - } |
|
57 | + /** |
|
58 | + * Check to see if a method is known. |
|
59 | + * This checks to see if the given method name belongs to one of the |
|
60 | + * registered extensions. If it does, then this will return TRUE. |
|
61 | + * |
|
62 | + * @param string $name |
|
63 | + * The name of the method to search for. |
|
64 | + * |
|
65 | + * @return boolean |
|
66 | + * TRUE if the method exists, false otherwise. |
|
67 | + */ |
|
68 | + public static function hasMethod($name) |
|
69 | + { |
|
70 | + return isset(self::$extensionMethodRegistry[$name]); |
|
71 | + } |
|
72 | 72 | |
73 | - /** |
|
74 | - * Check to see if the given extension class is registered. |
|
75 | - * Given a class name for a QueryPath::Extension class, this |
|
76 | - * will check to see if that class is registered. If so, it will return |
|
77 | - * TRUE. |
|
78 | - * |
|
79 | - * @param string $name |
|
80 | - * The name of the class. |
|
81 | - * |
|
82 | - * @return boolean |
|
83 | - * TRUE if the class is registered, FALSE otherwise. |
|
84 | - */ |
|
85 | - public static function hasExtension($name) |
|
86 | - { |
|
87 | - return in_array($name, self::$extensionRegistry); |
|
88 | - } |
|
73 | + /** |
|
74 | + * Check to see if the given extension class is registered. |
|
75 | + * Given a class name for a QueryPath::Extension class, this |
|
76 | + * will check to see if that class is registered. If so, it will return |
|
77 | + * TRUE. |
|
78 | + * |
|
79 | + * @param string $name |
|
80 | + * The name of the class. |
|
81 | + * |
|
82 | + * @return boolean |
|
83 | + * TRUE if the class is registered, FALSE otherwise. |
|
84 | + */ |
|
85 | + public static function hasExtension($name) |
|
86 | + { |
|
87 | + return in_array($name, self::$extensionRegistry); |
|
88 | + } |
|
89 | 89 | |
90 | - /** |
|
91 | - * Get the class that a given method belongs to. |
|
92 | - * Given a method name, this will check all registered extension classes |
|
93 | - * to see if any of them has the named method. If so, this will return |
|
94 | - * the classname. |
|
95 | - * |
|
96 | - * Note that if two extensions are registered that contain the same |
|
97 | - * method name, the last one registred will be the only one recognized. |
|
98 | - * |
|
99 | - * @param string $name |
|
100 | - * The name of the method. |
|
101 | - * |
|
102 | - * @return string |
|
103 | - * The name of the class. |
|
104 | - */ |
|
105 | - public static function getMethodClass($name) |
|
106 | - { |
|
107 | - return self::$extensionMethodRegistry[$name]; |
|
108 | - } |
|
90 | + /** |
|
91 | + * Get the class that a given method belongs to. |
|
92 | + * Given a method name, this will check all registered extension classes |
|
93 | + * to see if any of them has the named method. If so, this will return |
|
94 | + * the classname. |
|
95 | + * |
|
96 | + * Note that if two extensions are registered that contain the same |
|
97 | + * method name, the last one registred will be the only one recognized. |
|
98 | + * |
|
99 | + * @param string $name |
|
100 | + * The name of the method. |
|
101 | + * |
|
102 | + * @return string |
|
103 | + * The name of the class. |
|
104 | + */ |
|
105 | + public static function getMethodClass($name) |
|
106 | + { |
|
107 | + return self::$extensionMethodRegistry[$name]; |
|
108 | + } |
|
109 | 109 | |
110 | - /** |
|
111 | - * Get extensions for the given Query object. |
|
112 | - * |
|
113 | - * Given a Query object, this will return |
|
114 | - * an associative array of extension names to (new) instances. |
|
115 | - * Generally, this is intended to be used internally. |
|
116 | - * |
|
117 | - * @param Query $qp |
|
118 | - * The Query into which the extensions should be registered. |
|
119 | - * |
|
120 | - * @return array |
|
121 | - * An associative array of classnames to instances. |
|
122 | - */ |
|
123 | - public static function getExtensions(Query $qp) |
|
124 | - { |
|
125 | - $extInstances = []; |
|
126 | - foreach (self::$extensionRegistry as $ext) { |
|
127 | - $extInstances[$ext] = new $ext($qp); |
|
128 | - } |
|
110 | + /** |
|
111 | + * Get extensions for the given Query object. |
|
112 | + * |
|
113 | + * Given a Query object, this will return |
|
114 | + * an associative array of extension names to (new) instances. |
|
115 | + * Generally, this is intended to be used internally. |
|
116 | + * |
|
117 | + * @param Query $qp |
|
118 | + * The Query into which the extensions should be registered. |
|
119 | + * |
|
120 | + * @return array |
|
121 | + * An associative array of classnames to instances. |
|
122 | + */ |
|
123 | + public static function getExtensions(Query $qp) |
|
124 | + { |
|
125 | + $extInstances = []; |
|
126 | + foreach (self::$extensionRegistry as $ext) { |
|
127 | + $extInstances[$ext] = new $ext($qp); |
|
128 | + } |
|
129 | 129 | |
130 | - return $extInstances; |
|
131 | - } |
|
130 | + return $extInstances; |
|
131 | + } |
|
132 | 132 | |
133 | - public static function extensionNames(): array |
|
134 | - { |
|
135 | - return self::$extensionRegistry; |
|
136 | - } |
|
133 | + public static function extensionNames(): array |
|
134 | + { |
|
135 | + return self::$extensionRegistry; |
|
136 | + } |
|
137 | 137 | |
138 | - /** |
|
139 | - * Enable or disable automatic extension loading. |
|
140 | - * |
|
141 | - * If extension autoloading is disabled, then QueryPath will not |
|
142 | - * automatically load all registred extensions when a new Query |
|
143 | - * object is created using qp(). |
|
144 | - * |
|
145 | - * @param bool $boolean |
|
146 | - */ |
|
147 | - public static function autoloadExtensions($boolean = true): void |
|
148 | - { |
|
149 | - self::$useRegistry = $boolean; |
|
150 | - } |
|
138 | + /** |
|
139 | + * Enable or disable automatic extension loading. |
|
140 | + * |
|
141 | + * If extension autoloading is disabled, then QueryPath will not |
|
142 | + * automatically load all registred extensions when a new Query |
|
143 | + * object is created using qp(). |
|
144 | + * |
|
145 | + * @param bool $boolean |
|
146 | + */ |
|
147 | + public static function autoloadExtensions($boolean = true): void |
|
148 | + { |
|
149 | + self::$useRegistry = $boolean; |
|
150 | + } |
|
151 | 151 | } |
@@ -13,19 +13,19 @@ |
||
13 | 13 | interface Query |
14 | 14 | { |
15 | 15 | |
16 | - public function __construct($document = null, $selector = null, $options = []); |
|
16 | + public function __construct($document = null, $selector = null, $options = []); |
|
17 | 17 | |
18 | - public function find($selector); |
|
18 | + public function find($selector); |
|
19 | 19 | |
20 | - public function top($selector = null); |
|
20 | + public function top($selector = null); |
|
21 | 21 | |
22 | - public function next($selector = null); |
|
22 | + public function next($selector = null); |
|
23 | 23 | |
24 | - public function prev($selector = null); |
|
24 | + public function prev($selector = null); |
|
25 | 25 | |
26 | - public function siblings($selector = null); |
|
26 | + public function siblings($selector = null); |
|
27 | 27 | |
28 | - public function parent($selector = null); |
|
28 | + public function parent($selector = null); |
|
29 | 29 | |
30 | - public function children($selector = null); |
|
30 | + public function children($selector = null); |
|
31 | 31 | } |
@@ -157,7 +157,7 @@ discard block |
||
157 | 157 | */ |
158 | 158 | function qp($document = null, $string = '', array $options = []) |
159 | 159 | { |
160 | - return QueryPath::with($document, $string, $options); |
|
160 | + return QueryPath::with($document, $string, $options); |
|
161 | 161 | } |
162 | 162 | |
163 | 163 | /** |
@@ -193,7 +193,7 @@ discard block |
||
193 | 193 | function htmlqp($document = null, $selector = '', $options = []) |
194 | 194 | { |
195 | 195 | |
196 | - return QueryPath::withHTML($document, $selector, $options); |
|
196 | + return QueryPath::withHTML($document, $selector, $options); |
|
197 | 197 | } |
198 | 198 | |
199 | 199 | /** |
@@ -222,5 +222,5 @@ discard block |
||
222 | 222 | */ |
223 | 223 | function html5qp($document = null, $selector = null, array $options = []) |
224 | 224 | { |
225 | - return QueryPath::withHTML5($document, $selector, $options); |
|
225 | + return QueryPath::withHTML5($document, $selector, $options); |
|
226 | 226 | } |
@@ -93,5 +93,5 @@ |
||
93 | 93 | */ |
94 | 94 | interface Extension |
95 | 95 | { |
96 | - public function __construct(Query $qp); |
|
96 | + public function __construct(Query $qp); |
|
97 | 97 | } |
@@ -31,64 +31,64 @@ |
||
31 | 31 | class Options |
32 | 32 | { |
33 | 33 | |
34 | - /** |
|
35 | - * This is the static options array. |
|
36 | - * |
|
37 | - * Use the {@link set()}, {@link get()}, and {@link merge()} to |
|
38 | - * modify this array. |
|
39 | - */ |
|
40 | - static $options = []; |
|
34 | + /** |
|
35 | + * This is the static options array. |
|
36 | + * |
|
37 | + * Use the {@link set()}, {@link get()}, and {@link merge()} to |
|
38 | + * modify this array. |
|
39 | + */ |
|
40 | + static $options = []; |
|
41 | 41 | |
42 | - /** |
|
43 | - * Set the default options. |
|
44 | - * |
|
45 | - * The passed-in array will be used as the default options list. |
|
46 | - * |
|
47 | - * @param array $array |
|
48 | - * An associative array of options. |
|
49 | - */ |
|
50 | - static function set($array) |
|
51 | - { |
|
52 | - self::$options = $array; |
|
53 | - } |
|
42 | + /** |
|
43 | + * Set the default options. |
|
44 | + * |
|
45 | + * The passed-in array will be used as the default options list. |
|
46 | + * |
|
47 | + * @param array $array |
|
48 | + * An associative array of options. |
|
49 | + */ |
|
50 | + static function set($array) |
|
51 | + { |
|
52 | + self::$options = $array; |
|
53 | + } |
|
54 | 54 | |
55 | - /** |
|
56 | - * Get the default options. |
|
57 | - * |
|
58 | - * Get all options currently set as default. |
|
59 | - * |
|
60 | - * @return array |
|
61 | - * An array of options. Note that only explicitly set options are |
|
62 | - * returned. {@link QueryPath} defines default options which are not |
|
63 | - * stored in this object. |
|
64 | - */ |
|
65 | - static function get() |
|
66 | - { |
|
67 | - return self::$options; |
|
68 | - } |
|
55 | + /** |
|
56 | + * Get the default options. |
|
57 | + * |
|
58 | + * Get all options currently set as default. |
|
59 | + * |
|
60 | + * @return array |
|
61 | + * An array of options. Note that only explicitly set options are |
|
62 | + * returned. {@link QueryPath} defines default options which are not |
|
63 | + * stored in this object. |
|
64 | + */ |
|
65 | + static function get() |
|
66 | + { |
|
67 | + return self::$options; |
|
68 | + } |
|
69 | 69 | |
70 | - /** |
|
71 | - * Merge the provided array with existing options. |
|
72 | - * |
|
73 | - * On duplicate keys, the value in $array will overwrite the |
|
74 | - * value stored in the options. |
|
75 | - * |
|
76 | - * @param array $array |
|
77 | - * Associative array of options to merge into the existing options. |
|
78 | - */ |
|
79 | - static function merge($array) |
|
80 | - { |
|
81 | - self::$options = $array + self::$options; |
|
82 | - } |
|
70 | + /** |
|
71 | + * Merge the provided array with existing options. |
|
72 | + * |
|
73 | + * On duplicate keys, the value in $array will overwrite the |
|
74 | + * value stored in the options. |
|
75 | + * |
|
76 | + * @param array $array |
|
77 | + * Associative array of options to merge into the existing options. |
|
78 | + */ |
|
79 | + static function merge($array) |
|
80 | + { |
|
81 | + self::$options = $array + self::$options; |
|
82 | + } |
|
83 | 83 | |
84 | - /** |
|
85 | - * Returns true of the specified key is already overridden in this object. |
|
86 | - * |
|
87 | - * @param string $key |
|
88 | - * The key to search for. |
|
89 | - */ |
|
90 | - static function has($key) |
|
91 | - { |
|
92 | - return array_key_exists($key, self::$options); |
|
93 | - } |
|
84 | + /** |
|
85 | + * Returns true of the specified key is already overridden in this object. |
|
86 | + * |
|
87 | + * @param string $key |
|
88 | + * The key to search for. |
|
89 | + */ |
|
90 | + static function has($key) |
|
91 | + { |
|
92 | + return array_key_exists($key, self::$options); |
|
93 | + } |
|
94 | 94 | } |
@@ -23,19 +23,19 @@ |
||
23 | 23 | class QueryPathIterator extends IteratorIterator |
24 | 24 | { |
25 | 25 | |
26 | - public $options = []; |
|
27 | - private $qp; |
|
26 | + public $options = []; |
|
27 | + private $qp; |
|
28 | 28 | |
29 | - public function current() |
|
30 | - { |
|
31 | - if (! isset($this->qp)) { |
|
32 | - $this->qp = QueryPath::with(parent::current(), null, $this->options); |
|
33 | - } else { |
|
34 | - $splos = new SplObjectStorage(); |
|
35 | - $splos->attach(parent::current()); |
|
36 | - $this->qp->setMatches($splos); |
|
37 | - } |
|
29 | + public function current() |
|
30 | + { |
|
31 | + if (! isset($this->qp)) { |
|
32 | + $this->qp = QueryPath::with(parent::current(), null, $this->options); |
|
33 | + } else { |
|
34 | + $splos = new SplObjectStorage(); |
|
35 | + $splos->attach(parent::current()); |
|
36 | + $this->qp->setMatches($splos); |
|
37 | + } |
|
38 | 38 | |
39 | - return $this->qp; |
|
40 | - } |
|
39 | + return $this->qp; |
|
40 | + } |
|
41 | 41 | } |
@@ -28,7 +28,7 @@ |
||
28 | 28 | |
29 | 29 | public function current() |
30 | 30 | { |
31 | - if (! isset($this->qp)) { |
|
31 | + if (!isset($this->qp)) { |
|
32 | 32 | $this->qp = QueryPath::with(parent::current(), null, $this->options); |
33 | 33 | } else { |
34 | 34 | $splos = new SplObjectStorage(); |
@@ -49,1521 +49,1521 @@ |
||
49 | 49 | class DOMQuery extends DOM |
50 | 50 | { |
51 | 51 | |
52 | - use QueryFilters, QueryMutators, QueryChecks; |
|
53 | - |
|
54 | - /** |
|
55 | - * The last array of matches. |
|
56 | - */ |
|
57 | - protected $last = []; // Last set of matches. |
|
58 | - private $ext = []; // Extensions array. |
|
59 | - |
|
60 | - /** |
|
61 | - * The number of current matches. |
|
62 | - * |
|
63 | - * @see count() |
|
64 | - */ |
|
65 | - public $length = 0; |
|
66 | - |
|
67 | - /** |
|
68 | - * Get the effective options for the current DOMQuery object. |
|
69 | - * |
|
70 | - * This returns an associative array of all of the options as set |
|
71 | - * for the current DOMQuery object. This includes default options, |
|
72 | - * options directly passed in via {@link qp()} or the constructor, |
|
73 | - * an options set in the QueryPath::Options object. |
|
74 | - * |
|
75 | - * The order of merging options is this: |
|
76 | - * - Options passed in using qp() are highest priority, and will |
|
77 | - * override other options. |
|
78 | - * - Options set with QueryPath::Options will override default options, |
|
79 | - * but can be overridden by options passed into qp(). |
|
80 | - * - Default options will be used when no overrides are present. |
|
81 | - * |
|
82 | - * This function will return the options currently used, with the above option |
|
83 | - * overriding having been calculated already. |
|
84 | - * |
|
85 | - * @return array |
|
86 | - * An associative array of options, calculated from defaults and overridden |
|
87 | - * options. |
|
88 | - * @see qp() |
|
89 | - * @see QueryPath::Options::set() |
|
90 | - * @see QueryPath::Options::merge() |
|
91 | - * @since 2.0 |
|
92 | - */ |
|
93 | - public function getOptions(): array |
|
94 | - { |
|
95 | - return $this->options; |
|
96 | - } |
|
97 | - |
|
98 | - /** |
|
99 | - * Select the root element of the document. |
|
100 | - * |
|
101 | - * This sets the current match to the document's root element. For |
|
102 | - * practical purposes, this is the same as: |
|
103 | - * |
|
104 | - * @code |
|
105 | - * qp($someDoc)->find(':root'); |
|
106 | - * @endcode |
|
107 | - * However, since it doesn't invoke a parser, it has less overhead. It also |
|
108 | - * works in cases where the QueryPath has been reduced to zero elements (a |
|
109 | - * case that is not handled by find(':root') because there is no element |
|
110 | - * whose root can be found). |
|
111 | - * |
|
112 | - * @param string $selector |
|
113 | - * A selector. If this is supplied, QueryPath will navigate to the |
|
114 | - * document root and then run the query. (Added in QueryPath 2.0 Beta 2) |
|
115 | - * |
|
116 | - * @return DOMQuery |
|
117 | - * The DOMQuery object, wrapping the root element (document element) |
|
118 | - * for the current document. |
|
119 | - * @throws CSS\ParseException |
|
120 | - */ |
|
121 | - public function top($selector = null): Query |
|
122 | - { |
|
123 | - return $this->inst($this->document->documentElement, $selector); |
|
124 | - } |
|
125 | - |
|
126 | - /** |
|
127 | - * Given a CSS Selector, find matching items. |
|
128 | - * |
|
129 | - * @param string $selector |
|
130 | - * CSS 3 Selector |
|
131 | - * |
|
132 | - * @return DOMQuery |
|
133 | - * @throws CSS\ParseException |
|
134 | - * @see is() |
|
135 | - * @todo If a find() returns zero matches, then a subsequent find() will |
|
136 | - * also return zero matches, even if that find has a selector like :root. |
|
137 | - * The reason for this is that the {@link QueryPathEventHandler} does |
|
138 | - * not set the root of the document tree if it cannot find any elements |
|
139 | - * from which to determine what the root is. The workaround is to use |
|
140 | - * {@link top()} to select the root element again. |
|
141 | - * @see filter() |
|
142 | - */ |
|
143 | - public function find($selector): Query |
|
144 | - { |
|
145 | - $query = new DOMTraverser($this->matches); |
|
146 | - $query->find($selector); |
|
147 | - |
|
148 | - return $this->inst($query->matches(), null); |
|
149 | - } |
|
150 | - |
|
151 | - /** |
|
152 | - * @param $selector |
|
153 | - * |
|
154 | - * @return $this |
|
155 | - * @throws CSS\ParseException |
|
156 | - */ |
|
157 | - public function findInPlace($selector) |
|
158 | - { |
|
159 | - $query = new DOMTraverser($this->matches); |
|
160 | - $query->find($selector); |
|
161 | - $this->setMatches($query->matches()); |
|
162 | - |
|
163 | - return $this; |
|
164 | - } |
|
165 | - |
|
166 | - /** |
|
167 | - * Execute an XPath query and store the results in the QueryPath. |
|
168 | - * |
|
169 | - * Most methods in this class support CSS 3 Selectors. Sometimes, though, |
|
170 | - * XPath provides a finer-grained query language. Use this to execute |
|
171 | - * XPath queries. |
|
172 | - * |
|
173 | - * Beware, though. DOMQuery works best on DOM Elements, but an XPath |
|
174 | - * query can return other nodes, strings, and values. These may not work with |
|
175 | - * other QueryPath functions (though you will be able to access the |
|
176 | - * values with {@link get()}). |
|
177 | - * |
|
178 | - * @param string $query |
|
179 | - * An XPath query. |
|
180 | - * @param array $options |
|
181 | - * Currently supported options are: |
|
182 | - * - 'namespace_prefix': And XML namespace prefix to be used as the default. Used |
|
183 | - * in conjunction with 'namespace_uri' |
|
184 | - * - 'namespace_uri': The URI to be used as the default namespace URI. Used |
|
185 | - * with 'namespace_prefix' |
|
186 | - * |
|
187 | - * @return DOMQuery |
|
188 | - * A DOMQuery object wrapping the results of the query. |
|
189 | - * @throws CSS\ParseException |
|
190 | - * @author M Butcher |
|
191 | - * @author Xavier Prud'homme |
|
192 | - * @see find() |
|
193 | - */ |
|
194 | - public function xpath($query, $options = []) |
|
195 | - { |
|
196 | - $xpath = new DOMXPath($this->document); |
|
197 | - |
|
198 | - // Register a default namespace. |
|
199 | - if (! empty($options['namespace_prefix']) && ! empty($options['namespace_uri'])) { |
|
200 | - $xpath->registerNamespace($options['namespace_prefix'], $options['namespace_uri']); |
|
201 | - } |
|
202 | - |
|
203 | - $found = new SplObjectStorage(); |
|
204 | - foreach ($this->matches as $item) { |
|
205 | - $nl = $xpath->query($query, $item); |
|
206 | - if ($nl->length > 0) { |
|
207 | - for ($i = 0; $i < $nl->length; ++$i) { |
|
208 | - $found->attach($nl->item($i)); |
|
209 | - } |
|
210 | - } |
|
211 | - } |
|
212 | - |
|
213 | - return $this->inst($found, null); |
|
214 | - } |
|
215 | - |
|
216 | - /** |
|
217 | - * Get the number of elements currently wrapped by this object. |
|
218 | - * |
|
219 | - * Note that there is no length property on this object. |
|
220 | - * |
|
221 | - * @return int |
|
222 | - * Number of items in the object. |
|
223 | - * @deprecated QueryPath now implements Countable, so use count(). |
|
224 | - */ |
|
225 | - public function size() |
|
226 | - { |
|
227 | - return $this->matches->count(); |
|
228 | - } |
|
229 | - |
|
230 | - /** |
|
231 | - * Get the number of elements currently wrapped by this object. |
|
232 | - * |
|
233 | - * Since DOMQuery is Countable, the PHP count() function can also |
|
234 | - * be used on a DOMQuery. |
|
235 | - * |
|
236 | - * @code |
|
237 | - * <?php |
|
238 | - * count(qp($xml, 'div')); |
|
239 | - * ?> |
|
240 | - * @endcode |
|
241 | - * |
|
242 | - * @return int |
|
243 | - * The number of matches in the DOMQuery. |
|
244 | - */ |
|
245 | - public function count(): int |
|
246 | - { |
|
247 | - return $this->matches->count(); |
|
248 | - } |
|
249 | - |
|
250 | - /** |
|
251 | - * Get one or all elements from this object. |
|
252 | - * |
|
253 | - * When called with no paramaters, this returns all objects wrapped by |
|
254 | - * the DOMQuery. Typically, these are DOMElement objects (unless you have |
|
255 | - * used map(), xpath(), or other methods that can select |
|
256 | - * non-elements). |
|
257 | - * |
|
258 | - * When called with an index, it will return the item in the DOMQuery with |
|
259 | - * that index number. |
|
260 | - * |
|
261 | - * Calling this method does not change the DOMQuery (e.g. it is |
|
262 | - * non-destructive). |
|
263 | - * |
|
264 | - * You can use qp()->get() to iterate over all elements matched. You can |
|
265 | - * also iterate over qp() itself (DOMQuery implementations must be Traversable). |
|
266 | - * In the later case, though, each item |
|
267 | - * will be wrapped in a DOMQuery object. To learn more about iterating |
|
268 | - * in QueryPath, see {@link examples/techniques.php}. |
|
269 | - * |
|
270 | - * @param int $index |
|
271 | - * If specified, then only this index value will be returned. If this |
|
272 | - * index is out of bounds, a NULL will be returned. |
|
273 | - * @param boolean $asObject |
|
274 | - * If this is TRUE, an SplObjectStorage object will be returned |
|
275 | - * instead of an array. This is the preferred method for extensions to use. |
|
276 | - * |
|
277 | - * @return mixed |
|
278 | - * If an index is passed, one element will be returned. If no index is |
|
279 | - * present, an array of all matches will be returned. |
|
280 | - * @see eq() |
|
281 | - * @see SplObjectStorage |
|
282 | - */ |
|
283 | - public function get($index = null, $asObject = false) |
|
284 | - { |
|
285 | - if ($index !== null) { |
|
286 | - return ($this->count() > $index) ? $this->getNthMatch($index) : null; |
|
287 | - } |
|
288 | - // Retain support for legacy. |
|
289 | - if (! $asObject) { |
|
290 | - $matches = []; |
|
291 | - foreach ($this->matches as $m) { |
|
292 | - $matches[] = $m; |
|
293 | - } |
|
294 | - |
|
295 | - return $matches; |
|
296 | - } |
|
297 | - |
|
298 | - return $this->matches; |
|
299 | - } |
|
300 | - |
|
301 | - /** |
|
302 | - * Get the namespace of the current element. |
|
303 | - * |
|
304 | - * If QP is currently pointed to a list of elements, this will get the |
|
305 | - * namespace of the first element. |
|
306 | - */ |
|
307 | - public function ns() |
|
308 | - { |
|
309 | - return $this->get(0)->namespaceURI; |
|
310 | - } |
|
311 | - |
|
312 | - /** |
|
313 | - * Get the DOMDocument that we currently work with. |
|
314 | - * |
|
315 | - * This returns the current DOMDocument. Any changes made to this document will be |
|
316 | - * accessible to DOMQuery, as both will share access to the same object. |
|
317 | - * |
|
318 | - * @return DOMDocument |
|
319 | - */ |
|
320 | - public function document() |
|
321 | - { |
|
322 | - return $this->document; |
|
323 | - } |
|
324 | - |
|
325 | - /** |
|
326 | - * On an XML document, load all XIncludes. |
|
327 | - * |
|
328 | - * @return DOMQuery |
|
329 | - */ |
|
330 | - public function xinclude() |
|
331 | - { |
|
332 | - $this->document->xinclude(); |
|
333 | - |
|
334 | - return $this; |
|
335 | - } |
|
336 | - |
|
337 | - /** |
|
338 | - * Get all current elements wrapped in an array. |
|
339 | - * Compatibility function for jQuery 1.4, but identical to calling {@link get()} |
|
340 | - * with no parameters. |
|
341 | - * |
|
342 | - * @return array |
|
343 | - * An array of DOMNodes (typically DOMElements). |
|
344 | - */ |
|
345 | - public function toArray() |
|
346 | - { |
|
347 | - return $this->get(); |
|
348 | - } |
|
349 | - |
|
350 | - /** |
|
351 | - * Insert or retrieve a Data URL. |
|
352 | - * |
|
353 | - * When called with just $attr, it will fetch the result, attempt to decode it, and |
|
354 | - * return an array with the MIME type and the application data. |
|
355 | - * |
|
356 | - * When called with both $attr and $data, it will inject the data into all selected elements |
|
357 | - * So @code$qp->dataURL('src', file_get_contents('my.png'), 'image/png')@endcode will inject |
|
358 | - * the given PNG image into the selected elements. |
|
359 | - * |
|
360 | - * The current implementation only knows how to encode and decode Base 64 data. |
|
361 | - * |
|
362 | - * Note that this is known *not* to work on IE 6, but should render fine in other browsers. |
|
363 | - * |
|
364 | - * @param string $attr |
|
365 | - * The name of the attribute. |
|
366 | - * @param mixed $data |
|
367 | - * The contents to inject as the data. The value can be any one of the following: |
|
368 | - * - A URL: If this is given, then the subsystem will read the content from that URL. THIS |
|
369 | - * MUST BE A FULL URL, not a relative path. |
|
370 | - * - A string of data: If this is given, then the subsystem will encode the string. |
|
371 | - * - A stream or file handle: If this is given, the stream's contents will be encoded |
|
372 | - * and inserted as data. |
|
373 | - * (Note that we make the assumption here that you would never want to set data to be |
|
374 | - * a URL. If this is an incorrect assumption, file a bug.) |
|
375 | - * @param string $mime |
|
376 | - * The MIME type of the document. |
|
377 | - * @param resource $context |
|
378 | - * A valid context. Use this only if you need to pass a stream context. This is only necessary |
|
379 | - * if $data is a URL. (See {@link stream_context_create()}). |
|
380 | - * |
|
381 | - * @return DOMQuery|string |
|
382 | - * If this is called as a setter, this will return a DOMQuery object. Otherwise, it |
|
383 | - * will attempt to fetch data out of the attribute and return that. |
|
384 | - * @see http://en.wikipedia.org/wiki/Data:_URL |
|
385 | - * @see attr() |
|
386 | - * @since 2.1 |
|
387 | - */ |
|
388 | - public function dataURL($attr, $data = null, $mime = 'application/octet-stream', $context = null) |
|
389 | - { |
|
390 | - if (is_null($data)) { |
|
391 | - // Attempt to fetch the data |
|
392 | - $data = $this->attr($attr); |
|
393 | - if (empty($data) || is_array($data) || strpos($data, 'data:') !== 0) { |
|
394 | - return; |
|
395 | - } |
|
396 | - |
|
397 | - // So 1 and 2 should be MIME types, and 3 should be the base64-encoded data. |
|
398 | - $regex = '/^data:([a-zA-Z0-9]+)\/([a-zA-Z0-9]+);base64,(.*)$/'; |
|
399 | - $matches = []; |
|
400 | - preg_match($regex, $data, $matches); |
|
401 | - |
|
402 | - if (! empty($matches)) { |
|
403 | - $result = [ |
|
404 | - 'mime' => $matches[1] . '/' . $matches[2], |
|
405 | - 'data' => base64_decode($matches[3]), |
|
406 | - ]; |
|
407 | - |
|
408 | - return $result; |
|
409 | - } |
|
410 | - } else { |
|
411 | - $attVal = QueryPath::encodeDataURL($data, $mime, $context); |
|
412 | - |
|
413 | - return $this->attr($attr, $attVal); |
|
414 | - } |
|
415 | - } |
|
416 | - |
|
417 | - /** |
|
418 | - * Sort the contents of the QueryPath object. |
|
419 | - * |
|
420 | - * By default, this does not change the order of the elements in the |
|
421 | - * DOM. Instead, it just sorts the internal list. However, if TRUE |
|
422 | - * is passed in as the second parameter then QueryPath will re-order |
|
423 | - * the DOM, too. |
|
424 | - * |
|
425 | - * @attention |
|
426 | - * DOM re-ordering is done by finding the location of the original first |
|
427 | - * item in the list, and then placing the sorted list at that location. |
|
428 | - * |
|
429 | - * The argument $compartor is a callback, such as a function name or a |
|
430 | - * closure. The callback receives two DOMNode objects, which you can use |
|
431 | - * as DOMNodes, or wrap in QueryPath objects. |
|
432 | - * |
|
433 | - * A simple callback: |
|
434 | - * @code |
|
435 | - * <?php |
|
436 | - * $comp = function (\DOMNode $a, \DOMNode $b) { |
|
437 | - * if ($a->textContent == $b->textContent) { |
|
438 | - * return 0; |
|
439 | - * } |
|
440 | - * return $a->textContent > $b->textContent ? 1 : -1; |
|
441 | - * }; |
|
442 | - * $qp = QueryPath::with($xml, $selector)->sort($comp); |
|
443 | - * ?> |
|
444 | - * @endcode |
|
445 | - * |
|
446 | - * The above sorts the matches into lexical order using the text of each node. |
|
447 | - * If you would prefer to work with QueryPath objects instead of DOMNode |
|
448 | - * objects, you may prefer something like this: |
|
449 | - * |
|
450 | - * @code |
|
451 | - * <?php |
|
452 | - * $comp = function (\DOMNode $a, \DOMNode $b) { |
|
453 | - * $qpa = qp($a); |
|
454 | - * $qpb = qp($b); |
|
455 | - * |
|
456 | - * if ($qpa->text() == $qpb->text()) { |
|
457 | - * return 0; |
|
458 | - * } |
|
459 | - * return $qpa->text()> $qpb->text()? 1 : -1; |
|
460 | - * }; |
|
461 | - * |
|
462 | - * $qp = QueryPath::with($xml, $selector)->sort($comp); |
|
463 | - * ?> |
|
464 | - * @endcode |
|
465 | - * |
|
466 | - * @param callback $comparator |
|
467 | - * A callback. This will be called during sorting to compare two DOMNode |
|
468 | - * objects. |
|
469 | - * @param boolean $modifyDOM |
|
470 | - * If this is TRUE, the sorted results will be inserted back into |
|
471 | - * the DOM at the position of the original first element. |
|
472 | - * |
|
473 | - * @return DOMQuery |
|
474 | - * This object. |
|
475 | - * @throws CSS\ParseException |
|
476 | - */ |
|
477 | - public function sort($comparator, $modifyDOM = false): Query |
|
478 | - { |
|
479 | - // Sort as an array. |
|
480 | - $list = iterator_to_array($this->matches); |
|
481 | - |
|
482 | - if (empty($list)) { |
|
483 | - return $this; |
|
484 | - } |
|
485 | - |
|
486 | - $oldFirst = $list[0]; |
|
487 | - |
|
488 | - usort($list, $comparator); |
|
489 | - |
|
490 | - // Copy back into SplObjectStorage. |
|
491 | - $found = new SplObjectStorage(); |
|
492 | - foreach ($list as $node) { |
|
493 | - $found->attach($node); |
|
494 | - } |
|
495 | - //$this->setMatches($found); |
|
496 | - |
|
497 | - |
|
498 | - // Do DOM modifications only if necessary. |
|
499 | - if ($modifyDOM) { |
|
500 | - $placeholder = $oldFirst->ownerDocument->createElement('_PLACEHOLDER_'); |
|
501 | - $placeholder = $oldFirst->parentNode->insertBefore($placeholder, $oldFirst); |
|
502 | - $len = count($list); |
|
503 | - for ($i = 0; $i < $len; ++$i) { |
|
504 | - $node = $list[$i]; |
|
505 | - $node = $node->parentNode->removeChild($node); |
|
506 | - $placeholder->parentNode->insertBefore($node, $placeholder); |
|
507 | - } |
|
508 | - $placeholder->parentNode->removeChild($placeholder); |
|
509 | - } |
|
510 | - |
|
511 | - return $this->inst($found, null); |
|
512 | - } |
|
513 | - |
|
514 | - /** |
|
515 | - * Get an item's index. |
|
516 | - * |
|
517 | - * Given a DOMElement, get the index from the matches. This is the |
|
518 | - * converse of {@link get()}. |
|
519 | - * |
|
520 | - * @param DOMElement $subject |
|
521 | - * The item to match. |
|
522 | - * |
|
523 | - * @return mixed |
|
524 | - * The index as an integer (if found), or boolean FALSE. Since 0 is a |
|
525 | - * valid index, you should use strong equality (===) to test.. |
|
526 | - * @see get() |
|
527 | - * @see is() |
|
528 | - */ |
|
529 | - public function index($subject) |
|
530 | - { |
|
531 | - $i = 0; |
|
532 | - foreach ($this->matches as $m) { |
|
533 | - if ($m === $subject) { |
|
534 | - return $i; |
|
535 | - } |
|
536 | - ++$i; |
|
537 | - } |
|
538 | - |
|
539 | - return false; |
|
540 | - } |
|
541 | - |
|
542 | - /** |
|
543 | - * The tag name of the first element in the list. |
|
544 | - * |
|
545 | - * This returns the tag name of the first element in the list of matches. If |
|
546 | - * the list is empty, an empty string will be used. |
|
547 | - * |
|
548 | - * @return string |
|
549 | - * The tag name of the first element in the list. |
|
550 | - * @see replaceWith() |
|
551 | - * @see replaceAll() |
|
552 | - */ |
|
553 | - public function tag() |
|
554 | - { |
|
555 | - return ($this->matches->count() > 0) ? $this->getFirstMatch()->tagName : ''; |
|
556 | - } |
|
557 | - |
|
558 | - /** |
|
559 | - * Revert to the previous set of matches. |
|
560 | - * |
|
561 | - * <b>DEPRECATED</b> Do not use. |
|
562 | - * |
|
563 | - * This will revert back to the last set of matches (before the last |
|
564 | - * "destructive" set of operations). This undoes any change made to the set of |
|
565 | - * matched objects. Functions like find() and filter() change the |
|
566 | - * list of matched objects. The end() function will revert back to the last set of |
|
567 | - * matched items. |
|
568 | - * |
|
569 | - * Note that functions that modify the document, but do not change the list of |
|
570 | - * matched objects, are not "destructive". Thus, calling append('something')->end() |
|
571 | - * will not undo the append() call. |
|
572 | - * |
|
573 | - * Only one level of changes is stored. Reverting beyond that will result in |
|
574 | - * an empty set of matches. Example: |
|
575 | - * |
|
576 | - * @code |
|
577 | - * // The line below returns the same thing as qp(document, 'p'); |
|
578 | - * qp(document, 'p')->find('div')->end(); |
|
579 | - * // This returns an empty array: |
|
580 | - * qp(document, 'p')->end(); |
|
581 | - * // This returns an empty array: |
|
582 | - * qp(document, 'p')->find('div')->find('span')->end()->end(); |
|
583 | - * @endcode |
|
584 | - * |
|
585 | - * The last one returns an empty array because only one level of changes is stored. |
|
586 | - * |
|
587 | - * @return DOMQuery |
|
588 | - * A DOMNode object reflecting the list of matches prior to the last destructive |
|
589 | - * operation. |
|
590 | - * @see andSelf() |
|
591 | - * @see add() |
|
592 | - * @deprecated This function will be removed. |
|
593 | - */ |
|
594 | - public function end() |
|
595 | - { |
|
596 | - // Note that this does not use setMatches because it must set the previous |
|
597 | - // set of matches to empty array. |
|
598 | - $this->matches = $this->last; |
|
599 | - $this->last = new SplObjectStorage(); |
|
600 | - |
|
601 | - return $this; |
|
602 | - } |
|
603 | - |
|
604 | - /** |
|
605 | - * Combine the current and previous set of matched objects. |
|
606 | - * |
|
607 | - * Example: |
|
608 | - * |
|
609 | - * @code |
|
610 | - * qp(document, 'p')->find('div')->andSelf(); |
|
611 | - * @endcode |
|
612 | - * |
|
613 | - * The code above will contain a list of all p elements and all div elements that |
|
614 | - * are beneath p elements. |
|
615 | - * |
|
616 | - * @return DOMQuery |
|
617 | - * A DOMNode object with the results of the last two "destructive" operations. |
|
618 | - * @see end(); |
|
619 | - * @see add() |
|
620 | - * @see end() |
|
621 | - */ |
|
622 | - public function andSelf() |
|
623 | - { |
|
624 | - // This is destructive, so we need to set $last: |
|
625 | - $last = $this->matches; |
|
626 | - |
|
627 | - foreach ($this->last as $item) { |
|
628 | - $this->matches->attach($item); |
|
629 | - } |
|
630 | - |
|
631 | - $this->last = $last; |
|
632 | - |
|
633 | - return $this; |
|
634 | - } |
|
635 | - |
|
636 | - /** |
|
637 | - * Set or get the markup for an element. |
|
638 | - * |
|
639 | - * If $markup is set, then the giving markup will be injected into each |
|
640 | - * item in the set. All other children of that node will be deleted, and this |
|
641 | - * new code will be the only child or children. The markup MUST BE WELL FORMED. |
|
642 | - * |
|
643 | - * If no markup is given, this will return a string representing the child |
|
644 | - * markup of the first node. |
|
645 | - * |
|
646 | - * <b>Important:</b> This differs from jQuery's html() function. This function |
|
647 | - * returns <i>the current node</i> and all of its children. jQuery returns only |
|
648 | - * the children. This means you do not need to do things like this: |
|
649 | - * @code$qp->parent()->html()@endcode. |
|
650 | - * |
|
651 | - * By default, this is HTML 4.01, not XHTML. Use {@link xml()} for XHTML. |
|
652 | - * |
|
653 | - * @param string $markup |
|
654 | - * The text to insert. |
|
655 | - * |
|
656 | - * @return mixed |
|
657 | - * A string if no markup was passed, or a DOMQuery if markup was passed. |
|
658 | - * @throws Exception |
|
659 | - * @throws QueryPath |
|
660 | - * @see xml() |
|
661 | - * @see text() |
|
662 | - * @see contents() |
|
663 | - */ |
|
664 | - public function html($markup = null) |
|
665 | - { |
|
666 | - if (isset($markup)) { |
|
667 | - if ($this->options['replace_entities']) { |
|
668 | - $markup = Entities::replaceAllEntities($markup); |
|
669 | - } |
|
670 | - |
|
671 | - // Parse the HTML and insert it into the DOM |
|
672 | - //$doc = DOMDocument::loadHTML($markup); |
|
673 | - $doc = $this->document->createDocumentFragment(); |
|
674 | - $doc->appendXML($markup); |
|
675 | - $this->removeChildren(); |
|
676 | - $this->append($doc); |
|
677 | - |
|
678 | - return $this; |
|
679 | - } |
|
680 | - $length = $this->matches->count(); |
|
681 | - if ($length === 0) { |
|
682 | - return null; |
|
683 | - } |
|
684 | - // Only return the first item -- that's what JQ does. |
|
685 | - $first = $this->getFirstMatch(); |
|
686 | - |
|
687 | - // Catch cases where first item is not a legit DOM object. |
|
688 | - if (! ($first instanceof DOMNode)) { |
|
689 | - return null; |
|
690 | - } |
|
691 | - |
|
692 | - // Added by eabrand. |
|
693 | - if (! $first->ownerDocument->documentElement) { |
|
694 | - return null; |
|
695 | - } |
|
696 | - |
|
697 | - if ($first instanceof DOMDocument || $first->isSameNode($first->ownerDocument->documentElement)) { |
|
698 | - return $this->document->saveHTML(); |
|
699 | - } |
|
700 | - |
|
701 | - // saveHTML cannot take a node and serialize it. |
|
702 | - return $this->document->saveXML($first); |
|
703 | - } |
|
704 | - |
|
705 | - /** |
|
706 | - * Write the QueryPath document to HTML5. |
|
707 | - * |
|
708 | - * See html() |
|
709 | - * |
|
710 | - * @param null $markup |
|
711 | - * |
|
712 | - * @return null|DOMQuery|string |
|
713 | - * @throws QueryPath |
|
714 | - * @throws \QueryPath\Exception |
|
715 | - */ |
|
716 | - public function html5($markup = null) |
|
717 | - { |
|
718 | - $html5 = new HTML5($this->options); |
|
719 | - |
|
720 | - // append HTML to existing |
|
721 | - if ($markup === null) { |
|
722 | - // Parse the HTML and insert it into the DOM |
|
723 | - $doc = $html5->loadHTMLFragment($markup); |
|
724 | - $this->removeChildren(); |
|
725 | - $this->append($doc); |
|
726 | - |
|
727 | - return $this; |
|
728 | - } |
|
729 | - |
|
730 | - $length = $this->count(); |
|
731 | - if ($length === 0) { |
|
732 | - return null; |
|
733 | - } |
|
734 | - // Only return the first item -- that's what JQ does. |
|
735 | - $first = $this->getFirstMatch(); |
|
736 | - |
|
737 | - // Catch cases where first item is not a legit DOM object. |
|
738 | - if (! ($first instanceof DOMNode)) { |
|
739 | - return null; |
|
740 | - } |
|
741 | - |
|
742 | - // Added by eabrand. |
|
743 | - if (! $first->ownerDocument->documentElement) { |
|
744 | - return null; |
|
745 | - } |
|
746 | - |
|
747 | - if ($first instanceof DOMDocument || $first->isSameNode($first->ownerDocument->documentElement)) { |
|
748 | - return $html5->saveHTML($this->document); //$this->document->saveHTML(); |
|
749 | - } |
|
750 | - |
|
751 | - return $html5->saveHTML($first); |
|
752 | - } |
|
753 | - |
|
754 | - /** |
|
755 | - * Fetch the HTML contents INSIDE of the first DOMQuery item. |
|
756 | - * |
|
757 | - * <b>This behaves the way jQuery's @codehtml()@endcode function behaves.</b> |
|
758 | - * |
|
759 | - * This gets all children of the first match in DOMQuery. |
|
760 | - * |
|
761 | - * Consider this fragment: |
|
762 | - * |
|
763 | - * @code |
|
764 | - * <div> |
|
765 | - * test <p>foo</p> test |
|
766 | - * </div> |
|
767 | - * @endcode |
|
768 | - * |
|
769 | - * We can retrieve just the contents of this code by doing something like |
|
770 | - * this: |
|
771 | - * @code |
|
772 | - * qp($xml, 'div')->innerHTML(); |
|
773 | - * @endcode |
|
774 | - * |
|
775 | - * This would return the following: |
|
776 | - * @codetest <p>foo</p> test@endcode |
|
777 | - * |
|
778 | - * @return string |
|
779 | - * Returns a string representation of the child nodes of the first |
|
780 | - * matched element. |
|
781 | - * @see html() |
|
782 | - * @see innerXML() |
|
783 | - * @see innerXHTML() |
|
784 | - * @since 2.0 |
|
785 | - */ |
|
786 | - public function innerHTML() |
|
787 | - { |
|
788 | - return $this->innerXML(); |
|
789 | - } |
|
790 | - |
|
791 | - /** |
|
792 | - * Fetch child (inner) nodes of the first match. |
|
793 | - * |
|
794 | - * This will return the children of the present match. For an example, |
|
795 | - * see {@link innerHTML()}. |
|
796 | - * |
|
797 | - * @return string |
|
798 | - * Returns a string of XHTML that represents the children of the present |
|
799 | - * node. |
|
800 | - * @see innerXML() |
|
801 | - * @see innerHTML() |
|
802 | - * @since 2.0 |
|
803 | - */ |
|
804 | - public function innerXHTML() |
|
805 | - { |
|
806 | - $length = $this->matches->count(); |
|
807 | - if ($length === 0) { |
|
808 | - return null; |
|
809 | - } |
|
810 | - // Only return the first item -- that's what JQ does. |
|
811 | - $first = $this->getFirstMatch(); |
|
812 | - |
|
813 | - // Catch cases where first item is not a legit DOM object. |
|
814 | - if (! ($first instanceof DOMNode)) { |
|
815 | - return null; |
|
816 | - } |
|
817 | - |
|
818 | - if (! $first->hasChildNodes()) { |
|
819 | - return ''; |
|
820 | - } |
|
821 | - |
|
822 | - $buffer = ''; |
|
823 | - foreach ($first->childNodes as $child) { |
|
824 | - $buffer .= $this->document->saveXML($child, LIBXML_NOEMPTYTAG); |
|
825 | - } |
|
826 | - |
|
827 | - return $buffer; |
|
828 | - } |
|
829 | - |
|
830 | - /** |
|
831 | - * Fetch child (inner) nodes of the first match. |
|
832 | - * |
|
833 | - * This will return the children of the present match. For an example, |
|
834 | - * see {@link innerHTML()}. |
|
835 | - * |
|
836 | - * @return string |
|
837 | - * Returns a string of XHTML that represents the children of the present |
|
838 | - * node. |
|
839 | - * @see innerXHTML() |
|
840 | - * @see innerHTML() |
|
841 | - * @since 2.0 |
|
842 | - */ |
|
843 | - public function innerXML() |
|
844 | - { |
|
845 | - $length = $this->matches->count(); |
|
846 | - if ($length === 0) { |
|
847 | - return null; |
|
848 | - } |
|
849 | - // Only return the first item -- that's what JQ does. |
|
850 | - $first = $this->getFirstMatch(); |
|
851 | - |
|
852 | - // Catch cases where first item is not a legit DOM object. |
|
853 | - if (! ($first instanceof DOMNode)) { |
|
854 | - return null; |
|
855 | - } |
|
856 | - |
|
857 | - if (! $first->hasChildNodes()) { |
|
858 | - return ''; |
|
859 | - } |
|
860 | - |
|
861 | - $buffer = ''; |
|
862 | - foreach ($first->childNodes as $child) { |
|
863 | - $buffer .= $this->document->saveXML($child); |
|
864 | - } |
|
865 | - |
|
866 | - return $buffer; |
|
867 | - } |
|
868 | - |
|
869 | - /** |
|
870 | - * Get child elements as an HTML5 string. |
|
871 | - * |
|
872 | - * TODO: This is a very simple alteration of innerXML. Do we need better |
|
873 | - * support? |
|
874 | - */ |
|
875 | - public function innerHTML5() |
|
876 | - { |
|
877 | - $length = $this->matches->count(); |
|
878 | - if ($length === 0) { |
|
879 | - return null; |
|
880 | - } |
|
881 | - // Only return the first item -- that's what JQ does. |
|
882 | - $first = $this->getFirstMatch(); |
|
883 | - |
|
884 | - // Catch cases where first item is not a legit DOM object. |
|
885 | - if (! ($first instanceof DOMNode)) { |
|
886 | - return null; |
|
887 | - } |
|
888 | - |
|
889 | - if (! $first->hasChildNodes()) { |
|
890 | - return ''; |
|
891 | - } |
|
892 | - |
|
893 | - $html5 = new HTML5($this->options); |
|
894 | - $buffer = ''; |
|
895 | - foreach ($first->childNodes as $child) { |
|
896 | - $buffer .= $html5->saveHTML($child); |
|
897 | - } |
|
898 | - |
|
899 | - return $buffer; |
|
900 | - } |
|
901 | - |
|
902 | - /** |
|
903 | - * Retrieve the text of each match and concatenate them with the given separator. |
|
904 | - * |
|
905 | - * This has the effect of looping through all children, retrieving their text |
|
906 | - * content, and then concatenating the text with a separator. |
|
907 | - * |
|
908 | - * @param string $sep |
|
909 | - * The string used to separate text items. The default is a comma followed by a |
|
910 | - * space. |
|
911 | - * @param boolean $filterEmpties |
|
912 | - * If this is true, empty items will be ignored. |
|
913 | - * |
|
914 | - * @return string |
|
915 | - * The text contents, concatenated together with the given separator between |
|
916 | - * every pair of items. |
|
917 | - * @see implode() |
|
918 | - * @see text() |
|
919 | - * @since 2.0 |
|
920 | - */ |
|
921 | - public function textImplode($sep = ', ', $filterEmpties = true): string |
|
922 | - { |
|
923 | - $tmp = []; |
|
924 | - foreach ($this->matches as $m) { |
|
925 | - $txt = $m->textContent; |
|
926 | - $trimmed = trim($txt); |
|
927 | - // If filter empties out, then we only add items that have content. |
|
928 | - if ($filterEmpties) { |
|
929 | - if (strlen($trimmed) > 0) { |
|
930 | - $tmp[] = $txt; |
|
931 | - } |
|
932 | - } // Else add all content, even if it's empty. |
|
933 | - else { |
|
934 | - $tmp[] = $txt; |
|
935 | - } |
|
936 | - } |
|
937 | - |
|
938 | - return implode($sep, $tmp); |
|
939 | - } |
|
940 | - |
|
941 | - /** |
|
942 | - * Get the text contents from just child elements. |
|
943 | - * |
|
944 | - * This is a specialized variant of textImplode() that implodes text for just the |
|
945 | - * child elements of the current element. |
|
946 | - * |
|
947 | - * @param string $separator |
|
948 | - * The separator that will be inserted between found text content. |
|
949 | - * |
|
950 | - * @return string |
|
951 | - * The concatenated values of all children. |
|
952 | - * @throws CSS\ParseException |
|
953 | - */ |
|
954 | - public function childrenText($separator = ' '): string |
|
955 | - { |
|
956 | - // Branch makes it non-destructive. |
|
957 | - return $this->branch()->xpath('descendant::text()')->textImplode($separator); |
|
958 | - } |
|
959 | - |
|
960 | - /** |
|
961 | - * Get or set the text contents of a node. |
|
962 | - * |
|
963 | - * @param string $text |
|
964 | - * If this is not NULL, this value will be set as the text of the node. It |
|
965 | - * will replace any existing content. |
|
966 | - * |
|
967 | - * @return mixed |
|
968 | - * A DOMQuery if $text is set, or the text content if no text |
|
969 | - * is passed in as a pram. |
|
970 | - * @see html() |
|
971 | - * @see xml() |
|
972 | - * @see contents() |
|
973 | - */ |
|
974 | - public function text($text = null) |
|
975 | - { |
|
976 | - if (isset($text)) { |
|
977 | - $this->removeChildren(); |
|
978 | - foreach ($this->matches as $m) { |
|
979 | - $m->appendChild($this->document->createTextNode($text)); |
|
980 | - } |
|
981 | - |
|
982 | - return $this; |
|
983 | - } |
|
984 | - // Returns all text as one string: |
|
985 | - $buf = ''; |
|
986 | - foreach ($this->matches as $m) { |
|
987 | - $buf .= $m->textContent; |
|
988 | - } |
|
989 | - |
|
990 | - return $buf; |
|
991 | - } |
|
992 | - |
|
993 | - /** |
|
994 | - * Get or set the text before each selected item. |
|
995 | - * |
|
996 | - * If $text is passed in, the text is inserted before each currently selected item. |
|
997 | - * |
|
998 | - * If no text is given, this will return the concatenated text after each selected element. |
|
999 | - * |
|
1000 | - * @code |
|
1001 | - * <?php |
|
1002 | - * $xml = '<?xml version="1.0"?><root>Foo<a>Bar</a><b/></root>'; |
|
1003 | - * |
|
1004 | - * // This will return 'Foo' |
|
1005 | - * qp($xml, 'a')->textBefore(); |
|
1006 | - * |
|
1007 | - * // This will insert 'Baz' right before <b/>. |
|
1008 | - * qp($xml, 'b')->textBefore('Baz'); |
|
1009 | - * ?> |
|
1010 | - * @endcode |
|
1011 | - * |
|
1012 | - * @param string $text |
|
1013 | - * If this is set, it will be inserted before each node in the current set of |
|
1014 | - * selected items. |
|
1015 | - * |
|
1016 | - * @return mixed |
|
1017 | - * Returns the DOMQuery object if $text was set, and returns a string (possibly empty) |
|
1018 | - * if no param is passed. |
|
1019 | - * @throws Exception |
|
1020 | - * @throws QueryPath |
|
1021 | - */ |
|
1022 | - public function textBefore($text = null) |
|
1023 | - { |
|
1024 | - if (isset($text)) { |
|
1025 | - $textNode = $this->document->createTextNode($text); |
|
1026 | - |
|
1027 | - return $this->before($textNode); |
|
1028 | - } |
|
1029 | - $buffer = ''; |
|
1030 | - foreach ($this->matches as $m) { |
|
1031 | - $p = $m; |
|
1032 | - while (isset($p->previousSibling) && $p->previousSibling->nodeType === XML_TEXT_NODE) { |
|
1033 | - $p = $p->previousSibling; |
|
1034 | - $buffer .= $p->textContent; |
|
1035 | - } |
|
1036 | - } |
|
1037 | - |
|
1038 | - return $buffer; |
|
1039 | - } |
|
1040 | - |
|
1041 | - public function textAfter($text = null) |
|
1042 | - { |
|
1043 | - if (isset($text)) { |
|
1044 | - $textNode = $this->document->createTextNode($text); |
|
1045 | - |
|
1046 | - return $this->after($textNode); |
|
1047 | - } |
|
1048 | - $buffer = ''; |
|
1049 | - foreach ($this->matches as $m) { |
|
1050 | - $n = $m; |
|
1051 | - while (isset($n->nextSibling) && $n->nextSibling->nodeType === XML_TEXT_NODE) { |
|
1052 | - $n = $n->nextSibling; |
|
1053 | - $buffer .= $n->textContent; |
|
1054 | - } |
|
1055 | - } |
|
1056 | - |
|
1057 | - return $buffer; |
|
1058 | - } |
|
1059 | - |
|
1060 | - /** |
|
1061 | - * Set or get the value of an element's 'value' attribute. |
|
1062 | - * |
|
1063 | - * The 'value' attribute is common in HTML form elements. This is a |
|
1064 | - * convenience function for accessing the values. Since this is not common |
|
1065 | - * task on the server side, this method may be removed in future releases. (It |
|
1066 | - * is currently provided for jQuery compatibility.) |
|
1067 | - * |
|
1068 | - * If a value is provided in the params, then the value will be set for all |
|
1069 | - * matches. If no params are given, then the value of the first matched element |
|
1070 | - * will be returned. This may be NULL. |
|
1071 | - * |
|
1072 | - * @deprecated Just use attr(). There's no reason to use this on the server. |
|
1073 | - * @see attr() |
|
1074 | - * |
|
1075 | - * @param string $value |
|
1076 | - * |
|
1077 | - * @return mixed |
|
1078 | - * Returns a DOMQuery if a string was passed in, and a string if no string |
|
1079 | - * was passed in. In the later case, an error will produce NULL. |
|
1080 | - */ |
|
1081 | - public function val($value = null) |
|
1082 | - { |
|
1083 | - if (isset($value)) { |
|
1084 | - $this->attr('value', $value); |
|
1085 | - |
|
1086 | - return $this; |
|
1087 | - } |
|
1088 | - |
|
1089 | - return $this->attr('value'); |
|
1090 | - } |
|
1091 | - |
|
1092 | - /** |
|
1093 | - * Set or get XHTML markup for an element or elements. |
|
1094 | - * |
|
1095 | - * This differs from {@link html()} in that it processes (and produces) |
|
1096 | - * strictly XML 1.0 compliant markup. |
|
1097 | - * |
|
1098 | - * Like {@link xml()} and {@link html()}, this functions as both a |
|
1099 | - * setter and a getter. |
|
1100 | - * |
|
1101 | - * This is a convenience function for fetching HTML in XML format. |
|
1102 | - * It does no processing of the markup (such as schema validation). |
|
1103 | - * |
|
1104 | - * @param string $markup |
|
1105 | - * A string containing XML data. |
|
1106 | - * |
|
1107 | - * @return mixed |
|
1108 | - * If markup is passed in, a DOMQuery is returned. If no markup is passed |
|
1109 | - * in, XML representing the first matched element is returned. |
|
1110 | - * @see html() |
|
1111 | - * @see innerXHTML() |
|
1112 | - */ |
|
1113 | - public function xhtml($markup = null) |
|
1114 | - { |
|
1115 | - |
|
1116 | - // XXX: This is a minor reworking of the original xml() method. |
|
1117 | - // This should be refactored, probably. |
|
1118 | - // See http://github.com/technosophos/querypath/issues#issue/10 |
|
1119 | - |
|
1120 | - $omit_xml_decl = $this->options['omit_xml_declaration']; |
|
1121 | - if ($markup === true) { |
|
1122 | - // Basically, we handle the special case where we don't |
|
1123 | - // want the XML declaration to be displayed. |
|
1124 | - $omit_xml_decl = true; |
|
1125 | - } elseif (isset($markup)) { |
|
1126 | - return $this->xml($markup); |
|
1127 | - } |
|
1128 | - |
|
1129 | - $length = $this->matches->count(); |
|
1130 | - if ($length === 0) { |
|
1131 | - return null; |
|
1132 | - } |
|
1133 | - |
|
1134 | - // Only return the first item -- that's what JQ does. |
|
1135 | - $first = $this->getFirstMatch(); |
|
1136 | - // Catch cases where first item is not a legit DOM object. |
|
1137 | - if (! ($first instanceof DOMNode)) { |
|
1138 | - return null; |
|
1139 | - } |
|
1140 | - |
|
1141 | - if ($first instanceof DOMDocument || $first->isSameNode($first->ownerDocument->documentElement)) { |
|
1142 | - // Has the unfortunate side-effect of stripping doctype. |
|
1143 | - //$text = ($omit_xml_decl ? $this->document->saveXML($first->ownerDocument->documentElement, LIBXML_NOEMPTYTAG) : $this->document->saveXML(NULL, LIBXML_NOEMPTYTAG)); |
|
1144 | - $text = $this->document->saveXML(null, LIBXML_NOEMPTYTAG); |
|
1145 | - } else { |
|
1146 | - $text = $this->document->saveXML($first, LIBXML_NOEMPTYTAG); |
|
1147 | - } |
|
1148 | - |
|
1149 | - // Issue #47: Using the old trick for removing the XML tag also removed the |
|
1150 | - // doctype. So we remove it with a regex: |
|
1151 | - if ($omit_xml_decl) { |
|
1152 | - $text = preg_replace('/<\?xml\s[^>]*\?>/', '', $text); |
|
1153 | - } |
|
1154 | - |
|
1155 | - // This is slightly lenient: It allows for cases where code incorrectly places content |
|
1156 | - // inside of these supposedly unary elements. |
|
1157 | - $unary = '/<(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)(?(?=\s)([^>\/]+))><\/[^>]*>/i'; |
|
1158 | - $text = preg_replace($unary, '<\\1\\2 />', $text); |
|
1159 | - |
|
1160 | - // Experimental: Support for enclosing CDATA sections with comments to be both XML compat |
|
1161 | - // and HTML 4/5 compat |
|
1162 | - $cdata = '/(<!\[CDATA\[|\]\]>)/i'; |
|
1163 | - $replace = $this->options['escape_xhtml_js_css_sections']; |
|
1164 | - $text = preg_replace($cdata, $replace, $text); |
|
1165 | - |
|
1166 | - return $text; |
|
1167 | - } |
|
1168 | - |
|
1169 | - /** |
|
1170 | - * Set or get the XML markup for an element or elements. |
|
1171 | - * |
|
1172 | - * Like {@link html()}, this functions in both a setter and a getter mode. |
|
1173 | - * |
|
1174 | - * In setter mode, the string passed in will be parsed and then appended to the |
|
1175 | - * elements wrapped by this DOMNode object.When in setter mode, this parses |
|
1176 | - * the XML using the DOMFragment parser. For that reason, an XML declaration |
|
1177 | - * is not necessary. |
|
1178 | - * |
|
1179 | - * In getter mode, the first element wrapped by this DOMNode object will be |
|
1180 | - * converted to an XML string and returned. |
|
1181 | - * |
|
1182 | - * @param string $markup |
|
1183 | - * A string containing XML data. |
|
1184 | - * |
|
1185 | - * @return mixed |
|
1186 | - * If markup is passed in, a DOMQuery is returned. If no markup is passed |
|
1187 | - * in, XML representing the first matched element is returned. |
|
1188 | - * @see xhtml() |
|
1189 | - * @see html() |
|
1190 | - * @see text() |
|
1191 | - * @see content() |
|
1192 | - * @see innerXML() |
|
1193 | - */ |
|
1194 | - public function xml($markup = null) |
|
1195 | - { |
|
1196 | - $omit_xml_decl = $this->options['omit_xml_declaration']; |
|
1197 | - if ($markup === true) { |
|
1198 | - // Basically, we handle the special case where we don't |
|
1199 | - // want the XML declaration to be displayed. |
|
1200 | - $omit_xml_decl = true; |
|
1201 | - } elseif (isset($markup)) { |
|
1202 | - if ($this->options['replace_entities']) { |
|
1203 | - $markup = Entities::replaceAllEntities($markup); |
|
1204 | - } |
|
1205 | - $doc = $this->document->createDocumentFragment(); |
|
1206 | - $doc->appendXML($markup); |
|
1207 | - $this->removeChildren(); |
|
1208 | - $this->append($doc); |
|
1209 | - |
|
1210 | - return $this; |
|
1211 | - } |
|
1212 | - $length = $this->matches->count(); |
|
1213 | - if ($length === 0) { |
|
1214 | - return null; |
|
1215 | - } |
|
1216 | - // Only return the first item -- that's what JQ does. |
|
1217 | - $first = $this->getFirstMatch(); |
|
1218 | - |
|
1219 | - // Catch cases where first item is not a legit DOM object. |
|
1220 | - if (! ($first instanceof DOMNode)) { |
|
1221 | - return null; |
|
1222 | - } |
|
1223 | - |
|
1224 | - if ($first instanceof DOMDocument || $first->isSameNode($first->ownerDocument->documentElement)) { |
|
1225 | - return ($omit_xml_decl ? $this->document->saveXML($first->ownerDocument->documentElement) : $this->document->saveXML()); |
|
1226 | - } |
|
1227 | - |
|
1228 | - return $this->document->saveXML($first); |
|
1229 | - } |
|
1230 | - |
|
1231 | - /** |
|
1232 | - * Send the XML document to the client. |
|
1233 | - * |
|
1234 | - * Write the document to a file path, if given, or |
|
1235 | - * to stdout (usually the client). |
|
1236 | - * |
|
1237 | - * This prints the entire document. |
|
1238 | - * |
|
1239 | - * @param string $path |
|
1240 | - * The path to the file into which the XML should be written. if |
|
1241 | - * this is NULL, data will be written to STDOUT, which is usually |
|
1242 | - * sent to the remote browser. |
|
1243 | - * @param int $options |
|
1244 | - * (As of QueryPath 2.1) Pass libxml options to the saving mechanism. |
|
1245 | - * |
|
1246 | - * @return DOMQuery |
|
1247 | - * The DOMQuery object, unmodified. |
|
1248 | - * @throws Exception |
|
1249 | - * In the event that a file cannot be written, an Exception will be thrown. |
|
1250 | - * @see innerXML() |
|
1251 | - * @see writeXHTML() |
|
1252 | - * @see xml() |
|
1253 | - */ |
|
1254 | - public function writeXML($path = null, $options = 0) |
|
1255 | - { |
|
1256 | - // Backwards compatibility fix for PHP8+ |
|
1257 | - if (is_null($options)) { |
|
1258 | - $options = 0; |
|
1259 | - } |
|
1260 | - |
|
1261 | - if ($path === null) { |
|
1262 | - print $this->document->saveXML(null, $options); |
|
1263 | - } else { |
|
1264 | - try { |
|
1265 | - set_error_handler([IOException::class, 'initializeFromError']); |
|
1266 | - $this->document->save($path, $options); |
|
1267 | - } catch (Exception $e) { |
|
1268 | - restore_error_handler(); |
|
1269 | - throw $e; |
|
1270 | - } |
|
1271 | - restore_error_handler(); |
|
1272 | - } |
|
1273 | - |
|
1274 | - return $this; |
|
1275 | - } |
|
1276 | - |
|
1277 | - /** |
|
1278 | - * Writes HTML to output. |
|
1279 | - * |
|
1280 | - * HTML is formatted as HTML 4.01, without strict XML unary tags. This is for |
|
1281 | - * legacy HTML content. Modern XHTML should be written using {@link toXHTML()}. |
|
1282 | - * |
|
1283 | - * Write the document to stdout (usually the client) or to a file. |
|
1284 | - * |
|
1285 | - * @param string $path |
|
1286 | - * The path to the file into which the XML should be written. if |
|
1287 | - * this is NULL, data will be written to STDOUT, which is usually |
|
1288 | - * sent to the remote browser. |
|
1289 | - * |
|
1290 | - * @return DOMQuery |
|
1291 | - * The DOMQuery object, unmodified. |
|
1292 | - * @throws Exception |
|
1293 | - * In the event that a file cannot be written, an Exception will be thrown. |
|
1294 | - * @see innerHTML() |
|
1295 | - * @see html() |
|
1296 | - */ |
|
1297 | - public function writeHTML($path = null) |
|
1298 | - { |
|
1299 | - if ($path === null) { |
|
1300 | - print $this->document->saveHTML(); |
|
1301 | - } else { |
|
1302 | - try { |
|
1303 | - set_error_handler(['\QueryPath\ParseException', 'initializeFromError']); |
|
1304 | - $this->document->saveHTMLFile($path); |
|
1305 | - } catch (Exception $e) { |
|
1306 | - restore_error_handler(); |
|
1307 | - throw $e; |
|
1308 | - } |
|
1309 | - restore_error_handler(); |
|
1310 | - } |
|
1311 | - |
|
1312 | - return $this; |
|
1313 | - } |
|
1314 | - |
|
1315 | - /** |
|
1316 | - * Write the document to HTML5. |
|
1317 | - * |
|
1318 | - * This works the same as the other write* functions, but it encodes the output |
|
1319 | - * as HTML5 with UTF-8. |
|
1320 | - * |
|
1321 | - * @throws Exception |
|
1322 | - * In the event that a file cannot be written, an Exception will be thrown. |
|
1323 | - * @see innerHTML5() |
|
1324 | - * @see html5() |
|
1325 | - */ |
|
1326 | - public function writeHTML5($path = null) |
|
1327 | - { |
|
1328 | - $html5 = new HTML5(); |
|
1329 | - if ($path === null) { |
|
1330 | - // Print the document to stdout. |
|
1331 | - print $html5->saveHTML($this->document); |
|
1332 | - |
|
1333 | - return; |
|
1334 | - } |
|
1335 | - |
|
1336 | - $html5->save($this->document, $path); |
|
1337 | - } |
|
1338 | - |
|
1339 | - /** |
|
1340 | - * Write an XHTML file to output. |
|
1341 | - * |
|
1342 | - * Typically, you should use this instead of {@link writeHTML()}. |
|
1343 | - * |
|
1344 | - * Currently, this functions identically to {@link toXML()} <i>except that</i> |
|
1345 | - * it always uses closing tags (e.g. always @code<script></script>@endcode, |
|
1346 | - * never @code<script/>@endcode). It will |
|
1347 | - * write the file as well-formed XML. No XHTML schema validation is done. |
|
1348 | - * |
|
1349 | - * @param string $path |
|
1350 | - * The filename of the file to write to. |
|
1351 | - * |
|
1352 | - * @return DOMQuery |
|
1353 | - * Returns the DOMQuery, unmodified. |
|
1354 | - * @throws Exception |
|
1355 | - * In the event that the output file cannot be written, an exception is |
|
1356 | - * thrown. |
|
1357 | - * @see innerXHTML() |
|
1358 | - * @see xhtml() |
|
1359 | - * @see writeXML() |
|
1360 | - * @see xml() |
|
1361 | - * @see writeHTML() |
|
1362 | - * @since 2.0 |
|
1363 | - */ |
|
1364 | - public function writeXHTML($path = null) |
|
1365 | - { |
|
1366 | - return $this->writeXML($path, LIBXML_NOEMPTYTAG); |
|
1367 | - } |
|
1368 | - |
|
1369 | - /** |
|
1370 | - * Branch the base DOMQuery into another one with the same matches. |
|
1371 | - * |
|
1372 | - * This function makes a copy of the DOMQuery object, but keeps the new copy |
|
1373 | - * (initially) pointed at the same matches. This object can then be queried without |
|
1374 | - * changing the original DOMQuery. However, changes to the elements inside of this |
|
1375 | - * DOMQuery will show up in the DOMQuery from which it is branched. |
|
1376 | - * |
|
1377 | - * Compare this operation with {@link cloneAll()}. The cloneAll() call takes |
|
1378 | - * the current DOMNode object and makes a copy of all of its matches. You continue |
|
1379 | - * to operate on the same DOMNode object, but the elements inside of the DOMQuery |
|
1380 | - * are copies of those before the call to cloneAll(). |
|
1381 | - * |
|
1382 | - * This, on the other hand, copies <i>the DOMQuery</i>, but keeps valid |
|
1383 | - * references to the document and the wrapped elements. A new query branch is |
|
1384 | - * created, but any changes will be written back to the same document. |
|
1385 | - * |
|
1386 | - * In practice, this comes in handy when you want to do multiple queries on a part |
|
1387 | - * of the document, but then return to a previous set of matches. (see {@link QPTPL} |
|
1388 | - * for examples of this in practice). |
|
1389 | - * |
|
1390 | - * Example: |
|
1391 | - * |
|
1392 | - * @code |
|
1393 | - * <?php |
|
1394 | - * $qp = qp( QueryPath::HTML_STUB); |
|
1395 | - * $branch = $qp->branch(); |
|
1396 | - * $branch->find('title')->text('Title'); |
|
1397 | - * $qp->find('body')->text('This is the body')->writeHTML; |
|
1398 | - * ?> |
|
1399 | - * @endcode |
|
1400 | - * |
|
1401 | - * Notice that in the code, each of the DOMQuery objects is doing its own |
|
1402 | - * query. However, both are modifying the same document. The result of the above |
|
1403 | - * would look something like this: |
|
1404 | - * |
|
1405 | - * @code |
|
1406 | - * <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> |
|
1407 | - * <html xmlns="http://www.w3.org/1999/xhtml"> |
|
1408 | - * <head> |
|
1409 | - * <meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta> |
|
1410 | - * <title>Title</title> |
|
1411 | - * </head> |
|
1412 | - * <body>This is the body</body> |
|
1413 | - * </html> |
|
1414 | - * @endcode |
|
1415 | - * |
|
1416 | - * Notice that while $qp and $banch were performing separate queries, they |
|
1417 | - * both modified the same document. |
|
1418 | - * |
|
1419 | - * In jQuery or a browser-based solution, you generally do not need a branching |
|
1420 | - * function because there is (implicitly) only one document. In QueryPath, there |
|
1421 | - * is no implicit document. Every document must be explicitly specified (and, |
|
1422 | - * in most cases, parsed -- which is costly). Branching makes it possible to |
|
1423 | - * work on one document with multiple DOMNode objects. |
|
1424 | - * |
|
1425 | - * @param string $selector |
|
1426 | - * If a selector is passed in, an additional {@link find()} will be executed |
|
1427 | - * on the branch before it is returned. (Added in QueryPath 2.0.) |
|
1428 | - * |
|
1429 | - * @return DOMQuery |
|
1430 | - * A copy of the DOMQuery object that points to the same set of elements that |
|
1431 | - * the original DOMQuery was pointing to. |
|
1432 | - * @throws CSS\ParseException |
|
1433 | - * @see cloneAll() |
|
1434 | - * @see find() |
|
1435 | - * @since 1.1 |
|
1436 | - */ |
|
1437 | - public function branch($selector = null) |
|
1438 | - { |
|
1439 | - $temp = QueryPath::with($this->matches, null, $this->options); |
|
1440 | - //if (isset($selector)) $temp->find($selector); |
|
1441 | - $temp->document = $this->document; |
|
1442 | - if (isset($selector)) { |
|
1443 | - $temp->findInPlace($selector); |
|
1444 | - } |
|
1445 | - |
|
1446 | - return $temp; |
|
1447 | - } |
|
1448 | - |
|
1449 | - /** |
|
1450 | - * @param $matches |
|
1451 | - * @param $selector |
|
1452 | - * |
|
1453 | - * @return DOMQuery |
|
1454 | - * @throws CSS\ParseException |
|
1455 | - */ |
|
1456 | - protected function inst($matches, $selector): Query |
|
1457 | - { |
|
1458 | - $dolly = clone $this; |
|
1459 | - $dolly->setMatches($matches); |
|
1460 | - |
|
1461 | - if (isset($selector)) { |
|
1462 | - $dolly->findInPlace($selector); |
|
1463 | - } |
|
1464 | - |
|
1465 | - return $dolly; |
|
1466 | - } |
|
1467 | - |
|
1468 | - /** |
|
1469 | - * Perform a deep clone of each node in the DOMQuery. |
|
1470 | - * |
|
1471 | - * @attention |
|
1472 | - * This is an in-place modification of the current QueryPath object. |
|
1473 | - * |
|
1474 | - * This does not clone the DOMQuery object, but instead clones the |
|
1475 | - * list of nodes wrapped by the DOMQuery. Every element is deeply |
|
1476 | - * cloned. |
|
1477 | - * |
|
1478 | - * This method is analogous to jQuery's clone() method. |
|
1479 | - * |
|
1480 | - * This is a destructive operation, which means that end() will revert |
|
1481 | - * the list back to the clone's original. |
|
1482 | - * @return DOMQuery |
|
1483 | - * @see qp() |
|
1484 | - */ |
|
1485 | - public function cloneAll(): Query |
|
1486 | - { |
|
1487 | - $found = new SplObjectStorage(); |
|
1488 | - foreach ($this->matches as $m) { |
|
1489 | - $found->attach($m->cloneNode(true)); |
|
1490 | - } |
|
1491 | - $this->setMatches($found); |
|
1492 | - |
|
1493 | - return $this; |
|
1494 | - } |
|
1495 | - |
|
1496 | - /** |
|
1497 | - * Clone the DOMQuery. |
|
1498 | - * |
|
1499 | - * This makes a deep clone of the elements inside of the DOMQuery. |
|
1500 | - * |
|
1501 | - * This clones only the QueryPathImpl, not all of the decorators. The |
|
1502 | - * clone operator in PHP should handle the cloning of the decorators. |
|
1503 | - */ |
|
1504 | - public function __clone() |
|
1505 | - { |
|
1506 | - //XXX: Should we clone the document? |
|
1507 | - |
|
1508 | - // Make sure we clone the kids. |
|
1509 | - $this->cloneAll(); |
|
1510 | - } |
|
1511 | - |
|
1512 | - /** |
|
1513 | - * Call extension methods. |
|
1514 | - * |
|
1515 | - * This function is used to invoke extension methods. It searches the |
|
1516 | - * registered extenstensions for a matching function name. If one is found, |
|
1517 | - * it is executed with the arguments in the $arguments array. |
|
1518 | - * |
|
1519 | - * @throws ReflectionException |
|
1520 | - * @throws QueryPath::Exception |
|
1521 | - * An exception is thrown if a non-existent method is called. |
|
1522 | - * @throws Exception |
|
1523 | - */ |
|
1524 | - public function __call($name, $arguments) |
|
1525 | - { |
|
1526 | - |
|
1527 | - if (! ExtensionRegistry::$useRegistry) { |
|
1528 | - throw new Exception("No method named $name found (Extensions disabled)."); |
|
1529 | - } |
|
1530 | - |
|
1531 | - // Loading of extensions is deferred until the first time a |
|
1532 | - // non-core method is called. This makes constructing faster, but it |
|
1533 | - // may make the first invocation of __call() slower (if there are |
|
1534 | - // enough extensions.) |
|
1535 | - // |
|
1536 | - // The main reason for moving this out of the constructor is that most |
|
1537 | - // new DOMQuery instances do not use extensions. Charging qp() calls |
|
1538 | - // with the additional hit is not a good idea. |
|
1539 | - // |
|
1540 | - // Also, this will at least limit the number of circular references. |
|
1541 | - if (empty($this->ext)) { |
|
1542 | - // Load the registry |
|
1543 | - $this->ext = ExtensionRegistry::getExtensions($this); |
|
1544 | - } |
|
1545 | - |
|
1546 | - // Note that an empty ext registry indicates that extensions are disabled. |
|
1547 | - if (! empty($this->ext) && ExtensionRegistry::hasMethod($name)) { |
|
1548 | - $owner = ExtensionRegistry::getMethodClass($name); |
|
1549 | - $method = new ReflectionMethod($owner, $name); |
|
1550 | - |
|
1551 | - return $method->invokeArgs($this->ext[$owner], $arguments); |
|
1552 | - } |
|
1553 | - throw new Exception("No method named $name found. Possibly missing an extension."); |
|
1554 | - } |
|
1555 | - |
|
1556 | - /** |
|
1557 | - * Get an iterator for the matches in this object. |
|
1558 | - * |
|
1559 | - * @return Iterable |
|
1560 | - * Returns an iterator. |
|
1561 | - */ |
|
1562 | - public function getIterator(): Traversable |
|
1563 | - { |
|
1564 | - $i = new QueryPathIterator($this->matches); |
|
1565 | - $i->options = $this->options; |
|
1566 | - |
|
1567 | - return $i; |
|
1568 | - } |
|
52 | + use QueryFilters, QueryMutators, QueryChecks; |
|
53 | + |
|
54 | + /** |
|
55 | + * The last array of matches. |
|
56 | + */ |
|
57 | + protected $last = []; // Last set of matches. |
|
58 | + private $ext = []; // Extensions array. |
|
59 | + |
|
60 | + /** |
|
61 | + * The number of current matches. |
|
62 | + * |
|
63 | + * @see count() |
|
64 | + */ |
|
65 | + public $length = 0; |
|
66 | + |
|
67 | + /** |
|
68 | + * Get the effective options for the current DOMQuery object. |
|
69 | + * |
|
70 | + * This returns an associative array of all of the options as set |
|
71 | + * for the current DOMQuery object. This includes default options, |
|
72 | + * options directly passed in via {@link qp()} or the constructor, |
|
73 | + * an options set in the QueryPath::Options object. |
|
74 | + * |
|
75 | + * The order of merging options is this: |
|
76 | + * - Options passed in using qp() are highest priority, and will |
|
77 | + * override other options. |
|
78 | + * - Options set with QueryPath::Options will override default options, |
|
79 | + * but can be overridden by options passed into qp(). |
|
80 | + * - Default options will be used when no overrides are present. |
|
81 | + * |
|
82 | + * This function will return the options currently used, with the above option |
|
83 | + * overriding having been calculated already. |
|
84 | + * |
|
85 | + * @return array |
|
86 | + * An associative array of options, calculated from defaults and overridden |
|
87 | + * options. |
|
88 | + * @see qp() |
|
89 | + * @see QueryPath::Options::set() |
|
90 | + * @see QueryPath::Options::merge() |
|
91 | + * @since 2.0 |
|
92 | + */ |
|
93 | + public function getOptions(): array |
|
94 | + { |
|
95 | + return $this->options; |
|
96 | + } |
|
97 | + |
|
98 | + /** |
|
99 | + * Select the root element of the document. |
|
100 | + * |
|
101 | + * This sets the current match to the document's root element. For |
|
102 | + * practical purposes, this is the same as: |
|
103 | + * |
|
104 | + * @code |
|
105 | + * qp($someDoc)->find(':root'); |
|
106 | + * @endcode |
|
107 | + * However, since it doesn't invoke a parser, it has less overhead. It also |
|
108 | + * works in cases where the QueryPath has been reduced to zero elements (a |
|
109 | + * case that is not handled by find(':root') because there is no element |
|
110 | + * whose root can be found). |
|
111 | + * |
|
112 | + * @param string $selector |
|
113 | + * A selector. If this is supplied, QueryPath will navigate to the |
|
114 | + * document root and then run the query. (Added in QueryPath 2.0 Beta 2) |
|
115 | + * |
|
116 | + * @return DOMQuery |
|
117 | + * The DOMQuery object, wrapping the root element (document element) |
|
118 | + * for the current document. |
|
119 | + * @throws CSS\ParseException |
|
120 | + */ |
|
121 | + public function top($selector = null): Query |
|
122 | + { |
|
123 | + return $this->inst($this->document->documentElement, $selector); |
|
124 | + } |
|
125 | + |
|
126 | + /** |
|
127 | + * Given a CSS Selector, find matching items. |
|
128 | + * |
|
129 | + * @param string $selector |
|
130 | + * CSS 3 Selector |
|
131 | + * |
|
132 | + * @return DOMQuery |
|
133 | + * @throws CSS\ParseException |
|
134 | + * @see is() |
|
135 | + * @todo If a find() returns zero matches, then a subsequent find() will |
|
136 | + * also return zero matches, even if that find has a selector like :root. |
|
137 | + * The reason for this is that the {@link QueryPathEventHandler} does |
|
138 | + * not set the root of the document tree if it cannot find any elements |
|
139 | + * from which to determine what the root is. The workaround is to use |
|
140 | + * {@link top()} to select the root element again. |
|
141 | + * @see filter() |
|
142 | + */ |
|
143 | + public function find($selector): Query |
|
144 | + { |
|
145 | + $query = new DOMTraverser($this->matches); |
|
146 | + $query->find($selector); |
|
147 | + |
|
148 | + return $this->inst($query->matches(), null); |
|
149 | + } |
|
150 | + |
|
151 | + /** |
|
152 | + * @param $selector |
|
153 | + * |
|
154 | + * @return $this |
|
155 | + * @throws CSS\ParseException |
|
156 | + */ |
|
157 | + public function findInPlace($selector) |
|
158 | + { |
|
159 | + $query = new DOMTraverser($this->matches); |
|
160 | + $query->find($selector); |
|
161 | + $this->setMatches($query->matches()); |
|
162 | + |
|
163 | + return $this; |
|
164 | + } |
|
165 | + |
|
166 | + /** |
|
167 | + * Execute an XPath query and store the results in the QueryPath. |
|
168 | + * |
|
169 | + * Most methods in this class support CSS 3 Selectors. Sometimes, though, |
|
170 | + * XPath provides a finer-grained query language. Use this to execute |
|
171 | + * XPath queries. |
|
172 | + * |
|
173 | + * Beware, though. DOMQuery works best on DOM Elements, but an XPath |
|
174 | + * query can return other nodes, strings, and values. These may not work with |
|
175 | + * other QueryPath functions (though you will be able to access the |
|
176 | + * values with {@link get()}). |
|
177 | + * |
|
178 | + * @param string $query |
|
179 | + * An XPath query. |
|
180 | + * @param array $options |
|
181 | + * Currently supported options are: |
|
182 | + * - 'namespace_prefix': And XML namespace prefix to be used as the default. Used |
|
183 | + * in conjunction with 'namespace_uri' |
|
184 | + * - 'namespace_uri': The URI to be used as the default namespace URI. Used |
|
185 | + * with 'namespace_prefix' |
|
186 | + * |
|
187 | + * @return DOMQuery |
|
188 | + * A DOMQuery object wrapping the results of the query. |
|
189 | + * @throws CSS\ParseException |
|
190 | + * @author M Butcher |
|
191 | + * @author Xavier Prud'homme |
|
192 | + * @see find() |
|
193 | + */ |
|
194 | + public function xpath($query, $options = []) |
|
195 | + { |
|
196 | + $xpath = new DOMXPath($this->document); |
|
197 | + |
|
198 | + // Register a default namespace. |
|
199 | + if (! empty($options['namespace_prefix']) && ! empty($options['namespace_uri'])) { |
|
200 | + $xpath->registerNamespace($options['namespace_prefix'], $options['namespace_uri']); |
|
201 | + } |
|
202 | + |
|
203 | + $found = new SplObjectStorage(); |
|
204 | + foreach ($this->matches as $item) { |
|
205 | + $nl = $xpath->query($query, $item); |
|
206 | + if ($nl->length > 0) { |
|
207 | + for ($i = 0; $i < $nl->length; ++$i) { |
|
208 | + $found->attach($nl->item($i)); |
|
209 | + } |
|
210 | + } |
|
211 | + } |
|
212 | + |
|
213 | + return $this->inst($found, null); |
|
214 | + } |
|
215 | + |
|
216 | + /** |
|
217 | + * Get the number of elements currently wrapped by this object. |
|
218 | + * |
|
219 | + * Note that there is no length property on this object. |
|
220 | + * |
|
221 | + * @return int |
|
222 | + * Number of items in the object. |
|
223 | + * @deprecated QueryPath now implements Countable, so use count(). |
|
224 | + */ |
|
225 | + public function size() |
|
226 | + { |
|
227 | + return $this->matches->count(); |
|
228 | + } |
|
229 | + |
|
230 | + /** |
|
231 | + * Get the number of elements currently wrapped by this object. |
|
232 | + * |
|
233 | + * Since DOMQuery is Countable, the PHP count() function can also |
|
234 | + * be used on a DOMQuery. |
|
235 | + * |
|
236 | + * @code |
|
237 | + * <?php |
|
238 | + * count(qp($xml, 'div')); |
|
239 | + * ?> |
|
240 | + * @endcode |
|
241 | + * |
|
242 | + * @return int |
|
243 | + * The number of matches in the DOMQuery. |
|
244 | + */ |
|
245 | + public function count(): int |
|
246 | + { |
|
247 | + return $this->matches->count(); |
|
248 | + } |
|
249 | + |
|
250 | + /** |
|
251 | + * Get one or all elements from this object. |
|
252 | + * |
|
253 | + * When called with no paramaters, this returns all objects wrapped by |
|
254 | + * the DOMQuery. Typically, these are DOMElement objects (unless you have |
|
255 | + * used map(), xpath(), or other methods that can select |
|
256 | + * non-elements). |
|
257 | + * |
|
258 | + * When called with an index, it will return the item in the DOMQuery with |
|
259 | + * that index number. |
|
260 | + * |
|
261 | + * Calling this method does not change the DOMQuery (e.g. it is |
|
262 | + * non-destructive). |
|
263 | + * |
|
264 | + * You can use qp()->get() to iterate over all elements matched. You can |
|
265 | + * also iterate over qp() itself (DOMQuery implementations must be Traversable). |
|
266 | + * In the later case, though, each item |
|
267 | + * will be wrapped in a DOMQuery object. To learn more about iterating |
|
268 | + * in QueryPath, see {@link examples/techniques.php}. |
|
269 | + * |
|
270 | + * @param int $index |
|
271 | + * If specified, then only this index value will be returned. If this |
|
272 | + * index is out of bounds, a NULL will be returned. |
|
273 | + * @param boolean $asObject |
|
274 | + * If this is TRUE, an SplObjectStorage object will be returned |
|
275 | + * instead of an array. This is the preferred method for extensions to use. |
|
276 | + * |
|
277 | + * @return mixed |
|
278 | + * If an index is passed, one element will be returned. If no index is |
|
279 | + * present, an array of all matches will be returned. |
|
280 | + * @see eq() |
|
281 | + * @see SplObjectStorage |
|
282 | + */ |
|
283 | + public function get($index = null, $asObject = false) |
|
284 | + { |
|
285 | + if ($index !== null) { |
|
286 | + return ($this->count() > $index) ? $this->getNthMatch($index) : null; |
|
287 | + } |
|
288 | + // Retain support for legacy. |
|
289 | + if (! $asObject) { |
|
290 | + $matches = []; |
|
291 | + foreach ($this->matches as $m) { |
|
292 | + $matches[] = $m; |
|
293 | + } |
|
294 | + |
|
295 | + return $matches; |
|
296 | + } |
|
297 | + |
|
298 | + return $this->matches; |
|
299 | + } |
|
300 | + |
|
301 | + /** |
|
302 | + * Get the namespace of the current element. |
|
303 | + * |
|
304 | + * If QP is currently pointed to a list of elements, this will get the |
|
305 | + * namespace of the first element. |
|
306 | + */ |
|
307 | + public function ns() |
|
308 | + { |
|
309 | + return $this->get(0)->namespaceURI; |
|
310 | + } |
|
311 | + |
|
312 | + /** |
|
313 | + * Get the DOMDocument that we currently work with. |
|
314 | + * |
|
315 | + * This returns the current DOMDocument. Any changes made to this document will be |
|
316 | + * accessible to DOMQuery, as both will share access to the same object. |
|
317 | + * |
|
318 | + * @return DOMDocument |
|
319 | + */ |
|
320 | + public function document() |
|
321 | + { |
|
322 | + return $this->document; |
|
323 | + } |
|
324 | + |
|
325 | + /** |
|
326 | + * On an XML document, load all XIncludes. |
|
327 | + * |
|
328 | + * @return DOMQuery |
|
329 | + */ |
|
330 | + public function xinclude() |
|
331 | + { |
|
332 | + $this->document->xinclude(); |
|
333 | + |
|
334 | + return $this; |
|
335 | + } |
|
336 | + |
|
337 | + /** |
|
338 | + * Get all current elements wrapped in an array. |
|
339 | + * Compatibility function for jQuery 1.4, but identical to calling {@link get()} |
|
340 | + * with no parameters. |
|
341 | + * |
|
342 | + * @return array |
|
343 | + * An array of DOMNodes (typically DOMElements). |
|
344 | + */ |
|
345 | + public function toArray() |
|
346 | + { |
|
347 | + return $this->get(); |
|
348 | + } |
|
349 | + |
|
350 | + /** |
|
351 | + * Insert or retrieve a Data URL. |
|
352 | + * |
|
353 | + * When called with just $attr, it will fetch the result, attempt to decode it, and |
|
354 | + * return an array with the MIME type and the application data. |
|
355 | + * |
|
356 | + * When called with both $attr and $data, it will inject the data into all selected elements |
|
357 | + * So @code$qp->dataURL('src', file_get_contents('my.png'), 'image/png')@endcode will inject |
|
358 | + * the given PNG image into the selected elements. |
|
359 | + * |
|
360 | + * The current implementation only knows how to encode and decode Base 64 data. |
|
361 | + * |
|
362 | + * Note that this is known *not* to work on IE 6, but should render fine in other browsers. |
|
363 | + * |
|
364 | + * @param string $attr |
|
365 | + * The name of the attribute. |
|
366 | + * @param mixed $data |
|
367 | + * The contents to inject as the data. The value can be any one of the following: |
|
368 | + * - A URL: If this is given, then the subsystem will read the content from that URL. THIS |
|
369 | + * MUST BE A FULL URL, not a relative path. |
|
370 | + * - A string of data: If this is given, then the subsystem will encode the string. |
|
371 | + * - A stream or file handle: If this is given, the stream's contents will be encoded |
|
372 | + * and inserted as data. |
|
373 | + * (Note that we make the assumption here that you would never want to set data to be |
|
374 | + * a URL. If this is an incorrect assumption, file a bug.) |
|
375 | + * @param string $mime |
|
376 | + * The MIME type of the document. |
|
377 | + * @param resource $context |
|
378 | + * A valid context. Use this only if you need to pass a stream context. This is only necessary |
|
379 | + * if $data is a URL. (See {@link stream_context_create()}). |
|
380 | + * |
|
381 | + * @return DOMQuery|string |
|
382 | + * If this is called as a setter, this will return a DOMQuery object. Otherwise, it |
|
383 | + * will attempt to fetch data out of the attribute and return that. |
|
384 | + * @see http://en.wikipedia.org/wiki/Data:_URL |
|
385 | + * @see attr() |
|
386 | + * @since 2.1 |
|
387 | + */ |
|
388 | + public function dataURL($attr, $data = null, $mime = 'application/octet-stream', $context = null) |
|
389 | + { |
|
390 | + if (is_null($data)) { |
|
391 | + // Attempt to fetch the data |
|
392 | + $data = $this->attr($attr); |
|
393 | + if (empty($data) || is_array($data) || strpos($data, 'data:') !== 0) { |
|
394 | + return; |
|
395 | + } |
|
396 | + |
|
397 | + // So 1 and 2 should be MIME types, and 3 should be the base64-encoded data. |
|
398 | + $regex = '/^data:([a-zA-Z0-9]+)\/([a-zA-Z0-9]+);base64,(.*)$/'; |
|
399 | + $matches = []; |
|
400 | + preg_match($regex, $data, $matches); |
|
401 | + |
|
402 | + if (! empty($matches)) { |
|
403 | + $result = [ |
|
404 | + 'mime' => $matches[1] . '/' . $matches[2], |
|
405 | + 'data' => base64_decode($matches[3]), |
|
406 | + ]; |
|
407 | + |
|
408 | + return $result; |
|
409 | + } |
|
410 | + } else { |
|
411 | + $attVal = QueryPath::encodeDataURL($data, $mime, $context); |
|
412 | + |
|
413 | + return $this->attr($attr, $attVal); |
|
414 | + } |
|
415 | + } |
|
416 | + |
|
417 | + /** |
|
418 | + * Sort the contents of the QueryPath object. |
|
419 | + * |
|
420 | + * By default, this does not change the order of the elements in the |
|
421 | + * DOM. Instead, it just sorts the internal list. However, if TRUE |
|
422 | + * is passed in as the second parameter then QueryPath will re-order |
|
423 | + * the DOM, too. |
|
424 | + * |
|
425 | + * @attention |
|
426 | + * DOM re-ordering is done by finding the location of the original first |
|
427 | + * item in the list, and then placing the sorted list at that location. |
|
428 | + * |
|
429 | + * The argument $compartor is a callback, such as a function name or a |
|
430 | + * closure. The callback receives two DOMNode objects, which you can use |
|
431 | + * as DOMNodes, or wrap in QueryPath objects. |
|
432 | + * |
|
433 | + * A simple callback: |
|
434 | + * @code |
|
435 | + * <?php |
|
436 | + * $comp = function (\DOMNode $a, \DOMNode $b) { |
|
437 | + * if ($a->textContent == $b->textContent) { |
|
438 | + * return 0; |
|
439 | + * } |
|
440 | + * return $a->textContent > $b->textContent ? 1 : -1; |
|
441 | + * }; |
|
442 | + * $qp = QueryPath::with($xml, $selector)->sort($comp); |
|
443 | + * ?> |
|
444 | + * @endcode |
|
445 | + * |
|
446 | + * The above sorts the matches into lexical order using the text of each node. |
|
447 | + * If you would prefer to work with QueryPath objects instead of DOMNode |
|
448 | + * objects, you may prefer something like this: |
|
449 | + * |
|
450 | + * @code |
|
451 | + * <?php |
|
452 | + * $comp = function (\DOMNode $a, \DOMNode $b) { |
|
453 | + * $qpa = qp($a); |
|
454 | + * $qpb = qp($b); |
|
455 | + * |
|
456 | + * if ($qpa->text() == $qpb->text()) { |
|
457 | + * return 0; |
|
458 | + * } |
|
459 | + * return $qpa->text()> $qpb->text()? 1 : -1; |
|
460 | + * }; |
|
461 | + * |
|
462 | + * $qp = QueryPath::with($xml, $selector)->sort($comp); |
|
463 | + * ?> |
|
464 | + * @endcode |
|
465 | + * |
|
466 | + * @param callback $comparator |
|
467 | + * A callback. This will be called during sorting to compare two DOMNode |
|
468 | + * objects. |
|
469 | + * @param boolean $modifyDOM |
|
470 | + * If this is TRUE, the sorted results will be inserted back into |
|
471 | + * the DOM at the position of the original first element. |
|
472 | + * |
|
473 | + * @return DOMQuery |
|
474 | + * This object. |
|
475 | + * @throws CSS\ParseException |
|
476 | + */ |
|
477 | + public function sort($comparator, $modifyDOM = false): Query |
|
478 | + { |
|
479 | + // Sort as an array. |
|
480 | + $list = iterator_to_array($this->matches); |
|
481 | + |
|
482 | + if (empty($list)) { |
|
483 | + return $this; |
|
484 | + } |
|
485 | + |
|
486 | + $oldFirst = $list[0]; |
|
487 | + |
|
488 | + usort($list, $comparator); |
|
489 | + |
|
490 | + // Copy back into SplObjectStorage. |
|
491 | + $found = new SplObjectStorage(); |
|
492 | + foreach ($list as $node) { |
|
493 | + $found->attach($node); |
|
494 | + } |
|
495 | + //$this->setMatches($found); |
|
496 | + |
|
497 | + |
|
498 | + // Do DOM modifications only if necessary. |
|
499 | + if ($modifyDOM) { |
|
500 | + $placeholder = $oldFirst->ownerDocument->createElement('_PLACEHOLDER_'); |
|
501 | + $placeholder = $oldFirst->parentNode->insertBefore($placeholder, $oldFirst); |
|
502 | + $len = count($list); |
|
503 | + for ($i = 0; $i < $len; ++$i) { |
|
504 | + $node = $list[$i]; |
|
505 | + $node = $node->parentNode->removeChild($node); |
|
506 | + $placeholder->parentNode->insertBefore($node, $placeholder); |
|
507 | + } |
|
508 | + $placeholder->parentNode->removeChild($placeholder); |
|
509 | + } |
|
510 | + |
|
511 | + return $this->inst($found, null); |
|
512 | + } |
|
513 | + |
|
514 | + /** |
|
515 | + * Get an item's index. |
|
516 | + * |
|
517 | + * Given a DOMElement, get the index from the matches. This is the |
|
518 | + * converse of {@link get()}. |
|
519 | + * |
|
520 | + * @param DOMElement $subject |
|
521 | + * The item to match. |
|
522 | + * |
|
523 | + * @return mixed |
|
524 | + * The index as an integer (if found), or boolean FALSE. Since 0 is a |
|
525 | + * valid index, you should use strong equality (===) to test.. |
|
526 | + * @see get() |
|
527 | + * @see is() |
|
528 | + */ |
|
529 | + public function index($subject) |
|
530 | + { |
|
531 | + $i = 0; |
|
532 | + foreach ($this->matches as $m) { |
|
533 | + if ($m === $subject) { |
|
534 | + return $i; |
|
535 | + } |
|
536 | + ++$i; |
|
537 | + } |
|
538 | + |
|
539 | + return false; |
|
540 | + } |
|
541 | + |
|
542 | + /** |
|
543 | + * The tag name of the first element in the list. |
|
544 | + * |
|
545 | + * This returns the tag name of the first element in the list of matches. If |
|
546 | + * the list is empty, an empty string will be used. |
|
547 | + * |
|
548 | + * @return string |
|
549 | + * The tag name of the first element in the list. |
|
550 | + * @see replaceWith() |
|
551 | + * @see replaceAll() |
|
552 | + */ |
|
553 | + public function tag() |
|
554 | + { |
|
555 | + return ($this->matches->count() > 0) ? $this->getFirstMatch()->tagName : ''; |
|
556 | + } |
|
557 | + |
|
558 | + /** |
|
559 | + * Revert to the previous set of matches. |
|
560 | + * |
|
561 | + * <b>DEPRECATED</b> Do not use. |
|
562 | + * |
|
563 | + * This will revert back to the last set of matches (before the last |
|
564 | + * "destructive" set of operations). This undoes any change made to the set of |
|
565 | + * matched objects. Functions like find() and filter() change the |
|
566 | + * list of matched objects. The end() function will revert back to the last set of |
|
567 | + * matched items. |
|
568 | + * |
|
569 | + * Note that functions that modify the document, but do not change the list of |
|
570 | + * matched objects, are not "destructive". Thus, calling append('something')->end() |
|
571 | + * will not undo the append() call. |
|
572 | + * |
|
573 | + * Only one level of changes is stored. Reverting beyond that will result in |
|
574 | + * an empty set of matches. Example: |
|
575 | + * |
|
576 | + * @code |
|
577 | + * // The line below returns the same thing as qp(document, 'p'); |
|
578 | + * qp(document, 'p')->find('div')->end(); |
|
579 | + * // This returns an empty array: |
|
580 | + * qp(document, 'p')->end(); |
|
581 | + * // This returns an empty array: |
|
582 | + * qp(document, 'p')->find('div')->find('span')->end()->end(); |
|
583 | + * @endcode |
|
584 | + * |
|
585 | + * The last one returns an empty array because only one level of changes is stored. |
|
586 | + * |
|
587 | + * @return DOMQuery |
|
588 | + * A DOMNode object reflecting the list of matches prior to the last destructive |
|
589 | + * operation. |
|
590 | + * @see andSelf() |
|
591 | + * @see add() |
|
592 | + * @deprecated This function will be removed. |
|
593 | + */ |
|
594 | + public function end() |
|
595 | + { |
|
596 | + // Note that this does not use setMatches because it must set the previous |
|
597 | + // set of matches to empty array. |
|
598 | + $this->matches = $this->last; |
|
599 | + $this->last = new SplObjectStorage(); |
|
600 | + |
|
601 | + return $this; |
|
602 | + } |
|
603 | + |
|
604 | + /** |
|
605 | + * Combine the current and previous set of matched objects. |
|
606 | + * |
|
607 | + * Example: |
|
608 | + * |
|
609 | + * @code |
|
610 | + * qp(document, 'p')->find('div')->andSelf(); |
|
611 | + * @endcode |
|
612 | + * |
|
613 | + * The code above will contain a list of all p elements and all div elements that |
|
614 | + * are beneath p elements. |
|
615 | + * |
|
616 | + * @return DOMQuery |
|
617 | + * A DOMNode object with the results of the last two "destructive" operations. |
|
618 | + * @see end(); |
|
619 | + * @see add() |
|
620 | + * @see end() |
|
621 | + */ |
|
622 | + public function andSelf() |
|
623 | + { |
|
624 | + // This is destructive, so we need to set $last: |
|
625 | + $last = $this->matches; |
|
626 | + |
|
627 | + foreach ($this->last as $item) { |
|
628 | + $this->matches->attach($item); |
|
629 | + } |
|
630 | + |
|
631 | + $this->last = $last; |
|
632 | + |
|
633 | + return $this; |
|
634 | + } |
|
635 | + |
|
636 | + /** |
|
637 | + * Set or get the markup for an element. |
|
638 | + * |
|
639 | + * If $markup is set, then the giving markup will be injected into each |
|
640 | + * item in the set. All other children of that node will be deleted, and this |
|
641 | + * new code will be the only child or children. The markup MUST BE WELL FORMED. |
|
642 | + * |
|
643 | + * If no markup is given, this will return a string representing the child |
|
644 | + * markup of the first node. |
|
645 | + * |
|
646 | + * <b>Important:</b> This differs from jQuery's html() function. This function |
|
647 | + * returns <i>the current node</i> and all of its children. jQuery returns only |
|
648 | + * the children. This means you do not need to do things like this: |
|
649 | + * @code$qp->parent()->html()@endcode. |
|
650 | + * |
|
651 | + * By default, this is HTML 4.01, not XHTML. Use {@link xml()} for XHTML. |
|
652 | + * |
|
653 | + * @param string $markup |
|
654 | + * The text to insert. |
|
655 | + * |
|
656 | + * @return mixed |
|
657 | + * A string if no markup was passed, or a DOMQuery if markup was passed. |
|
658 | + * @throws Exception |
|
659 | + * @throws QueryPath |
|
660 | + * @see xml() |
|
661 | + * @see text() |
|
662 | + * @see contents() |
|
663 | + */ |
|
664 | + public function html($markup = null) |
|
665 | + { |
|
666 | + if (isset($markup)) { |
|
667 | + if ($this->options['replace_entities']) { |
|
668 | + $markup = Entities::replaceAllEntities($markup); |
|
669 | + } |
|
670 | + |
|
671 | + // Parse the HTML and insert it into the DOM |
|
672 | + //$doc = DOMDocument::loadHTML($markup); |
|
673 | + $doc = $this->document->createDocumentFragment(); |
|
674 | + $doc->appendXML($markup); |
|
675 | + $this->removeChildren(); |
|
676 | + $this->append($doc); |
|
677 | + |
|
678 | + return $this; |
|
679 | + } |
|
680 | + $length = $this->matches->count(); |
|
681 | + if ($length === 0) { |
|
682 | + return null; |
|
683 | + } |
|
684 | + // Only return the first item -- that's what JQ does. |
|
685 | + $first = $this->getFirstMatch(); |
|
686 | + |
|
687 | + // Catch cases where first item is not a legit DOM object. |
|
688 | + if (! ($first instanceof DOMNode)) { |
|
689 | + return null; |
|
690 | + } |
|
691 | + |
|
692 | + // Added by eabrand. |
|
693 | + if (! $first->ownerDocument->documentElement) { |
|
694 | + return null; |
|
695 | + } |
|
696 | + |
|
697 | + if ($first instanceof DOMDocument || $first->isSameNode($first->ownerDocument->documentElement)) { |
|
698 | + return $this->document->saveHTML(); |
|
699 | + } |
|
700 | + |
|
701 | + // saveHTML cannot take a node and serialize it. |
|
702 | + return $this->document->saveXML($first); |
|
703 | + } |
|
704 | + |
|
705 | + /** |
|
706 | + * Write the QueryPath document to HTML5. |
|
707 | + * |
|
708 | + * See html() |
|
709 | + * |
|
710 | + * @param null $markup |
|
711 | + * |
|
712 | + * @return null|DOMQuery|string |
|
713 | + * @throws QueryPath |
|
714 | + * @throws \QueryPath\Exception |
|
715 | + */ |
|
716 | + public function html5($markup = null) |
|
717 | + { |
|
718 | + $html5 = new HTML5($this->options); |
|
719 | + |
|
720 | + // append HTML to existing |
|
721 | + if ($markup === null) { |
|
722 | + // Parse the HTML and insert it into the DOM |
|
723 | + $doc = $html5->loadHTMLFragment($markup); |
|
724 | + $this->removeChildren(); |
|
725 | + $this->append($doc); |
|
726 | + |
|
727 | + return $this; |
|
728 | + } |
|
729 | + |
|
730 | + $length = $this->count(); |
|
731 | + if ($length === 0) { |
|
732 | + return null; |
|
733 | + } |
|
734 | + // Only return the first item -- that's what JQ does. |
|
735 | + $first = $this->getFirstMatch(); |
|
736 | + |
|
737 | + // Catch cases where first item is not a legit DOM object. |
|
738 | + if (! ($first instanceof DOMNode)) { |
|
739 | + return null; |
|
740 | + } |
|
741 | + |
|
742 | + // Added by eabrand. |
|
743 | + if (! $first->ownerDocument->documentElement) { |
|
744 | + return null; |
|
745 | + } |
|
746 | + |
|
747 | + if ($first instanceof DOMDocument || $first->isSameNode($first->ownerDocument->documentElement)) { |
|
748 | + return $html5->saveHTML($this->document); //$this->document->saveHTML(); |
|
749 | + } |
|
750 | + |
|
751 | + return $html5->saveHTML($first); |
|
752 | + } |
|
753 | + |
|
754 | + /** |
|
755 | + * Fetch the HTML contents INSIDE of the first DOMQuery item. |
|
756 | + * |
|
757 | + * <b>This behaves the way jQuery's @codehtml()@endcode function behaves.</b> |
|
758 | + * |
|
759 | + * This gets all children of the first match in DOMQuery. |
|
760 | + * |
|
761 | + * Consider this fragment: |
|
762 | + * |
|
763 | + * @code |
|
764 | + * <div> |
|
765 | + * test <p>foo</p> test |
|
766 | + * </div> |
|
767 | + * @endcode |
|
768 | + * |
|
769 | + * We can retrieve just the contents of this code by doing something like |
|
770 | + * this: |
|
771 | + * @code |
|
772 | + * qp($xml, 'div')->innerHTML(); |
|
773 | + * @endcode |
|
774 | + * |
|
775 | + * This would return the following: |
|
776 | + * @codetest <p>foo</p> test@endcode |
|
777 | + * |
|
778 | + * @return string |
|
779 | + * Returns a string representation of the child nodes of the first |
|
780 | + * matched element. |
|
781 | + * @see html() |
|
782 | + * @see innerXML() |
|
783 | + * @see innerXHTML() |
|
784 | + * @since 2.0 |
|
785 | + */ |
|
786 | + public function innerHTML() |
|
787 | + { |
|
788 | + return $this->innerXML(); |
|
789 | + } |
|
790 | + |
|
791 | + /** |
|
792 | + * Fetch child (inner) nodes of the first match. |
|
793 | + * |
|
794 | + * This will return the children of the present match. For an example, |
|
795 | + * see {@link innerHTML()}. |
|
796 | + * |
|
797 | + * @return string |
|
798 | + * Returns a string of XHTML that represents the children of the present |
|
799 | + * node. |
|
800 | + * @see innerXML() |
|
801 | + * @see innerHTML() |
|
802 | + * @since 2.0 |
|
803 | + */ |
|
804 | + public function innerXHTML() |
|
805 | + { |
|
806 | + $length = $this->matches->count(); |
|
807 | + if ($length === 0) { |
|
808 | + return null; |
|
809 | + } |
|
810 | + // Only return the first item -- that's what JQ does. |
|
811 | + $first = $this->getFirstMatch(); |
|
812 | + |
|
813 | + // Catch cases where first item is not a legit DOM object. |
|
814 | + if (! ($first instanceof DOMNode)) { |
|
815 | + return null; |
|
816 | + } |
|
817 | + |
|
818 | + if (! $first->hasChildNodes()) { |
|
819 | + return ''; |
|
820 | + } |
|
821 | + |
|
822 | + $buffer = ''; |
|
823 | + foreach ($first->childNodes as $child) { |
|
824 | + $buffer .= $this->document->saveXML($child, LIBXML_NOEMPTYTAG); |
|
825 | + } |
|
826 | + |
|
827 | + return $buffer; |
|
828 | + } |
|
829 | + |
|
830 | + /** |
|
831 | + * Fetch child (inner) nodes of the first match. |
|
832 | + * |
|
833 | + * This will return the children of the present match. For an example, |
|
834 | + * see {@link innerHTML()}. |
|
835 | + * |
|
836 | + * @return string |
|
837 | + * Returns a string of XHTML that represents the children of the present |
|
838 | + * node. |
|
839 | + * @see innerXHTML() |
|
840 | + * @see innerHTML() |
|
841 | + * @since 2.0 |
|
842 | + */ |
|
843 | + public function innerXML() |
|
844 | + { |
|
845 | + $length = $this->matches->count(); |
|
846 | + if ($length === 0) { |
|
847 | + return null; |
|
848 | + } |
|
849 | + // Only return the first item -- that's what JQ does. |
|
850 | + $first = $this->getFirstMatch(); |
|
851 | + |
|
852 | + // Catch cases where first item is not a legit DOM object. |
|
853 | + if (! ($first instanceof DOMNode)) { |
|
854 | + return null; |
|
855 | + } |
|
856 | + |
|
857 | + if (! $first->hasChildNodes()) { |
|
858 | + return ''; |
|
859 | + } |
|
860 | + |
|
861 | + $buffer = ''; |
|
862 | + foreach ($first->childNodes as $child) { |
|
863 | + $buffer .= $this->document->saveXML($child); |
|
864 | + } |
|
865 | + |
|
866 | + return $buffer; |
|
867 | + } |
|
868 | + |
|
869 | + /** |
|
870 | + * Get child elements as an HTML5 string. |
|
871 | + * |
|
872 | + * TODO: This is a very simple alteration of innerXML. Do we need better |
|
873 | + * support? |
|
874 | + */ |
|
875 | + public function innerHTML5() |
|
876 | + { |
|
877 | + $length = $this->matches->count(); |
|
878 | + if ($length === 0) { |
|
879 | + return null; |
|
880 | + } |
|
881 | + // Only return the first item -- that's what JQ does. |
|
882 | + $first = $this->getFirstMatch(); |
|
883 | + |
|
884 | + // Catch cases where first item is not a legit DOM object. |
|
885 | + if (! ($first instanceof DOMNode)) { |
|
886 | + return null; |
|
887 | + } |
|
888 | + |
|
889 | + if (! $first->hasChildNodes()) { |
|
890 | + return ''; |
|
891 | + } |
|
892 | + |
|
893 | + $html5 = new HTML5($this->options); |
|
894 | + $buffer = ''; |
|
895 | + foreach ($first->childNodes as $child) { |
|
896 | + $buffer .= $html5->saveHTML($child); |
|
897 | + } |
|
898 | + |
|
899 | + return $buffer; |
|
900 | + } |
|
901 | + |
|
902 | + /** |
|
903 | + * Retrieve the text of each match and concatenate them with the given separator. |
|
904 | + * |
|
905 | + * This has the effect of looping through all children, retrieving their text |
|
906 | + * content, and then concatenating the text with a separator. |
|
907 | + * |
|
908 | + * @param string $sep |
|
909 | + * The string used to separate text items. The default is a comma followed by a |
|
910 | + * space. |
|
911 | + * @param boolean $filterEmpties |
|
912 | + * If this is true, empty items will be ignored. |
|
913 | + * |
|
914 | + * @return string |
|
915 | + * The text contents, concatenated together with the given separator between |
|
916 | + * every pair of items. |
|
917 | + * @see implode() |
|
918 | + * @see text() |
|
919 | + * @since 2.0 |
|
920 | + */ |
|
921 | + public function textImplode($sep = ', ', $filterEmpties = true): string |
|
922 | + { |
|
923 | + $tmp = []; |
|
924 | + foreach ($this->matches as $m) { |
|
925 | + $txt = $m->textContent; |
|
926 | + $trimmed = trim($txt); |
|
927 | + // If filter empties out, then we only add items that have content. |
|
928 | + if ($filterEmpties) { |
|
929 | + if (strlen($trimmed) > 0) { |
|
930 | + $tmp[] = $txt; |
|
931 | + } |
|
932 | + } // Else add all content, even if it's empty. |
|
933 | + else { |
|
934 | + $tmp[] = $txt; |
|
935 | + } |
|
936 | + } |
|
937 | + |
|
938 | + return implode($sep, $tmp); |
|
939 | + } |
|
940 | + |
|
941 | + /** |
|
942 | + * Get the text contents from just child elements. |
|
943 | + * |
|
944 | + * This is a specialized variant of textImplode() that implodes text for just the |
|
945 | + * child elements of the current element. |
|
946 | + * |
|
947 | + * @param string $separator |
|
948 | + * The separator that will be inserted between found text content. |
|
949 | + * |
|
950 | + * @return string |
|
951 | + * The concatenated values of all children. |
|
952 | + * @throws CSS\ParseException |
|
953 | + */ |
|
954 | + public function childrenText($separator = ' '): string |
|
955 | + { |
|
956 | + // Branch makes it non-destructive. |
|
957 | + return $this->branch()->xpath('descendant::text()')->textImplode($separator); |
|
958 | + } |
|
959 | + |
|
960 | + /** |
|
961 | + * Get or set the text contents of a node. |
|
962 | + * |
|
963 | + * @param string $text |
|
964 | + * If this is not NULL, this value will be set as the text of the node. It |
|
965 | + * will replace any existing content. |
|
966 | + * |
|
967 | + * @return mixed |
|
968 | + * A DOMQuery if $text is set, or the text content if no text |
|
969 | + * is passed in as a pram. |
|
970 | + * @see html() |
|
971 | + * @see xml() |
|
972 | + * @see contents() |
|
973 | + */ |
|
974 | + public function text($text = null) |
|
975 | + { |
|
976 | + if (isset($text)) { |
|
977 | + $this->removeChildren(); |
|
978 | + foreach ($this->matches as $m) { |
|
979 | + $m->appendChild($this->document->createTextNode($text)); |
|
980 | + } |
|
981 | + |
|
982 | + return $this; |
|
983 | + } |
|
984 | + // Returns all text as one string: |
|
985 | + $buf = ''; |
|
986 | + foreach ($this->matches as $m) { |
|
987 | + $buf .= $m->textContent; |
|
988 | + } |
|
989 | + |
|
990 | + return $buf; |
|
991 | + } |
|
992 | + |
|
993 | + /** |
|
994 | + * Get or set the text before each selected item. |
|
995 | + * |
|
996 | + * If $text is passed in, the text is inserted before each currently selected item. |
|
997 | + * |
|
998 | + * If no text is given, this will return the concatenated text after each selected element. |
|
999 | + * |
|
1000 | + * @code |
|
1001 | + * <?php |
|
1002 | + * $xml = '<?xml version="1.0"?><root>Foo<a>Bar</a><b/></root>'; |
|
1003 | + * |
|
1004 | + * // This will return 'Foo' |
|
1005 | + * qp($xml, 'a')->textBefore(); |
|
1006 | + * |
|
1007 | + * // This will insert 'Baz' right before <b/>. |
|
1008 | + * qp($xml, 'b')->textBefore('Baz'); |
|
1009 | + * ?> |
|
1010 | + * @endcode |
|
1011 | + * |
|
1012 | + * @param string $text |
|
1013 | + * If this is set, it will be inserted before each node in the current set of |
|
1014 | + * selected items. |
|
1015 | + * |
|
1016 | + * @return mixed |
|
1017 | + * Returns the DOMQuery object if $text was set, and returns a string (possibly empty) |
|
1018 | + * if no param is passed. |
|
1019 | + * @throws Exception |
|
1020 | + * @throws QueryPath |
|
1021 | + */ |
|
1022 | + public function textBefore($text = null) |
|
1023 | + { |
|
1024 | + if (isset($text)) { |
|
1025 | + $textNode = $this->document->createTextNode($text); |
|
1026 | + |
|
1027 | + return $this->before($textNode); |
|
1028 | + } |
|
1029 | + $buffer = ''; |
|
1030 | + foreach ($this->matches as $m) { |
|
1031 | + $p = $m; |
|
1032 | + while (isset($p->previousSibling) && $p->previousSibling->nodeType === XML_TEXT_NODE) { |
|
1033 | + $p = $p->previousSibling; |
|
1034 | + $buffer .= $p->textContent; |
|
1035 | + } |
|
1036 | + } |
|
1037 | + |
|
1038 | + return $buffer; |
|
1039 | + } |
|
1040 | + |
|
1041 | + public function textAfter($text = null) |
|
1042 | + { |
|
1043 | + if (isset($text)) { |
|
1044 | + $textNode = $this->document->createTextNode($text); |
|
1045 | + |
|
1046 | + return $this->after($textNode); |
|
1047 | + } |
|
1048 | + $buffer = ''; |
|
1049 | + foreach ($this->matches as $m) { |
|
1050 | + $n = $m; |
|
1051 | + while (isset($n->nextSibling) && $n->nextSibling->nodeType === XML_TEXT_NODE) { |
|
1052 | + $n = $n->nextSibling; |
|
1053 | + $buffer .= $n->textContent; |
|
1054 | + } |
|
1055 | + } |
|
1056 | + |
|
1057 | + return $buffer; |
|
1058 | + } |
|
1059 | + |
|
1060 | + /** |
|
1061 | + * Set or get the value of an element's 'value' attribute. |
|
1062 | + * |
|
1063 | + * The 'value' attribute is common in HTML form elements. This is a |
|
1064 | + * convenience function for accessing the values. Since this is not common |
|
1065 | + * task on the server side, this method may be removed in future releases. (It |
|
1066 | + * is currently provided for jQuery compatibility.) |
|
1067 | + * |
|
1068 | + * If a value is provided in the params, then the value will be set for all |
|
1069 | + * matches. If no params are given, then the value of the first matched element |
|
1070 | + * will be returned. This may be NULL. |
|
1071 | + * |
|
1072 | + * @deprecated Just use attr(). There's no reason to use this on the server. |
|
1073 | + * @see attr() |
|
1074 | + * |
|
1075 | + * @param string $value |
|
1076 | + * |
|
1077 | + * @return mixed |
|
1078 | + * Returns a DOMQuery if a string was passed in, and a string if no string |
|
1079 | + * was passed in. In the later case, an error will produce NULL. |
|
1080 | + */ |
|
1081 | + public function val($value = null) |
|
1082 | + { |
|
1083 | + if (isset($value)) { |
|
1084 | + $this->attr('value', $value); |
|
1085 | + |
|
1086 | + return $this; |
|
1087 | + } |
|
1088 | + |
|
1089 | + return $this->attr('value'); |
|
1090 | + } |
|
1091 | + |
|
1092 | + /** |
|
1093 | + * Set or get XHTML markup for an element or elements. |
|
1094 | + * |
|
1095 | + * This differs from {@link html()} in that it processes (and produces) |
|
1096 | + * strictly XML 1.0 compliant markup. |
|
1097 | + * |
|
1098 | + * Like {@link xml()} and {@link html()}, this functions as both a |
|
1099 | + * setter and a getter. |
|
1100 | + * |
|
1101 | + * This is a convenience function for fetching HTML in XML format. |
|
1102 | + * It does no processing of the markup (such as schema validation). |
|
1103 | + * |
|
1104 | + * @param string $markup |
|
1105 | + * A string containing XML data. |
|
1106 | + * |
|
1107 | + * @return mixed |
|
1108 | + * If markup is passed in, a DOMQuery is returned. If no markup is passed |
|
1109 | + * in, XML representing the first matched element is returned. |
|
1110 | + * @see html() |
|
1111 | + * @see innerXHTML() |
|
1112 | + */ |
|
1113 | + public function xhtml($markup = null) |
|
1114 | + { |
|
1115 | + |
|
1116 | + // XXX: This is a minor reworking of the original xml() method. |
|
1117 | + // This should be refactored, probably. |
|
1118 | + // See http://github.com/technosophos/querypath/issues#issue/10 |
|
1119 | + |
|
1120 | + $omit_xml_decl = $this->options['omit_xml_declaration']; |
|
1121 | + if ($markup === true) { |
|
1122 | + // Basically, we handle the special case where we don't |
|
1123 | + // want the XML declaration to be displayed. |
|
1124 | + $omit_xml_decl = true; |
|
1125 | + } elseif (isset($markup)) { |
|
1126 | + return $this->xml($markup); |
|
1127 | + } |
|
1128 | + |
|
1129 | + $length = $this->matches->count(); |
|
1130 | + if ($length === 0) { |
|
1131 | + return null; |
|
1132 | + } |
|
1133 | + |
|
1134 | + // Only return the first item -- that's what JQ does. |
|
1135 | + $first = $this->getFirstMatch(); |
|
1136 | + // Catch cases where first item is not a legit DOM object. |
|
1137 | + if (! ($first instanceof DOMNode)) { |
|
1138 | + return null; |
|
1139 | + } |
|
1140 | + |
|
1141 | + if ($first instanceof DOMDocument || $first->isSameNode($first->ownerDocument->documentElement)) { |
|
1142 | + // Has the unfortunate side-effect of stripping doctype. |
|
1143 | + //$text = ($omit_xml_decl ? $this->document->saveXML($first->ownerDocument->documentElement, LIBXML_NOEMPTYTAG) : $this->document->saveXML(NULL, LIBXML_NOEMPTYTAG)); |
|
1144 | + $text = $this->document->saveXML(null, LIBXML_NOEMPTYTAG); |
|
1145 | + } else { |
|
1146 | + $text = $this->document->saveXML($first, LIBXML_NOEMPTYTAG); |
|
1147 | + } |
|
1148 | + |
|
1149 | + // Issue #47: Using the old trick for removing the XML tag also removed the |
|
1150 | + // doctype. So we remove it with a regex: |
|
1151 | + if ($omit_xml_decl) { |
|
1152 | + $text = preg_replace('/<\?xml\s[^>]*\?>/', '', $text); |
|
1153 | + } |
|
1154 | + |
|
1155 | + // This is slightly lenient: It allows for cases where code incorrectly places content |
|
1156 | + // inside of these supposedly unary elements. |
|
1157 | + $unary = '/<(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)(?(?=\s)([^>\/]+))><\/[^>]*>/i'; |
|
1158 | + $text = preg_replace($unary, '<\\1\\2 />', $text); |
|
1159 | + |
|
1160 | + // Experimental: Support for enclosing CDATA sections with comments to be both XML compat |
|
1161 | + // and HTML 4/5 compat |
|
1162 | + $cdata = '/(<!\[CDATA\[|\]\]>)/i'; |
|
1163 | + $replace = $this->options['escape_xhtml_js_css_sections']; |
|
1164 | + $text = preg_replace($cdata, $replace, $text); |
|
1165 | + |
|
1166 | + return $text; |
|
1167 | + } |
|
1168 | + |
|
1169 | + /** |
|
1170 | + * Set or get the XML markup for an element or elements. |
|
1171 | + * |
|
1172 | + * Like {@link html()}, this functions in both a setter and a getter mode. |
|
1173 | + * |
|
1174 | + * In setter mode, the string passed in will be parsed and then appended to the |
|
1175 | + * elements wrapped by this DOMNode object.When in setter mode, this parses |
|
1176 | + * the XML using the DOMFragment parser. For that reason, an XML declaration |
|
1177 | + * is not necessary. |
|
1178 | + * |
|
1179 | + * In getter mode, the first element wrapped by this DOMNode object will be |
|
1180 | + * converted to an XML string and returned. |
|
1181 | + * |
|
1182 | + * @param string $markup |
|
1183 | + * A string containing XML data. |
|
1184 | + * |
|
1185 | + * @return mixed |
|
1186 | + * If markup is passed in, a DOMQuery is returned. If no markup is passed |
|
1187 | + * in, XML representing the first matched element is returned. |
|
1188 | + * @see xhtml() |
|
1189 | + * @see html() |
|
1190 | + * @see text() |
|
1191 | + * @see content() |
|
1192 | + * @see innerXML() |
|
1193 | + */ |
|
1194 | + public function xml($markup = null) |
|
1195 | + { |
|
1196 | + $omit_xml_decl = $this->options['omit_xml_declaration']; |
|
1197 | + if ($markup === true) { |
|
1198 | + // Basically, we handle the special case where we don't |
|
1199 | + // want the XML declaration to be displayed. |
|
1200 | + $omit_xml_decl = true; |
|
1201 | + } elseif (isset($markup)) { |
|
1202 | + if ($this->options['replace_entities']) { |
|
1203 | + $markup = Entities::replaceAllEntities($markup); |
|
1204 | + } |
|
1205 | + $doc = $this->document->createDocumentFragment(); |
|
1206 | + $doc->appendXML($markup); |
|
1207 | + $this->removeChildren(); |
|
1208 | + $this->append($doc); |
|
1209 | + |
|
1210 | + return $this; |
|
1211 | + } |
|
1212 | + $length = $this->matches->count(); |
|
1213 | + if ($length === 0) { |
|
1214 | + return null; |
|
1215 | + } |
|
1216 | + // Only return the first item -- that's what JQ does. |
|
1217 | + $first = $this->getFirstMatch(); |
|
1218 | + |
|
1219 | + // Catch cases where first item is not a legit DOM object. |
|
1220 | + if (! ($first instanceof DOMNode)) { |
|
1221 | + return null; |
|
1222 | + } |
|
1223 | + |
|
1224 | + if ($first instanceof DOMDocument || $first->isSameNode($first->ownerDocument->documentElement)) { |
|
1225 | + return ($omit_xml_decl ? $this->document->saveXML($first->ownerDocument->documentElement) : $this->document->saveXML()); |
|
1226 | + } |
|
1227 | + |
|
1228 | + return $this->document->saveXML($first); |
|
1229 | + } |
|
1230 | + |
|
1231 | + /** |
|
1232 | + * Send the XML document to the client. |
|
1233 | + * |
|
1234 | + * Write the document to a file path, if given, or |
|
1235 | + * to stdout (usually the client). |
|
1236 | + * |
|
1237 | + * This prints the entire document. |
|
1238 | + * |
|
1239 | + * @param string $path |
|
1240 | + * The path to the file into which the XML should be written. if |
|
1241 | + * this is NULL, data will be written to STDOUT, which is usually |
|
1242 | + * sent to the remote browser. |
|
1243 | + * @param int $options |
|
1244 | + * (As of QueryPath 2.1) Pass libxml options to the saving mechanism. |
|
1245 | + * |
|
1246 | + * @return DOMQuery |
|
1247 | + * The DOMQuery object, unmodified. |
|
1248 | + * @throws Exception |
|
1249 | + * In the event that a file cannot be written, an Exception will be thrown. |
|
1250 | + * @see innerXML() |
|
1251 | + * @see writeXHTML() |
|
1252 | + * @see xml() |
|
1253 | + */ |
|
1254 | + public function writeXML($path = null, $options = 0) |
|
1255 | + { |
|
1256 | + // Backwards compatibility fix for PHP8+ |
|
1257 | + if (is_null($options)) { |
|
1258 | + $options = 0; |
|
1259 | + } |
|
1260 | + |
|
1261 | + if ($path === null) { |
|
1262 | + print $this->document->saveXML(null, $options); |
|
1263 | + } else { |
|
1264 | + try { |
|
1265 | + set_error_handler([IOException::class, 'initializeFromError']); |
|
1266 | + $this->document->save($path, $options); |
|
1267 | + } catch (Exception $e) { |
|
1268 | + restore_error_handler(); |
|
1269 | + throw $e; |
|
1270 | + } |
|
1271 | + restore_error_handler(); |
|
1272 | + } |
|
1273 | + |
|
1274 | + return $this; |
|
1275 | + } |
|
1276 | + |
|
1277 | + /** |
|
1278 | + * Writes HTML to output. |
|
1279 | + * |
|
1280 | + * HTML is formatted as HTML 4.01, without strict XML unary tags. This is for |
|
1281 | + * legacy HTML content. Modern XHTML should be written using {@link toXHTML()}. |
|
1282 | + * |
|
1283 | + * Write the document to stdout (usually the client) or to a file. |
|
1284 | + * |
|
1285 | + * @param string $path |
|
1286 | + * The path to the file into which the XML should be written. if |
|
1287 | + * this is NULL, data will be written to STDOUT, which is usually |
|
1288 | + * sent to the remote browser. |
|
1289 | + * |
|
1290 | + * @return DOMQuery |
|
1291 | + * The DOMQuery object, unmodified. |
|
1292 | + * @throws Exception |
|
1293 | + * In the event that a file cannot be written, an Exception will be thrown. |
|
1294 | + * @see innerHTML() |
|
1295 | + * @see html() |
|
1296 | + */ |
|
1297 | + public function writeHTML($path = null) |
|
1298 | + { |
|
1299 | + if ($path === null) { |
|
1300 | + print $this->document->saveHTML(); |
|
1301 | + } else { |
|
1302 | + try { |
|
1303 | + set_error_handler(['\QueryPath\ParseException', 'initializeFromError']); |
|
1304 | + $this->document->saveHTMLFile($path); |
|
1305 | + } catch (Exception $e) { |
|
1306 | + restore_error_handler(); |
|
1307 | + throw $e; |
|
1308 | + } |
|
1309 | + restore_error_handler(); |
|
1310 | + } |
|
1311 | + |
|
1312 | + return $this; |
|
1313 | + } |
|
1314 | + |
|
1315 | + /** |
|
1316 | + * Write the document to HTML5. |
|
1317 | + * |
|
1318 | + * This works the same as the other write* functions, but it encodes the output |
|
1319 | + * as HTML5 with UTF-8. |
|
1320 | + * |
|
1321 | + * @throws Exception |
|
1322 | + * In the event that a file cannot be written, an Exception will be thrown. |
|
1323 | + * @see innerHTML5() |
|
1324 | + * @see html5() |
|
1325 | + */ |
|
1326 | + public function writeHTML5($path = null) |
|
1327 | + { |
|
1328 | + $html5 = new HTML5(); |
|
1329 | + if ($path === null) { |
|
1330 | + // Print the document to stdout. |
|
1331 | + print $html5->saveHTML($this->document); |
|
1332 | + |
|
1333 | + return; |
|
1334 | + } |
|
1335 | + |
|
1336 | + $html5->save($this->document, $path); |
|
1337 | + } |
|
1338 | + |
|
1339 | + /** |
|
1340 | + * Write an XHTML file to output. |
|
1341 | + * |
|
1342 | + * Typically, you should use this instead of {@link writeHTML()}. |
|
1343 | + * |
|
1344 | + * Currently, this functions identically to {@link toXML()} <i>except that</i> |
|
1345 | + * it always uses closing tags (e.g. always @code<script></script>@endcode, |
|
1346 | + * never @code<script/>@endcode). It will |
|
1347 | + * write the file as well-formed XML. No XHTML schema validation is done. |
|
1348 | + * |
|
1349 | + * @param string $path |
|
1350 | + * The filename of the file to write to. |
|
1351 | + * |
|
1352 | + * @return DOMQuery |
|
1353 | + * Returns the DOMQuery, unmodified. |
|
1354 | + * @throws Exception |
|
1355 | + * In the event that the output file cannot be written, an exception is |
|
1356 | + * thrown. |
|
1357 | + * @see innerXHTML() |
|
1358 | + * @see xhtml() |
|
1359 | + * @see writeXML() |
|
1360 | + * @see xml() |
|
1361 | + * @see writeHTML() |
|
1362 | + * @since 2.0 |
|
1363 | + */ |
|
1364 | + public function writeXHTML($path = null) |
|
1365 | + { |
|
1366 | + return $this->writeXML($path, LIBXML_NOEMPTYTAG); |
|
1367 | + } |
|
1368 | + |
|
1369 | + /** |
|
1370 | + * Branch the base DOMQuery into another one with the same matches. |
|
1371 | + * |
|
1372 | + * This function makes a copy of the DOMQuery object, but keeps the new copy |
|
1373 | + * (initially) pointed at the same matches. This object can then be queried without |
|
1374 | + * changing the original DOMQuery. However, changes to the elements inside of this |
|
1375 | + * DOMQuery will show up in the DOMQuery from which it is branched. |
|
1376 | + * |
|
1377 | + * Compare this operation with {@link cloneAll()}. The cloneAll() call takes |
|
1378 | + * the current DOMNode object and makes a copy of all of its matches. You continue |
|
1379 | + * to operate on the same DOMNode object, but the elements inside of the DOMQuery |
|
1380 | + * are copies of those before the call to cloneAll(). |
|
1381 | + * |
|
1382 | + * This, on the other hand, copies <i>the DOMQuery</i>, but keeps valid |
|
1383 | + * references to the document and the wrapped elements. A new query branch is |
|
1384 | + * created, but any changes will be written back to the same document. |
|
1385 | + * |
|
1386 | + * In practice, this comes in handy when you want to do multiple queries on a part |
|
1387 | + * of the document, but then return to a previous set of matches. (see {@link QPTPL} |
|
1388 | + * for examples of this in practice). |
|
1389 | + * |
|
1390 | + * Example: |
|
1391 | + * |
|
1392 | + * @code |
|
1393 | + * <?php |
|
1394 | + * $qp = qp( QueryPath::HTML_STUB); |
|
1395 | + * $branch = $qp->branch(); |
|
1396 | + * $branch->find('title')->text('Title'); |
|
1397 | + * $qp->find('body')->text('This is the body')->writeHTML; |
|
1398 | + * ?> |
|
1399 | + * @endcode |
|
1400 | + * |
|
1401 | + * Notice that in the code, each of the DOMQuery objects is doing its own |
|
1402 | + * query. However, both are modifying the same document. The result of the above |
|
1403 | + * would look something like this: |
|
1404 | + * |
|
1405 | + * @code |
|
1406 | + * <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> |
|
1407 | + * <html xmlns="http://www.w3.org/1999/xhtml"> |
|
1408 | + * <head> |
|
1409 | + * <meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta> |
|
1410 | + * <title>Title</title> |
|
1411 | + * </head> |
|
1412 | + * <body>This is the body</body> |
|
1413 | + * </html> |
|
1414 | + * @endcode |
|
1415 | + * |
|
1416 | + * Notice that while $qp and $banch were performing separate queries, they |
|
1417 | + * both modified the same document. |
|
1418 | + * |
|
1419 | + * In jQuery or a browser-based solution, you generally do not need a branching |
|
1420 | + * function because there is (implicitly) only one document. In QueryPath, there |
|
1421 | + * is no implicit document. Every document must be explicitly specified (and, |
|
1422 | + * in most cases, parsed -- which is costly). Branching makes it possible to |
|
1423 | + * work on one document with multiple DOMNode objects. |
|
1424 | + * |
|
1425 | + * @param string $selector |
|
1426 | + * If a selector is passed in, an additional {@link find()} will be executed |
|
1427 | + * on the branch before it is returned. (Added in QueryPath 2.0.) |
|
1428 | + * |
|
1429 | + * @return DOMQuery |
|
1430 | + * A copy of the DOMQuery object that points to the same set of elements that |
|
1431 | + * the original DOMQuery was pointing to. |
|
1432 | + * @throws CSS\ParseException |
|
1433 | + * @see cloneAll() |
|
1434 | + * @see find() |
|
1435 | + * @since 1.1 |
|
1436 | + */ |
|
1437 | + public function branch($selector = null) |
|
1438 | + { |
|
1439 | + $temp = QueryPath::with($this->matches, null, $this->options); |
|
1440 | + //if (isset($selector)) $temp->find($selector); |
|
1441 | + $temp->document = $this->document; |
|
1442 | + if (isset($selector)) { |
|
1443 | + $temp->findInPlace($selector); |
|
1444 | + } |
|
1445 | + |
|
1446 | + return $temp; |
|
1447 | + } |
|
1448 | + |
|
1449 | + /** |
|
1450 | + * @param $matches |
|
1451 | + * @param $selector |
|
1452 | + * |
|
1453 | + * @return DOMQuery |
|
1454 | + * @throws CSS\ParseException |
|
1455 | + */ |
|
1456 | + protected function inst($matches, $selector): Query |
|
1457 | + { |
|
1458 | + $dolly = clone $this; |
|
1459 | + $dolly->setMatches($matches); |
|
1460 | + |
|
1461 | + if (isset($selector)) { |
|
1462 | + $dolly->findInPlace($selector); |
|
1463 | + } |
|
1464 | + |
|
1465 | + return $dolly; |
|
1466 | + } |
|
1467 | + |
|
1468 | + /** |
|
1469 | + * Perform a deep clone of each node in the DOMQuery. |
|
1470 | + * |
|
1471 | + * @attention |
|
1472 | + * This is an in-place modification of the current QueryPath object. |
|
1473 | + * |
|
1474 | + * This does not clone the DOMQuery object, but instead clones the |
|
1475 | + * list of nodes wrapped by the DOMQuery. Every element is deeply |
|
1476 | + * cloned. |
|
1477 | + * |
|
1478 | + * This method is analogous to jQuery's clone() method. |
|
1479 | + * |
|
1480 | + * This is a destructive operation, which means that end() will revert |
|
1481 | + * the list back to the clone's original. |
|
1482 | + * @return DOMQuery |
|
1483 | + * @see qp() |
|
1484 | + */ |
|
1485 | + public function cloneAll(): Query |
|
1486 | + { |
|
1487 | + $found = new SplObjectStorage(); |
|
1488 | + foreach ($this->matches as $m) { |
|
1489 | + $found->attach($m->cloneNode(true)); |
|
1490 | + } |
|
1491 | + $this->setMatches($found); |
|
1492 | + |
|
1493 | + return $this; |
|
1494 | + } |
|
1495 | + |
|
1496 | + /** |
|
1497 | + * Clone the DOMQuery. |
|
1498 | + * |
|
1499 | + * This makes a deep clone of the elements inside of the DOMQuery. |
|
1500 | + * |
|
1501 | + * This clones only the QueryPathImpl, not all of the decorators. The |
|
1502 | + * clone operator in PHP should handle the cloning of the decorators. |
|
1503 | + */ |
|
1504 | + public function __clone() |
|
1505 | + { |
|
1506 | + //XXX: Should we clone the document? |
|
1507 | + |
|
1508 | + // Make sure we clone the kids. |
|
1509 | + $this->cloneAll(); |
|
1510 | + } |
|
1511 | + |
|
1512 | + /** |
|
1513 | + * Call extension methods. |
|
1514 | + * |
|
1515 | + * This function is used to invoke extension methods. It searches the |
|
1516 | + * registered extenstensions for a matching function name. If one is found, |
|
1517 | + * it is executed with the arguments in the $arguments array. |
|
1518 | + * |
|
1519 | + * @throws ReflectionException |
|
1520 | + * @throws QueryPath::Exception |
|
1521 | + * An exception is thrown if a non-existent method is called. |
|
1522 | + * @throws Exception |
|
1523 | + */ |
|
1524 | + public function __call($name, $arguments) |
|
1525 | + { |
|
1526 | + |
|
1527 | + if (! ExtensionRegistry::$useRegistry) { |
|
1528 | + throw new Exception("No method named $name found (Extensions disabled)."); |
|
1529 | + } |
|
1530 | + |
|
1531 | + // Loading of extensions is deferred until the first time a |
|
1532 | + // non-core method is called. This makes constructing faster, but it |
|
1533 | + // may make the first invocation of __call() slower (if there are |
|
1534 | + // enough extensions.) |
|
1535 | + // |
|
1536 | + // The main reason for moving this out of the constructor is that most |
|
1537 | + // new DOMQuery instances do not use extensions. Charging qp() calls |
|
1538 | + // with the additional hit is not a good idea. |
|
1539 | + // |
|
1540 | + // Also, this will at least limit the number of circular references. |
|
1541 | + if (empty($this->ext)) { |
|
1542 | + // Load the registry |
|
1543 | + $this->ext = ExtensionRegistry::getExtensions($this); |
|
1544 | + } |
|
1545 | + |
|
1546 | + // Note that an empty ext registry indicates that extensions are disabled. |
|
1547 | + if (! empty($this->ext) && ExtensionRegistry::hasMethod($name)) { |
|
1548 | + $owner = ExtensionRegistry::getMethodClass($name); |
|
1549 | + $method = new ReflectionMethod($owner, $name); |
|
1550 | + |
|
1551 | + return $method->invokeArgs($this->ext[$owner], $arguments); |
|
1552 | + } |
|
1553 | + throw new Exception("No method named $name found. Possibly missing an extension."); |
|
1554 | + } |
|
1555 | + |
|
1556 | + /** |
|
1557 | + * Get an iterator for the matches in this object. |
|
1558 | + * |
|
1559 | + * @return Iterable |
|
1560 | + * Returns an iterator. |
|
1561 | + */ |
|
1562 | + public function getIterator(): Traversable |
|
1563 | + { |
|
1564 | + $i = new QueryPathIterator($this->matches); |
|
1565 | + $i->options = $this->options; |
|
1566 | + |
|
1567 | + return $i; |
|
1568 | + } |
|
1569 | 1569 | } |
@@ -196,7 +196,7 @@ discard block |
||
196 | 196 | $xpath = new DOMXPath($this->document); |
197 | 197 | |
198 | 198 | // Register a default namespace. |
199 | - if (! empty($options['namespace_prefix']) && ! empty($options['namespace_uri'])) { |
|
199 | + if (!empty($options['namespace_prefix']) && !empty($options['namespace_uri'])) { |
|
200 | 200 | $xpath->registerNamespace($options['namespace_prefix'], $options['namespace_uri']); |
201 | 201 | } |
202 | 202 | |
@@ -286,7 +286,7 @@ discard block |
||
286 | 286 | return ($this->count() > $index) ? $this->getNthMatch($index) : null; |
287 | 287 | } |
288 | 288 | // Retain support for legacy. |
289 | - if (! $asObject) { |
|
289 | + if (!$asObject) { |
|
290 | 290 | $matches = []; |
291 | 291 | foreach ($this->matches as $m) { |
292 | 292 | $matches[] = $m; |
@@ -399,7 +399,7 @@ discard block |
||
399 | 399 | $matches = []; |
400 | 400 | preg_match($regex, $data, $matches); |
401 | 401 | |
402 | - if (! empty($matches)) { |
|
402 | + if (!empty($matches)) { |
|
403 | 403 | $result = [ |
404 | 404 | 'mime' => $matches[1] . '/' . $matches[2], |
405 | 405 | 'data' => base64_decode($matches[3]), |
@@ -685,12 +685,12 @@ discard block |
||
685 | 685 | $first = $this->getFirstMatch(); |
686 | 686 | |
687 | 687 | // Catch cases where first item is not a legit DOM object. |
688 | - if (! ($first instanceof DOMNode)) { |
|
688 | + if (!($first instanceof DOMNode)) { |
|
689 | 689 | return null; |
690 | 690 | } |
691 | 691 | |
692 | 692 | // Added by eabrand. |
693 | - if (! $first->ownerDocument->documentElement) { |
|
693 | + if (!$first->ownerDocument->documentElement) { |
|
694 | 694 | return null; |
695 | 695 | } |
696 | 696 | |
@@ -735,12 +735,12 @@ discard block |
||
735 | 735 | $first = $this->getFirstMatch(); |
736 | 736 | |
737 | 737 | // Catch cases where first item is not a legit DOM object. |
738 | - if (! ($first instanceof DOMNode)) { |
|
738 | + if (!($first instanceof DOMNode)) { |
|
739 | 739 | return null; |
740 | 740 | } |
741 | 741 | |
742 | 742 | // Added by eabrand. |
743 | - if (! $first->ownerDocument->documentElement) { |
|
743 | + if (!$first->ownerDocument->documentElement) { |
|
744 | 744 | return null; |
745 | 745 | } |
746 | 746 | |
@@ -811,11 +811,11 @@ discard block |
||
811 | 811 | $first = $this->getFirstMatch(); |
812 | 812 | |
813 | 813 | // Catch cases where first item is not a legit DOM object. |
814 | - if (! ($first instanceof DOMNode)) { |
|
814 | + if (!($first instanceof DOMNode)) { |
|
815 | 815 | return null; |
816 | 816 | } |
817 | 817 | |
818 | - if (! $first->hasChildNodes()) { |
|
818 | + if (!$first->hasChildNodes()) { |
|
819 | 819 | return ''; |
820 | 820 | } |
821 | 821 | |
@@ -850,11 +850,11 @@ discard block |
||
850 | 850 | $first = $this->getFirstMatch(); |
851 | 851 | |
852 | 852 | // Catch cases where first item is not a legit DOM object. |
853 | - if (! ($first instanceof DOMNode)) { |
|
853 | + if (!($first instanceof DOMNode)) { |
|
854 | 854 | return null; |
855 | 855 | } |
856 | 856 | |
857 | - if (! $first->hasChildNodes()) { |
|
857 | + if (!$first->hasChildNodes()) { |
|
858 | 858 | return ''; |
859 | 859 | } |
860 | 860 | |
@@ -882,11 +882,11 @@ discard block |
||
882 | 882 | $first = $this->getFirstMatch(); |
883 | 883 | |
884 | 884 | // Catch cases where first item is not a legit DOM object. |
885 | - if (! ($first instanceof DOMNode)) { |
|
885 | + if (!($first instanceof DOMNode)) { |
|
886 | 886 | return null; |
887 | 887 | } |
888 | 888 | |
889 | - if (! $first->hasChildNodes()) { |
|
889 | + if (!$first->hasChildNodes()) { |
|
890 | 890 | return ''; |
891 | 891 | } |
892 | 892 | |
@@ -1030,7 +1030,7 @@ discard block |
||
1030 | 1030 | foreach ($this->matches as $m) { |
1031 | 1031 | $p = $m; |
1032 | 1032 | while (isset($p->previousSibling) && $p->previousSibling->nodeType === XML_TEXT_NODE) { |
1033 | - $p = $p->previousSibling; |
|
1033 | + $p = $p->previousSibling; |
|
1034 | 1034 | $buffer .= $p->textContent; |
1035 | 1035 | } |
1036 | 1036 | } |
@@ -1049,7 +1049,7 @@ discard block |
||
1049 | 1049 | foreach ($this->matches as $m) { |
1050 | 1050 | $n = $m; |
1051 | 1051 | while (isset($n->nextSibling) && $n->nextSibling->nodeType === XML_TEXT_NODE) { |
1052 | - $n = $n->nextSibling; |
|
1052 | + $n = $n->nextSibling; |
|
1053 | 1053 | $buffer .= $n->textContent; |
1054 | 1054 | } |
1055 | 1055 | } |
@@ -1134,7 +1134,7 @@ discard block |
||
1134 | 1134 | // Only return the first item -- that's what JQ does. |
1135 | 1135 | $first = $this->getFirstMatch(); |
1136 | 1136 | // Catch cases where first item is not a legit DOM object. |
1137 | - if (! ($first instanceof DOMNode)) { |
|
1137 | + if (!($first instanceof DOMNode)) { |
|
1138 | 1138 | return null; |
1139 | 1139 | } |
1140 | 1140 | |
@@ -1217,7 +1217,7 @@ discard block |
||
1217 | 1217 | $first = $this->getFirstMatch(); |
1218 | 1218 | |
1219 | 1219 | // Catch cases where first item is not a legit DOM object. |
1220 | - if (! ($first instanceof DOMNode)) { |
|
1220 | + if (!($first instanceof DOMNode)) { |
|
1221 | 1221 | return null; |
1222 | 1222 | } |
1223 | 1223 | |
@@ -1524,7 +1524,7 @@ discard block |
||
1524 | 1524 | public function __call($name, $arguments) |
1525 | 1525 | { |
1526 | 1526 | |
1527 | - if (! ExtensionRegistry::$useRegistry) { |
|
1527 | + if (!ExtensionRegistry::$useRegistry) { |
|
1528 | 1528 | throw new Exception("No method named $name found (Extensions disabled)."); |
1529 | 1529 | } |
1530 | 1530 | |
@@ -1544,7 +1544,7 @@ discard block |
||
1544 | 1544 | } |
1545 | 1545 | |
1546 | 1546 | // Note that an empty ext registry indicates that extensions are disabled. |
1547 | - if (! empty($this->ext) && ExtensionRegistry::hasMethod($name)) { |
|
1547 | + if (!empty($this->ext) && ExtensionRegistry::hasMethod($name)) { |
|
1548 | 1548 | $owner = ExtensionRegistry::getMethodClass($name); |
1549 | 1549 | $method = new ReflectionMethod($owner, $name); |
1550 | 1550 |
@@ -23,528 +23,528 @@ |
||
23 | 23 | abstract class DOM implements Query, IteratorAggregate, Countable |
24 | 24 | { |
25 | 25 | |
26 | - /** |
|
27 | - * The array of matches. |
|
28 | - */ |
|
29 | - protected $matches = []; |
|
30 | - |
|
31 | - /** |
|
32 | - * Default parser flags. |
|
33 | - * |
|
34 | - * These are flags that will be used if no global or local flags override them. |
|
35 | - * |
|
36 | - * @since 2.0 |
|
37 | - */ |
|
38 | - public const DEFAULT_PARSER_FLAGS = null; |
|
39 | - |
|
40 | - public const JS_CSS_ESCAPE_CDATA = '\\1'; |
|
41 | - public const JS_CSS_ESCAPE_CDATA_CCOMMENT = '/* \\1 */'; |
|
42 | - public const JS_CSS_ESCAPE_CDATA_DOUBLESLASH = '// \\1'; |
|
43 | - public const JS_CSS_ESCAPE_NONE = ''; |
|
44 | - |
|
45 | - protected $errTypes = 771; //E_ERROR; | E_USER_ERROR; |
|
46 | - |
|
47 | - protected $document; |
|
48 | - /** |
|
49 | - * The base DOMDocument. |
|
50 | - */ |
|
51 | - protected $options = [ |
|
52 | - 'parser_flags' => null, |
|
53 | - 'omit_xml_declaration' => false, |
|
54 | - 'replace_entities' => false, |
|
55 | - 'exception_level' => 771, // E_ERROR | E_USER_ERROR | E_USER_WARNING | E_WARNING |
|
56 | - 'ignore_parser_warnings' => false, |
|
57 | - 'escape_xhtml_js_css_sections' => self::JS_CSS_ESCAPE_CDATA_CCOMMENT, |
|
58 | - ]; |
|
59 | - |
|
60 | - /** |
|
61 | - * Constructor. |
|
62 | - * |
|
63 | - * Typically, a new DOMQuery is created by QueryPath::with(), QueryPath::withHTML(), |
|
64 | - * qp(), or htmlqp(). |
|
65 | - * |
|
66 | - * @param mixed $document |
|
67 | - * A document-like object. |
|
68 | - * @param string $string |
|
69 | - * A CSS 3 Selector |
|
70 | - * @param array $options |
|
71 | - * An associative array of options. |
|
72 | - * |
|
73 | - * @throws Exception |
|
74 | - * @see qp() |
|
75 | - */ |
|
76 | - public function __construct($document = null, $string = '', $options = []) |
|
77 | - { |
|
78 | - // Backwards compatibility fix for PHP8+ |
|
79 | - if (is_null($string)) { |
|
80 | - $string = ''; |
|
81 | - } |
|
82 | - |
|
83 | - $string = trim($string); |
|
84 | - $this->options = $options + Options::get() + $this->options; |
|
85 | - |
|
86 | - $parser_flags = $options['parser_flags'] ?? self::DEFAULT_PARSER_FLAGS; |
|
87 | - if (! empty($this->options['ignore_parser_warnings'])) { |
|
88 | - // Don't convert parser warnings into exceptions. |
|
89 | - $this->errTypes = 257; //E_ERROR | E_USER_ERROR; |
|
90 | - } elseif (isset($this->options['exception_level'])) { |
|
91 | - // Set the error level at which exceptions will be thrown. By default, |
|
92 | - // QueryPath will throw exceptions for |
|
93 | - // E_ERROR | E_USER_ERROR | E_WARNING | E_USER_WARNING. |
|
94 | - $this->errTypes = $this->options['exception_level']; |
|
95 | - } |
|
96 | - |
|
97 | - // Empty: Just create an empty QP. |
|
98 | - if (empty($document)) { |
|
99 | - $this->document = isset($this->options['encoding']) ? new DOMDocument( |
|
100 | - '1.0', |
|
101 | - $this->options['encoding'] |
|
102 | - ) : new DOMDocument(); |
|
103 | - $this->setMatches(new SplObjectStorage()); |
|
104 | - } // Figure out if document is DOM, HTML/XML, or a filename |
|
105 | - elseif (is_object($document)) { |
|
106 | - |
|
107 | - // This is the most frequent object type. |
|
108 | - if ($document instanceof SplObjectStorage) { |
|
109 | - $this->matches = $document; |
|
110 | - if ($document->count() !== 0) { |
|
111 | - $first = $this->getFirstMatch(); |
|
112 | - if (! empty($first->ownerDocument)) { |
|
113 | - $this->document = $first->ownerDocument; |
|
114 | - } |
|
115 | - } |
|
116 | - } elseif ($document instanceof self) { |
|
117 | - //$this->matches = $document->get(NULL, TRUE); |
|
118 | - $this->setMatches($document->get(null, true)); |
|
119 | - if ($this->matches->count() > 0) { |
|
120 | - $this->document = $this->getFirstMatch()->ownerDocument; |
|
121 | - } |
|
122 | - } elseif ($document instanceof DOMDocument) { |
|
123 | - $this->document = $document; |
|
124 | - //$this->matches = $this->matches($document->documentElement); |
|
125 | - $this->setMatches($document->documentElement); |
|
126 | - } elseif ($document instanceof DOMNode) { |
|
127 | - $this->document = $document->ownerDocument; |
|
128 | - //$this->matches = array($document); |
|
129 | - $this->setMatches($document); |
|
130 | - } elseif ($document instanceof HTML5) { |
|
131 | - $this->document = $document; |
|
132 | - $this->setMatches($document->documentElement); |
|
133 | - } elseif ($document instanceof SimpleXMLElement) { |
|
134 | - $import = dom_import_simplexml($document); |
|
135 | - $this->document = $import->ownerDocument; |
|
136 | - //$this->matches = array($import); |
|
137 | - $this->setMatches($import); |
|
138 | - } else { |
|
139 | - throw new Exception('Unsupported class type: ' . get_class($document)); |
|
140 | - } |
|
141 | - } elseif (is_array($document)) { |
|
142 | - //trigger_error('Detected deprecated array support', E_USER_NOTICE); |
|
143 | - if (! empty($document) && $document[0] instanceof DOMNode) { |
|
144 | - $found = new SplObjectStorage(); |
|
145 | - foreach ($document as $item) { |
|
146 | - $found->attach($item); |
|
147 | - } |
|
148 | - //$this->matches = $found; |
|
149 | - $this->setMatches($found); |
|
150 | - $this->document = $this->getFirstMatch()->ownerDocument; |
|
151 | - } |
|
152 | - } elseif ($this->isXMLish($document)) { |
|
153 | - // $document is a string with XML |
|
154 | - $this->document = $this->parseXMLString($document); |
|
155 | - $this->setMatches($this->document->documentElement); |
|
156 | - } else { |
|
157 | - |
|
158 | - // $document is a filename |
|
159 | - $context = empty($options['context']) ? null : $options['context']; |
|
160 | - $this->document = $this->parseXMLFile($document, $parser_flags, $context); |
|
161 | - $this->setMatches($this->document->documentElement); |
|
162 | - } |
|
163 | - |
|
164 | - // Globally set the output option. |
|
165 | - $this->document->formatOutput = true; |
|
166 | - if (isset($this->options['format_output']) && $this->options['format_output'] === false) { |
|
167 | - $this->document->formatOutput = false; |
|
168 | - } |
|
169 | - |
|
170 | - // Do a find if the second param was set. |
|
171 | - if (strlen($string) > 0) { |
|
172 | - // We don't issue a find because that creates a new DOMQuery. |
|
173 | - //$this->find($string); |
|
174 | - |
|
175 | - $query = new DOMTraverser($this->matches); |
|
176 | - $query->find($string); |
|
177 | - $this->setMatches($query->matches()); |
|
178 | - } |
|
179 | - } |
|
180 | - |
|
181 | - private function parseXMLString($string, $flags = 0) |
|
182 | - { |
|
183 | - $document = new DOMDocument('1.0'); |
|
184 | - $lead = strtolower(substr($string, 0, 5)); // <?xml |
|
185 | - try { |
|
186 | - set_error_handler([ParseException::class, 'initializeFromError'], $this->errTypes); |
|
187 | - |
|
188 | - if (isset($this->options['convert_to_encoding'])) { |
|
189 | - // Is there another way to do this? |
|
190 | - |
|
191 | - $from_enc = $this->options['convert_from_encoding'] ?? 'auto'; |
|
192 | - $to_enc = $this->options['convert_to_encoding']; |
|
193 | - |
|
194 | - if (function_exists('mb_convert_encoding')) { |
|
195 | - $string = mb_convert_encoding($string, $to_enc, $from_enc); |
|
196 | - } |
|
197 | - |
|
198 | - } |
|
199 | - |
|
200 | - // This is to avoid cases where low ascii digits have slipped into HTML. |
|
201 | - // AFAIK, it should not adversly effect UTF-8 documents. |
|
202 | - if (! empty($this->options['strip_low_ascii'])) { |
|
203 | - $string = filter_var($string, FILTER_UNSAFE_RAW, FILTER_FLAG_ENCODE_LOW); |
|
204 | - } |
|
205 | - |
|
206 | - // Allow users to override parser settings. |
|
207 | - $useParser = ''; |
|
208 | - if (! empty($this->options['use_parser'])) { |
|
209 | - $useParser = strtolower($this->options['use_parser']); |
|
210 | - } |
|
211 | - |
|
212 | - // If HTML parser is requested, we use it. |
|
213 | - if ($useParser === 'html') { |
|
214 | - $document->loadHTML($string); |
|
215 | - } // Parse as XML if it looks like XML, or if XML parser is requested. |
|
216 | - elseif ($lead === '<?xml' || $useParser === 'xml') { |
|
217 | - if ($this->options['replace_entities']) { |
|
218 | - $string = Entities::replaceAllEntities($string); |
|
219 | - } |
|
220 | - $document->loadXML($string, $flags); |
|
221 | - } // In all other cases, we try the HTML parser. |
|
222 | - else { |
|
223 | - $document->loadHTML($string); |
|
224 | - } |
|
225 | - } // Emulate 'finally' behavior. |
|
226 | - catch (Exception $e) { |
|
227 | - restore_error_handler(); |
|
228 | - throw $e; |
|
229 | - } |
|
230 | - restore_error_handler(); |
|
231 | - |
|
232 | - if (empty($document)) { |
|
233 | - throw new ParseException('Unknown parser exception.'); |
|
234 | - } |
|
235 | - |
|
236 | - return $document; |
|
237 | - } |
|
238 | - |
|
239 | - /** |
|
240 | - * EXPERT: Be very, very careful using this. |
|
241 | - * A utility function for setting the current set of matches. |
|
242 | - * It makes sure the last matches buffer is set (for end() and andSelf()). |
|
243 | - * |
|
244 | - * @param $matches |
|
245 | - * |
|
246 | - * @since 2.0 |
|
247 | - */ |
|
248 | - public function setMatches($matches) |
|
249 | - { |
|
250 | - // This causes a lot of overhead.... |
|
251 | - //if ($unique) $matches = self::unique($matches); |
|
252 | - $this->last = $this->matches; |
|
253 | - |
|
254 | - // Just set current matches. |
|
255 | - if ($matches instanceof SplObjectStorage) { |
|
256 | - $this->matches = $matches; |
|
257 | - } // This is likely legacy code that needs conversion. |
|
258 | - elseif (is_array($matches)) { |
|
259 | - trigger_error('Legacy array detected.'); |
|
260 | - $tmp = new SplObjectStorage(); |
|
261 | - foreach ($matches as $m) { |
|
262 | - $tmp->attach($m); |
|
263 | - } |
|
264 | - $this->matches = $tmp; |
|
265 | - } |
|
266 | - // For non-arrays, try to create a new match set and |
|
267 | - // add this object. |
|
268 | - else { |
|
269 | - $found = new SplObjectStorage(); |
|
270 | - if (isset($matches)) { |
|
271 | - $found->attach($matches); |
|
272 | - } |
|
273 | - $this->matches = $found; |
|
274 | - } |
|
275 | - |
|
276 | - // EXPERIMENTAL: Support for qp()->length. |
|
277 | - $this->length = $this->matches->count(); |
|
278 | - } |
|
279 | - |
|
280 | - /** |
|
281 | - * A depth-checking function. Typically, it only needs to be |
|
282 | - * invoked with the first parameter. The rest are used for recursion. |
|
283 | - * |
|
284 | - * @param DOMNode $ele |
|
285 | - * The element. |
|
286 | - * @param int $depth |
|
287 | - * The depth guage |
|
288 | - * @param mixed $current |
|
289 | - * The current set. |
|
290 | - * @param DOMNode $deepest |
|
291 | - * A reference to the current deepest node. |
|
292 | - * |
|
293 | - * @return array |
|
294 | - * Returns an array of DOM nodes. |
|
295 | - * @see deepest(); |
|
296 | - */ |
|
297 | - protected function deepestNode(DOMNode $ele, $depth = 0, $current = null, &$deepest = null) |
|
298 | - { |
|
299 | - // FIXME: Should this use SplObjectStorage? |
|
300 | - if (! isset($current)) { |
|
301 | - $current = [$ele]; |
|
302 | - } |
|
303 | - if (! isset($deepest)) { |
|
304 | - $deepest = $depth; |
|
305 | - } |
|
306 | - if ($ele->hasChildNodes()) { |
|
307 | - foreach ($ele->childNodes as $child) { |
|
308 | - if ($child->nodeType === XML_ELEMENT_NODE) { |
|
309 | - $current = $this->deepestNode($child, $depth + 1, $current, $deepest); |
|
310 | - } |
|
311 | - } |
|
312 | - } elseif ($depth > $deepest) { |
|
313 | - $current = [$ele]; |
|
314 | - $deepest = $depth; |
|
315 | - } elseif ($depth === $deepest) { |
|
316 | - $current[] = $ele; |
|
317 | - } |
|
318 | - |
|
319 | - return $current; |
|
320 | - } |
|
321 | - |
|
322 | - /** |
|
323 | - * Prepare an item for insertion into a DOM. |
|
324 | - * |
|
325 | - * This handles a variety of boilerplate tasks that need doing before an |
|
326 | - * indeterminate object can be inserted into a DOM tree. |
|
327 | - * - If item is a string, this is converted into a document fragment and returned. |
|
328 | - * - If item is a DOMQuery, then all items are retrieved and converted into |
|
329 | - * a document fragment and returned. |
|
330 | - * - If the item is a DOMNode, it is imported into the current DOM if necessary. |
|
331 | - * - If the item is a SimpleXMLElement, it is converted into a DOM node and then |
|
332 | - * imported. |
|
333 | - * |
|
334 | - * @param mixed $item |
|
335 | - * Item to prepare for insert. |
|
336 | - * |
|
337 | - * @return mixed |
|
338 | - * Returns the prepared item. |
|
339 | - * @throws QueryPath::Exception |
|
340 | - * Thrown if the object passed in is not of a supprted object type. |
|
341 | - * @throws Exception |
|
342 | - */ |
|
343 | - protected function prepareInsert($item) |
|
344 | - { |
|
345 | - if (empty($item)) { |
|
346 | - return null; |
|
347 | - } |
|
348 | - |
|
349 | - if (is_string($item)) { |
|
350 | - // If configured to do so, replace all entities. |
|
351 | - if ($this->options['replace_entities']) { |
|
352 | - $item = Entities::replaceAllEntities($item); |
|
353 | - } |
|
354 | - |
|
355 | - $frag = $this->document->createDocumentFragment(); |
|
356 | - try { |
|
357 | - set_error_handler([ParseException::class, 'initializeFromError'], $this->errTypes); |
|
358 | - $frag->appendXML($item); |
|
359 | - } // Simulate a finally block. |
|
360 | - catch (Exception $e) { |
|
361 | - restore_error_handler(); |
|
362 | - throw $e; |
|
363 | - } |
|
364 | - restore_error_handler(); |
|
365 | - |
|
366 | - return $frag; |
|
367 | - } |
|
368 | - |
|
369 | - if ($item instanceof self) { |
|
370 | - if ($item->count() === 0) { |
|
371 | - return null; |
|
372 | - } |
|
373 | - |
|
374 | - $frag = $this->document->createDocumentFragment(); |
|
375 | - foreach ($item->matches as $m) { |
|
376 | - $frag->appendXML($item->document->saveXML($m)); |
|
377 | - } |
|
378 | - |
|
379 | - return $frag; |
|
380 | - } |
|
381 | - |
|
382 | - if ($item instanceof DOMNode) { |
|
383 | - if ($item->ownerDocument !== $this->document) { |
|
384 | - // Deep clone this and attach it to this document |
|
385 | - $item = $this->document->importNode($item, true); |
|
386 | - } |
|
387 | - |
|
388 | - return $item; |
|
389 | - } |
|
390 | - |
|
391 | - if ($item instanceof SimpleXMLElement) { |
|
392 | - $element = dom_import_simplexml($item); |
|
393 | - |
|
394 | - return $this->document->importNode($element, true); |
|
395 | - } |
|
396 | - // What should we do here? |
|
397 | - //var_dump($item); |
|
398 | - throw new Exception('Cannot prepare item of unsupported type: ' . gettype($item)); |
|
399 | - } |
|
400 | - |
|
401 | - /** |
|
402 | - * Convenience function for getNthMatch(0). |
|
403 | - */ |
|
404 | - protected function getFirstMatch() |
|
405 | - { |
|
406 | - $this->matches->rewind(); |
|
407 | - |
|
408 | - return $this->matches->current(); |
|
409 | - } |
|
410 | - |
|
411 | - /** |
|
412 | - * Parse an XML or HTML file. |
|
413 | - * |
|
414 | - * This attempts to autodetect the type of file, and then parse it. |
|
415 | - * |
|
416 | - * @param string $filename |
|
417 | - * The file name to parse. |
|
418 | - * @param int $flags |
|
419 | - * The OR-combined flags accepted by the DOM parser. See the PHP documentation |
|
420 | - * for DOM or for libxml. |
|
421 | - * @param resource $context |
|
422 | - * The stream context for the file IO. If this is set, then an alternate |
|
423 | - * parsing path is followed: The file is loaded by PHP's stream-aware IO |
|
424 | - * facilities, read entirely into memory, and then handed off to |
|
425 | - * {@link parseXMLString()}. On large files, this can have a performance impact. |
|
426 | - * |
|
427 | - * @throws ParseException |
|
428 | - * Thrown when a file cannot be loaded or parsed. |
|
429 | - */ |
|
430 | - private function parseXMLFile($filename, $flags = 0, $context = null) |
|
431 | - { |
|
432 | - // Backwards compatibility fix for PHP8+ |
|
433 | - if (is_null($flags)) { |
|
434 | - $flags = 0; |
|
435 | - } |
|
436 | - |
|
437 | - // If a context is specified, we basically have to do the reading in |
|
438 | - // two steps: |
|
439 | - if (! empty($context)) { |
|
440 | - try { |
|
441 | - set_error_handler(['\QueryPath\ParseException', 'initializeFromError'], $this->errTypes); |
|
442 | - $contents = file_get_contents($filename, false, $context); |
|
443 | - } |
|
444 | - // Apparently there is no 'finally' in PHP, so we have to restore the error |
|
445 | - // handler this way: |
|
446 | - catch (Exception $e) { |
|
447 | - restore_error_handler(); |
|
448 | - throw $e; |
|
449 | - } |
|
450 | - restore_error_handler(); |
|
451 | - |
|
452 | - if ($contents == false) { |
|
453 | - throw new ParseException(sprintf( |
|
454 | - 'Contents of the file %s could not be retrieved.', |
|
455 | - $filename |
|
456 | - )); |
|
457 | - } |
|
458 | - |
|
459 | - return $this->parseXMLString($contents, $flags); |
|
460 | - } |
|
461 | - |
|
462 | - $document = new DOMDocument(); |
|
463 | - $lastDot = strrpos($filename, '.'); |
|
464 | - |
|
465 | - $htmlExtensions = [ |
|
466 | - '.html' => 1, |
|
467 | - '.htm' => 1, |
|
468 | - ]; |
|
469 | - |
|
470 | - // Allow users to override parser settings. |
|
471 | - if (empty($this->options['use_parser'])) { |
|
472 | - $useParser = ''; |
|
473 | - } else { |
|
474 | - $useParser = strtolower($this->options['use_parser']); |
|
475 | - } |
|
476 | - |
|
477 | - $ext = $lastDot !== false ? strtolower(substr($filename, $lastDot)) : ''; |
|
478 | - |
|
479 | - try { |
|
480 | - set_error_handler([ParseException::class, 'initializeFromError'], $this->errTypes); |
|
481 | - |
|
482 | - // If the parser is explicitly set to XML, use that parser. |
|
483 | - if ($useParser === 'xml') { |
|
484 | - $document->load($filename, $flags); |
|
485 | - } // Otherwise, see if it looks like HTML. |
|
486 | - elseif ($useParser === 'html' || isset($htmlExtensions[$ext])) { |
|
487 | - // Try parsing it as HTML. |
|
488 | - $document->loadHTMLFile($filename); |
|
489 | - } // Default to XML. |
|
490 | - else { |
|
491 | - $document->load($filename, $flags); |
|
492 | - } |
|
493 | - |
|
494 | - } // Emulate 'finally' behavior. |
|
495 | - catch (Exception $e) { |
|
496 | - restore_error_handler(); |
|
497 | - throw $e; |
|
498 | - } |
|
499 | - restore_error_handler(); |
|
500 | - |
|
501 | - return $document; |
|
502 | - } |
|
503 | - |
|
504 | - /** |
|
505 | - * Determine whether a given string looks like XML or not. |
|
506 | - * |
|
507 | - * Basically, this scans a portion of the supplied string, checking to see |
|
508 | - * if it has a tag-like structure. It is possible to "confuse" this, which |
|
509 | - * may subsequently result in parse errors, but in the vast majority of |
|
510 | - * cases, this method serves as a valid inicator of whether or not the |
|
511 | - * content looks like XML. |
|
512 | - * |
|
513 | - * Things that are intentional excluded: |
|
514 | - * - plain text with no markup. |
|
515 | - * - strings that look like filesystem paths. |
|
516 | - * |
|
517 | - * Subclasses SHOULD NOT OVERRIDE THIS. Altering it may be altering |
|
518 | - * core assumptions about how things work. Instead, classes should |
|
519 | - * override the constructor and pass in only one of the parsed types |
|
520 | - * that this class expects. |
|
521 | - */ |
|
522 | - protected function isXMLish($string) |
|
523 | - { |
|
524 | - return (strpos($string, '<') !== false && strpos($string, '>') !== false); |
|
525 | - } |
|
526 | - |
|
527 | - /** |
|
528 | - * A utility function for retriving a match by index. |
|
529 | - * |
|
530 | - * The internal data structure used in DOMQuery does not have |
|
531 | - * strong random access support, so we suppliment it with this method. |
|
532 | - * |
|
533 | - * @param $index |
|
534 | - * |
|
535 | - * @return object|void |
|
536 | - */ |
|
537 | - protected function getNthMatch(int $index) |
|
538 | - { |
|
539 | - if ($index < 0 || $index > $this->matches->count()) { |
|
540 | - return; |
|
541 | - } |
|
542 | - |
|
543 | - $i = 0; |
|
544 | - foreach ($this->matches as $m) { |
|
545 | - if ($i++ === $index) { |
|
546 | - return $m; |
|
547 | - } |
|
548 | - } |
|
549 | - } |
|
26 | + /** |
|
27 | + * The array of matches. |
|
28 | + */ |
|
29 | + protected $matches = []; |
|
30 | + |
|
31 | + /** |
|
32 | + * Default parser flags. |
|
33 | + * |
|
34 | + * These are flags that will be used if no global or local flags override them. |
|
35 | + * |
|
36 | + * @since 2.0 |
|
37 | + */ |
|
38 | + public const DEFAULT_PARSER_FLAGS = null; |
|
39 | + |
|
40 | + public const JS_CSS_ESCAPE_CDATA = '\\1'; |
|
41 | + public const JS_CSS_ESCAPE_CDATA_CCOMMENT = '/* \\1 */'; |
|
42 | + public const JS_CSS_ESCAPE_CDATA_DOUBLESLASH = '// \\1'; |
|
43 | + public const JS_CSS_ESCAPE_NONE = ''; |
|
44 | + |
|
45 | + protected $errTypes = 771; //E_ERROR; | E_USER_ERROR; |
|
46 | + |
|
47 | + protected $document; |
|
48 | + /** |
|
49 | + * The base DOMDocument. |
|
50 | + */ |
|
51 | + protected $options = [ |
|
52 | + 'parser_flags' => null, |
|
53 | + 'omit_xml_declaration' => false, |
|
54 | + 'replace_entities' => false, |
|
55 | + 'exception_level' => 771, // E_ERROR | E_USER_ERROR | E_USER_WARNING | E_WARNING |
|
56 | + 'ignore_parser_warnings' => false, |
|
57 | + 'escape_xhtml_js_css_sections' => self::JS_CSS_ESCAPE_CDATA_CCOMMENT, |
|
58 | + ]; |
|
59 | + |
|
60 | + /** |
|
61 | + * Constructor. |
|
62 | + * |
|
63 | + * Typically, a new DOMQuery is created by QueryPath::with(), QueryPath::withHTML(), |
|
64 | + * qp(), or htmlqp(). |
|
65 | + * |
|
66 | + * @param mixed $document |
|
67 | + * A document-like object. |
|
68 | + * @param string $string |
|
69 | + * A CSS 3 Selector |
|
70 | + * @param array $options |
|
71 | + * An associative array of options. |
|
72 | + * |
|
73 | + * @throws Exception |
|
74 | + * @see qp() |
|
75 | + */ |
|
76 | + public function __construct($document = null, $string = '', $options = []) |
|
77 | + { |
|
78 | + // Backwards compatibility fix for PHP8+ |
|
79 | + if (is_null($string)) { |
|
80 | + $string = ''; |
|
81 | + } |
|
82 | + |
|
83 | + $string = trim($string); |
|
84 | + $this->options = $options + Options::get() + $this->options; |
|
85 | + |
|
86 | + $parser_flags = $options['parser_flags'] ?? self::DEFAULT_PARSER_FLAGS; |
|
87 | + if (! empty($this->options['ignore_parser_warnings'])) { |
|
88 | + // Don't convert parser warnings into exceptions. |
|
89 | + $this->errTypes = 257; //E_ERROR | E_USER_ERROR; |
|
90 | + } elseif (isset($this->options['exception_level'])) { |
|
91 | + // Set the error level at which exceptions will be thrown. By default, |
|
92 | + // QueryPath will throw exceptions for |
|
93 | + // E_ERROR | E_USER_ERROR | E_WARNING | E_USER_WARNING. |
|
94 | + $this->errTypes = $this->options['exception_level']; |
|
95 | + } |
|
96 | + |
|
97 | + // Empty: Just create an empty QP. |
|
98 | + if (empty($document)) { |
|
99 | + $this->document = isset($this->options['encoding']) ? new DOMDocument( |
|
100 | + '1.0', |
|
101 | + $this->options['encoding'] |
|
102 | + ) : new DOMDocument(); |
|
103 | + $this->setMatches(new SplObjectStorage()); |
|
104 | + } // Figure out if document is DOM, HTML/XML, or a filename |
|
105 | + elseif (is_object($document)) { |
|
106 | + |
|
107 | + // This is the most frequent object type. |
|
108 | + if ($document instanceof SplObjectStorage) { |
|
109 | + $this->matches = $document; |
|
110 | + if ($document->count() !== 0) { |
|
111 | + $first = $this->getFirstMatch(); |
|
112 | + if (! empty($first->ownerDocument)) { |
|
113 | + $this->document = $first->ownerDocument; |
|
114 | + } |
|
115 | + } |
|
116 | + } elseif ($document instanceof self) { |
|
117 | + //$this->matches = $document->get(NULL, TRUE); |
|
118 | + $this->setMatches($document->get(null, true)); |
|
119 | + if ($this->matches->count() > 0) { |
|
120 | + $this->document = $this->getFirstMatch()->ownerDocument; |
|
121 | + } |
|
122 | + } elseif ($document instanceof DOMDocument) { |
|
123 | + $this->document = $document; |
|
124 | + //$this->matches = $this->matches($document->documentElement); |
|
125 | + $this->setMatches($document->documentElement); |
|
126 | + } elseif ($document instanceof DOMNode) { |
|
127 | + $this->document = $document->ownerDocument; |
|
128 | + //$this->matches = array($document); |
|
129 | + $this->setMatches($document); |
|
130 | + } elseif ($document instanceof HTML5) { |
|
131 | + $this->document = $document; |
|
132 | + $this->setMatches($document->documentElement); |
|
133 | + } elseif ($document instanceof SimpleXMLElement) { |
|
134 | + $import = dom_import_simplexml($document); |
|
135 | + $this->document = $import->ownerDocument; |
|
136 | + //$this->matches = array($import); |
|
137 | + $this->setMatches($import); |
|
138 | + } else { |
|
139 | + throw new Exception('Unsupported class type: ' . get_class($document)); |
|
140 | + } |
|
141 | + } elseif (is_array($document)) { |
|
142 | + //trigger_error('Detected deprecated array support', E_USER_NOTICE); |
|
143 | + if (! empty($document) && $document[0] instanceof DOMNode) { |
|
144 | + $found = new SplObjectStorage(); |
|
145 | + foreach ($document as $item) { |
|
146 | + $found->attach($item); |
|
147 | + } |
|
148 | + //$this->matches = $found; |
|
149 | + $this->setMatches($found); |
|
150 | + $this->document = $this->getFirstMatch()->ownerDocument; |
|
151 | + } |
|
152 | + } elseif ($this->isXMLish($document)) { |
|
153 | + // $document is a string with XML |
|
154 | + $this->document = $this->parseXMLString($document); |
|
155 | + $this->setMatches($this->document->documentElement); |
|
156 | + } else { |
|
157 | + |
|
158 | + // $document is a filename |
|
159 | + $context = empty($options['context']) ? null : $options['context']; |
|
160 | + $this->document = $this->parseXMLFile($document, $parser_flags, $context); |
|
161 | + $this->setMatches($this->document->documentElement); |
|
162 | + } |
|
163 | + |
|
164 | + // Globally set the output option. |
|
165 | + $this->document->formatOutput = true; |
|
166 | + if (isset($this->options['format_output']) && $this->options['format_output'] === false) { |
|
167 | + $this->document->formatOutput = false; |
|
168 | + } |
|
169 | + |
|
170 | + // Do a find if the second param was set. |
|
171 | + if (strlen($string) > 0) { |
|
172 | + // We don't issue a find because that creates a new DOMQuery. |
|
173 | + //$this->find($string); |
|
174 | + |
|
175 | + $query = new DOMTraverser($this->matches); |
|
176 | + $query->find($string); |
|
177 | + $this->setMatches($query->matches()); |
|
178 | + } |
|
179 | + } |
|
180 | + |
|
181 | + private function parseXMLString($string, $flags = 0) |
|
182 | + { |
|
183 | + $document = new DOMDocument('1.0'); |
|
184 | + $lead = strtolower(substr($string, 0, 5)); // <?xml |
|
185 | + try { |
|
186 | + set_error_handler([ParseException::class, 'initializeFromError'], $this->errTypes); |
|
187 | + |
|
188 | + if (isset($this->options['convert_to_encoding'])) { |
|
189 | + // Is there another way to do this? |
|
190 | + |
|
191 | + $from_enc = $this->options['convert_from_encoding'] ?? 'auto'; |
|
192 | + $to_enc = $this->options['convert_to_encoding']; |
|
193 | + |
|
194 | + if (function_exists('mb_convert_encoding')) { |
|
195 | + $string = mb_convert_encoding($string, $to_enc, $from_enc); |
|
196 | + } |
|
197 | + |
|
198 | + } |
|
199 | + |
|
200 | + // This is to avoid cases where low ascii digits have slipped into HTML. |
|
201 | + // AFAIK, it should not adversly effect UTF-8 documents. |
|
202 | + if (! empty($this->options['strip_low_ascii'])) { |
|
203 | + $string = filter_var($string, FILTER_UNSAFE_RAW, FILTER_FLAG_ENCODE_LOW); |
|
204 | + } |
|
205 | + |
|
206 | + // Allow users to override parser settings. |
|
207 | + $useParser = ''; |
|
208 | + if (! empty($this->options['use_parser'])) { |
|
209 | + $useParser = strtolower($this->options['use_parser']); |
|
210 | + } |
|
211 | + |
|
212 | + // If HTML parser is requested, we use it. |
|
213 | + if ($useParser === 'html') { |
|
214 | + $document->loadHTML($string); |
|
215 | + } // Parse as XML if it looks like XML, or if XML parser is requested. |
|
216 | + elseif ($lead === '<?xml' || $useParser === 'xml') { |
|
217 | + if ($this->options['replace_entities']) { |
|
218 | + $string = Entities::replaceAllEntities($string); |
|
219 | + } |
|
220 | + $document->loadXML($string, $flags); |
|
221 | + } // In all other cases, we try the HTML parser. |
|
222 | + else { |
|
223 | + $document->loadHTML($string); |
|
224 | + } |
|
225 | + } // Emulate 'finally' behavior. |
|
226 | + catch (Exception $e) { |
|
227 | + restore_error_handler(); |
|
228 | + throw $e; |
|
229 | + } |
|
230 | + restore_error_handler(); |
|
231 | + |
|
232 | + if (empty($document)) { |
|
233 | + throw new ParseException('Unknown parser exception.'); |
|
234 | + } |
|
235 | + |
|
236 | + return $document; |
|
237 | + } |
|
238 | + |
|
239 | + /** |
|
240 | + * EXPERT: Be very, very careful using this. |
|
241 | + * A utility function for setting the current set of matches. |
|
242 | + * It makes sure the last matches buffer is set (for end() and andSelf()). |
|
243 | + * |
|
244 | + * @param $matches |
|
245 | + * |
|
246 | + * @since 2.0 |
|
247 | + */ |
|
248 | + public function setMatches($matches) |
|
249 | + { |
|
250 | + // This causes a lot of overhead.... |
|
251 | + //if ($unique) $matches = self::unique($matches); |
|
252 | + $this->last = $this->matches; |
|
253 | + |
|
254 | + // Just set current matches. |
|
255 | + if ($matches instanceof SplObjectStorage) { |
|
256 | + $this->matches = $matches; |
|
257 | + } // This is likely legacy code that needs conversion. |
|
258 | + elseif (is_array($matches)) { |
|
259 | + trigger_error('Legacy array detected.'); |
|
260 | + $tmp = new SplObjectStorage(); |
|
261 | + foreach ($matches as $m) { |
|
262 | + $tmp->attach($m); |
|
263 | + } |
|
264 | + $this->matches = $tmp; |
|
265 | + } |
|
266 | + // For non-arrays, try to create a new match set and |
|
267 | + // add this object. |
|
268 | + else { |
|
269 | + $found = new SplObjectStorage(); |
|
270 | + if (isset($matches)) { |
|
271 | + $found->attach($matches); |
|
272 | + } |
|
273 | + $this->matches = $found; |
|
274 | + } |
|
275 | + |
|
276 | + // EXPERIMENTAL: Support for qp()->length. |
|
277 | + $this->length = $this->matches->count(); |
|
278 | + } |
|
279 | + |
|
280 | + /** |
|
281 | + * A depth-checking function. Typically, it only needs to be |
|
282 | + * invoked with the first parameter. The rest are used for recursion. |
|
283 | + * |
|
284 | + * @param DOMNode $ele |
|
285 | + * The element. |
|
286 | + * @param int $depth |
|
287 | + * The depth guage |
|
288 | + * @param mixed $current |
|
289 | + * The current set. |
|
290 | + * @param DOMNode $deepest |
|
291 | + * A reference to the current deepest node. |
|
292 | + * |
|
293 | + * @return array |
|
294 | + * Returns an array of DOM nodes. |
|
295 | + * @see deepest(); |
|
296 | + */ |
|
297 | + protected function deepestNode(DOMNode $ele, $depth = 0, $current = null, &$deepest = null) |
|
298 | + { |
|
299 | + // FIXME: Should this use SplObjectStorage? |
|
300 | + if (! isset($current)) { |
|
301 | + $current = [$ele]; |
|
302 | + } |
|
303 | + if (! isset($deepest)) { |
|
304 | + $deepest = $depth; |
|
305 | + } |
|
306 | + if ($ele->hasChildNodes()) { |
|
307 | + foreach ($ele->childNodes as $child) { |
|
308 | + if ($child->nodeType === XML_ELEMENT_NODE) { |
|
309 | + $current = $this->deepestNode($child, $depth + 1, $current, $deepest); |
|
310 | + } |
|
311 | + } |
|
312 | + } elseif ($depth > $deepest) { |
|
313 | + $current = [$ele]; |
|
314 | + $deepest = $depth; |
|
315 | + } elseif ($depth === $deepest) { |
|
316 | + $current[] = $ele; |
|
317 | + } |
|
318 | + |
|
319 | + return $current; |
|
320 | + } |
|
321 | + |
|
322 | + /** |
|
323 | + * Prepare an item for insertion into a DOM. |
|
324 | + * |
|
325 | + * This handles a variety of boilerplate tasks that need doing before an |
|
326 | + * indeterminate object can be inserted into a DOM tree. |
|
327 | + * - If item is a string, this is converted into a document fragment and returned. |
|
328 | + * - If item is a DOMQuery, then all items are retrieved and converted into |
|
329 | + * a document fragment and returned. |
|
330 | + * - If the item is a DOMNode, it is imported into the current DOM if necessary. |
|
331 | + * - If the item is a SimpleXMLElement, it is converted into a DOM node and then |
|
332 | + * imported. |
|
333 | + * |
|
334 | + * @param mixed $item |
|
335 | + * Item to prepare for insert. |
|
336 | + * |
|
337 | + * @return mixed |
|
338 | + * Returns the prepared item. |
|
339 | + * @throws QueryPath::Exception |
|
340 | + * Thrown if the object passed in is not of a supprted object type. |
|
341 | + * @throws Exception |
|
342 | + */ |
|
343 | + protected function prepareInsert($item) |
|
344 | + { |
|
345 | + if (empty($item)) { |
|
346 | + return null; |
|
347 | + } |
|
348 | + |
|
349 | + if (is_string($item)) { |
|
350 | + // If configured to do so, replace all entities. |
|
351 | + if ($this->options['replace_entities']) { |
|
352 | + $item = Entities::replaceAllEntities($item); |
|
353 | + } |
|
354 | + |
|
355 | + $frag = $this->document->createDocumentFragment(); |
|
356 | + try { |
|
357 | + set_error_handler([ParseException::class, 'initializeFromError'], $this->errTypes); |
|
358 | + $frag->appendXML($item); |
|
359 | + } // Simulate a finally block. |
|
360 | + catch (Exception $e) { |
|
361 | + restore_error_handler(); |
|
362 | + throw $e; |
|
363 | + } |
|
364 | + restore_error_handler(); |
|
365 | + |
|
366 | + return $frag; |
|
367 | + } |
|
368 | + |
|
369 | + if ($item instanceof self) { |
|
370 | + if ($item->count() === 0) { |
|
371 | + return null; |
|
372 | + } |
|
373 | + |
|
374 | + $frag = $this->document->createDocumentFragment(); |
|
375 | + foreach ($item->matches as $m) { |
|
376 | + $frag->appendXML($item->document->saveXML($m)); |
|
377 | + } |
|
378 | + |
|
379 | + return $frag; |
|
380 | + } |
|
381 | + |
|
382 | + if ($item instanceof DOMNode) { |
|
383 | + if ($item->ownerDocument !== $this->document) { |
|
384 | + // Deep clone this and attach it to this document |
|
385 | + $item = $this->document->importNode($item, true); |
|
386 | + } |
|
387 | + |
|
388 | + return $item; |
|
389 | + } |
|
390 | + |
|
391 | + if ($item instanceof SimpleXMLElement) { |
|
392 | + $element = dom_import_simplexml($item); |
|
393 | + |
|
394 | + return $this->document->importNode($element, true); |
|
395 | + } |
|
396 | + // What should we do here? |
|
397 | + //var_dump($item); |
|
398 | + throw new Exception('Cannot prepare item of unsupported type: ' . gettype($item)); |
|
399 | + } |
|
400 | + |
|
401 | + /** |
|
402 | + * Convenience function for getNthMatch(0). |
|
403 | + */ |
|
404 | + protected function getFirstMatch() |
|
405 | + { |
|
406 | + $this->matches->rewind(); |
|
407 | + |
|
408 | + return $this->matches->current(); |
|
409 | + } |
|
410 | + |
|
411 | + /** |
|
412 | + * Parse an XML or HTML file. |
|
413 | + * |
|
414 | + * This attempts to autodetect the type of file, and then parse it. |
|
415 | + * |
|
416 | + * @param string $filename |
|
417 | + * The file name to parse. |
|
418 | + * @param int $flags |
|
419 | + * The OR-combined flags accepted by the DOM parser. See the PHP documentation |
|
420 | + * for DOM or for libxml. |
|
421 | + * @param resource $context |
|
422 | + * The stream context for the file IO. If this is set, then an alternate |
|
423 | + * parsing path is followed: The file is loaded by PHP's stream-aware IO |
|
424 | + * facilities, read entirely into memory, and then handed off to |
|
425 | + * {@link parseXMLString()}. On large files, this can have a performance impact. |
|
426 | + * |
|
427 | + * @throws ParseException |
|
428 | + * Thrown when a file cannot be loaded or parsed. |
|
429 | + */ |
|
430 | + private function parseXMLFile($filename, $flags = 0, $context = null) |
|
431 | + { |
|
432 | + // Backwards compatibility fix for PHP8+ |
|
433 | + if (is_null($flags)) { |
|
434 | + $flags = 0; |
|
435 | + } |
|
436 | + |
|
437 | + // If a context is specified, we basically have to do the reading in |
|
438 | + // two steps: |
|
439 | + if (! empty($context)) { |
|
440 | + try { |
|
441 | + set_error_handler(['\QueryPath\ParseException', 'initializeFromError'], $this->errTypes); |
|
442 | + $contents = file_get_contents($filename, false, $context); |
|
443 | + } |
|
444 | + // Apparently there is no 'finally' in PHP, so we have to restore the error |
|
445 | + // handler this way: |
|
446 | + catch (Exception $e) { |
|
447 | + restore_error_handler(); |
|
448 | + throw $e; |
|
449 | + } |
|
450 | + restore_error_handler(); |
|
451 | + |
|
452 | + if ($contents == false) { |
|
453 | + throw new ParseException(sprintf( |
|
454 | + 'Contents of the file %s could not be retrieved.', |
|
455 | + $filename |
|
456 | + )); |
|
457 | + } |
|
458 | + |
|
459 | + return $this->parseXMLString($contents, $flags); |
|
460 | + } |
|
461 | + |
|
462 | + $document = new DOMDocument(); |
|
463 | + $lastDot = strrpos($filename, '.'); |
|
464 | + |
|
465 | + $htmlExtensions = [ |
|
466 | + '.html' => 1, |
|
467 | + '.htm' => 1, |
|
468 | + ]; |
|
469 | + |
|
470 | + // Allow users to override parser settings. |
|
471 | + if (empty($this->options['use_parser'])) { |
|
472 | + $useParser = ''; |
|
473 | + } else { |
|
474 | + $useParser = strtolower($this->options['use_parser']); |
|
475 | + } |
|
476 | + |
|
477 | + $ext = $lastDot !== false ? strtolower(substr($filename, $lastDot)) : ''; |
|
478 | + |
|
479 | + try { |
|
480 | + set_error_handler([ParseException::class, 'initializeFromError'], $this->errTypes); |
|
481 | + |
|
482 | + // If the parser is explicitly set to XML, use that parser. |
|
483 | + if ($useParser === 'xml') { |
|
484 | + $document->load($filename, $flags); |
|
485 | + } // Otherwise, see if it looks like HTML. |
|
486 | + elseif ($useParser === 'html' || isset($htmlExtensions[$ext])) { |
|
487 | + // Try parsing it as HTML. |
|
488 | + $document->loadHTMLFile($filename); |
|
489 | + } // Default to XML. |
|
490 | + else { |
|
491 | + $document->load($filename, $flags); |
|
492 | + } |
|
493 | + |
|
494 | + } // Emulate 'finally' behavior. |
|
495 | + catch (Exception $e) { |
|
496 | + restore_error_handler(); |
|
497 | + throw $e; |
|
498 | + } |
|
499 | + restore_error_handler(); |
|
500 | + |
|
501 | + return $document; |
|
502 | + } |
|
503 | + |
|
504 | + /** |
|
505 | + * Determine whether a given string looks like XML or not. |
|
506 | + * |
|
507 | + * Basically, this scans a portion of the supplied string, checking to see |
|
508 | + * if it has a tag-like structure. It is possible to "confuse" this, which |
|
509 | + * may subsequently result in parse errors, but in the vast majority of |
|
510 | + * cases, this method serves as a valid inicator of whether or not the |
|
511 | + * content looks like XML. |
|
512 | + * |
|
513 | + * Things that are intentional excluded: |
|
514 | + * - plain text with no markup. |
|
515 | + * - strings that look like filesystem paths. |
|
516 | + * |
|
517 | + * Subclasses SHOULD NOT OVERRIDE THIS. Altering it may be altering |
|
518 | + * core assumptions about how things work. Instead, classes should |
|
519 | + * override the constructor and pass in only one of the parsed types |
|
520 | + * that this class expects. |
|
521 | + */ |
|
522 | + protected function isXMLish($string) |
|
523 | + { |
|
524 | + return (strpos($string, '<') !== false && strpos($string, '>') !== false); |
|
525 | + } |
|
526 | + |
|
527 | + /** |
|
528 | + * A utility function for retriving a match by index. |
|
529 | + * |
|
530 | + * The internal data structure used in DOMQuery does not have |
|
531 | + * strong random access support, so we suppliment it with this method. |
|
532 | + * |
|
533 | + * @param $index |
|
534 | + * |
|
535 | + * @return object|void |
|
536 | + */ |
|
537 | + protected function getNthMatch(int $index) |
|
538 | + { |
|
539 | + if ($index < 0 || $index > $this->matches->count()) { |
|
540 | + return; |
|
541 | + } |
|
542 | + |
|
543 | + $i = 0; |
|
544 | + foreach ($this->matches as $m) { |
|
545 | + if ($i++ === $index) { |
|
546 | + return $m; |
|
547 | + } |
|
548 | + } |
|
549 | + } |
|
550 | 550 | } |
@@ -84,7 +84,7 @@ discard block |
||
84 | 84 | $this->options = $options + Options::get() + $this->options; |
85 | 85 | |
86 | 86 | $parser_flags = $options['parser_flags'] ?? self::DEFAULT_PARSER_FLAGS; |
87 | - if (! empty($this->options['ignore_parser_warnings'])) { |
|
87 | + if (!empty($this->options['ignore_parser_warnings'])) { |
|
88 | 88 | // Don't convert parser warnings into exceptions. |
89 | 89 | $this->errTypes = 257; //E_ERROR | E_USER_ERROR; |
90 | 90 | } elseif (isset($this->options['exception_level'])) { |
@@ -109,7 +109,7 @@ discard block |
||
109 | 109 | $this->matches = $document; |
110 | 110 | if ($document->count() !== 0) { |
111 | 111 | $first = $this->getFirstMatch(); |
112 | - if (! empty($first->ownerDocument)) { |
|
112 | + if (!empty($first->ownerDocument)) { |
|
113 | 113 | $this->document = $first->ownerDocument; |
114 | 114 | } |
115 | 115 | } |
@@ -140,7 +140,7 @@ discard block |
||
140 | 140 | } |
141 | 141 | } elseif (is_array($document)) { |
142 | 142 | //trigger_error('Detected deprecated array support', E_USER_NOTICE); |
143 | - if (! empty($document) && $document[0] instanceof DOMNode) { |
|
143 | + if (!empty($document) && $document[0] instanceof DOMNode) { |
|
144 | 144 | $found = new SplObjectStorage(); |
145 | 145 | foreach ($document as $item) { |
146 | 146 | $found->attach($item); |
@@ -199,13 +199,13 @@ discard block |
||
199 | 199 | |
200 | 200 | // This is to avoid cases where low ascii digits have slipped into HTML. |
201 | 201 | // AFAIK, it should not adversly effect UTF-8 documents. |
202 | - if (! empty($this->options['strip_low_ascii'])) { |
|
202 | + if (!empty($this->options['strip_low_ascii'])) { |
|
203 | 203 | $string = filter_var($string, FILTER_UNSAFE_RAW, FILTER_FLAG_ENCODE_LOW); |
204 | 204 | } |
205 | 205 | |
206 | 206 | // Allow users to override parser settings. |
207 | 207 | $useParser = ''; |
208 | - if (! empty($this->options['use_parser'])) { |
|
208 | + if (!empty($this->options['use_parser'])) { |
|
209 | 209 | $useParser = strtolower($this->options['use_parser']); |
210 | 210 | } |
211 | 211 | |
@@ -297,10 +297,10 @@ discard block |
||
297 | 297 | protected function deepestNode(DOMNode $ele, $depth = 0, $current = null, &$deepest = null) |
298 | 298 | { |
299 | 299 | // FIXME: Should this use SplObjectStorage? |
300 | - if (! isset($current)) { |
|
300 | + if (!isset($current)) { |
|
301 | 301 | $current = [$ele]; |
302 | 302 | } |
303 | - if (! isset($deepest)) { |
|
303 | + if (!isset($deepest)) { |
|
304 | 304 | $deepest = $depth; |
305 | 305 | } |
306 | 306 | if ($ele->hasChildNodes()) { |
@@ -436,7 +436,7 @@ discard block |
||
436 | 436 | |
437 | 437 | // If a context is specified, we basically have to do the reading in |
438 | 438 | // two steps: |
439 | - if (! empty($context)) { |
|
439 | + if (!empty($context)) { |
|
440 | 440 | try { |
441 | 441 | set_error_handler(['\QueryPath\ParseException', 'initializeFromError'], $this->errTypes); |
442 | 442 | $contents = file_get_contents($filename, false, $context); |
@@ -96,45 +96,45 @@ discard block |
||
96 | 96 | class QueryPath |
97 | 97 | { |
98 | 98 | |
99 | - /** |
|
100 | - * The version string for this version of QueryPath. |
|
101 | - * |
|
102 | - * Standard releases will be of the following form: <MAJOR>.<MINOR>[.<PATCH>][-STABILITY]. |
|
103 | - * |
|
104 | - * Examples: |
|
105 | - * - 2.0 |
|
106 | - * - 2.1.1 |
|
107 | - * - 2.0-alpha1 |
|
108 | - * |
|
109 | - * Developer releases will always be of the form dev-<DATE>. |
|
110 | - * |
|
111 | - * @since 2.0 |
|
112 | - */ |
|
113 | - public const VERSION = '3.0.x'; |
|
99 | + /** |
|
100 | + * The version string for this version of QueryPath. |
|
101 | + * |
|
102 | + * Standard releases will be of the following form: <MAJOR>.<MINOR>[.<PATCH>][-STABILITY]. |
|
103 | + * |
|
104 | + * Examples: |
|
105 | + * - 2.0 |
|
106 | + * - 2.1.1 |
|
107 | + * - 2.0-alpha1 |
|
108 | + * |
|
109 | + * Developer releases will always be of the form dev-<DATE>. |
|
110 | + * |
|
111 | + * @since 2.0 |
|
112 | + */ |
|
113 | + public const VERSION = '3.0.x'; |
|
114 | 114 | |
115 | - /** |
|
116 | - * Major version number. |
|
117 | - * |
|
118 | - * Examples: |
|
119 | - * - 3 |
|
120 | - * - 4 |
|
121 | - * |
|
122 | - * @since 3.0.1 |
|
123 | - */ |
|
124 | - public const VERSION_MAJOR = 3; |
|
115 | + /** |
|
116 | + * Major version number. |
|
117 | + * |
|
118 | + * Examples: |
|
119 | + * - 3 |
|
120 | + * - 4 |
|
121 | + * |
|
122 | + * @since 3.0.1 |
|
123 | + */ |
|
124 | + public const VERSION_MAJOR = 3; |
|
125 | 125 | |
126 | - /** |
|
127 | - * This is a stub HTML 4.01 document. |
|
128 | - * |
|
129 | - * <b>Using {@link QueryPath::XHTML_STUB} is preferred.</b> |
|
130 | - * |
|
131 | - * This is primarily for generating legacy HTML content. Modern web applications |
|
132 | - * should use QueryPath::XHTML_STUB. |
|
133 | - * |
|
134 | - * Use this stub with the HTML familiy of methods (QueryPath::Query::html(), |
|
135 | - * QueryPath::Query::writeHTML(), QueryPath::Query::innerHTML()). |
|
136 | - */ |
|
137 | - public const HTML_STUB = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> |
|
126 | + /** |
|
127 | + * This is a stub HTML 4.01 document. |
|
128 | + * |
|
129 | + * <b>Using {@link QueryPath::XHTML_STUB} is preferred.</b> |
|
130 | + * |
|
131 | + * This is primarily for generating legacy HTML content. Modern web applications |
|
132 | + * should use QueryPath::XHTML_STUB. |
|
133 | + * |
|
134 | + * Use this stub with the HTML familiy of methods (QueryPath::Query::html(), |
|
135 | + * QueryPath::Query::writeHTML(), QueryPath::Query::innerHTML()). |
|
136 | + */ |
|
137 | + public const HTML_STUB = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> |
|
138 | 138 | <html lang="en"> |
139 | 139 | <head> |
140 | 140 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> |
@@ -143,7 +143,7 @@ discard block |
||
143 | 143 | <body></body> |
144 | 144 | </html>'; |
145 | 145 | |
146 | - public const HTML5_STUB = '<!DOCTYPE html> |
|
146 | + public const HTML5_STUB = '<!DOCTYPE html> |
|
147 | 147 | <html> |
148 | 148 | <head> |
149 | 149 | <title>Untitled</title> |
@@ -151,24 +151,24 @@ discard block |
||
151 | 151 | <body></body> |
152 | 152 | </html>'; |
153 | 153 | |
154 | - /** |
|
155 | - * This is a stub XHTML document. |
|
156 | - * |
|
157 | - * Since XHTML is an XML format, you should use XML functions with this document |
|
158 | - * fragment. For example, you should use {@link xml()}, {@link innerXML()}, and |
|
159 | - * {@link writeXML()}. |
|
160 | - * |
|
161 | - * This can be passed into {@link qp()} to begin a new basic HTML document. |
|
162 | - * |
|
163 | - * Example: |
|
164 | - * |
|
165 | - * @code |
|
166 | - * $qp = qp(QueryPath::XHTML_STUB); // Creates a new XHTML document |
|
167 | - * $qp->writeXML(); // Writes the document as well-formed XHTML. |
|
168 | - * @endcode |
|
169 | - * @since 2.0 |
|
170 | - */ |
|
171 | - public const XHTML_STUB = '<?xml version="1.0"?> |
|
154 | + /** |
|
155 | + * This is a stub XHTML document. |
|
156 | + * |
|
157 | + * Since XHTML is an XML format, you should use XML functions with this document |
|
158 | + * fragment. For example, you should use {@link xml()}, {@link innerXML()}, and |
|
159 | + * {@link writeXML()}. |
|
160 | + * |
|
161 | + * This can be passed into {@link qp()} to begin a new basic HTML document. |
|
162 | + * |
|
163 | + * Example: |
|
164 | + * |
|
165 | + * @code |
|
166 | + * $qp = qp(QueryPath::XHTML_STUB); // Creates a new XHTML document |
|
167 | + * $qp->writeXML(); // Writes the document as well-formed XHTML. |
|
168 | + * @endcode |
|
169 | + * @since 2.0 |
|
170 | + */ |
|
171 | + public const XHTML_STUB = '<?xml version="1.0"?> |
|
172 | 172 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> |
173 | 173 | <html xmlns="http://www.w3.org/1999/xhtml"> |
174 | 174 | <head> |
@@ -179,197 +179,197 @@ discard block |
||
179 | 179 | </html>'; |
180 | 180 | |
181 | 181 | |
182 | - /** |
|
183 | - * @param null $document |
|
184 | - * @param null $selector |
|
185 | - * @param array $options |
|
186 | - * |
|
187 | - * @return mixed|DOMQuery |
|
188 | - */ |
|
189 | - public static function with($document = null, $selector = '', array $options = []) |
|
190 | - { |
|
191 | - $qpClass = $options['QueryPath_class'] ?? '\QueryPath\DOMQuery'; |
|
182 | + /** |
|
183 | + * @param null $document |
|
184 | + * @param null $selector |
|
185 | + * @param array $options |
|
186 | + * |
|
187 | + * @return mixed|DOMQuery |
|
188 | + */ |
|
189 | + public static function with($document = null, $selector = '', array $options = []) |
|
190 | + { |
|
191 | + $qpClass = $options['QueryPath_class'] ?? '\QueryPath\DOMQuery'; |
|
192 | 192 | |
193 | - return new $qpClass($document, $selector, $options); |
|
194 | - } |
|
193 | + return new $qpClass($document, $selector, $options); |
|
194 | + } |
|
195 | 195 | |
196 | - public static function withXML($source = null, $selector = '', array $options = []) |
|
197 | - { |
|
198 | - $options += [ |
|
199 | - 'use_parser' => 'xml', |
|
200 | - ]; |
|
196 | + public static function withXML($source = null, $selector = '', array $options = []) |
|
197 | + { |
|
198 | + $options += [ |
|
199 | + 'use_parser' => 'xml', |
|
200 | + ]; |
|
201 | 201 | |
202 | - return self::with($source, $selector, $options); |
|
203 | - } |
|
202 | + return self::with($source, $selector, $options); |
|
203 | + } |
|
204 | 204 | |
205 | - public static function withHTML($source = null, $selector = '', array $options = []) |
|
206 | - { |
|
207 | - // Need a way to force an HTML parse instead of an XML parse when the |
|
208 | - // doctype is XHTML, since many XHTML documents are not valid XML |
|
209 | - // (because of coding errors, not by design). |
|
205 | + public static function withHTML($source = null, $selector = '', array $options = []) |
|
206 | + { |
|
207 | + // Need a way to force an HTML parse instead of an XML parse when the |
|
208 | + // doctype is XHTML, since many XHTML documents are not valid XML |
|
209 | + // (because of coding errors, not by design). |
|
210 | 210 | |
211 | - $options += [ |
|
212 | - 'ignore_parser_warnings' => true, |
|
213 | - 'convert_to_encoding' => 'ISO-8859-1', |
|
214 | - 'convert_from_encoding' => 'auto', |
|
215 | - //'replace_entities' => TRUE, |
|
216 | - 'use_parser' => 'html', |
|
217 | - // This is stripping actually necessary low ASCII. |
|
218 | - //'strip_low_ascii' => TRUE, |
|
219 | - ]; |
|
211 | + $options += [ |
|
212 | + 'ignore_parser_warnings' => true, |
|
213 | + 'convert_to_encoding' => 'ISO-8859-1', |
|
214 | + 'convert_from_encoding' => 'auto', |
|
215 | + //'replace_entities' => TRUE, |
|
216 | + 'use_parser' => 'html', |
|
217 | + // This is stripping actually necessary low ASCII. |
|
218 | + //'strip_low_ascii' => TRUE, |
|
219 | + ]; |
|
220 | 220 | |
221 | - return @self::with($source, $selector, $options); |
|
222 | - } |
|
221 | + return @self::with($source, $selector, $options); |
|
222 | + } |
|
223 | 223 | |
224 | - /** |
|
225 | - * Parse HTML5 documents. |
|
226 | - * |
|
227 | - * This uses HTML5-PHP to parse the document. In actuality, this parser does |
|
228 | - * a fine job with pre-HTML5 documents in most cases, though really old HTML |
|
229 | - * (like 2.0) may have some substantial quirks. |
|
230 | - * |
|
231 | - * <b>Supported Options</b> |
|
232 | - * Any options supported by HTML5-PHP are allowed here. Additionally, the |
|
233 | - * following options have meaning to QueryPath. |
|
234 | - * - QueryPath_class |
|
235 | - * |
|
236 | - * |
|
237 | - * @param mixed $source |
|
238 | - * A document as an HTML string, or a path/URL. For compatibility with |
|
239 | - * existing functions, a DOMDocument, SimpleXMLElement, DOMNode or array |
|
240 | - * of DOMNodes will be passed through as well. However, these types are not |
|
241 | - * validated in any way. |
|
242 | - * |
|
243 | - * @param string $selector |
|
244 | - * A CSS3 selector. |
|
245 | - * |
|
246 | - * @param array $options |
|
247 | - * An associative array of options, which is passed on into HTML5-PHP. Note |
|
248 | - * that the standard QueryPath options may be ignored for this function, |
|
249 | - * since it uses a different parser. |
|
250 | - * |
|
251 | - * @return QueryPath |
|
252 | - */ |
|
253 | - public static function withHTML5($source = null, $selector = '', $options = []) |
|
254 | - { |
|
255 | - $qpClass = $options['QueryPath_class'] ?? '\QueryPath\DOMQuery'; |
|
224 | + /** |
|
225 | + * Parse HTML5 documents. |
|
226 | + * |
|
227 | + * This uses HTML5-PHP to parse the document. In actuality, this parser does |
|
228 | + * a fine job with pre-HTML5 documents in most cases, though really old HTML |
|
229 | + * (like 2.0) may have some substantial quirks. |
|
230 | + * |
|
231 | + * <b>Supported Options</b> |
|
232 | + * Any options supported by HTML5-PHP are allowed here. Additionally, the |
|
233 | + * following options have meaning to QueryPath. |
|
234 | + * - QueryPath_class |
|
235 | + * |
|
236 | + * |
|
237 | + * @param mixed $source |
|
238 | + * A document as an HTML string, or a path/URL. For compatibility with |
|
239 | + * existing functions, a DOMDocument, SimpleXMLElement, DOMNode or array |
|
240 | + * of DOMNodes will be passed through as well. However, these types are not |
|
241 | + * validated in any way. |
|
242 | + * |
|
243 | + * @param string $selector |
|
244 | + * A CSS3 selector. |
|
245 | + * |
|
246 | + * @param array $options |
|
247 | + * An associative array of options, which is passed on into HTML5-PHP. Note |
|
248 | + * that the standard QueryPath options may be ignored for this function, |
|
249 | + * since it uses a different parser. |
|
250 | + * |
|
251 | + * @return QueryPath |
|
252 | + */ |
|
253 | + public static function withHTML5($source = null, $selector = '', $options = []) |
|
254 | + { |
|
255 | + $qpClass = $options['QueryPath_class'] ?? '\QueryPath\DOMQuery'; |
|
256 | 256 | |
257 | - if (is_string($source)) { |
|
258 | - $html5 = new HTML5(); |
|
259 | - if (strpos($source, '<') !== false && strpos($source, '>') !== false) { |
|
260 | - $source = $html5->loadHTML($source); |
|
261 | - } else { |
|
262 | - $source = $html5->load($source); |
|
263 | - } |
|
264 | - } |
|
257 | + if (is_string($source)) { |
|
258 | + $html5 = new HTML5(); |
|
259 | + if (strpos($source, '<') !== false && strpos($source, '>') !== false) { |
|
260 | + $source = $html5->loadHTML($source); |
|
261 | + } else { |
|
262 | + $source = $html5->load($source); |
|
263 | + } |
|
264 | + } |
|
265 | 265 | |
266 | - $qp = new $qpClass($source, $selector, $options); |
|
266 | + $qp = new $qpClass($source, $selector, $options); |
|
267 | 267 | |
268 | - return $qp; |
|
269 | - } |
|
268 | + return $qp; |
|
269 | + } |
|
270 | 270 | |
271 | - /** |
|
272 | - * Enable one or more extensions. |
|
273 | - * |
|
274 | - * Extensions provide additional features to QueryPath. To enable and |
|
275 | - * extension, you can use this method. |
|
276 | - * |
|
277 | - * In this example, we enable the QPTPL extension: |
|
278 | - * |
|
279 | - * @code |
|
280 | - * <?php |
|
281 | - * QueryPath::enable('\QueryPath\QPTPL'); |
|
282 | - * ?> |
|
283 | - * @endcode |
|
284 | - * |
|
285 | - * Note that the name is a fully qualified class name. |
|
286 | - * |
|
287 | - * We can enable more than one extension at a time like this: |
|
288 | - * |
|
289 | - * @code |
|
290 | - * <?php |
|
291 | - * $extensions = array('\QueryPath\QPXML', '\QueryPath\QPDB'); |
|
292 | - * QueryPath::enable($extensions); |
|
293 | - * ?> |
|
294 | - * @endcode |
|
295 | - * |
|
296 | - * @attention If you are not using an autoloader, you will need to |
|
297 | - * manually `require` or `include` the files that contain the |
|
298 | - * extensions. |
|
299 | - * |
|
300 | - * @param mixed $extensionNames |
|
301 | - * The name of an extension or an array of extension names. |
|
302 | - * QueryPath assumes that these are extension class names, |
|
303 | - * and attempts to register these as QueryPath extensions. |
|
304 | - */ |
|
305 | - public static function enable($extensionNames): void |
|
306 | - { |
|
307 | - if (is_array($extensionNames)) { |
|
308 | - foreach ($extensionNames as $extension) { |
|
309 | - ExtensionRegistry::extend($extension); |
|
310 | - } |
|
311 | - } else { |
|
312 | - ExtensionRegistry::extend($extensionNames); |
|
313 | - } |
|
314 | - } |
|
271 | + /** |
|
272 | + * Enable one or more extensions. |
|
273 | + * |
|
274 | + * Extensions provide additional features to QueryPath. To enable and |
|
275 | + * extension, you can use this method. |
|
276 | + * |
|
277 | + * In this example, we enable the QPTPL extension: |
|
278 | + * |
|
279 | + * @code |
|
280 | + * <?php |
|
281 | + * QueryPath::enable('\QueryPath\QPTPL'); |
|
282 | + * ?> |
|
283 | + * @endcode |
|
284 | + * |
|
285 | + * Note that the name is a fully qualified class name. |
|
286 | + * |
|
287 | + * We can enable more than one extension at a time like this: |
|
288 | + * |
|
289 | + * @code |
|
290 | + * <?php |
|
291 | + * $extensions = array('\QueryPath\QPXML', '\QueryPath\QPDB'); |
|
292 | + * QueryPath::enable($extensions); |
|
293 | + * ?> |
|
294 | + * @endcode |
|
295 | + * |
|
296 | + * @attention If you are not using an autoloader, you will need to |
|
297 | + * manually `require` or `include` the files that contain the |
|
298 | + * extensions. |
|
299 | + * |
|
300 | + * @param mixed $extensionNames |
|
301 | + * The name of an extension or an array of extension names. |
|
302 | + * QueryPath assumes that these are extension class names, |
|
303 | + * and attempts to register these as QueryPath extensions. |
|
304 | + */ |
|
305 | + public static function enable($extensionNames): void |
|
306 | + { |
|
307 | + if (is_array($extensionNames)) { |
|
308 | + foreach ($extensionNames as $extension) { |
|
309 | + ExtensionRegistry::extend($extension); |
|
310 | + } |
|
311 | + } else { |
|
312 | + ExtensionRegistry::extend($extensionNames); |
|
313 | + } |
|
314 | + } |
|
315 | 315 | |
316 | - /** |
|
317 | - * Get a list of all of the enabled extensions. |
|
318 | - * |
|
319 | - * This example dumps a list of extensions to standard output: |
|
320 | - * |
|
321 | - * @code |
|
322 | - * <?php |
|
323 | - * $extensions = QueryPath::enabledExtensions(); |
|
324 | - * print_r($extensions); |
|
325 | - * ?> |
|
326 | - * @endcode |
|
327 | - * |
|
328 | - * @return array |
|
329 | - * An array of extension names. |
|
330 | - * |
|
331 | - * @see QueryPath::ExtensionRegistry |
|
332 | - */ |
|
333 | - public static function enabledExtensions(): array |
|
334 | - { |
|
335 | - return ExtensionRegistry::extensionNames(); |
|
336 | - } |
|
316 | + /** |
|
317 | + * Get a list of all of the enabled extensions. |
|
318 | + * |
|
319 | + * This example dumps a list of extensions to standard output: |
|
320 | + * |
|
321 | + * @code |
|
322 | + * <?php |
|
323 | + * $extensions = QueryPath::enabledExtensions(); |
|
324 | + * print_r($extensions); |
|
325 | + * ?> |
|
326 | + * @endcode |
|
327 | + * |
|
328 | + * @return array |
|
329 | + * An array of extension names. |
|
330 | + * |
|
331 | + * @see QueryPath::ExtensionRegistry |
|
332 | + */ |
|
333 | + public static function enabledExtensions(): array |
|
334 | + { |
|
335 | + return ExtensionRegistry::extensionNames(); |
|
336 | + } |
|
337 | 337 | |
338 | 338 | |
339 | - /** |
|
340 | - * A static function for transforming data into a Data URL. |
|
341 | - * |
|
342 | - * This can be used to create Data URLs for injection into CSS, JavaScript, or other |
|
343 | - * non-XML/HTML content. If you are working with QP objects, you may want to use |
|
344 | - * dataURL() instead. |
|
345 | - * |
|
346 | - * @param mixed $data |
|
347 | - * The contents to inject as the data. The value can be any one of the following: |
|
348 | - * - A URL: If this is given, then the subsystem will read the content from that URL. THIS |
|
349 | - * MUST BE A FULL URL, not a relative path. |
|
350 | - * - A string of data: If this is given, then the subsystem will encode the string. |
|
351 | - * - A stream or file handle: If this is given, the stream's contents will be encoded |
|
352 | - * and inserted as data. |
|
353 | - * (Note that we make the assumption here that you would never want to set data to be |
|
354 | - * a URL. If this is an incorrect assumption, file a bug.) |
|
355 | - * @param string $mime |
|
356 | - * The MIME type of the document. |
|
357 | - * @param resource $context |
|
358 | - * A valid context. Use this only if you need to pass a stream context. This is only necessary |
|
359 | - * if $data is a URL. (See {@link stream_context_create()}). |
|
360 | - * |
|
361 | - * @return string An encoded data URL. |
|
362 | - */ |
|
363 | - public static function encodeDataURL($data, $mime = 'application/octet-stream', $context = null): string |
|
364 | - { |
|
365 | - if (is_resource($data)) { |
|
366 | - $data = stream_get_contents($data); |
|
367 | - } elseif (filter_var($data, FILTER_VALIDATE_URL)) { |
|
368 | - $data = file_get_contents($data, false, $context); |
|
369 | - } |
|
339 | + /** |
|
340 | + * A static function for transforming data into a Data URL. |
|
341 | + * |
|
342 | + * This can be used to create Data URLs for injection into CSS, JavaScript, or other |
|
343 | + * non-XML/HTML content. If you are working with QP objects, you may want to use |
|
344 | + * dataURL() instead. |
|
345 | + * |
|
346 | + * @param mixed $data |
|
347 | + * The contents to inject as the data. The value can be any one of the following: |
|
348 | + * - A URL: If this is given, then the subsystem will read the content from that URL. THIS |
|
349 | + * MUST BE A FULL URL, not a relative path. |
|
350 | + * - A string of data: If this is given, then the subsystem will encode the string. |
|
351 | + * - A stream or file handle: If this is given, the stream's contents will be encoded |
|
352 | + * and inserted as data. |
|
353 | + * (Note that we make the assumption here that you would never want to set data to be |
|
354 | + * a URL. If this is an incorrect assumption, file a bug.) |
|
355 | + * @param string $mime |
|
356 | + * The MIME type of the document. |
|
357 | + * @param resource $context |
|
358 | + * A valid context. Use this only if you need to pass a stream context. This is only necessary |
|
359 | + * if $data is a URL. (See {@link stream_context_create()}). |
|
360 | + * |
|
361 | + * @return string An encoded data URL. |
|
362 | + */ |
|
363 | + public static function encodeDataURL($data, $mime = 'application/octet-stream', $context = null): string |
|
364 | + { |
|
365 | + if (is_resource($data)) { |
|
366 | + $data = stream_get_contents($data); |
|
367 | + } elseif (filter_var($data, FILTER_VALIDATE_URL)) { |
|
368 | + $data = file_get_contents($data, false, $context); |
|
369 | + } |
|
370 | 370 | |
371 | - $encoded = base64_encode($data); |
|
371 | + $encoded = base64_encode($data); |
|
372 | 372 | |
373 | - return 'data:' . $mime . ';base64,' . $encoded; |
|
374 | - } |
|
373 | + return 'data:' . $mime . ';base64,' . $encoded; |
|
374 | + } |
|
375 | 375 | } |