com.ctc.wstx.dtd.DTDValidator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of woodstox-core Show documentation
Show all versions of woodstox-core Show documentation
Woodstox is a high-performance XML processor that implements Stax (JSR-173),
SAX2 and Stax2 APIs
/* Woodstox XML processor
*
* Copyright (c) 2004- Tatu Saloranta, [email protected]
*
* 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.util.*;
import javax.xml.stream.XMLStreamException;
import org.codehaus.stax2.validation.*;
import com.ctc.wstx.cfg.ErrorConsts;
import com.ctc.wstx.ent.EntityDecl;
import com.ctc.wstx.util.DataUtil;
import com.ctc.wstx.util.ElementId;
import com.ctc.wstx.util.ElementIdMap;
import com.ctc.wstx.util.PrefixedName;
import com.ctc.wstx.util.StringUtil;
/**
* Woodstox implementation of {@link XMLValidator}; the class that
* handles DTD-based validation.
*/
public class DTDValidator
extends DTDValidatorBase
{
/*
///////////////////////////////////////
// Configuration
///////////////////////////////////////
*/
/**
* Determines if identical problems (definition of the same element,
* for example) should cause multiple error notifications or not:
* if true, will get one error per instance, if false, only the first
* one will get reported.
*/
protected boolean mReportDuplicateErrors = false;
/*
///////////////////////////////////////
// Id/idref state
///////////////////////////////////////
*/
/**
* Information about declared and referenced element ids (unique
* ids that attributes may defined, as defined by DTD)
*/
protected ElementIdMap mIdMap = null;
/*
///////////////////////////////////////////
// Element def/spec/validator stack, state
///////////////////////////////////////////
*/
/**
* Stack of validators for open elements
*/
protected StructValidator[] mValidators = null;
/**
* Bitset used for keeping track of required and defaulted attributes
* for which values have been found.
*/
protected BitSet mCurrSpecialAttrs = null;
boolean mCurrHasAnyFixed = false;
/*
///////////////////////////////////////
// Temporary helper objects
///////////////////////////////////////
*/
/**
* Reusable lazily instantiated BitSet; needed to keep track of
* missing 'special' attributes (required ones, ones with default
* values)
*/
BitSet mTmpSpecialAttrs;
/*
///////////////////////////////////////
// Life-cycle
///////////////////////////////////////
*/
public DTDValidator(DTDSubset schema, ValidationContext ctxt, boolean hasNsDefaults,
Map elemSpecs, Map genEntities)
{
super(schema, ctxt, hasNsDefaults, elemSpecs, genEntities);
mValidators = new StructValidator[DEFAULT_STACK_SIZE];
}
@Override
public final boolean reallyValidating() { return true; }
/*
///////////////////////////////////////
// XMLValidator implementation
///////////////////////////////////////
*/
//public XMLValidationSchema getSchema();
/**
* 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 void validateElementStart(String localName, String uri, String prefix)
throws XMLStreamException
{
/* Ok, need to find the element definition; if not found (or
* only implicitly defined), need to throw the exception.
*/
mTmpKey.reset(prefix, localName);
DTDElement elem = mElemSpecs.get(mTmpKey);
/* Let's add the entry in (even if it's a null); this is necessary
* to keep things in-sync if allowing graceful handling of validity
* errors
*/
int elemCount = mElemCount++;
if (elemCount >= mElems.length) {
mElems = (DTDElement[]) DataUtil.growArrayBy50Pct(mElems);
mValidators = (StructValidator[]) DataUtil.growArrayBy50Pct(mValidators);
}
mElems[elemCount] = mCurrElem = elem;
if (elem == null || !elem.isDefined()) {
reportValidationProblem(ErrorConsts.ERR_VLD_UNKNOWN_ELEM, mTmpKey.toString());
}
// Is this element legal under the parent element?
StructValidator pv = (elemCount > 0) ? mValidators[elemCount-1] : null;
if (pv != null && elem != null) {
String msg = pv.tryToValidate(elem.getName());
if (msg != null) {
int ix = msg.indexOf("$END");
String pname = mElems[elemCount-1].toString();
if (ix >= 0) {
msg = msg.substring(0, ix) + ""+pname+">"
+msg.substring(ix+4);
}
reportValidationProblem("Validation error, encountered element <"
+elem.getName()+"> as a child of <"
+pname+">: "+msg);
}
}
mAttrCount = 0;
mIdAttrIndex = -2; // -2 as a "don't know yet" marker
// Ok, need to get the child validator, then:
if (elem == null) {
mValidators[elemCount] = null;
mCurrAttrDefs = NO_ATTRS;
mCurrHasAnyFixed = false;
mCurrSpecialAttrs = null;
} else {
mValidators[elemCount] = elem.getValidator();
mCurrAttrDefs = elem.getAttributes();
if (mCurrAttrDefs == null) {
mCurrAttrDefs = NO_ATTRS;
}
mCurrHasAnyFixed = elem.hasFixedAttrs();
int specCount = elem.getSpecialCount();
if (specCount == 0) {
mCurrSpecialAttrs = null;
} else {
BitSet bs = mTmpSpecialAttrs;
if (bs == null) {
mTmpSpecialAttrs = bs = new BitSet(specCount);
} else {
bs.clear();
}
mCurrSpecialAttrs = bs;
}
}
}
@Override
public String validateAttribute(String localName, String uri,
String prefix, String value)
throws XMLStreamException
{
DTDAttribute attr = mCurrAttrDefs.get(mTmpKey.reset(prefix, localName));
if (attr == null) {
// Only report error if not already recovering from an error:
if (mCurrElem != null) {
reportValidationProblem(ErrorConsts.ERR_VLD_UNKNOWN_ATTR,
mCurrElem.toString(), mTmpKey.toString());
}
/* [WSTX-190] NPE if we continued (after reported didn't
* throw an exception); nothing more to do, let's leave
*/
return value;
}
int index = mAttrCount++;
if (index >= mAttrSpecs.length) {
mAttrSpecs = (DTDAttribute[]) DataUtil.growArrayBy50Pct(mAttrSpecs);
}
mAttrSpecs[index] = attr;
if (mCurrSpecialAttrs != null) { // Need to mark that we got it
int specIndex = attr.getSpecialIndex();
if (specIndex >= 0) {
mCurrSpecialAttrs.set(specIndex);
}
}
String result = attr.validate(this, value, mNormAttrs);
if (mCurrHasAnyFixed && attr.isFixed()) {
String act = (result == null) ? value : result;
String exp = attr.getDefaultValue(mContext, this);
if (!act.equals(exp)) {
reportValidationProblem("Value of attribute \""+attr+"\" (element <"+mCurrElem+">) not \""+exp+"\" as expected, but \""+act+"\"");
}
}
return result;
}
@Override
public String validateAttribute(String localName, String uri,
String prefix,
char[] valueChars, int valueStart,
int valueEnd)
throws XMLStreamException
{
DTDAttribute attr = mCurrAttrDefs.get(mTmpKey.reset(prefix, localName));
if (attr == null) {
// Only report error if not already covering from an error:
if (mCurrElem != null) {
reportValidationProblem(ErrorConsts.ERR_VLD_UNKNOWN_ATTR,
mCurrElem.toString(), mTmpKey.toString());
}
/* [WSTX-190] NPE if we continued (after reported didn't
* throw an exception); nothing more to do, let's leave
*/
return new String(valueChars, valueStart, valueEnd);
}
int index = mAttrCount++;
if (index >= mAttrSpecs.length) {
mAttrSpecs = (DTDAttribute[]) DataUtil.growArrayBy50Pct(mAttrSpecs);
}
mAttrSpecs[index] = attr;
if (mCurrSpecialAttrs != null) { // Need to mark that we got it
int specIndex = attr.getSpecialIndex();
if (specIndex >= 0) {
mCurrSpecialAttrs.set(specIndex);
}
}
String result = attr.validate(this, valueChars, valueStart, valueEnd, mNormAttrs);
if (mCurrHasAnyFixed && attr.isFixed()) {
String exp = attr.getDefaultValue(mContext, this);
boolean match;
if (result == null) {
match = StringUtil.matches(exp, valueChars, valueStart, valueEnd - valueStart);
} else {
match = exp.equals(result);
}
if (!match) {
String act = (result == null) ?
new String(valueChars, valueStart, valueEnd) : result;
reportValidationProblem("Value of #FIXED attribute \""+attr+"\" (element <"+mCurrElem+">) not \""+exp+"\" as expected, but \""+act+"\"");
}
}
return result;
}
@Override
public int validateElementAndAttributes()
throws XMLStreamException
{
// Ok: are we fine with the attributes?
DTDElement elem = mCurrElem;
if (elem == null) { // had an error, most likely no such element defined...
// need to just return, nothing to do here
return XMLValidator.CONTENT_ALLOW_ANY_TEXT;
}
// Any special attributes missing?
if (mCurrSpecialAttrs != null) {
BitSet specBits = mCurrSpecialAttrs;
int specCount = elem.getSpecialCount();
int ix = specBits.nextClearBit(0);
while (ix < specCount) { // something amiss!
List specAttrs = elem.getSpecialAttrs();
DTDAttribute attr = specAttrs.get(ix);
/* [WSTX-155]: Problems if reportValidationProblem returns
* ok (which happens if a reporter handles it). So what
* to do with missing required value? First thought is
* to just leave it as is.
*/
if (attr.isRequired()) {
reportValidationProblem("Required attribute \"{0}\" missing from element <{1}>", attr, elem);
} else {
doAddDefaultValue(attr);
}
ix = specBits.nextClearBit(ix+1);
}
}
return elem.getAllowedContent();
}
/**
* @return Validation state that should be effective for the parent
* element state
*/
@Override
public int validateElementEnd(String localName, String uri, String prefix)
throws XMLStreamException
{
// First, let's remove the top:
final int ix = mElemCount-1;
// [WSTX-200]: need to avoid problems when doing sub-tree
// validation...
if (ix < 0) {
return XMLValidator.CONTENT_ALLOW_WS;
}
mElemCount = ix;
DTDElement closingElem = mElems[ix];
mElems[ix] = null;
StructValidator v = mValidators[ix];
mValidators[ix] = null;
// Validation?
if (v != null) {
String msg = v.fullyValid();
if (msg != null) {
reportValidationProblem("Validation error, element "
+closingElem+">: "+msg);
}
}
// Then let's get info from parent, if any
if (ix < 1) { // root element closing..
// doesn't really matter; epilog/prolog differently handled:
return XMLValidator.CONTENT_ALLOW_WS;
}
final DTDElement element = mElems[ix-1];
// 22-Apr-2020, tatu: `null` validator added for unknown elements
return (element == null)
? XMLValidator.CONTENT_ALLOW_ANY_TEXT
: element.getAllowedContent();
}
//public void validateText(String text, boolean lastTextSegment) ;
//public void validateText(char[] cbuf, int textStart, int textEnd, boolean lastTextSegment) ;
@Override
public void validationCompleted(boolean eod) throws XMLStreamException
{
/* Need to now ensure that all IDREF/IDREFS references
* point to defined ID attributes
*/
checkIdRefs();
}
/*
///////////////////////////////////////
// Package methods, accessors
///////////////////////////////////////
*/
@Override
protected ElementIdMap getIdMap() {
if (mIdMap == null) {
mIdMap = new ElementIdMap();
}
return mIdMap;
}
/*
///////////////////////////////////////
// Internal methods
///////////////////////////////////////
*/
protected void checkIdRefs()
throws XMLStreamException
{
/* 02-Oct-2004, TSa: Now we can also check that all id references
* pointed to ids that actually are defined
*/
if (mIdMap != null) {
ElementId ref = mIdMap.getFirstUndefined();
if (ref != null) { // problem!
reportValidationProblem("Undefined id '"+ref.getId()
+"': referenced from element <"
+ref.getElemName()+">, attribute '"
+ref.getAttrName()+"'",
ref.getLocation());
}
}
}
}