Passed
Pull Request — master (#35)
by
unknown
05:31
created

FluidXml::length()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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