Common causes
- A raw & in text or URLs (e.g. ?a=1&b=2 or 'Tom & Jerry') instead of &
- A literal < in text content, or HTML pasted into an element without CDATA
- Text saved as Windows-1252/Latin-1 while the XML declares or defaults to UTF-8
- Control characters (0x00-0x1F except tab, newline, carriage return) copied from Word or a database
- HTML entities such as or © that are not defined in XML
- Unquoted attribute values or mismatched quotes in attributes
How to fix it
- Go to the reported position. Open the file in an editor that shows line and column and look at the exact character. For remote feeds, save the response to a file first (curl -o feed.xml URL).
- Escape special characters. Replace & with & and < with < in text and attribute values. In code, use an XML library or htmlspecialchars($text, ENT_XML1 | ENT_QUOTES, 'UTF-8') instead of string concatenation.
- Wrap markup in CDATA. If an element must contain HTML, wrap it in <![CDATA[ ... ]]> so the parser does not interpret it (the sequence ]]> must not appear inside).
- Fix the encoding. Save the file as UTF-8, or make the declaration match the real encoding, e.g. <?xml version="1.0" encoding="ISO-8859-1"?>. 'Input is not proper UTF-8' always means this.
- Remove control characters. Strip invalid characters before writing XML, e.g. preg_replace('/[^\x{9}\x{A}\x{D}\x{20}-\x{D7FF}\x{E000}-\x{FFFD}]/u', '', $text) in PHP.
- Replace HTML entities. Use numeric references (  for a non-breaking space, © for ©) or the real UTF-8 characters instead of HTML-only named entities.
PHP (build XML safely)
$doc = new DOMDocument('1.0', 'UTF-8');
$item = $doc->createElement('item');
$title = $doc->createElement('title');
$title->appendChild($doc->createTextNode('Tom & Jerry <Classic>')); // escaped automatically
$item->appendChild($title);
$doc->appendChild($item);
header('Content-Type: application/xml; charset=UTF-8');
echo $doc->saveXML(); How to stop it happening again
- Generate XML with DOMDocument, XMLWriter or a library, never by concatenating strings
- Keep data UTF-8 end to end and declare it in the XML prolog
- Validate feeds and sitemaps after every change to their templates