---
title: Lire le XML embarqué dans un PDF Factur-X (en PHP)
source: https://synapx.fr/blog/lire-xml-embarque-factur-x/
date: 2026-06-27
category: Facturation électronique
site: SynapxLab
---

# Lire le XML embarqué dans un PDF Factur-X

Une facture Factur-X est un PDF ordinaire à deux couches : la couche visuelle (lisible par un humain) et une couche données, un fichier XML au format CII (Cross Industry Invoice EN 16931) embarqué en pièce jointe dans le PDF. C'est cette seconde couche qui intéresse les logiciels de comptabilité, les vérificateurs de conformité et les pipelines d'archivage.

Cet article montre comment extraire ce XML en PHP, le parser, et l'exploiter.

> 🔎 Si vous construisez une facture Factur-X de zéro plutôt que de la lire, consultez le testeur disponible sur [/sdk/FactureX/](/sdk/FactureX/).

---

## Prérequis et rappels

- Factur-X est définie par la norme **EN 16931** ; le fichier XML s'appelle `factur-x.xml` (ou `zugferd-invoice.xml` pour les anciennes factures ZUGFeRD 1.x).
- Plusieurs profils existent : MINIMUM, BASIC WL, BASIC, EN 16931, EXTENDED. Le profil détermine quels champs XML sont obligatoires ou optionnels.
- L'extraction du XML ne dépend pas du profil ; le parsing, lui, doit en tenir compte.

Articles connexes :
- [Générer une Factur-X en PHP](/blog/generer-factur-x-php/)
- [Factur-X : comprendre et valider](/blog/factur-x-comprendre-valider/)
- [Les champs EN 16931 expliqués](/blog/champs-en-16931-expliques/)

---

## Méthode 1 — Avec la librairie `atgp/factur-x`

### Installation

```bash
composer require atgp/factur-x
```

La librairie dépend de `smalot/pdfparser` pour accéder aux pièces jointes du PDF.

### Extraire le XML

**Approche via la classe `Facturx` :**

```php
<?php

require 'vendor/autoload.php';

use Atgp\FacturX\Facturx;

$pdfBinary = file_get_contents('/chemin/vers/facture.pdf');

$facturx = new Facturx();
$xml = $facturx->getFacturxXmlFromPdf($pdfBinary);

// $xml est une chaîne UTF-8 contenant le XML CII brut
file_put_contents('/tmp/factur-x.xml', $xml);
```

**Approche alternative via la classe `Reader` :**

```php
<?php

require 'vendor/autoload.php';

use Atgp\FacturX\Reader;

$pdfBinary = file_get_contents('/chemin/vers/facture.pdf');

$reader = new Reader();
$xml = $reader->extractXML($pdfBinary);
```

Les deux formes renvoient la même chaîne XML brute. Choisissez selon votre style ou l'API que vous avez déjà en place.

### Parser le XML obtenu

Le XML CII utilise plusieurs espaces de noms. Sans les déclarer explicitement, les requêtes XPath ne renvoient rien — c'est le piège le plus fréquent.

Les principaux préfixes à connaître :

| Préfixe | URI |
|---------|-----|
| `rsm` | `urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100` |
| `ram` | `urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100` |
| `udt` | `urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100` |

#### Avec SimpleXML

```php
<?php

$sxe = simplexml_load_string($xml);

$rsm = $sxe->children('urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100');
$header = $rsm->ExchangedDocument
               ->children('urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100');

$numeroFacture = (string) $header->ID;
$dateRaw       = (string) $header->IssueDateTime
                          ->children('urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100')
                          ->DateTimeString;

echo "Numéro : $numeroFacture\n";
echo "Date   : $dateRaw\n";
```

#### Avec DOMDocument et XPath (plus robuste pour les requêtes complexes)

```php
<?php

$dom = new DOMDocument();
$dom->loadXML($xml);

$xpath = new DOMXPath($dom);

$xpath->registerNamespace('rsm', 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100');
$xpath->registerNamespace('ram', 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100');
$xpath->registerNamespace('udt', 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100');

// Numéro de facture
$numero = $xpath->evaluate('string(//rsm:ExchangedDocument/ram:ID)');

// Date d'émission
$date = $xpath->evaluate('string(//rsm:ExchangedDocument/ram:IssueDateTime/udt:DateTimeString)');

// Total TTC
$totalTTC = $xpath->evaluate('string(//ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:GrandTotalAmount)');

echo "Numéro  : $numero\n";
echo "Date    : $date\n";
echo "Total   : $totalTTC €\n";
```

---

## Méthode 2 — Sans librairie PHP

### En ligne de commande avec `pdfdetach` (poppler-utils)

`poppler-utils` expose l'utilitaire `pdfdetach` qui liste et extrait les pièces jointes d'un PDF sans avoir besoin de PHP.

```bash
# Installer poppler-utils (Debian/Ubuntu)
apt-get install poppler-utils

# Lister les pièces jointes
pdfdetach -list facture.pdf

# Extraire toutes les pièces jointes dans le répertoire courant
pdfdetach -saveall facture.pdf
```

