Passed
Push — master ( fa70e8...a6a555 )
by Daniele
02:35
created

FluidInsertionHandler::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1
Metric Value
dl 0
loc 6
ccs 5
cts 5
cp 1
rs 9.4285
cc 1
eloc 4
nc 1
nop 1
crap 1
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
62
                        } elseif (\is_bool($opt)) {
63 1
                                $switch_context = $opt;
64
65 1
                        } elseif (\is_string($opt) || \is_numeric($opt)) {
66 1
                                $e = \array_pop($element);
67 1
                                $element[$e] = $opt;
68
                        }
69
                }
70
71 1
                return [ $element, $attributes, $switch_context ];
72
        }
73
74
75 1
        protected function handleInsertion($parent, $k, $v, $fn, &$optionals)
76
        {
77
                // This is an highly optimized method.
78
                // Good code design would split this method in many different handlers
79
                // each one with its own checks. But it is too much expensive in terms
80
                // of performances for a core method like this, so this implementation
81
                // is prefered to collapse many identical checks to one.
82
83 1
                $handlers = ['handleStringMixed', 'handleIntegerMixed', 'handleIntegerDocument'];
84
85 1
                $status = false;
86 1
                foreach ($handlers as $handler) {
87 1
                        $return = $this->$handler($parent, $k, $v, $fn, $optionals, $status);
88
89 1
                        if ($status === true) {
90 1
                                return $return;
91
                        }
92
                }
93
94 1
                throw new \Exception('Input type not supported.');
95
        }
96
97 1
        protected function handleStringMixed($parent, $k, $v, $fn, &$optionals, &$status)
98
        {
99 1
                if (! \is_string($k)) {
100 1
                        return;
101
                }
102
103 1
                $handler = 'insertStringMixed';
104
105 1
                if ($k[0] === '@') {
106 1
                        $handler = 'insertSpecialAttribute';
107
108 1
                        if ($k === '@') {
109 1
                                $handler = 'insertSpecialContent';
110
                        }
111
                } else {
112 1
                        if (\is_string($v)) {
113 1
                                if (! FluidHelper::isAnXmlString($v)) {
114 1
                                        $handler = 'insertStringSimple';
115
                                }
116
                        } else {
117 1
                                if (\is_numeric($v)) {
118 1
                                        $handler = 'insertStringSimple';
119
                                }
120
                        }
121
                }
122
123 1
                $status = true;
124 1
                return $this->$handler($parent, $k, $v, $fn, $optionals);
125
        }
126
127 1
        protected function handleIntegerMixed($parent, $k, $v, $fn, &$optionals, &$status)
128
        {
129
                // if (! \is_integer($k)) {
130
                //         return;
131
                // }
132
133 1
                $handler = null;
134
135 1
                if (\is_string($v)) {
136 1
                        if (FluidHelper::isAnXmlString($v)) {
137 1
                                $handler = 'insertIntegerXml';
138
                        } else {
139 1
                                $handler = 'insertIntegerString';
140
                        }
141
                } elseif (\is_array($v)) {
142 1
                        $handler = 'insertIntegerArray';
143
                }
144
145 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...
146 1
                        $status = true;
147 1
                        return $this->$handler($parent, $k, $v, $fn, $optionals);
148
                }
149 1
        }
150
151 1
        protected function handleIntegerDocument($parent, $k, $v, $fn, &$optionals, &$status)
152
        {
153
                // if (! \is_integer($k)) {
154
                //         return;
155
                // }
156
157 1
                $handler = null;
158
159 1
                if ($v instanceof \DOMDocument) {
160 1
                        $handler = 'insertIntegerDomdocument';
161
162
                } elseif ($v instanceof \DOMNodeList) {
163 1
                        $handler = 'insertIntegerDomnodelist';
164
165
                } elseif ($v instanceof \DOMNode) {
166 1
                        $handler = 'insertIntegerDomnode';
167
168
                } elseif ($v instanceof \SimpleXMLElement) {
169 1
                        $handler = 'insertIntegerSimplexml';
170
171
                } elseif ($v instanceof FluidXml) {
172 1
                        $handler = 'insertIntegerFluidxml';
173
174
                } elseif ($v instanceof FluidContext) {
175 1
                        $handler = 'insertIntegerFluidcontext';
176
                }
177
178 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...
179 1
                        $status = true;
180 1
                        return $this->$handler($parent, $k, $v, $fn, $optionals);
181
                }
182 1
        }
183
184 1
        protected function createElement($name, $value = null)
185
        {
186
                // The DOMElement instance must be different for every node,
187
                // otherwise only one element is attached to the DOM.
188
189 1
                $id  = null;
190 1
                $uri = null;
191
192
                // The node name can contain the namespace id prefix.
193
                // Example: xsl:template
194 1
                $colon_pos = \strpos($name, ':');
195
196 1
                if ($colon_pos !== false) {
197 1
                        $id   = \substr($name, 0, $colon_pos);
198 1
                        $name = \substr($name, $colon_pos + 1);
199
                }
200
201 1
                if ($id !== null) {
202 1
                        $ns  = $this->namespaces[$id];
203 1
                        $uri = $ns->uri();
204
205 1
                        if ($ns->mode() === FluidNamespace::MODE_EXPLICIT) {
206 1
                                $name = "{$id}:{$name}";
207
                        }
208
                }
209
210
                // Algorithm 1:
211 1
                $el = new \DOMElement($name, $value, $uri);
212
213
                // Algorithm 2:
214
                // $el = $dom->createElement($name, $value);
215
216 1
                return $el;
217
        }
218
219 1
        protected function attachNodes($parent, $nodes, $fn)
