Passed
Push — master ( 3fd8db...26be1e )
by Daniele
02:31
created

FluidInsertionHandler::insertStringSimple()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1
Metric Value
dl 0
loc 10
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 4
nc 1
nop 4
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 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)) {
70 1
                                $e = \array_pop($element);
71 1
                                $element[$e] = $opt;
72 1
                                continue;
73
74
                        }
75
76 1
                        throw new \Exception("Optional argument '$opt' not recognized.");
77
                }
78
79 1
                return [ $element, $attributes, $switch_context ];
80
        }
81
82
83 1
        protected function handleInsertion($parent, $k, $v, $fn, &$optionals)
84
        {
85
                // This is an highly optimized method.
86
                // Good code design would split this method in many different handlers
87
                // each one with its own checks. But it is too much expensive in terms
88
                // of performances for a core method like this, so this implementation
89
                // is prefered to collapse many identical checks to one.
90
91 1
                $handlers = ['handleStringMixed', 'handleIntegerMixed', 'handleDocuments'];
92
93 1
                $status = false;
94 1
                foreach ($handlers as $handler) {
95 1
                        $return = $this->$handler($parent, $k, $v, $fn, $optionals, $status);
96
97 1
                        if ($status == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

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