diff --git a/doc/SPEC.md b/doc/SPEC.md index 696e508..e015e22 100644 --- a/doc/SPEC.md +++ b/doc/SPEC.md @@ -41,7 +41,7 @@ A Mews page MUST be a well-formed, valid XHTML-MP 1.2 document that also follows ### 3.1 Prologue and doctype -A Mews page MUST begin with an XML declaration specifying UTF-8, followed by the XHTML-MP 1.2 doctype: +A Mews page MUST begin with an XML declaration specifying UTF-8, followed by the XHTML-MP 1.2 doctype. Either quote character may be used in the declaration, as XML allows: ```xml diff --git a/mews/lint.py b/mews/lint.py index da2280f..5ed1b36 100644 --- a/mews/lint.py +++ b/mews/lint.py @@ -46,7 +46,12 @@ SIZE_SHOULD = 64 * 1024 SIZE_TOTAL = 320 * 1024 IMAGE_SHOULD = 50 * 1024 -XML_DECLARATION = re.compile(rb'^<\?xml version="1\.0" encoding="(?i:UTF-8)"\?>') +# Either quote character, because XML permits both and serialisers differ: +# lxml writes single quotes. Section 3.1 asks for a declaration naming UTF-8, +# and its example is one way of writing that rather than the only way. +XML_DECLARATION = re.compile( + rb"^<\?xml\s+version\s*=\s*([\"'])1\.0\1\s+encoding\s*=\s*([\"'])(?i:UTF-8)\2\s*\?>" +) DOCTYPE = re.compile( rb'' diff --git a/tests/test_lint_rules.py b/tests/test_lint_rules.py index bb9e29f..9b5b1e2 100644 --- a/tests/test_lint_rules.py +++ b/tests/test_lint_rules.py @@ -34,6 +34,17 @@ def test_rule_failure_maps_to_its_section(fail_page, name, expected): assert (match[0].section, match[0].level) == (section, level) +def test_either_quote_character_opens_the_declaration(conforming): + """Serialisers differ: lxml writes single quotes, and XML allows them.""" + page = conforming().replace( + b'', + b"", + ) + report = validate_bytes(page) + assert "xml-declaration" not in [f.code for f in report.findings] + assert report.conforms + + def test_entity_declarations_are_refused_before_parsing(fail_page): """A page declaring its own entities is rejected without expanding them.""" report = validate_bytes(fail_page("billion-laughs"))