220
        {
221 1
                if (! \is_array($nodes) && ! $nodes instanceof \Traversable) {
222 1
                        $nodes = [ $nodes ];
223
                }
224
225 1
                $context = [];
226
227 1
                foreach ($nodes as $el) {
228 1
                        $el        = $this->dom->importNode($el, true);
229 1
                        $context[] = $fn($parent, $el);
230
                }
231
232 1
                return $context;
233
        }
234
235 1
        protected function insertSpecialContent($parent, $k, $v)
236
        {
237
                // The user has passed an element text content:
238
                // [ '@' => 'Element content.' ]
239
240
                // Algorithm 1:
241 1
                $this->newContext($parent)->addText($v);
242
243
                // Algorithm 2:
244
                // $this->setText($v);
245
246
                // The user can specify multiple '@' special elements
247
                // so Algorithm 1 is the right choice.
248
249 1
                return [];
250
        }
251
252 1
        protected function insertSpecialAttribute($parent, $k, $v)
253
        {
254
                // The user has passed an attribute name and an attribute value:
255
                // [ '@attribute' => 'Attribute content' ]
256
257 1
                $attr = \substr($k, 1);
258 1
                $this->newContext($parent)->setAttribute($attr, $v);
259
260 1
                return [];
261
        }
262
263 1
        protected function insertStringSimple($parent, $k, $v, $fn)
264
        {
265
                // The user has passed an element name and an element value:
266
                // [ 'element' => 'Element content' ]
267
268 1
                $el = $this->createElement($k, $v);
269 1
                $el = $fn($parent, $el);
270
271 1
                return [ $el ];
272
        }
273
274 1
        protected function insertStringMixed($parent, $k, $v, $fn, &$optionals)
275
        {
276
                // The user has passed one of these cases:
277
                // - [ 'element' => [...] ]
278
                // - [ 'element' => '<xml>...</xml>' ]
279
                // - [ 'element' => DOMNode|SimpleXMLElement|FluidXml ]
280
281 1
                $el = $this->createElement($k);
282 1
                $el = $fn($parent, $el);
283
284
                // The new children elements must be created in the order
285
                // they are supplied, so 'addChild' is the perfect operation.
286 1
                $this->newContext($el)->addChild($v, ...$optionals);
287
288 1
                return [ $el ];
289
        }
290
291 1
        protected function insertIntegerArray($parent, $k, $v, $fn, &$optionals)
292
        {
293
                // The user has passed a wrapper array:
294
                // [ [...], ... ]
295
296 1
                $context = [];
297
298 1
                foreach ($v as $kk => $vv) {
299 1
                        $cx = $this->handleInsertion($parent, $kk, $vv, $fn, $optionals);
300
301 1
                        $context = \array_merge($context, $cx);
302
                }
303
304 1
                return $context;
305
        }
306
307 1
        protected function insertIntegerString($parent, $k, $v, $fn)
308
        {
309
                // The user has passed a node name without a node value:
310
                // [ 'element', ... ]
311
312 1
                $el = $this->createElement($v);
313 1
                $el = $fn($parent, $el);
314
315 1
                return [ $el ];
316
        }
317
318 1
        protected function insertIntegerXml($parent, $k, $v, $fn)
319
        {
320
                // The user has passed an XML document instance:
321
                // [ '<tag></tag>', DOMNode, SimpleXMLElement, FluidXml ]
322
323 1
                $wrapper = new \DOMDocument();
324 1
                $wrapper->formatOutput       = true;
325 1
                $wrapper->preserveWhiteSpace = false;
326
327 1
                $v = \ltrim($v);
328
329 1
                if ($v[1] === '?') {
330 1
                        $wrapper->loadXML($v);
331 1
                        $nodes = $wrapper->childNodes;
332
                } else {
333
                        // A way to import strings with multiple root nodes.
334 1
                        $wrapper->loadXML("<root>$v</root>");
335
336
                        // Algorithm 1:
337 1
                        $nodes = $wrapper->documentElement->childNodes;
338
339
                        // Algorithm 2:
340
                        // $xp = new \DOMXPath($wrapper);
341
                        // $nodes = $xp->query('/root/*');
342
                }
343
344 1
                return $this->attachNodes($parent, $nodes, $fn);
345
        }
346
347 1
        protected function insertIntegerDomdocument($parent, $k, $v, $fn)
348
        {
349
                // A DOMDocument can have multiple root nodes.
350
351
                // Algorithm 1:
352 1
                return $this->attachNodes($parent, $v->childNodes, $fn);
353
354
                // Algorithm 2:
355
                // return $this->attachNodes($parent, $v->documentElement, $fn);
356
        }
357
358 1
        protected function insertIntegerDomnodelist($parent, $k, $v, $fn)
359
        {
360 1
                return $this->attachNodes($parent, $v, $fn);
361
        }
362
363 1
        protected function insertIntegerDomnode($parent, $k, $v, $fn)
364
        {
365 1
                return $this->attachNodes($parent, $v, $fn);
366
        }
367
368 1
        protected function insertIntegerSimplexml($parent, $k, $v, $fn)
369
        {
370 1
                return $this->attachNodes($parent, \dom_import_simplexml($v), $fn);
371
        }
372
373 1
        protected function insertIntegerFluidxml($parent, $k, $v, $fn)
374
        {
375 1
                return $this->attachNodes($parent, $v->dom()->documentElement, $fn);
376
        }
377
378 1
        protected function insertIntegerFluidcontext($parent, $k, $v, $fn)
379
        {
380 1
                return $this->attachNodes($parent, $v->array(), $fn);
381
        }
382
}
383