View Javadoc
1   /*
2    * XML Format Maven Plugin (https://github.com/acegi/xml-format-maven-plugin)
3    *
4    * Copyright 2011-2025 Acegi Technology Pty Limited.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package au.com.acegi.xmlformat;
19  
20  import java.io.IOException;
21  import java.io.OutputStream;
22  import java.io.UnsupportedEncodingException;
23  import java.util.StringTokenizer;
24  
25  import org.apache.commons.lang3.StringUtils;
26  import org.dom4j.Node;
27  import org.dom4j.io.XMLWriter;
28  
29  /**
30   * Subclass of {@link XMLWriter} that preserves blank lines in outupt (at most one of them, among two subsequent tags).
31   */
32  class BlankLinesWriter extends XMLWriter {
33  
34      BlankLinesWriter(final OutputStream out, final XmlOutputFormat fmt) throws UnsupportedEncodingException {
35          super(out, fmt);
36      }
37  
38      @Override
39      protected void writeString(final String text) throws IOException {
40          if (text == null || text.length() == 0) {
41              return;
42          }
43  
44          String input = text;
45          if (isEscapeText()) {
46              input = escapeElementEntities(text);
47          }
48  
49          if (getOutputFormat().isTrimText()) {
50              boolean first = true;
51              final StringTokenizer tokenizer = new StringTokenizer(input, " \t\r\f");
52  
53              final NewLinesHandler newLinesHandler = new NewLinesHandler();
54              while (tokenizer.hasMoreTokens()) {
55                  final String token = tokenizer.nextToken();
56  
57                  // Only if more tokens exist, continue
58                  if (newLinesHandler.processToken(token, tokenizer.hasMoreTokens())) {
59                      continue;
60                  }
61  
62                  if (first) {
63                      first = false;
64                      if (lastOutputNodeType == Node.TEXT_NODE) {
65                          writer.write(" ");
66                      }
67                  } else {
68                      writer.write(" ");
69                  }
70  
71                  writer.write(token.trim());
72                  lastOutputNodeType = Node.TEXT_NODE;
73              }
74              newLinesHandler.finished();
75          } else {
76              lastOutputNodeType = Node.TEXT_NODE;
77              writer.write(input);
78          }
79      }
80  
81      private final class NewLinesHandler {
82          private int newLinesCount;
83  
84          /**
85           * Processes the token, counting the newlines and producing at most one in output.
86           *
87           * @param token
88           *            The token to be written
89           * @param hasMoreTokens
90           *            Does more tokens exist
91           *
92           * @return True if the token needs to be skipped (it's a newline or a set of newlines)
93           *
94           * @throws IOException
95           *             If an I/O error occurs.
96           */
97          private boolean processToken(final String token, final boolean hasMoreTokens) throws IOException {
98              final int tokenNewLines = StringUtils.countMatches(token, '\n');
99              if (tokenNewLines > 0) {
100                 newLinesCount += tokenNewLines;
101                 return hasMoreTokens;
102             }
103             if (newLinesCount > 1) {
104                 writer.write("\n");
105                 newLinesCount = 0;
106             }
107             return false;
108         }
109 
110         /**
111          * Marks the end of token streams, allows to emit a last newlines if the last tokens were all newlines.
112          *
113          * @throws IOException
114          *             If an I/O error occurs.
115          */
116         private void finished() throws IOException {
117             if (newLinesCount > 1) {
118                 writer.write("\n");
119             }
120         }
121     }
122 }