XmlArray   F
last analyzed

Complexity

Total Complexity 128

Size/Duplication

Total Lines 851
Duplicated Lines 0 %

Test Coverage

Coverage 62.45%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 263
c 1
b 0
f 0
dl 0
loc 851
rs 2
ccs 163
cts 261
cp 0.6245
wmc 128

17 Methods

Rating   Name   Duplication   Size   Complexity  
A to_array() 0 22 3
A count() 0 16 3
A exists() 0 34 4
A create_xml() 0 23 3
A set() 0 22 4
C _xml() 0 59 12
A _fetch() 0 31 5
A _array() 0 27 6
A _from_cdata_callback() 0 3 1
A _from_cdata() 0 10 2
B fetch() 0 31 7
C _to_cdata() 0 45 12
D _parse() 0 167 30
A name() 0 3 1
A __construct() 0 28 3
D _path() 0 71 19
C path() 0 62 13

How to fix   Complexity   

Complex Class

Complex classes like XmlArray often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use XmlArray, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * The XmlArray class is an xml parser.
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 dev
14
 *
15
 */
16
17
namespace ElkArte;
18
19
/**
20
 * Class representing an xml array.
21
 *
22
 * Reads in xml, allows you to access it simply.
23
 * Version 2.0 dev.
24
 */
