|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* Copyright (c) 2017–2019 Ryan Parman <http://ryanparman.com>. |
|
4
|
|
|
* Copyright (c) 2017–2019 Contributors. |
|
5
|
|
|
* |
|
6
|
|
|
* http://opensource.org/licenses/Apache2.0 |
|
7
|
|
|
*/ |
|
8
|
|
|
|
|
9
|
|
|
declare(strict_types=1); |
|
10
|
|
|
|
|
11
|
|
|
namespace SimplePie\Middleware\Xml; |
|
12
|
|
|
|
|
13
|
|
|
use SimplePie\Middleware\AbstractMiddleware; |
|
14
|
|
|
use SimplePie\Type\Node; |
|
15
|
|
|
|
|
16
|
|
|
/** |
|
17
|
|
|
* The base XML middleware class that all other XML middleware classes extend from. It handles low-level functionality |
|
18
|
|
|
* that is shared across all XML middleware classes. |
|
19
|
|
|
*/ |
|
20
|
|
|
abstract class AbstractXmlMiddleware extends AbstractMiddleware |
|
21
|
|
|
{ |
|
22
|
|
|
/** |
|
23
|
|
|
* The status of case-sensitivity. |
|
24
|
|
|
* |
|
25
|
|
|
* @var bool |
|
26
|
|
|
*/ |
|
27
|
|
|
protected $caseSensitive = true; |
|
28
|
|
|
|
|
29
|
|
|
/** |
|
30
|
|
|
* By default, SimplePie NG is case-sensitive (as per the specification). If an invalid feed is parsed that does not |
|
31
|
|
|
* follow the specification with regard to casing of XML elements, this method allows you to trade some performance |
|
32
|
|
|
* in favor of case-insensitive parsing. |
|
33
|
|
|
* |
|
34
|
|
|
* @param bool $makeInsensitive Whether or not the handling should be made case-insensitive. A value of `true` |
|
35
|
|
|
* means that the handling should be case-insensitive. A value of `false` means that |
|
36
|
|
|
* the handling should be case-sensitive. The default value is `true`. |
|
37
|
|
|
*/ |
|
38
|
5 |
|
public function setCaseInsensitive(bool $makeInsensitive = true): self |
|
39
|
|
|
{ |
|
40
|
5 |
|
$this->caseSensitive = !$makeInsensitive; |
|
41
|
|
|
|
|
42
|
5 |
|
return $this; |
|
43
|
|
|
} |
|
44
|
|
|
|
|
45
|
|
|
/** |
|
46
|
|
|
* Replace all instances of `%s` with the `$namespaceAlias` parameter. |
|
47
|
|
|
* |
|
48
|
|
|
* This is similar to `sprintf()`, but the `$namespaceAlias` is applied to _all_ instances of `%s`. |
|
49
|
|
|
* |
|
50
|
|
|
* @param string $query An XPath query where `%s` is used in-place of the XML namespace alias. |
|
51
|
|
|
* @param string $namespaceAlias The XML namespace alias to apply. |
|
52
|
|
|
*/ |
|
53
|
|
|
public function applyNsToQuery(string $query, string $namespaceAlias): string |
|
54
|
|
|
{ |
|
55
|
|
|
return \str_replace('%s', $namespaceAlias, $query); |
|
56
|
|
|
} |
|
57
|
|
|
|
|
58
|
|
|
/** |
|
59
|
|
|
* Produce an XPath 1.0 expression which is used to query XML document nodes. |
|
60
|
|
|
* |
|
61
|
|
|
* ```php |
|
62
|
|
|
* ['feed', 'entry', 5, 'id', '@xml:lang'] |
|
63
|
|
|
* ``` |
|
64
|
|
|
* |
|
65
|
|
|
* ```xpath |
|
66
|
|
|
* /feed/entry[5]/id/@xml:lang (simplified) |
|
67
|
|
|
* ``` |
|
68
|
|
|
* |
|
69
|
|
|
* @param string $namespaceAlias The XML namespace alias to apply. |
|
70
|
|
|
* @param array $path An ordered array of nested elements, starting from the top-level XML node. |
|
71
|
|
|
* If an integer is added, then it is assumed that the element before it should be |
|
72
|
|
|
* handled as an array and the integer is its index. Expression is generated |
|
73
|
|
|
* left-to-right. |
|
74
|
|
|
* |
|
75
|
|
|
* @return string An XPath 1.0 expression. |
|
76
|
|
|
*/ |
|
77
|
560 |
|
public function generateQuery(string $namespaceAlias, array $path): string |
|
78
|
|
|
{ |
|
79
|
560 |
|
$query = ''; |
|
80
|
|
|
|
|
81
|
560 |
|
while (\count($path)) { |
|
82
|
560 |
|
$p = \array_shift($path); |
|
83
|
560 |
|
$next = $path[0] ?? null; |
|
84
|
|
|
|
|
85
|
|
|
// Reduce to only the upper/lower of the active letters |
|
86
|
|
|
// ≈30-35% faster than the full alphabet |
|
87
|
560 |
|
$pLet = \count_chars($p, 3); |
|
88
|
560 |
|
$pLow = \mb_strtolower($pLet); |
|
89
|
560 |
|
$pUp = \mb_strtoupper($pLet); |
|
90
|
560 |
|
$pAttr = ('@' === \mb_substr($p, 0, 1)); |
|
91
|
|
|
|
|
92
|
560 |
|
if (\is_int($next)) { |
|
93
|
220 |
|
if ($this->caseSensitive || $pAttr) { |
|
94
|
|
|
// case; next |
|
95
|
215 |
|
$query .= \sprintf( |
|
96
|
215 |
|
'/%s%s[position() = %d]', |
|
97
|
215 |
|
(!$pAttr |
|
98
|
215 |
|
? $namespaceAlias . ':' |
|
99
|
215 |
|
: ''), |
|
100
|
215 |
|
$p, |
|
101
|
215 |
|
\array_shift($path) + 1 |
|
102
|
|
|
); |
|
103
|
|
|
} else { |
|
104
|
|
|
// icase; next |
|
105
|
5 |
|
$query .= \sprintf( |
|
106
|
5 |
|
'/%s*[translate(name(), \'%s\', \'%s\') = \'%s\'][position() = %d]', |
|
107
|
5 |
|
(!$pAttr |
|
108
|
5 |
|
? $namespaceAlias . ':' |
|
109
|
5 |
|
: ''), |
|
110
|
5 |
|
$pUp, |
|
111
|
5 |
|
$pLow, |
|
112
|
5 |
|
$p, |
|
113
|
220 |
|
\array_shift($path) + 1 |
|
114
|
|
|
); |
|
115
|
|
|
} |
|
116
|
|
|
} else { |
|
117
|
560 |
|
if ($this->caseSensitive || $pAttr) { |
|
118
|
|
|
// case; no-next |
|
119
|
560 |
|
$query .= \sprintf( |
|
120
|
560 |
|
'/%s%s', |
|
121
|
560 |
|
(!$pAttr |
|
122
|
555 |
|
? $namespaceAlias . ':' |
|
123
|
560 |
|
: ''), |
|
124
|
560 |
|
$p |
|
125
|
|
|
); |
|
126
|
|
|
} else { |
|
127
|
|
|
// icase; no-next |
|
128
|
5 |
|
$query .= \sprintf( |
|
129
|
5 |
|
'/%s*[translate(name(), \'%s\', \'%s\') = \'%s\']', |
|
130
|
5 |
|
(!$pAttr |
|
131
|
5 |
|
? $namespaceAlias . ':' |
|
132
|
5 |
|
: ''), |
|
133
|
5 |
|
$pUp, |
|
134
|
5 |
|
$pLow, |
|
135
|
5 |
|
$p |
|
136
|
|
|
); |
|
137
|
|
|
} |
|
138
|
|
|
} |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
560 |
|
return $query; |
|
142
|
|
|
} |
|
143
|
|
|
} |
|
144
|
|
|
|