Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
SvgDocument
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
4 / 4
11
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 findById
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 invalidateIdIndex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 indexInto
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Svg;
6
7/**
8 * The root `<svg>` element. Inherits the viewBox / width / height accessors
9 * from `ViewportElement` (shared with `<symbol>`), and owns the
10 * document-level `findById()` resolver used by `<use>` and gradient `href`
11 * lookups.
12 *
13 * The id index is lazily built on first lookup and then cached. Mutating the
14 * tree after the first lookup invalidates the cache; rebuild by calling
15 * `invalidateIdIndex()` (or just call findById on a freshly parsed
16 * document — the common case).
17 */
18final class SvgDocument extends ViewportElement
19{
20    /** @var array<string, Element>|null */
21    private ?array $idIndex = null;
22
23    public function __construct()
24    {
25        parent::__construct('svg');
26    }
27
28    /**
29     * Look up an element by its `id` attribute. Returns null when no
30     * element with that id exists. Lazily caches the index after the
31     * first call.
32     */
33    public function findById(string $id): ?Element
34    {
35        if ($id === '') {
36            return null;
37        }
38        if ($this->idIndex === null) {
39            $this->idIndex = [];
40            self::indexInto($this, $this->idIndex);
41        }
42        return $this->idIndex[$id] ?? null;
43    }
44
45    /**
46     * Drop the cached id index — call this after mutating the tree if
47     * you need subsequent `findById` calls to see the new state.
48     */
49    public function invalidateIdIndex(): void
50    {
51        $this->idIndex = null;
52    }
53
54    /**
55     * @param array<string, Element> $into
56     */
57    private static function indexInto(Element $element, array &$into): void
58    {
59        $id = $element->getAttribute('id');
60        if ($id !== null && $id !== '' && !isset($into[$id])) {
61            // SVG 2: duplicate ids are technically invalid; pick the first
62            // in document order — same shape browsers use for
63            // querySelector('#id').
64            $into[$id] = $element;
65        }
66        foreach ($element->children as $child) {
67            if ($child instanceof Element) {
68                self::indexInto($child, $into);
69            }
70        }
71    }
72}