From d8957cc0fea19b098d799a0c438a73504e7b326c Mon Sep 17 00:00:00 2001 From: HorstOeko Date: Thu, 21 Mar 2024 05:28:54 +0100 Subject: [PATCH] Added XSD Validation (new class OrderXsdValidator) --- examples/OrderBuilderComfortSimple.php | 8 +- examples/OrderXsdValidation.php | 29 ++++ src/OrderSettings.php | 10 ++ src/OrderXsdValidator.php | 210 +++++++++++++++++++++++++ 4 files changed, 253 insertions(+), 4 deletions(-) create mode 100644 examples/OrderXsdValidation.php create mode 100644 src/OrderXsdValidator.php diff --git a/examples/OrderBuilderComfortSimple.php b/examples/OrderBuilderComfortSimple.php index 8c49bae..f76aec8 100644 --- a/examples/OrderBuilderComfortSimple.php +++ b/examples/OrderBuilderComfortSimple.php @@ -95,7 +95,7 @@ ->addDocumentAllowanceCharge(31.00, false, "S", "VAT", 20, null, 10.00, 310.00, null, null, "64", "SPECIAL AGREEMENT") ->addDocumentAllowanceCharge(21.00, true, "S", "VAT", 20, null, 10.00, 210.00, null, null, "FC", "FREIGHT SERVICES") ->setDocumentSummation(310, 360, 21, 31, 300, 60) - ->setDocumentReceivableSpecifiedTradeAccountingAccount("BUYER_ACCOUNT_REF", "BUYER_ACCOUNT_REF_TYPE") + ->setDocumentReceivableSpecifiedTradeAccountingAccount("4711", "1") ->addDocumentTax("S", "VAT", 300.00, 60.00, 20.00, "ExcReason-1", "ExcReasonCode-1", 300.00, 300.00, null) @@ -129,7 +129,7 @@ ->addDocumentPositionAllowanceCharge(6.00, false, 10.0, 60.0, "64", "SPECIAL AGREEMENT") ->addDocumentPositionAllowanceCharge(6.00, true, 10.0, 60.0, "FC", "FREIGHT SERVICES") ->setDocumentPositionLineSummation(60.0) - ->setDocumentPositionReceivableTradeAccountingAccount("BUYER_ACCOUNTING_REF") + ->setDocumentPositionReceivableTradeAccountingAccount("4712") ->setDocumentPositionTax("S", "VAT", 19.0, 0.00, "Reason-1", "RC1") ->setDocumentPositionUltimateCustomerOrderReferencedDocument("ULTCUSTORDEREF-1", "1", $dt) @@ -160,7 +160,7 @@ ->addDocumentPositionAllowanceCharge(1.00, false, 1.0, 100.0, "64", "SPECIAL AGREEMENT") ->addDocumentPositionAllowanceCharge(1.00, true, 1.0, 100.0, "FC", "FREIGHT SERVICES") ->setDocumentPositionLineSummation(100.0) - ->setDocumentPositionReceivableTradeAccountingAccount("BUYER_ACCOUNTING_REF") + ->setDocumentPositionReceivableTradeAccountingAccount("4713") ->setDocumentPositionUltimateCustomerOrderReferencedDocument("ULTCUSTORDEREF-1", "2", $dt) ->addNewPosition("3") @@ -190,7 +190,7 @@ ->addDocumentPositionAllowanceCharge(15.00, false, 10.0, 150.0, "64", "SPECIAL AGREEMENT") ->addDocumentPositionAllowanceCharge(15.00, true, 10.0, 150.0, "FC", "FREIGHT SERVICES") ->setDocumentPositionLineSummation(150.0) - ->setDocumentPositionReceivableTradeAccountingAccount("BUYER_ACCOUNTING_REF") + ->setDocumentPositionReceivableTradeAccountingAccount("0816") ->setDocumentPositionUltimateCustomerOrderReferencedDocument("ULTCUSTORDEREF-1", "3", $dt) ->writeFile(getcwd() . "/order-x.xml"); diff --git a/examples/OrderXsdValidation.php b/examples/OrderXsdValidation.php new file mode 100644 index 0000000..56ea521 --- /dev/null +++ b/examples/OrderXsdValidation.php @@ -0,0 +1,29 @@ +validationFailed()) { + echo "\033[01;31mValidation failed\e[0m\n"; + foreach ($xsdValidator->validationErrors() as $validationError) { + echo $validationError . PHP_EOL; + } + } else { + echo "\033[01;32mValidation passed\e[0m\n"; + } +} + +/** + * Valid XML + */ + +$document = OrderDocumentReader::readAndGuessFromFile(dirname(__FILE__) . "/order-x.xml"); + +$xsdValidator = new OrderXsdValidator($document); +$xsdValidator->validate(); + +showValidationResult($xsdValidator); diff --git a/src/OrderSettings.php b/src/OrderSettings.php index 8eab18a..22bcb51 100644 --- a/src/OrderSettings.php +++ b/src/OrderSettings.php @@ -268,6 +268,16 @@ public static function getValidationDirectory(): string return PathUtils::combineAllPaths(static::getSourceDirectory(), "validation"); } + /** + * Get the directory where all the schema (XSD) files are located + * + * @return string + */ + public static function getSchemaDirectory(): string + { + return PathUtils::combineAllPaths(static::getSourceDirectory(), "schema"); + } + /** * Get the full filename of the ICC profile to use * diff --git a/src/OrderXsdValidator.php b/src/OrderXsdValidator.php new file mode 100644 index 0000000..d20fa81 --- /dev/null +++ b/src/OrderXsdValidator.php @@ -0,0 +1,210 @@ + + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/horstoeko/orderx + */ +class OrderXsdValidator +{ + /** + * The order document reference + * + * @var OrderDocument + */ + private $document; + + /** + * Internal error bag + * + * @var array + */ + private $errorBag = []; + + /** + * Constructor + * + * @codeCoverageIgnore + * @param OrderDocument $document + */ + public function __construct(OrderDocument $document) + { + $this->document = $document; + } + + /** + * Perform validation of document + * + * @return OrderXsdValidator + */ + public function validate(): OrderXsdValidator + { + $this->clearErrorBag(); + $this->initLibXml(); + + try { + if (!$this->getDocumentContentAsDomDocument()->schemaValidate($this->getDocumentXsdFilename())) { + $this->pushLibXmlErrorsToErrorBag(); + } + } catch (Exception $exception) { + $this->addToErrorBag($exception); + } finally { + $this->finalizeLibXml(); + } + + return $this; + } + + /** + * Returns true if validation passed otherwise false + * + * @return boolean + */ + public function validationPased(): bool + { + return empty($this->errorBag); + } + + /** + * Returns true if validation failed otherwise false + * + * @return boolean + */ + public function validationFailed(): bool + { + return !$this->validationPased(); + } + + /** + * Returns an array of all validation errors + * + * @return array + */ + public function validationErrors(): array + { + return $this->errorBag; + } + + /** + * Initialize LibXML + * + * @return void + */ + private function initLibXml(): void + { + libxml_use_internal_errors(true); + } + + /** + * Finalize LibXML + * + * @return void + */ + private function finalizeLibXml(): void + { + libxml_clear_errors(); + libxml_use_internal_errors(false); + } + + /** + * Get the content of the document + * + * @return string + */ + private function getDocumentContent(): string + { + return $this->document->serializeAsXml(); + } + + /** + * Get the content of the document as a DOMDocument + * + * @return DOMDocument + */ + private function getDocumentContentAsDomDocument(): DOMDocument + { + $doc = new DOMDocument(); + $doc->loadXML($this->getDocumentContent()); + + return $doc; + } + + /** + * Get the XSD file (schema definition) for the document + * + * @return string + */ + private function getDocumentXsdFilename(): string + { + $xsdFilename = PathUtils::combineAllPaths( + OrderSettings::getSchemaDirectory(), + $this->document->getProfileDefinitionParameter('name'), + $this->document->getProfileDefinitionParameter('xsdfilename') + ); + + if (!file_exists($xsdFilename)) { + throw new Exception(sprintf("XSD file '%s' not found", $xsdFilename)); + } + + return $xsdFilename; + } + + /** + * Clear the internal error bag + * + * @return void + */ + private function clearErrorBag(): void + { + $this->errorBag = []; + } + + /** + * Add message to error bag + * + * @param string|Exception|LibXMLError $error + * @return void + */ + private function addToErrorBag($error): void + { + if (is_string($error)) { + $this->errorBag[] = $error; + } elseif ($error instanceof Exception) { + $this->errorBag[] = $error->getMessage(); + } elseif ($error instanceof LibXMLError) { + $this->errorBag[] = sprintf('[line %d] %s : %s', $error->line, $error->code, $error->message); + } + } + + /** + * Pushes validation errors to error bag + * + * @return void + */ + private function pushLibXmlErrorsToErrorBag(): void + { + foreach (libxml_get_errors() as $xmlError) { + $this->addToErrorBag($xmlError); + } + } +}