XML Tutorial · XML Basics · Lesson 5 of 28

Escaping, CDATA and Encoding

Predefined entities, character references, CDATA sections, and character encodings such as UTF-8.

Create a free account to save this page, take notes and track your progress. Sign in

The five predefined entities

Character Escape Required?
< &lt; Always in text and attribute values
& &amp; Always
> &gt; Only in ]]>; escaping it is harmless
" &quot; Inside a "-quoted attribute
' &apos; Inside a '-quoted attribute

Character references insert any Unicode character by number: &#233; or &#xE9; both mean é. Named entities such as &nbsp; or &copy; are HTML, not XML; in XML they're errors unless a DTD defines them.

Let the library escape for you

Build XML with an API and every value is escaped correctly, including the awkward ones:

Values that need escapingTry it yourself
from lxml import etree

row = etree.Element("product", name='Fish & "Chips"')
row.text = "Price < 5 & tasty"
print(etree.tostring(row).decode())
print(etree.fromstring(etree.tostring(row)).get("name"))

Output

<product name="Fish &amp; &quot;Chips&quot;">Price &lt; 5 &amp; tasty</product>
Fish & "Chips"

Building XML with string concatenation ("<name>" + value + "</name>") breaks as soon as a value contains < or &, and it's an injection risk: a value like </name><admin>true</admin><name> changes the structure.

CDATA sections

Inside <![CDATA[ … ]]> everything is literal text, which is handy for code samples or embedded HTML:

CDATA is just textTry it yourself
from lxml import etree

doc = etree.fromstring(b"<snippet><![CDATA[if (a < b && c > d) { alert('hi'); }]]></snippet>")
print(doc.text)
print(etree.tostring(doc).decode())  # serialised again with normal escaping

Output

if (a < b && c > d) { alert('hi'); }
<snippet>if (a &lt; b &amp;&amp; c &gt; d) { alert('hi'); }</snippet>

CDATA is only a writing convenience; after parsing, the text is the same as if it had been escaped. The only sequence a CDATA section can't contain is ]]>.

Encodings

XML parsers must support UTF-8 and UTF-16; UTF-8 is the default and the right choice. Declare anything else (<?xml version="1.0" encoding="ISO-8859-1"?>), and make sure the bytes really are in that encoding. Parse bytes, not decoded strings, so the parser can honour the declaration:

Declared and undeclared encodingsTry it yourself
from lxml import etree

latin1 = '<?xml version="1.0" encoding="ISO-8859-1"?><city>Zürich</city>'.encode("iso-8859-1")
print(latin1[-12:])                       # the raw bytes: ü is one byte, 0xfc
print(etree.fromstring(latin1).text)      # decoded correctly thanks to the declaration

try:
    etree.fromstring('<city>Zürich</city>'.encode("iso-8859-1"))  # no declaration: UTF-8 assumed
except etree.XMLSyntaxError as e:
    print("error:", e.msg)

Output

b'\xfcrich</city>'
Zürich
error: Invalid bytes in character encoding, line 1, column 8

Test yourself

Which character must always be escaped in XML text content?

  1. &
  2. >
  3. '
  4. #
Show answer

Answer: &

& starts an entity reference, so a literal ampersand must be written & (and < as <).

Was this page helpful?