001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.netui.compiler.model;
020:
021: import org.w3c.dom.Document;
022: import org.w3c.dom.DOMImplementation;
023: import org.w3c.dom.DocumentType;
024: import org.w3c.dom.Element;
025: import org.w3c.dom.Comment;
026: import org.w3c.dom.NamedNodeMap;
027: import org.w3c.dom.Node;
028: import org.w3c.dom.NodeList;
029: import org.w3c.dom.Text;
030: import org.xml.sax.SAXException;
031: import org.apache.beehive.netui.compiler.LocalFileEntityResolver;
032:
033: import javax.xml.parsers.DocumentBuilderFactory;
034: import javax.xml.parsers.DocumentBuilder;
035: import javax.xml.parsers.ParserConfigurationException;
036: import javax.xml.transform.dom.DOMSource;
037: import javax.xml.transform.stream.StreamResult;
038: import javax.xml.transform.TransformerFactory;
039: import javax.xml.transform.Transformer;
040: import javax.xml.transform.OutputKeys;
041: import javax.xml.transform.TransformerException;
042: import java.io.File;
043: import java.io.IOException;
044: import java.io.Writer;
045:
046: public class XmlModelWriter {
047: private static final int INDENT_LEN = 2;
048:
049: private Document _doc;
050: private String _rootName;
051: private String _systemID;
052: private String _publicID;
053:
054: public XmlModelWriter(File starterFile, String rootName,
055: String publicID, String systemID, String headerComment)
056: throws XmlModelWriterException, IOException {
057: assert rootName != null;
058: _rootName = rootName;
059: _systemID = systemID;
060: _publicID = publicID;
061:
062: try {
063: DocumentBuilderFactory builderFactory = DocumentBuilderFactory
064: .newInstance();
065: DocumentBuilder builder = builderFactory
066: .newDocumentBuilder();
067: builder.setEntityResolver(LocalFileEntityResolver
068: .getInstance());
069:
070: if (starterFile != null && starterFile.canRead()) {
071: _doc = builder.parse(starterFile);
072: } else {
073: DOMImplementation impl = builder.getDOMImplementation();
074: DocumentType docType = impl.createDocumentType(
075: _rootName, _publicID, _systemID);
076: _doc = impl.createDocument(null, _rootName, docType);
077: }
078:
079: if (headerComment != null) {
080: Element root = _doc.getDocumentElement();
081: Comment comment = _doc.createComment(headerComment);
082: root.insertBefore(comment, root.getFirstChild());
083: }
084: } catch (ParserConfigurationException e) {
085: throw new XmlModelWriterException(e);
086: } catch (SAXException e) {
087: throw new XmlModelWriterException(e);
088: }
089: }
090:
091: /**
092: * Write the XML to a stream, without using standard APIs. This appears to be about ten times as fast (for our
093: * simple purposes) as using a Transformer ({@link #write}), and it avoids JDK and environment issues with obtaining
094: * a TransformerFactory.
095: */
096: public void simpleFastWrite(Writer out) throws IOException,
097: XmlModelWriterException {
098: out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
099:
100: if (_publicID != null && _systemID != null) {
101: out.write("<!DOCTYPE ");
102: out.write(_rootName);
103: out.write(" PUBLIC \"");
104: out.write(_publicID);
105: out.write("\" \"");
106: out.write(_systemID);
107: out.write("\">\n");
108: }
109:
110: writeElement(out, _doc.getDocumentElement(), 0);
111: }
112:
113: /**
114: * Write the XML to a stream, using standard APIs. This appears to be about ten times slower than our custom writer
115: * ({@link #simpleFastWrite}).
116: */
117: public void write(Writer out) throws XmlModelWriterException,
118: IOException {
119: try {
120: DOMSource domSource = new DOMSource(_doc);
121: StreamResult streamResult = new StreamResult(out);
122: TransformerFactory tf = TransformerFactory.newInstance();
123: tf.setAttribute("indent-number", new Integer(INDENT_LEN));
124: Transformer serializer = tf.newTransformer();
125: if (_publicID != null) {
126: serializer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
127: _publicID);
128: }
129: if (_systemID != null) {
130: serializer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,
131: _systemID);
132: }
133: serializer.setOutputProperty(OutputKeys.INDENT, "yes");
134: serializer.transform(domSource, streamResult);
135: } catch (TransformerException e) {
136: throw new XmlModelWriterException(e);
137: }
138: }
139:
140: public final Document getDocument() {
141: return _doc;
142: }
143:
144: public final Element getRootElement() {
145: return _doc.getDocumentElement();
146: }
147:
148: public final Element addElement(Element parent, String tagName) {
149: Element element = _doc.createElement(tagName);
150: parent.appendChild(element);
151: return element;
152: }
153:
154: public final Element addElementWithText(Element parent,
155: String tagName, String text) {
156: Element element = addElement(parent, tagName);
157: Text textNode = _doc.createTextNode(text);
158: element.appendChild(textNode);
159: return element;
160: }
161:
162: public final void addComment(Element parent, String commentText) {
163: if (commentText != null) {
164: Comment comment = _doc.createComment(commentText);
165: parent.appendChild(comment);
166: }
167: }
168:
169: private static void doIndent(Writer out, int indent)
170: throws IOException {
171: for (int i = 0; i < indent; ++i) {
172: out.write(' ');
173: }
174: }
175:
176: private static void writeElement(Writer out, Element element,
177: int indent) throws IOException {
178: doIndent(out, indent);
179: out.write('<');
180: String tagName = element.getTagName();
181: out.write(tagName);
182: NamedNodeMap attrs = element.getAttributes();
183: for (int i = 0, len = attrs.getLength(); i < len; ++i) {
184: Node attr = attrs.item(i);
185: out.write(' ');
186: out.write(attr.getNodeName());
187: out.write("=\"");
188: filterValue(out, attr.getNodeValue(), true);
189: out.write('"');
190: }
191:
192: String textContent = XmlElementSupport.getTextContent(element);
193: NodeList children = element.getChildNodes();
194: int childCount = children.getLength();
195: if (textContent != null) {
196: out.write('>');
197: filterValue(out, textContent, false);
198: out.write("</");
199: out.write(tagName);
200: out.write(">\n");
201: } else if (childCount > 0) {
202: out.write(">\n");
203: for (int i = 0; i < childCount; ++i) {
204: Node child = children.item(i);
205: if (child instanceof Comment) {
206: writeComment(out, (Comment) child, indent
207: + INDENT_LEN);
208: } else if (child instanceof Text) {
209: assert XmlElementSupport.isWhiteSpace(child
210: .getNodeValue()) : "expected only whitespace: "
211: + child.getNodeValue();
212: } else {
213: assert child instanceof Element : child.getClass()
214: .getName();
215: writeElement(out, (Element) child, indent
216: + INDENT_LEN);
217: }
218: }
219: doIndent(out, indent);
220: out.write("</");
221: out.write(tagName);
222: out.write(">\n");
223: } else {
224: out.write("/>\n");
225: }
226: }
227:
228: private static void writeComment(Writer out, Comment comment,
229: int indent) throws IOException {
230: doIndent(out, indent);
231: out.write("<!--");
232: filterValue(out, comment.getNodeValue(), false);
233: out.write("-->\n");
234: }
235:
236: private static void filterValue(Writer writer, String value,
237: boolean filterQuote) throws IOException {
238: for (int i = 0; i < value.length(); ++i) {
239: char c = value.charAt(i);
240: switch (c) {
241: case '<':
242: writer.write("<");
243: break;
244: case '>':
245: writer.write(">");
246: break;
247: case '&':
248: writer.write("&");
249: break;
250: default:
251: if (filterQuote && c == '"') {
252: writer.write(""");
253: } else {
254: writer.write(c);
255: }
256: }
257: }
258: }
259: }
|