1<?php
2
3
4namespace ComboStrap;
5
6
7use DOMDocument;
8use DOMElement;
9use DOMNode;
10use Exception;
11
12/**
13 * Class XmlUtility
14 * @package ComboStrap
15 * Static function around the {@link DOMDocument}
16 *
17 *
18 */
19class XmlUtility
20{
21    const OPEN = "open";
22    const CLOSED = "closed";
23    const NORMAL = "normal";
24
25
26    /**
27     * Get a Simple XMl Element and returns it without the XML header (ie as HTML node)
28     * @param DOMDocument $linkDom
29     * @return false|string
30     */
31    public static function asHtml($linkDom)
32    {
33
34        /**
35         * ownerDocument returned the DOMElement
36         */
37        return $linkDom->ownerDocument->saveXML($linkDom->ownerDocument->documentElement);
38    }
39
40    /**
41     * Check of the text is a valid XML
42     * @param $text
43     * @return bool
44     */
45    public static function isXml($text)
46    {
47
48        $valid = true;
49        try {
50            new XmlDocument($text);
51        } catch (\Exception $e) {
52            $valid = false;
53        }
54        return $valid;
55
56
57    }
58
59    /**
60     * Return a formatted HTML
61     * @param $text
62     * @return mixed
63     * DOMDocument supports formatted XML while SimpleXMLElement does not.
64     * @throws \Exception if empty
65     */
66    public static function format($text)
67    {
68        if (empty($text)) {
69            throw new \Exception("The text should not be empty");
70        }
71        $doc = new DOMDocument();
72        $doc->loadXML($text);
73        $doc->normalize();
74        $doc->formatOutput = true;
75        // Type doc can also be reach with $domNode->ownerDocument
76        return $doc->saveXML();
77
78
79    }
80
81    /**
82     * @param $text
83     * @return mixed
84     */
85    public static function normalize($text)
86    {
87        if (empty($text)) {
88            throw new \RuntimeException("The text should not be empty");
89        }
90        $xmlDoc = new XmlDocument($text, XmlDocument::XML_TYPE);
91        return $xmlDoc->getXmlTextNormalized();
92    }
93
94    /**
95     * note: Option for the loading of {@link XmlDocument}
96     * have also this option
97     *
98     * @param $text
99     * @return string|string[]
100     */
101    public static function extractTextWithoutCdata($text)
102    {
103        $text = str_replace("/*<![CDATA[*/", "", $text);
104        $text = str_replace("/*!]]>*/", "", $text);
105        $text = str_replace("\/", "/", $text);
106        return $text;
107    }
108
109    public static function preprocessText($text)
110    {
111
112    }
113
114
115    /**
116     * @noinspection PhpComposerExtensionStubsInspection
117     * @param DOMNode $leftNode
118     * @param DOMNode $rightNode
119     * Tip: To get the text of a node:
120     * $leftNode->ownerDocument->saveHTML($leftNode)
121     * @param $error
122     */
123    public static function diffNode(DOMNode $leftNode, DOMNode $rightNode, &$error)
124    {
125
126        $leftNodeName = $leftNode->localName;
127        $rightNodeName = $rightNode->localName;
128        if ($leftNodeName != $rightNodeName) {
129            $error .= "The node (" . $rightNode->getNodePath() . ") are different (" . $leftNodeName . "," . $rightNodeName . ")\n";
130        }
131        if ($leftNode->hasAttributes()) {
132            $leftAttributesLength = $leftNode->attributes->length;
133            $rightNodeAttributes = $rightNode->attributes;
134            if ($rightNodeAttributes == null) {
135                $error .= "The node (" . $rightNode->getNodePath() . ") have no attributes while the left node has.\n";
136            } else {
137
138                /**
139                 * Collect the attributes by name
140                 */
141                $leftAttributes = array();
142                for ($i = 0; $i < $leftAttributesLength; $i++) {
143                    $leftAtt = $leftNode->attributes->item($i);
144                    $leftAttributes[$leftAtt->nodeName] = $leftAtt;
145                }
146                ksort($leftAttributes);
147                $rightAttributes = array();
148                for ($i = 0; $i < $rightNodeAttributes->length; $i++) {
149                    $rightAtt = $rightNodeAttributes->item($i);
150                    $rightAttributes[$rightAtt->nodeName] = $rightAtt;
151                }
152
153                foreach ($leftAttributes as $leftAttName => $leftAtt) {
154                    /** @var \DOMAttr $leftAtt */
155                    $rightAtt = $rightAttributes[$leftAttName];
156                    if ($rightAtt == null) {
157                        $error .= "The attribute (" . $leftAtt->getNodePath() . ") does not exist on the right side\n";
158                    } else {
159                        unset($rightAttributes[$leftAttName]);
160                        $leftAttValue = $leftAtt->nodeValue;
161                        $rightAttValue = $rightAtt->nodeValue;
162                        if ($leftAttValue != $rightAttValue) {
163                            if ($leftAtt->name === "class") {
164                                $leftClasses = preg_split("/\s/", $leftAttValue);
165                                $rightClasses = preg_split("/\s/", $rightAttValue);
166                                foreach ($leftClasses as $leftClass) {
167                                    if (!in_array($leftClass, $rightClasses)) {
168                                        $error .= "The left class attribute (" . $leftAtt->getNodePath() . ") has the value (" . $leftClass . ") that is not present in the right node)\n";
169                                    } else {
170                                        // Delete the value
171                                        $key = array_search($leftClass, $rightClasses);
172                                        unset($rightClasses[$key]);
173                                    }
174                                }
175                                foreach ($rightClasses as $rightClass) {
176                                    $error .= "The right class attribute (" . $leftAtt->getNodePath() . ") has the value (" . $rightClass . ") that is not present in the left node)\n";
177                                }
178                            } else {
179                                $error .= "The attribute (" . $leftAtt->getNodePath() . ") have different values (" . $leftAttValue . "," . $rightAttValue . ")\n";
180                            }
181                        }
182                    }
183                }
184
185                ksort($rightAttributes);
186                foreach ($rightAttributes as $rightAttName => $rightAtt) {
187                    $error .= "The attribute (" . $rightAttName . ") of the node (" . $rightAtt->getNodePath() . ") does not exist on the left side\n";
188                }
189            }
190        } else {
191            if ($rightNode->hasAttributes()){
192                for ($i = 0; $i < $rightNode->attributes->length; $i++) {
193                    /** @var \DOMAttr $rightAtt */
194                    $rightAtt = $rightNode->attributes->item($i);
195                    $error .= "The attribute (" . $rightAtt->getNodePath() . ") does not exist on the left side\n";
196                }
197            }
198        }
199        if ($leftNode->nodeName == "#text") {
200            $leftNodeValue = trim($leftNode->nodeValue);
201            $rightNodeValue = trim($rightNode->nodeValue);
202            if ($leftNodeValue != $rightNodeValue) {
203                $error .= "The node (" . $rightNode->getNodePath() . ") have different values (" . $leftNodeValue . "," . $rightNodeValue . ")\n";
204            }
205        }
206
207        /**
208         * Sub
209         */
210        if ($leftNode->hasChildNodes()) {
211
212            $rightChildNodes = $rightNode->childNodes;
213            $rightChildNodesCount = $rightChildNodes->length;
214            if ($rightChildNodes == null || $rightChildNodesCount == 0) {
215                $firstNode = $leftNode->childNodes->item(0);
216                $firstNodeName = $firstNode->nodeName;
217                $firstValue = $firstNode->nodeValue;
218                $error .= "The left node (" . $leftNode->getNodePath() . ") have child nodes while the right has not (First Left Node: $firstNodeName, value: $firstValue) \n";
219            } else {
220                $leftChildNodeCount = $leftNode->childNodes->length;
221                $leftChildIndex = 0;
222                $rightChildIndex = 0;
223                while ($leftChildIndex < $leftChildNodeCount && $rightChildIndex < $rightChildNodesCount) {
224
225                    $leftChildNode = $leftNode->childNodes->item($leftChildIndex);
226                    if ($leftChildNode->nodeName == "#text") {
227                        $leftChildNodeValue = trim($leftChildNode->nodeValue);
228                        if (empty(trim($leftChildNodeValue))) {
229                            $leftChildIndex++;
230                            $leftChildNode = $leftNode->childNodes->item($leftChildIndex);
231                        }
232                    }
233
234                    $rightChildNode = $rightChildNodes->item($rightChildIndex);
235                    if ($rightChildNode->nodeName == "#text") {
236                        $leftChildNodeValue = trim($rightChildNode->nodeValue);
237                        if (empty(trim($leftChildNodeValue))) {
238                            $rightChildIndex++;
239                            $rightChildNode = $rightChildNodes->item($rightChildIndex);
240                        }
241                    }
242
243                    if ($rightChildNode != null) {
244                        if ($leftChildNode != null) {
245                            self::diffNode($leftChildNode, $rightChildNode, $error);
246                        } else {
247                            $error .= "The right node (" . $rightChildNode->getNodePath() . ") does not exist in the left document.\n";
248                        }
249                    } else {
250                        if ($leftChildNode != null) {
251                            $error .= "The left node (" . $leftChildNode->getNodePath() . ") does not exist in the right document.\n";
252                        }
253                    }
254
255                    /**
256                     * 0 based index
257                     */
258                    $leftChildIndex++;
259                    $rightChildIndex++;
260                }
261            }
262        }
263
264    }
265
266    /**
267     * Return a diff
268     * @param string $left
269     * @param string $right
270     * @return string
271     * DOMDocument supports formatted XML while SimpleXMLElement does not.
272     * @noinspection PhpComposerExtensionStubsInspection
273     */
274    public static function diffMarkup($left, $right)
275    {
276        if (empty($right)) {
277            throw new \RuntimeException("The right text should not be empty");
278        }
279        $leftDocument = new XmlDocument($left);
280
281        if (empty($left)) {
282            throw new \RuntimeException("The left text should not be empty");
283        }
284        $rightDocument = new XmlDocument($right);
285
286        return $leftDocument->diff($rightDocument);
287
288    }
289
290
291}
292