View Javadoc
1   /**
2    *    Copyright 2006-2016 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    *       http://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      /** The print writer. */
47      protected PrintWriter printWriter;
48  
49      /** The is xm l11. */
50      protected boolean isXML11;
51  
52      /**
53       * Instantiates a new dom writer.
54       */
55      public DomWriter() {
56          super();
57      }
58  
59      /**
60       * To string.
61       *
62       * @param document
63       *            the document
64       * @return the string
65       * @throws ShellException
66       *             the shell exception
67       */
68      public synchronized String toString(Document document)
69              throws ShellException {
70          StringWriter sw = new StringWriter();
71          printWriter = new PrintWriter(sw);
72          write(document);
73          String s = sw.toString();
74          return s;
75      }
76  
77      /**
78       * Returns a sorted list of attributes.
79       *
80       * @param attrs
81       *            the attrs
82       * @return the attr[]
83       */
84      protected Attr[] sortAttributes(NamedNodeMap attrs) {
85  
86          int len = (attrs != null) ? attrs.getLength() : 0;
87          Attr array[] = new Attr[len];
88          for (int i = 0; i < len; i++) {
89              array[i] = (Attr) attrs.item(i);
90          }
91          for (int i = 0; i < len - 1; i++) {
92              String name = array[i].getNodeName();
93              int index = i;
94              for (int j = i + 1; j < len; j++) {
95                  String curName = array[j].getNodeName();
96                  if (curName.compareTo(name) < 0) {
97                      name = curName;
98                      index = j;
99                  }
100             }
101             if (index != i) {
102                 Attr temp = array[i];
103                 array[i] = array[index];
104                 array[index] = temp;
105             }
106         }
107 
108         return array;
109 
110     }
111 
112     /**
113      * Normalizes and prints the given string.
114      *
115      * @param s
116      *            the s
117      * @param isAttValue
118      *            the is att value
119      */
120     protected void normalizeAndPrint(String s, boolean isAttValue) {
121 
122         int len = (s != null) ? s.length() : 0;
123         for (int i = 0; i < len; i++) {
124             char c = s.charAt(i);
125             normalizeAndPrint(c, isAttValue);
126         }
127     }
128 
129     /**
130      * Normalizes and print the given character.
131      *
132      * @param c
133      *            the c
134      * @param isAttValue
135      *            the is att value
136      */
137     protected void normalizeAndPrint(char c, boolean isAttValue) {
138 
139         switch (c) {
140         case '<': {
141             printWriter.print("&lt;"); //$NON-NLS-1$
142             break;
143         }
144         case '>': {
145             printWriter.print("&gt;"); //$NON-NLS-1$
146             break;
147         }
148         case '&': {
149             printWriter.print("&amp;"); //$NON-NLS-1$
150             break;
151         }
152         case '"': {
153             // A '"' that appears in character data
154             // does not need to be escaped.
155             if (isAttValue) {
156                 printWriter.print("&quot;"); //$NON-NLS-1$
157             } else {
158                 printWriter.print('"');
159             }
160             break;
161         }
162         case '\r': {
163             // If CR is part of the document's content, it
164             // must be printed as a literal otherwise
165             // it would be normalized to LF when the document
166             // is reparsed.
167             printWriter.print("&#xD;"); //$NON-NLS-1$
168             break;
169         }
170         case '\n': {
171             // If LF is part of the document's content, it
172             // should be printed back out with the system default
173             // line separator.  XML parsing forces \n only after a parse,
174             // but we should write it out as it was to avoid whitespace
175             // commits on some version control systems.
176             printWriter.print(System.getProperty("line.separator")); //$NON-NLS-1$
177             break;
178         }
179         default: {
180             // In XML 1.1, control chars in the ranges [#x1-#x1F, #x7F-#x9F]
181             // must be escaped.
182             //
183             // Escape space characters that would be normalized to #x20 in
184             // attribute values
185             // when the document is reparsed.
186             //
187             // Escape NEL (0x85) and LSEP (0x2028) that appear in content
188             // if the document is XML 1.1, since they would be normalized to LF
189             // when the document is reparsed.
190             if (isXML11
191                     && ((c >= 0x01 && c <= 0x1F && c != 0x09 && c != 0x0A)
192                             || (c >= 0x7F && c <= 0x9F) || c == 0x2028)
193                     || isAttValue && (c == 0x09 || c == 0x0A)) {
194                 printWriter.print("&#x"); //$NON-NLS-1$
195                 printWriter.print(Integer.toHexString(c).toUpperCase());
196                 printWriter.print(';');
197             } else {
198                 printWriter.print(c);
199             }
200         }
201         }
202     }
203 
204     /**
205      * Extracts the XML version from the Document.
206      *
207      * @param document
208      *            the document
209      * @return the version
210      */
211     protected String getVersion(Document document) {
212         if (document == null) {
213             return null;
214         }
215         
216         return document.getXmlVersion();
217     }
218 
219     /**
220      * Write any node.
221      *
222      * @param node
223      *            the node
224      * @throws ShellException
225      *             the shell exception
226      */
227     protected void writeAnyNode(Node node) throws ShellException {
228         // is there anything to do?
229         if (node == null) {
230             return;
231         }
232 
233         short type = node.getNodeType();
234         switch (type) {
235         case Node.DOCUMENT_NODE:
236             write((Document) node);
237             break;
238 
239         case Node.DOCUMENT_TYPE_NODE:
240             write((DocumentType) node);
241             break;
242 
243         case Node.ELEMENT_NODE:
244             write((Element) node);
245             break;
246 
247         case Node.ENTITY_REFERENCE_NODE:
248             write((EntityReference) node);
249             break;
250 
251         case Node.CDATA_SECTION_NODE:
252             write((CDATASection) node);
253             break;
254 
255         case Node.TEXT_NODE:
256             write((Text) node);
257             break;
258 
259         case Node.PROCESSING_INSTRUCTION_NODE:
260             write((ProcessingInstruction) node);
261             break;
262 
263         case Node.COMMENT_NODE:
264             write((Comment) node);
265             break;
266 
267         default:
268             throw new ShellException(getString(
269                     "RuntimeError.18", Short.toString(type))); //$NON-NLS-1$
270         }
271     }
272 
273     /**
274      * Write.
275      *
276      * @param node
277      *            the node
278      * @throws ShellException
279      *             the shell exception
280      */
281     protected void write(Document node) throws ShellException {
282         isXML11 = "1.1".equals(getVersion(node)); //$NON-NLS-1$
283         if (isXML11) {
284             printWriter.println("<?xml version=\"1.1\" encoding=\"UTF-8\"?>"); //$NON-NLS-1$
285         } else {
286             printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); //$NON-NLS-1$
287         }
288         printWriter.flush();
289         write(node.getDoctype());
290         write(node.getDocumentElement());
291     }
292 
293     /**
294      * Write.
295      *
296      * @param node
297      *            the node
298      * @throws ShellException
299      *             the shell exception
300      */
301     protected void write(DocumentType node) throws ShellException {
302         printWriter.print("<!DOCTYPE "); //$NON-NLS-1$
303         printWriter.print(node.getName());
304         String publicId = node.getPublicId();
305         String systemId = node.getSystemId();
306         if (publicId != null) {
307             printWriter.print(" PUBLIC \""); //$NON-NLS-1$
308             printWriter.print(publicId);
309             printWriter.print("\" \""); //$NON-NLS-1$
310             printWriter.print(systemId);
311             printWriter.print('\"');
312         } else if (systemId != null) {
313             printWriter.print(" SYSTEM \""); //$NON-NLS-1$
314             printWriter.print(systemId);
315             printWriter.print('"');
316         }
317 
318         String internalSubset = node.getInternalSubset();
319         if (internalSubset != null) {
320             printWriter.println(" ["); //$NON-NLS-1$
321             printWriter.print(internalSubset);
322             printWriter.print(']');
323         }
324         printWriter.println('>');
325     }
326 
327     /**
328      * Write.
329      *
330      * @param node
331      *            the node
332      * @throws ShellException
333      *             the shell exception
334      */
335     protected void write(Element node) throws ShellException {
336         printWriter.print('<');
337         printWriter.print(node.getNodeName());
338         Attr attrs[] = sortAttributes(node.getAttributes());
339         for (Attr attr : attrs) {
340             printWriter.print(' ');
341             printWriter.print(attr.getNodeName());
342             printWriter.print("=\""); //$NON-NLS-1$
343             normalizeAndPrint(attr.getNodeValue(), true);
344             printWriter.print('"');
345         }
346 
347         if (node.getChildNodes().getLength() == 0) {
348             printWriter.print(" />"); //$NON-NLS-1$
349             printWriter.flush();
350         } else {
351             printWriter.print('>');
352             printWriter.flush();
353 
354             Node child = node.getFirstChild();
355             while (child != null) {
356                 writeAnyNode(child);
357                 child = child.getNextSibling();
358             }
359 
360             printWriter.print("</"); //$NON-NLS-1$
361             printWriter.print(node.getNodeName());
362             printWriter.print('>');
363             printWriter.flush();
364         }
365     }
366 
367     /**
368      * Write.
369      *
370      * @param node
371      *            the node
372      */
373     protected void write(EntityReference node) {
374         printWriter.print('&');
375         printWriter.print(node.getNodeName());
376         printWriter.print(';');
377         printWriter.flush();
378     }
379 
380     /**
381      * Write.
382      *
383      * @param node
384      *            the node
385      */
386     protected void write(CDATASection node) {
387         printWriter.print("<![CDATA["); //$NON-NLS-1$
388         String data = node.getNodeValue();
389         // XML parsers normalize line endings to '\n'.  We should write
390         // it out as it was in the original to avoid whitespace commits
391         // on some version control systems
392         int len = (data != null) ? data.length() : 0;
393         for (int i = 0; i < len; i++) {
394             char c = data.charAt(i);
395             if (c == '\n') {
396                 printWriter.print(System.getProperty("line.separator")); //$NON-NLS-1$
397             } else {
398                 printWriter.print(c);
399             }
400         }
401         printWriter.print("]]>"); //$NON-NLS-1$
402         printWriter.flush();
403     }
404 
405     /**
406      * Write.
407      *
408      * @param node
409      *            the node
410      */
411     protected void write(Text node) {
412         normalizeAndPrint(node.getNodeValue(), false);
413         printWriter.flush();
414     }
415 
416     /**
417      * Write.
418      *
419      * @param node
420      *            the node
421      */
422     protected void write(ProcessingInstruction node) {
423         printWriter.print("<?"); //$NON-NLS-1$
424         printWriter.print(node.getNodeName());
425         String data = node.getNodeValue();
426         if (data != null && data.length() > 0) {
427             printWriter.print(' ');
428             printWriter.print(data);
429         }
430         printWriter.print("?>"); //$NON-NLS-1$
431         printWriter.flush();
432     }
433 
434     /**
435      * Write.
436      *
437      * @param node
438      *            the node
439      */
440     protected void write(Comment node) {
441         printWriter.print("<!--"); //$NON-NLS-1$
442         String comment = node.getNodeValue();
443         if (comment != null && comment.length() > 0) {
444             normalizeAndPrint(comment,  false);
445         }
446         printWriter.print("-->"); //$NON-NLS-1$
447         printWriter.flush();
448     }
449 }