Skip to content

Commit

Permalink
Added new ZugferdXsdValidator
Browse files Browse the repository at this point in the history
  • Loading branch information
HorstOeko committed Mar 11, 2024
1 parent 56bc31d commit 0d15c30
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 40 deletions.
30 changes: 30 additions & 0 deletions examples/InvoiceXsdInvalid.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" xmlns:a="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:10" xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<rsm:ExchangedDocumentContext>
<ram:GuidelineSpecifiedDocumentContextParameter>
<ram:ID>urn:cen.eu:en16931:2017</ram:ID>
</ram:GuidelineSpecifiedDocumentContextParameter>
</rsm:ExchangedDocumentContext>
<rsm:ExchangedDocument>
<ram:ID>471102</ram:ID>
<ram:TypeCode>380</ram:TypeCode>
<ram:IssueDateTime>
<udt:DateTimeString format="102">20180305</udt:DateTimeString>
</ram:IssueDateTime>
<ram:IncludedNote>
<ram:Content>Rechnung gemäß Bestellung vom 01.03.2018.</ram:Content>
</ram:IncludedNote>
<ram:IncludedNote>
<ram:Content>Lieferant GmbH
Lieferantenstraße 20
80333 München
Deutschland
Geschäftsführer: Hans Muster
Handelsregisternummer: H A 123
</ram:Content>
<ram:SubjectCode>REG</ram:SubjectCode>
</ram:IncludedNote>
</rsm:ExchangedDocument>
<rsm:SupplyChainTradeTransaction>
</rsm:SupplyChainTradeTransaction>
</rsm:CrossIndustryInvoice>
41 changes: 41 additions & 0 deletions examples/XsdValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

use horstoeko\zugferd\ZugferdXsdValidator;
use horstoeko\zugferd\ZugferdDocumentReader;
use horstoeko\zugferd\ZugferdDocumentPdfReader;

require dirname(__FILE__) . "/../vendor/autoload.php";

function showValidationResult($xsdValidator)
{
if ($xsdValidator->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";
}
}

/**
* Invalid XML
*/

$document = ZugferdDocumentReader::readAndGuessFromFile(dirname(__FILE__) . "/InvoiceXsdInvalid.xml");

$xsdValidator = new ZugferdXsdValidator($document);
$xsdValidator->validate();

showValidationResult($xsdValidator);

/**
* Valid XML
*/

$document = ZugferdDocumentPdfReader::readAndGuessFromFile(dirname(__FILE__) . "/invoice_1.pdf");

$xsdValidator = new ZugferdXsdValidator($document);
$xsdValidator->validate();

showValidationResult($xsdValidator);
10 changes: 10 additions & 0 deletions src/ZugferdSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
40 changes: 0 additions & 40 deletions src/ZugferdValidator.php

This file was deleted.

209 changes: 209 additions & 0 deletions src/ZugferdXsdValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
<?php

/**
* This file is a part of horstoeko/zugferd.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace horstoeko\zugferd;

use Exception;
use DOMDocument;
use LibXMLError;
use horstoeko\stringmanagement\PathUtils;
use horstoeko\zugferd\ZugferdDocument;
use horstoeko\zugferd\ZugferdSettings;

/**
* Class representing the validator against XSD for documents
*
* @category Zugferd
* @package Zugferd
* @author D. Erling <horstoeko@erling.com.de>
* @license https://opensource.org/licenses/MIT MIT
* @link https://github.com/horstoeko/zugferd
*/
class ZugferdXsdValidator
{
/**
* The invoice document reference
*
* @var ZugferdDocument
*/
private $document;

/**
* Internal error bag
*
* @var array
*/
private $errorBag = [];

/**
* Constructor
*
* @codeCoverageIgnore
* @param ZugferdDocument $document
*/
public function __construct(ZugferdDocument $document)
{
$this->document = $document;
}

/**
* Perform validation of document
*
* @return ZugferdXsdValidator
*/
public function validate(): ZugferdXsdValidator
{
$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(
ZugferdSettings::getSchemaDirectory(),
$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);
}
}
}

0 comments on commit 0d15c30

Please sign in to comment.