001: /* Copyright 2002-2005 Elliotte Rusty Harold
002:
003: This library is free software; you can redistribute it and/or modify
004: it under the terms of version 2.1 of the GNU Lesser General Public
005: License as published by the Free Software Foundation.
006:
007: This library is distributed in the hope that it will be useful,
008: but WITHOUT ANY WARRANTY; without even the implied warranty of
009: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
010: GNU Lesser General Public License for more details.
011:
012: You should have received a copy of the GNU Lesser General Public
013: License along with this library; if not, write to the
014: Free Software Foundation, Inc., 59 Temple Place, Suite 330,
015: Boston, MA 02111-1307 USA
016:
017: You can contact Elliotte Rusty Harold by sending e-mail to
018: elharo@metalab.unc.edu. Please include the word "XOM" in the
019: subject line. The XOM home page is located at http://www.xom.nu/
020: */
021:
022: package nu.xom.converters;
023:
024: import nu.xom.Attribute;
025: import nu.xom.Comment;
026: import nu.xom.DocType;
027: import nu.xom.Document;
028: import nu.xom.Element;
029: import nu.xom.Node;
030: import nu.xom.Nodes;
031: import nu.xom.ParentNode;
032: import nu.xom.ProcessingInstruction;
033: import nu.xom.Text;
034:
035: import org.xml.sax.ContentHandler;
036: import org.xml.sax.SAXException;
037: import org.xml.sax.ext.LexicalHandler;
038: import org.xml.sax.helpers.AttributesImpl;
039: import org.xml.sax.helpers.LocatorImpl;
040:
041: /**
042: * <p>
043: * Feeds a XOM <code>Document</code> into a
044: * SAX2 <code>ContentHandler</code>.
045: * </p>
046: *
047: * @author Elliotte Rusty Harold
048: * @version 1.1b2
049: */
050: public class SAXConverter {
051:
052: private ContentHandler contentHandler;
053: private LexicalHandler lexicalHandler;
054: private LocatorImpl locator;
055: private boolean stripBaseAttributes = true;
056:
057: /**
058: * <p>
059: * Creates a new <code>SAXConverter</code>.
060: * </p>
061: *
062: * @param handler the SAX2 content handler
063: * that receives the data
064: *
065: * @throws NullPointerException if handler is null
066: *
067: */
068: public SAXConverter(ContentHandler handler) {
069: setContentHandler(handler);
070: }
071:
072: /**
073: * <p>
074: * Set the content handler for this converter.
075: * </p>
076: *
077: * @param handler SAX2 content handler that
078: * receives the data
079: *
080: * @throws NullPointerException if handler is null
081: *
082: */
083: public void setContentHandler(ContentHandler handler) {
084:
085: if (handler == null) {
086: throw new NullPointerException(
087: "ContentHandler must be non-null.");
088: }
089: // unbelievably skanky hack to allow xml:base attributes
090: // to be passed to XSL transforms without mucking with the
091: // public API. This would be so much easier if Java had friend
092: // functions.
093: else if ("nu.xom.xslt.XSLTHandler".equals(handler.getClass()
094: .getName())) {
095: this .stripBaseAttributes = false;
096: } else {
097: this .contentHandler = handler;
098: }
099:
100: }
101:
102: /**
103: * <p>
104: * Returns the content handler.
105: * </p>
106: *
107: * @return SAX2 content handler that receives the data
108: */
109: public ContentHandler getContentHandler() {
110: return this .contentHandler;
111: }
112:
113: /**
114: * <p>
115: * Sets the optional lexical handler for this converter.
116: * The only lexical events the converter supplies
117: * are comments.
118: * </p>
119: *
120: * @param handler the lexical handler;
121: * may be null to turn off lexical events
122: */
123: public void setLexicalHandler(LexicalHandler handler) {
124: this .lexicalHandler = handler;
125: }
126:
127: /**
128: * <p>
129: * Returns the <code>LexicalHandler</code> for this
130: * converter. This is only used for comments.
131: * </p>
132: *
133: * @return SAX2 lexical handler that receives
134: * lexical events
135: */
136: public LexicalHandler getLexicalHandler() {
137: return this .lexicalHandler;
138: }
139:
140: // Not necessary to worry about parser exceptions passed to
141: // fatalError() because we're starting with a known good document.
142: // Only exceptions that can arise are thrown by
143: // the supplied ContentHandler, and we don't want to pass those
144: // to the ErrorHandler, or call endDocument() if such an exception
145: // is thrown
146: /**
147: * <p>
148: * Feed a document through this converter.
149: * </p>
150: *
151: * @param doc the document to pass to SAX
152: *
153: * @throws SAXException if the content handler
154: * or lexical handler throws an exception
155: */
156: public void convert(Document doc) throws SAXException {
157:
158: locator = new LocatorImpl();
159: locator.setSystemId(doc.getBaseURI());
160: contentHandler.setDocumentLocator(locator);
161: contentHandler.startDocument();
162: for (int i = 0; i < doc.getChildCount(); i++) {
163: process(doc.getChild(i));
164: }
165: contentHandler.endDocument();
166:
167: }
168:
169: private void process(Node node) throws SAXException {
170:
171: if (node instanceof Element) {
172: convertElement((Element) node);
173: } else if (node instanceof Text) {
174: String data = node.getValue();
175: contentHandler.characters(data.toCharArray(), 0, data
176: .length());
177: } else if (node instanceof ProcessingInstruction) {
178: ProcessingInstruction instruction = (ProcessingInstruction) node;
179:
180: contentHandler.processingInstruction(instruction
181: .getTarget(), instruction.getValue());
182: } else if (node instanceof Comment && lexicalHandler != null) {
183: String data = node.getValue();
184: lexicalHandler
185: .comment(data.toCharArray(), 0, data.length());
186: } else if (node instanceof DocType && lexicalHandler != null) {
187: DocType type = (DocType) node;
188: lexicalHandler.startDTD(type.getRootElementName(), type
189: .getPublicID(), type.getSystemID());
190: lexicalHandler.endDTD();
191: }
192: // all other types are ignored
193:
194: }
195:
196: /**
197: * @param element the context in which the prefix is mapped
198: * @param prefix the prefix to pass to statPrefixMapping
199: * @return true if and only if startPrefixMapping was called
200: * @throws SAXException if the ContentHandler throws an exception
201: */
202: private boolean convertNamespace(Element element, String prefix)
203: throws SAXException {
204:
205: String uri = element.getNamespaceURI(prefix);
206: ParentNode parentNode = element.getParent();
207: Element parent = null;
208: if (parentNode instanceof Element) {
209: parent = (Element) parentNode;
210: }
211:
212: if (parent != null
213: && uri.equals(parent.getNamespaceURI(prefix))) {
214: return false;
215: } else if (parent == null && "".equals(uri)) {
216: // Do not fire startPrefixMapping event for no namespace
217: // on root element
218: return false;
219: }
220: contentHandler.startPrefixMapping(prefix, uri);
221: return true; // i.e. converted
222:
223: }
224:
225: private void convertElement(Element element) throws SAXException {
226:
227: locator.setSystemId(element.getBaseURI());
228:
229: // start prefix mapping
230: int namespaceCount = element.getNamespaceDeclarationCount();
231: String[] prefixes = new String[namespaceCount];
232: int prefixCount = 0;
233: for (int i = 0; i < namespaceCount; i++) {
234: String prefix = element.getNamespacePrefix(i);
235: boolean converted = convertNamespace(element, prefix);
236: if (converted) {
237: prefixes[prefixCount] = prefix;
238: prefixCount++;
239: }
240: }
241:
242: // prepare attributes
243: AttributesImpl saxAttributes = new AttributesImpl();
244: int attributeCount = element.getAttributeCount();
245: for (int i = 0; i < attributeCount; i++) {
246: Attribute attribute = element.getAttribute(i);
247: // The base URIs provided by the locator have already
248: // accounted for any xml:base attributes. We do not
249: // also pass in xml:base attributes or some relative base
250: // URIs could be applied twice.
251: if ("base".equals(attribute.getLocalName())
252: && "http://www.w3.org/XML/1998/namespace"
253: .equals(attribute.getNamespaceURI())
254: && stripBaseAttributes) {
255: continue;
256: }
257: saxAttributes.addAttribute(attribute.getNamespaceURI(),
258: attribute.getLocalName(), attribute
259: .getQualifiedName(), getSAXType(attribute),
260: attribute.getValue());
261: }
262:
263: contentHandler.startElement(element.getNamespaceURI(), element
264: .getLocalName(), element.getQualifiedName(),
265: saxAttributes);
266: int childCount = element.getChildCount();
267: for (int i = 0; i < childCount; i++) {
268: process(element.getChild(i));
269: }
270: contentHandler.endElement(element.getNamespaceURI(), element
271: .getLocalName(), element.getQualifiedName());
272:
273: // end prefix mappings
274: for (int i = 0; i < prefixCount; i++) {
275: contentHandler.endPrefixMapping(prefixes[i]);
276: }
277:
278: }
279:
280: private static String getSAXType(Attribute attribute) {
281:
282: Attribute.Type type = attribute.getType();
283: if (type.equals(Attribute.Type.UNDECLARED))
284: return "CDATA";
285: if (type.equals(Attribute.Type.CDATA))
286: return "CDATA";
287: if (type.equals(Attribute.Type.ID))
288: return "ID";
289: if (type.equals(Attribute.Type.IDREF))
290: return "IDREF";
291: if (type.equals(Attribute.Type.IDREFS))
292: return "IDREFS";
293: if (type.equals(Attribute.Type.NMTOKEN))
294: return "NMTOKEN";
295: if (type.equals(Attribute.Type.NMTOKENS))
296: return "NMTOKENS";
297: if (type.equals(Attribute.Type.ENTITY))
298: return "ENTITY";
299: if (type.equals(Attribute.Type.ENTITIES))
300: return "ENTITIES";
301: if (type.equals(Attribute.Type.NOTATION))
302: return "NOTATION";
303: return "NMTOKEN"; // ENUMERATED
304:
305: }
306:
307: /**
308: * <p>
309: * Converts a <code>Nodes</code> list into SAX by firing events
310: * into the registered handlers. This method calls
311: * <code>startDocument</code> before processing the list
312: * of nodes, and calls <code>endDocument</code> after processing
313: * all of them.
314: * </p>
315: *
316: * @param nodes the nodes to pass to SAX
317: *
318: * @throws SAXException if the content handler
319: * or lexical handler throws an exception
320: */
321: public void convert(Nodes nodes) throws SAXException {
322:
323: if (nodes.size() == 1 && nodes.get(0) instanceof Document) {
324: convert((Document) nodes.get(0));
325: } else {
326: locator = new LocatorImpl();
327: contentHandler.setDocumentLocator(locator);
328: contentHandler.startDocument();
329: for (int i = 0; i < nodes.size(); i++) {
330: process(nodes.get(i));
331: }
332: contentHandler.endDocument();
333: }
334:
335: }
336:
337: }
|