| /* |
| |
| Copyright 2001-2003 The Apache Software Foundation |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| package org.apache.batik.dom.util; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InterruptedIOException; |
| import java.io.Reader; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| import org.w3c.dom.DOMImplementation; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| |
| import org.xml.sax.Attributes; |
| import org.xml.sax.ErrorHandler; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.Locator; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| import org.xml.sax.XMLReader; |
| import org.xml.sax.ext.LexicalHandler; |
| import org.xml.sax.helpers.DefaultHandler; |
| import org.xml.sax.helpers.XMLReaderFactory; |
| |
| import org.apache.batik.util.HaltingThread; |
| |
| /** |
| * This class contains methods for creating Document instances |
| * from an URI using SAX2. |
| * |
| * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a> |
| * @version $Id$ |
| */ |
| public class SAXDocumentFactory |
| extends DefaultHandler |
| implements LexicalHandler, |
| DocumentFactory { |
| |
| /** |
| * The DOM implementation used to create the document. |
| */ |
| protected DOMImplementation implementation; |
| |
| /** |
| * The SAX2 parser classname. |
| */ |
| protected String parserClassName; |
| |
| /** |
| * The created document. |
| */ |
| protected Document document; |
| |
| /** |
| * The created document descriptor. |
| */ |
| protected DocumentDescriptor documentDescriptor; |
| |
| /** |
| * Whether a document descriptor must be generated. |
| */ |
| protected boolean createDocumentDescriptor; |
| |
| /** |
| * The current node. |
| */ |
| protected Node currentNode; |
| |
| /** |
| * The locator. |
| */ |
| protected Locator locator; |
| |
| /** |
| * Contains collected string data. May be Text, CDATA or Comment. |
| */ |
| protected StringBuffer stringBuffer = new StringBuffer(); |
| /** |
| * Indicates if stringBuffer has content, needed in case of |
| * zero sized "text" content. |
| */ |
| protected boolean stringContent; |
| |
| /** |
| * True if the parser is currently parsing a DTD. |
| */ |
| protected boolean inDTD; |
| |
| /** |
| * True if the parser is currently parsing a CDATA section. |
| */ |
| protected boolean inCDATA; |
| |
| /** |
| * Whether the parser is in validating mode. |
| */ |
| protected boolean isValidating; |
| |
| /** |
| * The stack used to store the namespace URIs. |
| */ |
| protected HashTableStack namespaces; |
| |
| /** |
| * The error handler. |
| */ |
| protected ErrorHandler errorHandler; |
| |
| protected interface PreInfo { |
| public Node createNode(Document doc); |
| } |
| |
| static class ProcessingInstructionInfo implements PreInfo { |
| public String target, data; |
| public ProcessingInstructionInfo(String target, String data) { |
| this.target = target; |
| this.data = data; |
| } |
| public Node createNode(Document doc) { |
| return doc.createProcessingInstruction(target, data); |
| } |
| } |
| |
| static class CommentInfo implements PreInfo { |
| public String comment; |
| public CommentInfo(String comment) { |
| this.comment = comment; |
| } |
| public Node createNode(Document doc) { |
| return doc.createComment(comment); |
| } |
| } |
| |
| static class CDataInfo implements PreInfo { |
| public String cdata; |
| public CDataInfo(String cdata) { |
| this.cdata = cdata; |
| } |
| public Node createNode(Document doc) { |
| return doc.createCDATASection(cdata); |
| } |
| } |
| |
| static class TextInfo implements PreInfo { |
| public String text; |
| public TextInfo(String text) { |
| this.text = text; |
| } |
| public Node createNode(Document doc) { |
| return doc.createTextNode(text); |
| } |
| } |
| |
| /** |
| * Various elements encountered prior to real document root element. |
| * List of PreInfo objects. |
| */ |
| protected List preInfo; |
| |
| /** |
| * Creates a new SAXDocumentFactory object. |
| * No document descriptor will be created while generating a document. |
| * @param impl The DOM implementation to use for building the DOM tree. |
| * @param parser The SAX2 parser classname. |
| */ |
| public SAXDocumentFactory(DOMImplementation impl, |
| String parser) { |
| implementation = impl; |
| parserClassName = parser; |
| } |
| |
| /** |
| * Creates a new SAXDocumentFactory object. |
| * @param impl The DOM implementation to use for building the DOM tree. |
| * @param parser The SAX2 parser classname. |
| * @param dd Whether a document descriptor must be generated. |
| */ |
| public SAXDocumentFactory(DOMImplementation impl, |
| String parser, |
| boolean dd) { |
| implementation = impl; |
| parserClassName = parser; |
| createDocumentDescriptor = dd; |
| } |
| |
| /** |
| * Creates a Document instance. |
| * @param ns The namespace URI of the root element of the document. |
| * @param root The name of the root element of the document. |
| * @param uri The document URI. |
| * @exception IOException if an error occured while reading the document. |
| */ |
| public Document createDocument(String ns, String root, String uri) |
| throws IOException { |
| return createDocument(ns, root, uri, new InputSource(uri)); |
| } |
| |
| /** |
| * Creates a Document instance. |
| * @param uri The document URI. |
| * @exception IOException if an error occured while reading the document. |
| */ |
| public Document createDocument(String uri) |
| throws IOException { |
| return createDocument(new InputSource(uri)); |
| } |
| |
| /** |
| * Creates a Document instance. |
| * @param ns The namespace URI of the root element of the document. |
| * @param root The name of the root element of the document. |
| * @param uri The document URI. |
| * @param is The document input stream. |
| * @exception IOException if an error occured while reading the document. |
| */ |
| public Document createDocument(String ns, String root, String uri, |
| InputStream is) throws IOException { |
| InputSource inp = new InputSource(is); |
| inp.setSystemId(uri); |
| return createDocument(ns, root, uri, inp); |
| } |
| |
| /** |
| * Creates a Document instance. |
| * @param uri The document URI. |
| * @param is The document input stream. |
| * @exception IOException if an error occured while reading the document. |
| */ |
| public Document createDocument(String uri, InputStream is) |
| throws IOException { |
| InputSource inp = new InputSource(is); |
| inp.setSystemId(uri); |
| return createDocument(inp); |
| } |
| |
| /** |
| * Creates a Document instance. |
| * @param ns The namespace URI of the root element of the document. |
| * @param root The name of the root element of the document. |
| * @param uri The document URI. |
| * @param r The document reader. |
| * @exception IOException if an error occured while reading the document. |
| */ |
| public Document createDocument(String ns, String root, String uri, |
| Reader r) throws IOException { |
| InputSource inp = new InputSource(r); |
| inp.setSystemId(uri); |
| return createDocument(ns, root, uri, inp); |
| } |
| |
| /** |
| * Creates a Document instance. |
| * @param ns The namespace URI of the root element of the document. |
| * @param root The name of the root element of the document. |
| * @param uri The document URI. |
| * @param r an XMLReaderInstance |
| * @exception IOException if an error occured while reading the document. |
| */ |
| public Document createDocument(String ns, String root, String uri, |
| XMLReader r) throws IOException { |
| r.setContentHandler(this); |
| r.setDTDHandler(this); |
| r.setEntityResolver(this); |
| try { |
| r.parse(uri); |
| } catch (SAXException e) { |
| Exception ex = e.getException(); |
| if (ex != null && ex instanceof InterruptedIOException) { |
| throw (InterruptedIOException) ex; |
| } |
| throw new IOException(e.getMessage()); |
| } |
| currentNode = null; |
| Document ret = document; |
| document = null; |
| return ret; |
| } |
| |
| /** |
| * Creates a Document instance. |
| * @param uri The document URI. |
| * @param r The document reader. |
| * @exception IOException if an error occured while reading the document. |
| */ |
| public Document createDocument(String uri, Reader r) throws IOException { |
| InputSource inp = new InputSource(r); |
| inp.setSystemId(uri); |
| return createDocument(inp); |
| } |
| |
| /** |
| * Creates a Document. |
| * @param ns The namespace URI of the root element. |
| * @param root The name of the root element. |
| * @param uri The document URI. |
| * @param is The document input source. |
| * @exception IOException if an error occured while reading the document. |
| */ |
| protected Document createDocument(String ns, String root, String uri, |
| InputSource is) |
| throws IOException { |
| Document ret = createDocument(is); |
| Element docElem = ret.getDocumentElement(); |
| |
| String lname = root; |
| String nsURI = ns; |
| if (ns == null) { |
| int idx = lname.indexOf(':'); |
| String nsp = (idx == -1 || idx == lname.length()-1) |
| ? "" |
| : lname.substring(0, idx); |
| nsURI = namespaces.get(nsp); |
| if (idx != -1 && idx != lname.length()-1) { |
| lname = lname.substring(idx+1); |
| } |
| } |
| |
| |
| String docElemNS = docElem.getNamespaceURI(); |
| if ((docElemNS != nsURI) && |
| ((docElemNS == null) || (!docElemNS.equals(nsURI)))) |
| throw new IOException |
| ("Root element namespace does not match that requested:\n" + |
| "Requested: " + nsURI + "\n" + |
| "Found: " + docElemNS); |
| |
| if (docElemNS != null) { |
| if (!docElem.getLocalName().equals(lname)) |
| throw new IOException |
| ("Root element does not match that requested:\n" + |
| "Requested: " + lname + "\n" + |
| "Found: " + docElem.getLocalName()); |
| } else { |
| if (!docElem.getNodeName().equals(lname)) |
| throw new IOException |
| ("Root element does not match that requested:\n" + |
| "Requested: " + lname + "\n" + |
| "Found: " + docElem.getNodeName()); |
| } |
| |
| return ret; |
| } |
| |
| |
| /** |
| * Creates a Document. |
| * @param is The document input source. |
| * @exception IOException if an error occured while reading the document. |
| */ |
| protected Document createDocument(InputSource is) |
| throws IOException { |
| try { |
| XMLReader parser = |
| XMLReaderFactory.createXMLReader(parserClassName); |
| |
| parser.setContentHandler(this); |
| parser.setDTDHandler(this); |
| parser.setEntityResolver(this); |
| parser.setErrorHandler((errorHandler == null) ? |
| this : errorHandler); |
| |
| parser.setFeature("http://xml.org/sax/features/namespaces", |
| true); |
| parser.setFeature("http://xml.org/sax/features/namespace-prefixes", |
| true); |
| parser.setFeature("http://xml.org/sax/features/validation", |
| isValidating); |
| parser.setFeature("http://xml.org/sax/features/external-general-entities", false); |
| parser.setFeature("http://xml.org/sax/features/external-parameter-entities", false); |
| parser.setProperty("http://xml.org/sax/properties/lexical-handler", |
| this); |
| parser.parse(is); |
| } catch (SAXException e) { |
| Exception ex = e.getException(); |
| if (ex != null && ex instanceof InterruptedIOException) { |
| throw (InterruptedIOException)ex; |
| } |
| throw new IOException(e.getMessage()); |
| } |
| |
| currentNode = null; |
| Document ret = document; |
| document = null; |
| locator = null; |
| return ret; |
| } |
| |
| /** |
| * Returns the document descriptor associated with the latest created |
| * document. |
| * @return null if no document or descriptor was previously generated. |
| */ |
| public DocumentDescriptor getDocumentDescriptor() { |
| return documentDescriptor; |
| } |
| |
| /** |
| * <b>SAX</b>: Implements {@link |
| * org.xml.sax.ContentHandler#setDocumentLocator(Locator)}. |
| */ |
| public void setDocumentLocator(Locator l) { |
| locator = l; |
| } |
| |
| /** |
| * Sets whether or not the XML parser will validate the XML document |
| * depending on the specified parameter. |
| * |
| * @param isValidating indicates that the XML parser will validate the XML |
| * document |
| */ |
| public void setValidating(boolean isValidating) { |
| this.isValidating = isValidating; |
| } |
| |
| /** |
| * Returns true if the XML parser validates the XML stream, false |
| * otherwise. |
| */ |
| public boolean isValidating() { |
| return isValidating; |
| } |
| |
| /** |
| * Sets a custom error handler. |
| */ |
| public void setErrorHandler(ErrorHandler eh) { |
| errorHandler = eh; |
| } |
| |
| public DOMImplementation getDOMImplementation(String ver) { |
| return implementation; |
| } |
| |
| /** |
| * <b>SAX</b>: Implements {@link |
| * org.xml.sax.ErrorHandler#fatalError(SAXParseException)}. |
| */ |
| public void fatalError(SAXParseException ex) throws SAXException { |
| throw ex; |
| } |
| |
| /** |
| * <b>SAX</b>: Implements {@link |
| * org.xml.sax.ErrorHandler#error(SAXParseException)}. |
| */ |
| public void error(SAXParseException ex) throws SAXException { |
| throw ex; |
| } |
| |
| /** |
| * <b>SAX</b>: Implements {@link |
| * org.xml.sax.ErrorHandler#warning(SAXParseException)}. |
| */ |
| public void warning(SAXParseException ex) throws SAXException { |
| } |
| |
| /** |
| * <b>SAX</b>: Implements {@link |
| * org.xml.sax.ContentHandler#startDocument()}. |
| */ |
| public void startDocument() throws SAXException { |
| preInfo = new LinkedList(); |
| namespaces = new HashTableStack(); |
| namespaces.put("xml", XMLSupport.XML_NAMESPACE_URI); |
| namespaces.put("xmlns", XMLSupport.XMLNS_NAMESPACE_URI); |
| namespaces.put("", null); |
| |
| inDTD = false; |
| inCDATA = false; |
| currentNode = null; |
| document = null; |
| |
| stringBuffer.setLength(0); |
| stringContent = false; |
| |
| if (createDocumentDescriptor) { |
| documentDescriptor = new DocumentDescriptor(); |
| } else { |
| documentDescriptor = null; |
| } |
| } |
| |
| /** |
| * <b>SAX</b>: Implements {@link |
| * org.xml.sax.ContentHandler#startElement(String,String,String,Attributes)}. |
| */ |
| public void startElement(String uri, |
| String localName, |
| String rawName, |
| Attributes attributes) throws SAXException { |
| // Check If we should halt early. |
| if (HaltingThread.hasBeenHalted()) { |
| throw new SAXException(new InterruptedIOException()); |
| } |
| |
| // Namespaces resolution |
| int len = attributes.getLength(); |
| namespaces.push(); |
| String version = null; |
| for (int i = 0; i < len; i++) { |
| String aname = attributes.getQName(i); |
| int slen = aname.length(); |
| if (slen < 5) |
| continue; |
| if (aname.equals("version")) { |
| version = attributes.getValue(i); |
| continue; |
| } |
| if (!aname.startsWith("xmlns")) |
| continue; |
| if (slen == 5) { |
| String ns = attributes.getValue(i); |
| if (ns.length() == 0) |
| ns = null; |
| namespaces.put("", ns); |
| } else if (aname.charAt(5) == ':') { |
| String ns = attributes.getValue(i); |
| if (ns.length() == 0) { |
| ns = null; |
| } |
| namespaces.put(aname.substring(6), ns); |
| } |
| } |
| |
| // Add any collected String Data before element. |
| appendStringData(); |
| |
| // Element creation |
| Element e; |
| int idx = rawName.indexOf(':'); |
| String nsp = (idx == -1 || idx == rawName.length()-1) |
| ? "" |
| : rawName.substring(0, idx); |
| String nsURI = namespaces.get(nsp); |
| if (currentNode == null) { |
| implementation = getDOMImplementation(version); |
| document = implementation.createDocument(nsURI, rawName, null); |
| Iterator i = preInfo.iterator(); |
| currentNode = e = document.getDocumentElement(); |
| while (i.hasNext()) { |
| PreInfo pi = (PreInfo)i.next(); |
| Node n = pi.createNode(document); |
| document.insertBefore(n, e); |
| } |
| preInfo = null; |
| } else { |
| e = document.createElementNS(nsURI, rawName); |
| currentNode.appendChild(e); |
| currentNode = e; |
| } |
| |
| // Storage of the line number. |
| if (createDocumentDescriptor && locator != null) { |
| documentDescriptor.setLocationLine(e, locator.getLineNumber()); |
| } |
| |
| // Attributes creation |
| for (int i = 0; i < len; i++) { |
| String aname = attributes.getQName(i); |
| if (aname.equals("xmlns")) { |
| e.setAttributeNS(XMLSupport.XMLNS_NAMESPACE_URI, |
| aname, |
| attributes.getValue(i)); |
| } else { |
| idx = aname.indexOf(':'); |
| nsURI = (idx == -1) |
| ? null |
| : namespaces.get(aname.substring(0, idx)); |
| e.setAttributeNS(nsURI, aname, attributes.getValue(i)); |
| } |
| } |
| } |
| |
| /** |
| * <b>SAX</b>: Implements {@link |
| * org.xml.sax.ContentHandler#endElement(String,String,String)}. |
| */ |
| public void endElement(String uri, String localName, String rawName) |
| throws SAXException { |
| appendStringData(); // add string data if any. |
| |
| if (currentNode != null) |
| currentNode = currentNode.getParentNode(); |
| namespaces.pop(); |
| } |
| |
| public void appendStringData() { |
| if (!stringContent) return; |
| |
| String str = stringBuffer.toString(); |
| stringBuffer.setLength(0); // reuse buffer. |
| stringContent = false; |
| if (currentNode == null) { |
| if (inCDATA) preInfo.add(new CDataInfo(str)); |
| else preInfo.add(new TextInfo(str)); |
| } else { |
| Node n; |
| if (inCDATA) n = document.createCDATASection(str); |
| else n = document.createTextNode(str); |
| currentNode.appendChild(n); |
| } |
| } |
| |
| /** |
| * <b>SAX</b>: Implements {@link |
| * org.xml.sax.ContentHandler#characters(char[],int,int)}. |
| */ |
| public void characters(char ch[], int start, int length) |
| throws SAXException { |
| stringBuffer.append(ch, start, length); |
| stringContent = true; |
| } |
| |
| |
| /** |
| * <b>SAX</b>: Implements {@link |
| * org.xml.sax.ContentHandler#ignorableWhitespace(char[],int,int)}. |
| */ |
| public void ignorableWhitespace(char[] ch, |
| int start, |
| int length) |
| throws SAXException { |
| stringBuffer.append(ch, start, length); |
| stringContent = true; |
| } |
| |
| /** |
| * <b>SAX</b>: Implements {@link |
| * org.xml.sax.ContentHandler#processingInstruction(String,String)}. |
| */ |
| public void processingInstruction(String target, String data) |
| throws SAXException { |
| if (inDTD) |
| return; |
| |
| appendStringData(); // Add any collected String Data before PI |
| |
| if (currentNode == null) |
| preInfo.add(new ProcessingInstructionInfo(target, data)); |
| else |
| currentNode.appendChild |
| (document.createProcessingInstruction(target, data)); |
| } |
| |
| // LexicalHandler ///////////////////////////////////////////////////////// |
| |
| /** |
| * <b>SAX</b>: Implements {@link |
| * org.xml.sax.ext.LexicalHandler#startDTD(String,String,String)}. |
| */ |
| public void startDTD(String name, String publicId, String systemId) |
| throws SAXException { |
| appendStringData(); // Add collected string data before entering DTD |
| inDTD = true; |
| } |
| |
| /** |
| * <b>SAX</b>: Implements {@link org.xml.sax.ext.LexicalHandler#endDTD()}. |
| */ |
| public void endDTD() throws SAXException { |
| inDTD = false; |
| } |
| |
| /** |
| * <b>SAX</b>: Implements |
| * {@link org.xml.sax.ext.LexicalHandler#startEntity(String)}. |
| */ |
| public void startEntity(String name) throws SAXException { |
| } |
| |
| /** |
| * <b>SAX</b>: Implements |
| * {@link org.xml.sax.ext.LexicalHandler#endEntity(String)}. |
| */ |
| public void endEntity(String name) throws SAXException { |
| } |
| |
| /** |
| * <b>SAX</b>: Implements {@link |
| * org.xml.sax.ext.LexicalHandler#startCDATA()}. |
| */ |
| public void startCDATA() throws SAXException { |
| appendStringData(); // Add any collected String Data before CData |
| inCDATA = true; |
| stringContent = true; // always create CDATA even if empty. |
| } |
| |
| /** |
| * <b>SAX</b>: Implements {@link |
| * org.xml.sax.ext.LexicalHandler#endCDATA()}. |
| */ |
| public void endCDATA() throws SAXException { |
| appendStringData(); // Add the CDATA section |
| inCDATA = false; |
| } |
| |
| /** |
| * <b>SAX</b>: Implements |
| * {@link org.xml.sax.ext.LexicalHandler#comment(char[],int,int)}. |
| */ |
| public void comment(char ch[], int start, int length) throws SAXException { |
| if (inDTD) return; |
| appendStringData(); |
| |
| String str = new String(ch, start, length); |
| if (currentNode == null) { |
| preInfo.add(new CommentInfo(str)); |
| } else { |
| currentNode.appendChild(document.createComment(str)); |
| } |
| } |
| } |