Passed
Push — master ( 26be1e...fa70e8 )
by Daniele
03:11
created

FluidInsertionHandler::handleIntegerDocument()   C

Complexity

Conditions 8
Paths 14

Size

Total Lines 32
Code Lines 17

Duplication

Lines 4
Ratio 12.5 %

Code Coverage

Tests 13
CRAP Score 9.372
Metric Value
dl 4
loc 32
ccs 13
cts 18
cp 0.7221
rs 5.3846
cc 8
eloc 17
nc 14
nop 6
crap 9.372
1
<?php
2
3
namespace FluidXml;
4
5
class FluidInsertionHandler
6
{
7
        private $document;
8
        private $dom;
9
        private $namespaces;
10
11 1
        public function __construct($document)
12
        {
13 1
                $this->document   = $document;
14 1
                $this->dom        = $document->dom;
15 1
                $this->namespaces =& $document->namespaces;
16 1
        }
17
18 1
        public function insertElement(&$nodes, $element, &$optionals, $fn, $orig_context)
19
        {
20 1
                list($element, $attributes, $switch_context) = $this->handleOptionals($element, $optionals);
21
22 1
                $new_nodes = [];
23
24 1
                foreach ($nodes as $n) {
25 1
                        foreach ($element as $k => $v) {
26 1
                                $cx        = $this->handleInsertion($n, $k, $v, $fn, $optionals);
27 1
                                $new_nodes = \array_merge($new_nodes, $cx);
28
                        }
29
                }
30
31 1
                $new_context = $this->newContext($new_nodes);
32
33
                // Setting the attributes is an help that the addChild method
34
                // offers to the user and is the same of:
35
                // 1. appending a child switching the context
36
                // 2. setting the attributes over the new context.
37 1
                if (! empty($attributes)) {
38 1
                        $new_context->setAttribute($attributes);
39
                }
40
41 1
                return $switch_context ? $new_context : $orig_context;
42
        }
43
44 1
        protected function newContext(&$context)
45
        {
46 1
                return new FluidContext($this->document, $this, $context);
47
        }
48
49 1
        protected function handleOptionals($element, &$optionals)
50
        {
51 1
                if (! \is_array($element)) {
52 1
                        $element = [ $element ];
53
                }
54
55 1
                $switch_context = false;
56 1
                $attributes     = [];
57
58 1
                foreach ($optionals as $opt) {
59 1
                        if (\is_array($opt)) {
60 1
                                $attributes = $opt;
61 1
                                continue;
62
                        }
63
64 1
                        if (\is_bool($opt)) {
65 1
                                $switch_context = $opt;
66 1
                                continue;
67
                        }
68
69 1
                        if (\is_string($opt) || \is_numeric($opt)) {
70 1
                                $e = \array_pop($element);
71 1
                                $element[$e] = $opt;
72 1
                                continue;
73
74
                        }
75
                }
76
77 1
                return [ $element, $attributes, $switch_context ];
78
        }
79
80
81 1
        protected function handleInsertion($parent, $k, $v, $fn, &$optionals)
82
        {
83
                // This is an highly optimized method.
84
                // Good code design would split this method in many different handlers
85
                // each one with its own checks. But it is too much expensive in terms
86
                // of performances for a core method like this, so this implementation
87
                // is prefered to collapse many identical checks to one.
88
89 1
                $handlers = ['handleStringMixed', 'handleIntegerMixed', 'handleIntegerDocument'];
90
91 1
                $status = false;
92 1
                foreach ($handlers as $handler) {
93 1
                        $return = $this->$handler($parent, $k, $v, $fn, $optionals, $status);
94
95 1
                        if ($status === true) {
96 1
                                return $return;
97
                        }
98
                }
99
100 1
                throw new \Exception('Input type not supported.');
101
        }
102
103 1
        protected function handleStringMixed($parent, $k, $v, $fn, &$optionals, &$status)
104
        {
105 1
                if (! \is_string($k)) {
106 1
                        return;
107
                }
108
109 1
                $handler = 'insertStringMixed';
110
111 1
                if ($k[0] === '@') {
112 1
                        $handler = 'insertSpecialAttribute';
113
114 1
                        if ($k === '@') {
115 1
                                $handler = 'insertSpecialContent';
116
                        }
117
                } else {
118 1
                        if (\is_string($v)) {
119 1
                                if (! FluidHelper::isAnXmlString($v)) {
120 1
                                        $handler = 'insertStringSimple';
121
                                }
122
                        } else {
123 1
                                if (\is_numeric($v)) {
124 1
                                        $handler = 'insertStringSimple';
125
                                }
126
                        }
127
                }
128
129 1
                $status = true;
130 1
                return $this->$handler($parent, $k, $v, $fn, $optionals);
131
        }
132
133 1
        protected function handleIntegerMixed($parent, $k, $v, $fn, &$optionals, &$status)
134
        {
135
                // if (! \is_integer($k)) {
136
                //         return;
137
                // }
138
139 1
                $handler = null;
140
141 1
                if (\is_string($v)) {
142 1
                        if (FluidHelper::isAnXmlString($v)) {
143 1
                                $handler = 'insertIntegerXml';
144
                        } else {
145 1
                                $handler = 'insertIntegerString';
146
                        }
147
                } elseif (\is_array($v)) {
148 1
                        $handler = 'insertIntegerArray';
149
                }
150
151 1 View Code Duplication
                if ($handler !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
152 1
                        $status = true;
153 1
                        return $this->$handler($parent, $k, $v, $fn, $optionals);
154
                }
155 1
        }
156
157 1
        protected function handleIntegerDocument($parent, $k, $v, $fn, &$optionals, &$status)
158
        {
159
                // if (! \is_integer($k)) {
160
                //         return;
161
                // }
162
163 1
                $handler = null;
164
165 1
                if ($v instanceof \DOMDocument) {
166 1
                        $handler = 'insertIntegerDomdocument';
167
168
                } elseif ($v instanceof \DOMNodeList) {
169 1
                        $handler = 'insertIntegerDomnodelist';
170
171
                } elseif ($v instanceof \DOMNode) {
172 1
                        $handler = 'insertIntegerDomnode';
173
174
                } elseif ($v instanceof \SimpleXMLElement) {
175 1
                        $handler = 'insertIntegerSimplexml';
176
177
                } elseif ($v instanceof FluidXml) {
178 1
                        $handler = 'insertIntegerFluidxml';
179
180
                } elseif ($v instanceof FluidContext) {
181 1
                        $handler = 'insertIntegerFluidcontext';
182
                }
183
184 1 View Code Duplication
                if ($handler !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
185 1
                        $status = true;
186 1
                        return $this->$handler($parent, $k, $v, $fn, $optionals);
187
                }
188 1
        }
189
190 1
        protected function createElement($name, $value = null)
191
        {
192
                // The DOMElement instance must be different for every node,
193
                // otherwise only one element is attached to the DOM.
194
195 1
                $id  = null;
196 1
                $uri = null;
197
198
                // The node name can contain the namespace id prefix.
199
                // Example: xsl:template
200 1
                $colon_pos = \strpos($name, ':');
201
202 1
                if ($colon_pos !== false) {
203 1
                        $id   = \substr($name, 0, $colon_pos);
204 1
                        $name = \substr($name, $colon_pos + 1);
205
                }
206
207 1
                if ($id !== null) {
208 1
                        $ns  = $this->namespaces[$id];
209 1
                        $uri = $ns->uri();
210
211 1
                        if ($ns->mode() === FluidNamespace::MODE_EXPLICIT) {
212 1
                                $name = "{$id}:{$name}";
213
                        }
214
                }
215
216
                // Algorithm 1:
217 1
                $el = new \DOMElement($name, $value, $uri);
218
219
                // Algorithm 2:
220
                // $el = $dom->createElement($name, $value);
221
222 1
                return $el;
223
        }
224
225 1
        protected function attachNodes($parent, $nodes, $fn)
226
        {
227 1
                if (! \is_array($nodes) && ! $nodes instanceof \Traversable) {
228 1
                        $nodes = [ $nodes ];
229
                }
230
231 1
                $context = [];
232
233 1
                foreach ($nodes as $el) {
234 1
                        $el        = $this->dom->importNode($el, true);
235 1
                        $context[] = $fn($parent, $el);
236
                }
237
238 1
                return $context;
239
        }
240
241 1
        protected function insertSpecialContent($parent, $k, $v)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
242
        {
243
                // The user has passed an element text content:
244
                // [ '@' => 'Element content.' ]
245
246
                // Algorithm 1:
247 1
                $this->newContext($parent)->addText($v);
248
249
                // Algorithm 2:
250
                // $this->setText($v);
251
252
                // The user can specify multiple '@' special elements
253
                // so Algorithm 1 is the right choice.
254
255 1
                return [];
256
        }
257
258 1
        protected function insertSpecialAttribute($parent, $k, $v)
259
        {
260
                // The user has passed an attribute name and an attribute value:
261
                // [ '@attribute' => 'Attribute content' ]
262
263 1
                $attr = \substr($k, 1);
264 1
                $this->newContext($parent)->setAttribute($attr, $v);
265
266 1
                return [];
267
        }
268
269 1
        protected function insertStringSimple($parent, $k, $v, $fn)
270
        {
271
                // The user has passed an element name and an element value:
272
                // [ 'element' => 'Element content' ]
273
274 1
                $el = $this->createElement($k, $v);
275 1
                $el = $fn($parent, $el);
276
277 1
                return [ $el ];
278
        }
279
280 1
        protected function insertStringMixed($parent, $k, $v, $fn, &$optionals)
281
        {
282
                // The user has passed one of these cases:
283
                // - [ 'element' => [...] ]
284
                // - [ 'element' => '<xml>...</xml>' ]
285
                // - [ 'element' => DOMNode|SimpleXMLElement|FluidXml ]
286
287 1
                $el = $this->createElement($k);
288 1
                $el = $fn($parent, $el);
289
290
                // The new children elements must be created in the order
291
                // they are supplied, so 'addChild' is the perfect operation.
292 1
                $this->newContext($el)->addChild($v, ...$optionals);
293
294 1
                return [ $el ];
295
        }
296
297 1
        protected function insertIntegerArray($parent, $k, $v, $fn, &$optionals)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
298
        {
299
                // The user has passed a wrapper array:
300
                // [ [...], ... ]
301
302 1
                $context = [];
303
304 1
                foreach ($v as $kk => $vv) {
305 1
                        $cx = $this->handleInsertion($parent, $kk, $vv, $fn, $optionals);
306
307 1
                        $context = \array_merge($context, $cx);
308
                }
309
310 1
                return $context;
311
        }
312
313 1
        protected function insertIntegerString($parent, $k, $v, $fn)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
314
        {
315
                // The user has passed a node name without a node value:
316
                // [ 'element', ... ]
317
318 1
                $el = $this->createElement($v);
319 1
                $el = $fn($parent, $el);
320
321 1
                return [ $el ];
322
        }
323
324 1
        protected function insertIntegerXml($parent, $k, $v, $fn)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
325
        {
326
                // The user has passed an XML document instance:
327
                // [ '<tag></tag>', DOMNode, SimpleXMLElement, FluidXml ]
328
329 1
                $wrapper = new \DOMDocument();
330 1
                $wrapper->formatOutput       = true;
331 1
                $wrapper->preserveWhiteSpace = false;
332
333 1
                $v = \ltrim($v);
334
335 1
                if ($v[1] === '?') {
336 1
                        $wrapper->loadXML($v);
337 1
                        $nodes = $wrapper->childNodes;
338
                } else {
339
                        // A way to import strings with multiple root nodes.
340 1
                        $wrapper->loadXML("<root>$v</root>");
341
342
                        // Algorithm 1:
343 1
                        $nodes = $wrapper->documentElement->childNodes;
344
345
                        // Algorithm 2:
346
                        // $xp = new \DOMXPath($wrapper);
347
                        // $nodes = $xp->query('/root/*');
348
                }
349
350 1
                return $this->attachNodes($parent, $nodes, $fn);
351
        }
352
353 1
        protected function insertIntegerDomdocument($parent, $k, $v, $fn)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
354
        {
355
                // A DOMDocument can have multiple root nodes.
356
357
                // Algorithm 1:
358 1
                return $this->attachNodes($parent, $v->childNodes, $fn);
359
360
                // Algorithm 2:
361
                // return $this->attachNodes($parent, $v->documentElement, $fn);
362
        }
363
364 1
        protected function insertIntegerDomnodelist($parent, $k, $v, $fn)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
365
        {
366 1
                return $this->attachNodes($parent, $v, $fn);
367
        }
368
369 1
        protected function insertIntegerDomnode($parent, $k, $v, $fn)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
370
        {
371 1
                return $this->attachNodes($parent, $v, $fn);
372
        }
373
374 1
        protected function insertIntegerSimplexml($parent, $k, $v, $fn)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
375
        {
376 1
                return $this->attachNodes($parent, \dom_import_simplexml($v), $fn);
377
        }
378
379 1
        protected function insertIntegerFluidxml($parent, $k, $v, $fn)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
380
        {
381 1
                return $this->attachNodes($parent, $v->dom()->documentElement, $fn);
382
        }
383
384 1
        protected function insertIntegerFluidcontext($parent, $k, $v, $fn)
1 ignored issue
show
Unused Code introduced by
The parameter $k is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
385
        {
386 1
                return $this->attachNodes($parent, $v->array(), $fn);
387
        }
388
}
389