diff --git a/httpie/output/formatters/xml.py b/httpie/output/formatters/xml.py index 6752ab0f57..581f1ef04d 100644 --- a/httpie/output/formatters/xml.py +++ b/httpie/output/formatters/xml.py @@ -21,9 +21,15 @@ def parse_declaration(raw_body: str) -> Optional[str]: body = raw_body.strip() # XMLDecl ::= '' if body.startswith(XML_DECLARATION_OPEN): - end = body.find(XML_DECLARATION_CLOSE) - if end != -1: - return body[:end + len(XML_DECLARATION_CLOSE)] + # The declaration target is exactly ``xml``; a processing instruction + # such as ```` or ```` merely starts with + # it and must not be treated as the declaration (which would duplicate + # the instruction in the prettified output). + rest = body[len(XML_DECLARATION_OPEN):] + if rest[:1] in ('', ' ', '\t', '\r', '\n', '?'): + end = body.find(XML_DECLARATION_CLOSE) + if end != -1: + return body[:end + len(XML_DECLARATION_CLOSE)] def pretty_xml(document: 'Document', diff --git a/tests/test_xml.py b/tests/test_xml.py index a427b266e5..7ded991e4a 100644 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -97,3 +97,16 @@ def test_invalid_xml(file): # No formatting done, data is simply printed as-is. r = http(DUMMY_URL) assert xml_data in r + + +@pytest.mark.parametrize('target', ['xml-stylesheet', 'xml-model']) +def test_xml_leading_processing_instruction_not_duplicated(target): + """A leading ```` processing instruction (whose target merely + begins with ``xml``) must not be misdetected as the XML declaration and + duplicated in the prettified output. + """ + from httpie.output.formatters.xml import XMLFormatter + formatter = XMLFormatter(format_options={'xml': {'format': True, 'indent': 2}}) + body = f'x' + formatted = formatter.format_body(body, 'application/xml') + assert formatted.count(f'