La commande produit `factur-x.xml` (ou `zugferd-invoice.xml`) dans le répertoire courant. Vous pouvez ensuite le lire et le parser comme n'importe quel XML.

```bash
# Vérification rapide
xmllint --format factur-x.xml | head -40
```

### En PHP avec `smalot/pdfparser` seul

Si vous ne voulez pas la surcouche `atgp/factur-x` mais souhaitez rester dans PHP, `smalot/pdfparser` permet d'accéder aux objets embarqués. Le XML Factur-X est stocké dans le dictionnaire `/EmbeddedFiles` du PDF.

```php
<?php

require 'vendor/autoload.php';

use Smalot\PdfParser\Parser;

$parser = new Parser();
$pdf    = $parser->parseContent(file_get_contents('facture.pdf'));

foreach ($pdf->getObjectElements() as $object) {
    $details = $object->getDetails();
    if (isset($details['F']) && str_contains(strtolower($details['F']), 'factur-x')) {
        $xml = $object->getContent();
        file_put_contents('/tmp/factur-x.xml', $xml);
        break;
    }
}
```

Cette approche est plus bas niveau et moins fiable que `atgp/factur-x` (qui encapsule correctement ces cas). Elle convient surtout si vous avez déjà `smalot/pdfparser` en dépendance et n'avez besoin que d'un cas simple.

---

## Cas d'usage

**Import automatique en comptabilité.** Le XML CII contient les montants, la TVA décomposée, les références fournisseur et client. Un pipeline peut lire ce fichier à la réception d'un PDF et pré-remplir une écriture comptable sans saisie manuelle.

**Contrôle de cohérence PDF vs XML.** La couche visuelle peut diverger de la couche XML (erreur de génération, PDF retouché). Extraire le XML et comparer le total TTC avec celui affiché dans le PDF permet de détecter ces écarts avant paiement.

**Archivage des données structurées.** Conserver le XML à côté du PDF dans un GED (ou en base) permet d'interroger les archives par numéro, date, fournisseur, montant — sans OCR.

**Validation de conformité.** Après extraction, vous pouvez valider le XML contre le schéma XSD officiel ou via les règles Schematron EN 16931 (voir [Factur-X : comprendre et valider](/blog/factur-x-comprendre-valider/)).

---

## Pièges courants

**Les namespaces XML.** C'est la cause n°1 des bugs silencieux. Un XPath comme `//ExchangedDocument/ID` ne renvoie rien si les namespaces ne sont pas enregistrés. Toujours utiliser `registerNamespace()` avec `DOMXPath`, ou naviguer avec `->children('uri')` en SimpleXML.

**L'encodage UTF-8.** Le XML CII est en UTF-8. Si votre base ou votre buffer n'est pas en UTF-8, les caractères accentués seront corrompus.

**Le profil détermine les champs disponibles.** En profil MINIMUM, seuls quelques champs sont présents (numéro, date, montant TTC, identifiants des parties). Les détails de ligne n'apparaissent qu'à partir du profil EN 16931. Le profil est lisible dans `//rsm:ExchangedDocumentContext/ram:GuidelineSpecifiedDocumentContextParameter/ram:ID`.

**Le nom du fichier embarqué.** Les factures récentes utilisent `factur-x.xml` ; les anciennes ZUGFeRD 1.x utilisent `zugferd-invoice.xml`. La librairie gère les deux ; si vous extrayez manuellement, testez les deux noms.

---

## FAQ

**Peut-on modifier le XML et le réemballer dans le PDF ?**
Oui, `atgp/factur-x` expose aussi une API de génération. Voir [Générer une Factur-X en PHP](/blog/generer-factur-x-php/). Attention : modifier le XML sans régénérer le PDF visuel crée une incohérence entre les deux couches.

**La librairie fonctionne-t-elle avec les factures ZUGFeRD allemandes ?**
Oui. ZUGFeRD 2.x est aligné sur Factur-X ; seul le nom du fichier embarqué peut différer. ZUGFeRD 1.x utilise un schéma différent.

**`getFacturxXmlFromPdf()` retourne null ou lève une exception ?**
Si aucun XML n'est trouvé (PDF non Factur-X), la méthode peut lever une exception. Encapsulez l'appel dans un `try/catch` en production.

**Faut-il valider le XML après extraction ?**
Recommandé pour détecter des factures malformées. La validation XSD et Schematron est détaillée dans [Factur-X : comprendre et valider](/blog/factur-x-comprendre-valider/).

---

## Pour aller plus loin

- Code source et issues : [github.com/atgp/factur-x](https://github.com/atgp/factur-x)
- Référence des champs CII : [Les champs EN 16931 expliqués](/blog/champs-en-16931-expliques/)
- Générer une Factur-X depuis PHP : [/blog/generer-factur-x-php/](/blog/generer-factur-x-php/)
- Testeur en ligne : [/sdk/FactureX/](/sdk/FactureX/)
