1
|
|
|
<?php |
2
|
|
|
namespace CfdiUtils\Utils; |
3
|
|
|
|
4
|
|
|
use DOMDocument; |
5
|
|
|
use DOMElement; |
6
|
|
|
use DOMNode; |
7
|
|
|
|
8
|
|
|
class Xml |
9
|
|
|
{ |
10
|
105 |
|
public static function documentElement(DOMDocument $document): DOMElement |
11
|
|
|
{ |
12
|
105 |
|
if (! $document->documentElement instanceof DOMElement) { |
|
|
|
|
13
|
2 |
|
throw new \UnexpectedValueException('DOM Document does not have root element'); |
14
|
|
|
} |
15
|
103 |
|
return $document->documentElement; |
16
|
|
|
} |
17
|
|
|
|
18
|
19 |
|
public static function ownerDocument(DOMNode $node): DOMDocument |
19
|
|
|
{ |
20
|
|
|
// $node->ownerDocument is NULL if node is a DOMDocument |
21
|
19 |
|
if (null === $node->ownerDocument) { |
22
|
|
|
if ($node instanceof DOMDocument) { |
23
|
|
|
return $node; |
24
|
|
|
} |
25
|
|
|
/** @codeCoverageIgnore */ |
26
|
|
|
throw new \LogicException('node->ownerDocument is null but node is not a DOMDocument'); |
27
|
|
|
} |
28
|
19 |
|
return $node->ownerDocument; |
29
|
|
|
} |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Creates a DOMDocument object version 1.0 encoding UTF-8 |
33
|
|
|
* with output formatting and not preserving white spaces |
34
|
|
|
* |
35
|
|
|
* @return DOMDocument |
36
|
|
|
*/ |
37
|
126 |
|
public static function newDocument(): DOMDocument |
38
|
|
|
{ |
39
|
126 |
|
$document = new DOMDocument('1.0', 'UTF-8'); |
40
|
126 |
|
$document->formatOutput = true; |
41
|
126 |
|
$document->preserveWhiteSpace = false; |
42
|
126 |
|
return $document; |
43
|
|
|
} |
44
|
|
|
|
45
|
98 |
|
public static function newDocumentContent(string $content): DOMDocument |
46
|
|
|
{ |
47
|
98 |
|
if ('' === $content) { |
48
|
2 |
|
throw new \UnexpectedValueException('Received xml string argument is empty'); |
49
|
|
|
} |
50
|
96 |
|
$document = static::newDocument(); |
51
|
|
|
// this error silenced call is intentional, no need to alter libxml_use_internal_errors |
52
|
96 |
|
if (false === @$document->loadXML($content)) { |
53
|
2 |
|
throw new \UnexpectedValueException('Cannot create a DOM Document from xml string'); |
54
|
|
|
} |
55
|
94 |
|
return $document; |
56
|
|
|
} |
57
|
|
|
|
58
|
848 |
|
public static function isValidXmlName(string $name): bool |
59
|
|
|
{ |
60
|
848 |
|
if ('' === $name) { |
61
|
3 |
|
return false; |
62
|
|
|
} |
63
|
|
|
$pattern = '/^[:_A-Za-z' |
64
|
|
|
. '\xC0-\xD6\xD8-\xF6\xF8-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}' |
65
|
|
|
. '\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}]{1}' |
66
|
|
|
. '[\-:_A-Za-z0-9' |
67
|
|
|
. '\xC0-\xD6\xD8-\xF6\xF8-\x{2FF}\x{370}-\x{37D}\x{37F}-\x{1FFF}\x{200C}-\x{200D}\x{2070}-\x{218F}' |
68
|
|
|
. '\x{2C00}-\x{2FEF}\x{3001}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFFD}\x{10000}-\x{EFFFF}' |
69
|
845 |
|
. '\xB7\x{0300}-\x{036F}\x{203F}-\x{2040}]*$/u'; |
70
|
845 |
|
return (1 === preg_match($pattern, $name)); |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* This is an alias of DOMDocument::createElement that will replace ampersand '&' with '&' |
75
|
|
|
* @see https://www.php.net/manual/en/domdocument.createelement.php |
76
|
|
|
* |
77
|
|
|
* @param DOMDocument $document |
78
|
|
|
* @param string $name |
79
|
|
|
* @param string $content |
80
|
|
|
* @return DOMElement |
81
|
|
|
*/ |
82
|
10 |
|
public static function createElement(DOMDocument $document, string $name, string $content = ''): DOMElement |
83
|
|
|
{ |
84
|
10 |
|
return static::createDOMElement( |
85
|
|
|
function () use ($document, $name) { |
86
|
10 |
|
return $document->createElement($name); |
87
|
10 |
|
}, |
88
|
10 |
|
sprintf('Cannot create element with name %s', $name), |
89
|
|
|
$content |
90
|
|
|
); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* This is an alias of DOMDocument::createElementNS that will replace ampersand '&' with '&' |
95
|
|
|
* @see https://www.php.net/manual/en/domdocument.createelementns.php |
96
|
|
|
* |
97
|
|
|
* @param DOMDocument $document |
98
|
|
|
* @param string $namespaceURI |
99
|
|
|
* @param string $name |
100
|
|
|
* @param string $content |
101
|
|
|
* @return DOMElement |
102
|
|
|
*/ |
103
|
11 |
|
public static function createElementNS( |
104
|
|
|
DOMDocument $document, |
105
|
|
|
string $namespaceURI, |
106
|
|
|
string $name, |
107
|
|
|
string $content = '' |
108
|
|
|
): DOMElement { |
109
|
11 |
|
return static::createDOMElement( |
110
|
|
|
function () use ($document, $namespaceURI, $name) { |
111
|
11 |
|
return $document->createElementNS($namespaceURI, $name); |
112
|
11 |
|
}, |
113
|
11 |
|
sprintf('Cannot create element with name %s namespace %s', $name, $namespaceURI), |
114
|
|
|
$content |
115
|
|
|
); |
116
|
|
|
} |
117
|
|
|
|
118
|
21 |
|
private static function createDOMElement(\Closure $fnCreate, string $errorMessage, string $content): DOMElement |
119
|
|
|
{ |
120
|
|
|
/** @var DOMElement|null $element */ |
121
|
21 |
|
$element = null; |
|
|
|
|
122
|
21 |
|
$previousException = null; |
123
|
|
|
try { |
124
|
21 |
|
$element = $fnCreate(); |
125
|
3 |
|
} catch (\Throwable $creationException) { |
126
|
3 |
|
$previousException = $creationException; |
127
|
|
|
} |
128
|
21 |
|
if (! $element instanceof DOMElement) { |
129
|
3 |
|
throw new \LogicException($errorMessage, 0, $previousException); |
130
|
|
|
} |
131
|
18 |
|
if ('' !== $content) { |
132
|
16 |
|
$element->appendChild(static::ownerDocument($element)->createTextNode($content)); |
133
|
|
|
} |
134
|
18 |
|
return $element; |
135
|
|
|
} |
136
|
|
|
} |
137
|
|
|
|