Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.92% |
92 / 93 |
|
94.74% |
18 / 19 |
CRAP | |
0.00% |
0 / 1 |
XmlRdfWriter | |
98.92% |
92 / 93 |
|
94.74% |
18 / 19 |
32 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
1 | |||
escape | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expandSubject | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expandPredicate | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expandResource | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expandType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
tag | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
6 | |||
close | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
getTargetAttributes | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
5.02 | |||
beginDocument | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
3 | |||
writeSubject | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
finishSubject | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
finishDocument | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
writePredicate | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
writeResource | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
writeText | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
writeValue | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
newSubWriter | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getMimeType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace Wikimedia\Purtle; |
4 | |
5 | use InvalidArgumentException; |
6 | |
7 | /** |
8 | * XML/RDF implementation of RdfWriter |
9 | * |
10 | * @license GPL-2.0-or-later |
11 | * @author Daniel Kinzler |
12 | */ |
13 | class XmlRdfWriter extends RdfWriterBase { |
14 | |
15 | /** |
16 | * @param string $role |
17 | * @param BNodeLabeler|null $labeler |
18 | */ |
19 | public function __construct( $role = parent::DOCUMENT_ROLE, ?BNodeLabeler $labeler = null ) { |
20 | parent::__construct( $role, $labeler ); |
21 | |
22 | $this->transitionTable[self::STATE_START][self::STATE_DOCUMENT] = function () { |
23 | $this->beginDocument(); |
24 | }; |
25 | $this->transitionTable[self::STATE_DOCUMENT][self::STATE_FINISH] = function () { |
26 | $this->finishDocument(); |
27 | }; |
28 | $this->transitionTable[self::STATE_OBJECT][self::STATE_DOCUMENT] = function () { |
29 | $this->finishSubject(); |
30 | }; |
31 | $this->transitionTable[self::STATE_OBJECT][self::STATE_SUBJECT] = function () { |
32 | $this->finishSubject(); |
33 | }; |
34 | } |
35 | |
36 | /** |
37 | * @param string $text |
38 | * |
39 | * @return string |
40 | */ |
41 | private function escape( $text ) { |
42 | return htmlspecialchars( $text, ENT_QUOTES ); |
43 | } |
44 | |
45 | /** |
46 | * @inheritDoc |
47 | */ |
48 | protected function expandSubject( &$base, &$local ) { |
49 | $this->expandQName( $base, $local ); |
50 | } |
51 | |
52 | /** |
53 | * @inheritDoc |
54 | */ |
55 | protected function expandPredicate( &$base, &$local ) { |
56 | $this->expandShorthand( $base, $local ); |
57 | } |
58 | |
59 | /** |
60 | * @inheritDoc |
61 | */ |
62 | protected function expandResource( &$base, &$local ) { |
63 | $this->expandQName( $base, $local ); |
64 | } |
65 | |
66 | /** |
67 | * @inheritDoc |
68 | */ |
69 | protected function expandType( &$base, &$local ) { |
70 | $this->expandQName( $base, $local ); |
71 | } |
72 | |
73 | /** |
74 | * @param string $ns |
75 | * @param string $name |
76 | * @param string[] $attributes |
77 | * @param string|null $content |
78 | */ |
79 | private function tag( $ns, $name, $attributes = [], $content = null ) { |
80 | $sep = $ns === '' ? '' : ':'; |
81 | $this->write( '<' . $ns . $sep . $name ); |
82 | |
83 | foreach ( $attributes as $attr => $value ) { |
84 | if ( is_int( $attr ) ) { |
85 | // positional array entries are passed verbatim, may be callbacks. |
86 | $this->write( $value ); |
87 | continue; |
88 | } |
89 | |
90 | $this->write( " $attr=\"" . $this->escape( $value ) . '"' ); |
91 | } |
92 | |
93 | if ( $content === null ) { |
94 | $this->write( '>' ); |
95 | } elseif ( $content === '' ) { |
96 | $this->write( '/>' ); |
97 | } else { |
98 | $this->write( '>' . $content ); |
99 | $this->close( $ns, $name ); |
100 | } |
101 | } |
102 | |
103 | /** |
104 | * @param string $ns |
105 | * @param string $name |
106 | */ |
107 | private function close( $ns, $name ) { |
108 | $sep = $ns === '' ? '' : ':'; |
109 | $this->write( '</' . $ns . $sep . $name . '>' ); |
110 | } |
111 | |
112 | /** |
113 | * Generates an attribute list, containing the attribute given by $name, or rdf:nodeID |
114 | * if $target is a blank node id (starting with "_:"). If $target is a qname, an attempt |
115 | * is made to resolve it into a full IRI based on the namespaces registered by calling |
116 | * prefix(). |
117 | * |
118 | * @param string $name the attribute name (without the 'rdf:' prefix) |
119 | * @param string|null $base |
120 | * @param string|null $local |
121 | * |
122 | * @throws InvalidArgumentException |
123 | * @return string[] |
124 | */ |
125 | private function getTargetAttributes( $name, $base, $local ) { |
126 | if ( $base === null && $local === null ) { |
127 | return []; |
128 | } |
129 | |
130 | // handle blank |
131 | if ( $base === '_' ) { |
132 | $name = 'nodeID'; |
133 | $value = $local; |
134 | } elseif ( $local !== null ) { |
135 | throw new InvalidArgumentException( "Expected IRI, got QName: $base:$local" ); |
136 | } else { |
137 | $value = $base; |
138 | } |
139 | |
140 | return [ |
141 | // @phan-suppress-next-line PhanTypeMismatchReturn |
142 | "rdf:$name" => $value |
143 | ]; |
144 | } |
145 | |
146 | /** |
147 | * Emit a document header. |
148 | */ |
149 | private function beginDocument() { |
150 | $this->write( "<?xml version=\"1.0\"?>\n" ); |
151 | |
152 | // define a callback for generating namespace attributes |
153 | $namespaceAttrCallback = function () { |
154 | $attr = ''; |
155 | |
156 | $namespaces = $this->getPrefixes(); |
157 | foreach ( $namespaces as $ns => $uri ) { |
158 | $escapedUri = htmlspecialchars( $uri, ENT_QUOTES ); |
159 | $nss = $ns === '' ? '' : ":$ns"; |
160 | $attr .= " xmlns$nss=\"$escapedUri\""; |
161 | } |
162 | |
163 | return $attr; |
164 | }; |
165 | |
166 | $this->tag( 'rdf', 'RDF', [ $namespaceAttrCallback ] ); |
167 | $this->write( "\n" ); |
168 | } |
169 | |
170 | /** |
171 | * @param string $base |
172 | * @param string|null $local |
173 | */ |
174 | protected function writeSubject( $base, $local = null ) { |
175 | $attr = $this->getTargetAttributes( 'about', $base, $local ); |
176 | |
177 | $this->write( "\t" ); |
178 | $this->tag( 'rdf', 'Description', $attr ); |
179 | $this->write( "\n" ); |
180 | } |
181 | |
182 | /** |
183 | * Emit the root element |
184 | */ |
185 | private function finishSubject() { |
186 | $this->write( "\t" ); |
187 | $this->close( 'rdf', 'Description' ); |
188 | $this->write( "\n" ); |
189 | } |
190 | |
191 | /** |
192 | * Write document footer |
193 | */ |
194 | private function finishDocument() { |
195 | // close document element |
196 | $this->close( 'rdf', 'RDF' ); |
197 | $this->write( "\n" ); |
198 | } |
199 | |
200 | /** |
201 | * @param string $base |
202 | * @param string|null $local |
203 | */ |
204 | protected function writePredicate( $base, $local = null ) { |
205 | // noop |
206 | } |
207 | |
208 | /** |
209 | * @param string $base |
210 | * @param string|null $local |
211 | */ |
212 | protected function writeResource( $base, $local = null ) { |
213 | $attr = $this->getTargetAttributes( 'resource', $base, $local ); |
214 | |
215 | $this->write( "\t\t" ); |
216 | $this->tag( $this->currentPredicate[0], $this->currentPredicate[1], $attr, '' ); |
217 | $this->write( "\n" ); |
218 | } |
219 | |
220 | /** |
221 | * @param string $text |
222 | * @param string|null $language |
223 | */ |
224 | protected function writeText( $text, $language = null ) { |
225 | $attr = $this->isValidLanguageCode( $language ) |
226 | ? [ 'xml:lang' => $language ] |
227 | : []; |
228 | |
229 | $this->write( "\t\t" ); |
230 | $this->tag( |
231 | $this->currentPredicate[0], |
232 | $this->currentPredicate[1], |
233 | $attr, |
234 | $this->escape( $text ) |
235 | ); |
236 | $this->write( "\n" ); |
237 | } |
238 | |
239 | /** |
240 | * @param string $literal |
241 | * @param string|null $typeBase |
242 | * @param string|null $typeLocal |
243 | */ |
244 | public function writeValue( $literal, $typeBase, $typeLocal = null ) { |
245 | $attr = $this->getTargetAttributes( 'datatype', $typeBase, $typeLocal ); |
246 | |
247 | $this->write( "\t\t" ); |
248 | $this->tag( |
249 | $this->currentPredicate[0], |
250 | $this->currentPredicate[1], |
251 | $attr, |
252 | $this->escape( $literal ) |
253 | ); |
254 | $this->write( "\n" ); |
255 | } |
256 | |
257 | /** |
258 | * @param string $role |
259 | * @param BNodeLabeler $labeler |
260 | * |
261 | * @return RdfWriterBase |
262 | */ |
263 | protected function newSubWriter( $role, BNodeLabeler $labeler ) { |
264 | $writer = new self( $role, $labeler ); |
265 | |
266 | return $writer; |
267 | } |
268 | |
269 | /** |
270 | * @return string a MIME type |
271 | */ |
272 | public function getMimeType() { |
273 | return 'application/rdf+xml; charset=UTF-8'; |
274 | } |
275 | |
276 | } |