src.java.com.ctc.wstx.msv.RelaxNGValidator Maven / Gradle / Ivy
/* Woodstox XML processor
*
* Copyright (c) 2004- Tatu Saloranta, [email protected]
*
* Licensed under the License specified in the file LICENSE which is
* 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.msv;
import java.io.*;
import java.net.URL;
import java.util.*;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.*;
import org.codehaus.stax2.*;
import org.codehaus.stax2.validation.*;
import com.sun.msv.grammar.IDContextProvider2;
import com.sun.msv.util.DatatypeRef;
import com.sun.msv.util.StartTagInfo;
import com.sun.msv.util.StringRef;
import com.sun.msv.verifier.Acceptor;
import com.sun.msv.verifier.regexp.REDocumentDeclaration;
import com.ctc.wstx.exc.WstxIOException;
import com.ctc.wstx.util.TextAccumulator;
/**
* Actual non-shareable validator instance, that is bound to an XML
* document, or document subset.
*
* Some notes about implementation:
*
* (a) Some properties of RelaxNG model are used for optimization: for
* example, since text content in mixed-content models is not really
* validated for content, we only combine text segments between start
* and end tags (in any combination) -- full textual content between
* matching start and end tags is not necessarily collected (or rather,
* if there are child elements, is NEVER collected).
*/
public class RelaxNGValidator
extends XMLValidator
{
/*
////////////////////////////////////
// Configuration
////////////////////////////////////
*/
final XMLValidationSchema mParentSchema;
final ValidationContext mContext;
final REDocumentDeclaration mVGM;
/*
////////////////////////////////////
// State
////////////////////////////////////
*/
final ArrayList mAcceptors = new ArrayList();
Acceptor mCurrAcceptor = null;
final TextAccumulator mTextAccumulator = new TextAccumulator();
/*
////////////////////////////////////
// Helper objects
////////////////////////////////////
*/
final StringRef mErrorRef = new StringRef();
/**
* StartTagInfo instance need not be thread-safe, and it is not immutable
* so let's reuse one instance during a single validation.
*/
final StartTagInfo mStartTag = new StartTagInfo("", "", "", null, (IDContextProvider2) null);
/**
* Since RelaxNG never has to look into attributes during element
* processing (start/end) -- unlike W3C schema, which may need to
* access xsi:type and xsi:nillable -- we can just use an empty
* shared instance.
*/
final AttributeProxy mAttributeProxy;
final IDContextProvider2 mMsvContext;
/*
////////////////////////////////////
// Construction, configuration
////////////////////////////////////
*/
public RelaxNGValidator(XMLValidationSchema parent, ValidationContext ctxt,
REDocumentDeclaration vgm)
{
mParentSchema = parent;
mContext = ctxt;
mVGM = vgm;
mCurrAcceptor = mVGM.createAcceptor();
mMsvContext = new MSVContextProvider(ctxt);
mAttributeProxy = new AttributeProxy(ctxt);
}
/*
///////////////////////////////////////
// XMLValidator implementation
///////////////////////////////////////
*/
public XMLValidationSchema getSchema() {
return mParentSchema;
}
/**
* 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.
*/
public void validateElementStart(String localName, String uri, String prefix)
throws XMLValidationException
{
// Very first thing: do we have text collected?
if (mTextAccumulator.hasText()) {
doValidateText(mTextAccumulator);
}
/* 31-Mar-2006, TSa: MSV seems to require empty String for empty/no
* namespace, not null.
*/
if (uri == null) {
uri = "";
}
/* Do we need to properly fill it? Or could we just put local name?
* Looking at code, I do believe it's only used for error reporting
* purposes...
*/
//String qname = (prefix == null || prefix.length() == 0) ? localName : (prefix + ":" +localName);
String qname = localName;
mStartTag.reinit(uri, localName, qname, mAttributeProxy, mMsvContext);
mCurrAcceptor = mCurrAcceptor.createChildAcceptor(mStartTag, mErrorRef);
/* As per documentation, the side-effect of getting the error message
* is that we also get a recoverable non-null acceptor... thus, should
* never (?) see null acceptor being returned
*/
if (mErrorRef.str != null) {
reportError(mErrorRef);
}
mAcceptors.add(mCurrAcceptor);
}
public String validateAttribute(String localName, String uri,
String prefix, String value)
throws XMLValidationException
{
if (mCurrAcceptor != null) {
String qname = localName; // for now, let's assume we don't need prefixed version
DatatypeRef typeRef = null; // for now, let's not care
/* 31-Mar-2006, TSa: MSV seems to require empty String for empty/no
* namespace, not null.
*/
if (uri == null) {
uri = "";
}
if (!mCurrAcceptor.onAttribute2(uri, localName, qname, value, mMsvContext, mErrorRef, typeRef)
|| mErrorRef.str != null) {
reportError(mErrorRef);
}
}
/* No normalization done by RelaxNG, is there? (at least nothing
* visible to callers that is)
*/
return null;
}
public String validateAttribute(String localName, String uri,
String prefix,
char[] valueChars, int valueStart,
int valueEnd)
throws XMLValidationException
{
int len = valueEnd - valueStart;
/* This is very sub-optimal... but MSV doesn't deal with char
* arrays.
*/
return validateAttribute(localName, uri, prefix,
new String(valueChars, valueStart, len));
}
public int validateElementAndAttributes()
throws XMLValidationException
{
if (mCurrAcceptor != null) {
/* start tag info is still intact here (only attributes sent
* since child acceptor was created)
*/
if (!mCurrAcceptor.onEndAttributes(mStartTag, mErrorRef)
|| mErrorRef.str != null) {
reportError(mErrorRef);
}
int stringChecks = mCurrAcceptor.getStringCareLevel();
switch (stringChecks) {
case Acceptor.STRING_PROHIBITED: // only WS
return XMLValidator.CONTENT_ALLOW_WS;
case Acceptor.STRING_IGNORE: // anything (mixed content models)
return XMLValidator.CONTENT_ALLOW_ANY_TEXT;
case Acceptor.STRING_STRICT: // validatable (data-oriented)
return XMLValidator.CONTENT_ALLOW_VALIDATABLE_TEXT;
default:
throw new IllegalArgumentException("Internal error: unexpected string care level value return by MSV: "+stringChecks);
}
}
// If no acceptor, we are recovering, no need or use to validate text
return CONTENT_ALLOW_ANY_TEXT;
}
/**
* @return Validation state that should be effective for the parent
* element state
*/
public int validateElementEnd(String localName, String uri, String prefix)
throws XMLValidationException
{
// Very first thing: do we have text collected?
if (mTextAccumulator.hasText()) {
doValidateText(mTextAccumulator);
}
Acceptor acc = (Acceptor)mAcceptors.remove(mAcceptors.size()-1);
if (acc != null) { // may be null during error recovery? or not?
if (!acc.isAcceptState(mErrorRef) || mErrorRef.str != null) {
reportError(mErrorRef);
}
}
int len = mAcceptors.size();
if (len == 0) { // root closed
mCurrAcceptor = null;
} else {
mCurrAcceptor = (Acceptor) mAcceptors.get(len-1);
}
if (mCurrAcceptor != null && acc != null) {
if (!mCurrAcceptor.stepForward(acc, mErrorRef)
|| mErrorRef.str != null) {
reportError(mErrorRef);
}
int stringChecks = mCurrAcceptor.getStringCareLevel();
switch (stringChecks) {
case Acceptor.STRING_PROHIBITED: // only WS
return XMLValidator.CONTENT_ALLOW_WS;
case Acceptor.STRING_IGNORE: // anything (mixed content models)
return XMLValidator.CONTENT_ALLOW_ANY_TEXT;
case Acceptor.STRING_STRICT: // validatable (data-oriented)
return XMLValidator.CONTENT_ALLOW_VALIDATABLE_TEXT;
default:
throw new IllegalArgumentException("Internal error: unexpected string care level value return by MSV: "+stringChecks);
}
}
return XMLValidator.CONTENT_ALLOW_ANY_TEXT;
}
public void validateText(String text, boolean lastTextSegment)
throws XMLValidationException
{
/* If we got here, then it's likely we do need to call onText2().
* (not guaranteed, though; in case of multiple parallel validators,
* only one of them may actually be interested)
*/
mTextAccumulator.addText(text);
if (lastTextSegment) {
doValidateText(mTextAccumulator);
}
}
public void validateText(char[] cbuf, int textStart, int textEnd,
boolean lastTextSegment)
throws XMLValidationException
{
/* If we got here, then it's likely we do need to call onText().
* (not guaranteed, though; in case of multiple parallel validators,
* only one of them may actually be interested)
*/
mTextAccumulator.addText(cbuf, textStart, textEnd);
if (lastTextSegment) {
doValidateText(mTextAccumulator);
}
}
public void validationCompleted(boolean eod)
throws XMLValidationException
{
// Is there something we should do here...?
}
/*
///////////////////////////////////////
// Attribute info access
///////////////////////////////////////
*/
// // // Access to type info
public String getAttributeType(int index)
{
// !!! TBI
return null;
}
public int getIdAttrIndex()
{
// !!! TBI
return -1;
}
public int getNotationAttrIndex()
{
// !!! TBI
return -1;
}
/*
///////////////////////////////////////
// Internal methods
///////////////////////////////////////
*/
private void doValidateText(TextAccumulator textAcc)
throws XMLValidationException
{
if (mCurrAcceptor != null) {
String str = textAcc.getAndClear();
DatatypeRef typeRef = null;
if (!mCurrAcceptor.onText2(str, mMsvContext, mErrorRef, typeRef)
|| mErrorRef.str != null) {
reportError(mErrorRef);
}
}
}
private void reportError(StringRef errorRef)
throws XMLValidationException
{
String msg = errorRef.str;
errorRef.str = null;
if (msg == null) {
msg = "Unknown reason";
}
mContext.reportProblem(new XMLValidationProblem
(mContext.getValidationLocation(), msg, XMLValidationProblem.SEVERITY_ERROR));
}
}