View Javadoc
1   /*
2    *    Copyright 2006-2023 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.mybatis.generator.internal;
17  
18  import static org.mybatis.generator.internal.util.messages.Messages.getString;
19  
20  import java.io.PrintWriter;
21  import java.io.StringWriter;
22  
23  import org.mybatis.generator.exception.ShellException;
24  import org.w3c.dom.Attr;
25  import org.w3c.dom.CDATASection;
26  import org.w3c.dom.Comment;
27  import org.w3c.dom.Document;
28  import org.w3c.dom.DocumentType;
29  import org.w3c.dom.Element;
30  import org.w3c.dom.EntityReference;
31  import org.w3c.dom.NamedNodeMap;
32  import org.w3c.dom.Node;
33  import org.w3c.dom.ProcessingInstruction;
34  import org.w3c.dom.Text;
35  
36  /**
37   * This class is used to generate a String representation of an XML document. It
38   * is very much based on the class dom.Writer from the Apache Xerces examples,
39   * but I've simplified and updated it.
40   *
41   * @author Andy Clark, IBM (Original work)
42   * @author Jeff Butler (derivation)
43   */
44  public class DomWriter {
45  
46      protected PrintWriter printWriter;
47  
48      protected boolean isXML11;
49  
50      public DomWriter() {
51          super();
52      }
53  
54      public synchronized String toString(Document document)
55              throws ShellException {
56          StringWriter sw = new StringWriter();
57          printWriter = new PrintWriter(sw);
58          write(document);
59          return sw.toString();
60      }
61  
62      protected Attr[] sortAttributes(NamedNodeMap attrs) {
63  
64          int len = (attrs != null) ? attrs.getLength() : 0;
65          Attr[] array = new Attr[len];
66          for (int i = 0; i < len; i++) {
67              array[i] = (Attr) attrs.item(i);
68          }
69          for (int i = 0; i < len - 1; i++) {
70              String name = array[i].getNodeName();
71              int index = i;
72              for (int j = i + 1; j < len; j++) {
73                  String curName = array[j].getNodeName();
74                  if (curName.compareTo(name) < 0) {
75                      name = curName;
76                      index = j;
77                  }
78              }
79              if (index != i) {
80                  Attr temp = array[i];
81                  array[i] = array[index];
82                  array[index] = temp;
83              }
84          }
85  
86          return array;
87  
88      }
89  
90      protected void normalizeAndPrint(String s, boolean isAttValue) {
91  
92          int len = (s != null) ? s.length() : 0;
93          for (int i = 0; i < len; i++) {
94              char c = s.charAt(i);
95              normalizeAndPrint(c, isAttValue);
96          }
97      }
98  
99      protected void normalizeAndPrint(char c, boolean isAttValue) {
100 
101         switch (c) {
102         case '<':
103             handleLessThan();
104             break;
105         case '>':
106             handleGreaterThan();
107             break;
108         case '&':
109             handleAmpersand();
110             break;
111         case '"':
112             handleDoubleQuote(isAttValue);
113             break;
114         case '\r':
115             handleCarriageReturn();
116             break;
117         case '\n':
118             handleLineFeed();
119             break;
120         default:
121             handleDefault(c, isAttValue);
122         }
123     }
124 
125     private void handleDefault(char c, boolean isAttValue) {
126         // In XML 1.1, control chars in the ranges [#x1-#x1F, #x7F-#x9F]
127         // must be escaped.
128         //
129         // Escape space characters that would be normalized to #x20 in
130         // attribute values
131         // when the document is reparsed.
132         //
133         // Escape NEL (0x85) and LSEP (0x2028) that appear in content
134         // if the document is XML 1.1, since they would be normalized to LF
135         // when the document is reparsed.
136         if (isXML11
137                 && ((c >= 0x01 && c <= 0x1F && c != 0x09 && c != 0x0A)
138                         || (c >= 0x7F && c <= 0x9F) || c == 0x2028)
139                 || isAttValue && (c == 0x09 || c == 0x0A)) {
140             printWriter.print("&#x"); //$NON-NLS-1$
141             printWriter.print(Integer.toHexString(c).toUpperCase());
142             printWriter.print(';');
143         } else {
144             printWriter.print(c);
145         }
146     }
147 
148     private void handleLineFeed() {
149         // If LF is part of the document's content, it
150         // should be printed back out with the system default
151         // line separator.  XML parsing forces \n only after a parse,
152         // but we should write it out as it was to avoid whitespace
153         // commits on some version control systems.
154         printWriter.print(System.getProperty("line.separator")); //$NON-NLS-1$
155     }
156 
157     private void handleCarriageReturn() {
158         // If CR is part of the document's content, it
159         // must be printed as a literal otherwise
160         // it would be normalized to LF when the document
161         // is reparsed.
162         printWriter.print("&#xD;"); //$NON-NLS-1$
163     }
164 
165     private void handleDoubleQuote(boolean isAttValue) {
166         // A '"' that appears in character data
167         // does not need to be escaped.
168         if (isAttValue) {
169             printWriter.print("&quot;"); //$NON-NLS-1$
170         } else {
171             printWriter.print('"');
172         }
173     }
174 
175     private void handleAmpersand() {
176         printWriter.print("&amp;"); //$NON-NLS-1$
177     }
178 
179     private void handleGreaterThan() {
180         printWriter.print("&gt;"); //$NON-NLS-1$
181     }
182 
183     private void handleLessThan() {
184         printWriter.print("&lt;"); //$NON-NLS-1$
185     }
186 
187     /**
188      * Extracts the XML version from the Document.
189      *
190      * @param document
191      *            the document
192      * @return the version
193      */
194     protected String getVersion(Document document) {
195         if (document == null) {
196             return null;
197         }
198 
199         return document.getXmlVersion();
200     }
201 
202     protected void writeAnyNode(Node node) throws ShellException {
203         // is there anything to do?
204         if (node == null) {
205             return;
206         }
207 
208         short type = node.getNodeType();
209         switch (type) {
210         case Node.DOCUMENT_NODE:
211             write((Document) node);
212             break;
213 
214         case Node.DOCUMENT_TYPE_NODE:
215             write((DocumentType) node);
216             break;
217 
218         case Node.ELEMENT_NODE:
219             write((Element) node);
220             break;
221 
222         case Node.ENTITY_REFERENCE_NODE:
223             write((EntityReference) node);
224             break;
225 
226         case Node.CDATA_SECTION_NODE:
227             write((CDATASection) node);
228             break;
229 
230         case Node.TEXT_NODE:
231             write((Text) node);
232             break;
233 
234         case Node.PROCESSING_INSTRUCTION_NODE:
235             write((ProcessingInstruction) node);
236             break;
237 
238         case Node.COMMENT_NODE:
239             write((Comment) node);
240             break;
241 
242         default:
243             throw new ShellException(getString(
244                     "RuntimeError.18", Short.toString(type))); //$NON-NLS-1$
245         }
246     }
247 
248     protected void write(Document node) throws ShellException {
249         isXML11 = "1.1".equals(getVersion(node)); //$NON-NLS-1$
250         if (isXML11) {
251             printWriter.println("<?xml version=\"1.1\" encoding=\"UTF-8\"?>"); //$NON-NLS-1$
252         } else {
253             printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); //$NON-NLS-1$
254         }
255         printWriter.flush();
256         write(node.getDoctype());
257         write(node.getDocumentElement());
258     }
259 
260     protected void write(DocumentType node) {
261         printWriter.print("<!DOCTYPE "); //$NON-NLS-1$
262         printWriter.print(node.getName());
263         String publicId = node.getPublicId();
264         String systemId = node.getSystemId();
265         if (publicId != null) {
266             printWriter.print(" PUBLIC \""); //$NON-NLS-1$
267             printWriter.print(publicId);
268             printWriter.print("\" \""); //$NON-NLS-1$
269             printWriter.print(systemId);
270             printWriter.print('\"');
271         } else if (systemId != null) {
272             printWriter.print(" SYSTEM \""); //$NON-NLS-1$
273             printWriter.print(systemId);
274             printWriter.print('"');
275         }
276 
277         String internalSubset = node.getInternalSubset();
278         if (internalSubset != null) {
279             printWriter.println(" ["); //$NON-NLS-1$
280             printWriter.print(internalSubset);
281             printWriter.print(']');
282         }
283         printWriter.println('>');
284     }
285 
286     protected void write(Element node) throws ShellException {
287         printWriter.print('<');
288         printWriter.print(node.getNodeName());
289         Attr[] attrs = sortAttributes(node.getAttributes());
290         for (Attr attr : attrs) {
291             printWriter.print(' ');
292             printWriter.print(attr.getNodeName());
293             printWriter.print("=\""); //$NON-NLS-1$
294             normalizeAndPrint(attr.getNodeValue(), true);
295             printWriter.print('"');
296         }
297 
298         if (node.getChildNodes().getLength() == 0) {
299             printWriter.print(" />"); //$NON-NLS-1$
300             printWriter.flush();
301         } else {
302             printWriter.print('>');
303             printWriter.flush();
304 
305             Node child = node.getFirstChild();
306             while (child != null) {
307                 writeAnyNode(child);
308                 child = child.getNextSibling();
309             }
310 
311             printWriter.print("</"); //$NON-NLS-1$
312             printWriter.print(node.getNodeName());
313             printWriter.print('>');
314             printWriter.flush();
315         }
316     }
317 
318     protected void write(EntityReference node) {
319         printWriter.print('&');
320         printWriter.print(node.getNodeName());
321         printWriter.print(';');
322         printWriter.flush();
323     }
324 
325     protected void write(CDATASection node) {
326         printWriter.print("<![CDATA["); //$NON-NLS-1$
327         String data = node.getNodeValue();
328         // XML parsers normalize line endings to '\n'. We should write
329         // it out as it was in the original to avoid whitespace commits
330         // on some version control systems
331         int len = (data != null) ? data.length() : 0;
332         for (int i = 0; i < len; i++) {
333             char c = data.charAt(i);
334             if (c == '\n') {
335                 handleLineFeed();
336             } else {
337                 printWriter.print(c);
338             }
339         }
340         printWriter.print("]]>"); //$NON-NLS-1$
341         printWriter.flush();
342     }
343 
344     protected void write(Text node) {
345         normalizeAndPrint(node.getNodeValue(), false);
346         printWriter.flush();
347     }
348 
349     protected void write(ProcessingInstruction node) {
350         printWriter.print("<?"); //$NON-NLS-1$
351         printWriter.print(node.getNodeName());
352         String data = node.getNodeValue();
353         if (data != null && data.length() > 0) {
354             printWriter.print(' ');
355             printWriter.print(data);
356         }
357         printWriter.print("?>"); //$NON-NLS-1$
358         printWriter.flush();
359     }
360 
361     protected void write(Comment node) {
362         printWriter.print("<!--"); //$NON-NLS-1$
363         String comment = node.getNodeValue();
364         if (comment != null && comment.length() > 0) {
365             normalizeAndPrint(comment, false);
366         }
367         printWriter.print("-->"); //$NON-NLS-1$
368         printWriter.flush();
369     }
370 }