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:
018: /* $Id$ */
019:
020: package org.apache.fop.util;
021:
022: import java.util.List;
023: import java.util.Map;
024: import java.util.Stack;
025:
026: import org.w3c.dom.Document;
027: import org.w3c.dom.NamedNodeMap;
028: import org.w3c.dom.Node;
029:
030: import org.xml.sax.ContentHandler;
031: import org.xml.sax.SAXException;
032: import org.xml.sax.ext.LexicalHandler;
033: import org.xml.sax.helpers.AttributesImpl;
034:
035: /**
036: * Helper class that produces a SAX stream from a DOM Document.
037: * <p>
038: * Part of the code here copied and adapted from Apache Xalan-J,
039: * src/org/apache/xalan/xsltc/trax/DOM2SAX.java
040: */
041: public class DOM2SAX {
042:
043: private static final String EMPTYSTRING = "";
044: private static final String XMLNS_PREFIX = "xmlns";
045:
046: private ContentHandler contentHandler;
047: private LexicalHandler lexicalHandler;
048:
049: private Map prefixes = new java.util.HashMap();
050:
051: /**
052: * Main constructor
053: * @param handler the ContentHandler to send SAX events to
054: */
055: public DOM2SAX(ContentHandler handler) {
056: this .contentHandler = handler;
057: if (handler instanceof LexicalHandler) {
058: this .lexicalHandler = (LexicalHandler) handler;
059: }
060: }
061:
062: /**
063: * Writes the given document using the given ContentHandler.
064: * @param doc DOM document
065: * @param fragment if false no startDocument() and endDocument() calls are issued.
066: * @throws SAXException In case of a problem while writing XML
067: */
068: public void writeDocument(Document doc, boolean fragment)
069: throws SAXException {
070: if (!fragment) {
071: contentHandler.startDocument();
072: }
073: for (Node n = doc.getFirstChild(); n != null; n = n
074: .getNextSibling()) {
075: writeNode(n);
076: }
077: if (!fragment) {
078: contentHandler.endDocument();
079: }
080: }
081:
082: /**
083: * Begin the scope of namespace prefix. Forward the event to the SAX handler
084: * only if the prefix is unknown or it is mapped to a different URI.
085: */
086: private boolean startPrefixMapping(String prefix, String uri)
087: throws SAXException {
088: boolean pushed = true;
089: Stack uriStack = (Stack) prefixes.get(prefix);
090:
091: if (uriStack != null) {
092: if (uriStack.isEmpty()) {
093: contentHandler.startPrefixMapping(prefix, uri);
094: uriStack.push(uri);
095: } else {
096: final String lastUri = (String) uriStack.peek();
097: if (!lastUri.equals(uri)) {
098: contentHandler.startPrefixMapping(prefix, uri);
099: uriStack.push(uri);
100: } else {
101: pushed = false;
102: }
103: }
104: } else {
105: contentHandler.startPrefixMapping(prefix, uri);
106: uriStack = new Stack();
107: prefixes.put(prefix, uriStack);
108: uriStack.push(uri);
109: }
110: return pushed;
111: }
112:
113: /*
114: * End the scope of a name prefix by popping it from the stack and passing
115: * the event to the SAX Handler.
116: */
117: private void endPrefixMapping(String prefix) throws SAXException {
118: final Stack uriStack = (Stack) prefixes.get(prefix);
119:
120: if (uriStack != null) {
121: contentHandler.endPrefixMapping(prefix);
122: uriStack.pop();
123: }
124: }
125:
126: /**
127: * If the DOM was created using a DOM 1.0 API, the local name may be null.
128: * If so, get the local name from the qualified name before generating the
129: * SAX event.
130: */
131: private static String getLocalName(Node node) {
132: final String localName = node.getLocalName();
133:
134: if (localName == null) {
135: final String qname = node.getNodeName();
136: final int col = qname.lastIndexOf(':');
137: return (col > 0) ? qname.substring(col + 1) : qname;
138: }
139: return localName;
140: }
141:
142: /**
143: * Writes a node using the given writer.
144: * @param node node to serialize
145: * @throws SAXException In case of a problem while writing XML
146: */
147: private void writeNode(Node node) throws SAXException {
148: if (node == null) {
149: return;
150: }
151:
152: switch (node.getNodeType()) {
153: case Node.ATTRIBUTE_NODE: // handled by ELEMENT_NODE
154: case Node.DOCUMENT_FRAGMENT_NODE:
155: case Node.DOCUMENT_TYPE_NODE:
156: case Node.ENTITY_NODE:
157: case Node.ENTITY_REFERENCE_NODE:
158: case Node.NOTATION_NODE:
159: // These node types are ignored!!!
160: break;
161: case Node.CDATA_SECTION_NODE:
162: final String cdata = node.getNodeValue();
163: if (lexicalHandler != null) {
164: lexicalHandler.startCDATA();
165: contentHandler.characters(cdata.toCharArray(), 0, cdata
166: .length());
167: lexicalHandler.endCDATA();
168: } else {
169: // in the case where there is no lex handler, we still
170: // want the text of the cdate to make its way through.
171: contentHandler.characters(cdata.toCharArray(), 0, cdata
172: .length());
173: }
174: break;
175:
176: case Node.COMMENT_NODE: // should be handled!!!
177: if (lexicalHandler != null) {
178: final String value = node.getNodeValue();
179: lexicalHandler.comment(value.toCharArray(), 0, value
180: .length());
181: }
182: break;
183: case Node.DOCUMENT_NODE:
184: contentHandler.startDocument();
185: Node next = node.getFirstChild();
186: while (next != null) {
187: writeNode(next);
188: next = next.getNextSibling();
189: }
190: contentHandler.endDocument();
191: break;
192:
193: case Node.ELEMENT_NODE:
194: String prefix;
195: List pushedPrefixes = new java.util.ArrayList();
196: final AttributesImpl attrs = new AttributesImpl();
197: final NamedNodeMap map = node.getAttributes();
198: final int length = map.getLength();
199:
200: // Process all namespace declarations
201: for (int i = 0; i < length; i++) {
202: final Node attr = map.item(i);
203: final String qnameAttr = attr.getNodeName();
204:
205: // Ignore everything but NS declarations here
206: if (qnameAttr.startsWith(XMLNS_PREFIX)) {
207: final String uriAttr = attr.getNodeValue();
208: final int colon = qnameAttr.lastIndexOf(':');
209: prefix = (colon > 0) ? qnameAttr
210: .substring(colon + 1) : EMPTYSTRING;
211: if (startPrefixMapping(prefix, uriAttr)) {
212: pushedPrefixes.add(prefix);
213: }
214: }
215: }
216:
217: // Process all other attributes
218: for (int i = 0; i < length; i++) {
219: final Node attr = map.item(i);
220: final String qnameAttr = attr.getNodeName();
221:
222: // Ignore NS declarations here
223: if (!qnameAttr.startsWith(XMLNS_PREFIX)) {
224: final String uriAttr = attr.getNamespaceURI();
225:
226: // Uri may be implicitly declared
227: if (uriAttr != null) {
228: final int colon = qnameAttr.lastIndexOf(':');
229: prefix = (colon > 0) ? qnameAttr.substring(0,
230: colon) : EMPTYSTRING;
231: if (startPrefixMapping(prefix, uriAttr)) {
232: pushedPrefixes.add(prefix);
233: }
234: }
235:
236: // Add attribute to list
237: attrs.addAttribute(attr.getNamespaceURI(),
238: getLocalName(attr), qnameAttr, "CDATA",
239: attr.getNodeValue());
240: }
241: }
242:
243: // Now process the element itself
244: final String qname = node.getNodeName();
245: final String uri = node.getNamespaceURI();
246: final String localName = getLocalName(node);
247:
248: // Uri may be implicitly declared
249: if (uri != null) {
250: final int colon = qname.lastIndexOf(':');
251: prefix = (colon > 0) ? qname.substring(0, colon)
252: : EMPTYSTRING;
253: if (startPrefixMapping(prefix, uri)) {
254: pushedPrefixes.add(prefix);
255: }
256: }
257:
258: // Generate SAX event to start element
259: contentHandler.startElement(uri, localName, qname, attrs);
260:
261: // Traverse all child nodes of the element (if any)
262: next = node.getFirstChild();
263: while (next != null) {
264: writeNode(next);
265: next = next.getNextSibling();
266: }
267:
268: // Generate SAX event to close element
269: contentHandler.endElement(uri, localName, qname);
270:
271: // Generate endPrefixMapping() for all pushed prefixes
272: final int nPushedPrefixes = pushedPrefixes.size();
273: for (int i = 0; i < nPushedPrefixes; i++) {
274: endPrefixMapping((String) pushedPrefixes.get(i));
275: }
276: break;
277:
278: case Node.PROCESSING_INSTRUCTION_NODE:
279: contentHandler.processingInstruction(node.getNodeName(),
280: node.getNodeValue());
281: break;
282:
283: case Node.TEXT_NODE:
284: final String data = node.getNodeValue();
285: contentHandler.characters(data.toCharArray(), 0, data
286: .length());
287: break;
288: default:
289: //nop
290: }
291: }
292:
293: }
|