Passed
Push — master ( c01fee...eeab5f )
by Daniele
02:33
created

FluidXml::__construct()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 1
Metric Value
dl 0
loc 26
ccs 11
cts 11
cp 1
rs 8.8571
cc 1
eloc 10
nc 1
nop 1
crap 1
1
<?php
2
3
namespace FluidXml;
4
5
/**
6
 * @method FluidXml namespace(...$arguments)
7
 */
8
class FluidXml implements FluidInterface
9
{
10
        use FluidAliasesTrait,
11
            FluidSaveTrait,
12
            NewableTrait,
13
            ReservedCallTrait,          // For compatibility with PHP 5.6.
14
            ReservedCallStaticTrait;    // For compatibility with PHP 5.6.
15
16
        const ROOT_NODE = 'doc';
17
18
        private $defaults = [ 'root'       => self::ROOT_NODE,
19
                              'version'    => '1.0',
20
                              'encoding'   => 'UTF-8',
21
                              'stylesheet' => null ];
22
23
        private $document;
24
        private $handler;
25
        private $context;
26
        private $contextEl;
27
28 1
        public static function load($document)
29
        {
30 1
                $file     = $document;
31 1
                $document = \file_get_contents($file);
32
33
                // file_get_contents() returns false in case of error.
34 1
                if (! $document) {
35 1
                        throw new \Exception("File '$file' not accessible.");
36
                }
37
38 1
                return (new FluidXml(null))->addChild($document);
39
        }
40
41 1
        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 1
                $options = $this->mergeOptions($arguments);
47
48
                // Having the options set, we can build the FluidDocument model
49
                // which incapsulates the DOM and the corresponding XPath instance.
50 1
                $document = new FluidDocument();
51 1
                $document->dom   = $this->newDom($options);
52 1
                $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 1
                $handler = new FluidInsertionHandler($document);
58
59
                // Ok, it's time to let them beeing visible along the instance.
60 1
                $this->document = $document;
61 1
                $this->handler  = $handler;
62
63
                // Now, we can further populate the DOM with any stylesheet or child.
64 1
                $this->initStylesheet($options)
65 1
                     ->initRoot($options);
66 1
        }
67
68 1
        protected function mergeOptions(&$arguments)
69
        {
70 1
                $options = $this->defaults;
71
72 1
                if (\count($arguments) > 0) {
73
                        // The root option can be specified as first argument
74
                        // because it is the most common.
75 1
                        $options['root'] = $arguments[0];
76
                }
77
78 1
                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 1
                        $options = \array_merge($options, $arguments[1]);
82
                }
83
84 1
                return $options;
85
        }
86
87 1
        private function newDom(&$options)
88
        {
89 1
                $dom = new \DOMDocument($options['version'], $options['encoding']);
90 1
                $dom->formatOutput       = true;
91 1
                $dom->preserveWhiteSpace = false;
92
93 1
                return $dom;
94
        }
95
96 1
        private function initStylesheet(&$options)
