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