1 | <?php declare(strict_types=1); |
||||||
2 | |||||||
3 | namespace DOMWrap\Traits; |
||||||
4 | |||||||
5 | use DOMWrap\{ |
||||||
6 | Text, |
||||||
7 | Element, |
||||||
8 | NodeList |
||||||
9 | }; |
||||||
10 | |||||||
11 | /** |
||||||
12 | * Manipulation Trait |
||||||
13 | * |
||||||
14 | * @package DOMWrap\Traits |
||||||
15 | * @license http://opensource.org/licenses/BSD-3-Clause BSD 3 Clause |
||||||
16 | */ |
||||||
17 | trait ManipulationTrait |
||||||
18 | { |
||||||
19 | /** |
||||||
20 | * Magic method - Trap function names using reserved keyword (empty, clone, etc..) |
||||||
21 | * |
||||||
22 | * @param string $name |
||||||
23 | * @param array $arguments |
||||||
24 | * |
||||||
25 | * @return mixed |
||||||
26 | */ |
||||||
27 | 8 | public function __call(string $name, array $arguments) { |
|||||
28 | 8 | if (!method_exists($this, '_' . $name)) { |
|||||
29 | 1 | throw new \BadMethodCallException("Call to undefined method " . get_class($this) . '::' . $name . "()"); |
|||||
30 | } |
||||||
31 | |||||||
32 | 7 | return call_user_func_array([$this, '_' . $name], $arguments); |
|||||
33 | } |
||||||
34 | |||||||
35 | /** |
||||||
36 | * @return string |
||||||
37 | */ |
||||||
38 | public function __toString(): string { |
||||||
39 | return $this->getOuterHtml(true); |
||||||
40 | 61 | } |
|||||
41 | 61 | ||||||
42 | 34 | /** |
|||||
43 | 50 | * @param string|NodeList|\DOMNode $input |
|||||
44 | 45 | * |
|||||
45 | 13 | * @return iterable |
|||||
46 | 13 | */ |
|||||
47 | protected function inputPrepareAsTraversable($input): iterable { |
||||||
48 | if ($input instanceof \DOMNode) { |
||||||
49 | // Handle raw \DOMNode elements and 'convert' them into their DOMWrap/* counterpart |
||||||
50 | if (!method_exists($input, 'inputPrepareAsTraversable')) { |
||||||
51 | 61 | $input = $this->document()->importNode($input, true); |
|||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||||
52 | } |
||||||
53 | |||||||
54 | $nodes = [$input]; |
||||||
55 | } else if (is_string($input)) { |
||||||
56 | $nodes = $this->nodesFromHtml($input); |
||||||
57 | } else if (is_iterable($input)) { |
||||||
58 | $nodes = $input; |
||||||
59 | 61 | } else { |
|||||
60 | 61 | throw new \InvalidArgumentException(); |
|||||
61 | } |
||||||
62 | 61 | ||||||
63 | return $nodes; |
||||||
64 | 61 | } |
|||||
65 | 60 | ||||||
66 | 49 | /** |
|||||
67 | * @param string|NodeList|\DOMNode $input |
||||||
68 | 60 | * @param bool $cloneForManipulate |
|||||
69 | * |
||||||
70 | * @return NodeList |
||||||
71 | */ |
||||||
72 | 61 | protected function inputAsNodeList($input, $cloneForManipulate = true): NodeList { |
|||||
73 | $nodes = $this->inputPrepareAsTraversable($input); |
||||||
74 | |||||||
75 | $newNodes = $this->newNodeList(); |
||||||
0 ignored issues
–
show
The method
newNodeList() does not exist on DOMWrap\Traits\ManipulationTrait . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
76 | |||||||
77 | foreach ($nodes as $node) { |
||||||
78 | if ($node->document() !== $this->document()) { |
||||||
79 | $node = $this->document()->importNode($node, true); |
||||||
80 | 22 | } |
|||||
81 | 22 | ||||||
82 | if ($cloneForManipulate && $node->parentNode !== null) { |
||||||
83 | 22 | $node = $node->cloneNode(true); |
|||||
84 | } |
||||||
85 | |||||||
86 | $newNodes[] = $node; |
||||||
87 | } |
||||||
88 | |||||||
89 | return $newNodes; |
||||||
90 | } |
||||||
91 | 45 | ||||||
92 | 45 | /** |
|||||
93 | 45 | * @param string|NodeList|\DOMNode $input |
|||||
94 | 45 | * |
|||||
95 | * @return \DOMNode|null |
||||||
96 | 45 | */ |
|||||
97 | protected function inputAsFirstNode($input): ?\DOMNode { |
||||||
98 | $nodes = $this->inputAsNodeList($input); |
||||||
99 | |||||||
100 | return $nodes->findXPath('self::*')->first(); |
||||||
101 | } |
||||||
102 | |||||||
103 | /** |
||||||
104 | * @param string $html |
||||||
105 | * |
||||||
106 | 53 | * @return NodeList |
|||||
107 | 51 | */ |
|||||
108 | protected function nodesFromHtml($html): NodeList { |
||||||
109 | 51 | $class = get_class($this->document()); |
|||||
110 | 6 | $doc = new $class(); |
|||||
111 | $doc->setEncoding($this->document()->getEncoding()); |
||||||
112 | $nodes = $doc->html($html)->find('body > *'); |
||||||
113 | 51 | ||||||
114 | return $nodes; |
||||||
115 | 51 | } |
|||||
116 | 53 | ||||||
117 | /** |
||||||
118 | 53 | * @param string|NodeList|\DOMNode|callable $input |
|||||
119 | * @param callable $callback |
||||||
120 | * |
||||||
121 | * @return self |
||||||
122 | */ |
||||||
123 | protected function manipulateNodesWithInput($input, callable $callback): self { |
||||||
124 | $this->collection()->each(function($node, $index) use ($input, $callback) { |
||||||
0 ignored issues
–
show
The method
collection() does not exist on DOMWrap\Traits\ManipulationTrait . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
125 | $html = $input; |
||||||
126 | 40 | ||||||
127 | 40 | /*if ($input instanceof \DOMNode) { |
|||||
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
56% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. ![]() |
|||||||
128 | 1 | if ($input->parentNode !== null) { |
|||||
129 | $html = $input->cloneNode(true); |
||||||
130 | 39 | } |
|||||
131 | } else*/if (is_callable($input)) { |
||||||
132 | $html = $input($node, $index); |
||||||
133 | 40 | } |
|||||
134 | |||||||
135 | 40 | $newNodes = $this->inputAsNodeList($html); |
|||||
136 | 36 | ||||||
137 | 36 | $callback($node, $newNodes); |
|||||
138 | }); |
||||||
139 | 40 | ||||||
140 | return $this; |
||||||
141 | 40 | } |
|||||
142 | |||||||
143 | 40 | /** |
|||||
144 | * @param string|null $selector |
||||||
145 | * |
||||||
146 | * @return NodeList |
||||||
147 | */ |
||||||
148 | public function detach(string $selector = null): NodeList { |
||||||
149 | if (!is_null($selector)) { |
||||||
150 | $nodes = $this->find($selector, 'self::'); |
||||||
0 ignored issues
–
show
The method
find() does not exist on DOMWrap\Traits\ManipulationTrait . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
151 | 35 | } else { |
|||||
152 | 35 | $nodes = $this->collection(); |
|||||
153 | } |
||||||
154 | 35 | ||||||
155 | $nodeList = $this->newNodeList(); |
||||||
156 | |||||||
157 | $nodes->each(function($node) use($nodeList) { |
||||||
158 | if ($node->parent() instanceof \DOMNode) { |
||||||
159 | $nodeList[] = $node->parent()->removeChild($node); |
||||||
160 | } |
||||||
161 | }); |
||||||
162 | |||||||
163 | 3 | $nodes->fromArray([]); |
|||||
164 | 3 | ||||||
165 | 3 | return $nodeList; |
|||||
166 | } |
||||||
167 | 3 | ||||||
168 | /** |
||||||
169 | 3 | * @param string|null $selector |
|||||
170 | * |
||||||
171 | * @return self |
||||||
172 | */ |
||||||
173 | public function destroy(string $selector = null): self { |
||||||
174 | $this->detach($selector); |
||||||
175 | |||||||
176 | return $this; |
||||||
177 | 14 | } |
|||||
178 | 14 | ||||||
179 | 9 | /** |
|||||
180 | * @param string|NodeList|\DOMNode|callable $input |
||||||
181 | 5 | * |
|||||
182 | * @return self |
||||||
183 | */ |
||||||
184 | public function substituteWith($input): self { |
||||||
185 | $this->manipulateNodesWithInput($input, function($node, $newNodes) { |
||||||
186 | foreach ($newNodes as $newNode) { |
||||||
187 | $node->parent()->replaceChild($newNode, $node); |
||||||
188 | } |
||||||
189 | 13 | }); |
|||||
190 | 13 | ||||||
191 | 13 | return $this; |
|||||
192 | } |
||||||
193 | |||||||
194 | /** |
||||||
195 | * @param string|NodeList|\DOMNode|callable $input |
||||||
196 | * |
||||||
197 | * @return string|self |
||||||
198 | */ |
||||||
199 | 5 | public function text($input = null) { |
|||||
200 | 5 | if (is_null($input)) { |
|||||
201 | 4 | return $this->getText(); |
|||||
202 | } else { |
||||||
203 | return $this->setText($input); |
||||||
204 | 5 | } |
|||||
205 | } |
||||||
206 | 4 | ||||||
207 | /** |
||||||
208 | * @return string |
||||||
209 | 4 | */ |
|||||
210 | 5 | public function getText(): string { |
|||||
211 | return (string)$this->collection()->reduce(function($carry, $node) { |
||||||
212 | 5 | return $carry . $node->textContent; |
|||||
213 | }, ''); |
||||||
214 | } |
||||||
215 | |||||||
216 | /** |
||||||
217 | * @param string|NodeList|\DOMNode|callable $input |
||||||
218 | * |
||||||
219 | * @return self |
||||||
220 | */ |
||||||
221 | 12 | public function setText($input): self { |
|||||
222 | 12 | if (is_string($input)) { |
|||||
223 | 12 | $input = new Text($input); |
|||||
224 | } |
||||||
225 | 12 | ||||||
226 | $this->manipulateNodesWithInput($input, function($node, $newNodes) { |
||||||
227 | 12 | // Remove old contents from the current node. |
|||||
228 | $node->contents()->destroy(); |
||||||
229 | |||||||
230 | // Add new contents in it's place. |
||||||
231 | $node->appendWith(new Text($newNodes->getText())); |
||||||
232 | }); |
||||||
233 | |||||||
234 | return $this; |
||||||
235 | } |
||||||
236 | 12 | ||||||
237 | 12 | /** |
|||||
238 | 12 | * @param string|NodeList|\DOMNode|callable $input |
|||||
239 | 12 | * |
|||||
240 | * @return self |
||||||
241 | 12 | */ |
|||||
242 | public function precede($input): self { |
||||||
243 | $this->manipulateNodesWithInput($input, function($node, $newNodes) { |
||||||
244 | 12 | foreach ($newNodes as $newNode) { |
|||||
245 | $node->parent()->insertBefore($newNode, $node); |
||||||
246 | 12 | } |
|||||
247 | }); |
||||||
248 | |||||||
249 | return $this; |
||||||
250 | } |
||||||
251 | |||||||
252 | /** |
||||||
253 | * @param string|NodeList|\DOMNode|callable $input |
||||||
254 | * |
||||||
255 | 4 | * @return self |
|||||
256 | 4 | */ |
|||||
257 | 4 | public function follow($input): self { |
|||||
258 | $this->manipulateNodesWithInput($input, function($node, $newNodes) { |
||||||
259 | 4 | foreach ($newNodes as $newNode) { |
|||||
260 | if (is_null($node->following())) { |
||||||
261 | 4 | $node->parent()->appendChild($newNode); |
|||||
262 | } else { |
||||||
263 | $node->parent()->insertBefore($newNode, $node->following()); |
||||||
264 | } |
||||||
265 | } |
||||||
266 | }); |
||||||
267 | |||||||
268 | return $this; |
||||||
269 | } |
||||||
270 | 33 | ||||||
271 | 33 | /** |
|||||
272 | 33 | * @param string|NodeList|\DOMNode|callable $input |
|||||
273 | * |
||||||
274 | 33 | * @return self |
|||||
275 | */ |
||||||
276 | 33 | public function prependWith($input): self { |
|||||
277 | $this->manipulateNodesWithInput($input, function($node, $newNodes) { |
||||||
278 | foreach ($newNodes as $newNode) { |
||||||
279 | $node->insertBefore($newNode, $node->contents()->first()); |
||||||
280 | } |
||||||
281 | }); |
||||||
282 | |||||||
283 | 4 | return $this; |
|||||
284 | 3 | } |
|||||
285 | 4 | ||||||
286 | /** |
||||||
287 | 4 | * @param string|NodeList|\DOMNode|callable $input |
|||||
288 | * |
||||||
289 | * @return self |
||||||
290 | */ |
||||||
291 | public function appendWith($input): self { |
||||||
292 | $this->manipulateNodesWithInput($input, function($node, $newNodes) { |
||||||
293 | 3 | foreach ($newNodes as $newNode) { |
|||||
294 | 3 | $node->appendChild($newNode); |
|||||
295 | } |
||||||
296 | 3 | }); |
|||||
297 | 3 | ||||||
298 | 3 | return $this; |
|||||
299 | } |
||||||
300 | 3 | ||||||
301 | /** |
||||||
302 | * @param string|NodeList|\DOMNode $selector |
||||||
303 | * |
||||||
304 | * @return self |
||||||
305 | */ |
||||||
306 | public function prependTo($selector): self { |
||||||
307 | if ($selector instanceof \DOMNode || $selector instanceof NodeList) { |
||||||
308 | $nodes = $this->inputAsNodeList($selector); |
||||||
309 | 5 | } else { |
|||||
310 | 4 | $nodes = $this->document()->find($selector); |
|||||
311 | 4 | } |
|||||
312 | |||||||
313 | 5 | $nodes->prependWith($this); |
|||||
314 | |||||||
315 | 5 | return $this; |
|||||
316 | } |
||||||
317 | |||||||
318 | /** |
||||||
319 | * @param string|NodeList|\DOMNode $selector |
||||||
320 | * |
||||||
321 | * @return self |
||||||
322 | */ |
||||||
323 | public function appendTo($selector): self { |
||||||
324 | if ($selector instanceof \DOMNode || $selector instanceof NodeList) { |
||||||
325 | $nodes = $this->inputAsNodeList($selector); |
||||||
326 | } else { |
||||||
327 | $nodes = $this->document()->find($selector); |
||||||
328 | } |
||||||
329 | |||||||
330 | $nodes->appendWith($this); |
||||||
331 | |||||||
332 | return $this; |
||||||
333 | } |
||||||
334 | |||||||
335 | /** |
||||||
336 | * @return self |
||||||
337 | */ |
||||||
338 | public function _empty(): self { |
||||||
339 | $this->collection()->each(function($node) { |
||||||
340 | 18 | $node->contents()->destroy(); |
|||||
341 | 18 | }); |
|||||
342 | |||||||
343 | 18 | return $this; |
|||||
344 | 1 | } |
|||||
345 | |||||||
346 | /** |
||||||
347 | 17 | * @return NodeList|\DOMNode |
|||||
348 | */ |
||||||
349 | 17 | public function _clone() { |
|||||
350 | 6 | $clonedNodes = $this->newNodeList(); |
|||||
351 | |||||||
352 | $this->collection()->each(function($node) use($clonedNodes) { |
||||||
353 | 13 | $clonedNodes[] = $node->cloneNode(true); |
|||||
354 | }); |
||||||
355 | |||||||
356 | return $this->result($clonedNodes); |
||||||
0 ignored issues
–
show
The method
result() does not exist on DOMWrap\Traits\ManipulationTrait . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
357 | } |
||||||
358 | |||||||
359 | /** |
||||||
360 | * @param string $name |
||||||
361 | * |
||||||
362 | * @return self |
||||||
363 | */ |
||||||
364 | public function removeAttr(string $name): self { |
||||||
365 | 5 | $this->collection()->each(function($node) use($name) { |
|||||
366 | 3 | if ($node instanceof \DOMElement) { |
|||||
367 | 3 | $node->removeAttribute($name); |
|||||
368 | } |
||||||
369 | 5 | }); |
|||||
370 | |||||||
371 | 5 | return $this; |
|||||
372 | } |
||||||
373 | |||||||
374 | /** |
||||||
375 | * @param string $name |
||||||
376 | * |
||||||
377 | * @return bool |
||||||
378 | */ |
||||||
379 | public function hasAttr(string $name): bool { |
||||||
380 | 17 | return (bool)$this->collection()->reduce(function($carry, $node) use ($name) { |
|||||
381 | 17 | if ($node->hasAttribute($name)) { |
|||||
382 | 12 | return true; |
|||||
383 | } |
||||||
384 | 5 | ||||||
385 | return $carry; |
||||||
386 | }, false); |
||||||
387 | } |
||||||
388 | |||||||
389 | /** |
||||||
390 | * @internal |
||||||
391 | * |
||||||
392 | * @param string $name |
||||||
393 | * |
||||||
394 | * @return string |
||||||
395 | */ |
||||||
396 | 15 | public function getAttr(string $name): string { |
|||||
397 | 15 | $node = $this->collection()->first(); |
|||||
398 | 15 | ||||||
399 | if (!($node instanceof \DOMElement)) { |
||||||
400 | 15 | return ''; |
|||||
401 | 2 | } |
|||||
402 | |||||||
403 | return $node->getAttribute($name); |
||||||
404 | } |
||||||
405 | 15 | ||||||
406 | 15 | /** |
|||||
407 | 10 | * @internal |
|||||
408 | * |
||||||
409 | * @param string $name |
||||||
410 | 11 | * @param mixed $value |
|||||
411 | 15 | * |
|||||
412 | * @return self |
||||||
413 | */ |
||||||
414 | 15 | public function setAttr(string $name, $value): self { |
|||||
415 | 7 | $this->collection()->each(function($node) use($name, $value) { |
|||||
416 | if ($node instanceof \DOMElement) { |
||||||
417 | $node->setAttribute($name, (string)$value); |
||||||
418 | } |
||||||
419 | }); |
||||||
420 | |||||||
421 | return $this; |
||||||
422 | 15 | } |
|||||
423 | 13 | ||||||
424 | /** |
||||||
425 | * @param string $name |
||||||
426 | 15 | * @param mixed $value |
|||||
427 | 15 | * |
|||||
428 | * @return self|string |
||||||
429 | */ |
||||||
430 | public function attr(string $name, $value = null) { |
||||||
431 | if (is_null($value)) { |
||||||
432 | return $this->getAttr($name); |
||||||
433 | } else { |
||||||
434 | 7 | return $this->setAttr($name, $value); |
|||||
435 | 7 | } |
|||||
436 | } |
||||||
437 | 7 | ||||||
438 | /** |
||||||
439 | * @internal |
||||||
440 | * |
||||||
441 | * @param string $name |
||||||
442 | * @param string|callable $value |
||||||
443 | * @param bool $addValue |
||||||
444 | */ |
||||||
445 | 8 | protected function _pushAttrValue(string $name, $value, bool $addValue = false): void { |
|||||
446 | 8 | $this->collection()->each(function($node, $index) use($name, $value, $addValue) { |
|||||
447 | if ($node instanceof \DOMElement) { |
||||||
448 | 8 | $attr = $node->getAttribute($name); |
|||||
449 | |||||||
450 | if (is_callable($value)) { |
||||||
451 | $value = $value($node, $index, $attr); |
||||||
452 | } |
||||||
453 | |||||||
454 | // Remove any existing instances of the value, or empty values. |
||||||
455 | $values = array_filter(explode(' ', $attr), function($_value) use($value) { |
||||||
456 | if (strcasecmp($_value, $value) == 0 || empty($_value)) { |
||||||
457 | 6 | return false; |
|||||
458 | 6 | } |
|||||
459 | |||||||
460 | 6 | return true; |
|||||
461 | 6 | }); |
|||||
462 | 2 | ||||||
463 | // If required add attr value to array |
||||||
464 | if ($addValue) { |
||||||
465 | 6 | $values[] = $value; |
|||||
466 | 6 | } |
|||||
467 | 6 | ||||||
468 | // Set the attr if we either have values, or the attr already |
||||||
469 | // existed (we might be removing classes). |
||||||
470 | // |
||||||
471 | // Don't set the attr if it doesn't already exist. |
||||||
472 | if (!empty($values) || $node->hasAttribute($name)) { |
||||||
473 | $node->setAttribute($name, implode(' ', $values)); |
||||||
474 | } |
||||||
475 | 21 | } |
|||||
476 | 21 | }); |
|||||
477 | } |
||||||
478 | |||||||
479 | /** |
||||||
480 | 21 | * @param string|callable $class |
|||||
481 | * |
||||||
482 | * @return self |
||||||
483 | 21 | */ |
|||||
484 | 21 | public function addClass($class): self { |
|||||
485 | $this->_pushAttrValue('class', $class, true); |
||||||
486 | |||||||
487 | 21 | return $this; |
|||||
488 | } |
||||||
489 | |||||||
490 | /** |
||||||
491 | * @param string|callable $class |
||||||
492 | * |
||||||
493 | * @return self |
||||||
494 | */ |
||||||
495 | 21 | public function removeClass($class): self { |
|||||
496 | $this->_pushAttrValue('class', $class); |
||||||
497 | |||||||
498 | 21 | return $this; |
|||||
499 | } |
||||||
500 | |||||||
501 | 21 | /** |
|||||
502 | 21 | * @param string $class |
|||||
503 | * |
||||||
504 | * @return bool |
||||||
505 | 21 | */ |
|||||
506 | public function hasClass(string $class): bool { |
||||||
507 | return (bool)$this->collection()->reduce(function($carry, $node) use ($class) { |
||||||
508 | $attr = $node->getAttr('class'); |
||||||
509 | |||||||
510 | return array_reduce(explode(' ', (string)$attr), function($carry, $item) use ($class) { |
||||||
511 | if (strcasecmp($item, $class) == 0) { |
||||||
512 | return true; |
||||||
513 | 18 | } |
|||||
514 | 16 | ||||||
515 | return $carry; |
||||||
516 | 16 | }, false); |
|||||
517 | }, false); |
||||||
518 | } |
||||||
519 | |||||||
520 | 16 | /** |
|||||
521 | * @param Element $node |
||||||
522 | 16 | * |
|||||
523 | * @return \SplStack |
||||||
524 | 16 | */ |
|||||
525 | protected function _getFirstChildWrapStack(Element $node): \SplStack { |
||||||
526 | 16 | $stack = new \SplStack; |
|||||
527 | |||||||
528 | 18 | do { |
|||||
529 | 18 | // Push our current node onto the stack |
|||||
530 | $stack->push($node); |
||||||
531 | |||||||
532 | // Get the first element child node |
||||||
533 | $node = $node->children()->first(); |
||||||
534 | } while ($node instanceof Element); |
||||||
535 | |||||||
536 | // Get the top most node. |
||||||
537 | 9 | return $stack; |
|||||
538 | 8 | } |
|||||
539 | |||||||
540 | 8 | /** |
|||||
541 | * @param Element $node |
||||||
542 | * |
||||||
543 | 8 | * @return \SplStack |
|||||
544 | */ |
||||||
545 | protected function _prepareWrapStack(Element $node): \SplStack { |
||||||
546 | // Generate a stack (root to leaf) of the wrapper. |
||||||
547 | 8 | // Includes only first element nodes / first element children. |
|||||
548 | 9 | $stackNodes = $this->_getFirstChildWrapStack($node); |
|||||
549 | |||||||
550 | 9 | // Only using the first element, remove any siblings. |
|||||
551 | foreach ($stackNodes as $stackNode) { |
||||||
552 | $stackNode->siblings()->destroy(); |
||||||
553 | } |
||||||
554 | |||||||
555 | return $stackNodes; |
||||||
556 | } |
||||||
557 | |||||||
558 | /** |
||||||
559 | 9 | * @param string|NodeList|\DOMNode|callable $input |
|||||
560 | * @param callable $callback |
||||||
561 | 8 | */ |
|||||
562 | protected function wrapWithInputByCallback($input, callable $callback): void { |
||||||
563 | $this->collection()->each(function($node, $index) use ($input, $callback) { |
||||||
564 | 8 | $html = $input; |
|||||
565 | |||||||
566 | if (is_callable($input)) { |
||||||
567 | 8 | $html = $input($node, $index); |
|||||
568 | 9 | } |
|||||
569 | |||||||
570 | 9 | $inputNode = $this->inputAsFirstNode($html); |
|||||
571 | |||||||
572 | if ($inputNode instanceof Element) { |
||||||
573 | // Pre-process wrapper into a stack of first element nodes. |
||||||
574 | $stackNodes = $this->_prepareWrapStack($inputNode); |
||||||
575 | |||||||
576 | $callback($node, $stackNodes); |
||||||
577 | } |
||||||
578 | 7 | }); |
|||||
579 | 7 | } |
|||||
580 | 1 | ||||||
581 | /** |
||||||
582 | * @param string|NodeList|\DOMNode|callable $input |
||||||
583 | 6 | * |
|||||
584 | * @return self |
||||||
585 | */ |
||||||
586 | public function wrapInner($input): self { |
||||||
587 | 6 | $this->wrapWithInputByCallback($input, function($node, $stackNodes) { |
|||||
588 | foreach ($node->contents() as $child) { |
||||||
589 | 6 | // Remove child from the current node |
|||||
590 | 1 | $oldChild = $child->detach()->first(); |
|||||
591 | |||||||
592 | // Add it back as a child of the top (leaf) node on the stack |
||||||
593 | 5 | $stackNodes->top()->appendWith($oldChild); |
|||||
594 | } |
||||||
595 | |||||||
596 | 5 | // Add the bottom (root) node on the stack |
|||||
597 | $node->appendWith($stackNodes->bottom()); |
||||||
598 | 5 | }); |
|||||
599 | |||||||
600 | 5 | return $this; |
|||||
601 | 5 | } |
|||||
602 | |||||||
603 | 5 | /** |
|||||
604 | * @param string|NodeList|\DOMNode|callable $input |
||||||
605 | * |
||||||
606 | * @return self |
||||||
607 | */ |
||||||
608 | public function wrap($input): self { |
||||||
609 | $this->wrapWithInputByCallback($input, function($node, $stackNodes) { |
||||||
610 | 4 | // Add the new bottom (root) node after the current node |
|||||
611 | 3 | $node->follow($stackNodes->bottom()); |
|||||
612 | |||||||
613 | // Remove the current node |
||||||
614 | 3 | $oldNode = $node->detach()->first(); |
|||||
615 | 3 | ||||||
616 | // Add the 'current node' back inside the new top (leaf) node. |
||||||
617 | 3 | $stackNodes->top()->appendWith($oldNode); |
|||||
618 | 3 | }); |
|||||
619 | |||||||
620 | 3 | return $this; |
|||||
621 | 4 | } |
|||||
622 | |||||||
623 | 4 | /** |
|||||
624 | * @param string|NodeList|\DOMNode|callable $input |
||||||
625 | * |
||||||
626 | * @return self |
||||||
627 | */ |
||||||
628 | public function wrapAll($input): self { |
||||||
629 | 2 | if (!$this->collection()->count()) { |
|||||
630 | 2 | return $this; |
|||||
631 | 2 | } |
|||||
632 | |||||||
633 | if (is_callable($input)) { |
||||||
634 | $input = $input($this->collection()->first()); |
||||||
635 | } |
||||||
636 | |||||||
637 | $inputNode = $this->inputAsFirstNode($input); |
||||||
638 | |||||||
639 | 1 | if (!($inputNode instanceof Element)) { |
|||||
640 | 1 | return $this; |
|||||
641 | 1 | } |
|||||
642 | |||||||
643 | $stackNodes = $this->_prepareWrapStack($inputNode); |
||||||
644 | |||||||
645 | // Add the new bottom (root) node before the first matched node |
||||||
646 | $this->collection()->first()->precede($stackNodes->bottom()); |
||||||
647 | |||||||
648 | $this->collection()->each(function($node) use ($stackNodes) { |
||||||
649 | // Detach and add node back inside the new wrappers top (leaf) node. |
||||||
650 | 5 | $stackNodes->top()->appendWith($node->detach()); |
|||||
651 | }); |
||||||
652 | 4 | ||||||
653 | return $this; |
||||||
654 | } |
||||||
655 | 4 | ||||||
656 | 5 | /** |
|||||
657 | * @return self |
||||||
658 | 5 | */ |
|||||
659 | public function unwrap(): self { |
||||||
660 | $this->collection()->each(function($node) { |
||||||
661 | $parent = $node->parent(); |
||||||
662 | |||||||
663 | // Replace parent node (the one we're unwrapping) with it's children. |
||||||
664 | $parent->contents()->each(function($childNode) use($parent) { |
||||||
665 | $oldChildNode = $childNode->detach()->first(); |
||||||
666 | 140 | ||||||
667 | 140 | $parent->precede($oldChildNode); |
|||||
668 | 2 | }); |
|||||
669 | |||||||
670 | 140 | $parent->destroy(); |
|||||
671 | }); |
||||||
672 | |||||||
673 | return $this; |
||||||
674 | } |
||||||
675 | |||||||
676 | /** |
||||||
677 | * @param int $isIncludeAll |
||||||
678 | * |
||||||
679 | * @return string |
||||||
680 | */ |
||||||
681 | public function getOuterHtml(bool $isIncludeAll = false): string { |
||||||
682 | $nodes = $this->collection(); |
||||||
683 | |||||||
684 | if (!$isIncludeAll) { |
||||||
685 | $nodes = $this->newNodeList([$nodes->first()]); |
||||||
686 | } |
||||||
687 | |||||||
688 | return $nodes->reduce(function($carry, $node) { |
||||||
689 | return $carry . $this->document()->saveHTML($node); |
||||||
690 | }, ''); |
||||||
691 | } |
||||||
692 | |||||||
693 | /** |
||||||
694 | * @param int $isIncludeAll |
||||||
695 | * |
||||||
696 | * @return string |
||||||
697 | */ |
||||||
698 | public function getHtml(bool $isIncludeAll = false): string { |
||||||
699 | $nodes = $this->collection(); |
||||||
700 | |||||||
701 | if (!$isIncludeAll) { |
||||||
702 | $nodes = $this->newNodeList([$nodes->first()]); |
||||||
703 | } |
||||||
704 | |||||||
705 | return $nodes->contents()->reduce(function($carry, $node) { |
||||||
706 | return $carry . $this->document()->saveHTML($node); |
||||||
707 | }, ''); |
||||||
708 | } |
||||||
709 | |||||||
710 | /** |
||||||
711 | * @param string|NodeList|\DOMNode|callable $input |
||||||
712 | * |
||||||
713 | * @return self |
||||||
714 | */ |
||||||
715 | public function setHtml($input): self { |
||||||
716 | $this->manipulateNodesWithInput($input, function($node, $newNodes) { |
||||||
717 | // Remove old contents from the current node. |
||||||
718 | $node->contents()->destroy(); |
||||||
719 | |||||||
720 | // Add new contents in it's place. |
||||||
721 | $node->appendWith($newNodes); |
||||||
722 | }); |
||||||
723 | |||||||
724 | return $this; |
||||||
725 | } |
||||||
726 | |||||||
727 | /** |
||||||
728 | * @param string|NodeList|\DOMNode|callable $input |
||||||
729 | * |
||||||
730 | * @return string|self |
||||||
731 | */ |
||||||
732 | public function html($input = null) { |
||||||
733 | if (is_null($input)) { |
||||||
734 | return $this->getHtml(); |
||||||
735 | } else { |
||||||
736 | return $this->setHtml($input); |
||||||
737 | } |
||||||
738 | } |
||||||
739 | |||||||
740 | /** |
||||||
741 | * @param string|NodeList|\DOMNode $input |
||||||
742 | * |
||||||
743 | * @return NodeList |
||||||
744 | */ |
||||||
745 | public function create($input): NodeList { |
||||||
746 | return $this->inputAsNodeList($input); |
||||||
747 | } |
||||||
748 | } |