EntityRef: expecting ';' – znak & a < v XML

Znak & zahajuje v XML entitu (&amp;, &lt;, &#160;) a znak < značku. Když se v textu objeví samotné, parser čeká název entity nebo elementu a skončí chybou. V textu je zapiš jako &amp; a &lt;, a hlavně XML neskládej z řetězců, ale knihovnou, která escapuje sama.

Autor: Aktualizováno

Znění hlášky v jednotlivých parserech

Pro & hlásí libxml2 (PHP, xmllint) EntityRef: expecting ';' nebo xmlParseEntityRef: no name, Java The reference to entity "…" must end with the ';' delimiter, Python not well-formed (invalid token), .NET An error occurred while parsing EntityName:

Parser Hláška Kdy
libxml2 2.13 a novější PHP (DOM, SimpleXML), xmllint, Python lxml, validátor na XML.cz EntityRef: expecting ';' Znak & v adrese URL
xmlParseEntityRef: no name Samotný znak & v textu
StartTag: invalid element name Znak < v textu
Entity 'nbsp' not defined HTML entita, kterou XML nezná
Java (Xerces) DocumentBuilder, SAXParser, JAXB, Spring, Maven The reference to entity "barva" must end with the ';' delimiter. Znak & v adrese URL
The entity name must immediately follow the '&' in the entity reference. Samotný znak & v textu
The content of elements must consist of well-formed character data or markup. Znak < v textu
The entity "nbsp" was referenced, but not declared. HTML entita, kterou XML nezná
Python (expat) xml.etree.ElementTree, minidom not well-formed (invalid token): line 3, column 44 Znak & v adrese URL
not well-formed (invalid token): line 2, column 14 Samotný znak & v textu
not well-formed (invalid token): line 2, column 16 Znak < v textu
undefined entity: line 2, column 7 HTML entita, kterou XML nezná
.NET XmlDocument, XDocument, XmlReader An error occurred while parsing EntityName. Line 3, position 40. Znak & v adrese URL
An error occurred while parsing EntityName. Line 2, position 15. Samotný znak & v textu
Name cannot begin with the ' ' character, hexadecimal value 0x20. Line 2, position 17. Znak < v textu
Reference to undeclared entity 'nbsp'. Line 2, position 9. HTML entita, kterou XML nezná
Chrome 151+ DOMParser, zobrazení XML souboru Unexpected token inside entity: = Znak & v adrese URL
Unexpected token inside entity: Samotný znak & v textu
Qualified name is invalid Znak < v textu
Unexpected entity: nbsp HTML entita, kterou XML nezná
Firefox DOMParser, zobrazení XML souboru not well-formed Znak & v adrese URL, Samotný znak & v textu, Znak < v textu
undefined entity HTML entita, kterou XML nezná

Která hláška se objeví, záleží na tom, co za & následuje. Za &barva= parser přečte název entity barva a chybí mu středník. Za & s mezerou nenajde žádný název.

Příčiny a opravy

Znak & v adrese URL

Chybně
<?xml version="1.0" encoding="UTF-8"?>
<produkt>
  <url>https://example.cz/detail?id=42&barva=cerna</url>
</produkt>
Opraveno
<?xml version="1.0" encoding="UTF-8"?>
<produkt>
  <url>https://example.cz/detail?id=42&amp;barva=cerna</url>
</produkt>

Nejčastější případ: adresa s parametry v produktovém feedu, sitemapě nebo RSS. V XML musí být každý & v URL zapsaný jako &amp;. Program, který XML čte, dostane zpátky původní adresu s &.

Samotný znak & v textu

Chybně
<?xml version="1.0" encoding="UTF-8"?>
<firma>Novák & syn s.r.o.</firma>
Opraveno
<?xml version="1.0" encoding="UTF-8"?>
<firma>Novák &amp; syn s.r.o.</firma>

Názvy firem, produktů a textů od uživatelů. Stejně se opraví &amp;.

Znak < v textu

Chybně
<?xml version="1.0" encoding="UTF-8"?>
<podminka>cena < 1000</podminka>
Opraveno
<?xml version="1.0" encoding="UTF-8"?>
<podminka>cena &lt; 1000</podminka>

Za < parser čeká název elementu. Mezera nebo číslice název začínat nesmí, proto libxml2 hlásí StartTag: invalid element name a Java obecné The content of elements must consist of well-formed character data or markup. Delší text se spoustou takových znaků (kód, HTML) se hodí zabalit do <![CDATA[ … ]]>.

HTML entita, kterou XML nezná

Chybně
<?xml version="1.0" encoding="UTF-8"?>
<cena>1&nbsp;290&nbsp;Kč</cena>
Opraveno
<?xml version="1.0" encoding="UTF-8"?>
<cena>1&#160;290&#160;Kč</cena>

XML zná jen pět pojmenovaných entit: &amp;, &lt;, &gt;, &quot; a &apos;. HTML entity jako &nbsp;, &copy; nebo &ndash; v něm neexistují, dokud je nedeklaruje DTD. Nahraď je číselnou entitou (&#160; pro nezlomitelnou mezeru) nebo přímo znakem v UTF-8.

Které znaky se musí escapovat

ZnakV textu elementuV hodnotě atributu
&&amp; (vždy)&amp; (vždy)
<&lt; (vždy)&lt; (vždy)
>smí zůstat, kromě dvojice ]]> (pak &gt;)smí zůstat
"smí zůstat&quot;, když je hodnota v uvozovkách "…"
'smí zůstat&apos;, když je hodnota v apostrofech '…'

