ci4

Форк
0
/
DOMParser.php 
300 строк · 7.9 Кб
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\Test;
15

16
use BadMethodCallException;
17
use DOMDocument;
18
use DOMNodeList;
19
use DOMXPath;
20
use InvalidArgumentException;
21

22
/**
23
 * Load a response into a DOMDocument for testing assertions based on that
24
 *
25
 * @see \CodeIgniter\Test\DOMParserTest
26
 */
27
class DOMParser
28
{
29
    /**
30
     * DOM for the body,
31
     *
32
     * @var DOMDocument
33
     */
34
    protected $dom;
35

36
    /**
37
     * Constructor.
38
     *
39
     * @throws BadMethodCallException
40
     */
41
    public function __construct()
42
    {
43
        if (! extension_loaded('DOM')) {
44
            throw new BadMethodCallException('DOM extension is required, but not currently loaded.'); // @codeCoverageIgnore
45
        }
46

47
        $this->dom = new DOMDocument('1.0', 'utf-8');
48
    }
49

50
    /**
51
     * Returns the body of the current document.
52
     */
53
    public function getBody(): string
54
    {
55
        return $this->dom->saveHTML();
56
    }
57

58
    /**
59
     * Sets a string as the body that we want to work with.
60
     *
61
     * @return $this
62
     */
63
    public function withString(string $content)
64
    {
65
        // DOMDocument::loadHTML() will treat your string as being in ISO-8859-1
66
        // (the HTTP/1.1 default character set) unless you tell it otherwise.
67
        // https://stackoverflow.com/a/8218649
68
        // So encode characters to HTML numeric string references.
69
        $content = mb_encode_numericentity($content, [0x80, 0x10FFFF, 0, 0x1FFFFF], 'UTF-8');
70

71
        // turning off some errors
72
        libxml_use_internal_errors(true);
73

74
        if (! $this->dom->loadHTML($content)) {
75
            // unclear how we would get here, given that we are trapping libxml errors
76
            // @codeCoverageIgnoreStart
77
            libxml_clear_errors();
78

79
            throw new BadMethodCallException('Invalid HTML');
80
            // @codeCoverageIgnoreEnd
81
        }
82

83
        // ignore the whitespace.
84
        $this->dom->preserveWhiteSpace = false;
85

86
        return $this;
87
    }
88

89
    /**
90
     * Loads the contents of a file as a string
91
     * so that we can work with it.
92
     *
93
     * @return $this
94
     */
95
    public function withFile(string $path)
96
    {
97
        if (! is_file($path)) {
98
            throw new InvalidArgumentException(basename($path) . ' is not a valid file.');
99
        }
100

101
        $content = file_get_contents($path);
102

103
        return $this->withString($content);
104
    }
105

106
    /**
107
     * Checks to see if the text is found within the result.
108
     */
109
    public function see(?string $search = null, ?string $element = null): bool
110
    {
111
        // If Element is null, we're just scanning for text
112
        if ($element === null) {
113
            $content = $this->dom->saveHTML($this->dom->documentElement);
114

115
            return mb_strpos($content, $search) !== false;
116
        }
117

118
        $result = $this->doXPath($search, $element);
119

120
        return (bool) $result->length;
121
    }
122

123
    /**
124
     * Checks to see if the text is NOT found within the result.
125
     */
126
    public function dontSee(?string $search = null, ?string $element = null): bool
127
    {
128
        return ! $this->see($search, $element);
129
    }
130

131
    /**
132
     * Checks to see if an element with the matching CSS specifier
133
     * is found within the current DOM.
134
     */
135
    public function seeElement(string $element): bool
136
    {
137
        return $this->see(null, $element);
138
    }
139

140
    /**
141
     * Checks to see if the element is available within the result.
142
     */
143
    public function dontSeeElement(string $element): bool
144
    {
145
        return $this->dontSee(null, $element);
146
    }
147

148
    /**
149
     * Determines if a link with the specified text is found
150
     * within the results.
151
     */
152
    public function seeLink(string $text, ?string $details = null): bool
153
    {
154
        return $this->see($text, 'a' . $details);
155
    }
156

157
    /**
158
     * Checks for an input named $field with a value of $value.
159
     */
160
    public function seeInField(string $field, string $value): bool
161
    {
162
        $result = $this->doXPath(null, 'input', ["[@value=\"{$value}\"][@name=\"{$field}\"]"]);
163

164
        return (bool) $result->length;
165
    }
166

167
    /**
168
     * Checks for checkboxes that are currently checked.
169
     */
170
    public function seeCheckboxIsChecked(string $element): bool
171
    {
172
        $result = $this->doXPath(null, 'input' . $element, [
173
            '[@type="checkbox"]',
174
            '[@checked="checked"]',
175
        ]);
176

177
        return (bool) $result->length;
178
    }
179

180
    /**
181
     * Checks to see if the XPath can be found.
182
     */
183
    public function seeXPath(string $path): bool
184
    {
185
        $xpath = new DOMXPath($this->dom);
186

187
        return (bool) $xpath->query($path)->length;
188
    }
189

190
    /**
191
     * Checks to see if the XPath can't be found.
192
     */
193
    public function dontSeeXPath(string $path): bool
194
    {
195
        return ! $this->seeXPath($path);
196
    }
197

198
    /**
199
     * Search the DOM using an XPath expression.
200
     *
201
     * @return DOMNodeList|false
202
     */
203
    protected function doXPath(?string $search, string $element, array $paths = [])
204
    {
205
        // Otherwise, grab any elements that match
206
        // the selector
207
        $selector = $this->parseSelector($element);
208

209
        $path = '';
210

211
        // By ID
212
        if (isset($selector['id'])) {
213
            $path = ($selector['tag'] === '')
214
                ? "id(\"{$selector['id']}\")"
215
                : "//{$selector['tag']}[@id=\"{$selector['id']}\"]";
216
        }
217
        // By Class
218
        elseif (isset($selector['class'])) {
219
            $path = ($selector['tag'] === '')
220
                ? "//*[@class=\"{$selector['class']}\"]"
221
                : "//{$selector['tag']}[@class=\"{$selector['class']}\"]";
222
        }
223
        // By tag only
224
        elseif ($selector['tag'] !== '') {
225
            $path = "//{$selector['tag']}";
226
        }
227

228
        if (isset($selector['attr'])) {
229
            foreach ($selector['attr'] as $key => $value) {
230
                $path .= "[@{$key}=\"{$value}\"]";
231
            }
232
        }
233

234
        // $paths might contain a number of different
235
        // ready to go xpath portions to tack on.
236
        if ($paths !== [] && is_array($paths)) {
237
            foreach ($paths as $extra) {
238
                $path .= $extra;
239
            }
240
        }
241

242
        if ($search !== null) {
243
            $path .= "[contains(., \"{$search}\")]";
244
        }
245

246
        $xpath = new DOMXPath($this->dom);
247

248
        return $xpath->query($path);
249
    }
250

251
    /**
252
     * Look for the a selector  in the passed text.
253
     *
254
     * @return array{tag: string, id: string|null, class: string|null, attr: array<string, string>|null}
255
     */
256
    public function parseSelector(string $selector)
257
    {
258
        $id    = null;
259
        $class = null;
260
        $attr  = null;
261

262
        // ID?
263
        if (str_contains($selector, '#')) {
264
            [$tag, $id] = explode('#', $selector);
265
        }
266
        // Attribute
267
        elseif (str_contains($selector, '[') && str_contains($selector, ']')) {
268
            $open  = strpos($selector, '[');
269
            $close = strpos($selector, ']');
270

271
            $tag  = substr($selector, 0, $open);
272
            $text = substr($selector, $open + 1, $close - 2);
273

274
            // We only support a single attribute currently
275
            $text = explode(',', $text);
276
            $text = trim(array_shift($text));
277

278
            [$name, $value] = explode('=', $text);
279

280
            $name  = trim($name);
281
            $value = trim($value);
282
            $attr  = [$name => trim($value, '] ')];
283
        }
284
        // Class?
285
        elseif (str_contains($selector, '.')) {
286
            [$tag, $class] = explode('.', $selector);
287
        }
288
        // Otherwise, assume the entire string is our tag
289
        else {
290
            $tag = $selector;
291
        }
292

293
        return [
294
            'tag'   => $tag,
295
            'id'    => $id,
296
            'class' => $class,
297
            'attr'  => $attr,
298
        ];
299
    }
300
}
301

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.