Common causes
1. Company and product names
Names such as “AT&T”, “Tom & Jerry” or “R&D” contain a literal ampersand. Escape it in text and in attributes alike.
<product name="Tom & Jerry DVD"/><product name="Tom & Jerry DVD"/>2. URLs with query strings
Every & between query parameters must be escaped inside XML, including in sitemaps, RSS feeds and Android or Maven config. The URL still works: the parser turns & back into &.
<loc>https://shop.example.com/search?q=dvd&page=2</loc><loc>https://shop.example.com/search?q=dvd&page=2</loc>3. XML built with string concatenation
Inserting values into an XML template copies their special characters verbatim. Build the document with an XML library, which escapes &, < and quotes for you.
xml = f'<product name="{name}"/>'el = ET.Element("product", name=name)
xml = ET.tostring(el, encoding="unicode")4. Text that should be kept verbatim
For a block of code or markup full of ampersands, a CDATA section avoids escaping each one. Note that CDATA cannot be used inside attribute values.
<script>if (a && b) run();</script><script><![CDATA[if (a && b) run();]]></script>Frequently asked questions
Do I need to escape & inside attribute values too?
Yes. The rule is the same in attributes and text. In attributes you also need to escape the quote character that delimits the value, as " or '.
What about a & that is already part of &?
That is fine; only bare ampersands are errors. Be careful not to escape twice, which turns & into & and shows a literal “&” to readers.
Why does Python only say "invalid token"?
Expat uses the generic invalid token message for a bare &. The column points at the character after the ampersand. See not well-formed (invalid token) for its other causes.