Jak chybu najít

  • Hledej v souboru regulárním výrazem &(?!(amp|lt|gt|quot|apos|#[0-9]+|#x[0-9a-fA-F]+);). Najde každý &, který nezačíná platnou entitu.
  • Validátor neescapovaný znak nejen najde, ale nabídne i opravu na &amp;.
  • Opravuj zdroj, ne výsledný soubor: pokud feed vzniká ze šablony, chyba se vrátí při dalším exportu.

Oprava v kódu

Nejjistější je XML vůbec neskládat spojováním řetězců. Knihovna, která dokument sestaví ze stromu, escapuje text i atributy sama. Když řetězec skládat musíš, použij escapovací funkci pro XML, ne pro HTML ani URL:

Python
from xml.sax.saxutils import escape
escape("cena < 1000 & více")   # 'cena &lt; 1000 &amp; více'

# Lepší je nechat escapování na knihovně, která XML sestavuje:
import xml.etree.ElementTree as ET
url = ET.Element("url")
url.text = "https://example.cz/detail?id=42&barva=cerna"
ET.tostring(url, encoding="unicode")
# '<url>https://example.cz/detail?id=42&amp;barva=cerna</url>'
PHP
echo htmlspecialchars($text, ENT_XML1 | ENT_QUOTES, 'UTF-8');

$dom = new DOMDocument('1.0', 'UTF-8');
$url = $dom->createElement('url');
$url->appendChild($dom->createTextNode($text)); // escapuje sám
$dom->appendChild($url);
Java
Element url = doc.createElement("url");
url.setTextContent("https://example.cz/detail?id=42&barva=cerna");
// Transformer při zápisu vypíše &amp;
C#
var url = new XElement("url", "https://example.cz/detail?id=42&barva=cerna");
// <url>https://example.cz/detail?id=42&amp;barva=cerna</url>

SecurityElement.Escape("cena < 1000 & více"); // cena &lt; 1000 &amp; více
JavaScript
const doc = document.implementation.createDocument(null, 'produkt', null);
const url = doc.createElement('url');
url.textContent = 'https://example.cz/detail?id=42&barva=cerna';
doc.documentElement.appendChild(url);
new XMLSerializer().serializeToString(doc);

Pozor na dvojí escapování: když text už obsahuje &amp; a projde escapováním znovu, vznikne &amp;amp; a čtenář uvidí doslova &amp;.

Související