/* * @(#)$Id: PathMatcher.java,v 1.3 2001/08/08 00:01:20 Bear 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.identity; import com.sun.msv.datatype.xsd.XSDatatype; import com.sun.msv.grammar.NameClass; import com.sun.msv.grammar.xmlschema.XPath; import org.relaxng.datatype.Datatype; import org.xml.sax.Attributes; import org.xml.sax.SAXException; /** * Base implementation of XPath matching engine. * * It only supports the subset defined in XML Schema Part 1. Extra care * must be taken to call the testInitialMatch method after the creation of an object. * * Match to an attribute is not supported. It is implemented in FieldPathMatcher * class. * * The onMatched method is called when the specified XPath matches the current element. * Derived classes should implement this method to do something useful. * * @author <a href="mailto:kohsuke.kawaguchi@eng.sun.com">Kohsuke KAWAGUCHI</a> */ public abstract class PathMatcher extends MatcherBundle { protected PathMatcher( IDConstraintChecker owner, XPath[] paths ) { super(owner); children = new Matcher[paths.length]; for( int i=0; i<paths.length; i++ ) children[i] = new SinglePathMatcher(paths[i]); // if there is a empty XPath ("."), then matchFound is set // in the above constructor. } /** * this method should be called immediately after the installment of this PathMatcher. */ protected void start( String namespaceURI, String localName ) throws SAXException { if(matchFound) onElementMatched(namespaceURI,localName); matchFound = false; } /** * this method is called when the element matches the XPath. */ protected abstract void onElementMatched( String namespaceURI, String localName ) throws SAXException; /** * this method is called when the attribute matches the XPath. */ protected abstract void onAttributeMatched( String namespaceURI, String localName, String value, Datatype type ) throws SAXException; protected void startElement( String namespaceURI, String localName ) throws SAXException { super.startElement(namespaceURI,localName); if(matchFound) onElementMatched(namespaceURI,localName); matchFound = false; } protected void onAttribute( String namespaceURI, String localName, String value, Datatype type ) throws SAXException { super.onAttribute(namespaceURI,localName,value,type); if(matchFound) onAttributeMatched(namespaceURI,localName,value,type); matchFound = false; } /** * a flag that indicates that this element/attribute matches the path expression. * This flag is set by one of the child SinglePathMatcher. */ private boolean matchFound = false; /** * the XPath matching engine. * * <p> * This class implements the matching engine for single * <a href="http://www.w3.org/TR/xmlschema-1/#Path"> * "Path"</a>. * * <p> * The outer <code>PathMatcher</code> uses multiple instances of this class * and thereby implements the matching engine for the whole "Selector". * * <p> * This class only supports the subset defined in XML Schema Part 1. Extra care * must be taken to call the testInitialMatch method * after the creation of an object. * * <p> * When a match is found, this class notifies the parent object by using a flag. * * */ private class SinglePathMatcher extends Matcher { /** * stores matched steps. * first dimension is expanded as the depth goes deep. * second dimension is always equal to the size of steps. */ private boolean[][] activeSteps; // private short currentDepth=0; protected final XPath path; /** * this flag is set to true when the path contains an attribute step * and the current element matches the element part of the path. * * When this flag is true, we need to honor the onAttribute event * and check if the path really matches to the attribute. */ private boolean elementMatched = false; protected SinglePathMatcher( XPath path ) { super(PathMatcher.this.owner); this.path = path; activeSteps = new boolean[4][]; /* activeSteps[i][0] is used to represent an implicit "root". For example, when XPath is "//A/B", [0]:root [1]:A [2]:B (initial state) 1 0 0 (1 indicates "active") [0] is initialized to 1. (startElement(X)) (step:1 shift to right) 1(*1) 1 0 *1 [0] will be populated by isAnyDescendant field. In this case, since isAnyDescendant ("//") is true, [0] is set to true after shift. This indicates that new element X can possibly be used as the implicit root. (step:2 perform name test) 1 0 0 root is excluded from the test. Since A doesn't match X, the corresponding field is set to false. (startElement(A)) (step:1 shift to right) 1 1 0 (step:2 perform name test) 1 1 0 (startElement(B)) (step:1 shift to right) 1 1 1 (step:2 perform name test) 1 0 1 (*2) *2 Now that the right most slot is true, this element B matches XPath. */ activeSteps[0] = new boolean[path.steps.length+1]; activeSteps[0][0] = true; // initialization // we only need an empty buffer for activeStep[0]. // other slots are filled on demand. if(path.steps.length==0) { // if the step is length 0, (that is, ".") // it is an immediate match. if( path.attributeStep==null ) // report to the parent PathMatcher that a match was found matchFound = true; else elementMatched = true; } } protected void startElement( String namespaceURI, String localName ) throws SAXException { elementMatched = false; // reset the flag. final int depth = getDepth(); if(depth==activeSteps.length-1) { // if the buffer is used up, expand buffer boolean[][] newBuf = new boolean[depth*2][]; System.arraycopy( activeSteps, 0, newBuf, 0, activeSteps.length ); activeSteps = newBuf; } // currentDepth++; int len = path.steps.length; boolean[] prvBuf = activeSteps[depth-1]; boolean[] curBuf = activeSteps[depth]; if(curBuf==null) activeSteps[depth]=curBuf=new boolean[len+1/*implicit root*/]; // shift to right if(len!=0) { System.arraycopy( prvBuf, 0, curBuf, 1, len ); curBuf[0] = path.isAnyDescendant; } // perform name test and deactivate unmatched steps for( int i=1; i<=len; i++ ) // exclude root from test. if( curBuf[i] && !path.steps[i-1].accepts(namespaceURI,localName) ) curBuf[i] = false; if( curBuf[len] ) { // this element matched this path if( path.attributeStep==null ) // report to the parent PathMatcher that a match was found matchFound = true; else elementMatched = true; } } protected void onAttribute( String namespaceURI, String localName, String value, Datatype type ) throws SAXException { // attribute step is not tested when the parent element doesn't match // the parent XPath expression. if( !elementMatched ) return; if( path.attributeStep.accepts(namespaceURI,localName) ) // report to the parent PathMatcher that a match was found matchFound = true; // keep the elementMatched flag as-is. } protected void endElement( Datatype dt ) { elementMatched = false; // reset the flag. // currentDepth--; } } }