/* Woodstox XML processor
 *
 * Copyright (c) 2004- Tatu Saloranta, tatu.saloranta@iki.fi
 *
 * Licensed under the License specified in file LICENSE, included with
 * the source code.
 * You may not use this file except in compliance with the License.
 *
 * 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 com.ctc.wstx.dtd;

import java.text.MessageFormat;
import java.util.*;

import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamException;

import org.codehaus.stax2.validation.*;

import com.ctc.wstx.api.WstxInputProperties;
import com.ctc.wstx.ent.EntityDecl;
import com.ctc.wstx.sr.NsDefaultProvider;
import com.ctc.wstx.sr.InputElementStack;
import com.ctc.wstx.util.DataUtil;
import com.ctc.wstx.util.ElementIdMap;
import com.ctc.wstx.util.ExceptionUtil;
import com.ctc.wstx.util.PrefixedName;

Shared abstract base class for Woodstox implementations of XMLValidator for DTD validation. Since there are 2 sub-types -- full actual DTD validator, and a dummy one that only adds type information and default values, with no actual validation -- common functionality was refactored into this base class.
/** * Shared abstract base class for Woodstox implementations * of {@link XMLValidator} for DTD validation. * Since there are 2 sub-types -- full actual DTD validator, and a dummy * one that only adds type information and default values, with no actual * validation -- common functionality was refactored into this base * class. */
public abstract class DTDValidatorBase extends XMLValidator implements NsDefaultProvider // for namespace attr defaults { protected final static HashMap<PrefixedName,DTDAttribute> NO_ATTRS = new HashMap<PrefixedName,DTDAttribute>(); /* ///////////////////////////////////////////////////// // Constants ///////////////////////////////////////////////////// */
Estimated maximum depth of typical documents; used to allocate the array for element stack
/** * Estimated maximum depth of typical documents; used to allocate * the array for element stack */
final static int DEFAULT_STACK_SIZE = 16;
Estimated maximum number of attributes for a single element
/** * Estimated maximum number of attributes for a single element */
final static int EXP_MAX_ATTRS = 16;
Let's actually just reuse a local Map...
/** * Let's actually just reuse a local Map... */
protected final static HashMap<String,EntityDecl> EMPTY_MAP = new HashMap<String,EntityDecl>(); /* /////////////////////////////////////// // Configuration /////////////////////////////////////// */
Flag that indicates whether any of the elements declared has default attribute values for namespace declaration pseudo-attributes.
/** * Flag that indicates whether any of the elements declared has default * attribute values for namespace declaration pseudo-attributes. */
final boolean mHasNsDefaults;
DTD schema (DTDSubsetImpl) object that created this validator instance.
/** * DTD schema ({@link DTDSubsetImpl}) object that created this validator * instance. */
final DTDSubset mSchema;
Validation context (owner) for this validator. Needed for adding default attribute values, for example.
/** * Validation context (owner) for this validator. Needed for adding * default attribute values, for example. */
final ValidationContext mContext;
Map that contains element specifications from DTD; null if no DOCTYPE declaration found.
/** * Map that contains element specifications from DTD; null if no * DOCTYPE declaration found. */
final Map<PrefixedName,DTDElement> mElemSpecs;
General entities defined in DTD subsets; needed for validating ENTITY/ENTITIES attributes.
/** * General entities defined in DTD subsets; needed for validating * ENTITY/ENTITIES attributes. */
final Map<String,EntityDecl> mGeneralEntities;
Flag that indicates whether parser wants the attribute values to be normalized (according to XML specs) or not (which may be more efficient, although not compliant with the specs)
/** * Flag that indicates whether parser wants the attribute values * to be normalized (according to XML specs) or not (which may be * more efficient, although not compliant with the specs) */
protected boolean mNormAttrs; /* /////////////////////////////////////////// // Element def/spec/validator stack, state /////////////////////////////////////////// */
This is the element that is currently being validated; valid during validateElementStart, validateAttribute, validateElementAndAttributes calls.
/** * This is the element that is currently being validated; valid * during * <code>validateElementStart</code>, * <code>validateAttribute</code>, * <code>validateElementAndAttributes</code> calls. */
protected DTDElement mCurrElem = null;
Stack of element definitions matching the current active element stack. Instances are elements definitions read from DTD.
/** * Stack of element definitions matching the current active element stack. * Instances are elements definitions read from DTD. */
protected DTDElement[] mElems = null;
Number of elements in mElems.
/** * Number of elements in {@link #mElems}. */
protected int mElemCount = 0;
Attribute definitions for attributes the current element may have
/** * Attribute definitions for attributes the current element may have */
protected HashMap<PrefixedName,DTDAttribute> mCurrAttrDefs = null;
List of attribute declarations/specifications, one for each attribute of the current element, for which there is a matching value (either explicitly defined, or assigned via defaulting).
/** * List of attribute declarations/specifications, one for each * attribute of the current element, for which there is a matching * value (either explicitly defined, or assigned via defaulting). */
protected DTDAttribute[] mAttrSpecs = new DTDAttribute[EXP_MAX_ATTRS];
Number of attribute specification Objects in mAttrSpecs; needed to store in case type information is requested later on.
/** * Number of attribute specification Objects in * {@link #mAttrSpecs}; needed to store in case type information * is requested later on. */
protected int mAttrCount = 0;
Index of the attribute of type ID, within current element's attribute list. Track of this is kept separate from other attribute since id attributes often need to be used for resolving cross-references.
/** * Index of the attribute of type ID, within current element's * attribute list. Track of this is kept separate from other * attribute since id attributes often need to be used for resolving * cross-references. */
protected int mIdAttrIndex = -1; /* /////////////////////////////////////// // Temporary helper objects /////////////////////////////////////// */ protected final transient PrefixedName mTmpKey = new PrefixedName(null, null);
Temporary buffer attribute instances can share for validation purposes
/** * Temporary buffer attribute instances can share for validation * purposes */
char[] mTmpAttrValueBuffer = null; /* /////////////////////////////////////// // Life-cycle /////////////////////////////////////// */ public DTDValidatorBase(DTDSubset schema, ValidationContext ctxt, boolean hasNsDefaults, Map<PrefixedName,DTDElement> elemSpecs, Map<String,EntityDecl> genEntities) { mSchema = schema; mContext = ctxt; mHasNsDefaults = hasNsDefaults; if (elemSpecs == null || elemSpecs.size() == 0) { mElemSpecs = Collections.emptyMap(); } else { mElemSpecs = elemSpecs; } mGeneralEntities = genEntities; // By default, let's assume attrs are to be normalized (fully xml compliant) mNormAttrs = true; mElems = new DTDElement[DEFAULT_STACK_SIZE]; } /* /////////////////////////////////////// // Configuration /////////////////////////////////////// */
Method that allows enabling/disabling attribute value normalization. In general, readers by default enable normalization (to be fully xml compliant), whereas writers do not (since there is usually little to gain, if anything -- it is even possible value may be written before validation is called in some cases)
/** * Method that allows enabling/disabling attribute value normalization. * In general, readers by default enable normalization (to be fully xml * compliant), * whereas writers do not (since there is usually little to gain, if * anything -- it is even possible value may be written before validation * is called in some cases) */
public void setAttrValueNormalization(boolean state) { mNormAttrs = state; }
Returns:True for validator object that actually do validate content; false for objects that only use DTD type information.
/** * @return True for validator object that actually do validate * content; false for objects that only use DTD type information. */
public abstract boolean reallyValidating(); /* /////////////////////////////////////// // XMLValidator implementation /////////////////////////////////////// */ @Override public final XMLValidationSchema getSchema() { return mSchema; }
Method called to update information about the newly encountered (start) element. At this point namespace information has been resolved, but no DTD validation has been done. Validator is to do these validations, including checking for attribute value (and existence) compatibility.
/** * Method called to update information about the newly encountered (start) * element. At this point namespace information has been resolved, but * no DTD validation has been done. Validator is to do these validations, * including checking for attribute value (and existence) compatibility. */
@Override public abstract void validateElementStart(String localName, String uri, String prefix) throws XMLStreamException; @Override public abstract String validateAttribute(String localName, String uri, String prefix, String value) throws XMLStreamException; @Override public abstract String validateAttribute(String localName, String uri, String prefix, char[] valueChars, int valueStart, int valueEnd) throws XMLStreamException; @Override public abstract int validateElementAndAttributes() throws XMLStreamException;
Returns:Validation state that should be effective for the parent element state
/** * @return Validation state that should be effective for the parent * element state */
@Override public abstract int validateElementEnd(String localName, String uri, String prefix) throws XMLStreamException; @Override public void validateText(String text, boolean lastTextSegment) throws XMLStreamException { /* This method is a NOP, since basic DTD has no mechanism for * validating textual content. */ } @Override public void validateText(char[] cbuf, int textStart, int textEnd, boolean lastTextSegment) throws XMLStreamException { /* This method is a NOP, since basic DTD has no mechanism for * validating textual content. */ } @Override public abstract void validationCompleted(boolean eod) throws XMLStreamException; /* /////////////////////////////////////// // Attribute info access /////////////////////////////////////// */ // // // Access to type info @Override public String getAttributeType(int index) { DTDAttribute attr = mAttrSpecs[index]; return (attr == null) ? WstxInputProperties.UNKNOWN_ATTR_TYPE : attr.getValueTypeString(); }
Method for finding out the index of the attribute (collected using the attribute collector; having DTD-derived info in same order) that is of type ID. DTD explicitly specifies that at most one attribute can have this type for any element.
Returns:Index of the attribute with type ID, in the current element, if one exists: -1 otherwise
/** * Method for finding out the index of the attribute (collected using * the attribute collector; having DTD-derived info in same order) * that is of type ID. DTD explicitly specifies that at most one * attribute can have this type for any element. * * @return Index of the attribute with type ID, in the current * element, if one exists: -1 otherwise */
@Override public int getIdAttrIndex() { // Let's figure out the index only when needed int ix = mIdAttrIndex; if (ix == -2) { ix = -1; if (mCurrElem != null) { DTDAttribute idAttr = mCurrElem.getIdAttribute(); if (idAttr != null) { DTDAttribute[] attrs = mAttrSpecs; for (int i = 0, len = attrs.length; i < len; ++i) { if (attrs[i] == idAttr) { ix = i; break; } } } } mIdAttrIndex = ix; } return ix; }
Method for finding out the index of the attribute (collected using the attribute collector; having DTD-derived info in same order) that is of type NOTATION. DTD explicitly specifies that at most one attribute can have this type for any element.
Returns:Index of the attribute with type NOTATION, in the current element, if one exists: -1 otherwise
/** * Method for finding out the index of the attribute (collected using * the attribute collector; having DTD-derived info in same order) * that is of type NOTATION. DTD explicitly specifies that at most one * attribute can have this type for any element. * * @return Index of the attribute with type NOTATION, in the current * element, if one exists: -1 otherwise */
@Override public int getNotationAttrIndex() { /* If necessary, we could find this index when resolving the * element, could avoid linear search. But who knows how often * it's really needed... */ for (int i = 0, len = mAttrCount; i < len; ++i) { if (mAttrSpecs[i].typeIsNotation()) { return i; } } return -1; } /* ///////////////////////////////////////////////////// // NsDefaultProvider interface ///////////////////////////////////////////////////// */
Calling this method before checkNsDefaults is necessary to pass information regarding the current element; although it will become available later on (via normal XMLValidator interface), that's too late (after namespace binding and resolving).
/** * Calling this method before {@link #checkNsDefaults} is necessary * to pass information regarding the current element; although * it will become available later on (via normal XMLValidator interface), * that's too late (after namespace binding and resolving). */
@Override public boolean mayHaveNsDefaults(String elemPrefix, String elemLN) { mTmpKey.reset(elemPrefix, elemLN); DTDElement elem = mElemSpecs.get(mTmpKey); mCurrElem = elem; return (elem != null) && elem.hasNsDefaults(); } @Override public void checkNsDefaults(InputElementStack nsStack) throws XMLStreamException { // We only get called if mCurrElem != null, and has defaults HashMap<String,DTDAttribute> m = mCurrElem.getNsDefaults(); if (m != null) { for (Map.Entry<String,DTDAttribute> me : m.entrySet()) { String prefix = me.getKey(); if (!nsStack.isPrefixLocallyDeclared(prefix)) { DTDAttribute attr = me.getValue(); String uri = attr.getDefaultValue(mContext, this); nsStack.addNsBinding(prefix, uri); } } } } /* /////////////////////////////////////// // Package methods, accessors /////////////////////////////////////// */
Name of current element on the top of the element stack.
/** * Name of current element on the top of the element stack. */
PrefixedName getElemName() { DTDElement elem = mElems[mElemCount-1]; return elem.getName(); } Location getLocation() { return mContext.getValidationLocation(); } protected abstract ElementIdMap getIdMap(); Map<String,EntityDecl> getEntityMap() { return mGeneralEntities; } char[] getTempAttrValueBuffer(int neededLength) { if (mTmpAttrValueBuffer == null || mTmpAttrValueBuffer.length < neededLength) { int size = (neededLength < 100) ? 100 : neededLength; mTmpAttrValueBuffer = new char[size]; } return mTmpAttrValueBuffer; } public boolean hasNsDefaults() { return mHasNsDefaults; } /* /////////////////////////////////////// // Package methods, error handling /////////////////////////////////////// */
Method called to report validity problems; depending on mode, will either throw an exception, or add a problem notification to the list of problems.
/** * Method called to report validity problems; depending on mode, will * either throw an exception, or add a problem notification to the * list of problems. */
void reportValidationProblem(String msg) throws XMLStreamException { doReportValidationProblem(msg, null); } void reportValidationProblem(String msg, Location loc) throws XMLStreamException { doReportValidationProblem(msg, loc); } void reportValidationProblem(String format, Object arg) throws XMLStreamException { doReportValidationProblem(MessageFormat.format(format, new Object[] { arg }), null); } void reportValidationProblem(String format, Object arg1, Object arg2) throws XMLStreamException { doReportValidationProblem(MessageFormat.format(format, new Object[] { arg1, arg2 }), null); } /* /////////////////////////////////////// // Private/sub-class methods /////////////////////////////////////// */ protected void doReportValidationProblem(String msg, Location loc) throws XMLStreamException { if (loc == null) { loc = getLocation(); } XMLValidationProblem prob = new XMLValidationProblem(loc, msg, XMLValidationProblem.SEVERITY_ERROR); prob.setReporter(this); mContext.reportProblem(prob); } protected void doAddDefaultValue(DTDAttribute attr) throws XMLStreamException { /* If we get here, we should have a non-null (possibly empty) default * value: */ String def = attr.getDefaultValue(mContext, this); if (def == null) { ExceptionUtil.throwInternal("null default attribute value"); } PrefixedName an = attr.getName(); // Ok, do we need to find the URI? String prefix = an.getPrefix(); String uri = ""; if (prefix != null && prefix.length() > 0) { uri = mContext.getNamespaceURI(prefix); // Can not map to empty NS! if (uri == null || uri.length() == 0) { /* Hmmh. This is a weird case where we do have to * throw a validity exception; even though it really * is more a ns-well-formedness error... */ reportValidationProblem("Unbound namespace prefix \"{0}\" for default attribute \"{1}\"", prefix, attr); // May continue if we don't throw errors, just collect them to a list uri = ""; } } int defIx = mContext.addDefaultAttribute(an.getLocalName(), uri, prefix, def); if (defIx < 0) { /* 13-Dec-2005, Tatus: Hmmh. For readers this is an error * condition, but writers may just indicate they are not * interested in defaults. So let's let context report * problem(s) if it has any regarding the request. */ // nop } else { while (defIx >= mAttrSpecs.length) { mAttrSpecs = (DTDAttribute[]) DataUtil.growArrayBy50Pct(mAttrSpecs); } /* Any intervening empty slots? (can happen if other * validators add default attributes...) */ while (mAttrCount < defIx) { mAttrSpecs[mAttrCount++] = null; } mAttrSpecs[defIx] = attr; mAttrCount = defIx+1; } } }