
org.enhydra.xml.xmlc.parsers.DocBuilder Maven / Gradle / Ivy
/*
* Enhydra Java Application Server Project
*
* The contents of this file are subject to the Enhydra Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License on
* the Enhydra web site ( http://www.enhydra.org/ ).
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific terms governing rights and limitations
* under the License.
*
* The Initial Developer of the Enhydra Application Server is Lutris
* Technologies, Inc. The Enhydra Application Server and portions created
* by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
* All Rights Reserved.
*
* Contributor(s):
*
* $Id: DocBuilder.java,v 1.2 2005/01/26 08:29:24 jkjome Exp $
*/
package org.enhydra.xml.xmlc.parsers;
import java.util.ArrayList;
import org.enhydra.xml.xmlc.XMLCError;
import org.enhydra.xml.xmlc.XMLCException;
import org.enhydra.xml.xmlc.dom.XMLCDocument;
import org.enhydra.xml.xmlc.dom.XMLCDomFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.EntityReference;
import org.w3c.dom.Node;
/**
* Class for building a Document DOM. Used by XMLC XML parsers to build a DOM
* as well as collect other information about the document.
*
* The document builder functions assume they are being called in the order the
* document is parsed. They keep a current node where new child nodes are
* appended.
*/
public class DocBuilder {
/**
* Default XML encoding.
*/
private static final String DEFAULT_XML_ENCODING = "UTF-8";
/**
* Factory for creating the document.
*/
private XMLCDomFactory fDomFactory;
/**
* XMLC container for document.
*/
private XMLCDocument fXmlcDoc;
/**
* The document.
*/
private Document fDocument;
/**
* Information from the DocumentType.
*/
private String fDocTypeName; // Root element.
private String fPublicId;
private String fSystemId;
/*
* Internal subset as a string.
*/
private String fInternalSubsetStr;
/**
* The current node that is being constructed. This functions
* as a stack during document construction.
*/
protected Node fCurrentNode;
/**
* Holds comments and processing instructions before the first element
* until the first element is encountered. Comments are strings,
* processing instructions are string arrays of length two.
*/
private ArrayList fPendingPreDocument;
/**
* Constructor.
*
* @param domFactory Factory class for Documents.
*/
public DocBuilder(XMLCDomFactory domFactory) throws XMLCException {
fDomFactory = domFactory;
fXmlcDoc = new XMLCDocument(domFactory);
}
/**
* Generate an error if the doc type has been created, indicating
* a bug in the code using this class.
*/
private void checkIfAlreadyCreated() {
if (fDocument != null) {
throw new XMLCError("XMLC bug: attempt to add document type data after DocumentType object has been created");
}
}
/**
* Set the XML version.
*
* @param xmlVersion XML version string.
*/
public void setXMLVersion(String xmlVersion) {
fXmlcDoc.setXMLVersion(xmlVersion);
}
/**
* Set the encoding for the document.
*
* @param encoding The encoding for the document.
*/
public void setEncoding(String encoding) {
fXmlcDoc.setEncoding(encoding);
}
/**
* Set the document type name (rootElement).
*
* @param docTypeName The Document type name (also root node name).
*/
public void setDocumentTypeName(String docTypeName) {
checkIfAlreadyCreated();
fDocTypeName = docTypeName;
}
/**
* Set the publicId.
*
* @param publicId Document type public id or null if standalone.
*/
public void setPublicId(String publicId) {
checkIfAlreadyCreated();
fPublicId = publicId;
}
/**
* Set the systemId.
*
* @param systemId Document type system id or null if standalone.
*/
public void setSystemId(String systemId) {
checkIfAlreadyCreated();
fSystemId = systemId;
}
/**
* Add internal subset as a single string.
*/
public void setInternalSubset(String subsetStr) {
checkIfAlreadyCreated();
fInternalSubsetStr = subsetStr;
}
/**
* Flag a element as having #PCDATA as part of its content model.
*/
public void addPCDataContentElement(String elementName) {
checkIfAlreadyCreated();
fXmlcDoc.addPCDataContentElement(elementName);
}
/**
* Define an element id attribute.
*/
public void addIdAttribute(String elementName,
String attributeName) {
checkIfAlreadyCreated();
fXmlcDoc.addIdAttribute(elementName, attributeName);
}
/**
* Generate error about a method being called that should be called before
* the document is created.
*/
private void docNotCreatedError() {
throw new XMLCError("Bug: parser event on document contents occured before document is created");
}
/**
* Hold a comment until the start of the document.
*/
private void holdComment(String data) {
if (fPendingPreDocument == null) {
fPendingPreDocument = new ArrayList();
}
fPendingPreDocument.add(data);
}
/**
* Hold a processing instruction until the start of the document.
*/
private void holdProcessingInstruction(String target,
String data) {
if (fPendingPreDocument == null) {
fPendingPreDocument = new ArrayList();
}
fPendingPreDocument.add(new String[]{target, data});
}
/**
* Add in comments and processing instructions that occured before the
* first element.
*/
private void addPendingPreDocument() {
// Add comments before document element.
Node first = fDocument.getFirstChild();
for (int idx = 0; idx < fPendingPreDocument.size(); idx++) {
Object entry = fPendingPreDocument.get(idx);
if (entry instanceof String) {
fDocument.insertBefore(fDocument.createComment((String)entry),
first);
} else {
String[] pi = (String[])entry;
fDocument.insertBefore(fDocument.createProcessingInstruction(pi[0], pi[1]),
first);
}
}
}
/**
* Create the Document object when the first element of the document is
* found. This is delayed until the the first element, since all
* information needed to build the document is not available until that
* time.
*/
private void createDocument(String namespaceURI,
String rootTagName) {
if ((fPublicId != null) || (fSystemId != null) || (fInternalSubsetStr != null)) {
fXmlcDoc.createDocumentType(fDocTypeName, fPublicId, fSystemId,
fInternalSubsetStr);
}
fDocument = fXmlcDoc.createDocument(namespaceURI, rootTagName);
fCurrentNode = fDocument.getDocumentElement();
if (fPendingPreDocument != null) {
addPendingPreDocument();
}
}
/**
* Get the document associated with this object.
*/
public XMLCDocument getDocument() {
if (fDocument == null) {
throw new XMLCError("XMLC bug: Attempt to get document that hasn;t been created");
}
return fXmlcDoc;
}
/**
* Get the node on the top of the stack during parsing.
* FIXME: To support the broken swing parser.
*/
public Node getCurrentNode() {
return fCurrentNode;
}
/**
* Pop the current node off of the stack. This is *only* used
* during error recover from a broken parser.
* FIXME: This and getCurrentNode() should go away when the
* with the broken swing parser.
*/
public void popCurrentNode() {
fCurrentNode = fCurrentNode.getParentNode();
}
/**
* Start a new Element.
*/
public void startElement(String namespaceURI,
String tagName) {
if (fDocument == null) {
// root (first) element
createDocument(namespaceURI, tagName);
} else {
// non-root element
Element element;
if (namespaceURI != null) {
element = fDocument.createElementNS(namespaceURI, tagName);
} else {
element = fDocument.createElement(tagName);
}
fCurrentNode.appendChild(element);
fCurrentNode = element;
}
}
/**
* Add an attribute to the element on the top of the
* stack.
*/
//FIXME: This doesn't handle attr entity refs
public void addAttribute(String namespaceURI,
String name,
String value) {
// Xerces code sezs: DOM Level 2 wants all namespace declaration
// attributes to be bound to "http://www.w3.org/2000/xmlns/" So as
// long as the XML parser doesn't do it, it needs to done here.
if ((namespaceURI == null) || (namespaceURI.length() == 0)) {
int prefixIdx = name.indexOf(':');
String prefix = (prefixIdx < 0) ? null : name.substring(0, prefixIdx);
if ((prefix != null) && (prefix.equals("xmlns"))
|| (name.equals("xmlns"))) {
namespaceURI = "http://www.w3.org/2000/xmlns/";
}
}
if (namespaceURI != null) {
((Element)fCurrentNode).setAttributeNS(namespaceURI, name, value);
} else {
((Element)fCurrentNode).setAttribute(name, value);
}
}
/**
* Finish the element being constructed.
*/
public void finishElement() {
if (fCurrentNode == null) {
throw new XMLCError("node stack underflow; malformed document");
}
if (!(fCurrentNode instanceof Element)) {
throw new XMLCError("DOM node top of stack not a element for end tag");
}
fCurrentNode = fCurrentNode.getParentNode();
}
/**
* Start an entity reference in the document (not DTD).
*/
public void startEntityReference(String entityName) {
if (fDocument == null) {
docNotCreatedError();
}
EntityReference entityRef = fDocument.createEntityReference(entityName);
fCurrentNode.appendChild(entityRef);
fCurrentNode = entityRef;
}
/**
* End an entity reference.
*/
public void endEntityReference() {
if (fCurrentNode == null) {
throw new XMLCError("node stack underflow; malformed document");
}
if (!(fCurrentNode instanceof EntityReference)) {
throw new XMLCError("DOM node top of stack not a EntityReference for end tag");
}
fCurrentNode = fCurrentNode.getParentNode();
}
/**
* Add a Text
node.
*/
public void addTextNode(String data) {
if (fDocument == null) {
docNotCreatedError();
}
fCurrentNode.appendChild(fDocument.createTextNode(data));
}
/**
* Add a Comment
node.
*/
public void addComment(String data) {
if (fDocument == null) {
holdComment(data); // before first element..
} else {
fCurrentNode.appendChild(fDocument.createComment(data));
}
}
/**
* Add a CDATASection
node.
*/
public void addCDATASection(String data) {
if (fDocument == null) {
docNotCreatedError();
}
fCurrentNode.appendChild(fDocument.createCDATASection(data));
}
/**
* Add a ProcessingInstruction
node.
*/
public void addProcessingInstruction(String target,
String data) {
if (fDocument == null) {
holdProcessingInstruction(target, data);
} else {
fCurrentNode.appendChild(fDocument.createProcessingInstruction(target, data));
}
}
/**
* Add an EntityReference object.
*/
public void addEntityReference(String name) {
if (fDocument == null) {
docNotCreatedError();
}
fCurrentNode.appendChild(fDocument.createEntityReference(name));
}
/**
* Called at the end of parsing, to finish any pending tasks, default
* values, etc.
*/
public void finish() {
if (fXmlcDoc.getEncoding() == null) {
fXmlcDoc.setEncoding(DEFAULT_XML_ENCODING);
}
}
}