# FW-XML-001: Fix XML that the parser refuses to read

An Ironfang diagnostic, not an official rule: the XML is malformed, in an unsupported encoding, or uses DOCTYPE, entities or XInclude. Nothing else ran.

- Layer: Ironfang diagnostic
- Severity: fatal (the document is invalid)
- Topics: XML input
- Ironfang validation contract: https://api.ironfang.uk/finance/openapi.yaml
- Explanation last updated: 2026-09-20

**FW-XML-001 is Ironfang's own diagnostic.** It is not an EN 16931 or Peppol BIS Billing rule, and other validators will not report it under this identifier.

## The short answer

`FW-XML-001` is an identifier of our own. It is not part of EN 16931 or Peppol, and you will not find it in their rule lists. It means the XML layer refused the document before any invoice rule was looked at: the text is not well-formed XML, it is in an encoding we do not accept, or it contains a construct we block for safety.

The XSD, EN 16931 and Peppol layers are all reported as skipped, so the result says nothing yet about the invoice content. Fix the file so that it parses, then validate again and expect the real findings to appear.

## What the rule checks

Well-formedness. Truncated documents, unclosed or mismatched tags, a bare `&` or `<` in text, control characters, a second root element, text after the root, and input that is not XML at all were each refused when tried. For these the finding gives the line and column where the parser stopped.

The XML declaration, when there is one, must be the very first thing in the file. A blank line or any other text before it is refused. A document with no declaration at all is accepted.

Encoding. UTF-8 is accepted with or without a byte-order mark. UTF-16 is accepted only with a byte-order mark and a declaration that agrees with it. Everything else is refused, including a declared `ISO-8859-1`, `windows-1252` or `US-ASCII` even when the bytes are plain ASCII, and bytes that are not valid for the declared encoding.

Prohibited constructs. Any `DOCTYPE` is refused, with or without entity declarations. So is any entity reference beyond the five predefined ones, which makes HTML names such as `&nbsp;` fail. Any element in an XInclude namespace is refused wherever it sits; that finding carries no location.

Not refused: numeric character references such as `&#160;`, `&amp;` and the other predefined entities, CDATA sections, comments, and processing instructions.

## How an integration ends up here

Possible causes, from the shape of the rule rather than from measured usage:

- The XML is assembled by string concatenation or a text template, and a name such as `Smith & Sons` goes in unescaped.
- The file was cut short by a failed upload, a size limit upstream, or a stream that was not flushed.
- The exporter writes a legacy single-byte encoding, or declares UTF-8 while writing bytes in another encoding.
- The XML is base64-encoded or wrapped in a JSON envelope before it is sent, so what arrives in the XML body is not XML.
- A logging or templating step puts a blank line or a byte sequence in front of the XML declaration.
- The generating tool adds a `DOCTYPE`, or the text contains HTML entities copied from a web form.

## How to fix it

1. Go to the line and column in the finding and look at what is there. For a truncated file that is the end of the document.
2. Generate the XML with an XML library and not by joining strings, so that text is escaped and tags are balanced for you.
3. Write the file as UTF-8 and declare it as UTF-8, or leave the declaration out. Make sure nothing comes before the declaration.
4. Send the XML bytes themselves, as the raw request body or as the `document` part of a multipart request: not base64, not inside JSON.
5. Remove any `DOCTYPE`, entity declarations and XInclude elements. Replace named HTML entities with the character itself or a numeric reference.
6. Validate again. The layers that were skipped will now run, and the document may report rules it could not reach before.

## Before and after

These are fragments, not complete documents.

Illustration, not a downloadable fixture: a document cut off in the middle of a closing tag

```xml
<cac:Price>
  <cbc:PriceAmount currencyID="EUR">500</cbc:PriceAmount>
</c
```

Illustration: the same document complete, with every element closed through to the root

```xml
<cac:Price>
  <cbc:PriceAmount currencyID="EUR">500</cbc:PriceAmount>
</cac:Price>
<!-- the enclosing cac:InvoiceLine and the Invoice root are then closed in turn -->
```

The failing input stops partway through a closing tag, so the parser reaches the end of the file with elements still open. The document reports only `FW-XML-001`, in the XML layer; the XSD, EN 16931 and Peppol layers are skipped. These fragments are hand-written illustrations of a regression case. There is no downloadable pair for this diagnostic, because the failing file is by definition not a usable document.

### What the validator reported

- The failing invoice reports **FW-XML-001**; the xsd and en16931 and peppol layers did not run.

Recorded on phive 12.1.0 / phive-rules-peppol 4.5.6 / Saxon-HE 12.10, the engine behind the free validator, using synthetic data. A recorded result is regression evidence for these documents; it is not a certification.

## Where it applies

- Applies to anything submitted, whatever it was meant to be. The document type cannot matter, because the input was never read as an `Invoice` or a `CreditNote`.
- This is an Ironfang diagnostic. Another validator will describe the same input in its own words, and may be more or less strict about `DOCTYPE` and encodings.
- It is separate from the size and shape limits. An empty body is reported as `FW-INPUT-001`, and documents that are too deeply nested or have too many elements have their own `FW-XML` identifiers. Compressed bodies and archives are turned away by the API with a `415` response before validation starts, so they do not produce this finding.
- Well-formed XML that does not follow the UBL schema is a different failure: that is reported by the XSD layer, not here.

## Related rules

- [PEPPOL-EN16931-R008 covers elements that are well-formed but empty, which the parser accepts and Peppol does not](https://ironfang.uk/docs/finance/rules/PEPPOL-EN16931-R008.md)
- [BR-01 is one of the first content rules a document meets once it parses: the specification identifier must be present](https://ironfang.uk/docs/finance/rules/BR-01.md)
- [PEPPOL-EN16931-R004 then checks that the specification identifier is the Peppol Billing one](https://ironfang.uk/docs/finance/rules/PEPPOL-EN16931-R004.md)

## Scope and source

Describes how Ironfang Finance handles input before validation. It applies to every ruleset the validator offers. Guidance version 2026-09-24.1: source checked 2026-09-24, explanation last updated 2026-09-20.

[Ironfang validation contract](https://api.ironfang.uk/finance/openapi.yaml)

Guidance does not change the engine verdict. Fixing this finding does not mean the document passes every layer, and validation does not certify legal or tax compliance or transmit a document over Peppol.

## Links

- [This rule as a web page](https://ironfang.uk/docs/finance/rules/FW-XML-001)
- [Free Peppol invoice validator](https://ironfang.uk/tools/peppol-validator)
- [Rule index](https://ironfang.uk/docs/finance/rules.md)
- [Ironfang Finance API docs](https://ironfang.uk/docs/finance)
- The same rule is available to MCP clients as the tool `finance.rule.get` on https://mcp.ironfang.uk/mcp
