xref: /plugin/combo/ComboStrap/Xml/XmlSystems.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1*04fd306cSNickeau<?php
2*04fd306cSNickeau
3*04fd306cSNickeau
4*04fd306cSNickeaunamespace ComboStrap\Xml;
5*04fd306cSNickeau
6*04fd306cSNickeau
7*04fd306cSNickeauuse ComboStrap\ExceptionBadArgument;
8*04fd306cSNickeauuse ComboStrap\ExceptionBadSyntax;
9*04fd306cSNickeauuse ComboStrap\ExceptionCompile;
10*04fd306cSNickeauuse ComboStrap\ExceptionNotEquals;
11*04fd306cSNickeauuse ComboStrap\ExceptionRuntime;
12*04fd306cSNickeauuse ComboStrap\Html;
13*04fd306cSNickeauuse ComboStrap\TagAttribute\StyleAttribute;
14*04fd306cSNickeauuse ComboStrap\Web\Url;
15*04fd306cSNickeauuse ComboStrap\Xml\XmlDocument;
16*04fd306cSNickeauuse ComboStrap\Xml\XmlElement;
17*04fd306cSNickeauuse DOMDocument;
18*04fd306cSNickeauuse DOMElement;
19*04fd306cSNickeauuse DOMNode;
20*04fd306cSNickeauuse Exception;
21*04fd306cSNickeau
22*04fd306cSNickeau/**
23*04fd306cSNickeau *
24*04fd306cSNickeau * @package ComboStrap
25*04fd306cSNickeau * Static function around the {@link XmlDocument}
26*04fd306cSNickeau *
27*04fd306cSNickeau *
28*04fd306cSNickeau */
29*04fd306cSNickeauclass XmlSystems
30*04fd306cSNickeau{
31*04fd306cSNickeau    const OPEN = "open";
32*04fd306cSNickeau    const CLOSED = "closed";
33*04fd306cSNickeau    const NORMAL = "normal";
34*04fd306cSNickeau
35*04fd306cSNickeau
36*04fd306cSNickeau    /**
37*04fd306cSNickeau     * Get a Simple XMl Element and returns it without the XML header (ie as HTML node)
38*04fd306cSNickeau     * @param DOMDocument $linkDom
39*04fd306cSNickeau     * @return false|string
40*04fd306cSNickeau     */
41*04fd306cSNickeau    public static function asHtml($linkDom)
42*04fd306cSNickeau    {
43*04fd306cSNickeau
44*04fd306cSNickeau        /**
45*04fd306cSNickeau         * ownerDocument returned the DOMElement
46*04fd306cSNickeau         */
47*04fd306cSNickeau        return $linkDom->ownerDocument->saveXML($linkDom->ownerDocument->documentElement);
48*04fd306cSNickeau    }
49*04fd306cSNickeau
50*04fd306cSNickeau    /**
51*04fd306cSNickeau     * Check of the text is a valid XML
52*04fd306cSNickeau     * @param $text
53*04fd306cSNickeau     * @return bool
54*04fd306cSNickeau     */
55*04fd306cSNickeau    public static function isXml($text)
56*04fd306cSNickeau    {
57*04fd306cSNickeau
58*04fd306cSNickeau        $valid = true;
59*04fd306cSNickeau        try {
60*04fd306cSNickeau            new XmlDocument($text);
61*04fd306cSNickeau        } catch (\Exception $e) {
62*04fd306cSNickeau            $valid = false;
63*04fd306cSNickeau        }
64*04fd306cSNickeau        return $valid;
65*04fd306cSNickeau
66*04fd306cSNickeau
67*04fd306cSNickeau    }
68*04fd306cSNickeau
69*04fd306cSNickeau    /**
70*04fd306cSNickeau     * Return a formatted HTML
71*04fd306cSNickeau     * @param $text
72*04fd306cSNickeau     * @return mixed
73*04fd306cSNickeau     * DOMDocument supports formatted XML while SimpleXMLElement does not.
74*04fd306cSNickeau     * @throws \Exception if empty
75*04fd306cSNickeau     */
76*04fd306cSNickeau    public static function format($text)
77*04fd306cSNickeau    {
78*04fd306cSNickeau        if (empty($text)) {
79*04fd306cSNickeau            throw new \Exception("The text should not be empty");
80*04fd306cSNickeau        }
81*04fd306cSNickeau        $doc = new DOMDocument();
82*04fd306cSNickeau        $doc->loadXML($text);
83*04fd306cSNickeau        $doc->normalize();
84*04fd306cSNickeau        $doc->formatOutput = true;
85*04fd306cSNickeau        // Type doc can also be reach with $domNode->ownerDocument
86*04fd306cSNickeau        return $doc->saveXML();
87*04fd306cSNickeau
88*04fd306cSNickeau
89*04fd306cSNickeau    }
90*04fd306cSNickeau
91*04fd306cSNickeau    /**
92*04fd306cSNickeau     * @param $text
93*04fd306cSNickeau     * @return string
94*04fd306cSNickeau     * @throws ExceptionBadSyntax
95*04fd306cSNickeau     */
96*04fd306cSNickeau    public static function normalize($text)
97*04fd306cSNickeau    {
98*04fd306cSNickeau        if (empty($text)) {
99*04fd306cSNickeau            throw new ExceptionBadSyntax("The text should not be empty");
100*04fd306cSNickeau        }
101*04fd306cSNickeau        $xmlDoc = new XmlDocument($text, XmlDocument::XML_TYPE);
102*04fd306cSNickeau        return $xmlDoc->toXmlNormalized();
103*04fd306cSNickeau    }
104*04fd306cSNickeau
105*04fd306cSNickeau    /**
106*04fd306cSNickeau     * note: Option for the loading of {@link XmlDocument}
107*04fd306cSNickeau     * have also this option
108*04fd306cSNickeau     *
109*04fd306cSNickeau     * @param $text
110*04fd306cSNickeau     * @return string|string[]
111*04fd306cSNickeau     */
112*04fd306cSNickeau    public static function extractTextWithoutCdata($text)
113*04fd306cSNickeau    {
114*04fd306cSNickeau        $text = str_replace("/*<![CDATA[*/", "", $text);
115*04fd306cSNickeau        $text = str_replace("/*!]]>*/", "", $text);
116*04fd306cSNickeau        $text = str_replace("\/", "/", $text);
117*04fd306cSNickeau        return $text;
118*04fd306cSNickeau    }
119*04fd306cSNickeau
120*04fd306cSNickeau    public static function preprocessText($text)
121*04fd306cSNickeau    {
122*04fd306cSNickeau
123*04fd306cSNickeau    }
124*04fd306cSNickeau
125*04fd306cSNickeau
126*04fd306cSNickeau    /**
127*04fd306cSNickeau     * @param DOMNode $leftNode
128*04fd306cSNickeau     * @param DOMNode $rightNode
129*04fd306cSNickeau     * Tip: To get the text of a node:
130*04fd306cSNickeau     * $leftNode->ownerDocument->saveHTML($leftNode)
131*04fd306cSNickeau     * @param $error
132*04fd306cSNickeau     * @param string[]|null $excludedAttributes - the value of this attributes will not be checked
133*04fd306cSNickeau     */
134*04fd306cSNickeau    public static function diffNode(DOMNode $leftNode, DOMNode $rightNode, &$error, array $excludedAttributes = null)
135*04fd306cSNickeau    {
136*04fd306cSNickeau
137*04fd306cSNickeau        if ($excludedAttributes === null) {
138*04fd306cSNickeau            $excludedAttributes = [];
139*04fd306cSNickeau        }
140*04fd306cSNickeau        $leftNodeName = $leftNode->localName;
141*04fd306cSNickeau        $rightNodeName = $rightNode->localName;
142*04fd306cSNickeau        if ($leftNodeName != $rightNodeName) {
143*04fd306cSNickeau            $error .= "The node (" . $rightNode->getNodePath() . ") are different (" . $leftNodeName . "," . $rightNodeName . ")\n";
144*04fd306cSNickeau        }
145*04fd306cSNickeau        if ($leftNode->hasAttributes()) {
146*04fd306cSNickeau            $leftAttributesLength = $leftNode->attributes->length;
147*04fd306cSNickeau            $rightNodeAttributes = $rightNode->attributes;
148*04fd306cSNickeau            if ($rightNodeAttributes == null) {
149*04fd306cSNickeau                $error .= "The node (" . $rightNode->getNodePath() . ") have no attributes while the left node has.\n";
150*04fd306cSNickeau            } else {
151*04fd306cSNickeau
152*04fd306cSNickeau                /**
153*04fd306cSNickeau                 * Collect the attributes by name
154*04fd306cSNickeau                 */
155*04fd306cSNickeau                $leftAttributes = array();
156*04fd306cSNickeau                for ($i = 0; $i < $leftAttributesLength; $i++) {
157*04fd306cSNickeau                    $leftAtt = $leftNode->attributes->item($i);
158*04fd306cSNickeau                    $leftAttributes[$leftAtt->nodeName] = $leftAtt;
159*04fd306cSNickeau                }
160*04fd306cSNickeau                ksort($leftAttributes);
161*04fd306cSNickeau                $rightAttributes = array();
162*04fd306cSNickeau                for ($i = 0; $i < $rightNodeAttributes->length; $i++) {
163*04fd306cSNickeau                    $rightAtt = $rightNodeAttributes->item($i);
164*04fd306cSNickeau                    $rightAttributes[$rightAtt->nodeName] = $rightAtt;
165*04fd306cSNickeau                }
166*04fd306cSNickeau
167*04fd306cSNickeau                foreach ($leftAttributes as $leftAttName => $leftAtt) {
168*04fd306cSNickeau                    /** @var \DOMAttr $leftAtt */
169*04fd306cSNickeau                    $rightAtt = $rightAttributes[$leftAttName];
170*04fd306cSNickeau                    if ($rightAtt == null) {
171*04fd306cSNickeau                        if (!in_array($leftAttName, $excludedAttributes)) {
172*04fd306cSNickeau                            $error .= "The attribute (" . $leftAtt->getNodePath() . ") does not exist on the right side\n";
173*04fd306cSNickeau                        }
174*04fd306cSNickeau                        continue;
175*04fd306cSNickeau                    }
176*04fd306cSNickeau
177*04fd306cSNickeau                    unset($rightAttributes[$leftAttName]);
178*04fd306cSNickeau
179*04fd306cSNickeau                    /**
180*04fd306cSNickeau                     * Value check
181*04fd306cSNickeau                     */
182*04fd306cSNickeau                    if (in_array($leftAttName, $excludedAttributes)) {
183*04fd306cSNickeau                        continue;
184*04fd306cSNickeau                    }
185*04fd306cSNickeau                    $leftAttValue = $leftAtt->nodeValue;
186*04fd306cSNickeau                    $rightAttValue = $rightAtt->nodeValue;
187*04fd306cSNickeau                    if ($leftAttValue !== $rightAttValue) {
188*04fd306cSNickeau                        switch ($leftAtt->name) {
189*04fd306cSNickeau                            case "class":
190*04fd306cSNickeau                                $error .= Html::getDiffBetweenValuesSeparatedByBlank($leftAttValue, $rightAttValue, "left ,{$leftAtt->getNodePath()}", "right, {$leftAtt->getNodePath()}");
191*04fd306cSNickeau                                break;
192*04fd306cSNickeau                            case "srcset":
193*04fd306cSNickeau                            case "data-srcset":
194*04fd306cSNickeau                                try {
195*04fd306cSNickeau                                    Html::getDiffBetweenSrcSet($leftAttValue, $rightAttValue);
196*04fd306cSNickeau                                } catch (ExceptionBadSyntax|ExceptionNotEquals $e) {
197*04fd306cSNickeau                                    $error .= $e->getMessage();
198*04fd306cSNickeau                                }
199*04fd306cSNickeau                                break;
200*04fd306cSNickeau                            case "src":
201*04fd306cSNickeau                            case "data-src":
202*04fd306cSNickeau                            case "href":
203*04fd306cSNickeau                            case "action": // form
204*04fd306cSNickeau                                try {
205*04fd306cSNickeau                                    $leftUrl = Url::createFromString($leftAttValue);
206*04fd306cSNickeau                                    try {
207*04fd306cSNickeau                                        $rightUrl = Url::createFromString($rightAttValue);
208*04fd306cSNickeau                                        try {
209*04fd306cSNickeau                                            $leftUrl->equals($rightUrl);
210*04fd306cSNickeau                                        } catch (ExceptionNotEquals $e) {
211*04fd306cSNickeau                                            $error .= "The attribute (" . $rightAtt->getNodePath() . ") has different values. Error:{$e->getMessage()}\n";
212*04fd306cSNickeau                                        }
213*04fd306cSNickeau                                    } catch (ExceptionBadSyntax|ExceptionBadArgument $e) {
214*04fd306cSNickeau                                        $error .= "The attribute (" . $leftAtt->getNodePath() . ") has different values (" . $leftAttValue . "," . $rightAttValue . ") and the right value is not an URL. Error:{$e->getMessage()}\n";
215*04fd306cSNickeau                                    }
216*04fd306cSNickeau                                } catch (ExceptionBadSyntax|ExceptionBadArgument $e) {
217*04fd306cSNickeau                                    $error .= "The attribute (" . $leftAtt->getNodePath() . ") has different values (" . $leftAttValue . "," . $rightAttValue . ") and the left value is not an URL. Error:{$e->getMessage()}\n";
218*04fd306cSNickeau                                }
219*04fd306cSNickeau                                break;
220*04fd306cSNickeau                            case "style":
221*04fd306cSNickeau                                try {
222*04fd306cSNickeau                                    StyleAttribute::stringEquals($leftAttValue, $rightAttValue);
223*04fd306cSNickeau                                } catch (ExceptionNotEquals $e) {
224*04fd306cSNickeau                                    $error .= "The style attribute (" . $leftAtt->getNodePath() . ") has different values (" . $leftAttValue . "," . $rightAttValue . "). Error:{$e->getMessage()}\n";
225*04fd306cSNickeau                                }
226*04fd306cSNickeau                                break;
227*04fd306cSNickeau                            default:
228*04fd306cSNickeau                                $error .= "The attribute (" . $leftAtt->getNodePath() . ") have different values (" . $leftAttValue . "," . $rightAttValue . ")\n";
229*04fd306cSNickeau                                break;
230*04fd306cSNickeau                        }
231*04fd306cSNickeau                    }
232*04fd306cSNickeau
233*04fd306cSNickeau                }
234*04fd306cSNickeau
235*04fd306cSNickeau                ksort($rightAttributes);
236*04fd306cSNickeau                foreach ($rightAttributes as $rightAttName => $rightAtt) {
237*04fd306cSNickeau                    if (!in_array($rightAttName, $excludedAttributes)) {
238*04fd306cSNickeau                        $error .= "The attribute (" . $rightAttName . ") of the node (" . $rightAtt->getNodePath() . ") does not exist on the left side\n";
239*04fd306cSNickeau                    }
240*04fd306cSNickeau                }
241*04fd306cSNickeau            }
242*04fd306cSNickeau        } else {
243*04fd306cSNickeau            if ($rightNode->hasAttributes()) {
244*04fd306cSNickeau                for ($i = 0; $i < $rightNode->attributes->length; $i++) {
245*04fd306cSNickeau                    /** @var \DOMAttr $rightAtt */
246*04fd306cSNickeau                    $rightAtt = $rightNode->attributes->item($i);
247*04fd306cSNickeau                    $error .= "The attribute (" . $rightAtt->getNodePath() . ") does not exist on the left side\n";
248*04fd306cSNickeau                }
249*04fd306cSNickeau            }
250*04fd306cSNickeau        }
251*04fd306cSNickeau        if ($leftNode->nodeName == "#text") {
252*04fd306cSNickeau            $leftNodeValue = trim($leftNode->nodeValue);
253*04fd306cSNickeau            $rightNodeValue = trim($rightNode->nodeValue);
254*04fd306cSNickeau            if ($leftNodeValue != $rightNodeValue) {
255*04fd306cSNickeau                $error .= "The node (" . $rightNode->getNodePath() . ") have different values (" . $leftNodeValue . "," . $rightNodeValue . ")\n";
256*04fd306cSNickeau            }
257*04fd306cSNickeau        }
258*04fd306cSNickeau
259*04fd306cSNickeau        /**
260*04fd306cSNickeau         * Sub
261*04fd306cSNickeau         */
262*04fd306cSNickeau        if ($leftNode->hasChildNodes()) {
263*04fd306cSNickeau
264*04fd306cSNickeau            $rightChildNodes = $rightNode->childNodes;
265*04fd306cSNickeau            $rightChildNodesCount = $rightChildNodes->length;
266*04fd306cSNickeau            if ($rightChildNodes == null || $rightChildNodesCount == 0) {
267*04fd306cSNickeau                $firstNode = $leftNode->childNodes->item(0);
268*04fd306cSNickeau                $firstNodeName = $firstNode->nodeName;
269*04fd306cSNickeau                $firstValue = $firstNode->nodeValue;
270*04fd306cSNickeau                $error .= "The left node (" . $leftNode->getNodePath() . ") have child nodes while the right has not (First Left Node: $firstNodeName, value: $firstValue) \n";
271*04fd306cSNickeau            } else {
272*04fd306cSNickeau                $leftChildNodeCount = $leftNode->childNodes->length;
273*04fd306cSNickeau                $leftChildIndex = 0;
274*04fd306cSNickeau                $rightChildIndex = 0;
275*04fd306cSNickeau                while ($leftChildIndex < $leftChildNodeCount && $rightChildIndex < $rightChildNodesCount) {
276*04fd306cSNickeau
277*04fd306cSNickeau                    $leftChildNode = $leftNode->childNodes->item($leftChildIndex);
278*04fd306cSNickeau                    if ($leftChildNode->nodeName == "#text") {
279*04fd306cSNickeau                        $leftChildNodeValue = trim($leftChildNode->nodeValue);
280*04fd306cSNickeau                        if (empty(trim($leftChildNodeValue))) {
281*04fd306cSNickeau                            $leftChildIndex++;
282*04fd306cSNickeau                            $leftChildNode = $leftNode->childNodes->item($leftChildIndex);
283*04fd306cSNickeau                        }
284*04fd306cSNickeau                    }
285*04fd306cSNickeau
286*04fd306cSNickeau                    $rightChildNode = $rightChildNodes->item($rightChildIndex);
287*04fd306cSNickeau                    if ($rightChildNode->nodeName == "#text") {
288*04fd306cSNickeau                        $leftChildNodeValue = trim($rightChildNode->nodeValue);
289*04fd306cSNickeau                        if (empty(trim($leftChildNodeValue))) {
290*04fd306cSNickeau                            $rightChildIndex++;
291*04fd306cSNickeau                            $rightChildNode = $rightChildNodes->item($rightChildIndex);
292*04fd306cSNickeau                        }
293*04fd306cSNickeau                    }
294*04fd306cSNickeau
295*04fd306cSNickeau                    if ($rightChildNode != null) {
296*04fd306cSNickeau                        if ($leftChildNode != null) {
297*04fd306cSNickeau                            self::diffNode($leftChildNode, $rightChildNode, $error, $excludedAttributes);
298*04fd306cSNickeau                        } else {
299*04fd306cSNickeau                            $error .= "The right node (" . $rightChildNode->getNodePath() . ") does not exist in the left document.\n";
300*04fd306cSNickeau                        }
301*04fd306cSNickeau                    } else {
302*04fd306cSNickeau                        if ($leftChildNode != null) {
303*04fd306cSNickeau                            $error .= "The left node (" . $leftChildNode->getNodePath() . ") does not exist in the right document.\n";
304*04fd306cSNickeau                        }
305*04fd306cSNickeau                    }
306*04fd306cSNickeau
307*04fd306cSNickeau                    /**
308*04fd306cSNickeau                     * 0 based index
309*04fd306cSNickeau                     */
310*04fd306cSNickeau                    $leftChildIndex++;
311*04fd306cSNickeau                    $rightChildIndex++;
312*04fd306cSNickeau                }
313*04fd306cSNickeau            }
314*04fd306cSNickeau        }
315*04fd306cSNickeau
316*04fd306cSNickeau    }
317*04fd306cSNickeau
318*04fd306cSNickeau    /**
319*04fd306cSNickeau     * Return a diff
320*04fd306cSNickeau     * @param string $left
321*04fd306cSNickeau     * @param string $right
322*04fd306cSNickeau     * @return string
323*04fd306cSNickeau     * DOMDocument supports formatted XML while SimpleXMLElement does not.
324*04fd306cSNickeau     * @throws ExceptionCompile
325*04fd306cSNickeau     */
326*04fd306cSNickeau    public
327*04fd306cSNickeau    static function diffMarkup(string $left, string $right): string
328*04fd306cSNickeau    {
329*04fd306cSNickeau        if (empty($right)) {
330*04fd306cSNickeau            throw new \RuntimeException("The right text should not be empty");
331*04fd306cSNickeau        }
332*04fd306cSNickeau        $leftDocument = new XmlDocument($left);
333*04fd306cSNickeau
334*04fd306cSNickeau        if (empty($left)) {
335*04fd306cSNickeau            throw new \RuntimeException("The left text should not be empty");
336*04fd306cSNickeau        }
337*04fd306cSNickeau        $rightDocument = new XmlDocument($right);
338*04fd306cSNickeau
339*04fd306cSNickeau        return $leftDocument->diff($rightDocument);
340*04fd306cSNickeau
341*04fd306cSNickeau    }
342*04fd306cSNickeau
343*04fd306cSNickeau    public static function deleteAllElementsByName(string $elementName, XmlDocument $xmlDocument)
344*04fd306cSNickeau    {
345*04fd306cSNickeau        $xpathQuery = "//*[local-name()='$elementName']";
346*04fd306cSNickeau        try {
347*04fd306cSNickeau            $svgElement = $xmlDocument->xpath($xpathQuery);
348*04fd306cSNickeau        } catch (ExceptionBadSyntax $e) {
349*04fd306cSNickeau            // should not happen on prod
350*04fd306cSNickeau            throw new ExceptionRuntime("xpath query error ($xpathQuery");
351*04fd306cSNickeau        }
352*04fd306cSNickeau        for ($i = 0; $i < $svgElement->length; $i++) {
353*04fd306cSNickeau            $nodeElement = XmlElement::create($svgElement[$i], $xmlDocument);
354*04fd306cSNickeau            $nodeElement->remove();
355*04fd306cSNickeau        }
356*04fd306cSNickeau    }
357*04fd306cSNickeau
358*04fd306cSNickeau
359*04fd306cSNickeau}
360