Passed
Push — master ( eeab5f...5e4f21 )
by Daniele
02:41
created

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