Repository: collective/zpretty (language: python)

Issue:
Empty XML file after zpretty run
`zpretty -ix FILENAME.xml` removes all lines except the first from the file below ([see here what it is good for ](https://community.plone.org/t/overriding-zope-i18n-locale-settings/13677)).

I think this is a bug.

```
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE ldml SYSTEM "http://www.unicode.org/cldr/dtd/1.1/ldml.dtd">
<ldml>
	<identity>
		<version number="1.1"/>
		<generation date="2004-06-04" />
		<language type="de" /> 
		<territory type="AT" /> 
	</identity>
    <numbers>
        <symbols>
            <decimal>.</decimal>
            <group></group>
            <list>;</list>
            <percentSign>%</percentSign>
            <nativeZeroDigit>0</nativeZeroDigit>
            <patternDigit>#</patternDigit>
            <plusSign>+</plusSign>
            <minusSign>-</minusSign>
            <exponential>E</exponential>
            <perMille>‰</perMille>
            <infinity>∞</infinity>
            <nan>�</nan>
        </symbols>	
    </numbers>
    <dates>
		<calendars>
			<calendar type="gregorian">
				<months>
					<monthContext type="format">
						<monthWidth type="abbreviated">
							<month type="1">Jän</month>
							<month type="2">Feb</month>
							<month type="3">Mär</month>
							<month type="4">Apr</month>
							<month type="5">Mai</month>
							<month type="6">Jun</month>
							<month type="7">Jul</month>
							<month type="8">Aug</month>
							<month type="9">Sep</month>
							<month type="10">Okt</month>
							<month type="11">Nov</month>
							<month type="12">Dez</month>
						</monthWidth>
						<monthWidth type="wide">
							<month type="1">Jänner</month>
							<month type="2">Feber</month>
							<month type="3">März</month>
							<month type="4">April</month>
							<month type="5">Mai</month>
							<month type="6">Juni</month>
							<month type="7">Juli</month>
							<month type="8">August</month>
							<month type="9">September</month>
							<month type="10">Oktober</month>
							<month type="11">November</month>
							<month type="12">Dezember</month>
						</monthWidth>
					</monthContext>
				</months>
				<week>
					<minDays count="1"/>
					<firstDay day="mon"/>
				</week>
				<dateFormats>
					<default type="medium"/>
					<dateFormatLength type="full">
						<dateFormat >
							<pattern>EEEE, dd. MMMM yyyy</pattern>
						</dateFormat>
					</dateFormatLength >
					<dateFormatLength type="long">
						<dateFormat >
							<pattern>dd. MMMM yyyy</pattern>
						</dateFormat>
					</dateFormatLength >
					<dateFormatLength type="medium">
						<dateFormat >
							<pattern>dd.MM.yyyy</pattern>
						</dateFormat>
					</dateFormatLength >
					<dateFormatLength type="short">
						<dateFormat >
							<pattern>dd.MM.yy</pattern>
						</dateFormat>
					</dateFormatLength >
				</dateFormats>
				<timeFormats>
					<default type="medium"/>
					<timeFormatLength type="full">
						<timeFormat >
							<pattern>HH:mm' Uhr 'z</pattern>
						</timeFormat>
					</timeFormatLength >
					<timeFormatLength type="long">
						<timeFormat >
							<pattern>HH:mm:ss z</pattern>
						</timeFormat>
					</timeFormatLength >
					<timeFormatLength type="medium">
						<timeFormat >
							<pattern>HH:mm:ss</pattern>
						</timeFormat>
					</timeFormatLength >
					<timeFormatLength type="short">
						<timeFormat >
							<pattern>HH:mm</pattern>
						</timeFormat>
					</timeFormatLength >
				</timeFormats>
				<dateTimeFormats>
					<dateTimeFormatLength >
						<dateTimeFormat>
							<pattern>{1} {0}</pattern>
						</dateTimeFormat>
					</dateTimeFormatLength >
				</dateTimeFormats>
			</calendar>
		</calendars>
	</dates>
</ldml>
```


Discussion / hints:
I see it it gets confused by the `<!DOCTYPE ldml SYSTEM "http://www.unicode.org/cldr/dtd/1.1/ldml.dtd">`.
Thanks for reporting! 
I will try to fix this. 


Fix patch:
diff --git a/HISTORY.md b/HISTORY.md
index 19e9188..989a5ce 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -3,6 +3,7 @@
 2.2.1 (unreleased)
 ------------------
 
+- Do not kill XML documents with a doctype (Fixes #47) [ale-rt]
 - Support Python 3.10 [ale-rt]
 
 
diff --git a/zpretty/elements.py b/zpretty/elements.py
index db987a2..e036bb9 100644
--- a/zpretty/elements.py
+++ b/zpretty/elements.py
@@ -150,7 +150,7 @@ def is_self_closing(self):
 
     def is_null(self):
         """We define a special tag null_tag_name to wrap text"""
-        return self.context.name == self.null_tag_name
+        return self.context.name in (self.null_tag_name, BeautifulSoup.ROOT_TAG_NAME)
 
     def is_processing_instruction(self):
         """Check if this element is a processing instruction like <?xml...>"""
@@ -241,7 +241,7 @@ def render_doctype(self):
 
     def render_processing_instruction(self):
         """Render a properly indented processing instruction"""
-        return f"{self.prefix}<?{self.text}?>"
+        return f"{self.prefix}<?{self.text.rstrip('?')}?>"
 
     def render_text(self):
         """Render a properly indented text
diff --git a/zpretty/prettifier.py b/zpretty/prettifier.py
index a6b00c1..7b93250 100644
--- a/zpretty/prettifier.py
+++ b/zpretty/prettifier.py
@@ -1,4 +1,6 @@
 from bs4 import BeautifulSoup
+from bs4.element import Doctype
+from bs4.element import ProcessingInstruction
 from logging import getLogger
 from uuid import uuid4
 from zpretty.elements import PrettyElement
@@ -44,12 +46,16 @@ def __init__(self, filename="", text="", encoding="utf8"):
     def get_soup(self, text):
         """Tries to get the soup from the given test
 
-        At first it will try to parse the text:
-
-        1. as an xml
-        2. as an html
-        3. will just return the unparsed text
+        If the text is not some xml like think a dummy element will be used to wrap it.
         """
+        original_soup = BeautifulSoup(text, self.parser, builder=self.builder)
+        try:
+            first_el = next(original_soup.children)
+        except StopIteration:
+            first_el = None
+        if isinstance(first_el, (Doctype, ProcessingInstruction)):
+            return original_soup
+
         markup = "<{null}>{text}</{null}>".format(
             null=self.pretty_element.null_tag_name, text=text
         )
diff --git a/zpretty/xml.py b/zpretty/xml.py
index cf84505..2be6a08 100644
--- a/zpretty/xml.py
+++ b/zpretty/xml.py
@@ -37,10 +37,10 @@ def is_self_closing(self):
     @property
     def tag(self):
         """Return the tag name"""
-        if not self.context.prefix:
+        prefix = getattr(self.context, "prefix", "")
+        if not prefix:
             return self.context.name
-        else:
-            return ":".join((self.context.prefix, self.context.name))
+        return f"{prefix}:{self.context.name}"
 
     def render_text(self):
         """Add an empty line between each element"""


Test patch:
diff --git a/zpretty/tests/original/sample_dtml.dtml b/zpretty/tests/original/sample_dtml.dtml
new file mode 100644
index 0000000..c1cad26
--- /dev/null
+++ b/zpretty/tests/original/sample_dtml.dtml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE ldml SYSTEM "http://www.unicode.org/cldr/dtd/1.1/ldml.dtd">
+<ldml>
+  <identity>
+    <version number="1.1"></version>
+    <generation date="2004-06-04"></generation>
+    <language type="de"></language>
+    <territory type="AT"></territory>
+  </identity>
+</ldml>
diff --git a/zpretty/tests/test_zpretty.py b/zpretty/tests/test_zpretty.py
index 9a878e1..16594e4 100644
--- a/zpretty/tests/test_zpretty.py
+++ b/zpretty/tests/test_zpretty.py
@@ -171,6 +171,9 @@ def test_entities(self):
     def test_single_quotes_in_attrs(self):
         self.assertPrettified('<root a="\'" />', '<root a="\'"></root>\n')
 
+    def test_sample_dtml(self):
+        self.prettify("sample_dtml.dtml")
+
     def test_sample_html(self):
         self.prettify("sample_html.html")