Skip to content

Commit aea35e2

Browse files
Fix malformed XML declarations in OPF/XML files causing Amazon E999 rejections
Amazon's Send-to-Kindle service was rejecting some EPUBs with E999 errors due to malformed XML declarations containing excessive whitespace. Example: <?xml version="1.0" encoding="UTF-8"?> ^^ Double space breaks Amazon's strict XML parser Root cause: fix_encoding() only processed HTML/XHTML files and missed OPF/XML/NCX files entirely. Additionally, the validation regex incorrectly accepted malformed declarations with multiple consecutive spaces. Changes to kindle_epub_fixer.py: - Extended fix_encoding() to process OPF, XML, and NCX files (not just HTML/XHTML) - Added malformed XML declaration detection pattern (2+ consecutive spaces) - Normalizes malformed declarations to proper single-space format - Uses elif logic to ensure malformed fix runs before missing declaration check - Preserves all existing behavior for properly formatted files Fixes issue where EPUBs passed Python's lenient minidom parser but were rejected by Amazon's strict validation.
1 parent 0746472 commit aea35e2

1 file changed

Lines changed: 25 additions & 8 deletions

File tree

scripts/kindle_epub_fixer.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -229,19 +229,36 @@ def read_epub(self, epub_path):
229229
self.binary_files[filename] = zip_ref.read(filename)
230230

231231
def fix_encoding(self):
232-
"""Add UTF-8 encoding declaration if missing"""
232+
"""Add UTF-8 encoding declaration if missing and fix malformed XML declarations"""
233233
encoding = '<?xml version="1.0" encoding="utf-8"?>'
234234
regex = r'^<\?xml\s+version=["\'][\d.]+["\']\s+encoding=["\'][a-zA-Z\d\-\.]+["\'].*?\?>'
235+
# Pattern to detect malformed XML declarations (excessive whitespace)
236+
malformed_xml_pattern = r'^<\?xml\s+version=["\'][\d.]+["\']\s{2,}encoding=["\'][a-zA-Z\d\-\.]+["\'].*?\?>'
235237

236238
for filename in list(self.files.keys()):
237239
ext = filename.split('.')[-1]
238-
if ext in ['html', 'xhtml']:
239-
html = self.files[filename]
240-
html = html.lstrip()
241-
if not re.match(regex, html, re.IGNORECASE):
242-
html = encoding + '\n' + html
243-
self.fixed_problems.append(f"Fixed encoding for file {filename}")
244-
self.files[filename] = html
240+
# Check HTML, XHTML, XML, OPF, and NCX files
241+
if ext in ['html', 'xhtml', 'xml', 'opf', 'ncx']:
242+
content = self.files[filename]
243+
content = content.lstrip()
244+
245+
# First, check for malformed XML declaration (double/triple spaces)
246+
if re.match(malformed_xml_pattern, content, re.IGNORECASE):
247+
# Replace malformed declaration with clean one
248+
content = re.sub(
249+
r'^<\?xml\s+version=["\'][\d.]+["\']\s+encoding=["\'][a-zA-Z\d\-\.]+["\'].*?\?>',
250+
encoding,
251+
content,
252+
count=1,
253+
flags=re.IGNORECASE
254+
)
255+
self.fixed_problems.append(f"Fixed malformed XML declaration in {filename}")
256+
# Then check if encoding declaration is missing
257+
elif not re.match(regex, content, re.IGNORECASE):
258+
content = encoding + '\n' + content
259+
self.fixed_problems.append(f"Added encoding declaration to {filename}")
260+
261+
self.files[filename] = content
245262

246263
def fix_body_id_link(self):
247264
"""Fix linking to body ID showing up as unresolved hyperlink"""

0 commit comments

Comments
 (0)