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