An XML formatter can be stable and still change text
Paste XML to format and download it below. Compare the parsed text before using a reformatted file: our local two-pass controls show that a formatter can keep adding whitespace, while a stable second pass still does not prove that the first pass preserved the original text.
No formatted output yet.
Download XMLMeasured November 5, 2026 using Python 3.12.14 and ElementTree 1.3.0. Six newly constructed XML inputs were formatted with ElementTree.indent plus tostring and the bundled minidom.toprettyxml, each with two-space indentation. Every first-pass output became the next input. ElementTree and minidom independently read the saved text back.
A second pass is a separate test
The compact batch input contained 47 bytes and the text northsouth. ElementTree produced 54 bytes on both passes. Minidom produced 94 bytes first and 110 bytes second. Its concatenated text grew from 17 to 33 characters between passes, whereas ElementTree stayed at 17. Element counts remained unchanged. Counting elements alone therefore missed the additional text nodes in this supplied pair.
The preformatted control had the same two row elements and values as the compact input, but already contained the chosen line breaks and indentation. Its input was 54 bytes. ElementTree returned 54 bytes twice; minidom returned 110 and then 126. Both paths received identical bytes for this control. Existing indentation, rather than a different set of element values, distinguished it from the compact case.
Scroll the result table sideways on narrow screens.
| Fixture | Local path | Input bytes | First bytes | Second bytes | Second = first bytes | Text length first → second |
|---|---|---|---|---|---|---|
| compact | elementtree | 47 | 54 | 54 | True | 17 → 17 |
| compact | minidom | 47 | 94 | 110 | False | 17 → 33 |
| preformatted | elementtree | 54 | 54 | 54 | True | 17 → 17 |
| preformatted | minidom | 54 | 110 | 126 | False | 33 → 49 |
| mixed-fresh | elementtree | 54 | 54 | 54 | True | 18 → 18 |
| mixed-fresh | minidom | 54 | 104 | 114 | False | 28 → 38 |
| preserve-inherited | elementtree | 75 | 92 | 92 | True | 31 → 31 |
| preserve-inherited | minidom | 75 | 132 | 170 | False | 31 → 69 |
| preserve-reset | elementtree | 95 | 112 | 112 | True | 31 → 31 |
| preserve-reset | minidom | 95 | 152 | 190 | False | 31 → 69 |
| pad-fresh | elementtree | 61 | 68 | 68 | True | 16 → 16 |
| pad-fresh | minidom | 61 | 108 | 124 | False | 16 → 32 |
Inherited preserve and explicit reset
The 75-byte inherited-preserve control put xml:space on its document element and nested two value elements inside a group. ElementTree returned 92 bytes on each pass. Minidom returned 132 and then 170. Both first outputs contained 31 concatenated text characters; only the minidom second output grew to 69. The original preserve attribute remained present, so retaining that attribute did not establish text preservation.
Adding an explicit xml:space default reset to the nested group made a separate 95-byte control. Its ElementTree exports were 112 bytes twice; its minidom exports were 152 and 190. The reset and inherited cases had matching parsed text within each corresponding path and pass. This measured pair shows that the attribute change did not produce a different whitespace policy in these default calls.
Read text, not just element counts
The 61-byte pad control contained three literal spaces in a leaf element beside a cobalt value. ElementTree returned 68 bytes twice, while minidom returned 108 and 124. Both readers agreed on 16 concatenated text characters after the first pass and 32 after the second minidom pass. The three-space leaf survived; the growth occurred in surrounding text. The report keeps each text and tail value rather than trimming them away.
Mixed content needs a separate check. Our fresh note puts text before and after an inline part element. ElementTree kept that control at 54 bytes on both passes, while minidom returned 104 and 114. This is a known formatting hazard; examine the complete text, including spaces and newlines, rather than assuming that matching element names preserve a sentence.
A byte comparison, a text comparison and an element count answer different questions. This experiment intentionally counts all parsed whitespace as text. Some applications ignore layout-only whitespace under their own rules; the test does not decide those application rules. Stable output only establishes that a particular operation stopped changing these particular bytes.
Does a stable second pass mean the XML is unchanged? No: compare against the original, not only against the first result. Does xml:space force every formatter to protect text? These default local calls changed text while retaining the attribute. Are these results a ranking of online formatters? No: the saved outputs came from the named Python paths.
Reproduce the two passes
Run the reproduction script in an empty folder with the stated Python environment. It creates the fixed inputs, both passes and the raw report. Full checksums and text maps accompany every output. Test files are small constructed controls, not untrusted documents; no external resource, performance ceiling or cross-browser compatibility test was performed.
python reproduce.py- reproduce.py
- results.json
- six-column-audit.json
- compact.xml
- preformatted.xml
- mixed-fresh.xml
- preserve-inherited.xml
- preserve-reset.xml
- pad-fresh.xml
- compact-elementtree-pass1.xml
- compact-elementtree-pass2.xml
- compact-minidom-pass1.xml
- compact-minidom-pass2.xml
- preformatted-elementtree-pass1.xml
- preformatted-elementtree-pass2.xml
- preformatted-minidom-pass1.xml
- preformatted-minidom-pass2.xml
- mixed-fresh-elementtree-pass1.xml
- mixed-fresh-elementtree-pass2.xml
- mixed-fresh-minidom-pass1.xml
- mixed-fresh-minidom-pass2.xml
- preserve-inherited-elementtree-pass1.xml
- preserve-inherited-elementtree-pass2.xml
- preserve-inherited-minidom-pass1.xml
- preserve-inherited-minidom-pass2.xml
- preserve-reset-elementtree-pass1.xml
- preserve-reset-elementtree-pass2.xml
- preserve-reset-minidom-pass1.xml
- preserve-reset-minidom-pass2.xml
- pad-fresh-elementtree-pass1.xml
- pad-fresh-elementtree-pass2.xml
- pad-fresh-minidom-pass1.xml
- pad-fresh-minidom-pass2.xml
- prose-audit.json
Limits and related tools
This browser tool conservatively leaves a whole subtree inline when its element has direct non-whitespace text or xml:space preserve. Leaf elements are serialized without reindentation. Other element-only content receives layout whitespace. Parsing and serialization can change lexical details such as quoting or empty-tag notation. DOCTYPE input is rejected. This is not byte preservation, schema validation or a signature-safe transformation. Input stays in this page.



