1/*
2 * Copyright (c) 2009-2010 jMonkeyEngine
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package com.jme3.export.xml;
34
35import java.io.*;
36import java.nio.charset.Charset;
37import org.w3c.dom.*;
38
39/**
40 * The DOMSerializer was based primarily off the DOMSerializer.java class from the
41 * "Java and XML" 3rd Edition book by Brett McLaughlin, and Justin Edelson. Some
42 * modifications were made to support formatting of elements and attributes.
43 *
44 * @author Brett McLaughlin, Justin Edelson - Original creation for "Java and XML" book.
45 * @author Doug Daniels (dougnukem) - adjustments for XML formatting
46 * @version $Revision: 4207 $, $Date: 2009-03-29 11:19:16 -0400 (Sun, 29 Mar 2009) $
47 */
48public class DOMSerializer {
49
50    /** The encoding to use for output (default is UTF-8) */
51    private Charset encoding = Charset.forName("utf-8");
52
53    /** The amount of indentation to use (default is 4 spaces). */
54    private int indent = 4;
55
56    /** The line separator to use (default is the based on the current system settings). */
57    private String lineSeparator = System.getProperty("line.separator", "\n");
58
59    private void escape(Writer writer, String s) throws IOException {
60        if (s == null) { return; }
61        for (int i = 0, len = s.length(); i < len; i++) {
62            char c = s.charAt(i);
63            switch (c) {
64            case '<':
65                writer.write("&lt;");
66                break;
67            case '>':
68                writer.write("&gt;");
69                break;
70            case '&':
71                writer.write("&amp;");
72                break;
73            case '\r':
74                writer.write("&#xD;");
75                break;
76            default:
77                writer.write(c);
78            }
79        }
80    }
81
82    /**
83     * Serialize {@code doc} to {@code out}
84     *
85     * @param doc the document to serialize.
86     * @param file the file to serialize to.
87     * @throws IOException
88     */
89    public void serialize(Document doc, File file) throws IOException {
90        serialize(doc, new FileOutputStream(file));
91    }
92
93    /**
94     * Serialize {@code doc} to {@code out}
95     *
96     * @param doc the document to serialize.
97     * @param out the stream to serialize to.
98     * @throws IOException
99     */
100    public void serialize(Document doc, OutputStream out) throws IOException {
101        Writer writer = new OutputStreamWriter(out, encoding);
102        write(doc, writer, 0);
103        writer.flush();
104    }
105
106    /**
107     * Set the encoding used by this serializer.
108     *
109     * @param encoding the encoding to use, passing in {@code null} results in the
110     *  default encoding (UTF-8) being set.
111     * @throws IllegalCharsetNameException if the given charset name is illegal.
112     * @throws UnsupportedCharsetException if the given charset is not supported by the
113     *  current JVM.
114     */
115    public void setEncoding(String encoding) {
116        this.encoding = Charset.forName(encoding);
117    }
118
119    /**
120     * Set the number of spaces to use for indentation.
121     * <p>
122     * The default is to use 4 spaces.
123     *
124     * @param indent the number of spaces to use for indentation, values less than or
125     *  equal to zero result in no indentation being used.
126     */
127    public void setIndent(int indent) {
128        this.indent = indent >= 0 ? indent : 0;
129    }
130
131    /**
132     * Set the line separator that will be used when serializing documents.
133     * <p>
134     * If this is not called then the serializer uses a default based on the
135     * {@code line.separator} system property.
136     *
137     * @param lineSeparator the line separator to set.
138     */
139    public void setLineSeparator(String lineSeparator) {
140        this.lineSeparator = lineSeparator;
141    }
142
143    private void write(Node node, Writer writer, int depth) throws IOException {
144        switch (node.getNodeType()) {
145        case Node.DOCUMENT_NODE:
146            writeDocument((Document) node, writer);
147            break;
148        case Node.ELEMENT_NODE:
149            writeElement((Element) node, writer, depth);
150            break;
151        case Node.TEXT_NODE:
152            escape(writer, node.getNodeValue());
153            break;
154        case Node.CDATA_SECTION_NODE:
155            writer.write("<![CDATA[");
156            escape(writer, node.getNodeValue());
157            writer.write("]]>");
158            break;
159        case Node.COMMENT_NODE:
160            for (int i = 0; i < depth; ++i) { writer.append(' '); }
161            writer.append("<!-- ").append(node.getNodeValue()).append(" -->").append(lineSeparator);
162            break;
163        case Node.PROCESSING_INSTRUCTION_NODE:
164            String n = node.getNodeName();
165            String v = node.getNodeValue();
166            for (int i = 0; i < depth; ++i) { writer.append(' '); }
167            writer.append("<?").append(n).append(' ').append(v).append("?>").append(lineSeparator);
168            break;
169        case Node.ENTITY_REFERENCE_NODE:
170            writer.append('&').append(node.getNodeName()).append(';');
171            break;
172        case Node.DOCUMENT_TYPE_NODE:
173            writeDocumentType((DocumentType) node, writer, depth);
174            break;
175        }
176    }
177
178    private void writeDocument(Document document, Writer writer) throws IOException {
179        String v = document.getXmlVersion();
180
181        writer.append("<?xml ");
182        writer.append(" version='").append(v == null ? "1.0" : v).append("'");
183        writer.append(" encoding='").append(encoding.name()).append("'");
184        if (document.getXmlStandalone()) {
185            writer.append(" standalone='yes'");
186        }
187        writer.append("?>").append(lineSeparator);
188
189        NodeList nodes = document.getChildNodes();
190        for (int i = 0, imax = nodes.getLength(); i < imax; ++i) {
191            write(nodes.item(i), writer, 0);
192        }
193    }
194
195    private void writeDocumentType(DocumentType docType, Writer writer, int depth) throws IOException {
196        String publicId = docType.getPublicId();
197        String internalSubset = docType.getInternalSubset();
198
199        for (int i = 0; i < depth; ++i) { writer.append(' '); }
200        writer.append("<!DOCTYPE ").append(docType.getName());
201        if (publicId != null) {
202            writer.append(" PUBLIC '").append(publicId).append("' ");
203        } else {
204            writer.write(" SYSTEM ");
205        }
206        writer.append("'").append(docType.getSystemId()).append("'");
207        if (internalSubset != null) {
208            writer.append(" [").append(internalSubset).append("]");
209        }
210        writer.append('>').append(lineSeparator);
211    }
212
213    private void writeElement(Element element, Writer writer, int depth) throws IOException {
214        for (int i = 0; i < depth; ++i) { writer.append(' '); }
215        writer.append('<').append(element.getTagName());
216        NamedNodeMap attrs = element.getAttributes();
217        for (int i = 0, imax = attrs.getLength(); i < imax; ++i) {
218            Attr attr = (Attr) attrs.item(i);
219            writer.append(' ').append(attr.getName()).append("='").append(attr.getValue()).append("'");
220        }
221        NodeList nodes = element.getChildNodes();
222        if (nodes.getLength() == 0) {
223            // no children, so just close off the element and return
224            writer.append("/>").append(lineSeparator);
225            return;
226        }
227        writer.append('>').append(lineSeparator);
228        for (int i = 0, imax = nodes.getLength(); i < imax; ++i) {
229            Node n = nodes.item(i);
230            if (n.getNodeType() == Node.ATTRIBUTE_NODE) { continue; }
231            write(n, writer, depth + indent);
232        }
233        for (int i = 0; i < depth; ++i) { writer.append(' '); }
234        writer.append("</").append(element.getTagName()).append('>').append(lineSeparator);
235    }
236
237}
238