Passed
Push — master ( 60319d...b48e8d )
by Daniele
08:21
created

FluidInsertionHandler::attachNodes()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 15
rs 9.2
cc 4
eloc 8
nc 4
nop 3
1
<?php
2
3
namespace FluidXml;
4
5
class FluidInsertionHandler
6
{
7
        private $document;
8
        private $dom;
9
        private $namespaces;
10
11
        public function __construct($document)
12
        {
13
                $this->document   = $document;
14
                $this->dom        = $document->dom;
15
                $this->namespaces =& $document->namespaces;
16
        }
17
18
        public function insertElement(&$nodes, $element, &$optionals, $fn, $orig_context)
19
        {
20
                list($element, $attributes, $switch_context) = $this->handleOptionals($element, $optionals);
21
22
                $new_nodes = [];
23
24
                foreach ($nodes as $n) {
25
                        foreach ($element as $k => $v) {
26
                                $cx        = $this->handleInsertion($n, $k, $v, $fn, $optionals);
27
                                $new_nodes = \array_merge($new_nodes, $cx);
28
                        }
29
                }
30
31
                $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
                if (! empty($attributes)) {
38
                        $new_context->setAttribute($attributes);
39
                }
40
41
                return $switch_context ? $new_context : $orig_context;
42
        }
43
44
        protected function newContext(&$context)
45
        {
46
                return new FluidContext($this->document, $this, $context);
47
        }
48
49
        protected function handleOptionals($element, &$optionals)
50
        {
51
                if (! \is_array($element)) {
52
                        $element = [ $element ];
53
                }
54
55
                $switch_context = false;
56
                $attributes     = [];
57
58
                foreach ($optionals as $opt) {
59
                        if (\is_array($opt)) {
60
                                $attributes = $opt;
61
62
                        }  elseif (\is_bool($opt)) {
63
                                $switch_context = $opt;
64
65
                        }  elseif (\is_string($opt)) {
66
                                $e = \array_pop($element);
67
68
                                $element[$e] = $opt;
69
70
                        } else {
71
                                throw new \Exception("Optional argument '$opt' not recognized.");
72
                        }
73
                }
74
75
                return [ $element, $attributes, $switch_context ];
76
        }
77
78
79
        protected function handleInsertion($parent, $k, $v, $fn, &$optionals)
80
        {
81
                // This is an highly optimized method.
82
                // Good code design would split this method in many different handlers
83
                // each one with its own checks. But it is too much expensive in terms
84
                // of performances for a core method like this, so this implementation
85
                // is prefered to collapse many identical checks to one.
86
87
                $k_is_string       = \is_string($k);
88
                $k_is_integer      = ! $k_is_string && \is_integer($k);
89
                $k_is_special      = $k_is_string && $k[0] === '@';
90
                $k_is_special_cont = $k_is_special && $k === '@';
91
                $k_is_special_attr = $k_is_special && ! $k_is_special_cont;
92
93
                $v_is_string       = \is_string($v);
94
                $v_is_xml          = $v_is_string && FluidHelper::isAnXmlString($v);
95
                $v_is_simple       = $v_is_string || \is_numeric($v);
96
                $v_is_array        = ! $v_is_simple && \is_array($v);
97
                $v_matches         = $v_is_string || $v_is_xml || $v_is_simple || $v_is_array;
98
                $v_is_domdoc       = ! $v_matches && $v instanceof \DOMDocument;
99
                $v_matches         = $v_matches || $v_is_domdoc;
100
                $v_is_domnodelist  = ! $v_matches && $v instanceof \DOMNodeList;
101
                $v_matches         = $v_matches || $v_is_domnodelist;
102
                $v_is_domnode      = ! $v_matches && $v instanceof \DOMNode;
103
                $v_matches         = $v_matches || $v_is_domnode;
104
                $v_is_simplexml    = ! $v_matches && $v instanceof \SimpleXMLElement;
105
                $v_matches         = $v_matches || $v_is_simplexml;
106
                $v_is_fluidxml     = ! $v_matches && $v instanceof FluidXml;
107
                $v_matches         = $v_matches || $v_is_fluidxml;
108
                $v_is_fluidcx      = ! $v_matches && $v instanceof FluidContext;
109
                $v_matches         = $v_matches || $v_is_fluidcx;
0 ignored issues
show
Unused Code introduced by
$v_matches is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
110
111
                $map = ['insertStringSimple'        => $k_is_string && ! $k_is_special && $v_is_simple && ! $v_is_xml,
112
                        'insertSpecialContent'      => $k_is_special_cont && $v_is_simple,
113
                        'insertSpecialAttribute'    => $k_is_special_attr && $v_is_simple,
114
                        'insertStringMixed'         => $k_is_string && ! $v_is_string,
115
                        'insertIntegerString'       => $k_is_integer && $v_is_string && ! $v_is_xml,
116
                        'insertIntegerXml'          => $k_is_integer && $v_is_string && $v_is_xml,
117
                        'insertIntegerArray'        => $k_is_integer && $v_is_array,
118
                        'insertIntegerDomdocument'  => $v_is_domdoc,
119
                        'insertIntegerDomnodelist'  => $v_is_domnodelist,
120
                        'insertIntegerDomnode'      => $v_is_domnode,
121
                        'insertIntegerSimplexml'    => $v_is_simplexml,
122
                        'insertIntegerFluidxml'     => $v_is_fluidxml,
123
                        'insertIntegerFluidcontext' => $v_is_fluidcx
124
                ];
125
126
127
                foreach ($map as $handler => $match) {
128
                        if ($match) {
129
                                return $this->$handler($parent, $k, $v, $fn, $optionals);
130
                        }
131
                }
132
133
                throw new \Exception('XML document not supported.');
134
        }
135
136
        protected function createElement($name, $value = null)
137
        {
138
                // The DOMElement instance must be different for every node,
139
                // otherwise only one element is attached to the DOM.
140
141
                $id  = null;
142
                $uri = null;
143
144
                // The node name can contain the namespace id prefix.
145
                // Example: xsl:template
146
                $colon_pos = \strpos($name, ':');
147
148
                if ($colon_pos !== false) {
149
                        $id   = \substr($name, 0, $colon_pos);
150
                        $name = \substr($name, $colon_pos + 1);
151
                }
152
153
                if ($id !== null) {
154
                        $ns  = $this->namespaces[$id];
155
                        $uri = $ns->uri();
156
157
                        if ($ns->mode() === FluidNamespace::MODE_EXPLICIT) {
158
                                $name = "{$id}:{$name}";
159
                        }
160
                }
161
162
                // Algorithm 1:
163
                $el = new \DOMElement($name, $value, $uri);
164
165
                // Algorithm 2:
166
                // $el = $dom->createElement($name, $value);
167
168
                return $el;
169
        }
170
171
        protected function attachNodes($parent, $nodes, $fn)
172
        {
173
                if (! \is_array($nodes) && ! $nodes instanceof \Traversable) {
174
                        $nodes = [ $nodes ];
175
                }
176
177
                $context = [];
178
179
                foreach ($nodes as $el) {
180
                        $el        = $this->dom->importNode($el, true);
181
                        $context[] = $fn($parent, $el);
182
                }
183
184
                return $context;
185
        }
186
187
        protected function insertSpecialContent($parent, $k, $v)
188
        {
189
                // The user has passed an element text content:
190
                // [ '@' => 'Element content.' ]
191
192
                // Algorithm 1:
193
                $this->newContext($parent)->addText($v);
194
195
                // Algorithm 2:
196
                // $this->setText($v);
197
198
                // The user can specify multiple '@' special elements
199
                // so Algorithm 1 is the right choice.
200
201
                return [];
202
        }
203
204
        protected function insertSpecialAttribute($parent, $k, $v)
205
        {
206
                // The user has passed an attribute name and an attribute value:
207
                // [ '@attribute' => 'Attribute content' ]
208
209
                $attr = \substr($k, 1);
210
                $this->newContext($parent)->setAttribute($attr, $v);
211
212
                return [];
213
        }
214
215
        protected function insertStringSimple($parent, $k, $v, $fn)
216
        {
217
                // The user has passed an element name and an element value:
218
                // [ 'element' => 'Element content' ]
219
220
                $el = $this->createElement($k, $v);
221
                $el = $fn($parent, $el);
222
223
                return [ $el ];
224
        }
225
226
        protected function insertStringMixed($parent, $k, $v, $fn, &$optionals)
227
        {
228
                // The user has passed one of these two cases:
229
                // - [ 'element' => [...] ]
230
                // - [ 'element' => DOMNode|SimpleXMLElement|FluidXml ]
231
232
                $el = $this->createElement($k);
233
                $el = $fn($parent, $el);
234
235
                // The new children elements must be created in the order
236
                // they are supplied, so 'addChild' is the perfect operation.
237
                $this->newContext($el)->addChild($v, ...$optionals);
238
239
                return [ $el ];
240
        }
241
242
        protected function insertIntegerArray($parent, $k, $v, $fn, &$optionals)
243
        {
244
                // The user has passed a wrapper array:
245
                // [ [...], ... ]
246
247
                $context = [];
248
249
                foreach ($v as $kk => $vv) {
250
                        $cx = $this->handleInsertion($parent, $kk, $vv, $fn, $optionals);
251
252
                        $context = \array_merge($context, $cx);
253
                }
254
255
                return $context;
256
        }
257
258
        protected function insertIntegerString($parent, $k, $v, $fn)
259
        {
260
                // The user has passed a node name without a node value:
261
                // [ 'element', ... ]
262
263
                $el = $this->createElement($v);
264
                $el = $fn($parent, $el);
265
266
                return [ $el ];
267
        }
268
269
        protected function insertIntegerXml($parent, $k, $v, $fn)
270
        {
271
                // The user has passed an XML document instance:
272
                // [ '<tag></tag>', DOMNode, SimpleXMLElement, FluidXml ]
273
274
                $wrapper = new \DOMDocument();
275
                $wrapper->formatOutput       = true;
276
                $wrapper->preserveWhiteSpace = false;
277
278
                $v = \ltrim($v);
279
280
                if ($v[1] === '?') {
281
                        $wrapper->loadXML($v);
282
                        $nodes = $wrapper->childNodes;
283
                } else {
284
                        // A way to import strings with multiple root nodes.
285
                        $wrapper->loadXML("<root>$v</root>");
286
287
                        // Algorithm 1:
288
                        $nodes = $wrapper->documentElement->childNodes;
289
290
                        // Algorithm 2:
291
                        // $xp = new \DOMXPath($wrapper);
292
                        // $nodes = $xp->query('/root/*');
293
                }
294
295
                return $this->attachNodes($parent, $nodes, $fn);
296
        }
297
298
        protected function insertIntegerDomdocument($parent, $k, $v, $fn)
299
        {
300
                // A DOMDocument can have multiple root nodes.
301
302
                // Algorithm 1:
303
                return $this->attachNodes($parent, $v->childNodes, $fn);
304
305
                // Algorithm 2:
306
                // return $this->attachNodes($parent, $v->documentElement, $fn);
307
        }
308
309
        protected function insertIntegerDomnodelist($parent, $k, $v, $fn)
310
        {
311
                return $this->attachNodes($parent, $v, $fn);
312
        }
313
314
        protected function insertIntegerDomnode($parent, $k, $v, $fn)
315
        {
316
                return $this->attachNodes($parent, $v, $fn);
317
        }
318
319
        protected function insertIntegerSimplexml($parent, $k, $v, $fn)
320
        {
321
                return $this->attachNodes($parent, \dom_import_simplexml($v), $fn);
322
        }
323
324
        protected function insertIntegerFluidxml($parent, $k, $v, $fn)
325
        {
326
                return $this->attachNodes($parent, $v->dom()->documentElement, $fn);
327
        }
328
329
        protected function insertIntegerFluidcontext($parent, $k, $v, $fn)
330
        {
331
                return $this->attachNodes($parent, $v->array(), $fn);
332
        }
333
}
334