1 /*
2  * XMLWriter.java
3  *
4  * Copyright (c) 2015 Emmanuel PUYBARET / eTeks <info@eteks.com>. All Rights Reserved.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20 package com.eteks.sweethome3d.io;
21 
22 import java.io.FilterWriter;
23 import java.io.IOException;
24 import java.io.OutputStream;
25 import java.io.OutputStreamWriter;
26 import java.math.BigDecimal;
27 import java.util.Stack;
28 
29 /**
30  * A simple XML writer able to write XML elements, their attributes and texts, indenting child elements.
31  * @author Emmanuel Puybaret
32  */
33 public class XMLWriter extends FilterWriter {
34   private Stack<String> elements = new Stack<String>();
35   private boolean emptyElement;
36   private boolean elementWithText;
37 
38   /**
39    * Creates a writer in the given output stream encoded in UTF-8.
40    */
XMLWriter(OutputStream out)41   public XMLWriter(OutputStream out) throws IOException {
42     super(new OutputStreamWriter(out, "UTF-8"));
43     this.out.write("<?xml version='1.0'?>\n");
44   }
45 
46   /**
47    * Writes a start tag for the given element.
48    */
writeStartElement(String element)49   public void writeStartElement(String element) throws IOException {
50     if (this.elements.size() > 0) {
51       if (this.emptyElement) {
52         this.out.write(">");
53       }
54       writeIndentation();
55     }
56     this.out.write("<" + element);
57     this.elements.push(element);
58     this.emptyElement = true;
59     this.elementWithText = false;
60   }
61 
62   /**
63    * Writes an end tag for the given element.
64    */
writeEndElement()65   public void writeEndElement() throws IOException {
66     String element = this.elements.pop();
67     if (this.emptyElement) {
68       this.out.write("/>");
69     } else {
70       if (!this.elementWithText) {
71         writeIndentation();
72       }
73       this.out.write("</" + element + ">");
74     }
75     this.emptyElement = false;
76     this.elementWithText = false;
77   }
78 
79   /**
80    * Adds spaces according to the current depth of XML tree.
81    */
writeIndentation()82   private void writeIndentation() throws IOException {
83     this.out.write("\n");
84     for (int i = 0; i < this.elements.size(); i++) {
85       this.out.write("  ");
86     }
87   }
88 
89   /**
90    * Writes the attribute of the given <code>name</code> with its <code>value</code>
91    * in the tag of the last started element.
92    */
writeAttribute(String name, String value)93   public void writeAttribute(String name, String value) throws IOException {
94     this.out.write(" " + name + "='" + replaceByEntities(value) + "'");
95   }
96 
97   /**
98    * Writes the name and the value of an attribute in the tag of the last started element,
99    * except if <code>value</code> equals <code>defaultValue</code>.
100    */
writeAttribute(String name, String value, String defaultValue)101   public void writeAttribute(String name, String value, String defaultValue) throws IOException {
102     if ((value != null || value != defaultValue)
103         && !value.equals(defaultValue)) {
104       writeAttribute(name, value);
105     }
106   }
107 
108   /**
109    * Writes the attribute of the given <code>name</code> with its integer <code>value</code>
110    * in the tag of the last started element.
111    */
writeIntegerAttribute(String name, int value)112   public void writeIntegerAttribute(String name, int value) throws IOException {
113     writeAttribute(name, String.valueOf(value));
114   }
115 
116   /**
117    * Writes the name and the integer value of an attribute in the tag of the last started element,
118    * except if <code>value</code> equals <code>defaultValue</code>.
119    */
writeIntegerAttribute(String name, int value, int defaultValue)120   public void writeIntegerAttribute(String name, int value, int defaultValue) throws IOException {
121     if (value != defaultValue) {
122       writeAttribute(name, String.valueOf(value));
123     }
124   }
125 
126   /**
127    * Writes the attribute of the given <code>name</code> with its long <code>value</code>
128    * in the tag of the last started element.
129    */
writeLongAttribute(String name, long value)130   public void writeLongAttribute(String name, long value) throws IOException {
131     writeAttribute(name, String.valueOf(value));
132   }
133 
134   /**
135    * Writes the name and the long value of an attribute in the tag of the last started element,
136    * except if <code>value</code> equals <code>null</code>.
137    */
writeLongAttribute(String name, Long value)138   public void writeLongAttribute(String name, Long value) throws IOException {
139     if (value != null) {
140       writeAttribute(name, value.toString());
141     }
142   }
143 
144   /**
145    * Writes the attribute of the given <code>name</code> with its float <code>value</code>
146    * in the tag of the last started element.
147    */
writeFloatAttribute(String name, float value)148   public void writeFloatAttribute(String name, float value) throws IOException {
149     writeAttribute(name, String.valueOf(value));
150   }
151 
152   /**
153    * Writes the name and the float value of an attribute in the tag of the last started element,
154    * except if <code>value</code> equals <code>defaultValue</code>.
155    */
writeFloatAttribute(String name, float value, float defaultValue)156   public void writeFloatAttribute(String name, float value, float defaultValue) throws IOException {
157     if (value != defaultValue) {
158       writeFloatAttribute(name, value);
159     }
160   }
161 
162   /**
163    * Writes the name and the float value of an attribute in the tag of the last started element,
164    * except if <code>value</code> equals <code>null</code>.
165    */
writeFloatAttribute(String name, Float value)166   public void writeFloatAttribute(String name, Float value) throws IOException {
167     if (value != null) {
168       writeAttribute(name, value.toString());
169     }
170   }
171 
172   /**
173    * Writes the name and the value of an attribute in the tag of the last started element,
174    * except if <code>value</code> equals <code>null</code>.
175    */
writeBigDecimalAttribute(String name, BigDecimal value)176   public void writeBigDecimalAttribute(String name, BigDecimal value) throws IOException {
177     if (value != null) {
178       writeAttribute(name, String.valueOf(value));
179     }
180   }
181 
182   /**
183    * Writes the name and the boolean value of an attribute in the tag of the last started element,
184    * except if <code>value</code> equals <code>defaultValue</code>.
185    */
writeBooleanAttribute(String name, boolean value, boolean defaultValue)186   public void writeBooleanAttribute(String name, boolean value, boolean defaultValue) throws IOException {
187     if (value != defaultValue) {
188       writeAttribute(name, String.valueOf(value));
189     }
190   }
191 
192   /**
193    * Writes the name and the color value of an attribute in the tag of the last started element,
194    * except if <code>value</code> equals <code>null</code>. The color is written in hexadecimal.
195    */
writeColorAttribute(String name, Integer color)196   public void writeColorAttribute(String name, Integer color) throws IOException {
197     if (color != null) {
198       writeAttribute(name, String.format("%08X", color));
199     }
200   }
201 
202   /**
203    * Writes the given <code>text</code> as the content of the current element.
204    */
writeText(String text)205   public void writeText(String text) throws IOException {
206     if (this.emptyElement) {
207       this.out.write(">");
208       this.emptyElement = false;
209       this.elementWithText = true;
210     }
211     super.out.write(replaceByEntities(text));
212   }
213 
214   /**
215    * Returns the string in parameter with &amp;, &lt;, &apos;, &quot; and feed line characters replaced by their matching entities.
216    */
replaceByEntities(String s)217   private static String replaceByEntities(String s) {
218     return s.replace("&", "&amp;").replace("<", "&lt;").replace("'", "&apos;").replace("\"", "&quot;").replace("\n", "&#10;");
219   }
220 
221   /**
222    * Writes the given character as the content of the current element.
223    */
write(int c)224   public void write(int c) throws IOException {
225     writeText(String.valueOf((char)c));
226   }
227 
228   /**
229    * Writes the given characters array as the content of the current element.
230    */
write(char buffer[], int offset, int length)231   public void write(char buffer[], int offset, int length) throws IOException {
232     writeText(new String(buffer, offset, length));
233   }
234 
235   /**
236    * Writes the given string as the content of the current element.
237    */
write(String str, int offset, int length)238   public void write(String str, int offset, int length) throws IOException {
239     writeText(str.substring(offset, offset + length));
240   }
241 }