• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

heimrichhannot / contao-utils-bundle / 15017483614

14 May 2025 09:41AM UTC coverage: 25.302% (-54.2%) from 79.482%
15017483614

Pull #96

github

koertho
backport anonymize utils
Pull Request #96: Backport anonymize utils

6 of 7 new or added lines in 2 files covered. (85.71%)

248 existing lines in 22 files now uncovered.

1488 of 5881 relevant lines covered (25.3%)

1.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.42
/src/Dom/DOMLettersIterator.php
1
<?php
2

3
/*
4
 * Copyright (c) 2021 Heimrich & Hannot GmbH
5
 *
6
 * @license LGPL-3.0-or-later
7
 */
8

9
namespace HeimrichHannot\UtilsBundle\Dom;
10

11
use DOMDocument;
12
use DOMElement;
13
use DOMNode;
14
use InvalidArgumentException;
15
use Iterator;
16

17
/**
18
 * Iterates individual characters (Unicode codepoints) of DOM text and CDATA nodes
19
 * while keeping track of their position in the document.
20
 *
21
 * Example:
22
 *
23
 *  $doc = new DOMDocument();
24
 *  $doc->load('example.xml');
25
 *  foreach(new DOMLettersIterator($doc) as $letter) echo $letter;
26
 *
27
 * NB: If you only need characters without their position
28
 *     in the document, use DOMNode->textContent instead.
29
 *
30
 * @author porneL http://pornel.net
31
 * @license Public Domain
32
 */
33
final class DOMLettersIterator implements Iterator
34
{
35
    private $start;
36
    private $current;
37
    private $offset;
38
    private $key;
39
    private $letters;
40
    /**
41
     * @var \SplQueue
42
     */
43
    private $queue;
44

45
    /**
46
     * expects DOMElement or DOMDocument (see DOMDocument::load and DOMDocument::loadHTML).
47
     *
48
     * @param DOMDocument|DOMElement $el
49
     */
50
    public function __construct(DOMNode $el)
51
    {
52
        if ($el instanceof DOMDocument) {
11✔
UNCOV
53
            $this->start = $el->documentElement;
×
54
        } else {
55
            if ($el instanceof DOMElement) {
11✔
56
                $this->start = $el;
11✔
57
            } else {
UNCOV
58
                throw new InvalidArgumentException('Invalid arguments, expected DOMElement or DOMDocument');
×
59
            }
60
        }
61
        $this->queue = new \SplQueue();
11✔
62
    }
63

64
    /**
65
     * Returns position in text as DOMText node and character offset.
66
     * (it's NOT a byte offset, you must use mb_substr() or similar to use this offset properly).
67
     * node may be NULL if iterator has finished.
68
     *
69
     * @return DOMElement[]|int[]
70
     */
71
    public function currentTextPosition()
72
    {
73
        return [$this->current, $this->offset, $this->queue->bottom()];
11✔
74
    }
75

76
    /**
77
     * Returns DOMElement that is currently being iterated or NULL if iterator has finished.
78
     *
79
     * @return DOMElement
80
     */
81
    public function currentElement()
82
    {
UNCOV
83
        return $this->current ? $this->current->parentNode : null;
×
84
    }
85

86
    // Implementation of Iterator interface
87
    public function key()
88
    {
89
        return $this->key;
11✔
90
    }
91

92
    public function next(): void
93
    {
94
        if (!$this->current) {
11✔
UNCOV
95
            return;
×
96
        }
97

98
        if ($this->isTextNode($this->current)) {
11✔
99
            if (-1 == $this->offset) {
11✔
100
                // fastest way to get individual Unicode chars and does not require mb_* functions
101
                preg_match_all('/./us', $this->current->textContent, $m);
11✔
102
                $this->letters = $m[0];
11✔
103
            }
104
            ++$this->offset;
11✔
105
            ++$this->key;
11✔
106

107
            if ($this->offset < \count($this->letters)) {
11✔
108
                return;
11✔
109
            }
110
            $this->offset = -1;
5✔
111
            --$this->key;
5✔
112
        }
113

114
        while (XML_ELEMENT_NODE == $this->current->nodeType && $this->current->firstChild) {
11✔
115
            $this->current = $this->current->firstChild;
11✔
116

117
            if ($this->isTextNode($this->current)) {
11✔
118
                $this->addToQueue($this->current);
11✔
119
                $this->next();
11✔
120

121
                return;
11✔
122
            }
123
        }
124

125
        while (!$this->current->nextSibling && $this->current->parentNode) {
5✔
126
            $this->current = $this->current->parentNode;
5✔
127

128
            if ($this->current === $this->start) {
5✔
UNCOV
129
                $this->current = null;
×
130

UNCOV
131
                return;
×
132
            }
133
        }
134

135
        $this->current = $this->current->nextSibling;
5✔
136

137
        if ($this->isTextNode($this->current)) {
5✔
138
            $this->addToQueue($this->current);
2✔
139
        }
140

141
        $this->next();
5✔
142
    }
143

144
    public function current()
145
    {
146
        if ($this->current) {
11✔
147
            return $this->letters[$this->offset];
11✔
148
        }
149

UNCOV
150
        return null;
×
151
    }
152

153
    public function valid()
154
    {
155
        return (bool) $this->current;
11✔
156
    }
157

158
    public function rewind()
159
    {
160
        $this->offset = -1;
11✔
161
        $this->letters = [];
11✔
162
        $this->current = $this->start;
11✔
163
        $this->next();
11✔
164
    }
165

166
    private function isTextNode(DOMNode $element): bool
167
    {
168
        return XML_TEXT_NODE == $element->nodeType || XML_CDATA_SECTION_NODE == $element->nodeType;
11✔
169
    }
170

171
    private function addToQueue(DOMNode $node): void
172
    {
173
        $this->queue->enqueue($node);
11✔
174

175
        while ($this->queue->count() > 2) {
11✔
176
            $this->queue->dequeue();
2✔
177
        }
178
    }
179
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc