Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't inconsistently add XML declarations #1227

Merged
merged 1 commit into from
Dec 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 3 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
34 changes: 27 additions & 7 deletions httpie/output/formatters/xml.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import sys
from typing import TYPE_CHECKING, Optional

from ...encoding import UTF8
Expand All @@ -8,27 +7,47 @@
from xml.dom.minidom import Document


XML_DECLARATION_OPEN = '<?xml'
XML_DECLARATION_CLOSE = '?>'


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 ::= '<?xml' DECL_CONTENT '?>'
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)
isidentical marked this conversation as resolved.
Show resolved Hide resolved


class XMLFormatter(FormatterPlugin):
Expand All @@ -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:
Expand All @@ -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
3 changes: 3 additions & 0 deletions tests/fixtures/xmldata/valid/custom-header.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?><!-- comment -->
<root><element key="value">text</element><element>text</element>tail<empty-element/></root>
<!-- comment -->
9 changes: 9 additions & 0 deletions tests/fixtures/xmldata/valid/custom-header_formatted.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- comment -->
<root>
<element key="value">text</element>
<element>text</element>
tail
<empty-element/>
</root>

1 change: 0 additions & 1 deletion tests/fixtures/xmldata/valid/simple-ns_formatted.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<?pi data?>
<!-- comment -->
<root xmlns="namespace">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<a/>
1 change: 1 addition & 0 deletions tests/fixtures/xmldata/valid/simple-single-tag_raw.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<a></a>
1 change: 0 additions & 1 deletion tests/fixtures/xmldata/valid/simple_formatted.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- comment -->
<root>
<element key="value">text</element>
Expand Down