1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  */
   4 /*
   5  * Licensed to the Apache Software Foundation (ASF) under one or more
   6  * contributor license agreements.  See the NOTICE file distributed with
   7  * this work for additional information regarding copyright ownership.
   8  * The ASF licenses this file to You under the Apache License, Version 2.0
   9  * (the "License"); you may not use this file except in compliance with
  10  * the License.  You may obtain a copy of the License at
  11  *
  12  *      http://www.apache.org/licenses/LICENSE-2.0
  13  *
  14  * Unless required by applicable law or agreed to in writing, software
  15  * distributed under the License is distributed on an "AS IS" BASIS,
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17  * See the License for the specific language governing permissions and
  18  * limitations under the License.
  19  */
  20 
  21 package com.sun.org.apache.xerces.internal.impl.xs.identity;
  22 
  23 import com.sun.org.apache.xerces.internal.impl.Constants;
  24 import com.sun.org.apache.xerces.internal.impl.xpath.XPath;
  25 import com.sun.org.apache.xerces.internal.util.IntStack;
  26 import com.sun.org.apache.xerces.internal.xni.QName;
  27 import com.sun.org.apache.xerces.internal.xni.XMLAttributes;
  28 import com.sun.org.apache.xerces.internal.xs.AttributePSVI;
  29 import com.sun.org.apache.xerces.internal.xs.ShortList;
  30 import com.sun.org.apache.xerces.internal.xs.XSTypeDefinition;
  31 import org.xml.sax.SAXException;
  32 
  33 
  34 /**
  35  * XPath matcher.
  36  *
  37  * @xerces.internal
  38  *
  39  * @author Andy Clark, IBM
  40  *
  41  */
  42 public class XPathMatcher {
  43 
  44     //
  45     // Constants
  46     //
  47 
  48     // debugging
  49 
  50     /** Compile to true to debug everything. */
  51     protected static final boolean DEBUG_ALL = false;
  52 
  53     /** Compile to true to debug method callbacks. */
  54     protected static final boolean DEBUG_METHODS = false || DEBUG_ALL;
  55 
  56     /** Compile to true to debug important method callbacks. */
  57     protected static final boolean DEBUG_METHODS2 = false || DEBUG_METHODS || DEBUG_ALL;
  58 
  59     /** Compile to true to debug the <em>really</em> important methods. */
  60     protected static final boolean DEBUG_METHODS3 = false || DEBUG_METHODS || DEBUG_ALL;
  61 
  62     /** Compile to true to debug match. */
  63     protected static final boolean DEBUG_MATCH = false || DEBUG_ALL;
  64 
  65     /** Compile to true to debug step index stack. */
  66     protected static final boolean DEBUG_STACK = false || DEBUG_ALL;
  67 
  68     /** Don't touch this value unless you add more debug constants. */
  69     protected static final boolean DEBUG_ANY = DEBUG_METHODS ||
  70                                                DEBUG_METHODS2 ||
  71                                                DEBUG_METHODS3 ||
  72                                                DEBUG_MATCH ||
  73                                                DEBUG_STACK;
  74 
  75     // constants describing whether a match was made,
  76     // and if so how.
  77     // matched any way
  78     protected static final int MATCHED = 1;
  79     // matched on the attribute axis
  80     protected static final int MATCHED_ATTRIBUTE = 3;
  81     // matched on the descendant-or-self axixs
  82     protected static final int MATCHED_DESCENDANT = 5;
  83     // matched some previous (ancestor) node on the descendant-or-self-axis, but not this node
  84     protected static final int MATCHED_DESCENDANT_PREVIOUS = 13;
  85 
  86     //
  87     // Data
  88     //
  89 
  90     /** XPath location path. */
  91     private XPath.LocationPath[] fLocationPaths;
  92 
  93     /** True if XPath has been matched. */
  94     private int[] fMatched;
  95 
  96     /** The matching string. */
  97     protected Object fMatchedString;
  98 
  99     /** Integer stack of step indexes. */
 100     private IntStack[] fStepIndexes;
 101 
 102     /** Current step. */
 103     private int[] fCurrentStep;
 104 
 105     /**
 106      * No match depth. The value of this field will be zero while
 107      * matching is successful for the given xpath expression.
 108      */
 109     private int [] fNoMatchDepth;
 110 
 111     final QName fQName = new QName();
 112 
 113 
 114     //
 115     // Constructors
 116     //
 117 
 118     /**
 119      * Constructs an XPath matcher that implements a document fragment
 120      * handler.
 121      *
 122      * @param xpath   The xpath.
 123      */
 124     public XPathMatcher(XPath xpath) {
 125         fLocationPaths = xpath.getLocationPaths();
 126         fStepIndexes = new IntStack[fLocationPaths.length];
 127         for(int i=0; i<fStepIndexes.length; i++) fStepIndexes[i] = new IntStack();
 128         fCurrentStep = new int[fLocationPaths.length];
 129         fNoMatchDepth = new int[fLocationPaths.length];
 130         fMatched = new int[fLocationPaths.length];
 131     } // <init>(XPath)
 132 
 133     //
 134     // Public methods
 135     //
 136 
 137     /**
 138      * Returns value of first member of fMatched that
 139      * is nonzero.
 140      */
 141     public boolean isMatched() {
 142         // xpath has been matched if any one of the members of the union have matched.
 143         for (int i=0; i < fLocationPaths.length; i++)
 144             if (((fMatched[i] & MATCHED) == MATCHED)
 145                     && ((fMatched[i] & MATCHED_DESCENDANT_PREVIOUS) != MATCHED_DESCENDANT_PREVIOUS)
 146                     && ((fNoMatchDepth[i] == 0)
 147                     || ((fMatched[i] & MATCHED_DESCENDANT) == MATCHED_DESCENDANT)))
 148                 return true;
 149 
 150         return false;
 151     } // isMatched():int
 152 
 153     //
 154     // Protected methods
 155     //
 156 
 157     // a place-holder method; to be overridden by subclasses
 158     // that care about matching element content.
 159     protected void handleContent(XSTypeDefinition type, boolean nillable,
 160             Object value, short valueType, ShortList itemValueType) {
 161     }
 162 
 163     /**
 164      * This method is called when the XPath handler matches the
 165      * XPath expression. Subclasses can override this method to
 166      * provide default handling upon a match.
 167      */
 168     protected void matched(Object actualValue, short valueType,
 169             ShortList itemValueType, boolean isNil) {
 170         if (DEBUG_METHODS3) {
 171             System.out.println(toString()+"#matched(\""+actualValue+"\")");
 172         }
 173     } // matched(String content, XSSimpleType val)
 174 
 175     //
 176     // ~XMLDocumentFragmentHandler methods
 177     //
 178 
 179     /**
 180      * The start of the document fragment.
 181      */
 182     public void startDocumentFragment(){
 183         if (DEBUG_METHODS) {
 184             System.out.println(toString()+"#startDocumentFragment("+
 185                                ")");
 186         }
 187 
 188         // reset state
 189         fMatchedString = null;
 190         for(int i = 0; i < fLocationPaths.length; i++) {
 191             fStepIndexes[i].clear();
 192             fCurrentStep[i] = 0;
 193             fNoMatchDepth[i] = 0;
 194             fMatched[i] = 0;
 195         }
 196 
 197 
 198     } // startDocumentFragment()
 199 
 200     /**
 201      * The start of an element. If the document specifies the start element
 202      * by using an empty tag, then the startElement method will immediately
 203      * be followed by the endElement method, with no intervening methods.
 204      *
 205      * @param element    The name of the element.
 206      * @param attributes The element attributes.
 207      *
 208      * @throws SAXException Thrown by handler to signal an error.
 209      */
 210     public void startElement(QName element, XMLAttributes attributes){
 211         if (DEBUG_METHODS2) {
 212             System.out.println(toString()+"#startElement("+
 213                                "element={"+element+"},"+
 214                                "attributes=..."+attributes+
 215                                ")");
 216         }
 217 
 218         for(int i = 0; i < fLocationPaths.length; i++) {
 219             // push context
 220             int startStep = fCurrentStep[i];
 221             fStepIndexes[i].push(startStep);
 222 
 223             // try next xpath, if not matching
 224             if ((fMatched[i] & MATCHED_DESCENDANT) == MATCHED || fNoMatchDepth[i] > 0) {
 225                 fNoMatchDepth[i]++;
 226                 continue;
 227             }
 228             if((fMatched[i] & MATCHED_DESCENDANT) == MATCHED_DESCENDANT) {
 229                 fMatched[i] = MATCHED_DESCENDANT_PREVIOUS;
 230             }
 231 
 232             if (DEBUG_STACK) {
 233                 System.out.println(toString()+": "+fStepIndexes[i]);
 234             }
 235 
 236             // consume self::node() steps
 237             XPath.Step[] steps = fLocationPaths[i].steps;
 238             while (fCurrentStep[i] < steps.length &&
 239                     steps[fCurrentStep[i]].axis.type == XPath.Axis.SELF) {
 240                 if (DEBUG_MATCH) {
 241                     XPath.Step step = steps[fCurrentStep[i]];
 242                     System.out.println(toString()+" [SELF] MATCHED!");
 243                 }
 244                 fCurrentStep[i]++;
 245             }
 246             if (fCurrentStep[i] == steps.length) {
 247                 if (DEBUG_MATCH) {
 248                     System.out.println(toString()+" XPath MATCHED!");
 249                 }
 250                 fMatched[i] = MATCHED;
 251                 continue;
 252             }
 253 
 254             // now if the current step is a descendant step, we let the next
 255             // step do its thing; if it fails, we reset ourselves
 256             // to look at this step for next time we're called.
 257             // so first consume all descendants:
 258             int descendantStep = fCurrentStep[i];
 259             while(fCurrentStep[i] < steps.length &&
 260                     steps[fCurrentStep[i]].axis.type == XPath.Axis.DESCENDANT) {
 261                 if (DEBUG_MATCH) {
 262                     XPath.Step step = steps[fCurrentStep[i]];
 263                     System.out.println(toString()+" [DESCENDANT] MATCHED!");
 264                 }
 265                 fCurrentStep[i]++;
 266             }
 267             boolean sawDescendant = fCurrentStep[i] > descendantStep;
 268             if (fCurrentStep[i] == steps.length) {
 269                 if (DEBUG_MATCH) {
 270                     System.out.println(toString()+" XPath DIDN'T MATCH!");
 271                 }
 272                 fNoMatchDepth[i]++;
 273                 if (DEBUG_MATCH) {
 274                     System.out.println(toString()+" [CHILD] after NO MATCH");
 275                 }
 276                 continue;
 277             }
 278 
 279             // match child::... step, if haven't consumed any self::node()
 280             if ((fCurrentStep[i] == startStep || fCurrentStep[i] > descendantStep) &&
 281                 steps[fCurrentStep[i]].axis.type == XPath.Axis.CHILD) {
 282                 XPath.Step step = steps[fCurrentStep[i]];
 283                 XPath.NodeTest nodeTest = step.nodeTest;
 284                 if (DEBUG_MATCH) {
 285                     System.out.println(toString()+" [CHILD] before");
 286                 }
 287                 if (nodeTest.type == XPath.NodeTest.QNAME) {
 288                     if (!nodeTest.name.equals(element)) {
 289                         if(fCurrentStep[i] > descendantStep) {
 290                             fCurrentStep[i] = descendantStep;
 291                             continue;
 292                         }
 293                         fNoMatchDepth[i]++;
 294                         if (DEBUG_MATCH) {
 295                             System.out.println(toString()+" [CHILD] after NO MATCH");
 296                         }
 297                         continue;
 298                     }
 299                 }
 300                 fCurrentStep[i]++;
 301                 if (DEBUG_MATCH) {
 302                     System.out.println(toString()+" [CHILD] after MATCHED!");
 303                 }
 304             }
 305             if (fCurrentStep[i] == steps.length) {
 306                 if(sawDescendant) {
 307                     fCurrentStep[i] = descendantStep;
 308                     fMatched[i] = MATCHED_DESCENDANT;
 309                 } else {
 310                     fMatched[i] = MATCHED;
 311                 }
 312                 continue;
 313             }
 314 
 315             // match attribute::... step
 316             if (fCurrentStep[i] < steps.length &&
 317                 steps[fCurrentStep[i]].axis.type == XPath.Axis.ATTRIBUTE) {
 318                 if (DEBUG_MATCH) {
 319                     System.out.println(toString()+" [ATTRIBUTE] before");
 320                 }
 321                 int attrCount = attributes.getLength();
 322                 if (attrCount > 0) {
 323                     XPath.NodeTest nodeTest = steps[fCurrentStep[i]].nodeTest;
 324 
 325                     for (int aIndex = 0; aIndex < attrCount; aIndex++) {
 326                         attributes.getName(aIndex, fQName);
 327                         if (nodeTest.type != XPath.NodeTest.QNAME ||
 328                             nodeTest.name.equals(fQName)) {
 329                             fCurrentStep[i]++;
 330                             if (fCurrentStep[i] == steps.length) {
 331                                 fMatched[i] = MATCHED_ATTRIBUTE;
 332                                 int j=0;
 333                                 for(; j<i && ((fMatched[j] & MATCHED) != MATCHED); j++);
 334                                 if(j==i) {
 335                                     AttributePSVI attrPSVI = (AttributePSVI)attributes.
 336                                             getAugmentations(aIndex).getItem(Constants.ATTRIBUTE_PSVI);
 337                                     fMatchedString = attrPSVI.getSchemaValue().getActualValue();
 338                                     matched(fMatchedString, attrPSVI.getSchemaValue().getActualValueType(),
 339                                             attrPSVI.getSchemaValue().getListValueTypes(), false);
 340                                 }
 341                             }
 342                             break;
 343                         }
 344                     }
 345                 }
 346                 if ((fMatched[i] & MATCHED) != MATCHED) {
 347                     if(fCurrentStep[i] > descendantStep) {
 348                         fCurrentStep[i] = descendantStep;
 349                         continue;
 350                     }
 351                     fNoMatchDepth[i]++;
 352                     if (DEBUG_MATCH) {
 353                         System.out.println(toString()+" [ATTRIBUTE] after");
 354                     }
 355                     continue;
 356                 }
 357                 if (DEBUG_MATCH) {
 358                     System.out.println(toString()+" [ATTRIBUTE] after MATCHED!");
 359                 }
 360             }
 361         }
 362 
 363     }
 364     // startElement(QName,XMLAttrList,int)
 365 
 366     /**
 367        * @param element
 368        *        name of the element.
 369        * @param type
 370        *        content type of this element. IOW, the XML schema type
 371        *        of the <tt>value</tt>. Note that this may not be the type declared
 372        *        in the element declaration, but it is "the actual type". For example,
 373        *        if the XML is &lt;foo xsi:type="xs:string">aaa&lt;/foo>, this
 374        *        parameter will be "xs:string".
 375        * @param nillable - nillable
 376        *        true if the element declaration is nillable.
 377        * @param value - actual value
 378        *        the typed value of the content of this element.
 379        */
 380     public void endElement(QName element, XSTypeDefinition type, boolean nillable,
 381             Object value, short valueType, ShortList itemValueType) {
 382         if (DEBUG_METHODS2) {
 383             System.out.println(toString()+"#endElement("+
 384                                "element={"+element+"},"+
 385                                ")");
 386         }
 387         for(int i = 0; i<fLocationPaths.length; i++) {
 388             // go back a step
 389             fCurrentStep[i] = fStepIndexes[i].pop();
 390 
 391             // don't do anything, if not matching
 392             if (fNoMatchDepth[i] > 0) {
 393                 fNoMatchDepth[i]--;
 394             }
 395 
 396             // signal match, if appropriate
 397             else {
 398                 int j=0;
 399                 for(; j<i && ((fMatched[j] & MATCHED) != MATCHED); j++);
 400                 if ((j<i) || (fMatched[j] == 0) ||
 401                         ((fMatched[j] & MATCHED_ATTRIBUTE) == MATCHED_ATTRIBUTE)) {
 402                     continue;
 403                 }
 404                 // only certain kinds of matchers actually
 405                 // match element content.  This permits
 406                 // them a way to override this to do nothing
 407                 // and hopefully save a few operations.
 408                 handleContent(type, nillable, value, valueType, itemValueType);
 409                 fMatched[i] = 0;
 410             }
 411 
 412             if (DEBUG_STACK) {
 413                 System.out.println(toString()+": "+fStepIndexes[i]);
 414             }
 415         }
 416 
 417     } // endElement(QName)
 418 
 419     //
 420     // Object methods
 421     //
 422 
 423     /** Returns a string representation of this object. */
 424     public String toString() {
 425         /***
 426         return fLocationPath.toString();
 427         /***/
 428         StringBuffer str = new StringBuffer();
 429         String s = super.toString();
 430         int index2 = s.lastIndexOf('.');
 431         if (index2 != -1) {
 432             s = s.substring(index2 + 1);
 433         }
 434         str.append(s);
 435         for(int i =0;i<fLocationPaths.length; i++) {
 436             str.append('[');
 437             XPath.Step[] steps = fLocationPaths[i].steps;
 438             for (int j = 0; j < steps.length; j++) {
 439                 if (j == fCurrentStep[i]) {
 440                     str.append('^');
 441                 }
 442                 str.append(steps[j].toString());
 443                 if (j < steps.length - 1) {
 444                     str.append('/');
 445                 }
 446             }
 447             if (fCurrentStep[i] == steps.length) {
 448                 str.append('^');
 449             }
 450             str.append(']');
 451             str.append(',');
 452         }
 453         return str.toString();
 454     } // toString():String
 455 
 456     //
 457     // Private methods
 458     //
 459 
 460     /** Normalizes text. */
 461     private String normalize(String s) {
 462         StringBuffer str = new StringBuffer();
 463         int length = s.length();
 464         for (int i = 0; i < length; i++) {
 465             char c = s.charAt(i);
 466             switch (c) {
 467                 case '\n': {
 468                     str.append("\\n");
 469                     break;
 470                 }
 471                 default: {
 472                     str.append(c);
 473                 }
 474             }
 475         }
 476         return str.toString();
 477     } // normalize(String):String
 478 
 479     //
 480     // MAIN
 481     //
 482 
 483     // NOTE: The main of this class is here for debugging purposes.
 484     //       However, javac (JDK 1.1.8) has an internal compiler
 485     //       error when compiling. Jikes has no problem, though.
 486     //
 487     //       If you want to use this main, use Jikes to compile but
 488     //       *never* check in this code to CVS without commenting it
 489     //       out. -Ac
 490 
 491     /** Main program. */
 492     /***
 493     public static void main(String[] argv) throws XNIException {
 494 
 495         if (DEBUG_ANY) {
 496             for (int i = 0; i < argv.length; i++) {
 497                 final String expr = argv[i];
 498                 final XPath xpath = new XPath(expr, symbols, null);
 499                 final XPathMatcher matcher = new XPathMatcher(xpath, true);
 500                 com.sun.org.apache.xerces.internal.parsers.SAXParser parser =
 501                     new com.sun.org.apache.xerces.internal.parsers.SAXParser(symbols) {
 502                     public void startDocument() throws XNIException {
 503                         matcher.startDocumentFragment(symbols, null);
 504                     }
 505                     public void startElement(QName element, XMLAttrList attributes, int handle) throws XNIException {
 506                         matcher.startElement(element, attributes, handle);
 507                     }
 508                     public void characters(char[] ch, int offset, int length) throws XNIException {
 509                         matcher.characters(ch, offset, length);
 510                     }
 511                     public void endElement(QName element) throws XNIException {
 512                         matcher.endElement(element);
 513                     }
 514                     public void endDocument() throws XNIException {
 515                         matcher.endDocumentFragment();
 516                     }
 517                 };
 518                 System.out.println("#### argv["+i+"]: \""+expr+"\" -> \""+xpath.toString()+'"');
 519                 final String uri = argv[++i];
 520                 System.out.println("#### argv["+i+"]: "+uri);
 521                 parser.parse(uri);
 522             }
 523         }
 524 
 525     } // main(String[])
 526     /***/
 527 
 528 } // class XPathMatcher