xref: /plugin/combo/ComboStrap/Xml/XmlSystems.php (revision 70bbd7f1f72440223cc13f3495efdcb2b0a11514)
104fd306cSNickeau<?php
204fd306cSNickeau
304fd306cSNickeau
404fd306cSNickeaunamespace ComboStrap\Xml;
504fd306cSNickeau
604fd306cSNickeau
704fd306cSNickeauuse ComboStrap\ExceptionBadArgument;
804fd306cSNickeauuse ComboStrap\ExceptionBadSyntax;
904fd306cSNickeauuse ComboStrap\ExceptionCompile;
1004fd306cSNickeauuse ComboStrap\ExceptionNotEquals;
1104fd306cSNickeauuse ComboStrap\ExceptionRuntime;
1204fd306cSNickeauuse ComboStrap\Html;
1304fd306cSNickeauuse ComboStrap\TagAttribute\StyleAttribute;
1404fd306cSNickeauuse ComboStrap\Web\Url;
1504fd306cSNickeauuse ComboStrap\Xml\XmlDocument;
1604fd306cSNickeauuse ComboStrap\Xml\XmlElement;
1704fd306cSNickeauuse DOMDocument;
1804fd306cSNickeauuse DOMElement;
1904fd306cSNickeauuse DOMNode;
2004fd306cSNickeauuse Exception;
2104fd306cSNickeau
2204fd306cSNickeau/**
2304fd306cSNickeau *
2404fd306cSNickeau * @package ComboStrap
2504fd306cSNickeau * Static function around the {@link XmlDocument}
2604fd306cSNickeau *
2704fd306cSNickeau *
2804fd306cSNickeau */
2904fd306cSNickeauclass XmlSystems
3004fd306cSNickeau{
3104fd306cSNickeau    const OPEN = "open";
3204fd306cSNickeau    const CLOSED = "closed";
3304fd306cSNickeau    const NORMAL = "normal";
3404fd306cSNickeau
3504fd306cSNickeau
3604fd306cSNickeau    /**
3704fd306cSNickeau     * Get a Simple XMl Element and returns it without the XML header (ie as HTML node)
3804fd306cSNickeau     * @param DOMDocument $linkDom
3904fd306cSNickeau     * @return false|string
4004fd306cSNickeau     */
4104fd306cSNickeau    public static function asHtml($linkDom)
4204fd306cSNickeau    {
4304fd306cSNickeau
4404fd306cSNickeau        /**
4504fd306cSNickeau         * ownerDocument returned the DOMElement
4604fd306cSNickeau         */
4704fd306cSNickeau        return $linkDom->ownerDocument->saveXML($linkDom->ownerDocument->documentElement);
4804fd306cSNickeau    }
4904fd306cSNickeau
5004fd306cSNickeau    /**
5104fd306cSNickeau     * Check of the text is a valid XML
5204fd306cSNickeau     * @param $text
5304fd306cSNickeau     * @return bool
5404fd306cSNickeau     */
5504fd306cSNickeau    public static function isXml($text)
5604fd306cSNickeau    {
5704fd306cSNickeau
5804fd306cSNickeau        $valid = true;
5904fd306cSNickeau        try {
6004fd306cSNickeau            new XmlDocument($text);
6104fd306cSNickeau        } catch (\Exception $e) {
6204fd306cSNickeau            $valid = false;
6304fd306cSNickeau        }
6404fd306cSNickeau        return $valid;
6504fd306cSNickeau
6604fd306cSNickeau
6704fd306cSNickeau    }
6804fd306cSNickeau
6904fd306cSNickeau    /**
7004fd306cSNickeau     * Return a formatted HTML
7104fd306cSNickeau     * @param $text
7204fd306cSNickeau     * @return mixed
7304fd306cSNickeau     * DOMDocument supports formatted XML while SimpleXMLElement does not.
7404fd306cSNickeau     * @throws \Exception if empty
7504fd306cSNickeau     */
7604fd306cSNickeau    public static function format($text)
7704fd306cSNickeau    {
7804fd306cSNickeau        if (empty($text)) {
7904fd306cSNickeau            throw new \Exception("The text should not be empty");
8004fd306cSNickeau        }
8104fd306cSNickeau        $doc = new DOMDocument();
8204fd306cSNickeau        $doc->loadXML($text);
8304fd306cSNickeau        $doc->normalize();
8404fd306cSNickeau        $doc->formatOutput = true;
8504fd306cSNickeau        // Type doc can also be reach with $domNode->ownerDocument
8604fd306cSNickeau        return $doc->saveXML();
8704fd306cSNickeau
8804fd306cSNickeau
8904fd306cSNickeau    }
9004fd306cSNickeau
9104fd306cSNickeau    /**
9204fd306cSNickeau     * @param $text
9304fd306cSNickeau     * @return string
9404fd306cSNickeau     * @throws ExceptionBadSyntax
9504fd306cSNickeau     */
9604fd306cSNickeau    public static function normalize($text)
9704fd306cSNickeau    {
9804fd306cSNickeau        if (empty($text)) {
9904fd306cSNickeau            throw new ExceptionBadSyntax("The text should not be empty");
10004fd306cSNickeau        }
10104fd306cSNickeau        $xmlDoc = new XmlDocument($text, XmlDocument::XML_TYPE);
10204fd306cSNickeau        return $xmlDoc->toXmlNormalized();
10304fd306cSNickeau    }
10404fd306cSNickeau
10504fd306cSNickeau    /**
10604fd306cSNickeau     * note: Option for the loading of {@link XmlDocument}
10704fd306cSNickeau     * have also this option
10804fd306cSNickeau     *
10904fd306cSNickeau     * @param $text
11004fd306cSNickeau     * @return string|string[]
11104fd306cSNickeau     */
11204fd306cSNickeau    public static function extractTextWithoutCdata($text)
11304fd306cSNickeau    {
11404fd306cSNickeau        $text = str_replace("/*<![CDATA[*/", "", $text);
11504fd306cSNickeau        $text = str_replace("/*!]]>*/", "", $text);
11604fd306cSNickeau        $text = str_replace("\/", "/", $text);
11704fd306cSNickeau        return $text;
11804fd306cSNickeau    }
11904fd306cSNickeau
12004fd306cSNickeau    public static function preprocessText($text)
12104fd306cSNickeau    {
12204fd306cSNickeau
12304fd306cSNickeau    }
12404fd306cSNickeau
12504fd306cSNickeau
12604fd306cSNickeau    /**
12704fd306cSNickeau     * @param DOMNode $leftNode
12804fd306cSNickeau     * @param DOMNode $rightNode
12904fd306cSNickeau     * Tip: To get the text of a node:
13004fd306cSNickeau     * $leftNode->ownerDocument->saveHTML($leftNode)
13104fd306cSNickeau     * @param $error
13204fd306cSNickeau     * @param string[]|null $excludedAttributes - the value of this attributes will not be checked
13304fd306cSNickeau     */
13404fd306cSNickeau    public static function diffNode(DOMNode $leftNode, DOMNode $rightNode, &$error, array $excludedAttributes = null)
13504fd306cSNickeau    {
13604fd306cSNickeau
13704fd306cSNickeau        if ($excludedAttributes === null) {
13804fd306cSNickeau            $excludedAttributes = [];
13904fd306cSNickeau        }
14004fd306cSNickeau        $leftNodeName = $leftNode->localName;
14104fd306cSNickeau        $rightNodeName = $rightNode->localName;
14204fd306cSNickeau        if ($leftNodeName != $rightNodeName) {
14304fd306cSNickeau            $error .= "The node (" . $rightNode->getNodePath() . ") are different (" . $leftNodeName . "," . $rightNodeName . ")\n";
14404fd306cSNickeau        }
14504fd306cSNickeau        if ($leftNode->hasAttributes()) {
14604fd306cSNickeau            $leftAttributesLength = $leftNode->attributes->length;
14704fd306cSNickeau            $rightNodeAttributes = $rightNode->attributes;
14804fd306cSNickeau            if ($rightNodeAttributes == null) {
14904fd306cSNickeau                $error .= "The node (" . $rightNode->getNodePath() . ") have no attributes while the left node has.\n";
15004fd306cSNickeau            } else {
15104fd306cSNickeau
15204fd306cSNickeau                /**
15304fd306cSNickeau                 * Collect the attributes by name
15404fd306cSNickeau                 */
15504fd306cSNickeau                $leftAttributes = array();
15604fd306cSNickeau                for ($i = 0; $i < $leftAttributesLength; $i++) {
15704fd306cSNickeau                    $leftAtt = $leftNode->attributes->item($i);
15804fd306cSNickeau                    $leftAttributes[$leftAtt->nodeName] = $leftAtt;
15904fd306cSNickeau                }
16004fd306cSNickeau                ksort($leftAttributes);
16104fd306cSNickeau                $rightAttributes = array();
16204fd306cSNickeau                for ($i = 0; $i < $rightNodeAttributes->length; $i++) {
16304fd306cSNickeau                    $rightAtt = $rightNodeAttributes->item($i);
16404fd306cSNickeau                    $rightAttributes[$rightAtt->nodeName] = $rightAtt;
16504fd306cSNickeau                }
16604fd306cSNickeau
16704fd306cSNickeau                foreach ($leftAttributes as $leftAttName => $leftAtt) {
16804fd306cSNickeau                    /** @var \DOMAttr $leftAtt */
169*70bbd7f1Sgerardnico                    $rightAtt = $rightAttributes[$leftAttName] ?? null;
17004fd306cSNickeau                    if ($rightAtt == null) {
17104fd306cSNickeau                        if (!in_array($leftAttName, $excludedAttributes)) {
17204fd306cSNickeau                            $error .= "The attribute (" . $leftAtt->getNodePath() . ") does not exist on the right side\n";
17304fd306cSNickeau                        }
17404fd306cSNickeau                        continue;
17504fd306cSNickeau                    }
17604fd306cSNickeau
17704fd306cSNickeau                    unset($rightAttributes[$leftAttName]);
17804fd306cSNickeau
17904fd306cSNickeau                    /**
18004fd306cSNickeau                     * Value check
18104fd306cSNickeau                     */
18204fd306cSNickeau                    if (in_array($leftAttName, $excludedAttributes)) {
18304fd306cSNickeau                        continue;
18404fd306cSNickeau                    }
18504fd306cSNickeau                    $leftAttValue = $leftAtt->nodeValue;
18604fd306cSNickeau                    $rightAttValue = $rightAtt->nodeValue;
18704fd306cSNickeau                    if ($leftAttValue !== $rightAttValue) {
18804fd306cSNickeau                        switch ($leftAtt->name) {
18904fd306cSNickeau                            case "class":
19004fd306cSNickeau                                $error .= Html::getDiffBetweenValuesSeparatedByBlank($leftAttValue, $rightAttValue, "left ,{$leftAtt->getNodePath()}", "right, {$leftAtt->getNodePath()}");
19104fd306cSNickeau                                break;
19204fd306cSNickeau                            case "srcset":
19304fd306cSNickeau                            case "data-srcset":
19404fd306cSNickeau                                try {
19504fd306cSNickeau                                    Html::getDiffBetweenSrcSet($leftAttValue, $rightAttValue);
19604fd306cSNickeau                                } catch (ExceptionBadSyntax|ExceptionNotEquals $e) {
19704fd306cSNickeau                                    $error .= $e->getMessage();
19804fd306cSNickeau                                }
19904fd306cSNickeau                                break;
20004fd306cSNickeau                            case "src":
20104fd306cSNickeau                            case "data-src":
20204fd306cSNickeau                            case "href":
20304fd306cSNickeau                            case "action": // form
20404fd306cSNickeau                                try {
20504fd306cSNickeau                                    $leftUrl = Url::createFromString($leftAttValue);
20604fd306cSNickeau                                    try {
20704fd306cSNickeau                                        $rightUrl = Url::createFromString($rightAttValue);
20804fd306cSNickeau                                        try {
20904fd306cSNickeau                                            $leftUrl->equals($rightUrl);
21004fd306cSNickeau                                        } catch (ExceptionNotEquals $e) {
21104fd306cSNickeau                                            $error .= "The attribute (" . $rightAtt->getNodePath() . ") has different values. Error:{$e->getMessage()}\n";
21204fd306cSNickeau                                        }
21304fd306cSNickeau                                    } catch (ExceptionBadSyntax|ExceptionBadArgument $e) {
21404fd306cSNickeau                                        $error .= "The attribute (" . $leftAtt->getNodePath() . ") has different values (" . $leftAttValue . "," . $rightAttValue . ") and the right value is not an URL. Error:{$e->getMessage()}\n";
21504fd306cSNickeau                                    }
21604fd306cSNickeau                                } catch (ExceptionBadSyntax|ExceptionBadArgument $e) {
21704fd306cSNickeau                                    $error .= "The attribute (" . $leftAtt->getNodePath() . ") has different values (" . $leftAttValue . "," . $rightAttValue . ") and the left value is not an URL. Error:{$e->getMessage()}\n";
21804fd306cSNickeau                                }
21904fd306cSNickeau                                break;
22004fd306cSNickeau                            case "style":
22104fd306cSNickeau                                try {
22204fd306cSNickeau                                    StyleAttribute::stringEquals($leftAttValue, $rightAttValue);
22304fd306cSNickeau                                } catch (ExceptionNotEquals $e) {
22404fd306cSNickeau                                    $error .= "The style attribute (" . $leftAtt->getNodePath() . ") has different values (" . $leftAttValue . "," . $rightAttValue . "). Error:{$e->getMessage()}\n";
22504fd306cSNickeau                                }
22604fd306cSNickeau                                break;
22704fd306cSNickeau                            default:
22804fd306cSNickeau                                $error .= "The attribute (" . $leftAtt->getNodePath() . ") have different values (" . $leftAttValue . "," . $rightAttValue . ")\n";
22904fd306cSNickeau                                break;
23004fd306cSNickeau                        }
23104fd306cSNickeau                    }
23204fd306cSNickeau
23304fd306cSNickeau                }
23404fd306cSNickeau
23504fd306cSNickeau                ksort($rightAttributes);
23604fd306cSNickeau                foreach ($rightAttributes as $rightAttName => $rightAtt) {
23704fd306cSNickeau                    if (!in_array($rightAttName, $excludedAttributes)) {
23804fd306cSNickeau                        $error .= "The attribute (" . $rightAttName . ") of the node (" . $rightAtt->getNodePath() . ") does not exist on the left side\n";
23904fd306cSNickeau                    }
24004fd306cSNickeau                }
24104fd306cSNickeau            }
24204fd306cSNickeau        } else {
24304fd306cSNickeau            if ($rightNode->hasAttributes()) {
24404fd306cSNickeau                for ($i = 0; $i < $rightNode->attributes->length; $i++) {
24504fd306cSNickeau                    /** @var \DOMAttr $rightAtt */
24604fd306cSNickeau                    $rightAtt = $rightNode->attributes->item($i);
24704fd306cSNickeau                    $error .= "The attribute (" . $rightAtt->getNodePath() . ") does not exist on the left side\n";
24804fd306cSNickeau                }
24904fd306cSNickeau            }
25004fd306cSNickeau        }
25104fd306cSNickeau        if ($leftNode->nodeName == "#text") {
25204fd306cSNickeau            $leftNodeValue = trim($leftNode->nodeValue);
25304fd306cSNickeau            $rightNodeValue = trim($rightNode->nodeValue);
25404fd306cSNickeau            if ($leftNodeValue != $rightNodeValue) {
25504fd306cSNickeau                $error .= "The node (" . $rightNode->getNodePath() . ") have different values (" . $leftNodeValue . "," . $rightNodeValue . ")\n";
25604fd306cSNickeau            }
25704fd306cSNickeau        }
25804fd306cSNickeau
25904fd306cSNickeau        /**
26004fd306cSNickeau         * Sub
26104fd306cSNickeau         */
26204fd306cSNickeau        if ($leftNode->hasChildNodes()) {
26304fd306cSNickeau
26404fd306cSNickeau            $rightChildNodes = $rightNode->childNodes;
26504fd306cSNickeau            $rightChildNodesCount = $rightChildNodes->length;
26604fd306cSNickeau            if ($rightChildNodes == null || $rightChildNodesCount == 0) {
26704fd306cSNickeau                $firstNode = $leftNode->childNodes->item(0);
26804fd306cSNickeau                $firstNodeName = $firstNode->nodeName;
26904fd306cSNickeau                $firstValue = $firstNode->nodeValue;
27004fd306cSNickeau                $error .= "The left node (" . $leftNode->getNodePath() . ") have child nodes while the right has not (First Left Node: $firstNodeName, value: $firstValue) \n";
27104fd306cSNickeau            } else {
27204fd306cSNickeau                $leftChildNodeCount = $leftNode->childNodes->length;
27304fd306cSNickeau                $leftChildIndex = 0;
27404fd306cSNickeau                $rightChildIndex = 0;
27504fd306cSNickeau                while ($leftChildIndex < $leftChildNodeCount && $rightChildIndex < $rightChildNodesCount) {
27604fd306cSNickeau
27704fd306cSNickeau                    $leftChildNode = $leftNode->childNodes->item($leftChildIndex);
27804fd306cSNickeau                    if ($leftChildNode->nodeName == "#text") {
27904fd306cSNickeau                        $leftChildNodeValue = trim($leftChildNode->nodeValue);
28004fd306cSNickeau                        if (empty(trim($leftChildNodeValue))) {
28104fd306cSNickeau                            $leftChildIndex++;
28204fd306cSNickeau                            $leftChildNode = $leftNode->childNodes->item($leftChildIndex);
28304fd306cSNickeau                        }
28404fd306cSNickeau                    }
28504fd306cSNickeau
28604fd306cSNickeau                    $rightChildNode = $rightChildNodes->item($rightChildIndex);
28704fd306cSNickeau                    if ($rightChildNode->nodeName == "#text") {
28804fd306cSNickeau                        $leftChildNodeValue = trim($rightChildNode->nodeValue);
28904fd306cSNickeau                        if (empty(trim($leftChildNodeValue))) {
29004fd306cSNickeau                            $rightChildIndex++;
29104fd306cSNickeau                            $rightChildNode = $rightChildNodes->item($rightChildIndex);
29204fd306cSNickeau                        }
29304fd306cSNickeau                    }
29404fd306cSNickeau
29504fd306cSNickeau                    if ($rightChildNode != null) {
29604fd306cSNickeau                        if ($leftChildNode != null) {
29704fd306cSNickeau                            self::diffNode($leftChildNode, $rightChildNode, $error, $excludedAttributes);
29804fd306cSNickeau                        } else {
29904fd306cSNickeau                            $error .= "The right node (" . $rightChildNode->getNodePath() . ") does not exist in the left document.\n";
30004fd306cSNickeau                        }
30104fd306cSNickeau                    } else {
30204fd306cSNickeau                        if ($leftChildNode != null) {
30304fd306cSNickeau                            $error .= "The left node (" . $leftChildNode->getNodePath() . ") does not exist in the right document.\n";
30404fd306cSNickeau                        }
30504fd306cSNickeau                    }
30604fd306cSNickeau
30704fd306cSNickeau                    /**
30804fd306cSNickeau                     * 0 based index
30904fd306cSNickeau                     */
31004fd306cSNickeau                    $leftChildIndex++;
31104fd306cSNickeau                    $rightChildIndex++;
31204fd306cSNickeau                }
31304fd306cSNickeau            }
31404fd306cSNickeau        }
31504fd306cSNickeau
31604fd306cSNickeau    }
31704fd306cSNickeau
31804fd306cSNickeau    /**
31904fd306cSNickeau     * Return a diff
32004fd306cSNickeau     * @param string $left
32104fd306cSNickeau     * @param string $right
32204fd306cSNickeau     * @return string
32304fd306cSNickeau     * DOMDocument supports formatted XML while SimpleXMLElement does not.
32404fd306cSNickeau     * @throws ExceptionCompile
32504fd306cSNickeau     */
32604fd306cSNickeau    public
32704fd306cSNickeau    static function diffMarkup(string $left, string $right): string
32804fd306cSNickeau    {
32904fd306cSNickeau        if (empty($right)) {
33004fd306cSNickeau            throw new \RuntimeException("The right text should not be empty");
33104fd306cSNickeau        }
33204fd306cSNickeau        $leftDocument = new XmlDocument($left);
33304fd306cSNickeau
33404fd306cSNickeau        if (empty($left)) {
33504fd306cSNickeau            throw new \RuntimeException("The left text should not be empty");
33604fd306cSNickeau        }
33704fd306cSNickeau        $rightDocument = new XmlDocument($right);
33804fd306cSNickeau
33904fd306cSNickeau        return $leftDocument->diff($rightDocument);
34004fd306cSNickeau
34104fd306cSNickeau    }
34204fd306cSNickeau
34304fd306cSNickeau    public static function deleteAllElementsByName(string $elementName, XmlDocument $xmlDocument)
34404fd306cSNickeau    {
34504fd306cSNickeau        $xpathQuery = "//*[local-name()='$elementName']";
34604fd306cSNickeau        try {
34704fd306cSNickeau            $svgElement = $xmlDocument->xpath($xpathQuery);
34804fd306cSNickeau        } catch (ExceptionBadSyntax $e) {
34904fd306cSNickeau            // should not happen on prod
35004fd306cSNickeau            throw new ExceptionRuntime("xpath query error ($xpathQuery");
35104fd306cSNickeau        }
35204fd306cSNickeau        for ($i = 0; $i < $svgElement->length; $i++) {
35304fd306cSNickeau            $nodeElement = XmlElement::create($svgElement[$i], $xmlDocument);
35404fd306cSNickeau            $nodeElement->remove();
35504fd306cSNickeau        }
35604fd306cSNickeau    }
35704fd306cSNickeau
35804fd306cSNickeau
35904fd306cSNickeau}
360