View Javadoc
1   /*
2    * SPDX-License-Identifier: Apache-2.0
3    * See LICENSE file for details.
4    *
5    * Copyright 2025-2026 Hazendaz
6    * Copyright 2011-2025 Acegi Technology Pty Limited.
7    */
8   package au.com.acegi.xmlformat;
9   
10  import static java.nio.charset.StandardCharsets.UTF_8;
11  
12  import static au.com.acegi.xmlformat.FormatUtil.format;
13  import static au.com.acegi.xmlformat.FormatUtil.formatInPlace;
14  import static au.com.acegi.xmlformat.FormatUtil.needsFormatting;
15  import static au.com.acegi.xmlformat.TestUtil.getResource;
16  import static au.com.acegi.xmlformat.TestUtil.streamToString;
17  import static au.com.acegi.xmlformat.TestUtil.stringToFile;
18  import static org.hamcrest.CoreMatchers.containsString;
19  import static org.hamcrest.CoreMatchers.is;
20  import static org.hamcrest.MatcherAssert.assertThat;
21  import static org.junit.jupiter.api.Assertions.assertThrows;
22  
23  import java.io.ByteArrayOutputStream;
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.InputStream;
27  
28  import org.dom4j.DocumentException;
29  import org.junit.jupiter.api.Test;
30  import org.junit.jupiter.api.io.TempDir;
31  
32  /** Tests {@link FormatUtil}. */
33  public class FormatUtilTest {
34  
35      private static final String FORMATTED_XML = "<xml><hello/></xml>";
36      private static final String UNFORMATTED_XML = "<xml>   <hello/> </xml>";
37  
38      @TempDir
39      private File tmp;
40  
41      @Test
42      void formattedWillNotChange() throws DocumentException, IOException {
43          inPlaceChange(FORMATTED_XML, false);
44      }
45  
46      @Test
47      void test1() throws DocumentException, IOException {
48          final XmlOutputFormat fmt = new XmlOutputFormat();
49          fmt.setIndentSize(4);
50          fmt.setNewLineAfterDeclaration(false);
51          fmt.setPadText(false);
52          testInOut(1, fmt);
53      }
54  
55      @Test
56      void test1KeepBlankLines() throws DocumentException, IOException {
57          final XmlOutputFormat fmt = new XmlOutputFormat();
58          fmt.setIndentSize(4);
59          fmt.setNewLineAfterDeclaration(false);
60          fmt.setPadText(false);
61          fmt.setKeepBlankLines(true);
62          testInOut(1, fmt);
63      }
64  
65      @Test
66      void test2() throws DocumentException, IOException {
67          final XmlOutputFormat fmt = new XmlOutputFormat();
68          fmt.setIndentSize(2);
69          fmt.setNewLineAfterDeclaration(false);
70          fmt.setPadText(false);
71          testInOut(2, fmt);
72      }
73  
74      @Test
75      void test2KeepBlankLines() throws DocumentException, IOException {
76          final XmlOutputFormat fmt = new XmlOutputFormat();
77          fmt.setIndentSize(2);
78          fmt.setNewLineAfterDeclaration(false);
79          fmt.setPadText(false);
80          fmt.setKeepBlankLines(true);
81          testInOut(2, fmt);
82      }
83  
84      @Test
85      void test3() throws DocumentException, IOException {
86          final XmlOutputFormat fmt = new XmlOutputFormat();
87          fmt.setIndentSize(2);
88          fmt.setNewLineAfterDeclaration(false);
89          fmt.setPadText(false);
90          testInOut(3, fmt);
91      }
92  
93      @Test
94      void test4() throws DocumentException, IOException {
95          final XmlOutputFormat fmt = new XmlOutputFormat();
96          fmt.setIndent("\t");
97          fmt.setNewLineAfterDeclaration(false);
98          fmt.setPadText(false);
99          testInOut(4, fmt);
100     }
101 
102     @Test
103     void test4KeepBlankLines() throws DocumentException, IOException {
104         final XmlOutputFormat fmt = new XmlOutputFormat();
105         fmt.setIndent("\t");
106         fmt.setNewLineAfterDeclaration(false);
107         fmt.setPadText(false);
108         fmt.setKeepBlankLines(true);
109         testInOut(4, fmt);
110     }
111 
112     @Test
113     void test5() throws DocumentException, IOException {
114         final XmlOutputFormat fmt = new XmlOutputFormat();
115         fmt.setIndent("    ");
116         fmt.setNewLineAfterDeclaration(false);
117         fmt.setPadText(false);
118         fmt.setTrimText(true);
119         testInOut(5, fmt);
120     }
121 
122     /**
123      * New lines between the XML declaration and the root elements are ignored at the parse level it seems, they don't
124      * reach the XMLWriter. Not ideal, but believe we can leave with this exception
125      */
126     @Test
127     void test5KeepBlankLines() throws DocumentException, IOException {
128         final XmlOutputFormat fmt = new XmlOutputFormat();
129         fmt.setIndent("    ");
130         // Set to true to keep the new line given keep blank lines will not
131         fmt.setNewLineAfterDeclaration(true);
132         fmt.setPadText(false);
133         fmt.setTrimText(true);
134         fmt.setKeepBlankLines(true);
135         testInOut(5, fmt);
136     }
137 
138     /**
139      * New lines between the XML declaration and the root elements are ignored at the parse level it seems, they don't
140      * reach the XMLWriter. Not ideal, but believe we can leave with this exception
141      */
142     @Test
143     void test6() throws DocumentException, IOException {
144         final XmlOutputFormat fmt = new XmlOutputFormat();
145         fmt.setIndent("    ");
146         // Set to true to keep the new line given keep blank lines will not
147         fmt.setNewLineAfterDeclaration(true);
148         fmt.setPadText(false);
149         fmt.setTrimText(true);
150         fmt.setKeepBlankLines(true);
151         testInOut(6, fmt);
152     }
153 
154     @Test
155     void testInvalid() throws DocumentException, IOException {
156         assertThrows(DocumentException.class, () -> {
157             try (InputStream in = getResource("/invalid.xml")) {
158                 final ByteArrayOutputStream out = new ByteArrayOutputStream();
159                 format(in, out, new XmlOutputFormat());
160             }
161         });
162     }
163 
164     @Test
165     void testWithExternalDoctypeDoesNotResolveNetworkEntity() throws DocumentException, IOException {
166         final String withExternalDtd = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
167                 + "<!DOCTYPE root SYSTEM \"https://example.invalid/non-existent.dtd\">\n" + "<root><child/></root>";
168         final ByteArrayOutputStream out = new ByteArrayOutputStream();
169         format(new java.io.ByteArrayInputStream(withExternalDtd.getBytes(UTF_8)), out, new XmlOutputFormat());
170 
171         final String formatted = out.toString(UTF_8);
172         assertThat(formatted, containsString("<root>"));
173         assertThat(formatted, containsString("<child/>"));
174     }
175 
176     @Test
177     void unformattedWillChange() throws DocumentException, IOException {
178         inPlaceChange(UNFORMATTED_XML, true);
179     }
180 
181     @Test
182     void needsFormattingReturnsFalseForEmptyFile() throws DocumentException, IOException {
183         final File file = File.createTempFile("junit", null, tmp);
184         // empty file: length == 0
185         assertThat(needsFormatting(file, new XmlOutputFormat()), is(false));
186     }
187 
188     @Test
189     void needsFormattingReturnsFalseForFormattedFile() throws DocumentException, IOException {
190         final File file = File.createTempFile("junit", null, tmp);
191         stringToFile(FORMATTED_XML, file);
192 
193         final XmlOutputFormat fmt = new XmlOutputFormat();
194         fmt.setSuppressDeclaration(true);
195         fmt.setIndent("");
196         fmt.setNewlines(false);
197 
198         assertThat(needsFormatting(file, fmt), is(false));
199     }
200 
201     @Test
202     void needsFormattingReturnsTrueForUnformattedFile() throws DocumentException, IOException {
203         final File file = File.createTempFile("junit", null, tmp);
204         stringToFile(UNFORMATTED_XML, file);
205 
206         final XmlOutputFormat fmt = new XmlOutputFormat();
207         fmt.setSuppressDeclaration(true);
208         fmt.setIndent("");
209         fmt.setNewlines(false);
210 
211         assertThat(needsFormatting(file, fmt), is(true));
212     }
213 
214     @Test
215     void formatInPlaceReturnsFalseForEmptyFile() throws DocumentException, IOException {
216         final File file = File.createTempFile("junit", null, tmp);
217         assertThat(formatInPlace(file, new XmlOutputFormat()), is(false));
218     }
219 
220     private void inPlaceChange(final String txt, final boolean shouldChange) throws DocumentException, IOException {
221         final File file = File.createTempFile("junit", null, tmp);
222         stringToFile(txt, file);
223 
224         final XmlOutputFormat fmt = new XmlOutputFormat();
225         fmt.setSuppressDeclaration(true);
226         fmt.setIndent("");
227         fmt.setNewlines(false);
228 
229         final boolean written = formatInPlace(file, fmt);
230         assertThat(written, is(shouldChange));
231     }
232 
233     private void testInOut(final int id, final XmlOutputFormat fmt) throws DocumentException, IOException {
234         try (InputStream in = getResource("/test" + id + "-in.xml")) {
235             final ByteArrayOutputStream out = new ByteArrayOutputStream();
236             format(in, out, fmt);
237 
238             final String received = new String(out.toByteArray(), UTF_8);
239             final String expected = streamToString(getResource(getOutputFileName(id, fmt)));
240             assertThat(received, is(expected));
241         }
242     }
243 
244     private String getOutputFileName(final int id, final XmlOutputFormat fmt) {
245         if (fmt.isKeepBlankLines()) {
246             return "/test" + id + "-out-kbl.xml";
247         }
248         return "/test" + id + "-out.xml";
249     }
250 }