25
class XmlArray
26
{
27
	/** @var array Holds xml parsed results */
28
	public $array;
29
30
	/** @var int|null Holds debugging level */
31
	public $debug_level;
32
33
	/** @var bool Holds trim level textual data */
34
	public $trim;
35
36
	/**
37
	 * Constructor for the xml parser.
38
	 *
39
	 * Example use:
40
	 *   $xml = new \ElkArte\XmlArray(file('data.xml'));
41
	 *
42
	 * @param string $data the xml data or an array of, unless is_clone is true.
43
	 * @param bool $auto_trim default false, used to automatically trim textual data.
44
	 * @param int|null $level default null, the debug level, specifies whether notices should be generated for missing elements and attributes.
45
	 * @param bool $is_clone default false. If is_clone is true, the  \ElkArte\XmlArray is cloned from another - used internally only.
46
	 */
47
	public function __construct($data, $auto_trim = false, $level = null, $is_clone = false)
48
	{
49
		// If we're using this try to get some more memory.
50
		detectServer()->setMemoryLimit('128M');
51
52
		// Set the debug level.
53
		$this->debug_level = $level ?? error_reporting();
54
		$this->trim = $auto_trim;
55
56
		// Is the data already parsed?
57
		if ($is_clone)
58
		{
59 4
			$this->array = $data;
0 ignored issues
show
Documentation Bug introduced by
It seems like $data of type string is incompatible with the declared type array of property $array.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
60
61
			return;
62 4
		}
63
64
		// Is the input an array? (ie. passed from file()?)
65 4
		if (is_array($data))
0 ignored issues
show
introduced by
The condition is_array($data) is always false.
Loading history...
66 4
		{
67
			$data = implode('', $data);
68
		}
69 4
70
		// Remove any xml declaration or doctype, and parse out comments and CDATA.
71 4
		$data = preg_replace('/<!--.*?-->/s', '', $this->_to_cdata(preg_replace(['/^<\?xml.+?\?' . '>/is', '/<!DOCTYPE[^>]+?' . '>/s'], '', $data)));
72
73 4
		// Now parse the xml!
74
		$this->array = $this->_parse($data);
75
	}
76
77 4
	/**
78
	 * Parse out CDATA tags. (htmlspecialchars them...)
79
	 *
80
	 * @param string $data The data with CDATA tags
81
	 *
82
	 * @return string
83 4
	 */
84
	protected function _to_cdata($data)
85
	{
86 4
		$inCdata = false;
87 4
		$inComment = false;
88
		$output = '';
89
90
		$parts = preg_split('~(<!\[CDATA\[|\]\]>|<!--|-->)~', $data, -1, PREG_SPLIT_DELIM_CAPTURE);
91
		foreach ($parts as $part)
92
		{
93
			// Handle XML comments.
94
			if (!$inCdata && $part === '<!--')
95
			{
96 4
				$inComment = true;
97
			}
98 4
			if ($inComment && $part === '-->')
99 4
			{
100
				$inComment = false;
101 4
			}
102 4
			elseif ($inComment)
103
			{
104
				continue;
105 4
			}
106
107
			// Handle Cdata blocks.
108
			elseif (!$inComment && $part === '<![CDATA[')
109 4
			{
110
				$inCdata = true;
111
			}
112
			elseif ($inCdata && $part === ']]>')
113 4
			{
114
				$inCdata = false;
115
			}
116
			elseif ($inCdata)
117
			{
118
				$output .= htmlentities($part, ENT_QUOTES);
119 4
			}
120
121 4
			// Everything else is kept as is.
122
			else
123 4
			{
124
				$output .= $part;
125 4
			}
126
		}
127 4
128
		return $output;
129 4
	}
130
131
	/**
132
	 * Parse data into an array. (privately used...)
133
	 *
134
	 * @param string $data to parse
135 4
	 *
136
	 * @return array
137
	 */
138
	protected function _parse($data)
139 4
	{
140
		// Start with an 'empty' array with no data.
141
		$current = array();
142
143
		// Loop until we're out of data.
144
		while ($data !== '')
145
		{
146
			// Find and remove the next tag.
147
			preg_match('/\A<([\w\-:]+)((?:\s+.+?)?)([\s]?\/)?' . '>/', $data, $match);
148
			if (isset($match[0]))
149 4
			{
150
				$data = preg_replace('/' . preg_quote($match[0], '/') . '/s', '', $data, 1);
151
			}
152 4
153
			// Didn't find a tag?  Keep looping....
154
			if (!isset($match[1]) || $match[1] === '')
155 4
			{
156
				// If there's no <, the rest is data.
157
				$data_strpos = strpos($data, '<');
158 4
				if ($data_strpos === false)
159 4
				{
160
					$text_value = $this->_from_cdata($data);
161 4
					$data = '';
162
163
					if ($text_value !== '')
164
					{
165 4
						$current[] = array(
166
							'name' => '!',
167
							'value' => $text_value
168 4
						);
169 4
					}
170
				}
171 4
				// If the < isn't immediately next to the current position... more data.
172 4
				elseif ($data_strpos > 0)
173
				{
174 4
					$text_value = $this->_from_cdata(substr($data, 0, $data_strpos));
175
					$data = substr($data, $data_strpos);
176 4
177 4
					if ($text_value !== '')
178 4
					{
179
						$current[] = array(
180
							'name' => '!',
181
							'value' => $text_value
182
						);
183 4
					}
184
				}
185 4
				// If we're looking at a </something> with no start, kill it.
186 4
				elseif ($data_strpos !== false && $data_strpos === 0)
187
				{
188 4
					$data_strpos = strpos($data, '<', 1);
189
					if ($data_strpos !== false)
190 4
					{
191 4
						$text_value = $this->_from_cdata(substr($data, 0, $data_strpos));
192 4
						$data = substr($data, $data_strpos);
193
194
						if ($text_value !== '')
195
						{
196
							$current[] = array(
197
								'name' => '!',
198
								'value' => $text_value
199
							);
200
						}
201
					}
202
					else
203
					{
204
						$text_value = $this->_from_cdata($data);
205
						$data = '';
206
207
						if ($text_value !== '')
208
						{
209
							$current[] = array(
210
								'name' => '!',
211
								'value' => $text_value
212
							);
213
						}
214
					}
215
				}
216
217
				// Wait for an actual occurrence of an element.
218
				continue;
219
			}
220
221
			// Create a new element in the array.
222
			$el = &$current[];
223
			$el['name'] = $match[1];
224
225
			// If this ISN'T empty, remove the close tag and parse the inner data.
226
			if ((!isset($match[3]) || trim($match[3]) !== '/') && (!isset($match[2]) || trim($match[2]) !== '/'))
227
			{
228
				// Because PHP 5.2.0+ seems to croak using regex, we'll have to do this the less fun way.
229 4
				$last_tag_end = strpos($data, '</' . $match[1] . '>');
230
				if ($last_tag_end === false)
231
				{
232
					continue;
233 4
				}
234 4
235
				$offset = 0;
236
				while (true)
237 4
				{
238
					// Where is the next start tag?
239
					$next_tag_start = strpos($data, '<' . $match[1], $offset);
240 4
241 4
					// If the next start tag is after the last end tag then we've found the right close.
242
					if ($next_tag_start === false || $next_tag_start > $last_tag_end)
243
					{
244
						break;
245
					}
246 4
247 4
					// If not then find the next ending tag.
248
					$next_tag_end = strpos($data, '</' . $match[1] . '>', $offset);
249
250 4
					// Didn't find one? Then just use the last and sod it.
251
					if ($next_tag_end === false)
252
					{
253 4
						break;
254
					}
255 4
					else
256
					{
257
						$last_tag_end = $next_tag_end;
258
						$offset = $next_tag_start + 1;
259
					}
260
				}
261
262
				// Parse the insides.
263
				$inner_match = substr($data, 0, $last_tag_end);
264
265
				// Data now starts from where this section ends.
266
				$data = substr($data, $last_tag_end + strlen('</' . $match[1] . '>'));
267
268
				if (!empty($inner_match))
269
				{
270
					// Parse the inner data.
271
					if (strpos($inner_match, '<') !== false)
272
					{
273
						$el += $this->_parse($inner_match);
274 4
					}
275
					elseif (trim($inner_match) !== '')
276
					{
277 4
						$text_value = $this->_from_cdata($inner_match);
278
						if (trim($text_value) !== '')
279 4
						{
280
							$el[] = array(
281
								'name' => '!',
282 4
								'value' => $text_value
283
							);
284 4
						}
285
					}
286 4
				}
287
			}
288 4
289 4
			// If we're dealing with attributes as well, parse them out.
290
			if (isset($match[2]) && $match[2] !== '')
291 4
			{
292 4
				// Find all the attribute pairs in the string.
293 4
				preg_match_all('/([\w:]+)="(.+?)"/', $match[2], $attr, PREG_SET_ORDER);
294
295
				// Set them as @attribute-name.
296
				foreach ($attr as $match_attr)
297
				{
298
					$el['@' . $match_attr[1]] = $match_attr[2];
299
				}
300
			}
301 4
		}
302
303
		// Return the parsed array.
304 4
		return $current;
305
	}
306
307 4
	/**
308
	 * Turn the CDATAs back to normal text.
309 4
	 *
310
	 * @param string $data The data with CDATA tags
311
	 *
312
	 * @return string
313
	 */
314
	protected function _from_cdata($data)
315 4
	{
316
		// Get the HTML translation table and reverse it
317
		$trans_tbl = array_flip(get_html_translation_table(HTML_ENTITIES, ENT_QUOTES));
318
319
		// Translate all the entities out.
320
		$data = preg_replace_callback('~&#(\d{1,4});~', fn($match) => $this->_from_cdata_callback($match), $data);
321
		$data = strtr($data, $trans_tbl);
322
323
		return $this->trim ? trim($data) : $data;
324
	}
325 4
326
	/**
327
	 * Callback for the preg_replace in _from_cdata
328 4
	 *
329
	 * @param array $match An array of data
330
	 *
331
	 * @return string
332
	 */
333 4
	protected function _from_cdata_callback($match)
334 4
	{
335
		return chr($match[1]);
336 4
	}
337
338
	/**
339
	 * Get the root element's name.
340
	 *
341
	 * Example use:
342
	 *   echo $element->name();
343
	 */
344
	public function name()
345
	{
346
		return $this->array['name'] ?? '';
347
	}
348
349
	/**
350
	 * Get a specified element's value or attribute by path.
351
	 *
352
	 * - Children are parsed for text, but only textual data is returned
353
	 * unless get_elements is true.
354
	 *
355
	 * Example use:
356
	 *    $data = $xml->fetch('html/head/title');
357 2
	 *
358
	 * @param string $path - the path to the element to fetch
359 2
	 * @param bool $get_elements - whether to include elements
360
	 *
361
	 * @return bool|string
362
	 */
363
	public function fetch($path, $get_elements = false)
364
	{
365
		// Get the element, in array form.
366
		$array = $this->path($path);
367
368
		if ($array === false)
369
		{
370
			return false;
371
		}
372
373
		// Getting elements into this is a bit complicated...
374
		if ($get_elements && !is_string($array))
375
		{
376 2
			$temp = '';
377
378
			// Use the _xml() function to get the xml data.
379 2
			foreach ($array->array as $val)
380
			{
381 2
				// Skip the name and any attributes.
382
				if (is_array($val))
383
				{
384
					$temp .= $this->_xml($val, null);
385
				}
386
			}
387 2
388
			// Just get the XML data and then take out the CDATAs.
389
			return $this->_to_cdata($temp);
390
		}
391
392
		// Return the value - taking care to pick out all the text values.
393
		return is_string($array) ? $array : $this->_fetch($array->array);
0 ignored issues
show
introduced by
The condition is_string($array) is always false.
Loading history...
394
	}
395
396
	/**
397
	 * Get an element, returns a new \ElkArte\XmlArray.
398
	 *
399
	 * - It finds any elements that match the path specified.
400
	 * - It will always return a set if there is more than one of the element
401
	 * or return_set is true.
402
	 *
403
	 * Example use:
404
	 *   $element = $xml->path('html/body');
405
	 *
406 2
	 * @param string $path - the path to the element to get
407
	 * @param bool $return_full - always return full result set
408
	 * @return \ElkArte\XmlArray|bool a new \ElkArte\XmlArray.
409
	 */
410
	public function path($path, $return_full = false)
411
	{
412
		// Split up the path.
413
		$path = explode('/', $path);
414
415
		// Start with a base array.
416
		$array = $this->array;
417
418
		// For each element in the path.
419
		foreach ($path as $el)
420
		{
421
			// Deal with sets....
422
			if (strpos($el, '[') !== false)
423 4
			{
424
				$lvl = (int) substr($el, strpos($el, '[') + 1);
425
				$el = substr($el, 0, strpos($el, '['));
426 4
			}
427
			// Find an attribute.
428
			elseif (substr($el, 0, 1) === '@')
429 4
			{
430
				// It simplifies things if the attribute is already there ;).
431
				if (isset($array[$el]))
432 4
				{
433
					return $array[$el];
434
				}
435 4
436
				$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
437 4
				$i = 0;
438 4
				while ($i < count($trace) && isset($trace[$i]['class']) && $trace[$i]['class'] === get_class($this))
439
				{
440
					$i++;
441 2
				}
442
				$debug = ' from ' . $trace[$i - 1]['file'] . ' on line ' . $trace[$i - 1]['line'];
443
444 2
				// Cause an error.
445
				if (($this->debug_level & E_NOTICE) !== 0)
446 2
				{
447
					trigger_error('Undefined XML attribute: ' . substr($el, 1) . $debug, E_USER_NOTICE);
448
				}
449
450
				return false;
451
			}
452
			else
453
			{
454
				$lvl = null;
455
			}
456
457
			// Find this element.
458
			$array = $this->_path($array, $el, $lvl);
459
		}
460
461
		// Clean up after $lvl, for $return_full.
462
		if ($return_full && (!isset($array['name']) || substr($array['name'], -1) !== ']'))
463
		{
464
			$array = ['name' => $el . '[]', $array];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $el seems to be defined by a foreach iteration on line 419. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
465
		}
466
467
		// Create the right type of class...
468
		$newClass = get_class($this);
469 2
470
		// Return a new \ElkArte\XmlArray for the result.
471
		return $array === false ? false : new $newClass($array, $this->trim, $this->debug_level, true);
472
	}
473 4
474
	/**
475
	 * Get a specific array by path, one level down. (privately used...)
476
	 *
477 4
	 * @param array $array An array of data
478
	 * @param string $path The path
479
	 * @param int $level How far deep into the array we should go
480
	 * @param bool $no_error Whether or not to ignore errors
481
	 *
482
	 * @return array|bool
483 4
	 */
484
	protected function _path($array, $path, $level, $no_error = false)
485
	{
486 4
		// Is $array even an array?  It might be false!
487
		if (!is_array($array))
0 ignored issues
show
introduced by
The condition is_array($array) is always true.
Loading history...
488
		{
489
			return false;
490
		}
491
492
		// Asking for *no* path?
493
		if ($path === '' || $path === '.')
494
		{
495
			return $array;
496
		}
497
		$paths = explode('|', $path);
498
499 4
		// A * means all elements of any name.
500
		$show_all = in_array('*', $paths);
501
502 4
		$results = [];
503
504
		// Check each element.
505
		foreach ($array as $value)
506
		{
507
			if (!is_array($value) || $value['name'] === '!')
508 4
			{
509
				continue;
510 2
			}
511
512 4
			if ($show_all || in_array($value['name'], $paths, true))
513
			{
514
				// Skip elements before "the one".
515 4
				if ($level !== null && $level > 0)
516
				{
517 4
					$level--;
518
				}
519
				else
520 4
				{
521
					$results[] = $value;
522 4
				}
523
			}
524 4
		}
525
526
		// No results found...
527 4
		if (empty($results))
528
		{
529
			$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
530 4
			$i = 0;
531
			while ($i < count($trace) && isset($trace[$i]['class']) && $trace[$i]['class'] === get_class($this))
532
			{
533
				$i++;
534
			}
535
536 4
			$debug = ' from ' . $trace[$i - 1]['file'] . ' on line ' . $trace[$i - 1]['line'];
537
538
			// Cause an error.
539
			if ($this->debug_level & E_NOTICE && !$no_error)
540
			{
541
				trigger_error('Undefined XML element: ' . $path . $debug, E_USER_NOTICE);
542 4
			}
543
544
			return false;
545
		}
546
547
		// Only one result.
548
		if ($level !== null || count($results) === 1)
0 ignored issues
show
introduced by
The condition $level !== null is always true.
Loading history...
549
		{
550
			return $results[0];
551
		}
552
553
		// Return the result set.
554
		return $results + ['name' => $path . '[]'];
555
	}
556
557
	/**
558
	 * Get a specific element's xml. (privately used...)
559
	 *
560
	 * @param array $array
561
	 * @param null|int $indent
562 4
	 *
563
	 * @return string
564 4
	 */
565
	protected function _xml($array, $indent)
566
	{
567
		$indentation = $indent !== null ? '
568
' . str_repeat('	', $indent) : '';
569 2
570
		// This is a set of elements, with no name...
571
		if (is_array($array) && !isset($array['name']))
572
		{
573
			$temp = '';
574
			foreach ($array as $val)
575
			{
576
				$temp .= $this->_xml($val, $indent);
577
			}
578
579
			return $temp;
580
		}
581
582
		// This is just text!
583
		if ($array['name'] === '!')
584
		{
585
			return $indentation . '<![CDATA[' . $array['value'] . ']]>';
586
		}
587
588
		if (substr($array['name'], -2) === '[]')
589
		{
590
			$array['name'] = substr($array['name'], 0, -2);
591
		}
592
593
		// Start the element.
594
		$output = $indentation . '<' . $array['name'];
595
596
		$inside_elements = false;
597
		$output_el = '';
598
599
		// Run through and recursively output all the elements or attributes inside this.
600
		foreach ($array as $k => $v)
601
		{
602
			if (substr($k, 0, 1) === '@')
603
			{
604
				$output .= ' ' . substr($k, 1) . '="' . $v . '"';
605
			}
606
			elseif (is_array($v))
607
			{
608
				$output_el .= $this->_xml($v, $indent === null ? null : $indent + 1);
609
				$inside_elements = true;
610
			}
611
		}
612
613
		// Indent, if necessary.... then close the tag.
614
		if ($inside_elements)
615
		{
616
			$output .= '>' . $output_el . $indentation . '</' . $array['name'] . '>';
617
		}
618
		else
619
		{
620
			$output .= ' />';
621
		}
622
623
		return $output;
624
	}
625
626
	/**
627
	 * Given an array, return the text from that array. (recursive and privately used.)
628
	 *
629
	 * @param string[]|string $array An array of data
630
	 *
631
	 * @return string
632
	 */
633
	protected function _fetch($array)
634
	{
635
		// Don't return anything if this is just a string.
636
		if (is_string($array))
637
		{
638
			return '';
639
		}
640
641
		$temp = '';
642
		foreach ($array as $text)
643
		{
644
			// This means it's most likely an attribute or the name itself.
645
			if (!isset($text['name']))
646
			{
647
				continue;
648 2
			}
649
650
			// This is text!
651 2
			if ($text['name'] === '!')
652
			{
653
				$temp .= $text['value'];
654
			}
655
			// Another element - dive in ;).
656 2
			else
657 2
			{
658
				$temp .= $this->_fetch($text);
659
			}
660 2
		}
661
662 2
		// Return all the bits and pieces we've put together.
663
		return $temp;
664
	}
665
666 2
	/**
667
	 * Check if an element exists.
668 2
	 *
669
	 * Example use,
670
	 *   echo $xml->exists('html/body') ? 'y' : 'n';
671
	 *
672
	 * @param string $path - the path to the element to get.
673 1
	 * @return bool
674
	 */
675
	public function exists($path)
676
	{
677
		// Split up the path.
678 2
		$path = explode('/', $path);
679
680
		// Start with a base array.
681
		$array = $this->array;
682
683
		// For each element in the path.
684
		foreach ($path as $el)
685
		{
686
			// Deal with sets....
687
			$el_strpos = strpos($el, '[');
688
689
			if ($el_strpos !== false)
690 4
			{
691
				$lvl = (int) substr($el, $el_strpos + 1);
692
				$el = substr($el, 0, $el_strpos);
693 4
			}
694
			// Find an attribute.
695
			elseif (substr($el, 0, 1) === '@')
696 4
			{
697
				return isset($array[$el]);
698
			}
699 4
			else
700
			{
701
				$lvl = null;
702 4
			}
703
704 4
			// Find this element.
705
			$array = $this->_path($array, $el, $lvl, true);
0 ignored issues
show
Bug introduced by
It seems like $array can also be of type boolean; however, parameter $array of ElkArte\XmlArray::_path() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

705
			$array = $this->_path(/** @scrutinizer ignore-type */ $array, $el, $lvl, true);
Loading history...
706 4
		}
707 4
708
		return $array !== false;
709
	}
710 2
711
	/**
712 2
	 * Count the number of occurrences of a path.
713
	 *
714
	 * Example use:
715
	 *   echo $xml->count('html/head/meta');
716 2
	 *
717
	 * @param string $path - the path to search for.
718
	 * @return int the number of elements the path matches.
719
	 */
720 4
	public function count($path)
721
	{
722
		// Get the element, always returning a full set.
723 4
		$temp = $this->path($path, true);
724
725
		// Start at zero, then count up all the numeric keys.
726
		$i = 0;
727
		foreach ($temp->array as $item)
728
		{
729
			if (is_array($item))
730
			{
731
				$i++;
732
			}
733
		}
734
735
		return $i;
736
	}
737
738
	/**
739
	 * Get an array of \ElkArte\XmlArray's matching the specified path.
740
	 *
741
	 * - This differs from ->path(path, true) in that instead of an \ElkArte\XmlArray
742
	 * of elements, an array of \ElkArte\XmlArray's is returned for use with foreach.
743
	 *
744
	 * Example use:
745
	 *   foreach ($xml->set('html/body/p') as $p)
746
	 *
747
	 * @param string $path - the path to search for.
748
	 * @return array an array of \ElkArte\XmlArray objects
749
	 */
750
	public function set($path)
751
	{
752
		// None as yet, just get the path.
753
		$array = array();
754
		$xml = $this->path($path, true);
755
756
		foreach ($xml->array as $val)
757
		{
758
			// Skip these, they aren't elements.
759
			if (!is_array($val) || $val['name'] === '!')
760
			{
761
				continue;
762
			}
763
764
			// Create the right type of class...
765 2
			$newClass = get_class($this);
766
767
			// Create a new \ElkArte\XmlArray and stick it in the array.
768 2
			$array[] = new $newClass($val, $this->trim, $this->debug_level, true);
769 2
		}
770
771 2
		return $array;
772
	}
773
774 2
	/**
775
	 * Create an xml file from an \ElkArte\XmlArray, the specified path if any.
776 2
	 *
777
	 * Example use:
778
	 *   echo $this->create_xml();
779
	 *
780 2
	 * @param string|null $path - the path to the element. (optional)
781
	 * @return string xml-formatted string.
782
	 */
783 2
	public function create_xml($path = null)
784
	{
785
		// Was a path specified?  If so, use that array.
786 2
		if ($path !== null)
787
		{
788
			$path = $this->path($path);
789
790
			// The path was not found
791
			if ($path === false)
792
			{
793
				return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
794
			}
795
796
			$path = $path->array;
797
		}
798
		// Just use the current array.
799
		else
800
		{
801
			$path = $this->array;
802
		}
803
804
		// Add the xml declaration to the front.
805
		return '<?xml version="1.0"?' . '>' . $this->_xml($path, 0);
806
	}
807
808
	/**
809
	 * Output the xml in an array form.
810
	 *
811
	 * Example use:
812
	 *   print_r($xml->to_array());
813
	 *
814
	 * @param string|null $path the path to output.
815
	 *
816
	 * @return array|bool|string
817
	 */
818
	public function to_array($path = null)
819
	{
820
		// Are we doing a specific path?
821
		if ($path !== null)
822
		{
823
			$path = $this->path($path);
824
825
			// The path was not found
826
			if ($path === false)
827
			{
828
				return false;
829
			}
830
831
			$path = $path->array;
832
		}
833 4
		// No, so just use the current array.
834
		else
835
		{
836 4
			$path = $this->array;
837
		}
838
839
		return $this->_array($path);
840
	}
841
842
	/**
843
	 * Return an element as an array
844
	 *
845
	 * @param array $array An array of data
846
	 *
847
	 * @return array|string
848
	 */
849
	protected function _array($array)
850
	{
851 4
		$return = array();
852
		$text = '';
853
		foreach ($array as $value)
854 4
		{
855
			if (!is_array($value) || !isset($value['name']))
856
			{
857
				continue;
858
			}
859
860
			if ($value['name'] === '!')
861
			{
862
				$text .= $value['value'];
863
			}
864 4
			else
865
			{
866 4
				$return[$value['name']] = $this->_array($value);
867 4
			}
868 4
		}
869
870 4
		if (empty($return))
871
		{
872 4
			return $text;
873
		}
874
875 4
		return $return;
876
	}
877
}
878