Passed
Branch scrutinizer (dd8772)
by Thomas
03:03
created

HtmlNode::hasAttr()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 7
rs 10
c 1
b 1
f 0
1
<?php
2
3
namespace Sulao\HtmlQuery;
4
5
use DOMDocument, DOMElement, DOMNode;
6
7
/**
8
 * Class HtmlNode
9
 *
10
 * @package Sulao\HtmlQuery
11
 */
12
class HtmlNode
13
{
14
    /**
15
     * @var DOMNode
16
     */
17
    protected $node;
18
19
    /**
20
     * Node constructor.
21
     *
22
     * @param DOMNode $node
23
     */
24
    public function __construct(DOMNode $node)
25
    {
26
        $this->node = $node;
27
    }
28
29
    /**
30
     * Get the outer HTML content.
31
     *
32
     * @return string|null
33
     */
34
    public function outerHtml()
35
    {
36
        return $this->getDoc()->saveHTML($this->node);
37
    }
38
39
    /**
40
     * Get the inner HTML content.
41
     *
42
     * @return string|null
43
     */
44
    public function getHtml()
45
    {
46
        $content = '';
47
        foreach (iterator_to_array($this->node->childNodes) as $childNode) {
48
            $content .= $this->getDoc()->saveHTML($childNode);
49
        }
50
51
        return $content;
52
    }
53
54
    /**
55
     * Get the combined text contents, including it's descendants.
56
     *
57
     * @return string|null
58
     */
59
    public function getText()
60
    {
61
        return $this->node->textContent;
62
    }
63
64
    /**
65
     * Set the text contents.
66
     *
67
     * @param string $text
68
     */
69
    public function setText(string $text)
70
    {
71
        $this->node->nodeValue = $text;
72
    }
73
74
    /**
75
     * Get the value of an attribute
76
     *
77
     * @param string $name
78
     *
79
     * @return string|null
80
     */
81
    public function getAttr(string $name)
82
    {
83
        if (!($this->node instanceof DOMElement)) {
84
            return null;
85
        }
86
87
        return $this->node->getAttribute($name);
88
    }
89
90
    /**
91
     * Set attribute.
92
     *
93
     * @param string $name
94
     * @param string $value
95
     */
96
    public function setAttr(string $name, string $value)
97
    {
98
        if ($this->node instanceof DOMElement) {
99
            $this->node->setAttribute($name, $value);
100
        }
101
    }
102
103
    /**
104
     * Remove an attribute.
105
     *
106
     * @param string $attributeName
107
     */
108
    public function removeAttr(string $attributeName)
109
    {
110
        if ($this->node instanceof DOMElement) {
111
            $this->node->removeAttribute($attributeName);
112
        }
113
    }
114
115
    /**
116
     * Remove all attributes except the specified ones.
117
     *
118
     * @param string|array $except The attribute name(s) that won't be removed
119
     */
120
    public function removeAllAttrs($except = [])
121
    {
122
        if ($this->node instanceof DOMElement) {
123
            $names = [];
124
            foreach (iterator_to_array($this->node->attributes) as $attribute) {
125
                $names[] = $attribute->name;
126
            }
127
128
            foreach (array_diff($names, (array) $except) as $name) {
129
                $this->node->removeAttribute($name);
130
            }
131
        }
132
    }
133
134
    /**
135
     * Determine whether the node has the given attribute.
136
     *
137
     * @param string $attributeName
138
     *
139
     * @return bool
140
     */
141
    public function hasAttr(string $attributeName)
142
    {
143
        if (!($this->node instanceof DOMElement)) {
144
            return false;
145
        }
146
147
        return $this->node->hasAttribute($attributeName);
148
    }
149
150
    /**
151
     * Remove all child nodes from the DOM.
152
     */
153
    public function empty()
154
    {
155
        $this->node->nodeValue = '';
156
    }
157
158
    /**
159
     * Remove the node from the DOM.
160
     */
161
    public function remove()
162
    {
163
        if ($this->node->parentNode) {
164
            $this->node->parentNode->removeChild($this->node);
165
        }
166
    }
167
168
    /**
169
     * Adds the specified class(es).
170
     *
171
     * @param string $className
172
     */
173
    public function addClass(string $className)
174
    {
175
        if (!$this->hasAttr('class')) {
176
            $this->setAttr('class', $className);
177
            return;
178
        }
179
180
        $classNames = Helper::splitClass($className);
181
        $class = (string) $this->getAttr('class');
182
        $classes = Helper::splitClass($class);
183
184
        $classArr = array_diff($classNames, $classes);
185
        if (empty($classArr)) {
186
            return;
187
        }
188
189
        $class .= ' ' . implode(' ', $classArr);
190
        $this->setAttr('class', $class);
191
    }
192
193
    /**
194
     * Determine whether the node is assigned the given class.
195
     *
196
     * @param string $className
197
     *
198
     * @return bool
199
     */
200
    public function hasClass(string $className)
201
    {
202
        if (!$this->hasAttr('class')) {
203
            return false;
204
        }
205
206
        $class = (string) $this->getAttr('class');
207
        $classes = Helper::splitClass($class);
208
209
        return in_array($className, $classes);
210
    }
211
212
    /**
213
     * Remove a single class, multiple classes, or all classes.
214
     *
215
     * @param string|null $className
216
     */
217
    public function removeClass(?string $className = null)
218
    {
219
        if (!$this->hasAttr('class')) {
220
            return;
221
        }
222
223
        if (is_null($className)) {
224
            $this->removeAttr('class');
225
            return;
226
        }
227
228
        $classNames = Helper::splitClass($className);
229
        $class = (string) $this->getAttr('class');
230
        $classes = Helper::splitClass($class);
231
232
        $classArr = array_diff($classes, $classNames);
233
        if (empty($classArr)) {
234
            $this->removeAttr('class');
235
            return;
236
        }
237
238
        $class = implode(' ', $classArr);
239
        $this->setAttr('class', $class);
240
    }
241
242
    /**
243
     * Add or remove class(es), depending on either the class's presence
244
     * or the value of the state argument.
245
     *
246
     * @param string $className
247
     * @param bool|null   $state
248
     */
249
    public function toggleClass(string $className, ?bool $state = null)
250
    {
251
        if (!is_null($state)) {
252
            if ($state) {
253
                $this->addClass($className);
254
            } else {
255
                $this->removeClass($className);
256
            }
257
            return;
258
        }
259
260
        if (!$this->hasAttr('class')) {
261
            $this->setAttr('class', $className);
262
            return;
263
        }
264
265
        $classNames = Helper::splitClass($className);
266
        $classes = Helper::splitClass((string) $this->getAttr('class'));
267
268
        $classArr = array_diff($classes, $classNames);
269
        $classArr = array_merge(
270
            $classArr,
271
            array_diff($classNames, $classes)
272
        );
273
        if (empty($classArr)) {
274
            $this->removeClass($className);
275
            return;
276
        }
277
278
        $this->setAttr('class', implode(' ', $classArr));
279
    }
280
281
    /**
282
     * Get the value of a computed style property
283
     *
284
     * @param string $name
285
     *
286
     * @return string|null
287
     */
288
    public function getCss(string $name)
289
    {
290
        $style = (string) $this->getAttr('style');
291
        $css = Helper::splitCss($style);
292
        if (!$css) {
293
            return null;
294
        }
295
296
        if (array_key_exists($name, $css)) {
297
            return $css[$name];
298
        }
299
300
        $arr = array_change_key_case($css, CASE_LOWER);
301
        $key = strtolower($name);
302
        if (array_key_exists($key, $arr)) {
303
            return $arr[$key];
304
        }
305
306
        return null;
307
    }
308
309
    /**
310
     * Set or Remove one CSS property.
311
     *
312
     * @param string      $name
313
     * @param string|null $value
314
     */
315
    public function setCss(string $name, ?string $value)
316
    {
317
        if ((string) $value === '') {
318
            $this->removeCss($name);
319
            return;
320
        }
321
322
        $style = (string) $this->getAttr('style');
323
        if (!$style) {
324
            $this->setAttr('style', $name . ': ' . $value . ';');
325
            return;
326
        }
327
328
        $css = Helper::splitCss($style);
329
        if (!array_key_exists($name, $css)) {
330
            $allKeys = array_keys($css);
331
            $arr = array_combine(
332
                $allKeys,
333
                array_map('strtolower', $allKeys)
334
            ) ?: [];
335
336
            $keys = array_keys($arr, strtolower($name));
337
            foreach ($keys as $key) {
338
                unset($css[$key]);
339
            }
340
        }
341
342
        $css[$name] = $value;
343
        $style = Helper::implodeCss($css);
344
        $this->setAttr('style', $style);
345
    }
346
347
    /**
348
     * Remove one CSS property.
349
     *
350
     * @param string $name
351
     */
352
    public function removeCss(string $name)
353
    {
354
        $style = (string) $this->getAttr('style');
355
        if (!$style) {
356
            return;
357
        }
358
359
        $css = Helper::splitCss($style);
360
        $removed = false;
361
        if (array_key_exists($name, $css)) {
362
            unset($css[$name]);
363
            $removed = true;
364
        } else {
365
            $allKeys = array_keys($css);
366
            $arr = array_combine(
367
                $allKeys,
368
                array_map('strtolower', $allKeys)
369
            ) ?: [];
370
371
            $keys = array_keys($arr, strtolower($name));
372
            foreach ($keys as $key) {
373
                unset($css[$key]);
374
                $removed = true;
375
            }
376
        }
377
378
        if ($removed) {
379
            $style = Helper::implodeCss($css);
380
            $this->setAttr('style', $style);
381
        }
382
    }
383
384
    /**
385
     * Insert a node before the node.
386
     *
387
     * @param DOMNode $newNode
388
     */
389
    public function before(DOMNode $newNode)
390
    {
391
        $this->node->parentNode->insertBefore($newNode, $this->node);
392
    }
393
394
    /**
395
     * Insert a node to the end of the node.
396
     *
397
     * @param DOMNode $newNode
398
     */
399
    public function append(DOMNode $newNode)
400
    {
401
        $this->node->appendChild($newNode);
402
    }
403
404
    /**
405
     * Insert content or node(s) to the beginning of each matched node.
406
     *
407
     * @param DOMNode $newNode
408
     */
409
    public function prepend(DOMNode $newNode)
410
    {
411
        if ($this->node->firstChild) {
412
            $this->node->insertBefore($newNode, $this->node->firstChild);
413
        } else {
414
            $this->node->appendChild($newNode);
415
        }
416
    }
417
418
    /**
419
     * Replace the node with the provided node
420
     *
421
     * @param DOMNode $newNode
422
     */
423
    public function replaceWith(DOMNode $newNode)
424
    {
425
        if ($this->node->parentNode) {
426
            $this->node->parentNode->replaceChild($newNode, $this->node);
427
        }
428
    }
429
430
    protected function getDoc()
431
    {
432
        return $this->node instanceof DOMDocument
433
            ? $this->node :
434
            $this->node->ownerDocument;
435
    }
436
}
437