From 89e5f9ee0083e1f3ebdaefe83b22743a2b6fa898 Mon Sep 17 00:00:00 2001 From: Quentin Dequippe Date: Thu, 19 Feb 2026 18:17:11 +0100 Subject: [PATCH 1/6] feat: Schematron validation using Saxon Java --- .github/workflows/tests.yml | 26 + Dockerfile | 36 + composer.json | 3 +- docs/.gitkeep | 0 docs/SaxonJarSchematronValidator.md | 81 + .../SaxonJarSchematronValidator.php | 108 + .../SchematronValidatorInterface.php | 17 + src/Schematron/ValidationError.php | 57 + src/Schematron/ValidationFailedException.php | 20 + .../SaxonJarSchematronValidatorTest.php | 49 + .../UC1_F202500003_01-CDV-200_Deposee.xml | 124 +- ...60216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl | 1754 +++++++++++++++++ 12 files changed, 2214 insertions(+), 61 deletions(-) create mode 100644 Dockerfile delete mode 100644 docs/.gitkeep create mode 100644 docs/SaxonJarSchematronValidator.md create mode 100644 src/Schematron/SaxonJarSchematronValidator.php create mode 100644 src/Schematron/SchematronValidatorInterface.php create mode 100644 src/Schematron/ValidationError.php create mode 100644 src/Schematron/ValidationFailedException.php create mode 100644 tests/Schematron/SaxonJarSchematronValidatorTest.php create mode 100644 xslt/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f9f8342..8d87bee 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,6 +21,32 @@ jobs: with: php-version: ${{ matrix.php-versions }} + - name: Cache Saxon HE + uses: actions/cache@v5 + id: saxon-cache + with: + path: saxon + key: saxon-he-12.9 + + - name: Download Saxon HE 12.9 + id: saxon_download + if: steps.saxon-cache.outputs.cache-hit != 'true' + run: | + SAXON_ZIP_URL="https://github.com/Saxonica/Saxon-HE/releases/download/SaxonHE12-9/SaxonHE12-9J.zip" + curl -L -o saxon.zip "$SAXON_ZIP_URL" + unzip -q saxon.zip -d saxon + rm saxon.zip + + - name: Set Saxon JAR path + run: | + SAXON_JAR=$(find saxon -type f -name "saxon-he-12.9.jar" | head -n 1) + if [ -z "$SAXON_JAR" ]; then + echo "⚠️ JAR Saxon not found!" + exit 1 + fi + echo "SAXON_JAR=$SAXON_JAR" >> $GITHUB_ENV + echo "Saxon JAR path: $SAXON_JAR" + - uses: "ramsey/composer-install@v3" - name: Run test suite diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5d072e6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +# Use PHP 8.3 CLI as base image +FROM php:8.3-cli + +# Install system dependencies and PHP extensions +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + git \ + unzip \ + curl \ + ca-certificates \ + libzip-dev \ + libicu-dev \ + libxml2-dev \ + default-jre \ + wget \ + && docker-php-ext-install intl zip dom xml \ + && rm -rf /var/lib/apt/lists/* + +# Install Composer +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +# Set working directory +WORKDIR /app + +# Install Saxon Home edition for Java +RUN wget -nv "https://github.com/Saxonica/Saxon-HE/releases/download/SaxonHE12-9/SaxonHE12-9J.zip" -O /tmp/saxon.zip && \ + unzip /tmp/saxon.zip -d /opt/saxon && \ + rm /tmp/saxon.zip + +ENV PATH="/opt/saxon/bin:${PATH}" + +ENV SAXON_JAR="/opt/saxon/saxon-he-12.9.jar" + + + + diff --git a/composer.json b/composer.json index b08f00c..0789956 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "friendsofphp/php-cs-fixer": "^3.93", "phpunit/phpunit": "^12.5", "ext-libxml": "*", - "ext-dom": "*" + "ext-dom": "*", + "symfony/process": "^7.4" }, "scripts": { "cs-fix": "php-cs-fixer fix", diff --git a/docs/.gitkeep b/docs/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/docs/SaxonJarSchematronValidator.md b/docs/SaxonJarSchematronValidator.md new file mode 100644 index 0000000..01c7d3b --- /dev/null +++ b/docs/SaxonJarSchematronValidator.md @@ -0,0 +1,81 @@ +# SaxonJarSchematronValidator + +The `SaxonJarSchematronValidator` is a Schematron validator implementation that uses the Saxon XSLT processor (Java edition) to validate XML documents against Schematron rules compiled as XSLT stylesheets. + +## Overview + +Schematron is a rule-based validation language for XML documents. Unlike XSD (XML Schema Definition), which validates structure, Schematron validates business rules and constraints using XPath expressions. + +In this implementation: +1. Schematron rules are pre-compiled into an XSLT stylesheet +2. Saxon XSLT processor transforms the XML document using the XSLT rules +3. The output is an SVRL (Schematron Validation Report Language) document +4. Failed assertions are extracted and reported as validation errors + +## Requirements + +### System Requirements + +- **Java Runtime Environment (JRE)** 8 or higher + - The `java` command must be available in your system PATH + - Check with: `java -version` + +- **Saxon-HE JAR file** (Home Edition, version 9.x or higher) + - Download from: [Saxonica Downloads](https://www.saxonica.com/download/java.xml) + +### PHP Requirements + +- **PHP 8.3** or higher +- **Required PHP extensions:** + - `dom` - for parsing SVRL XML output + - `libxml` - for XML handling + - `mbstring` - for string operations + +### Composer Dependencies + +```bash +composer require symfony/process +``` + +The `symfony/process` component is used to execute the Java Saxon processor as a subprocess. + +## Usage + +### Basic Usage + +```php +validate( + xmlFilepath: '/path/to/document.xml', + xsltFilepath: '/path/to/schematron-rules.xsl' + ); + + echo "Validation successful!\n"; + +} catch (ValidationFailedException $e) { + echo "Validation failed: " . $e->getMessage() . "\n"; + + // Access validation errors + foreach ($e->errors as $error) { + echo sprintf( + "Error [%s]: %s\n Location: %s\n Test: %s\n", + $error->getId(), + $error->getText(), + $error->getLocation(), + $error->getTest() + ); + } +} +``` + diff --git a/src/Schematron/SaxonJarSchematronValidator.php b/src/Schematron/SaxonJarSchematronValidator.php new file mode 100644 index 0000000..dc10e93 --- /dev/null +++ b/src/Schematron/SaxonJarSchematronValidator.php @@ -0,0 +1,108 @@ +saxonJar, + '-s:'.$xmlFilepath, + '-xsl:'.$xsltFilepath, + ]); + + $process->setTimeout(3600); + $process->run(); + + if (false === $process->isSuccessful()) { + throw new ValidationFailedException( + message: $process->getErrorOutput(), + code: $process->getExitCode() ?? 1, + ); + } + + $output = mb_trim($process->getOutput()); + + $doc = new \DOMDocument(); + + try { + if (false === $doc->loadXML($output)) { + throw new ValidationFailedException( + message: 'Failed to parse Schematron validation output: invalid XML.', + ); + } + } catch (\Throwable $throwable) { + throw new ValidationFailedException( + message: 'Failed to parse Schematron validation output: '.$throwable->getMessage(), + previous: $throwable, + ); + } + + $xpath = new \DOMXPath($doc); + $xpath->registerNamespace('svrl', 'http://purl.oclc.org/dsdl/svrl'); + + $failedAsserts = $xpath->query('//svrl:failed-assert'); + + if (false === $failedAsserts) { + throw new ValidationFailedException( + message: 'Failed to parse Schematron validation output', + ); + } + + $errors = []; + + /** @var \DOMNode $fa */ + foreach ($failedAsserts as $fa) { + if (!$fa instanceof \DOMElement) { + continue; + } + + $location = $fa->getAttribute('location'); + $test = $fa->getAttribute('test'); + + $text = null; + $textElements = $fa->getElementsByTagName('text'); + if (0 !== $textElements->length && null !== $textElements->item(0)) { + $text = $textElements->item(0)->nodeValue; + } + + $errors[] = new ValidationError( + test: $test, + id: $fa->getAttribute('id'), + flag: $fa->getAttribute('flag'), + location: $location, + text: $text, + ); + } + + if ([] === $errors) { + return; + } + + throw new ValidationFailedException( + errors: $errors, + message: 'Schematron validation failed', + ); + } +} \ No newline at end of file diff --git a/src/Schematron/SchematronValidatorInterface.php b/src/Schematron/SchematronValidatorInterface.php new file mode 100644 index 0000000..835f367 --- /dev/null +++ b/src/Schematron/SchematronValidatorInterface.php @@ -0,0 +1,17 @@ +test = $test; + $this->id = $id; + $this->flag = $flag; + $this->location = $location; + $this->text = $text; + } + + public function getTest(): string + { + return $this->test; + } + + public function getId(): string + { + return $this->id; + } + + public function getFlag(): string + { + return $this->flag; + } + + public function getLocation(): string + { + return $this->location; + } + + public function getText(): ?string + { + return $this->text; + } +} diff --git a/src/Schematron/ValidationFailedException.php b/src/Schematron/ValidationFailedException.php new file mode 100644 index 0000000..1ef038c --- /dev/null +++ b/src/Schematron/ValidationFailedException.php @@ -0,0 +1,20 @@ +expectNotToPerformAssertions(); + + // Act + $validator->validate( + xmlFilepath: __DIR__ . '/../data/UC1_F202500003_01-CDV-200_Deposee.xml', + xsltFilepath: __DIR__ . '/../../xslt/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl', + ); + } + + public function testValidateWithErrors(): void + { + // Arrange + $validator = new SaxonJarSchematronValidator(saxonJar: getenv('SAXON_JAR')); + + // Act + try { + $validator->validate( + xmlFilepath: __DIR__ . '/../data/invalid_cdar.xml', + xsltFilepath: __DIR__ . '/../../xslt/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl', + ); + } catch (ValidationFailedException $validationFailedException) { + $this->assertCount(4, $validationFailedException->errors); + $error1 = $validationFailedException->errors[0]; + $this->assertStringContainsString('L\'identifiant du document (ram:ID) est obligatoire', $error1->getText()); + $error2 = $validationFailedException->errors[1]; + $this->assertStringContainsString('Lorsque le rôle du destinataire (MDT-59) est différent de "WK" ou "DFH"', $error2->getText()); + } + } +} \ No newline at end of file diff --git a/tests/data/UC1_F202500003_01-CDV-200_Deposee.xml b/tests/data/UC1_F202500003_01-CDV-200_Deposee.xml index 168227e..c1a0d14 100644 --- a/tests/data/UC1_F202500003_01-CDV-200_Deposee.xml +++ b/tests/data/UC1_F202500003_01-CDV-200_Deposee.xml @@ -1,63 +1,67 @@  - - - REGULATED - - - urn.cpro.gouv.fr:1p0:CDV:invoice - - - - UC1_F202500003_01-CDV-200_Déposée - - 20250701151500 - - - WK - - - WK - - - 100000009 - VENDEUR - SE - - - 9998 - PPF - DFH - - - - - false - - 305 - - 20250701151000 - - - F202500003 - 10 - 380 - - 20250701151000 - - - 20250701 - - 200 - Déposée - - 100000009 - - - +xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" + xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" + xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" + xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossDomainAcknowledgementAndResponse:100" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + + + REGULATED + + + urn.cpro.gouv.fr:1p0:CDV:invoice + + + + F202500003_200_20250701151000#380_20250701 + UC1_F202500003_01-CDV-200_Deposee + + 20250701151500 + + + WK + + + WK + + + 100000009 + VENDEUR + SE + + 100000009_STATUTS + + + + 9998 + PPF + DFH + + + + + false + + 305 + + 20250701151000 + + + F202500003 + 10 + 380 + + 20250701151000 + + + 20250701 + + 200 + Déposée + + 100000009 + + + \ No newline at end of file diff --git a/xslt/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl b/xslt/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl new file mode 100644 index 0000000..d921472 --- /dev/null +++ b/xslt/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl @@ -0,0 +1,1754 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + / + + + + + + *: + + [namespace-uri()=' + + '] + + + + [ + + ] + + + + / + + @ + + + @*[local-name()=' + + ' and namespace-uri()=' + + '] + + + + + + + + / + + + [ + + ] + + + + /@ + + + + + + + / + + + [ + + ] + + + + /@ + + + + + + + + + + + + + + + + + + + + + + + . + + + + U + + U + + + + U. + + n + + + + U. + + _ + + _ + + + + + + + + +   +   +   + + + + + + + + + + + + BR-FR-04 + BR-FR-04 — Validation des codes de type de document + + + + + + + + BR-FR-CDV-01 + BR-FR-CDV-01 — Présence obligatoire de MDG-3 + + + + + + + + BR-FR-CDV-02 + BR-FR-CDV-02 — Vérification de la valeur de MDT-3 + + + + + + + + BR-FR-CDV-03 + BR-FR-CDV-03 — Présence obligatoire de MDT-4 + + + + + + + + BR-FR-CDV-04 + BR-FR-CDV-04 — Présence obligatoire de MDG-4 + + + + + + + + BR-FR-CDV-05 + BR-FR-CDV-05 — Présence obligatoire de MDG-9 + + + + + + + + BR-FR-CDV-06 + BR-FR-CDV-06 — Présence obligatoire de MDT-21 + + + + + + + + BR-FR-CDV-07 + BR-FR-CDV-07 — Validation conditionnelle de MDT-38 selon MDT-77 + + + + + + + + BR-FR-CDV-08 + BR-FR-CDV-08 — Obligation conditionnelle de MDT-73 + + + + + + + + BR-FR-CDV-09 + BR-FR-CDV-09 — Présence et valeur de MDT-77 + + + + + + + + BR-FR-CDV-10 + BR-FR-CDV-10 — Présence obligatoire de MDT-87 + + + + + + + + BR-FR-CDV-11 + BR-FR-CDV-11 — Présence obligatoire de MDG-35 + + + + + + + + BR-FR-CDV-12 + BR-FR-CDV-12 — Présence obligatoire de MDT-105 + + + + + + + + BR-FR-CDV-13 + BR-FR-CDV-13 — Présence obligatoire de MDT-129 + + + + + + + + BR-FR-CDV-14 + BR-FR-CDV-14 — Vérification des caractéristiques en cas de statut "Encaissé" + + + + + + + + BR-FR-CDV-15 + BR-FR-CDV-14 — Vérification des caractéristiques en cas de statut "Encaissé" + + + + + + + + BR-FR-CDV-CL-01 + BR-FR-CDV-CL-01 — Liste fermée de valeurs pour MDT-2 + + + + + + + + BR-FR-CDV-CL-02 + BR-FR-CDV-CL-02 — Contrôle de cohérence entre MDT-77 et MDT-21 + + + + + + + + BR-FR-CDV-CL-03 + BR-FR-CDV-CL-03 — Contrôle de cohérence entre MDT-77 et MDT-40 + + + + + + + + BR-FR-CDV-CL-04 + BR-FR-CDV-CL-04 — Liste fermée de valeurs pour MDT-59 + + + + + + + + BR-FR-CDV-CL-05 + BR-FR-CDV-CL-05 — Contrôle des statuts MDT-88 selon MDT-77 + + + + + + + + BR-FR-CDV-CL-06 + BR-FR-CDV-CL-06 — Liste fermée de codes statuts de facture + + + + + + + + BR-FR-CDV-CL-07 + BR-FR-CDV-CL-07 — Vérification de la valeur de MDT-132 + + + + + + + + BR-FR-CDV-CL-08 + BR-FR-CDV-CL-08 — Liste fermée de valeurs pour MDT-158 + + + + + + + + BR-FR-CDV-CL-09 + BR-FR-CDV-CL-09 — Liste fermée de codes motifs de statuts + + + + + + + + BR-FR-CDV-CL-10 + BR-FR-CDV-CL-10 — Liste fermée de codes actions de facture + + + + + + + + BR-FR-CDV-CL-11 + BR-FR-CDV-CL-11 — Liste fermée de codes pour MDT-207 + + + + + + + + + BR-FR-04 — Validation des codes de type de document + + + + + + + + + BR-FR-04_MDT-91 + warning + + + + + [BR-FR-04/MDT-91] : Le code de type de document " + + " n'est pas autorisé selon les spécifications françaises. + Veuillez utiliser un code parmi ceux définis dans la documentation (ex. : 380, 389, 393, etc.). + + + + + + + + + + + + BR-FR-CDV-01 — Présence obligatoire de MDG-3 + + + + + + + + + BR-FR-CDV-01_MDG-3 + warning + + + + + [BR-FR-CDV-01/MDG-3] : Le paramètre de contexte MDG-3 est obligatoire dans le document. + Veuillez vous assurer que l'élément ram:GuidelineSpecifiedDocumentContextParameter est bien présent et correctement renseigné. + + + + + + + + + + + + BR-FR-CDV-02 — Vérification de la valeur de MDT-3 + + + + + + + + + BR-FR-CDV-02_MDT-3 + warning + + + + + [BR-FR-CDV-02/MDT-3] : La valeur de MDT-3 doit être : + - "urn.cpro.gouv.fr:1p0:CDV:invoice", ou + - "urn.cpro.gouv.fr:1p0:CDV:einvoicingF2" **uniquement si** il y a un unique Destinataire (Recipent) et que c'est le PPF : GlobalID = 9998 avec @shemeId = 0238 et CodeRole = DFH. + Valeurs actuelles : " + + ". Nombre de Recipient : " + + " - GlobalID : " + + " - @shemeID : " + + " - CodeRole : " + + " + Veuillez corriger cette valeur pour respecter les spécifications du format CDV. + + + + + + + + + + + + BR-FR-CDV-03 — Présence obligatoire de MDT-4 + + + + + + + + + BR-FR-CDV-03_MDT-4 + warning + + + + + [BR-FR-CDV-03/MDT-4] : L'identifiant du document (ram:ID) est obligatoire. + Veuillez vous assurer que l'élément "ram:ID" est bien présent dans "rsm:ExchangedDocument". + + + + + + + + + + + + BR-FR-CDV-04 — Présence obligatoire de MDG-4 + + + + + + + + + BR-FR-CDV-04_MDG-4 + warning + + + + + [BR-FR-CDV-04/MDG-4] : La date d’émission du document (MDG-4) est obligatoire. + Veuillez vous assurer que l’élément "ram:IssueDateTime" est bien présent dans "rsm:ExchangedDocument". + + + + + + + + + + + + BR-FR-CDV-05 — Présence obligatoire de MDG-9 + + + + + + + + + BR-FR-CDV-05_MDG-9 + warning + + + + + [BR-FR-CDV-05/MDG-9] : Le partenaire commercial émetteur (MDG-9) est obligatoire. + Veuillez vous assurer que l’élément "ram:SenderTradeParty" est bien présent dans "rsm:ExchangedDocument". + + + + + + + + + + + + BR-FR-CDV-06 — Présence obligatoire de MDT-21 + + + + + + + + + BR-FR-CDV-06_MDT-21 + warning + + + + + [BR-FR-CDV-06/MDT-21] : Le rôle du partenaire commercial émetteur (MDT-21) est obligatoire. + Veuillez vous assurer que l’élément "ram:RoleCode" est bien présent dans "rsm:ExchangedDocument/ram:SenderTradeParty". + + + + + + + + + + + + BR-FR-CDV-07 — Validation conditionnelle de MDT-38 selon MDT-77 + + + + + + + + + BR-FR-CDV-07_MDT-38_yes + warning + + + + + [BR-FR-CDV-07/MDT-38] : Lorsque le rôle du partenaire commercial émetteur (MDT-77) est égal à "23", l'identifiant (MDT-38) doit être renseigné. + Veuillez vous assurer que l'élément "ram:GlobalID" est présent dans "ram:IssuerTradeParty". + + + + + + + + + + + + + + + BR-FR-CDV-07_MDT-38_no + warning + + + + + [BR-FR-CDV-07/MDT-38] : Lorsque le rôle du partenaire commercial émetteur (MDT-77) est égal à "305", l'identifiant (MDT-38) ne doit pas être renseigné. + Veuillez retirer l'élément "ram:GlobalID" de "ram:IssuerTradeParty" dans ce cas. + + + + + + + + + + + + BR-FR-CDV-08 — Obligation conditionnelle de MDT-73 + + + + + + + + + BR-FR-CDV-08_MDT-73 + warning + + + + + [BR-FR-CDV-08/MDT-73] : Lorsque le rôle du destinataire (MDT-59) est différent de "WK" ou "DFH", l'adresse électronique du destinataire (MDT-73) est obligatoire. + Veuillez vous assurer que l’élément "ram:URIID" est bien présent dans "ram:URIUniversalCommunication". + + + + + + + + + + + + BR-FR-CDV-09 — Présence et valeur de MDT-77 + + + + + + + + + BR-FR-CDV-09_MDT-77 + warning + + + + + [BR-FR-CDV-09/MDT-77] : Le code de type de document (MDT-77) est obligatoire et doit être égal à "23" ou "305". + Valeur actuelle : " + + ". Veuillez corriger cette valeur si nécessaire. + + + + + + + + + + + + BR-FR-CDV-10 — Présence obligatoire de MDT-87 + + + + + + + + + BR-FR-CDV-10_MDT-87 + warning + + + + + [BR-FR-CDV-10/MDT-87] : L'identifiant de la facture référencée (MDT-87) est obligatoire. + Veuillez vous assurer que l’élément "ram:IssuerAssignedID" est bien présent dans "rsm:ReferenceReferencedDocument". + + + + + + + + + + + + BR-FR-CDV-11 — Présence obligatoire de MDG-35 + + + + + + + + + BR-FR-CDV-11_MDG-35 + warning + + + + + [BR-FR-CDV-11/MDG-35] : La date d’émission formatée de la facture référencée (MDG-35) est obligatoire, + sauf si MDT-105 (code statut) est égal à "501" (IRRECEVABLE). + + + + + + + + + + + + BR-FR-CDV-12 — Présence obligatoire de MDT-105 + + + + + + + + + BR-FR-CDV-12_MDT-105 + warning + + + + + [BR-FR-CDV-12/MDT-105] : Le code de condition de traitement (MDT-105) est obligatoire. + Veuillez vous assurer que l’élément "ram:ProcessConditionCode" est bien présent dans "rsm:ReferenceReferencedDocument". + + + + + + + + + + + + BR-FR-CDV-13 — Présence obligatoire de MDT-129 + + + + + + + + + BR-FR-CDV-13_MDT-129 + warning + + + + + [BR-FR-CDV-13/MDT-129] : L'identifiant du partenaire commercial émetteur (MDT-129) est obligatoire, + sauf si MDT-105 (ram:ID dans BusinessProcessSpecifiedDocumentContextParameter) est égal à "501". + + + + + + + + + + + + BR-FR-CDV-14 — Vérification des caractéristiques en cas de statut "Encaissé" + + + + + + + + + BR-FR-CDV-14_MDT-207 + warning + + + + + [BR-FR-CDV-14/MDT-207] : Lorsque le statut de traitement (MDT-105) est "212" (encaissé), il doit exister au moins un bloc "ram:SpecifiedDocumentCharacteristic" avec : + - un "ram:TypeCode" égal à "MEN" + - et une valeur "ram:ValueAmount" renseignée. + Veuillez vérifier la présence et le contenu de ces éléments. + + + + + + + + + + + + BR-FR-CDV-14 — Vérification des caractéristiques en cas de statut "Encaissé" + + + + + + + + + BR-FR-CDV-15_MDT-113 + warning + + + + + [BR-FR-CDV-15/MDT-113] : Code Statut : " + + " : lorsque le statut (MDT-105 ou MDT-115) est égal à 210 (Refusée), 123 (Rejetée), 501 (Irrecevable), 207 (Litige), 206 (Suspendue) pu 208 (Approuvée Partiellement), lors un MOTIF (MDT-113) DOIT être présent. + Veuillez vérifier la présence et le contenu du MOTIF (MDT-113). + + + + + + + + + + + + BR-FR-CDV-CL-01 — Liste fermée de valeurs pour MDT-2 + + + + + + + + + + BR-FR-CDV-CL-01_MDT-2 + warning + + + + + [BR-FR-CDV-CL-01/MDT-2] : La valeur de MDT-2 doit être l'une des suivantes : "REGULATED", "NON_REGULATED", "B2C", "B2BINT", "OUTOFSCOPE" sauf pour un CDV pour le PPF pourlequel le nombre de caractères DOIT être inférieur à 3. + Valeur actuelle : CDV PPF ? (true) : " + + " - Valeur MDT-2 : " + + ". Veuillez corriger cette valeur si nécessaire. + + + + + + + + + + + + BR-FR-CDV-CL-02 — Contrôle de cohérence entre MDT-77 et MDT-21 + + + + + + + + + BR-FR-CDV-CL-02_MDT-21_305 + warning + + + + + [BR-FR-CDV-CL-02/MDT-21] : Lorsque le statut (MDT-77) est "305", le rôle du partenaire commercial émetteur (MDT-21) doit être "WK". + Valeur actuelle : " + + ". + + + + + + + + + + BR-FR-CDV-CL-02_MDT-21_23 + warning + + + + + [BR-FR-CDV-CL-02/MDT-21] : Lorsque le statut (MDT-77) est "23", le rôle du partenaire commercial émetteur (MDT-21) doit être dans la liste suivante : + "BY", "AB", "DL", "SE", "SR", "WK", "PE", "PR", "II", "IV". + Valeur actuelle : " + + ". + + + + + + + + + + + + BR-FR-CDV-CL-03 — Contrôle de cohérence entre MDT-77 et MDT-40 + + + + + + + + + BR-FR-CDV-CL-03_MDT-40_305 + warning + + + + + [BR-FR-CDV-CL-03/MDT-40] : Lorsque le statut (MDT-77) est "305", le rôle du partenaire commercial émetteur (MDT-40) doit être "WK". + Valeur actuelle : " + + ". + + + + + + + + + + BR-FR-CDV-CL-03_MDT-40_23 + warning + + + + + [BR-FR-CDV-CL-03/MDT-40] : Lorsque le statut (MDT-77) est "23", le rôle du partenaire commercial émetteur (MDT-40) doit être dans la liste suivante : + "BY", "AB", "DL", "SE", "SR", "PE", "PR", "II", "IV". + Valeur actuelle : " + + ". + + + + + + + + + + + + BR-FR-CDV-CL-04 — Liste fermée de valeurs pour MDT-59 + + + + + + + + + BR-FR-CDV-CL-04_MDT-59 + warning + + + + + [BR-FR-CDV-CL-04/MDT-59] : Le rôle du partenaire commercial destinataire (MDT-59) doit être dans la liste suivante : + "BY", "AB", "DL", "SE", "SR", "PE", "PR", "II", "IV", "WK", "DFH". + Valeur actuelle : " + + ". Veuillez corriger cette valeur si nécessaire. + + + + + + + + + + + + BR-FR-CDV-CL-05 — Contrôle des statuts MDT-88 selon MDT-77 + + + + + + + + + BR-FR-CDV-CL-05_MDT-88_305 + warning + + + + + [BR-FR-CDV-CL-05/MDT-88] : Lorsque MDT-77 = "305" (Phase Transmission), siprésent, MDT-88 doit être l’un des codes suivants : + "10", "51", "43", "8", "48". + Valeur actuelle : " + + ". + + + + + + + + + + BR-FR-CDV-CL-05_MDT-88_23 + warning + + + + + [BR-FR-CDV-CL-05/MDT-88] : Lorsque MDT-77 = "23" (Phase Traitement), si présent, MDT-88 doit être l’un des codes suivants : + "45", "39", "37", "50", "49", "47", "46", "1". + Valeur actuelle : " + + ". + + + + + + + + + + + + BR-FR-CDV-CL-06 — Liste fermée de codes statuts de facture + + + + + + + + + BR-FR-CDV-CL-06_MDT-105 + warning + + + + + [BR-FR-CDV-CL-06/MDT-105] : Le code de statut de facture (MDT-105) doit être dans la liste des codes autorisés : + "200", "201", ..., "228". + Valeur actuelle : " + + ". + + + + + + + + + + + + + + + BR-FR-CDV-CL-06_MDT-115 + warning + + + + + [BR-FR-CDV-CL-06/MDT-115] : Le code de statut de facture (MDT-115) doit être dans la liste des codes autorisés : + "200", "201", ..., "228". + Valeur actuelle : " + + ". + + + + + + + + + + + + BR-FR-CDV-CL-07 — Vérification de la valeur de MDT-132 + + + + + + + + + BR-FR-CDV-CL-07_MDT-132 + warning + + + + + [BR-FR-CDV-CL-07/MDT-132] : Le rôle du partenaire commercial émetteur (MDT-132) doit être "SE" (Vendeur). + Valeur actuelle : " + + ". Veuillez corriger cette valeur si nécessaire. + + + + + + + + + + + + BR-FR-CDV-CL-08 — Liste fermée de valeurs pour MDT-158 + + + + + + + + + BR-FR-CDV-CL-08_MDT-158 + warning + + + + + [BR-FR-CDV-CL-08/MDT-158] : Le rôle du partenaire commercial destinataire (MDT-158) doit être dans la liste suivante : + "BY", "AB", "DL", "SE", "SR", "WK", "DFH", "PE", "PR", "II", "IV". + Valeur actuelle : " + + ". Veuillez corriger cette valeur si nécessaire. + + + + + + + + + + + + BR-FR-CDV-CL-09 — Liste fermée de codes motifs de statuts + + + + + + + + + BR-FR-CDV-CL-09_MDT-113 + warning + + + + + [BR-FR-CDV-CL-09/MDT-113] : Le code motif de statut (MDT-113) : " + + " n'est pas dans la liste des codes autorisés : + "NON_TRANSMISE", "JUSTIF_ABS", "ROUTAGE_ERR", "AUTRE", "COORD_BANC_ERR", "TX_TVA_ERR", "MONTANTTOTAL_ERR", "CALCUL_ERR", "NON_CONFORME", "DOUBLON", "DEST_INC", "DEST_ERR", "TRANSAC_INC", "EMMET_INC", "CONTRAT_TERM", "DOUBLE_FACT", "CMD_ERR", "ADR_ERR", "SIRET_ERR", "CODE_ROUTAGE_ERR", "REF_CT_ABSENT", "REF_ERR", "PU_ERR", "REM_ERR", "QTE_ERR", "ART_ERR", "MODPAI_ERR", "QUALITE_ERR", "LIVR_INCOMP", "REJ_SEMAN", "REJ_UNI", "REJ_COH", "REJ_ADR", "REJ_CONT_B2G", "REJ_REF_PJ", "REJ_ASS_PJ", "IRR_VIDE_F", "IRR_TYPE_F", "IRR_SYNTAX", "IRR_TAILLE_PJ", "IRR_NOM_PJ", "IRR_VID_PJ", "IRR_EXT_DOC", "IRR_TAILLE_F", "IRR_ANTIVIRUS". + Veuillez corriger cette valeur si nécessaire. + + + + + + + + + + BR-FR-CDV-CL-09_MDT-113_200 + warning + + + + + [BR-FR-CDV-CL-09/MDT-113_200] : Le code motif de statut (MDT-113) : " + + ", n'est pas dans la liste des codes autorisés pour le statut DÉPOSÉE (200) : + "NON_TRANSMISE". Veuillez corriger cette valeur si nécessaire. + + + + + + + + + + BR-FR-CDV-CL-09_MDT-113_213 + warning + + + + + [BR-FR-CDV-CL-09/MDT-113_213] : Le code motif de statut (MDT-113) : " + + ", n'est pas dans la liste des codes autorisés pour le statut REJETÉE (213) : + "MONTANTTOTAL_ERR", "CALCUL_ERR", "DOUBLON", "DEST_INC", "ADR_ERR", "REJ_SEMAN", "REJ_UNI", "REJ_COH", "REJ_ADR", "REJ_CONT_B2G", "REJ_REF_PJ", "REJ_ASS_PJ". + Veuillez corriger cette valeur si nécessaire. + + + + + + + + + + BR-FR-CDV-CL-09_MDT-113_210 + warning + + + + + [BR-FR-CDV-CL-09/MDT-113_210] : Le code motif de statut (MDT-113) : " + + ", n'est pas dans la liste des codes autorisés pour le statut REFUSÉE (210) : + "TX_TVA_ERR", "MONTANTTOTAL_ERR", "CALCUL_ERR", "NON_CONFORME", "DOUBLON", "DEST_ERR", "TRANSAC_INC", "EMMET_INC", "CONTRAT_TERM", "DOUBLE_FACT", "CMD_ERR", "ADR_ERR", "REF_CT_ABSENT". + Veuillez corriger cette valeur si nécessaire. + + + + + + + + + + BR-FR-CDV-CL-09_MDT-113_207 + warning + + + + + [BR-FR-CDV-CL-09/MDT-113_207] : Le code motif de statut (MDT-113) : " + + ", n'est pas dans la liste des codes autorisés pour le statut LITIGE (207) : + "AUTRE", "COORD_BANC_ERR", "TX_TVA_ERR", "MONTANTTOTAL_ERR", "CALCUL_ERR", "NON_CONFORME", "DOUBLON", "DEST_INC", "DEST_ERR", "TRANSAC_INC", "EMMET_INC", "CONTRAT_TERM", "DOUBLE_FACT", + "CMD_ERR", "ADR_ERR", "SIRET_ERR", "CODE_ROUTAGE_ERR", "REF_CT_ABSENT", "REF_ERR", "PU_ERR", "REM_ERR", "QTE_ERR", "ART_ERR", "MODPAI_ERR", "QUALITE_ERR", "LIVR_INCOMP". + Veuillez corriger cette valeur si nécessaire. + + + + + + + + + + BR-FR-CDV-CL-09_MDT-113_206 + warning + + + + + [BR-FR-CDV-CL-09/MDT-113_206] : Le code motif de statut (MDT-113) : " + + ", n'est pas dans la liste des codes autorisés pour le statut APPROUVÉE PARTIELLEMENT (206) : + "AUTRE", "CMD_ERR", "SIRET_ERR", "CODE_ROUTAGE_ERR", "REF_CT_ABSENT", "REF_ERR", "PU_ERR", "REM_ERR", "QTE_ERR", "ART_ERR", "MODPAI_ERR", "QUALITE_ERR", "LIVR_INCOMP". + Veuillez corriger cette valeur si nécessaire. + + + + + + + + + + BR-FR-CDV-CL-09_MDT-113_208 + warning + + + + + [BR-FR-CDV-CL-09/MDT-113_208] : Le code motif de statut (MDT-113) : " + + ", n'est pas dans la liste des codes autorisés pour le statut SUSPENDUE (208) : + "JUSTIF_ABS", "COORD_BANC_ERR", "CMD_ERR", "SIRET_ERR", "CODE_ROUTAGE_ERR", "REF_CT_ABSENT", "REF_ERR". + Veuillez corriger cette valeur si nécessaire. + + + + + + + + + + BR-FR-CDV-CL-09_MDT-113_221 + warning + + + + + [BR-FR-CDV-CL-09/MDT-113_221] : Le code motif de statut (MDT-113) : " + + ", n'est pas dans la liste des codes autorisés pour le statut ERREUR_ROUTAGE (221) : + "ROUTAGE_ERR". Veuillez corriger cette valeur si nécessaire. + + + + + + + + + + BR-FR-CDV-CL-09_MDT-113_501 + warning + + + + + [BR-FR-CDV-CL-09/MDT-113_501] : Le code motif de statut (MDT-113) : " + + ", n'est pas dans la liste des codes autorisés pour le statut IRRECEVABLE (501) : + "IRR_VIDE_F", "IRR_TYPE_F", "IRR_SYNTAX", "IRR_TAILLE_PJ", "IRR_NOM_PJ", "IRR_VID_PJ", "IRR_EXT_DOC, "IRR_TAILLE_F", "IRR_ANTIVIRUS". Veuillez corriger cette valeur si nécessaire. + + + + + + + + + + + + BR-FR-CDV-CL-10 — Liste fermée de codes actions de facture + + + + + + + + + BR-FR-CDV-CL-10_MDT-121 + warning + + + + + [BR-FR-CDV-CL-10/MDT-121] : Le code d'action de facture (MDT-121) doit être dans la liste des codes autorisés : + "NOA", "PIN", "NIN", "CNF", "CNP", "CNA", "OTH". + Valeur actuelle : " + + ". Veuillez corriger cette valeur si nécessaire. + + + + + + + + + + + + BR-FR-CDV-CL-11 — Liste fermée de codes pour MDT-207 + + + + + + + + + BR-FR-CDV-CL-11_MDT-207 + warning + + + + + [BR-FR-CDV-CL-11/MDT-207] : La valeur du TypeCode (MDT-207) doit appartenir à la liste fermée des codes autorisés : + MEN, MPA, RAP, ESC, RAB, REM, MAP, MAPTTC, MNA, MNATTC, CBB, DIV, DVA, MAJ. + Valeur actuelle : " + + ". Veuillez corriger cette valeur si elle ne correspond pas à un code valide. + + + + + + + + + + + From 3d47295465ed21952db6d1cf69e9fc151d4f190c Mon Sep 17 00:00:00 2001 From: Quentin Dequippe Date: Thu, 19 Feb 2026 18:21:44 +0100 Subject: [PATCH 2/6] fix code style, phpstan and tests --- .../SaxonJarSchematronValidator.php | 29 +++++-------------- .../SchematronValidatorInterface.php | 7 ++--- .../SaxonJarSchematronValidatorTest.php | 20 ++++++++----- tests/Serializer/SerializerTest.php | 2 +- 4 files changed, 25 insertions(+), 33 deletions(-) diff --git a/src/Schematron/SaxonJarSchematronValidator.php b/src/Schematron/SaxonJarSchematronValidator.php index dc10e93..1386150 100644 --- a/src/Schematron/SaxonJarSchematronValidator.php +++ b/src/Schematron/SaxonJarSchematronValidator.php @@ -14,7 +14,7 @@ public function __construct(private string $saxonJar) { if (false === class_exists(Process::class)) { - throw new \LogicException('Symfony Process component is required to use SaxonBusinessRuleValidator. Run "composer require symfony/process"',); + throw new \LogicException('Symfony Process component is required to use SaxonBusinessRuleValidator. Run "composer require symfony/process"'); } if (false === extension_loaded('dom') || false === extension_loaded('libxml')) { @@ -36,10 +36,7 @@ public function validate(string $xmlFilepath, string $xsltFilepath): void $process->run(); if (false === $process->isSuccessful()) { - throw new ValidationFailedException( - message: $process->getErrorOutput(), - code: $process->getExitCode() ?? 1, - ); + throw new ValidationFailedException(message: $process->getErrorOutput(), code: $process->getExitCode() ?? 1); } $output = mb_trim($process->getOutput()); @@ -48,15 +45,10 @@ public function validate(string $xmlFilepath, string $xsltFilepath): void try { if (false === $doc->loadXML($output)) { - throw new ValidationFailedException( - message: 'Failed to parse Schematron validation output: invalid XML.', - ); + throw new ValidationFailedException(message: 'Failed to parse Schematron validation output: invalid XML.'); } } catch (\Throwable $throwable) { - throw new ValidationFailedException( - message: 'Failed to parse Schematron validation output: '.$throwable->getMessage(), - previous: $throwable, - ); + throw new ValidationFailedException(message: 'Failed to parse Schematron validation output: '.$throwable->getMessage(), previous: $throwable); } $xpath = new \DOMXPath($doc); @@ -65,9 +57,7 @@ public function validate(string $xmlFilepath, string $xsltFilepath): void $failedAsserts = $xpath->query('//svrl:failed-assert'); if (false === $failedAsserts) { - throw new ValidationFailedException( - message: 'Failed to parse Schematron validation output', - ); + throw new ValidationFailedException(message: 'Failed to parse Schematron validation output'); } $errors = []; @@ -83,7 +73,7 @@ public function validate(string $xmlFilepath, string $xsltFilepath): void $text = null; $textElements = $fa->getElementsByTagName('text'); - if (0 !== $textElements->length && null !== $textElements->item(0)) { + if (0 !== $textElements->length && $textElements->item(0) instanceof \DOMElement) { $text = $textElements->item(0)->nodeValue; } @@ -100,9 +90,6 @@ public function validate(string $xmlFilepath, string $xsltFilepath): void return; } - throw new ValidationFailedException( - errors: $errors, - message: 'Schematron validation failed', - ); + throw new ValidationFailedException(errors: $errors, message: 'Schematron validation failed'); } -} \ No newline at end of file +} diff --git a/src/Schematron/SchematronValidatorInterface.php b/src/Schematron/SchematronValidatorInterface.php index 835f367..82c61fc 100644 --- a/src/Schematron/SchematronValidatorInterface.php +++ b/src/Schematron/SchematronValidatorInterface.php @@ -7,11 +7,10 @@ interface SchematronValidatorInterface { /** - * @param string $xmlFilepath The path to the XML file to validate against the business rules. - * @param string $xsltFilepath The path to the Schematron XSLT file to use for validation. - * @return void + * @param string $xmlFilepath the path to the XML file to validate against the business rules + * @param string $xsltFilepath the path to the Schematron XSLT file to use for validation * * @throws ValidationFailedException */ public function validate(string $xmlFilepath, string $xsltFilepath): void; -} \ No newline at end of file +} diff --git a/tests/Schematron/SaxonJarSchematronValidatorTest.php b/tests/Schematron/SaxonJarSchematronValidatorTest.php index 2c217a4..99a8073 100644 --- a/tests/Schematron/SaxonJarSchematronValidatorTest.php +++ b/tests/Schematron/SaxonJarSchematronValidatorTest.php @@ -15,35 +15,41 @@ final class SaxonJarSchematronValidatorTest extends TestCase public function testValidateWithSuccess(): void { // Arrange - $validator = new SaxonJarSchematronValidator(saxonJar: getenv('SAXON_JAR')); + /** @var string $saxonJar */ + $saxonJar = getenv('SAXON_JAR'); + $validator = new SaxonJarSchematronValidator(saxonJar: $saxonJar); // Assert $this->expectNotToPerformAssertions(); // Act $validator->validate( - xmlFilepath: __DIR__ . '/../data/UC1_F202500003_01-CDV-200_Deposee.xml', - xsltFilepath: __DIR__ . '/../../xslt/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl', + xmlFilepath: __DIR__.'/../data/UC1_F202500003_01-CDV-200_Deposee.xml', + xsltFilepath: __DIR__.'/../../xslt/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl', ); } public function testValidateWithErrors(): void { // Arrange - $validator = new SaxonJarSchematronValidator(saxonJar: getenv('SAXON_JAR')); + /** @var string $saxonJar */ + $saxonJar = getenv('SAXON_JAR'); + $validator = new SaxonJarSchematronValidator(saxonJar: $saxonJar); // Act try { $validator->validate( - xmlFilepath: __DIR__ . '/../data/invalid_cdar.xml', - xsltFilepath: __DIR__ . '/../../xslt/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl', + xmlFilepath: __DIR__.'/../data/invalid_cdar.xml', + xsltFilepath: __DIR__.'/../../xslt/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl', ); } catch (ValidationFailedException $validationFailedException) { $this->assertCount(4, $validationFailedException->errors); $error1 = $validationFailedException->errors[0]; + $this->assertNotNull($error1->getText()); $this->assertStringContainsString('L\'identifiant du document (ram:ID) est obligatoire', $error1->getText()); $error2 = $validationFailedException->errors[1]; + $this->assertNotNull($error2->getText()); $this->assertStringContainsString('Lorsque le rôle du destinataire (MDT-59) est différent de "WK" ou "DFH"', $error2->getText()); } } -} \ No newline at end of file +} diff --git a/tests/Serializer/SerializerTest.php b/tests/Serializer/SerializerTest.php index 5c32ff1..64bc94b 100644 --- a/tests/Serializer/SerializerTest.php +++ b/tests/Serializer/SerializerTest.php @@ -58,7 +58,7 @@ public function testDeserialize(): void $this->assertSame('REGULATED', $exchangedDocumentContext->getBusinessProcessSpecifiedDocumentContextParameter()?->getID()->getValue()); $this->assertSame('urn.cpro.gouv.fr:1p0:CDV:invoice', $exchangedDocumentContext->getGuidelineSpecifiedDocumentContextParameter()->getID()->getValue()); - $this->assertSame('UC1_F202500003_01-CDV-200_Déposée', $cdar->getExchangedDocument()->getName()?->getValue()); + $this->assertSame('UC1_F202500003_01-CDV-200_Deposee', $cdar->getExchangedDocument()->getName()?->getValue()); $this->assertCount(2, $cdar->getExchangedDocument()->getRecipientTradeParty()); $recipientTradeParty1 = $cdar->getExchangedDocument()->getRecipientTradeParty()[0]; $this->assertNotNull($recipientTradeParty1->getGlobalID()); From d8eb4535ee436eae28b0d929e5d9897f0d6f9df9 Mon Sep 17 00:00:00 2001 From: Quentin Dequippe Date: Thu, 19 Feb 2026 18:26:10 +0100 Subject: [PATCH 3/6] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Schematron/SaxonJarSchematronValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Schematron/SaxonJarSchematronValidator.php b/src/Schematron/SaxonJarSchematronValidator.php index 1386150..b5d5a25 100644 --- a/src/Schematron/SaxonJarSchematronValidator.php +++ b/src/Schematron/SaxonJarSchematronValidator.php @@ -14,7 +14,7 @@ public function __construct(private string $saxonJar) { if (false === class_exists(Process::class)) { - throw new \LogicException('Symfony Process component is required to use SaxonBusinessRuleValidator. Run "composer require symfony/process"'); + throw new \LogicException('Symfony Process component is required to use SaxonJarSchematronValidator. Run "composer require symfony/process"'); } if (false === extension_loaded('dom') || false === extension_loaded('libxml')) { From ca43b4821e5d5f09428c819eecec91989835ec5b Mon Sep 17 00:00:00 2001 From: Quentin Dequippe Date: Thu, 19 Feb 2026 18:28:03 +0100 Subject: [PATCH 4/6] apply reviews --- src/Schematron/SaxonJarSchematronValidator.php | 2 +- tests/Schematron/SaxonJarSchematronValidatorTest.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Schematron/SaxonJarSchematronValidator.php b/src/Schematron/SaxonJarSchematronValidator.php index b5d5a25..bb97d1e 100644 --- a/src/Schematron/SaxonJarSchematronValidator.php +++ b/src/Schematron/SaxonJarSchematronValidator.php @@ -39,7 +39,7 @@ public function validate(string $xmlFilepath, string $xsltFilepath): void throw new ValidationFailedException(message: $process->getErrorOutput(), code: $process->getExitCode() ?? 1); } - $output = mb_trim($process->getOutput()); + $output = trim($process->getOutput()); $doc = new \DOMDocument(); diff --git a/tests/Schematron/SaxonJarSchematronValidatorTest.php b/tests/Schematron/SaxonJarSchematronValidatorTest.php index 99a8073..f6c99b5 100644 --- a/tests/Schematron/SaxonJarSchematronValidatorTest.php +++ b/tests/Schematron/SaxonJarSchematronValidatorTest.php @@ -42,6 +42,8 @@ public function testValidateWithErrors(): void xmlFilepath: __DIR__.'/../data/invalid_cdar.xml', xsltFilepath: __DIR__.'/../../xslt/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl', ); + + $this->fail('Expected ValidationFailedException was not thrown.'); } catch (ValidationFailedException $validationFailedException) { $this->assertCount(4, $validationFailedException->errors); $error1 = $validationFailedException->errors[0]; From a8455de2d9c24fa2af40cfd8f3790c16c15f8309 Mon Sep 17 00:00:00 2001 From: Quentin Dequippe Date: Thu, 19 Feb 2026 18:28:33 +0100 Subject: [PATCH 5/6] Update docs/SaxonJarSchematronValidator.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/SaxonJarSchematronValidator.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/SaxonJarSchematronValidator.md b/docs/SaxonJarSchematronValidator.md index 01c7d3b..2cfa236 100644 --- a/docs/SaxonJarSchematronValidator.md +++ b/docs/SaxonJarSchematronValidator.md @@ -29,7 +29,6 @@ In this implementation: - **Required PHP extensions:** - `dom` - for parsing SVRL XML output - `libxml` - for XML handling - - `mbstring` - for string operations ### Composer Dependencies From b92ac35386e94321878fdb169834937ab94a11b5 Mon Sep 17 00:00:00 2001 From: Quentin Dequippe Date: Thu, 19 Feb 2026 18:34:18 +0100 Subject: [PATCH 6/6] update gitattributes --- .gitattributes | 7 ++++--- tests/Schematron/SaxonJarSchematronValidatorTest.php | 4 ++-- .../data}/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl | 0 3 files changed, 6 insertions(+), 5 deletions(-) rename {xslt => tests/data}/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl (100%) diff --git a/.gitattributes b/.gitattributes index 06a4051..169923f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,13 +3,14 @@ /.gitignore export-ignore /CHANGELOG.md export-ignore /CONTRIBUTING.md export-ignore -/doc export-ignore +/docs export-ignore /tools export-ignore -/Makefile export-ignore /phpunit.xml.dist export-ignore +/phpunit.xml export-ignore /tests export-ignore /VERSIONING.md export-ignore /.php-cs-fixer.php export-ignore /rector.php export-ignore /phpstan.neon.dist export-ignore -/phpstan-baseline.neon export-ignore \ No newline at end of file +/phpstan-baseline.neon export-ignore +/Dockerfile export-ignore \ No newline at end of file diff --git a/tests/Schematron/SaxonJarSchematronValidatorTest.php b/tests/Schematron/SaxonJarSchematronValidatorTest.php index f6c99b5..c0aee2d 100644 --- a/tests/Schematron/SaxonJarSchematronValidatorTest.php +++ b/tests/Schematron/SaxonJarSchematronValidatorTest.php @@ -25,7 +25,7 @@ public function testValidateWithSuccess(): void // Act $validator->validate( xmlFilepath: __DIR__.'/../data/UC1_F202500003_01-CDV-200_Deposee.xml', - xsltFilepath: __DIR__.'/../../xslt/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl', + xsltFilepath: __DIR__.'/../data/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl', ); } @@ -40,7 +40,7 @@ public function testValidateWithErrors(): void try { $validator->validate( xmlFilepath: __DIR__.'/../data/invalid_cdar.xml', - xsltFilepath: __DIR__.'/../../xslt/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl', + xsltFilepath: __DIR__.'/../data/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl', ); $this->fail('Expected ValidationFailedException was not thrown.'); diff --git a/xslt/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl b/tests/data/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl similarity index 100% rename from xslt/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl rename to tests/data/20260216_BR-FR-CDV-Schematron-CDAR_V1.3.0.xsl