Passed
Push — master ( 60319d...b48e8d )
by Daniele
08:21
created

FluidXml::length()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace FluidXml;
4
5
/**
6
 * @method FluidXml namespace(...$arguments)
7
 */
8
class FluidXml implements FluidInterface
9
{
10
        use NewableTrait,
11
            FluidSaveTrait,
12
            ReservedCallTrait,          // For compatibility with PHP 5.6.
13
            ReservedCallStaticTrait;    // For compatibility with PHP 5.6.
14
15
        const ROOT_NODE = 'doc';
16
17
        private $defaults = [ 'root'       => self::ROOT_NODE,
18
                              'version'    => '1.0',
19
                              'encoding'   => 'UTF-8',
20
                              'stylesheet' => null ];
21
22
        private $document;
23
        private $handler;
24
25
        private $context;
26
        private $contextEl;
27
28
        public static function load($document)
29
        {
30
                $file     = $document;
31
                $document = \file_get_contents($file);
32
33
                // file_get_contents() returns false in case of error.
34
                if (! $document) {
35
                        throw new \Exception("File '$file' not accessible.");
36
                }
37
38
                return (new FluidXml(null))->addChild($document);
39
        }
40
41
        public function __construct(...$arguments)
42
        {
43
                // First, we parse the arguments detecting the options provided.
44
                // This options are needed to build the DOM, add the stylesheet
45
                // and to create the document root/structure.
46
                $options = $this->buildOptions($arguments);
47
48
                // Having the options set, we can build the FluidDocument model
49
                // which incapsulates the DOM and the corresponding XPath instance.
50
                $document = new FluidDocument();
51
                $document->dom   = $this->buildDom($options);
52
                $document->xpath = new \DOMXPath($document->dom);
53
54
                // After the FluidDocument model creation, we can proceed to build
55
                // the FluidInsertionHandler which requires the model to perform
56
                // its logics.
57
                $handler = new FluidInsertionHandler($document);
58
59
                // Ok, it's time to let them beeing visible along the instance.
60
                $this->document = $document;
61
                $this->handler  = $handler;
62
63
                // Now, we can further populate the DOM with any stylesheet or child.
64
                $this->initStylesheet($options)
65
                     ->initRoot($options);
66
        }
67
68
        protected function buildOptions(&$arguments)
69
        {
70
                $custom = [];
71
72
                if (\count($arguments) > 0) {
73
                        // The root option can be specified as first argument
74
                        // because it is the most common.
75
                        $this->defaults['root'] = $arguments[0];
76
                }
77
78
                if (\count($arguments) > 1) {
79
                        // Custom options can be specified only as second argument,
80
                        // to avoid confusion with array to XML construction style.
81
                        $custom = $arguments[1];
82
                }
83
84
                return \array_merge($this->defaults, $custom);
85
        }
86
87
        private function buildDom(&$options)
88
        {
89
                $dom = new \DOMDocument($options['version'], $options['encoding']);
90
                $dom->formatOutput       = true;
91
                $dom->preserveWhiteSpace = false;
92
93
                return $dom;
94
        }
95
96
        private function initStylesheet(&$options)
97
        {
98
                if (! empty($options['stylesheet'])) {
99
                        $attrs = 'type="text/xsl" '
100
                               . "encoding=\"{$options['encoding']}\" "
101
                               . 'indent="yes" '
102
                               . "href=\"{$options['stylesheet']}\"";
103
104
                        $stylesheet = new \DOMProcessingInstruction('xml-stylesheet', $attrs);
105
106
                        $this->addChild($stylesheet);
107
108
                        // Algorithm 2:
109
                        // Used in case the order of the stylesheet and root creation is reversed.
110
                        // $this->document->dom->insertBefore($stylesheet, $this->document->dom->documentElement);
111
                }
112
113
                return $this;
114
        }
115
116
        private function initRoot(&$options)
117
        {
118
                if (! empty($options['root'])) {
119
                        $this->appendSibling($options['root']);
120
                }
121
122
                return $this;
123
        }
124
125
        public function length()
126
        {
127
                return 1;
128
        }
129
130
        // Alias of ->length().
131
        public function size()
132
        {
133
                return $this->length();
134
        }
135
136
        public function dom()
137
        {
138
                return $this->document->dom;
139
        }
140
141
        // This method should be called 'array',
142
        // but for compatibility with PHP 5.6
143
        // it is shadowed by the __call() method.
144
        public function array_()
145
        {
146
                return [ $this->document->dom ];
147
        }
148
149
        public function __toString()
150
        {
151
                return $this->xml();
152
        }
153
154
        public function xml($strip = false)
155
        {
156
                if ($strip) {
157
                        return FluidHelper::domdocumentToStringWithoutHeaders($this->document->dom);
158
                }
159
160
                return $this->document->dom->saveXML();
161
        }
162
163
        public function html($strip = false)
164
        {
165
                $header = "<!DOCTYPE html>\n";
166
167
                if ($strip) {
168
                        $header = '';
169
                }
170
171
                $html = FluidHelper::domdocumentToStringWithoutHeaders($this->document->dom, true);
172
173
                return "{$header}{$html}";
174
        }
175
176
        public function namespaces()
177
        {
178
                return $this->document->namespaces;
179
        }
180
181
        // This method should be called 'namespace',
182
        // but for compatibility with PHP 5.6
183
        // it is shadowed by the __call() method.
184
        protected function namespace_(...$arguments)
185
        {
186
                $namespaces = [];
187
188
                if (\is_string($arguments[0])) {
189
                        $args = [ $arguments[0], $arguments[1] ];
190
191
                        if (isset($arguments[2])) {
192
                                $args[] = $arguments[2];
193
                        }
194
195
                        $namespaces[] = new FluidNamespace(...$args);
196
                }  elseif (\is_array($arguments[0])) {
197
                        $namespaces = $arguments[0];
198
                } else {
199
                        $namespaces = $arguments;
200
                }
201
202
                foreach ($namespaces as $n) {
203
                        $this->document->namespaces[$n->id()] = $n;
204
                        $this->document->xpath->registerNamespace($n->id(), $n->uri());
205
                }
206
207
                return $this;
208
        }
209
210
        public function query(...$xpath)
211
        {
212
                return $this->context()->query(...$xpath);
213
        }
214
215
        // Alias of ->query().
216
        public function __invoke(...$xpath)
217
        {
218
                return $this->query(...$xpath);
219
        }
220
221
        public function times($times, callable $fn = null)
222
        {
223
                return $this->context()->times($times, $fn);
224
        }
225
226
        public function each(callable $fn)
227
        {
228
                return $this->context()->each($fn);
229
        }
230
231
        public function filter(callable $fn)
232
        {
233
                return $this->context()->filter($fn);
234
        }
235
236
        public function addChild($child, ...$optionals)
237
        {
238
                // If the user has requested ['root' => null] at construction time
239
                // 'context()' promotes DOMDocument as root node.
240
                $context     = $this->context();
241
                $new_context = $context->addChild($child, ...$optionals);
242
243
                return $this->chooseContext($context, $new_context);
244
        }
245
246
        // Alias of ->addChild().
247
        public function add($child, ...$optionals)
248
        {
249
                return $this->addChild($child, ...$optionals);
250
        }
251
252 View Code Duplication
        public function prependSibling($sibling, ...$optionals)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
253
        {
254
                if ($this->document->dom->documentElement === null) {
255
                        // If the document doesn't have at least one root node,
256
                        // the sibling creation fails. In this case we replace
257
                        // the sibling creation with the creation of a generic node.
258
                        return $this->addChild($sibling, ...$optionals);
259
                }
260
261
                $context     = $this->context();
262
                $new_context = $context->prependSibling($sibling, ...$optionals);
263
264
                return $this->chooseContext($context, $new_context);
265
        }
266
267
        // Alias of ->prependSibling().
268
        public function prepend($sibling, ...$optionals)
269
        {
270
                return $this->prependSibling($sibling, ...$optionals);
271
        }
272
273 View Code Duplication
        public function appendSibling($sibling, ...$optionals)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
274
        {
275
                if ($this->document->dom->documentElement === null) {
276
                        // If the document doesn't have at least one root node,
277
                        // the sibling creation fails. In this case we replace
278
                        // the sibling creation with the creation of a generic node.
279
                        return $this->addChild($sibling, ...$optionals);
280
                }
281
282
                $context     = $this->context();
283
                $new_context = $context->appendSibling($sibling, ...$optionals);
284
285
                return $this->chooseContext($context, $new_context);
286
        }
287
288
        // Alias of ->appendSibling().
289
        public function append($sibling, ...$optionals)
290
        {
291
                return $this->appendSibling($sibling, ...$optionals);
292
        }
293
294
        public function setAttribute(...$arguments)
295
        {
296
                $this->context()->setAttribute(...$arguments);
297
298
                return $this;
299
        }
300
301
        // Alias of ->setAttribute().
302
        public function attr(...$arguments)
303
        {
304
                return $this->setAttribute(...$arguments);
305
        }
306
307
        public function setText($text)
308
        {
309
                $this->context()->setText($text);
310
311
                return $this;
312
        }
313
314
        // Alias of ->setText().
315
        public function text($text)
316
        {
317
                return $this->setText($text);
318
        }
319
320
        public function addText($text)
321
        {
322
                $this->context()->addText($text);
323
324
                return $this;
325
        }
326
327
        public function setCdata($text)
328
        {
329
                $this->context()->setCdata($text);
330
331
                return $this;
332
        }
333
334
        // Alias of ->setCdata().
335
        public function cdata($text)
336
        {
337
                return $this->setCdata($text);
338
        }
339
340
        public function addCdata($text)
341
        {
342
                $this->context()->addCdata($text);
343
344
                return $this;
345
        }
346
347
        public function setComment($text)
348
        {
349
                $this->context()->setComment($text);
350
351
                return $this;
352
        }
353
354
        // Alias of ->setComment().
355
        public function comment($text)
356
        {
357
                return $this->setComment($text);
358
        }
359
360
        public function addComment($text)
361
        {
362
                $this->context()->addComment($text);
363
364
                return $this;
365
        }
366
367
        public function remove(...$xpath)
368
        {
369
                $this->context()->remove(...$xpath);
370
371
                return $this;
372
        }
373
374
        protected function context()
375
        {
376
                $el = $this->document->dom->documentElement;
377
378
                if ($el === null) {
379
                        // Whether there is not a root node
380
                        // the DOMDocument is promoted as root node.
381
                        $el = $this->document->dom;
382
                }
383
384
                if ($this->context === null || $el !== $this->contextEl) {
385
                        // The user can prepend a root node to the current root node.
386
                        // In this case we have to update the context with the new first root node.
387
                        $this->context   = new FluidContext($this->document, $this->handler, $el);
388
                        $this->contextEl = $el;
389
                }
390
391
                return $this->context;
392
        }
393
394
        protected function chooseContext($help_context, $new_context)
395
        {
396
                // If the two contextes are diffent, the user has requested
397
                // a switch of the context and we have to return it.
398
                if ($help_context !== $new_context) {
399
                        return $new_context;
400
                }
401
402
                return $this;
403
        }
404
}
405