97
        {
98 1
                if (! empty($options['stylesheet'])) {
99
                        $attrs = 'type="text/xsl" '
100 1
                               . "encoding=\"{$options['encoding']}\" "
101 1
                               . 'indent="yes" '
102 1
                               . "href=\"{$options['stylesheet']}\"";
103
104 1
                        $stylesheet = new \DOMProcessingInstruction('xml-stylesheet', $attrs);
105
106 1
                        $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 1
                return $this;
114
        }
115
116 1
        private function initRoot(&$options)
117
        {
118 1
                if (! empty($options['root'])) {
119 1
                        $this->appendSibling($options['root']);
120
                }
121
122 1
                return $this;
123
        }
124
125 1
        public function length()
126
        {
127 1
                return \count($this->array());
0 ignored issues
show
Bug introduced by
The method array() does not exist on FluidXml\FluidXml. Did you maybe mean array_()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
128
        }
129
130 1
        public function dom()
131
        {
132 1
                return $this->document->dom;
133
        }
134
135
        // This method should be called 'array',
136
        // but for compatibility with PHP 5.6
137
        // it is shadowed by the __call() method.
138 1
        public function array_()
139
        {
140 1
                $el = $this->document->dom->documentElement;
141
142 1
                if ($el === null) {
143
                        $el = $this->document->dom;
144
                }
145
146 1
                return [ $el ];
147
        }
148
149 1
        public function __toString()
150
        {
151 1
                return $this->xml();
152
        }
153
154 1
        public function xml($strip = false)
155
        {
156 1
                if ($strip) {
157 1
                        return FluidHelper::domdocumentToStringWithoutHeaders($this->document->dom);
158
                }
159
160 1
                return $this->document->dom->saveXML();
161
        }
162
163 1
        public function html($strip = false)
164
        {
165 1
                $header = "<!DOCTYPE html>\n";
166
167 1
                if ($strip) {
168 1
                        $header = '';
169
                }
170
171 1
                $html = FluidHelper::domdocumentToStringWithoutHeaders($this->document->dom, true);
172
173 1
                return "{$header}{$html}";
174
        }
175
176 1
        public function namespaces()
177
        {
178 1
                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 1
        protected function namespace_(...$arguments)
185
        {
186 1
                $namespaces = [];
187
188 1
                if (\is_string($arguments[0])) {
189 1
                        $args = [ $arguments[0], $arguments[1] ];
190
191 1
                        if (isset($arguments[2])) {
192 1
                                $args[] = $arguments[2];
193
                        }
194
195 1
                        $namespaces[] = new FluidNamespace(...$args);
196 1
                }  elseif (\is_array($arguments[0])) {
197 1
                        $namespaces = $arguments[0];
198
                } else {
199 1
                        $namespaces = $arguments;
200
                }
201
202 1
                foreach ($namespaces as $n) {
203 1
                        $this->document->namespaces[$n->id()] = $n;
204 1
                        $this->document->xpath->registerNamespace($n->id(), $n->uri());
205
                }
206
207 1
                return $this;
208
        }
209
210
        public function query(...$query)                   { return $this->context()->query(...$query); }
211
        public function times($times, callable $fn = null) { return $this->context()->times($times, $fn); }
212
        public function each(callable $fn)                 { return $this->context()->each($fn); }
213
        public function filter(callable $fn)               { return $this->context()->filter($fn); }
214
        public function setAttribute($name, $value = null) { $this->context()->setAttribute($name, $value); return $this; }
215
        public function setText($text)                     { $this->context()->setText($text);    return $this; }
216
        public function addText($text)                     { $this->context()->addText($text);    return $this; }
217
        public function setCdata($text)                    { $this->context()->setCdata($text);   return $this; }
218
        public function addCdata($text)                    { $this->context()->addCdata($text);   return $this; }
219
        public function setComment($text)                  { $this->context()->setComment($text); return $this; }
220
        public function addComment($text)                  { $this->context()->addComment($text); return $this; }
221
        public function remove(...$query)                  { $this->context()->remove(...$query); return $this; }
222
223 1
        public function addChild($child, ...$optionals)
224
        {
225
                return $this->chooseContext(function($cx) use ($child, &$optionals) {
226 1
                        return $cx->addChild($child, ...$optionals);
227 1
                });
228
        }
229
230 1
        public function prependSibling($sibling, ...$optionals)
231
        {
232
                return $this->chooseContext(function($cx) use ($sibling, &$optionals) {
233 1
                        return $cx->prependSibling($sibling, ...$optionals);
234 1
                });
235
        }
236
237
        public function appendSibling($sibling, ...$optionals)
238
        {
239 1
                return $this->chooseContext(function($cx) use ($sibling, &$optionals) {
240 1
                        return $cx->appendSibling($sibling, ...$optionals);
241 1
                });
242
        }
243
244 1
        protected function context()
245
        {
246 1
                $el = $this->document->dom->documentElement;
247
248 1
                if ($el === null) {
249
                        // Whether there is not a root node
250
                        // the DOMDocument is promoted as root node.
251 1
                        $el = $this->document->dom;
252
                }
253
254 1
                if ($this->context === null || $el !== $this->contextEl) {
255
                        // The user can prepend a root node to the current root node.
256
                        // In this case we have to update the context with the new first root node.
257 1
                        $this->context   = new FluidContext($this->document, $this->handler, $el);
258 1
                        $this->contextEl = $el;
259
                }
260
261 1
                return $this->context;
262
        }
263
264 1
        protected function chooseContext(\Closure $fn)
265
        {
266
                // If the user has requested ['root' => null] at construction time
267
                // 'context()' promotes DOMDocument as root node.
268
269 1
                $context     = $this->context();
270 1
                $new_context = $fn($context);
271
272 1
                if ($context !== $new_context) {
273
                        // If the two contextes are diffent, the user has requested
274
                        // a switch of the context and we have to return it.
275 1
                        return $new_context;
276
                }
277
278 1
                return $this;
279
        }
280
}
281