/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you 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. | |
*/ | |
/* | |
* $Id: $ | |
*/ | |
package org.apache.xml.serializer.dom3; | |
import java.io.File; | |
import java.io.IOException; | |
import java.io.Writer; | |
import java.util.Enumeration; | |
import java.util.Hashtable; | |
import java.util.Properties; | |
import org.apache.xml.serializer.dom3.NamespaceSupport; | |
import org.apache.xml.serializer.OutputPropertiesFactory; | |
import org.apache.xml.serializer.SerializationHandler; | |
import org.apache.xml.serializer.utils.MsgKey; | |
import org.apache.xml.serializer.utils.Utils; | |
import org.apache.xml.serializer.utils.XML11Char; | |
import org.apache.xml.serializer.utils.XMLChar; | |
import org.w3c.dom.Attr; | |
import org.w3c.dom.CDATASection; | |
import org.w3c.dom.Comment; | |
import org.w3c.dom.DOMError; | |
import org.w3c.dom.DOMErrorHandler; | |
import org.w3c.dom.Document; | |
import org.w3c.dom.DocumentType; | |
import org.w3c.dom.Element; | |
import org.w3c.dom.Entity; | |
import org.w3c.dom.EntityReference; | |
import org.w3c.dom.NamedNodeMap; | |
import org.w3c.dom.Node; | |
import org.w3c.dom.NodeList; | |
import org.w3c.dom.ProcessingInstruction; | |
import org.w3c.dom.Text; | |
import org.w3c.dom.ls.LSSerializerFilter; | |
import org.w3c.dom.traversal.NodeFilter; | |
import org.xml.sax.Locator; | |
import org.xml.sax.SAXException; | |
import org.xml.sax.ext.LexicalHandler; | |
import org.xml.sax.helpers.LocatorImpl; | |
/** | |
* Built on org.apache.xml.serializer.TreeWalker and adds functionality to | |
* traverse and serialize a DOM Node (Level 2 or Level 3) as specified in | |
* the DOM Level 3 LS Recommedation by evaluating and applying DOMConfiguration | |
* parameters and filters if any during serialization. | |
* | |
* @xsl.usage internal | |
*/ | |
final class DOM3TreeWalker { | |
/** | |
* The SerializationHandler, it extends ContentHandler and when | |
* this class is instantiated via the constructor provided, a | |
* SerializationHandler object is passed to it. | |
*/ | |
private SerializationHandler fSerializer = null; | |
/** We do not need DOM2Helper since DOM Level 3 LS applies to DOM Level 2 or newer */ | |
/** Locator object for this TreeWalker */ | |
private LocatorImpl fLocator = new LocatorImpl(); | |
/** ErrorHandler */ | |
private DOMErrorHandler fErrorHandler = null; | |
/** LSSerializerFilter */ | |
private LSSerializerFilter fFilter = null; | |
/** If the serializer is an instance of a LexicalHandler */ | |
private LexicalHandler fLexicalHandler = null; | |
private int fWhatToShowFilter; | |
/** New Line character to use in serialization */ | |
private String fNewLine = null; | |
/** DOMConfiguration Properties */ | |
private Properties fDOMConfigProperties = null; | |
/** Keeps track if we are in an entity reference when entities=true */ | |
private boolean fInEntityRef = false; | |
/** Stores the version of the XML document to be serialize */ | |
private String fXMLVersion = null; | |
/** XML Version, default 1.0 */ | |
private boolean fIsXMLVersion11 = false; | |
/** Is the Node a Level 3 DOM node */ | |
private boolean fIsLevel3DOM = false; | |
/** DOM Configuration Parameters */ | |
private int fFeatures = 0; | |
/** Flag indicating whether following text to be processed is raw text */ | |
boolean fNextIsRaw = false; | |
// | |
private static final String XMLNS_URI = "http://www.w3.org/2000/xmlns/"; | |
// | |
private static final String XMLNS_PREFIX = "xmlns"; | |
// | |
private static final String XML_URI = "http://www.w3.org/XML/1998/namespace"; | |
// | |
private static final String XML_PREFIX = "xml"; | |
/** stores namespaces in scope */ | |
protected NamespaceSupport fNSBinder; | |
/** stores all namespace bindings on the current element */ | |
protected NamespaceSupport fLocalNSBinder; | |
/** stores the current element depth */ | |
private int fElementDepth = 0; | |
// *********************************************************************** | |
// DOMConfiguration paramter settings | |
// *********************************************************************** | |
// Parameter canonical-form, true [optional] - NOT SUPPORTED | |
private final static int CANONICAL = 0x1 << 0; | |
// Parameter cdata-sections, true [required] (default) | |
private final static int CDATA = 0x1 << 1; | |
// Parameter check-character-normalization, true [optional] - NOT SUPPORTED | |
private final static int CHARNORMALIZE = 0x1 << 2; | |
// Parameter comments, true [required] (default) | |
private final static int COMMENTS = 0x1 << 3; | |
// Parameter datatype-normalization, true [optional] - NOT SUPPORTED | |
private final static int DTNORMALIZE = 0x1 << 4; | |
// Parameter element-content-whitespace, true [required] (default) - value - false [optional] NOT SUPPORTED | |
private final static int ELEM_CONTENT_WHITESPACE = 0x1 << 5; | |
// Parameter entities, true [required] (default) | |
private final static int ENTITIES = 0x1 << 6; | |
// Parameter infoset, true [required] (default), false has no effect --> True has no effect for the serializer | |
private final static int INFOSET = 0x1 << 7; | |
// Parameter namespaces, true [required] (default) | |
private final static int NAMESPACES = 0x1 << 8; | |
// Parameter namespace-declarations, true [required] (default) | |
private final static int NAMESPACEDECLS = 0x1 << 9; | |
// Parameter normalize-characters, true [optional] - NOT SUPPORTED | |
private final static int NORMALIZECHARS = 0x1 << 10; | |
// Parameter split-cdata-sections, true [required] (default) | |
private final static int SPLITCDATA = 0x1 << 11; | |
// Parameter validate, true [optional] - NOT SUPPORTED | |
private final static int VALIDATE = 0x1 << 12; | |
// Parameter validate-if-schema, true [optional] - NOT SUPPORTED | |
private final static int SCHEMAVALIDATE = 0x1 << 13; | |
// Parameter split-cdata-sections, true [required] (default) | |
private final static int WELLFORMED = 0x1 << 14; | |
// Parameter discard-default-content, true [required] (default) | |
// Not sure how this will be used in level 2 Documents | |
private final static int DISCARDDEFAULT = 0x1 << 15; | |
// Parameter format-pretty-print, true [optional] | |
private final static int PRETTY_PRINT = 0x1 << 16; | |
// Parameter ignore-unknown-character-denormalizations, true [required] (default) | |
// We currently do not support XML 1.1 character normalization | |
private final static int IGNORE_CHAR_DENORMALIZE = 0x1 << 17; | |
// Parameter discard-default-content, true [required] (default) | |
private final static int XMLDECL = 0x1 << 18; | |
/** | |
* Constructor. | |
* @param contentHandler serialHandler The implemention of the SerializationHandler interface | |
*/ | |
DOM3TreeWalker( | |
SerializationHandler serialHandler, | |
DOMErrorHandler errHandler, | |
LSSerializerFilter filter, | |
String newLine) { | |
fSerializer = serialHandler; | |
//fErrorHandler = errHandler == null ? new DOMErrorHandlerImpl() : errHandler; // Should we be using the default? | |
fErrorHandler = errHandler; | |
fFilter = filter; | |
fLexicalHandler = null; | |
fNewLine = newLine; | |
fNSBinder = new NamespaceSupport(); | |
fLocalNSBinder = new NamespaceSupport(); | |
fDOMConfigProperties = fSerializer.getOutputFormat(); | |
fSerializer.setDocumentLocator(fLocator); | |
initProperties(fDOMConfigProperties); | |
try { | |
// Bug see Bugzilla 26741 | |
fLocator.setSystemId( | |
System.getProperty("user.dir") + File.separator + "dummy.xsl"); | |
} catch (SecurityException se) { // user.dir not accessible from applet | |
} | |
} | |
/** | |
* Perform a pre-order traversal non-recursive style. | |
* | |
* Note that TreeWalker assumes that the subtree is intended to represent | |
* a complete (though not necessarily well-formed) document and, during a | |
* traversal, startDocument and endDocument will always be issued to the | |
* SAX listener. | |
* | |
* @param pos Node in the tree where to start traversal | |
* | |
* @throws TransformerException | |
*/ | |
public void traverse(Node pos) throws org.xml.sax.SAXException { | |
this.fSerializer.startDocument(); | |
// Determine if the Node is a DOM Level 3 Core Node. | |
if (pos.getNodeType() != Node.DOCUMENT_NODE) { | |
Document ownerDoc = pos.getOwnerDocument(); | |
if (ownerDoc != null | |
&& ownerDoc.getImplementation().hasFeature("Core", "3.0")) { | |
fIsLevel3DOM = true; | |
} | |
} else { | |
if (((Document) pos) | |
.getImplementation() | |
.hasFeature("Core", "3.0")) { | |
fIsLevel3DOM = true; | |
} | |
} | |
if (fSerializer instanceof LexicalHandler) { | |
fLexicalHandler = ((LexicalHandler) this.fSerializer); | |
} | |
if (fFilter != null) | |
fWhatToShowFilter = fFilter.getWhatToShow(); | |
Node top = pos; | |
while (null != pos) { | |
startNode(pos); | |
Node nextNode = null; | |
nextNode = pos.getFirstChild(); | |
while (null == nextNode) { | |
endNode(pos); | |
if (top.equals(pos)) | |
break; | |
nextNode = pos.getNextSibling(); | |
if (null == nextNode) { | |
pos = pos.getParentNode(); | |
if ((null == pos) || (top.equals(pos))) { | |
if (null != pos) | |
endNode(pos); | |
nextNode = null; | |
break; | |
} | |
} | |
} | |
pos = nextNode; | |
} | |
this.fSerializer.endDocument(); | |
} | |
/** | |
* Perform a pre-order traversal non-recursive style. | |
* Note that TreeWalker assumes that the subtree is intended to represent | |
* a complete (though not necessarily well-formed) document and, during a | |
* traversal, startDocument and endDocument will always be issued to the | |
* SAX listener. | |
* | |
* @param pos Node in the tree where to start traversal | |
* @param top Node in the tree where to end traversal | |
* | |
* @throws TransformerException | |
*/ | |
public void traverse(Node pos, Node top) throws org.xml.sax.SAXException { | |
this.fSerializer.startDocument(); | |
// Determine if the Node is a DOM Level 3 Core Node. | |
if (pos.getNodeType() != Node.DOCUMENT_NODE) { | |
Document ownerDoc = pos.getOwnerDocument(); | |
if (ownerDoc != null | |
&& ownerDoc.getImplementation().hasFeature("Core", "3.0")) { | |
fIsLevel3DOM = true; | |
} | |
} else { | |
if (((Document) pos) | |
.getImplementation() | |
.hasFeature("Core", "3.0")) { | |
fIsLevel3DOM = true; | |
} | |
} | |
if (fSerializer instanceof LexicalHandler) { | |
fLexicalHandler = ((LexicalHandler) this.fSerializer); | |
} | |
if (fFilter != null) | |
fWhatToShowFilter = fFilter.getWhatToShow(); | |
while (null != pos) { | |
startNode(pos); | |
Node nextNode = null; | |
nextNode = pos.getFirstChild(); | |
while (null == nextNode) { | |
endNode(pos); | |
if ((null != top) && top.equals(pos)) | |
break; | |
nextNode = pos.getNextSibling(); | |
if (null == nextNode) { | |
pos = pos.getParentNode(); | |
if ((null == pos) || ((null != top) && top.equals(pos))) { | |
nextNode = null; | |
break; | |
} | |
} | |
} | |
pos = nextNode; | |
} | |
this.fSerializer.endDocument(); | |
} | |
/** | |
* Optimized dispatch of characters. | |
*/ | |
private final void dispatachChars(Node node) | |
throws org.xml.sax.SAXException { | |
if (fSerializer != null) { | |
this.fSerializer.characters(node); | |
} else { | |
String data = ((Text) node).getData(); | |
this.fSerializer.characters(data.toCharArray(), 0, data.length()); | |
} | |
} | |
/** | |
* Start processing given node | |
* | |
* @param node Node to process | |
* | |
* @throws org.xml.sax.SAXException | |
*/ | |
protected void startNode(Node node) throws org.xml.sax.SAXException { | |
if (node instanceof Locator) { | |
Locator loc = (Locator) node; | |
fLocator.setColumnNumber(loc.getColumnNumber()); | |
fLocator.setLineNumber(loc.getLineNumber()); | |
fLocator.setPublicId(loc.getPublicId()); | |
fLocator.setSystemId(loc.getSystemId()); | |
} else { | |
fLocator.setColumnNumber(0); | |
fLocator.setLineNumber(0); | |
} | |
switch (node.getNodeType()) { | |
case Node.DOCUMENT_TYPE_NODE : | |
serializeDocType((DocumentType) node, true); | |
break; | |
case Node.COMMENT_NODE : | |
serializeComment((Comment) node); | |
break; | |
case Node.DOCUMENT_FRAGMENT_NODE : | |
// Children are traversed | |
break; | |
case Node.DOCUMENT_NODE : | |
break; | |
case Node.ELEMENT_NODE : | |
serializeElement((Element) node, true); | |
break; | |
case Node.PROCESSING_INSTRUCTION_NODE : | |
serializePI((ProcessingInstruction) node); | |
break; | |
case Node.CDATA_SECTION_NODE : | |
serializeCDATASection((CDATASection) node); | |
break; | |
case Node.TEXT_NODE : | |
serializeText((Text) node); | |
break; | |
case Node.ENTITY_REFERENCE_NODE : | |
serializeEntityReference((EntityReference) node, true); | |
break; | |
default : | |
} | |
} | |
/** | |
* End processing of given node | |
* | |
* | |
* @param node Node we just finished processing | |
* | |
* @throws org.xml.sax.SAXException | |
*/ | |
protected void endNode(Node node) throws org.xml.sax.SAXException { | |
switch (node.getNodeType()) { | |
case Node.DOCUMENT_NODE : | |
break; | |
case Node.DOCUMENT_TYPE_NODE : | |
serializeDocType((DocumentType) node, false); | |
break; | |
case Node.ELEMENT_NODE : | |
serializeElement((Element) node, false); | |
break; | |
case Node.CDATA_SECTION_NODE : | |
break; | |
case Node.ENTITY_REFERENCE_NODE : | |
serializeEntityReference((EntityReference) node, false); | |
break; | |
default : | |
} | |
} | |
// *********************************************************************** | |
// Node serialization methods | |
// *********************************************************************** | |
/** | |
* Applies a filter on the node to serialize | |
* | |
* @param node The Node to serialize | |
* @return True if the node is to be serialized else false if the node | |
* is to be rejected or skipped. | |
*/ | |
protected boolean applyFilter(Node node, int nodeType) { | |
if (fFilter != null && (fWhatToShowFilter & nodeType) != 0) { | |
short code = fFilter.acceptNode(node); | |
switch (code) { | |
case NodeFilter.FILTER_REJECT : | |
case NodeFilter.FILTER_SKIP : | |
return false; // skip the node | |
default : // fall through.. | |
} | |
} | |
return true; | |
} | |
/** | |
* Serializes a Document Type Node. | |
* | |
* @param node The Docuemnt Type Node to serialize | |
* @param bStart Invoked at the start or end of node. Default true. | |
*/ | |
protected void serializeDocType(DocumentType node, boolean bStart) | |
throws SAXException { | |
// The DocType and internalSubset can not be modified in DOM and is | |
// considered to be well-formed as the outcome of successful parsing. | |
String docTypeName = node.getNodeName(); | |
String publicId = node.getPublicId(); | |
String systemId = node.getSystemId(); | |
String internalSubset = node.getInternalSubset(); | |
//DocumentType nodes are never passed to the filter | |
if (internalSubset != null && !"".equals(internalSubset)) { | |
if (bStart) { | |
try { | |
// The Serializer does not provide a way to write out the | |
// DOCTYPE internal subset via an event call, so we write it | |
// out here. | |
Writer writer = fSerializer.getWriter(); | |
StringBuffer dtd = new StringBuffer(); | |
dtd.append("<!DOCTYPE "); | |
dtd.append(docTypeName); | |
if (null != publicId) { | |
dtd.append(" PUBLIC \""); | |
dtd.append(publicId); | |
dtd.append('\"'); | |
} | |
if (null != systemId) { | |
if (null == publicId) { | |
dtd.append(" SYSTEM \""); | |
} else { | |
dtd.append(" \""); | |
} | |
dtd.append(systemId); | |
dtd.append('\"'); | |
} | |
dtd.append(" [ "); | |
dtd.append(fNewLine); | |
dtd.append(internalSubset); | |
dtd.append("]>"); | |
dtd.append(new String(fNewLine)); | |
writer.write(dtd.toString()); | |
writer.flush(); | |
} catch (IOException e) { | |
throw new SAXException(Utils.messages.createMessage( | |
MsgKey.ER_WRITING_INTERNAL_SUBSET, null), e); | |
} | |
} // else if !bStart do nothing | |
} else { | |
if (bStart) { | |
if (fLexicalHandler != null) { | |
fLexicalHandler.startDTD(docTypeName, publicId, systemId); | |
} | |
} else { | |
if (fLexicalHandler != null) { | |
fLexicalHandler.endDTD(); | |
} | |
} | |
} | |
} | |
/** | |
* Serializes a Comment Node. | |
* | |
* @param node The Comment Node to serialize | |
*/ | |
protected void serializeComment(Comment node) throws SAXException { | |
// comments=true | |
if ((fFeatures & COMMENTS) != 0) { | |
String data = node.getData(); | |
// well-formed=true | |
if ((fFeatures & WELLFORMED) != 0) { | |
isCommentWellFormed(data); | |
} | |
if (fLexicalHandler != null) { | |
// apply the LSSerializer filter after the operations requested by the | |
// DOMConfiguration parameters have been applied | |
if (!applyFilter(node, NodeFilter.SHOW_COMMENT)) { | |
return; | |
} | |
fLexicalHandler.comment(data.toCharArray(), 0, data.length()); | |
} | |
} | |
} | |
/** | |
* Serializes an Element Node. | |
* | |
* @param node The Element Node to serialize | |
* @param bStart Invoked at the start or end of node. | |
*/ | |
protected void serializeElement(Element node, boolean bStart) | |
throws SAXException { | |
if (bStart) { | |
fElementDepth++; | |
// We use the Xalan specific startElement and starPrefixMapping calls | |
// (and addAttribute and namespaceAfterStartElement) as opposed to | |
// SAX specific, for performance reasons as they reduce the overhead | |
// of creating an AttList object upfront. | |
// well-formed=true | |
if ((fFeatures & WELLFORMED) != 0) { | |
isElementWellFormed(node); | |
} | |
// REVISIT: We apply the LSSerializer filter for elements before | |
// namesapce fixup | |
if (!applyFilter(node, NodeFilter.SHOW_ELEMENT)) { | |
return; | |
} | |
// namespaces=true, record and fixup namspaced element | |
if ((fFeatures & NAMESPACES) != 0) { | |
fNSBinder.pushContext(); | |
fLocalNSBinder.reset(); | |
recordLocalNSDecl(node); | |
fixupElementNS(node); | |
} | |
// Namespace normalization | |
fSerializer.startElement( | |
node.getNamespaceURI(), | |
node.getLocalName(), | |
node.getNodeName()); | |
serializeAttList(node); | |
} else { | |
fElementDepth--; | |
// apply the LSSerializer filter | |
if (!applyFilter(node, NodeFilter.SHOW_ELEMENT)) { | |
return; | |
} | |
this.fSerializer.endElement( | |
node.getNamespaceURI(), | |
node.getLocalName(), | |
node.getNodeName()); | |
// since endPrefixMapping was not used by SerializationHandler it was removed | |
// for performance reasons. | |
if ((fFeatures & NAMESPACES) != 0 ) { | |
fNSBinder.popContext(); | |
} | |
} | |
} | |
/** | |
* Serializes the Attr Nodes of an Element. | |
* | |
* @param node The OwnerElement whose Attr Nodes are to be serialized. | |
*/ | |
protected void serializeAttList(Element node) throws SAXException { | |
NamedNodeMap atts = node.getAttributes(); | |
int nAttrs = atts.getLength(); | |
for (int i = 0; i < nAttrs; i++) { | |
Node attr = atts.item(i); | |
String localName = attr.getLocalName(); | |
String attrName = attr.getNodeName(); | |
String attrPrefix = attr.getPrefix() == null ? "" : attr.getPrefix(); | |
String attrValue = attr.getNodeValue(); | |
// Determine the Attr's type. | |
String type = null; | |
if (fIsLevel3DOM) { | |
type = ((Attr) attr).getSchemaTypeInfo().getTypeName(); | |
} | |
type = type == null ? "CDATA" : type; | |
String attrNS = attr.getNamespaceURI(); | |
if (attrNS !=null && attrNS.length() == 0) { | |
attrNS=null; | |
// we must remove prefix for this attribute | |
attrName=attr.getLocalName(); | |
} | |
boolean isSpecified = ((Attr) attr).getSpecified(); | |
boolean addAttr = true; | |
boolean applyFilter = false; | |
boolean xmlnsAttr = | |
attrName.equals("xmlns") || attrName.startsWith("xmlns:"); | |
// well-formed=true | |
if ((fFeatures & WELLFORMED) != 0) { | |
isAttributeWellFormed(attr); | |
} | |
//----------------------------------------------------------------- | |
// start Attribute namespace fixup | |
//----------------------------------------------------------------- | |
// namespaces=true, normalize all non-namespace attributes | |
// Step 3. Attribute | |
if ((fFeatures & NAMESPACES) != 0 && !xmlnsAttr) { | |
// If the Attr has a namespace URI | |
if (attrNS != null) { | |
attrPrefix = attrPrefix == null ? "" : attrPrefix; | |
String declAttrPrefix = fNSBinder.getPrefix(attrNS); | |
String declAttrNS = fNSBinder.getURI(attrPrefix); | |
// attribute has no prefix (default namespace decl does not apply to | |
// attributes) | |
// OR | |
// attribute prefix is not declared | |
// OR | |
// conflict: attribute has a prefix that conflicts with a binding | |
if ("".equals(attrPrefix) || "".equals(declAttrPrefix) | |
|| !attrPrefix.equals(declAttrPrefix)) { | |
// namespaceURI matches an in scope declaration of one or | |
// more prefixes | |
if (declAttrPrefix != null && !"".equals(declAttrPrefix)) { | |
// pick the prefix that was found and change attribute's | |
// prefix and nodeName. | |
attrPrefix = declAttrPrefix; | |
if (declAttrPrefix.length() > 0 ) { | |
attrName = declAttrPrefix + ":" + localName; | |
} else { | |
attrName = localName; | |
} | |
} else { | |
// The current prefix is not null and it has no in scope | |
// declaration | |
if (attrPrefix != null && !"".equals(attrPrefix) | |
&& declAttrNS == null) { | |
// declare this prefix | |
if ((fFeatures & NAMESPACEDECLS) != 0) { | |
fSerializer.addAttribute(XMLNS_URI, attrPrefix, | |
XMLNS_PREFIX + ":" + attrPrefix, "CDATA", | |
attrNS); | |
fNSBinder.declarePrefix(attrPrefix, attrNS); | |
fLocalNSBinder.declarePrefix(attrPrefix, attrNS); | |
} | |
} else { | |
// find a prefix following the pattern "NS" +index | |
// (starting at 1) | |
// make sure this prefix is not declared in the current | |
// scope. | |
int counter = 1; | |
attrPrefix = "NS" + counter++; | |
while (fLocalNSBinder.getURI(attrPrefix) != null) { | |
attrPrefix = "NS" + counter++; | |
} | |
// change attribute's prefix and Name | |
attrName = attrPrefix + ":" + localName; | |
// create a local namespace declaration attribute | |
// Add the xmlns declaration attribute | |
if ((fFeatures & NAMESPACEDECLS) != 0) { | |
fSerializer.addAttribute(XMLNS_URI, attrPrefix, | |
XMLNS_PREFIX + ":" + attrPrefix, "CDATA", | |
attrNS); | |
fNSBinder.declarePrefix(attrPrefix, attrNS); | |
fLocalNSBinder.declarePrefix(attrPrefix, attrNS); | |
} | |
} | |
} | |
} | |
} else { // if the Attr has no namespace URI | |
// Attr has no localName | |
if (localName == null) { | |
// DOM Level 1 node! | |
String msg = Utils.messages.createMessage( | |
MsgKey.ER_NULL_LOCAL_ELEMENT_NAME, | |
new Object[] { attrName }); | |
if (fErrorHandler != null) { | |
fErrorHandler | |
.handleError(new DOMErrorImpl( | |
DOMError.SEVERITY_ERROR, msg, | |
MsgKey.ER_NULL_LOCAL_ELEMENT_NAME, null, | |
null, null)); | |
} | |
} else { // uri=null and no colon | |
// attr has no namespace URI and no prefix | |
// no action is required, since attrs don't use default | |
} | |
} | |
} | |
// discard-default-content=true | |
// Default attr's are not passed to the filter and this contraint | |
// is applied only when discard-default-content=true | |
// What about default xmlns attributes???? check for xmlnsAttr | |
if ((((fFeatures & DISCARDDEFAULT) != 0) && isSpecified) | |
|| ((fFeatures & DISCARDDEFAULT) == 0)) { | |
applyFilter = true; | |
} else { | |
addAttr = false; | |
} | |
if (applyFilter) { | |
// apply the filter for Attributes that are not default attributes | |
// or namespace decl attributes | |
if (fFilter != null | |
&& (fFilter.getWhatToShow() & NodeFilter.SHOW_ATTRIBUTE) | |
!= 0) { | |
if (!xmlnsAttr) { | |
short code = fFilter.acceptNode(attr); | |
switch (code) { | |
case NodeFilter.FILTER_REJECT : | |
case NodeFilter.FILTER_SKIP : | |
addAttr = false; | |
break; | |
default : //fall through.. | |
} | |
} | |
} | |
} | |
// if the node is a namespace node | |
if (addAttr && xmlnsAttr) { | |
// If namespace-declarations=true, add the node , else don't add it | |
if ((fFeatures & NAMESPACEDECLS) != 0) { | |
// The namespace may have been fixed up, in that case don't add it. | |
if (localName != null && !"".equals(localName)) { | |
fSerializer.addAttribute(attrNS, localName, attrName, type, attrValue); | |
} | |
} | |
} else if ( | |
addAttr && !xmlnsAttr) { // if the node is not a namespace node | |
// If namespace-declarations=true, add the node with the Attr nodes namespaceURI | |
// else add the node setting it's namespace to null or else the serializer will later | |
// attempt to add a xmlns attr for the prefixed attribute | |
if (((fFeatures & NAMESPACEDECLS) != 0) && (attrNS != null)) { | |
fSerializer.addAttribute( | |
attrNS, | |
localName, | |
attrName, | |
type, | |
attrValue); | |
} else { | |
fSerializer.addAttribute( | |
"", | |
localName, | |
attrName, | |
type, | |
attrValue); | |
} | |
} | |
// | |
if (xmlnsAttr && ((fFeatures & NAMESPACEDECLS) != 0)) { | |
int index; | |
// Use "" instead of null, as Xerces likes "" for the | |
// name of the default namespace. Fix attributed | |
// to "Steven Murray" <smurray@ebt.com>. | |
String prefix = | |
(index = attrName.indexOf(":")) < 0 | |
? "" | |
: attrName.substring(index + 1); | |
if (!"".equals(prefix)) { | |
fSerializer.namespaceAfterStartElement(prefix, attrValue); | |
} | |
} | |
} | |
} | |
/** | |
* Serializes an ProcessingInstruction Node. | |
* | |
* @param node The ProcessingInstruction Node to serialize | |
*/ | |
protected void serializePI(ProcessingInstruction node) | |
throws SAXException { | |
ProcessingInstruction pi = node; | |
String name = pi.getNodeName(); | |
// well-formed=true | |
if ((fFeatures & WELLFORMED) != 0) { | |
isPIWellFormed(node); | |
} | |
// apply the LSSerializer filter | |
if (!applyFilter(node, NodeFilter.SHOW_PROCESSING_INSTRUCTION)) { | |
return; | |
} | |
// String data = pi.getData(); | |
if (name.equals("xslt-next-is-raw")) { | |
fNextIsRaw = true; | |
} else { | |
this.fSerializer.processingInstruction(name, pi.getData()); | |
} | |
} | |
/** | |
* Serializes an CDATASection Node. | |
* | |
* @param node The CDATASection Node to serialize | |
*/ | |
protected void serializeCDATASection(CDATASection node) | |
throws SAXException { | |
// well-formed=true | |
if ((fFeatures & WELLFORMED) != 0) { | |
isCDATASectionWellFormed(node); | |
} | |
// cdata-sections = true | |
if ((fFeatures & CDATA) != 0) { | |
// split-cdata-sections = true | |
// Assumption: This parameter has an effect only when | |
// cdata-sections=true | |
// ToStream, by default splits cdata-sections. Hence the check | |
// below. | |
String nodeValue = node.getNodeValue(); | |
int endIndex = nodeValue.indexOf("]]>"); | |
if ((fFeatures & SPLITCDATA) != 0) { | |
if (endIndex >= 0) { | |
// The first node split will contain the ]] markers | |
String relatedData = nodeValue.substring(0, endIndex + 2); | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_CDATA_SECTIONS_SPLIT, | |
null); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_WARNING, | |
msg, | |
MsgKey.ER_CDATA_SECTIONS_SPLIT, | |
null, | |
relatedData, | |
null)); | |
} | |
} | |
} else { | |
if (endIndex >= 0) { | |
// The first node split will contain the ]] markers | |
String relatedData = nodeValue.substring(0, endIndex + 2); | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_CDATA_SECTIONS_SPLIT, | |
null); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_ERROR, | |
msg, | |
MsgKey.ER_CDATA_SECTIONS_SPLIT)); | |
} | |
// Report an error and return. What error??? | |
return; | |
} | |
} | |
// apply the LSSerializer filter | |
if (!applyFilter(node, NodeFilter.SHOW_CDATA_SECTION)) { | |
return; | |
} | |
// splits the cdata-section | |
if (fLexicalHandler != null) { | |
fLexicalHandler.startCDATA(); | |
} | |
dispatachChars(node); | |
if (fLexicalHandler != null) { | |
fLexicalHandler.endCDATA(); | |
} | |
} else { | |
dispatachChars(node); | |
} | |
} | |
/** | |
* Serializes an Text Node. | |
* | |
* @param node The Text Node to serialize | |
*/ | |
protected void serializeText(Text node) throws SAXException { | |
if (fNextIsRaw) { | |
fNextIsRaw = false; | |
fSerializer.processingInstruction( | |
javax.xml.transform.Result.PI_DISABLE_OUTPUT_ESCAPING, | |
""); | |
dispatachChars(node); | |
fSerializer.processingInstruction( | |
javax.xml.transform.Result.PI_ENABLE_OUTPUT_ESCAPING, | |
""); | |
} else { | |
// keep track of dispatch or not to avoid duplicaiton of filter code | |
boolean bDispatch = false; | |
// well-formed=true | |
if ((fFeatures & WELLFORMED) != 0) { | |
isTextWellFormed(node); | |
} | |
// if the node is whitespace | |
// Determine the Attr's type. | |
boolean isElementContentWhitespace = false; | |
if (fIsLevel3DOM) { | |
isElementContentWhitespace = | |
node.isElementContentWhitespace(); | |
} | |
if (isElementContentWhitespace) { | |
// element-content-whitespace=true | |
if ((fFeatures & ELEM_CONTENT_WHITESPACE) != 0) { | |
bDispatch = true; | |
} | |
} else { | |
bDispatch = true; | |
} | |
// apply the LSSerializer filter | |
if (!applyFilter(node, NodeFilter.SHOW_TEXT)) { | |
return; | |
} | |
if (bDispatch) { | |
dispatachChars(node); | |
} | |
} | |
} | |
/** | |
* Serializes an EntityReference Node. | |
* | |
* @param node The EntityReference Node to serialize | |
* @param bStart Inicates if called from start or endNode | |
*/ | |
protected void serializeEntityReference( | |
EntityReference node, | |
boolean bStart) | |
throws SAXException { | |
if (bStart) { | |
EntityReference eref = node; | |
// entities=true | |
if ((fFeatures & ENTITIES) != 0) { | |
// perform well-formedness and other checking only if | |
// entities = true | |
// well-formed=true | |
if ((fFeatures & WELLFORMED) != 0) { | |
isEntityReferneceWellFormed(node); | |
} | |
// check "unbound-prefix-in-entity-reference" [fatal] | |
// Raised if the configuration parameter "namespaces" is set to true | |
if ((fFeatures & NAMESPACES) != 0) { | |
checkUnboundPrefixInEntRef(node); | |
} | |
// The filter should not apply in this case, since the | |
// EntityReference is not being expanded. | |
// should we pass entity reference nodes to the filter??? | |
} | |
if (fLexicalHandler != null) { | |
// startEntity outputs only Text but not Element, Attr, Comment | |
// and PI child nodes. It does so by setting the m_inEntityRef | |
// in ToStream and using this to decide if a node is to be | |
// serialized or not. | |
fLexicalHandler.startEntity(eref.getNodeName()); | |
} | |
} else { | |
EntityReference eref = node; | |
// entities=true or false, | |
if (fLexicalHandler != null) { | |
fLexicalHandler.endEntity(eref.getNodeName()); | |
} | |
} | |
} | |
// *********************************************************************** | |
// Methods to check well-formedness | |
// *********************************************************************** | |
/** | |
* Taken from org.apache.xerces.dom.CoreDocumentImpl | |
* | |
* Check the string against XML's definition of acceptable names for | |
* elements and attributes and so on using the XMLCharacterProperties | |
* utility class | |
*/ | |
protected boolean isXMLName(String s, boolean xml11Version) { | |
if (s == null) { | |
return false; | |
} | |
if (!xml11Version) | |
return XMLChar.isValidName(s); | |
else | |
return XML11Char.isXML11ValidName(s); | |
} | |
/** | |
* Taken from org.apache.xerces.dom.CoreDocumentImpl | |
* | |
* Checks if the given qualified name is legal with respect | |
* to the version of XML to which this document must conform. | |
* | |
* @param prefix prefix of qualified name | |
* @param local local part of qualified name | |
*/ | |
protected boolean isValidQName( | |
String prefix, | |
String local, | |
boolean xml11Version) { | |
// check that both prefix and local part match NCName | |
if (local == null) | |
return false; | |
boolean validNCName = false; | |
if (!xml11Version) { | |
validNCName = | |
(prefix == null || XMLChar.isValidNCName(prefix)) | |
&& XMLChar.isValidNCName(local); | |
} else { | |
validNCName = | |
(prefix == null || XML11Char.isXML11ValidNCName(prefix)) | |
&& XML11Char.isXML11ValidNCName(local); | |
} | |
return validNCName; | |
} | |
/** | |
* Checks if a XML character is well-formed | |
* | |
* @param characters A String of characters to be checked for Well-Formedness | |
* @param refInvalidChar A reference to the character to be returned that was determined invalid. | |
*/ | |
protected boolean isWFXMLChar(String chardata, Character refInvalidChar) { | |
if (chardata == null || (chardata.length() == 0)) { | |
return true; | |
} | |
char[] dataarray = chardata.toCharArray(); | |
int datalength = dataarray.length; | |
// version of the document is XML 1.1 | |
if (fIsXMLVersion11) { | |
//we need to check all characters as per production rules of XML11 | |
int i = 0; | |
while (i < datalength) { | |
if (XML11Char.isXML11Invalid(dataarray[i++])) { | |
// check if this is a supplemental character | |
char ch = dataarray[i - 1]; | |
if (XMLChar.isHighSurrogate(ch) && i < datalength) { | |
char ch2 = dataarray[i++]; | |
if (XMLChar.isLowSurrogate(ch2) | |
&& XMLChar.isSupplemental( | |
XMLChar.supplemental(ch, ch2))) { | |
continue; | |
} | |
} | |
// Reference to invalid character which is returned | |
refInvalidChar = new Character(ch); | |
return false; | |
} | |
} | |
} // version of the document is XML 1.0 | |
else { | |
// we need to check all characters as per production rules of XML 1.0 | |
int i = 0; | |
while (i < datalength) { | |
if (XMLChar.isInvalid(dataarray[i++])) { | |
// check if this is a supplemental character | |
char ch = dataarray[i - 1]; | |
if (XMLChar.isHighSurrogate(ch) && i < datalength) { | |
char ch2 = dataarray[i++]; | |
if (XMLChar.isLowSurrogate(ch2) | |
&& XMLChar.isSupplemental( | |
XMLChar.supplemental(ch, ch2))) { | |
continue; | |
} | |
} | |
// Reference to invalid character which is returned | |
refInvalidChar = new Character(ch); | |
return false; | |
} | |
} | |
} // end-else fDocument.isXMLVersion() | |
return true; | |
} // isXMLCharWF | |
/** | |
* Checks if a XML character is well-formed. If there is a problem with | |
* the character a non-null Character is returned else null is returned. | |
* | |
* @param characters A String of characters to be checked for Well-Formedness | |
* @return Character A reference to the character to be returned that was determined invalid. | |
*/ | |
protected Character isWFXMLChar(String chardata) { | |
Character refInvalidChar; | |
if (chardata == null || (chardata.length() == 0)) { | |
return null; | |
} | |
char[] dataarray = chardata.toCharArray(); | |
int datalength = dataarray.length; | |
// version of the document is XML 1.1 | |
if (fIsXMLVersion11) { | |
//we need to check all characters as per production rules of XML11 | |
int i = 0; | |
while (i < datalength) { | |
if (XML11Char.isXML11Invalid(dataarray[i++])) { | |
// check if this is a supplemental character | |
char ch = dataarray[i - 1]; | |
if (XMLChar.isHighSurrogate(ch) && i < datalength) { | |
char ch2 = dataarray[i++]; | |
if (XMLChar.isLowSurrogate(ch2) | |
&& XMLChar.isSupplemental( | |
XMLChar.supplemental(ch, ch2))) { | |
continue; | |
} | |
} | |
// Reference to invalid character which is returned | |
refInvalidChar = new Character(ch); | |
return refInvalidChar; | |
} | |
} | |
} // version of the document is XML 1.0 | |
else { | |
// we need to check all characters as per production rules of XML 1.0 | |
int i = 0; | |
while (i < datalength) { | |
if (XMLChar.isInvalid(dataarray[i++])) { | |
// check if this is a supplemental character | |
char ch = dataarray[i - 1]; | |
if (XMLChar.isHighSurrogate(ch) && i < datalength) { | |
char ch2 = dataarray[i++]; | |
if (XMLChar.isLowSurrogate(ch2) | |
&& XMLChar.isSupplemental( | |
XMLChar.supplemental(ch, ch2))) { | |
continue; | |
} | |
} | |
// Reference to invalid character which is returned | |
refInvalidChar = new Character(ch); | |
return refInvalidChar; | |
} | |
} | |
} // end-else fDocument.isXMLVersion() | |
return null; | |
} // isXMLCharWF | |
/** | |
* Checks if a comment node is well-formed | |
* | |
* @param data The contents of the comment node | |
* @return a boolean indiacating if the comment is well-formed or not. | |
*/ | |
protected void isCommentWellFormed(String data) { | |
if (data == null || (data.length() == 0)) { | |
return; | |
} | |
char[] dataarray = data.toCharArray(); | |
int datalength = dataarray.length; | |
// version of the document is XML 1.1 | |
if (fIsXMLVersion11) { | |
// we need to check all chracters as per production rules of XML11 | |
int i = 0; | |
while (i < datalength) { | |
char c = dataarray[i++]; | |
if (XML11Char.isXML11Invalid(c)) { | |
// check if this is a supplemental character | |
if (XMLChar.isHighSurrogate(c) && i < datalength) { | |
char c2 = dataarray[i++]; | |
if (XMLChar.isLowSurrogate(c2) | |
&& XMLChar.isSupplemental( | |
XMLChar.supplemental(c, c2))) { | |
continue; | |
} | |
} | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_WF_INVALID_CHARACTER_IN_COMMENT, | |
new Object[] { new Character(c)}); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_FATAL_ERROR, | |
msg, | |
MsgKey.ER_WF_INVALID_CHARACTER, | |
null, | |
null, | |
null)); | |
} | |
} else if (c == '-' && i < datalength && dataarray[i] == '-') { | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_WF_DASH_IN_COMMENT, | |
null); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_FATAL_ERROR, | |
msg, | |
MsgKey.ER_WF_INVALID_CHARACTER, | |
null, | |
null, | |
null)); | |
} | |
} | |
} | |
} // version of the document is XML 1.0 | |
else { | |
// we need to check all chracters as per production rules of XML 1.0 | |
int i = 0; | |
while (i < datalength) { | |
char c = dataarray[i++]; | |
if (XMLChar.isInvalid(c)) { | |
// check if this is a supplemental character | |
if (XMLChar.isHighSurrogate(c) && i < datalength) { | |
char c2 = dataarray[i++]; | |
if (XMLChar.isLowSurrogate(c2) | |
&& XMLChar.isSupplemental( | |
XMLChar.supplemental(c, c2))) { | |
continue; | |
} | |
} | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_WF_INVALID_CHARACTER_IN_COMMENT, | |
new Object[] { new Character(c)}); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_FATAL_ERROR, | |
msg, | |
MsgKey.ER_WF_INVALID_CHARACTER, | |
null, | |
null, | |
null)); | |
} | |
} else if (c == '-' && i < datalength && dataarray[i] == '-') { | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_WF_DASH_IN_COMMENT, | |
null); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_FATAL_ERROR, | |
msg, | |
MsgKey.ER_WF_INVALID_CHARACTER, | |
null, | |
null, | |
null)); | |
} | |
} | |
} | |
} | |
return; | |
} | |
/** | |
* Checks if an element node is well-formed, by checking its Name for well-formedness. | |
* | |
* @param data The contents of the comment node | |
* @return a boolean indiacating if the comment is well-formed or not. | |
*/ | |
protected void isElementWellFormed(Node node) { | |
boolean isNameWF = false; | |
if ((fFeatures & NAMESPACES) != 0) { | |
isNameWF = | |
isValidQName( | |
node.getPrefix(), | |
node.getLocalName(), | |
fIsXMLVersion11); | |
} else { | |
isNameWF = isXMLName(node.getNodeName(), fIsXMLVersion11); | |
} | |
if (!isNameWF) { | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_WF_INVALID_CHARACTER_IN_NODE_NAME, | |
new Object[] { "Element", node.getNodeName()}); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_FATAL_ERROR, | |
msg, | |
MsgKey.ER_WF_INVALID_CHARACTER_IN_NODE_NAME, | |
null, | |
null, | |
null)); | |
} | |
} | |
} | |
/** | |
* Checks if an attr node is well-formed, by checking it's Name and value | |
* for well-formedness. | |
* | |
* @param data The contents of the comment node | |
* @return a boolean indiacating if the comment is well-formed or not. | |
*/ | |
protected void isAttributeWellFormed(Node node) { | |
boolean isNameWF = false; | |
if ((fFeatures & NAMESPACES) != 0) { | |
isNameWF = | |
isValidQName( | |
node.getPrefix(), | |
node.getLocalName(), | |
fIsXMLVersion11); | |
} else { | |
isNameWF = isXMLName(node.getNodeName(), fIsXMLVersion11); | |
} | |
if (!isNameWF) { | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_WF_INVALID_CHARACTER_IN_NODE_NAME, | |
new Object[] { "Attr", node.getNodeName()}); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_FATAL_ERROR, | |
msg, | |
MsgKey.ER_WF_INVALID_CHARACTER_IN_NODE_NAME, | |
null, | |
null, | |
null)); | |
} | |
} | |
// Check the Attr's node value | |
// WFC: No < in Attribute Values | |
String value = node.getNodeValue(); | |
if (value.indexOf('<') >= 0) { | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_WF_LT_IN_ATTVAL, | |
new Object[] { | |
((Attr) node).getOwnerElement().getNodeName(), | |
node.getNodeName()}); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_FATAL_ERROR, | |
msg, | |
MsgKey.ER_WF_LT_IN_ATTVAL, | |
null, | |
null, | |
null)); | |
} | |
} | |
// we need to loop through the children of attr nodes and check their values for | |
// well-formedness | |
NodeList children = node.getChildNodes(); | |
for (int i = 0; i < children.getLength(); i++) { | |
Node child = children.item(i); | |
// An attribute node with no text or entity ref child for example | |
// doc.createAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:ns"); | |
// followes by | |
// element.setAttributeNodeNS(attribute); | |
// can potentially lead to this situation. If the attribute | |
// was a prefix Namespace attribute declaration then then DOM Core | |
// should have some exception defined for this. | |
if (child == null) { | |
// we should probably report an error | |
continue; | |
} | |
switch (child.getNodeType()) { | |
case Node.TEXT_NODE : | |
isTextWellFormed((Text) child); | |
break; | |
case Node.ENTITY_REFERENCE_NODE : | |
isEntityReferneceWellFormed((EntityReference) child); | |
break; | |
default : | |
} | |
} | |
// TODO: | |
// WFC: Check if the attribute prefix is bound to | |
// http://www.w3.org/2000/xmlns/ | |
// WFC: Unique Att Spec | |
// Perhaps pass a seen boolean value to this method. serializeAttList will determine | |
// if the attr was seen before. | |
} | |
/** | |
* Checks if a PI node is well-formed, by checking it's Name and data | |
* for well-formedness. | |
* | |
* @param data The contents of the comment node | |
*/ | |
protected void isPIWellFormed(ProcessingInstruction node) { | |
// Is the PI Target a valid XML name | |
if (!isXMLName(node.getNodeName(), fIsXMLVersion11)) { | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_WF_INVALID_CHARACTER_IN_NODE_NAME, | |
new Object[] { "ProcessingInstruction", node.getTarget()}); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_FATAL_ERROR, | |
msg, | |
MsgKey.ER_WF_INVALID_CHARACTER_IN_NODE_NAME, | |
null, | |
null, | |
null)); | |
} | |
} | |
// Does the PI Data carry valid XML characters | |
// REVISIT: Should we check if the PI DATA contains a ?> ??? | |
Character invalidChar = isWFXMLChar(node.getData()); | |
if (invalidChar != null) { | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_WF_INVALID_CHARACTER_IN_PI, | |
new Object[] { Integer.toHexString(Character.getNumericValue(invalidChar.charValue())) }); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_FATAL_ERROR, | |
msg, | |
MsgKey.ER_WF_INVALID_CHARACTER, | |
null, | |
null, | |
null)); | |
} | |
} | |
} | |
/** | |
* Checks if an CDATASection node is well-formed, by checking it's data | |
* for well-formedness. Note that the presence of a CDATA termination mark | |
* in the contents of a CDATASection is handled by the parameter | |
* spli-cdata-sections | |
* | |
* @param data The contents of the comment node | |
*/ | |
protected void isCDATASectionWellFormed(CDATASection node) { | |
// Does the data valid XML character data | |
Character invalidChar = isWFXMLChar(node.getData()); | |
//if (!isWFXMLChar(node.getData(), invalidChar)) { | |
if (invalidChar != null) { | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_WF_INVALID_CHARACTER_IN_CDATA, | |
new Object[] { Integer.toHexString(Character.getNumericValue(invalidChar.charValue())) }); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_FATAL_ERROR, | |
msg, | |
MsgKey.ER_WF_INVALID_CHARACTER, | |
null, | |
null, | |
null)); | |
} | |
} | |
} | |
/** | |
* Checks if an Text node is well-formed, by checking if it contains invalid | |
* XML characters. | |
* | |
* @param data The contents of the comment node | |
*/ | |
protected void isTextWellFormed(Text node) { | |
// Does the data valid XML character data | |
Character invalidChar = isWFXMLChar(node.getData()); | |
if (invalidChar != null) { | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_WF_INVALID_CHARACTER_IN_TEXT, | |
new Object[] { Integer.toHexString(Character.getNumericValue(invalidChar.charValue())) }); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_FATAL_ERROR, | |
msg, | |
MsgKey.ER_WF_INVALID_CHARACTER, | |
null, | |
null, | |
null)); | |
} | |
} | |
} | |
/** | |
* Checks if an EntityRefernece node is well-formed, by checking it's node name. Then depending | |
* on whether it is referenced in Element content or in an Attr Node, checks if the EntityReference | |
* references an unparsed entity or a external entity and if so throws raises the | |
* appropriate well-formedness error. | |
* | |
* @param data The contents of the comment node | |
* @parent The parent of the EntityReference Node | |
*/ | |
protected void isEntityReferneceWellFormed(EntityReference node) { | |
// Is the EntityReference name a valid XML name | |
if (!isXMLName(node.getNodeName(), fIsXMLVersion11)) { | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_WF_INVALID_CHARACTER_IN_NODE_NAME, | |
new Object[] { "EntityReference", node.getNodeName()}); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_FATAL_ERROR, | |
msg, | |
MsgKey.ER_WF_INVALID_CHARACTER_IN_NODE_NAME, | |
null, | |
null, | |
null)); | |
} | |
} | |
// determine the parent node | |
Node parent = node.getParentNode(); | |
// Traverse the declared entities and check if the nodeName and namespaceURI | |
// of the EntityReference matches an Entity. If so, check the if the notationName | |
// is not null, if so, report an error. | |
DocumentType docType = node.getOwnerDocument().getDoctype(); | |
if (docType != null) { | |
NamedNodeMap entities = docType.getEntities(); | |
for (int i = 0; i < entities.getLength(); i++) { | |
Entity ent = (Entity) entities.item(i); | |
String nodeName = | |
node.getNodeName() == null ? "" : node.getNodeName(); | |
String nodeNamespaceURI = | |
node.getNamespaceURI() == null | |
? "" | |
: node.getNamespaceURI(); | |
String entName = | |
ent.getNodeName() == null ? "" : ent.getNodeName(); | |
String entNamespaceURI = | |
ent.getNamespaceURI() == null ? "" : ent.getNamespaceURI(); | |
// If referenced in Element content | |
// WFC: Parsed Entity | |
if (parent.getNodeType() == Node.ELEMENT_NODE) { | |
if (entNamespaceURI.equals(nodeNamespaceURI) | |
&& entName.equals(nodeName)) { | |
if (ent.getNotationName() != null) { | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_WF_REF_TO_UNPARSED_ENT, | |
new Object[] { node.getNodeName()}); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_FATAL_ERROR, | |
msg, | |
MsgKey.ER_WF_REF_TO_UNPARSED_ENT, | |
null, | |
null, | |
null)); | |
} | |
} | |
} | |
} // end if WFC: Parsed Entity | |
// If referenced in an Attr value | |
// WFC: No External Entity References | |
if (parent.getNodeType() == Node.ATTRIBUTE_NODE) { | |
if (entNamespaceURI.equals(nodeNamespaceURI) | |
&& entName.equals(nodeName)) { | |
if (ent.getPublicId() != null | |
|| ent.getSystemId() != null | |
|| ent.getNotationName() != null) { | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_WF_REF_TO_EXTERNAL_ENT, | |
new Object[] { node.getNodeName()}); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_FATAL_ERROR, | |
msg, | |
MsgKey.ER_WF_REF_TO_EXTERNAL_ENT, | |
null, | |
null, | |
null)); | |
} | |
} | |
} | |
} //end if WFC: No External Entity References | |
} | |
} | |
} // isEntityReferneceWellFormed | |
/** | |
* If the configuration parameter "namespaces" is set to true, this methods | |
* checks if an entity whose replacement text contains unbound namespace | |
* prefixes is referenced in a location where there are no bindings for | |
* the namespace prefixes and if so raises a LSException with the error-type | |
* "unbound-prefix-in-entity-reference" | |
* | |
* @param Node, The EntityReference nodes whose children are to be checked | |
*/ | |
protected void checkUnboundPrefixInEntRef(Node node) { | |
Node child, next; | |
for (child = node.getFirstChild(); child != null; child = next) { | |
next = child.getNextSibling(); | |
if (child.getNodeType() == Node.ELEMENT_NODE) { | |
//If a NamespaceURI is not declared for the current | |
//node's prefix, raise a fatal error. | |
String prefix = child.getPrefix(); | |
if (prefix != null | |
&& fNSBinder.getURI(prefix) == null) { | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_ELEM_UNBOUND_PREFIX_IN_ENTREF, | |
new Object[] { | |
node.getNodeName(), | |
child.getNodeName(), | |
prefix }); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_FATAL_ERROR, | |
msg, | |
MsgKey.ER_ELEM_UNBOUND_PREFIX_IN_ENTREF, | |
null, | |
null, | |
null)); | |
} | |
} | |
NamedNodeMap attrs = child.getAttributes(); | |
for (int i = 0; i < attrs.getLength(); i++) { | |
String attrPrefix = attrs.item(i).getPrefix(); | |
if (attrPrefix != null | |
&& fNSBinder.getURI(attrPrefix) == null) { | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_ATTR_UNBOUND_PREFIX_IN_ENTREF, | |
new Object[] { | |
node.getNodeName(), | |
child.getNodeName(), | |
attrs.item(i)}); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_FATAL_ERROR, | |
msg, | |
MsgKey.ER_ATTR_UNBOUND_PREFIX_IN_ENTREF, | |
null, | |
null, | |
null)); | |
} | |
} | |
} | |
} | |
if (child.hasChildNodes()) { | |
checkUnboundPrefixInEntRef(child); | |
} | |
} | |
} | |
// *********************************************************************** | |
// Namespace normalization | |
// *********************************************************************** | |
/** | |
* Records local namespace declarations, to be used for normalization later | |
* | |
* @param Node, The element node, whose namespace declarations are to be recorded | |
*/ | |
protected void recordLocalNSDecl(Node node) { | |
NamedNodeMap atts = ((Element) node).getAttributes(); | |
int length = atts.getLength(); | |
for (int i = 0; i < length; i++) { | |
Node attr = atts.item(i); | |
String localName = attr.getLocalName(); | |
String attrPrefix = attr.getPrefix(); | |
String attrValue = attr.getNodeValue(); | |
String attrNS = attr.getNamespaceURI(); | |
localName = | |
localName == null | |
|| XMLNS_PREFIX.equals(localName) ? "" : localName; | |
attrPrefix = attrPrefix == null ? "" : attrPrefix; | |
attrValue = attrValue == null ? "" : attrValue; | |
attrNS = attrNS == null ? "" : attrNS; | |
// check if attribute is a namespace decl | |
if (XMLNS_URI.equals(attrNS)) { | |
// No prefix may be bound to http://www.w3.org/2000/xmlns/. | |
if (XMLNS_URI.equals(attrValue)) { | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_NS_PREFIX_CANNOT_BE_BOUND, | |
new Object[] { attrPrefix, XMLNS_URI }); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_ERROR, | |
msg, | |
MsgKey.ER_NS_PREFIX_CANNOT_BE_BOUND, | |
null, | |
null, | |
null)); | |
} | |
} else { | |
// store the namespace-declaration | |
if (XMLNS_PREFIX.equals(attrPrefix) ) { | |
// record valid decl | |
if (attrValue.length() != 0) { | |
fNSBinder.declarePrefix(localName, attrValue); | |
} else { | |
// Error; xmlns:prefix="" | |
} | |
} else { // xmlns | |
// empty prefix is always bound ("" or some string) | |
fNSBinder.declarePrefix("", attrValue); | |
} | |
} | |
} | |
} | |
} | |
/** | |
* Fixes an element's namespace | |
* | |
* @param Node, The element node, whose namespace is to be fixed | |
*/ | |
protected void fixupElementNS(Node node) throws SAXException { | |
String namespaceURI = ((Element) node).getNamespaceURI(); | |
String prefix = ((Element) node).getPrefix(); | |
String localName = ((Element) node).getLocalName(); | |
if (namespaceURI != null) { | |
//if ( Element's prefix/namespace pair (or default namespace, | |
// if no prefix) are within the scope of a binding ) | |
prefix = prefix == null ? "" : prefix; | |
String inScopeNamespaceURI = fNSBinder.getURI(prefix); | |
if ((inScopeNamespaceURI != null | |
&& inScopeNamespaceURI.equals(namespaceURI))) { | |
// do nothing, declaration in scope is inherited | |
} else { | |
// Create a local namespace declaration attr for this namespace, | |
// with Element's current prefix (or a default namespace, if | |
// no prefix). If there's a conflicting local declaration | |
// already present, change its value to use this namespace. | |
// Add the xmlns declaration attribute | |
//fNSBinder.pushNamespace(prefix, namespaceURI, fElementDepth); | |
if ((fFeatures & NAMESPACEDECLS) != 0) { | |
if ("".equals(prefix) || "".equals(namespaceURI)) { | |
((Element)node).setAttributeNS(XMLNS_URI, XMLNS_PREFIX, namespaceURI); | |
} else { | |
((Element)node).setAttributeNS(XMLNS_URI, XMLNS_PREFIX + ":" + prefix, namespaceURI); | |
} | |
} | |
fLocalNSBinder.declarePrefix(prefix, namespaceURI); | |
fNSBinder.declarePrefix(prefix, namespaceURI); | |
} | |
} else { | |
// Element has no namespace | |
// DOM Level 1 | |
if (localName == null || "".equals(localName)) { | |
// DOM Level 1 node! | |
String msg = | |
Utils.messages.createMessage( | |
MsgKey.ER_NULL_LOCAL_ELEMENT_NAME, | |
new Object[] { node.getNodeName()}); | |
if (fErrorHandler != null) { | |
fErrorHandler.handleError( | |
new DOMErrorImpl( | |
DOMError.SEVERITY_ERROR, | |
msg, | |
MsgKey.ER_NULL_LOCAL_ELEMENT_NAME, | |
null, | |
null, | |
null)); | |
} | |
} else { | |
namespaceURI = fNSBinder.getURI(""); | |
if (namespaceURI !=null && namespaceURI.length() > 0) { | |
((Element)node).setAttributeNS(XMLNS_URI, XMLNS_PREFIX, ""); | |
fLocalNSBinder.declarePrefix("", ""); | |
fNSBinder.declarePrefix("", ""); | |
} | |
} | |
} | |
} | |
/** | |
* This table is a quick lookup of a property key (String) to the integer that | |
* is the bit to flip in the fFeatures field, so the integers should have | |
* values 1,2,4,8,16... | |
* | |
*/ | |
private static final Hashtable s_propKeys = new Hashtable(); | |
static { | |
// Initialize the mappings of property keys to bit values (Integer objects) | |
// or mappings to a String object "", which indicates we are interested | |
// in the property, but it does not have a simple bit value to flip | |
// cdata-sections | |
int i = CDATA; | |
Integer val = new Integer(i); | |
s_propKeys.put( | |
DOMConstants.S_DOM3_PROPERTIES_NS + DOMConstants.DOM_CDATA_SECTIONS, | |
val); | |
// comments | |
int i1 = COMMENTS; | |
val = new Integer(i1); | |
s_propKeys.put( | |
DOMConstants.S_DOM3_PROPERTIES_NS + DOMConstants.DOM_COMMENTS, | |
val); | |
// element-content-whitespace | |
int i2 = ELEM_CONTENT_WHITESPACE; | |
val = new Integer(i2); | |
s_propKeys.put( | |
DOMConstants.S_DOM3_PROPERTIES_NS | |
+ DOMConstants.DOM_ELEMENT_CONTENT_WHITESPACE, | |
val); | |
int i3 = ENTITIES; | |
// entities | |
val = new Integer(i3); | |
s_propKeys.put( | |
DOMConstants.S_DOM3_PROPERTIES_NS + DOMConstants.DOM_ENTITIES, | |
val); | |
// namespaces | |
int i4 = NAMESPACES; | |
val = new Integer(i4); | |
s_propKeys.put( | |
DOMConstants.S_DOM3_PROPERTIES_NS + DOMConstants.DOM_NAMESPACES, | |
val); | |
// namespace-declarations | |
int i5 = NAMESPACEDECLS; | |
val = new Integer(i5); | |
s_propKeys.put( | |
DOMConstants.S_DOM3_PROPERTIES_NS | |
+ DOMConstants.DOM_NAMESPACE_DECLARATIONS, | |
val); | |
// split-cdata-sections | |
int i6 = SPLITCDATA; | |
val = new Integer(i6); | |
s_propKeys.put( | |
DOMConstants.S_DOM3_PROPERTIES_NS + DOMConstants.DOM_SPLIT_CDATA, | |
val); | |
// discard-default-content | |
int i7 = WELLFORMED; | |
val = new Integer(i7); | |
s_propKeys.put( | |
DOMConstants.S_DOM3_PROPERTIES_NS + DOMConstants.DOM_WELLFORMED, | |
val); | |
// discard-default-content | |
int i8 = DISCARDDEFAULT; | |
val = new Integer(i8); | |
s_propKeys.put( | |
DOMConstants.S_DOM3_PROPERTIES_NS | |
+ DOMConstants.DOM_DISCARD_DEFAULT_CONTENT, | |
val); | |
// We are interested in these properties, but they don't have a simple | |
// bit value to deal with. | |
s_propKeys.put( | |
DOMConstants.S_DOM3_PROPERTIES_NS | |
+ DOMConstants.DOM_FORMAT_PRETTY_PRINT, | |
""); | |
s_propKeys.put(DOMConstants.S_XSL_OUTPUT_OMIT_XML_DECL, ""); | |
s_propKeys.put( | |
DOMConstants.S_XERCES_PROPERTIES_NS + DOMConstants.S_XML_VERSION, | |
""); | |
s_propKeys.put(DOMConstants.S_XSL_OUTPUT_ENCODING, ""); | |
s_propKeys.put(DOMConstants.S_XERCES_PROPERTIES_NS + DOMConstants.DOM_ENTITIES, ""); | |
} | |
/** | |
* Initializes fFeatures based on the DOMConfiguration Parameters set. | |
* | |
* @param properties DOMConfiguraiton properties that were set and which are | |
* to be used while serializing the DOM. | |
*/ | |
protected void initProperties(Properties properties) { | |
for (Enumeration keys = properties.keys(); keys.hasMoreElements();) { | |
final String key = (String) keys.nextElement(); | |
// caonical-form | |
// Other features will be enabled or disabled when this is set to true or false. | |
// error-handler; set via the constructor | |
// infoset | |
// Other features will be enabled or disabled when this is set to true | |
// A quick lookup for the given set of properties (cdata-sections ...) | |
final Object iobj = s_propKeys.get(key); | |
if (iobj != null) { | |
if (iobj instanceof Integer) { | |
// Dealing with a property that has a simple bit value that | |
// we need to set | |
// cdata-sections | |
// comments | |
// element-content-whitespace | |
// entities | |
// namespaces | |
// namespace-declarations | |
// split-cdata-sections | |
// well-formed | |
// discard-default-content | |
final int BITFLAG = ((Integer) iobj).intValue(); | |
if ((properties.getProperty(key).endsWith("yes"))) { | |
fFeatures = fFeatures | BITFLAG; | |
} else { | |
fFeatures = fFeatures & ~BITFLAG; | |
} | |
} else { | |
// We are interested in the property, but it is not | |
// a simple bit that we need to set. | |
if ((DOMConstants.S_DOM3_PROPERTIES_NS | |
+ DOMConstants.DOM_FORMAT_PRETTY_PRINT) | |
.equals(key)) { | |
// format-pretty-print; set internally on the serializers via xsl:output properties in LSSerializer | |
if ((properties.getProperty(key).endsWith("yes"))) { | |
fSerializer.setIndent(true); | |
fSerializer.setIndentAmount(3); | |
} else { | |
fSerializer.setIndent(false); | |
} | |
} else if ( | |
(DOMConstants.S_XSL_OUTPUT_OMIT_XML_DECL).equals( | |
key)) { | |
// omit-xml-declaration; set internally on the serializers via xsl:output properties in LSSerializer | |
if ((properties.getProperty(key).endsWith("yes"))) { | |
fSerializer.setOmitXMLDeclaration(true); | |
} else { | |
fSerializer.setOmitXMLDeclaration(false); | |
} | |
} else if ( | |
( | |
DOMConstants.S_XERCES_PROPERTIES_NS | |
+ DOMConstants.S_XML_VERSION).equals( | |
key)) { | |
// Retreive the value of the XML Version attribute via the xml-version | |
String version = properties.getProperty(key); | |
if ("1.1".equals(version)) { | |
fIsXMLVersion11 = true; | |
fSerializer.setVersion(version); | |
} else { | |
fSerializer.setVersion("1.0"); | |
} | |
} else if ( | |
(DOMConstants.S_XSL_OUTPUT_ENCODING).equals(key)) { | |
// Retreive the value of the XML Encoding attribute | |
String encoding = properties.getProperty(key); | |
if (encoding != null) { | |
fSerializer.setEncoding(encoding); | |
} | |
} else if ((DOMConstants.S_XERCES_PROPERTIES_NS | |
+ DOMConstants.DOM_ENTITIES).equals(key)) { | |
// Preserve entity references in the document | |
if ((properties.getProperty(key).endsWith("yes"))) { | |
fSerializer.setDTDEntityExpansion(false); | |
} | |
else { | |
fSerializer.setDTDEntityExpansion(true); | |
} | |
} else { | |
// We shouldn't get here, ever, now what? | |
} | |
} | |
} | |
} | |
// Set the newLine character to use | |
if (fNewLine != null) { | |
fSerializer.setOutputProperty(OutputPropertiesFactory.S_KEY_LINE_SEPARATOR, fNewLine); | |
} | |
} | |
} //TreeWalker |