Completed
Branch minor/InsertElement (a60c8c)
by Josh
11:04
created

Element::insertElement()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 3
dl 0
loc 15
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 DOMNodeList;
14
use InvalidArgumentException;
15
16
/**
17
* @method self appendElement(string $nodeName, $text = '')
18
* @method self appendElementSibling(string $nodeName, $text = '')
19
* @method void appendText(string $text)
20
* @method void appendTextSibling(string $text)
21
* @method self appendXslApplyTemplates(string $select = null)
22
* @method self appendXslApplyTemplatesSibling(string $select = null)
23
* @method self appendXslAttribute(string $name, string $text = '')
24
* @method self appendXslAttributeSibling(string $name, string $text = '')
25
* @method self appendXslChoose()
26
* @method self appendXslChooseSibling()
27
* @method self appendXslComment(string $text = '')
28
* @method self appendXslCommentSibling(string $text = '')
29
* @method self appendXslCopyOf(string $select)
30
* @method self appendXslCopyOfSibling(string $select)
31
* @method self appendXslIf(string $test, string $text = '')
32
* @method self appendXslIfSibling(string $test, string $text = '')
33
* @method self appendXslOtherwise(string $text = '')
34
* @method self appendXslOtherwiseSibling(string $text = '')
35
* @method self appendXslText(string $text = '')
36
* @method self appendXslTextSibling(string $text = '')
37
* @method self appendXslValueOf(string $select)
38
* @method self appendXslValueOfSibling(string $select)
39
* @method self appendXslVariable(string $name, string $select = null)
40
* @method self appendXslVariableSibling(string $name, string $select = null)
41
* @method self appendXslWhen(string $test, string $text = '')
42
* @method self appendXslWhenSibling(string $test, string $text = '')
43
* @method self prependElement(string $nodeName, $text = '')
44
* @method self prependElementSibling(string $nodeName, $text = '')
45
* @method void prependText(string $text)
46
* @method void prependTextSibling(string $text)
47
* @method self prependXslApplyTemplates(string $select = null)
48
* @method self prependXslApplyTemplatesSibling(string $select = null)
49
* @method self prependXslAttribute(string $name, string $text = '')
50
* @method self prependXslAttributeSibling(string $name, string $text = '')
51
* @method self prependXslChoose()
52
* @method self prependXslChooseSibling()
53
* @method self prependXslComment(string $text = '')
54
* @method self prependXslCommentSibling(string $text = '')
55
* @method self prependXslCopyOf(string $select)
56
* @method self prependXslCopyOfSibling(string $select)
57
* @method self prependXslIf(string $test, string $text = '')
58
* @method self prependXslIfSibling(string $test, string $text = '')
59
* @method self prependXslOtherwise(string $text = '')
60
* @method self prependXslOtherwiseSibling(string $text = '')
61
* @method self prependXslText(string $text = '')
62
* @method self prependXslTextSibling(string $text = '')
63
* @method self prependXslValueOf(string $select)
64
* @method self prependXslValueOfSibling(string $select)
65
* @method self prependXslVariable(string $name, string $select = null)
66
* @method self prependXslVariableSibling(string $name, string $select = null)
67
* @method self prependXslWhen(string $test, string $text = '')
68
* @method self prependXslWhenSibling(string $test, string $text = '')
69
*/
70
class Element extends DOMElement
71
{
72
	public function __call(string $name, array $arguments)
73
	{
74
		$name      = strtolower($name);
75
		$positions = [
76
			'append'         => 'beforeend',
77
			'appendsibling'  => 'afterend',
78
			'prepend'        => 'afterbegin',
79
			'prependsibling' => 'beforebegin'
80
		];
81
82
		if (preg_match('(^(append|prepend)(xsl\\w+?)(sibling|)$)', $name, $m))
83
		{
84
			$localName = $m[2];
85
			$where     = $positions[$m[1] . $m[3]];
86
87
			return $this->insertXslElement($localName, $where, $arguments);
88
		}
89
		if (preg_match('(^(append|prepend)element(sibling|)$)', $name, $m))
90
		{
91
			$nodeName = $arguments[0];
92
			$text     = $arguments[1] ?? '';
93
			$where    = $positions[$m[1] . $m[2]];
94
95
			return $this->insertElement($nodeName, $where, $text);
96
		}
97
		if (preg_match('(^(append|prepend)text(sibling|)$)', $name, $m))
98
		{
99
			$text  = $arguments[0];
100
			$where = $positions[$m[1] . $m[2]];
101
102
			return $this->insertAdjacentText($where, $text);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->insertAdjacentText($where, $text) targeting s9e\SweetDOM\Element::insertAdjacentText() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
103
		}
104
105
		throw new BadMethodCallException;
106
	}
107
108
	/**
109
	* 
110
	*
111
	* @return self
112
	*/
113
	protected function insertElement(string $nodeName, string $where, string $text): self
114
	{
115
		$pos = strpos($nodeName, ':');
116
		if ($pos === false)
117
		{
118
			$element = $this->ownerDocument->createElement($nodeName, $text);
119
		}
120
		else
121
		{
122
			$prefix       = substr($nodeName, 0, $pos);
123
			$namespaceURI = $this->ownerDocument->lookupNamespaceURI($prefix);
124
			$element      = $this->ownerDocument->createElementNS($namespaceURI, $nodeName, $text);
125
		}
126
127
		return $this->insertAdjacentElement($where, $element);
128
	}
129
130
	/**
131
	* 
132
	*
133
	* @return self
134
	*/
135
	protected function insertXslElement(string $localName, string $where, array $arguments): self
136
	{
137
		$callback = [$this->ownerDocument, 'create' . $localName];
138
		if (!is_callable($callback))
139
		{
140
			throw new BadMethodCallException;
141
		}
142
143
		$element = call_user_func_array($callback, $arguments);
144
145
		return $this->insertAdjacentElement($where, $element);
146
	}
147
148
	/**
149
	* Evaluate and return the result of a given XPath expression using this element as context node
150
	*
151
	* @param  string  $expr XPath expression
152
	* @return mixed
153
	*/
154
	public function evaluate(string $expr)
155
	{
156
		return $this->ownerDocument->evaluate($expr, $this);
157
	}
158
159
	/**
160
	* Evaluate and return the first element of a given XPath query using this element as context node
161
	*
162
	* @param  string       $expr XPath expression
163
	* @return DOMNode|null
164
	*/
165
	public function firstOf(string $expr): ?DOMNode
166
	{
167
		return $this->ownerDocument->firstOf($expr, $this);
168
	}
169
170
	/**
171
	* Insert given element relative to this element's position
172
	*
173
	* @param  string $where   One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend'
174
	* @param  self   $element
175
	* @return self
176
	*/
177
	public function insertAdjacentElement(string $where, self $element): self
178
	{
179
		$this->insertAdjacentNode($where, $element);
180
181
		return $element;
182
	}
183
184
	/**
185
	* Insert given text relative to this element's position
186
	*
187
	* @param  string $where One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend'
188
	* @param  string $text
189
	* @return void
190
	*/
191
	public function insertAdjacentText(string $where, string $text): void
192
	{
193
		$this->insertAdjacentNode($where, $this->ownerDocument->createTextNode($text));
194
	}
195
196
	/**
197
	* Insert given XML relative to this element's position
198
	*
199
	* @param  string $where One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend'
200
	* @param  string $xml
201
	* @return void
202
	*/
203
	public function insertAdjacentXML(string $where, string $xml): void
204
	{
205
		$fragment = $this->ownerDocument->createDocumentFragment();
206
		$fragment->appendXML($this->addMissingNamespaceDeclarations($xml));
207
208
		$this->insertAdjacentNode($where, $fragment);
209
	}
210
211
	/**
212
	* Evaluate and return the result of a given XPath query using this element as context node
213
	*
214
	* @param  string      $expr XPath expression
215
	* @return DOMNodeList
216
	*/
217
	public function query(string $expr): DOMNodeList
218
	{
219
		return $this->ownerDocument->query($expr, $this);
220
	}
221
222
	/**
223
	* Remove this element from the document
224
	*
225
	* @return void
226
	*/
227
	public function remove(): void
228
	{
229
		$this->parentNode->removeChild($this);
230
	}
231
232
	/**
233
	* Replace this element with given nodes/text
234
	*
235
	* @param  DOMNode|string $nodes
236
	* @return void
237
	*/
238
	public function replaceWith(...$nodes): void
239
	{
240
		foreach ($nodes as $node)
241
		{
242
			if (!($node instanceof DOMNode))
243
			{
244
				$node = $this->ownerDocument->createTextNode((string) $node);
245
			}
246
			$this->parentNode->insertBefore($node, $this);
247
		}
248
		$this->parentNode->removeChild($this);
249
	}
250
251
	/**
252
	* Add namespace declarations that may be missing in given XML
253
	*
254
	* @param  string $xml Original XML
255
	* @return string      Modified XML
256
	*/
257
	protected function addMissingNamespaceDeclarations(string $xml): string
258
	{
259
		preg_match_all('(xmlns:\\K[-\\w]++(?==))', $xml, $m);
260
		$prefixes = array_flip($m[0]);
261
262
		return preg_replace_callback(
263
			'(<([-\\w]++):[^>]*?\\K\\s*/?>)',
264
			function ($m) use ($prefixes)
265
			{
266
				$return = $m[0];
267
				$prefix = $m[1];
268
				if (!isset($prefixes[$prefix]))
269
				{
270
					$nsURI  = $this->lookupNamespaceURI($prefix);
271
					$return = ' xmlns:' . $prefix . '="' . htmlspecialchars($nsURI, ENT_XML1) . '"' . $return;
272
				}
273
274
				return $return;
275
			},
276
			$xml
277
		);
278
	}
279
280
	/**
281
	* Insert given node relative to this element's position
282
	*
283
	* @param  string  $where One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend'
284
	* @param  DOMNode $node
285
	* @return void
286
	*/
287
	protected function insertAdjacentNode(string $where, DOMNode $node): void
288
	{
289
		$where = strtolower($where);
290
		if ($where === 'beforebegin')
291
		{
292
			$this->parentNode->insertBefore($node, $this);
293
		}
294
		elseif ($where === 'beforeend')
295
		{
296
			$this->appendChild($node);
297
		}
298
		elseif ($where === 'afterend')
299
		{
300
			$this->parentNode->insertBefore($node, $this->nextSibling);
301
		}
302
		elseif ($where === 'afterbegin')
303
		{
304
			$this->insertBefore($node, $this->firstChild);
305
		}
306
		else
307
		{
308
			throw new InvalidArgumentException;
309
		}
310
	}
311
}