/*
* @(#)$Id: TypeDetector.java,v 1.7 2002/06/24 19:58:00 kk122374 Exp $
*
* Copyright 2001 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the proprietary information of Sun Microsystems, Inc.
* Use is subject to license terms.
*
*/
package com.sun.msv.verifier.psvi;
import org.xml.sax.*;
import org.relaxng.datatype.Datatype;
import java.util.Set;
import java.util.Iterator;
import java.util.StringTokenizer;
import com.sun.msv.grammar.ElementExp;
import com.sun.msv.grammar.Grammar;
import com.sun.msv.util.StartTagInfo;
import com.sun.msv.util.StringPair;
import com.sun.msv.util.StringRef;
import com.sun.msv.util.DatatypeRef;
import com.sun.msv.verifier.Acceptor;
import com.sun.msv.verifier.DocumentDeclaration;
import com.sun.msv.verifier.ErrorInfo;
import com.sun.msv.verifier.ValidityViolation;
import com.sun.msv.verifier.Verifier;
import com.sun.msv.verifier.regexp.REDocumentDeclaration;
//import com.sun.msv.verifier.regexp.AttributeToken;
import com.sun.msv.verifier.regexp.SimpleAcceptor;
import com.sun.msv.verifier.regexp.ComplexAcceptor;
/**
* assign types to the incoming SAX2 events and reports them to
* the application handler through TypedContentHandler.
*
* This class "augment" infoset by adding type information. The application can
* receive augmented infoset by implementing TypedContentHandler.
*
* @author <a href="mailto:kohsuke.kawaguchi@sun.com">Kohsuke KAWAGUCHI</a>
*/
public class TypeDetector extends Verifier {
/** characters that were read (but not processed) */
private StringBuffer text = new StringBuffer();
protected TypedContentHandler handler;
public TypeDetector( DocumentDeclaration documentDecl, ErrorHandler errorHandler ) {
super(documentDecl,errorHandler);
}
public TypeDetector( DocumentDeclaration documentDecl, TypedContentHandler handler, ErrorHandler errorHandler ) {
this(documentDecl,errorHandler);
setContentHandler(handler);
}
/**
* sets the TypedContentHandler which will received the type-augmented
* infoset.
*/
public void setContentHandler( TypedContentHandler handler ) {
this.handler = handler;
}
private final DatatypeRef characterType = new DatatypeRef();
protected void verifyText() throws SAXException {
if(text.length()!=0) {
final String txt = new String(text);
if(!current.onText( txt, this, null, characterType )) {
// error
// diagnose error, if possible
StringRef err = new StringRef();
current.onText( txt, this, err, null );
// report an error
errorHandler.error( new ValidityViolation(locator,
localizeMessage( ERR_UNEXPECTED_TEXT, null ),
new ErrorInfo.BadText(txt) ) );
}
// characters are validated. report to the handler.
reportCharacterChunks( txt, characterType.types );
text = new StringBuffer();
}
}
private void reportCharacterChunks( String text, Datatype[] types ) throws SAXException {
if( types==null )
// unable to assign type.
throw new AmbiguousDocumentException();
switch( types.length ) {
case 0:
return; // this text is ignored.
case 1:
handler.characterChunk( text, types[0] );
return;
default:
StringTokenizer tokens = new StringTokenizer(text);
for( int i=0; i<types.length; i++ )
handler.characterChunk( tokens.nextToken(), types[i] );
if( tokens.hasMoreTokens() ) throw new Error(); // assertion failed
}
}
protected Datatype[] feedAttribute( Acceptor child, String uri, String localName, String qName, String value ) throws SAXException {
// thanks to Damian Gajda <zwierzem@ngo.pl> for the patch.
// the startAttribute method should be called before the feedAttribute.
//
// this makes the error report consistent with the startAttribute event.
handler.startAttribute( uri, localName, qName );
Datatype[] result = super.feedAttribute(child,uri,localName,qName,value);
reportCharacterChunks( value, result );
handler.endAttribute( uri, localName, qName,
((REDocumentDeclaration)docDecl).attToken.matchedExp );
return result;
}
public void startElement( String namespaceUri, String localName, String qName, Attributes atts )
throws SAXException {
super.startElement( namespaceUri, localName, qName, atts );
handler.endAttributePart();
}
protected void onNextAcceptorReady( StartTagInfo sti, Acceptor nextAcceptor ) throws SAXException {
/*
You cannot call handler.startElement before super.startElement invocation
because unconsumed text maybe processed here.
*/
handler.startElement( sti.namespaceURI, sti.localName, sti.qName );
}
public void endElement( String namespaceUri, String localName, String qName )
throws SAXException {
Acceptor child = current;
super.endElement(namespaceUri,localName,qName);
{// report to the handler
ElementExp type;
if( child instanceof SimpleAcceptor ) {
type = ((SimpleAcceptor)child).owner;
} else
if( child instanceof ComplexAcceptor ) {
ElementExp[] exps = ((ComplexAcceptor)child).getSatisfiedOwners();
if(exps.length!=1)
throw new AmbiguousDocumentException();
type = exps[0];
} else
throw new Error(); // assertion failed. not supported.
handler.endElement( namespaceUri, localName, qName, type );
}
}
public void characters( char[] buf, int start, int len ) throws SAXException {
text.append(buf,start,len);
}
public void ignorableWhitespace( char[] buf, int start, int len ) throws SAXException {
text.append(buf,start,len);
}
public void startDocument() throws SAXException {
super.startDocument();
handler.startDocument(this);
}
public void endDocument() throws SAXException {
super.endDocument();
handler.endDocument();
}
/**
* signals that the document is ambiguous.
* This exception is thrown when
* <ol>
* <li>we cannot uniquely assign the type for given characters.
* <li>or we cannot uniquely determine the type for the element
* when we reached the end element.
* </ol>
*
* The formar case happens for patterns like:
* <PRE><XMP>
* <choice>
* <data type="xsd:string"/>
* <data type="xsd:token"/>
* </choice>
* </XMP></PRE>
*
* The latter case happens for patterns like:
* <PRE><XMP>
* <choice>
* <element name="foo">
* <text/>
* </element>
* <element>
* <anyName/>
* <text/>
* </element>
* </choice>
* </XMP></PRE>
*/
public class AmbiguousDocumentException extends SAXException {
public AmbiguousDocumentException() {
super("");
}
/** returns the source of the error. */
Locator getLocation() { return TypeDetector.this.getLocator(); }
};
}