diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e3b9a5f4f..ff3773394f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). - Added support for basic JSON types on `--form`/`--multipart` when using JSON only operators (`:=`/`:=@`). ([#1212](https://github.com/httpie/httpie/issues/1212)) - Added support for automatically enabling `--stream` when `Content-Type` is `text/event-stream`. ([#376](https://github.com/httpie/httpie/issues/376)) - Broken plugins will no longer crash the whole application. ([#1204](https://github.com/httpie/httpie/issues/1204)) +- Fixed auto addition of XML declaration to every formatted XML response. ([#1156](https://github.com/httpie/httpie/issues/1156)) ## [2.6.0](https://github.com/httpie/httpie/compare/2.5.0...2.6.0) (2021-10-14) diff --git a/docs/README.md b/docs/README.md index 5fd320db97..e515304c08 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1747,7 +1747,9 @@ Formatting has the following effects: to the characters they represent. - XML and XHTML data is indented. -You can further control the applied formatting via the more granular [format options](#format-options). +Please note that sometimes there might be changes made by formatters on the actual response body (e.g +collapsing empty tags on XML) but the end result will always be semantically indistinguishable. Some of +these formatting changes can be configured more granularly through [format options](#format-options). ### Format options diff --git a/httpie/output/formatters/xml.py b/httpie/output/formatters/xml.py index 3d63fbd574..6752ab0f57 100644 --- a/httpie/output/formatters/xml.py +++ b/httpie/output/formatters/xml.py @@ -1,4 +1,3 @@ -import sys from typing import TYPE_CHECKING, Optional from ...encoding import UTF8 @@ -8,27 +7,47 @@ from xml.dom.minidom import Document +XML_DECLARATION_OPEN = '' + + def parse_xml(data: str) -> 'Document': """Parse given XML `data` string into an appropriate :class:`~xml.dom.minidom.Document` object.""" from defusedxml.minidom import parseString return parseString(data) +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)] + + def pretty_xml(document: 'Document', + declaration: Optional[str] = None, encoding: Optional[str] = UTF8, - indent: int = 2, - standalone: Optional[bool] = None) -> str: + indent: int = 2) -> str: """Render the given :class:`~xml.dom.minidom.Document` `document` into a prettified string.""" kwargs = { 'encoding': encoding or UTF8, 'indent': ' ' * indent, } - if standalone is not None and sys.version_info >= (3, 9): - kwargs['standalone'] = standalone body = document.toprettyxml(**kwargs).decode(kwargs['encoding']) # Remove blank lines automatically added by `toprettyxml()`. - return '\n'.join(line for line in body.splitlines() if line.strip()) + lines = [line for line in body.splitlines() if line.strip()] + + # xml.dom automatically adds the declaration, even if + # it is not present in the actual body. Remove it. + if len(lines) >= 1 and parse_declaration(lines[0]): + lines.pop(0) + if declaration: + lines.insert(0, declaration) + + return '\n'.join(lines) class XMLFormatter(FormatterPlugin): @@ -44,6 +63,7 @@ def format_body(self, body: str, mime: str): from xml.parsers.expat import ExpatError from defusedxml.common import DefusedXmlException + declaration = parse_declaration(body) try: parsed_body = parse_xml(body) except ExpatError: @@ -54,6 +74,6 @@ def format_body(self, body: str, mime: str): body = pretty_xml(parsed_body, encoding=parsed_body.encoding, indent=self.format_options['xml']['indent'], - standalone=parsed_body.standalone) + declaration=declaration) return body diff --git a/tests/fixtures/xmldata/valid/custom-header.xml b/tests/fixtures/xmldata/valid/custom-header.xml new file mode 100644 index 0000000000..9bc0570920 --- /dev/null +++ b/tests/fixtures/xmldata/valid/custom-header.xml @@ -0,0 +1,3 @@ + +texttexttail + diff --git a/tests/fixtures/xmldata/valid/custom-header_formatted.xml b/tests/fixtures/xmldata/valid/custom-header_formatted.xml new file mode 100644 index 0000000000..4c75e11a3c --- /dev/null +++ b/tests/fixtures/xmldata/valid/custom-header_formatted.xml @@ -0,0 +1,9 @@ + + + + text + text + tail + + + diff --git a/tests/fixtures/xmldata/valid/simple-ns_formatted.xml b/tests/fixtures/xmldata/valid/simple-ns_formatted.xml index 8273afca19..43b49c6203 100644 --- a/tests/fixtures/xmldata/valid/simple-ns_formatted.xml +++ b/tests/fixtures/xmldata/valid/simple-ns_formatted.xml @@ -1,4 +1,3 @@ - diff --git a/tests/fixtures/xmldata/valid/simple-single-tag_formatted.xml b/tests/fixtures/xmldata/valid/simple-single-tag_formatted.xml new file mode 100644 index 0000000000..d80a5e273c --- /dev/null +++ b/tests/fixtures/xmldata/valid/simple-single-tag_formatted.xml @@ -0,0 +1 @@ + diff --git a/tests/fixtures/xmldata/valid/simple-single-tag_raw.xml b/tests/fixtures/xmldata/valid/simple-single-tag_raw.xml new file mode 100644 index 0000000000..41ab602321 --- /dev/null +++ b/tests/fixtures/xmldata/valid/simple-single-tag_raw.xml @@ -0,0 +1 @@ + diff --git a/tests/fixtures/xmldata/valid/simple_formatted.xml b/tests/fixtures/xmldata/valid/simple_formatted.xml index 1638b5b6a7..9db0eba2ae 100644 --- a/tests/fixtures/xmldata/valid/simple_formatted.xml +++ b/tests/fixtures/xmldata/valid/simple_formatted.xml @@ -1,4 +1,3 @@ - text