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

FluidInsertionHandler::recognizeStringMixed()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

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