Completed
Push — master ( 8146b2...061ea8 )
by Ryan
03:02
created

Atom::getNodeAttributes()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 27
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 13
nc 5
nop 5
dl 0
loc 27
rs 8.5806
c 0
b 0
f 0
1
<?php
2
/**
3
 * Copyright (c) 2017–2018 Ryan Parman <http://ryanparman.com>.
4
 * Copyright (c) 2017–2018 Contributors.
5
 *
6
 * http://opensource.org/licenses/Apache2.0
7
 */
8
declare(strict_types=1);
9
10
namespace SimplePie\Middleware\Xml;
11
12
use DOMXPath;
13
use ReflectionClass;
14
use SimplePie\Configuration as C;
15
use SimplePie\Mixin as Tr;
16
use SimplePie\Type as T;
17
use stdClass;
18
19
/**
20
 * Support for the Atom 1.0 grammar.
21
 *
22
 * @see https://tools.ietf.org/html/rfc4287
23
 * @see https://www.w3.org/wiki/Atom
24
 */
25
class Atom extends AbstractXmlMiddleware implements XmlInterface, C\SetLoggerInterface
26
{
27
    use Tr\LoggerTrait;
28
29
    /**
30
     * {@inheritdoc}
31
     */
32
    public function __invoke(stdClass $feedRoot, string $namespaceAlias, DOMXPath $xpath): void
33
    {
34
        // Top-level feed
35
        $path = ['feed'];
36
37
        $this->getNodeAttributes($feedRoot, $namespaceAlias, $xpath, $path);
38
39
        $feedFallback = [
40
            'base' => $feedRoot->base[$namespaceAlias]->getNode(),
41
            'lang' => $feedRoot->lang[$namespaceAlias]->getNode(),
42
        ];
43
44
        $this->getSingleScalarTypes($feedRoot, $namespaceAlias, $xpath, $path, $feedFallback);
45
        $this->getSingleComplexTypes($feedRoot, $namespaceAlias, $xpath, $path);
46
        $this->getMultipleComplexTypes($feedRoot, $namespaceAlias, $xpath, $path);
47
48
        // <entry> element
49
        $path = ['feed', 'entry'];
50
51
        foreach ($feedRoot->entry[$namespaceAlias] as $i => &$entry) {
52
            $cpath   = $path;
53
            $cpath[] = $i;
54
55
            $feedFallback = [
56
                'base' => $feedRoot->base[$namespaceAlias]->getNode(),
57
                'lang' => $feedRoot->lang[$namespaceAlias]->getNode(),
58
            ];
59
60
            $this->getNodeAttributes($entry, $namespaceAlias, $xpath, $cpath, $feedFallback);
61
62
            $entryFallback = [
63
                'base' => $entry->base[$namespaceAlias]->getNode(),
64
                'lang' => $entry->lang[$namespaceAlias]->getNode(),
65
            ];
66
67
            $this->getSingleScalarTypes($entry, $namespaceAlias, $xpath, $cpath, $entryFallback);
68
            $this->getSingleComplexTypes($entry, $namespaceAlias, $xpath, $cpath);
69
            $this->getMultipleComplexTypes($entry, $namespaceAlias, $xpath, $cpath);
70
        }
71
    }
72
73
    /**
74
     * {@inheritdoc}
75
     *
76
     * Supports valid and invalid variations.
77
     *
78
     * * http://www.w3.org/2005/Atom
79
     * * http://www.w3.org/2005/Atom/
80
     * * https://www.w3.org/2005/Atom
81
     * * https://www.w3.org/2005/Atom/
82
     */
83
    public function getSupportedNamespaces(): array
84
    {
85
        return [
86
            'http://www.w3.org/2005/Atom'              => 'atom10',
87
            '/https?:\/\/www\.w3\.org\/2005\/Atom\/?/' => 'atom10',
88
        ];
89
    }
90
91
    /**
92
     * Fetches attributes with a single, scalar value, on elements.
93
     *
94
     * @param stdClass $feedRoot       The root of the feed. This will be written-to when the parsing middleware runs.
95
     * @param string   $namespaceAlias The preferred namespace alias for a given XML namespace URI. Should be the result
96
     *                                 of a call to `SimplePie\Util\Ns`.
97
     * @param DOMXPath $xpath          The `DOMXPath` object with this middleware's namespace alias applied.
98
     * @param array    $path           The path of the XML traversal. Should begin with `<feed>` or `<channel>`,
99
     *                                 then `<entry>` or `<item>`.
100
     * @param array    $fallback       An array of attributes for default XML attributes. The default value is an
101
     *                                 empty array.
102
     *
103
     * @phpcs:disable Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine
104
     */
105
    protected function getNodeAttributes(
106
        object $feedRoot,
107
        string $namespaceAlias,
108
        DOMXPath $xpath,
109
        array $path,
110
        array $fallback = []
111
    ): void {
112
        // @phpcs:enable
113
114
        $attrs = [
115
            'base' => '@xml:base',
116
            'lang' => '@xml:lang',
117
        ];
118
119
        // Used for traversing up the tree for inheritance
120
        $pathMinusLastBit = $path;
121
        \array_pop($pathMinusLastBit);
122
123
        foreach ($attrs as $nodeName => $searchName) {
124
            $query = $this->generateQuery($namespaceAlias, \array_merge($path, [$searchName]));
125
            $xq    = $xpath->query($query);
126
            $this->addArrayProperty($feedRoot, $nodeName);
127
            $this->getLogger()->debug(\sprintf('%s is running an XPath query:', __CLASS__), [$query]);
128
129
            $feedRoot->{$nodeName}[$namespaceAlias] = (false !== $xq && $xq->length > 0)
130
                ? new T\Node($xq->item(0))
131
                : new T\Node($this->get($fallback, $nodeName));
132
        }
133
    }
134
135
    /**
136
     * Fetches elements with a single, scalar value.
137
     *
138
     * @param stdClass $feedRoot       The root of the feed. This will be written-to when the parsing middleware runs.
139
     * @param string   $namespaceAlias The preferred namespace alias for a given XML namespace URI. Should be the result
140
     *                                 of a call to `SimplePie\Util\Ns`.
141
     * @param DOMXPath $xpath          The `DOMXPath` object with this middleware's namespace alias applied.
142
     * @param array    $path           The path of the XML traversal. Should begin with `<feed>` or `<channel>`,
143
     *                                 then `<entry>` or `<item>`.
144
     * @param array    $fallback       An array of attributes for default XML attributes. The default value is an
145
     *                                 empty array.
146
     *
147
     * @phpcs:disable Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine
148
     */
149
    protected function getSingleScalarTypes(
150
        object $feedRoot,
151
        string $namespaceAlias,
152
        DOMXPath $xpath,
153
        array $path,
154
        array $fallback = []
155
    ): void {
156
        // @phpcs:enable
157
158
        $cpath = $path;
159
        $nodes = [];
160
161
        if (\is_int(\end($cpath))) {
162
            \array_pop($cpath);
163
        }
164
165
        if ('feed' === \end($cpath)) {
166
            $nodes = [
167
                'icon',
168
                'id',
169
                'logo',
170
                'published',
171
                'rights',
172
                'subtitle',
173
                'summary',
174
                'title',
175
                'updated',
176
            ];
177
        } elseif ('entry' === \end($cpath)) {
178
            $nodes = [
179
                'content',
180
                'id',
181
                'published',
182
                'rights',
183
                'summary',
184
                'title',
185
                'updated',
186
            ];
187
        }
188
189
        foreach ($nodes as $nodeName) {
190
            $query = $this->generateQuery($namespaceAlias, \array_merge($path, [$nodeName]));
191
            $xq    = $xpath->query($query);
192
            $this->addArrayProperty($feedRoot, $nodeName);
193
            $this->getLogger()->debug(\sprintf('%s is running an XPath query:', __CLASS__), [$query]);
194
195
            $feedRoot->{$nodeName}[$namespaceAlias] = (false !== $xq && $xq->length > 0)
196
                ? new T\Node($xq->item(0), $fallback)
197
                : new T\Node();
198
        }
199
    }
200
201
    /**
202
     * Fetches elements with a single, complex value.
203
     *
204
     * @param stdClass $feedRoot       The root of the feed. This will be written-to when the parsing middleware runs.
205
     * @param string   $namespaceAlias The preferred namespace alias for a given XML namespace URI. Should be the result
206
     *                                 of a call to `SimplePie\Util\Ns`.
207
     * @param DOMXPath $xpath          The `DOMXPath` object with this middleware's namespace alias applied.
208
     * @param array    $path           The path of the XML traversal. Should begin with `<feed>` or `<channel>`,
209
     *                                 then `<entry>` or `<item>`.
210
     *
211
     * @phpcs:disable Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine
212
     */
213
    protected function getSingleComplexTypes(
214
        object $feedRoot,
215
        string $namespaceAlias,
216
        DOMXPath $xpath,
217
        array $path
218
    ): void {
219
        // @phpcs:enable
220
221
        $cpath = $path;
222
        $nodes = [];
223
224
        if (\is_int(\end($cpath))) {
225
            \array_pop($cpath);
226
        }
227
228
        if ('feed' === \end($cpath)) {
229
            $nodes = [
230
                'generator' => T\Generator::class,
231
            ];
232
        }
233
234
        foreach ($nodes as $name => $class) {
235
            $query = $this->generateQuery($namespaceAlias, \array_merge($path, [$name]));
236
            $xq    = $xpath->query($query);
237
            $this->addArrayProperty($feedRoot, $name);
238
            $this->getLogger()->debug(\sprintf('%s is running an XPath query:', __CLASS__), [$query]);
239
240
            $feedRoot->{$name}[$namespaceAlias] = (false !== $xq && $xq->length > 0)
241
                ? new $class($xq->item(0), $this->getLogger())
242
                : null;
243
        }
244
    }
245
246
    /**
247
     * Fetches elements with a multiple, complex values.
248
     *
249
     * @param stdClass $feedRoot       The root of the feed. This will be written-to when the parsing middleware runs.
250
     * @param string   $namespaceAlias The preferred namespace alias for a given XML namespace URI. Should be the result
251
     *                                 of a call to `SimplePie\Util\Ns`.
252
     * @param DOMXPath $xpath          The `DOMXPath` object with this middleware's namespace alias applied.
253
     * @param array    $path           The path of the XML traversal. Should begin with `<feed>` or `<channel>`,
254
     *                                 then `<entry>` or `<item>`.
255
     *
256
     * @phpcs:disable Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine
257
     */
258
    protected function getMultipleComplexTypes(
259
        object $feedRoot,
260
        string $namespaceAlias,
261
        DOMXPath $xpath,
262
        array $path
263
    ): void {
264
        // @phpcs:enable
265
266
        $cpath = $path;
267
        $nodes = [];
268
269
        if (\is_int(\end($cpath))) {
270
            \array_pop($cpath);
271
        }
272
273
        if ('feed' === \end($cpath)) {
274
            $nodes = [
275
                'author'      => T\Person::class,
276
                'category'    => T\Category::class,
277
                'contributor' => T\Person::class,
278
                'entry'       => T\Entry::class,
279
                'link'        => T\Link::class,
280
            ];
281
        } elseif ('entry' === \end($cpath)) {
282
            $nodes = [
283
                'author'      => T\Person::class,
284
                'category'    => T\Category::class,
285
                'contributor' => T\Person::class,
286
                'link'        => T\Link::class,
287
            ];
288
        }
289
290
        foreach ($nodes as $name => $class) {
291
            $query = $this->generateQuery($namespaceAlias, \array_merge($path, [$name]));
292
            $xq    = $xpath->query($query);
293
            $this->addArrayProperty($feedRoot, $name);
294
            $this->getLogger()->debug(\sprintf('%s is running an XPath query:', __CLASS__), [$query]);
295
296
            $feedRoot->{$name}[$namespaceAlias] = [];
297
298
            foreach ($xq as $result) {
299
                // What kind of class is this?
300
                $rclass = (new ReflectionClass($class))
301
                    ->newInstanceWithoutConstructor();
302
303
                if ($rclass instanceof T\BranchInterface) {
304
                    $feedRoot->{$name}[$namespaceAlias][] = new $class(
305
                        $namespaceAlias,
306
                        $result,
307
                        $this->getLogger()
308
                    );
309
                } else {
310
                    $feedRoot->{$name}[$namespaceAlias][] = new $class(
311
                        $result,
312
                        $this->getLogger()
313
                    );
314
                }
315
            }
316
        }
317
    }
318
}
319