Completed
Branch wip (acc569)
by Josh
16:51 queued 07:59
created

Element::insertAdjacentElement()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
/**
4
* @package   s9e\SweetDOM
5
* @copyright Copyright (c) 2019-2020 The s9e authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\SweetDOM;
9
10
use BadMethodCallException;
11
use DOMElement;
12
use DOMNode;
13
use InvalidArgumentException;
14
15
class Element extends DOMElement
16
{
17
	public function __call(string $name, array $arguments)
18
	{
19
		if (preg_match('(^(append|prepend)(Xsl\\w++)$)', $name, $m))
20
		{
21
			$callback = [$this->ownerDocument, 'create' . $m[2]];
22
			if (is_callable($callback))
23
			{
24
				$element  = call_user_func_array($callback, $arguments);
25
				$where = ($m[1] === 'append') ? 'beforeend' : 'afterbegin';
26
27
				return $this->insertAdjacentElement($where, $element);
28
			}
29
		}
30
31
		throw new BadMethodCallException;
32
	}
33
34
	/**
35
	* Insert given element relative to this element's position
36
	*
37
	* @param  string     $where   One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend'
38
	* @param  DOMElement $element
39
	* @return DOMElement
40
	*/
41
	public function insertAdjacentElement(string $where, DOMElement $element): DOMElement
42
	{
43
		$this->insertAdjacentNode($where, $element);
44
45
		return $element;
46
	}
47
48
	/**
49
	* Insert given XML relative to this element's position
50
	*
51
	* @param  string $where One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend'
52
	* @param  string $xml
53
	* @return void
54
	*/
55
	public function insertAdjacentXML(string $where, string $xml): void
56
	{
57
		$fragment = $this->ownerDocument->createDocumentFragment();
58
		$fragment->appendXML($this->addMissingNamespaceDeclarations($xml));
59
60
		$this->insertAdjacentNode($where, $fragment);
61
	}
62
63
	/**
64
	* Remove this element from the document
65
	*
66
	* @return self This element
67
	*/
68
	public function remove(): self
69
	{
70
		return $this->parentNode->removeChild($this);
1 ignored issue
show
Bug Best Practice introduced by
The expression return $this->parentNode->removeChild($this) returns the type DOMNode which includes types incompatible with the type-hinted return s9e\SweetDOM\Element.
Loading history...
71
	}
72
73
	/**
74
	* Replace this element with given element
75
	*
76
	* @param  DOMElement $element Replacement element
77
	* @return self                This element
78
	*/
79
	public function replace(DOMElement $element): self
80
	{
81
		return $this->parentNode->replaceChild($element, $this);
1 ignored issue
show
Bug Best Practice introduced by
The expression return $this->parentNode...eChild($element, $this) returns the type DOMNode which includes types incompatible with the type-hinted return s9e\SweetDOM\Element.
Loading history...
82
	}
83
84
	/**
85
	* Add namespace declarations that may be missing in given XML
86
	*
87
	* @param  string $xml Original XML
88
	* @return string      Modified XML
89
	*/
90
	protected function addMissingNamespaceDeclarations(string $xml): string
91
	{
92
		preg_match_all('(xmlns:\\K[-\\w]++(?==))', $xml, $m);
93
		$prefixes = array_flip($m[0]);
94
95
		return preg_replace_callback(
96
			'(<([-\\w]++):[^>]++>)',
97
			function ($m) use ($prefixes)
98
			{
99
				$xml    = $m[0];
100
				$prefix = $m[1];
101
				if (isset($prefixes[$prefix]))
102
				{
103
					return $xml;
104
				}
105
106
				$nsURI         = $this->lookupNamespaceURI($prefix);
107
				$nsDeclaration = ' xmlns:' . $prefix . '="' . htmlspecialchars($nsURI, ENT_XML1) . '"';
108
109
				$pos = ($xml[-2] === '/') ? -2 : -1;
110
111
				return substr($xml, 0, $pos) . $nsDeclaration . substr($xml, $pos);
112
			},
113
			$xml
114
		);
115
	}
116
117
	/**
118
	* Insert given node relative to this element's position
119
	*
120
	* @param  string  $where One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend'
121
	* @param  DOMNode $node
122
	* @return void
123
	*/
124
	protected function insertAdjacentNode(string $where, DOMNode $node): void
125
	{
126
		if ($where === 'beforebegin')
127
		{
128
			$this->parentNode->insertBefore($node, $this);
129
		}
130
		elseif ($where === 'beforeend')
131
		{
132
			$this->appendChild($node);
133
		}
134
		elseif ($where === 'afterend')
135
		{
136
			$this->parentNode->insertBefore($node, $this->nextSibling);
137
		}
138
		elseif ($where === 'afterbegin')
139
		{
140
			$this->insertBefore($node, $this->firstChild);
141
		}
142
		else
143
		{
144
			throw new InvalidArgumentException;
145
		}
146
	}
147
}