Completed
Branch scrutinizer (79ce98)
by Thomas
02:53
created

Attribute::getVal()   B

Complexity

Conditions 7
Paths 1

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 7
eloc 18
c 1
b 1
f 0
nc 1
nop 0
dl 0
loc 28
rs 8.8333
1
<?php
2
3
namespace Sulao\HtmlQuery;
4
5
use DOMElement, DOMNode;
6
7
/**
8
 * Class Attribute
9
 *
10
 * @package Sulao\HtmlQuery
11
 */
12
abstract class Attribute extends Selection
13
{
14
    /**
15
     * Get the value of an attribute for the first matched node
16
     * or set one or more attributes for every matched node.
17
     *
18
     * @param string|array $name
19
     * @param string|null  $value
20
     *
21
     * @return static|mixed|null
22
     */
23
    public function attr($name, $value = null)
24
    {
25
        if (is_array($name)) {
26
            foreach ($name as $key => $val) {
27
                $this->setAttr($key, $val);
28
            }
29
30
            return $this;
31
        }
32
33
        if (!is_null($value)) {
34
            return $this->setAttr($name, $value);
35
        }
36
37
        return $this->getAttr($name);
38
    }
39
40
    /**
41
     * Get the value of an attribute for the first matched node
42
     *
43
     * @param string $name
44
     *
45
     * @return string|null
46
     */
47
    public function getAttr(string $name)
48
    {
49
        return $this->mapFirst(function (DOMNode $node) use ($name) {
50
            if (!($node instanceof DOMElement)) {
51
                return null;
52
            }
53
54
            return $node->getAttribute($name);
55
        });
56
    }
57
58
    /**
59
     * Set one or more attributes for every matched node.
60
     *
61
     * @param string $name
62
     * @param string $value
63
     *
64
     * @return static
65
     */
66
    public function setAttr(string $name, string $value)
67
    {
68
        return $this->each(function (DOMNode $node) use ($name, $value) {
69
            if ($node instanceof DOMElement) {
70
                $node->setAttribute($name, $value);
71
            }
72
        });
73
    }
74
75
    /**
76
     * Remove an attribute from every matched nodes.
77
     *
78
     * @param string $attributeName
79
     *
80
     * @return static
81
     */
82
    public function removeAttr(string $attributeName)
83
    {
84
        return $this->each(function (DOMNode $node) use ($attributeName) {
85
            if ($node instanceof DOMElement) {
86
                $node->removeAttribute($attributeName);
87
            }
88
        });
89
    }
90
91
    /**
92
     * Remove all attributes from every matched nodes except the specified ones.
93
     *
94
     * @param string|array $except The attribute name(s) that won't be removed
95
     *
96
     * @return static
97
     */
98
    public function removeAllAttrs($except = [])
99
    {
100
        return $this->each(function (DOMNode $node) use ($except) {
101
            $names = [];
102
            foreach (iterator_to_array($node->attributes) as $attribute) {
103
                $names[] = $attribute->name;
104
            }
105
106
            foreach (array_diff($names, (array) $except) as $name) {
107
                if ($node instanceof DOMElement) {
108
                    $node->removeAttribute($name);
109
                }
110
            }
111
        });
112
    }
113
114
    /**
115
     * Determine whether any of the nodes have the given attribute.
116
     *
117
     * @param string $attributeName
118
     *
119
     * @return bool
120
     */
121
    public function hasAttr(string $attributeName)
122
    {
123
        return $this->mapAnyTrue(
124
            function (DOMNode $node) use ($attributeName) {
125
                if (!($node instanceof DOMElement)) {
126
                    return false;
127
                }
128
129
                return $node->hasAttribute($attributeName);
130
            }
131
        );
132
    }
133
134
    /**
135
     * Alias of attr
136
     *
137
     * @param string|array $name
138
     * @param string|null  $value
139
     *
140
     * @return static|mixed|null
141
     */
142
    public function prop($name, $value = null)
143
    {
144
        return $this->attr($name, $value);
145
    }
146
147
    /**
148
     * Alias of removeAttr
149
     *
150
     * @param string $attributeName
151
     *
152
     * @return static
153
     */
154
    public function removeProp(string $attributeName)
155
    {
156
        return $this->removeAttr($attributeName);
157
    }
158
159
    /**
160
     * Get the value of an attribute with prefix data- for the first matched
161
     * node, if the value is valid json string, returns the value encoded in
162
     * json in appropriate PHP type
163
     *
164
     * or set one or more attributes with prefix data- for every matched node.
165
     *
166
     * @param string|array $name
167
     * @param string|null  $value
168
     *
169
     * @return static|mixed|null
170
     */
171
    public function data($name, $value = null)
172
    {
173
        if (is_array($name)) {
174
            $keys = array_keys($name);
175
            $keys = array_map(function ($value) {
176
                return 'data-' . $value;
177
            }, $keys);
178
179
            $name = array_combine($keys, $name);
180
        } else {
181
            $name = 'data-' . $name;
182
        }
183
184
        if (!is_null($value) && !is_string($value)) {
0 ignored issues
show
introduced by
The condition is_string($value) is always true.
Loading history...
185
            $value = (string) json_encode($value);
186
        }
187
188
        $result = $this->attr($name, $value);
0 ignored issues
show
Bug introduced by
It seems like $name can also be of type false; however, parameter $name of Sulao\HtmlQuery\Attribute::attr() does only seem to accept array|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

188
        $result = $this->attr(/** @scrutinizer ignore-type */ $name, $value);
Loading history...
189
190
        if (is_string($result)) {
191
            $json = json_decode($result);
192
            if (json_last_error() === JSON_ERROR_NONE) {
193
                return $json;
194
            }
195
        }
196
197
        return $result;
198
    }
199
200
    /**
201
     * Determine whether any of the nodes have the given attribute
202
     * prefix with data-.
203
     *
204
     * @param string $name
205
     *
206
     * @return bool
207
     */
208
    public function hasData(string $name)
209
    {
210
        return $this->hasAttr('data-' . $name);
211
    }
212
213
    /**
214
     * Remove an attribute prefix with data- from every matched nodes.
215
     *
216
     * @param string $name
217
     *
218
     * @return static
219
     */
220
    public function removeData(string $name)
221
    {
222
        return $this->removeAttr('data-' . $name);
223
    }
224
225
    /**
226
     * Get the current value of the first matched node
227
     * or set the value of every matched node.
228
     *
229
     * @param string|null $value
230
     *
231
     * @return string|null|static
232
     */
233
    public function val(?string $value = null)
234
    {
235
        if (is_null($value)) {
236
            return $this->getVal();
237
        }
238
239
        return $this->setVal($value);
240
    }
241
242
    /**
243
     * Get the current value of the first matched node
244
     *
245
     * @return string|null
246
     */
247
    public function getVal()
248
    {
249
        return $this->mapFirst(function (DOMNode $node) {
250
            if (!($node instanceof DOMElement)) {
251
                return null;
252
            }
253
254
            switch ($node->tagName) {
255
                case 'input':
256
                    return $node->getAttribute('value');
257
                case 'textarea':
258
                    return $node->nodeValue;
259
                case 'select':
260
                    $ht = $this->resolve($node);
261
262
                    $selected = $ht->children('option:selected');
263
                    if ($selected->count()) {
264
                        return $selected->getAttr('value');
265
                    }
266
267
                    $fistChild = $ht->xpathFind('child::*[1]');
268
                    if ($fistChild->count()) {
269
                        return $fistChild->getAttr('value');
270
                    }
271
                    break;
272
            }
273
274
            return null;
275
        });
276
    }
277
278
    /**
279
     * Set the value of every matched node.
280
     *
281
     * @param string $value
282
     *
283
     * @return static
284
     */
285
    public function setVal(string $value)
286
    {
287
        return $this->each(function (DOMNode $node) use ($value) {
288
            if (!($node instanceof DOMElement)) {
289
                return;
290
            }
291
292
            switch ($node->tagName) {
293
                case 'input':
294
                    $node->setAttribute('value', $value);
295
                    break;
296
                case 'textarea':
297
                    $node->nodeValue = $value;
298
                    break;
299
                case 'select':
300
                    $ht = $this->resolve($node);
301
302
                    $selected = $ht->children('option:selected');
303
                    if ($selected->count()) {
304
                        $selected->removeAttr('selected');
305
                    }
306
307
                    $options = $ht->children("option[value='{$value}']");
308
                    if ($options->count()) {
309
                        $options->first()->setAttr('selected', 'selected');
310
                    }
311
                    break;
312
            }
313
        });
314
    }
315
316
    /**
317
     * Adds the specified class(es) to each node in the matched nodes.
318
     *
319
     * @param string $className
320
     *
321
     * @return static
322
     */
323
    public function addClass(string $className)
324
    {
325
        return $this->each(function (HtmlQuery $node) use ($className) {
326
            if (!$node->hasAttr('class')) {
327
                $node->setAttr('class', $className);
328
                return;
329
            }
330
331
            $classNames = Helper::splitClass($className);
332
            $class = (string) $node->getAttr('class');
333
            $classes = Helper::splitClass($class);
334
335
            $classArr = array_diff($classNames, $classes);
336
            if (empty($classArr)) {
337
                return;
338
            }
339
340
            $class .= ' ' . implode(' ', $classArr);
341
            $this->setAttr('class', $class);
342
        });
343
    }
344
345
    /**
346
     * Determine whether any of the matched nodes are assigned the given class.
347
     *
348
     * @param string $className
349
     *
350
     * @return bool
351
     */
352
    public function hasClass(string $className)
353
    {
354
        return $this->mapAnyTrue(
355
            function (HtmlQuery $node) use ($className) {
356
                if (!$node->hasAttr('class')) {
357
                    return false;
358
                }
359
360
                $class = (string) $node->getAttr('class');
361
                $classes = Helper::splitClass($class);
362
363
                return in_array($className, $classes);
364
            }
365
        );
366
    }
367
368
    /**
369
     * Remove a single class, multiple classes, or all classes
370
     * from each matched node.
371
     *
372
     * @param string|null $className
373
     *
374
     * @return static
375
     */
376
    public function removeClass(?string $className = null)
377
    {
378
        return $this->each(function (HtmlQuery $node) use ($className) {
379
            if (!$node->hasAttr('class')) {
380
                return;
381
            }
382
383
            if (is_null($className)) {
384
                $node->removeAttr('class');
385
                return;
386
            }
387
388
            $classNames = Helper::splitClass($className);
389
            $class = (string) $node->getAttr('class');
390
            $classes = Helper::splitClass($class);
391
392
            $classArr = array_diff($classes, $classNames);
393
            if (empty($classArr)) {
394
                $node->removeAttr('class');
395
                return;
396
            }
397
398
            $class = implode(' ', $classArr);
399
            $node->setAttr('class', $class);
400
        });
401
    }
402
403
    /**
404
     * Add or remove class(es) from each matched node, depending on
405
     * either the class's presence or the value of the state argument.
406
     *
407
     * @param string $className
408
     * @param bool|null   $state
409
     *
410
     * @return static
411
     */
412
    public function toggleClass(string $className, ?bool $state = null)
413
    {
414
        return $this->each(function (HtmlQuery $node) use ($className, $state) {
415
            if (!is_null($state)) {
416
                if ($state) {
417
                    $node->addClass($className);
418
                } else {
419
                    $node->removeClass($className);
420
                }
421
                return;
422
            }
423
424
            if (!$this->hasAttr('class')) {
425
                $node->setAttr('class', $className);
426
                return;
427
            }
428
429
            $classNames = Helper::splitClass($className);
430
            $classes = Helper::splitClass((string) $this->getAttr('class'));
431
432
            $classArr = array_diff($classes, $classNames);
433
            $classArr = array_merge(
434
                $classArr,
435
                array_diff($classNames, $classes)
436
            );
437
            if (empty($classArr)) {
438
                $node->removeClass($className);
439
                return;
440
            }
441
442
            $node->setAttr('class', implode(' ', $classArr));
443
        });
444
    }
445
446
    /**
447
     * Get the value of a computed style property for the first matched node
448
     * or set one or more CSS properties for every matched node.
449
     *
450
     * @param string|array $name
451
     * @param string|null  $value
452
     *
453
     * @return static|string|null
454
     */
455
    public function css($name, $value = null)
456
    {
457
        if (is_null($value) && !is_array($name)) {
458
            return $this->getCss($name);
459
        }
460
461
        if (is_array($name)) {
462
            foreach ($name as $key => $val) {
463
                $this->setCss($key, $val);
464
            }
465
        } else {
466
            $this->setCss($name, $value);
467
        }
468
469
        return $this;
470
    }
471
472
473
    /**
474
     * Get the value of a computed style property for the first matched node
475
     *
476
     * @param string $name
477
     *
478
     * @return string|null
479
     */
480
    public function getCss(string $name)
481
    {
482
        return $this->mapFirst(function (HtmlQuery $node) use ($name) {
483
            $style = (string) $node->attr('style');
484
            $css = Helper::splitCss($style);
485
            if (!$css) {
486
                return null;
487
            }
488
489
            if (array_key_exists($name, $css)) {
490
                return $css[$name];
491
            }
492
493
            $arr = array_change_key_case($css, CASE_LOWER);
494
            $key = strtolower($name);
495
            if (array_key_exists($key, $arr)) {
496
                return $arr[$key];
497
            }
498
499
            return null;
500
        });
501
    }
502
503
    /**
504
     * Set or Remove one CSS property for every matched node.
505
     *
506
     * @param string      $name
507
     * @param string|null $value
508
     *
509
     * @return static
510
     */
511
    public function setCss(string $name, ?string $value)
512
    {
513
        return $this->each(function (HtmlQuery $node) use ($name, $value) {
514
            if ((string) $value === '') {
515
                $node->removeCss($name);
516
                return;
517
            }
518
519
            $style = (string) $node->attr('style');
520
            if (!$style) {
521
                $node->setAttr('style', $name . ': ' . $value . ';');
522
                return;
523
            }
524
525
            $css = Helper::splitCss($style);
526
            if (!array_key_exists($name, $css)) {
527
                $allKeys = array_keys($css);
528
                $arr = array_combine(
529
                    $allKeys,
530
                    array_map('strtolower', $allKeys)
531
                ) ?: [];
532
533
                $keys = array_keys($arr, strtolower($name));
534
                foreach ($keys as $key) {
535
                    unset($css[$key]);
536
                }
537
            }
538
539
            $css[$name] = $value;
540
            $style = Helper::implodeCss($css);
541
            $this->setAttr('style', $style);
542
        });
543
    }
544
545
    /**
546
     * Remove one CSS property for every matched node.
547
     *
548
     * @param string $name
549
     *
550
     * @return static
551
     */
552
    public function removeCss(string $name)
553
    {
554
        return $this->each(function (HtmlQuery $node) use ($name) {
555
            $style = (string) $node->attr('style');
556
            if (!$style) {
557
                return;
558
            }
559
560
            $css = Helper::splitCss($style);
561
            $removed = false;
562
            if (array_key_exists($name, $css)) {
563
                unset($css[$name]);
564
                $removed = true;
565
            } else {
566
                $allKeys = array_keys($css);
567
                $arr = array_combine(
568
                    $allKeys,
569
                    array_map('strtolower', $allKeys)
570
                ) ?: [];
571
572
                $keys = array_keys($arr, strtolower($name));
573
                foreach ($keys as $key) {
574
                    unset($css[$key]);
575
                    $removed = true;
576
                }
577
            }
578
579
            if ($removed) {
580
                $style = Helper::implodeCss($css);
581
                $this->setAttr('style', $style);
582
            }
583
        });
584
    }
585
}
586