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