001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.xerces.impl.xs.identity;
019:
020: import org.apache.xerces.impl.Constants;
021: import org.apache.xerces.impl.xpath.XPath;
022: import org.apache.xerces.util.IntStack;
023: import org.apache.xerces.xni.QName;
024: import org.apache.xerces.xni.XMLAttributes;
025: import org.apache.xerces.xs.AttributePSVI;
026: import org.apache.xerces.xs.ShortList;
027: import org.apache.xerces.xs.XSTypeDefinition;
028: import org.xml.sax.SAXException;
029:
030: /**
031: * XPath matcher.
032: *
033: * @xerces.internal
034: *
035: * @author Andy Clark, IBM
036: *
037: * @version $Id: XPathMatcher.java 572110 2007-09-02 19:04:44Z mrglavas $
038: */
039: public class XPathMatcher {
040:
041: //
042: // Constants
043: //
044:
045: // debugging
046:
047: /** Compile to true to debug everything. */
048: protected static final boolean DEBUG_ALL = false;
049:
050: /** Compile to true to debug method callbacks. */
051: protected static final boolean DEBUG_METHODS = false || DEBUG_ALL;
052:
053: /** Compile to true to debug important method callbacks. */
054: protected static final boolean DEBUG_METHODS2 = false
055: || DEBUG_METHODS || DEBUG_ALL;
056:
057: /** Compile to true to debug the <em>really</em> important methods. */
058: protected static final boolean DEBUG_METHODS3 = false
059: || DEBUG_METHODS || DEBUG_ALL;
060:
061: /** Compile to true to debug match. */
062: protected static final boolean DEBUG_MATCH = false || DEBUG_ALL;
063:
064: /** Compile to true to debug step index stack. */
065: protected static final boolean DEBUG_STACK = false || DEBUG_ALL;
066:
067: /** Don't touch this value unless you add more debug constants. */
068: protected static final boolean DEBUG_ANY = DEBUG_METHODS
069: || DEBUG_METHODS2 || DEBUG_METHODS3 || DEBUG_MATCH
070: || DEBUG_STACK;
071:
072: // constants describing whether a match was made,
073: // and if so how.
074: // matched any way
075: protected static final int MATCHED = 1;
076: // matched on the attribute axis
077: protected static final int MATCHED_ATTRIBUTE = 3;
078: // matched on the descendant-or-self axixs
079: protected static final int MATCHED_DESCENDANT = 5;
080: // matched some previous (ancestor) node on the descendant-or-self-axis, but not this node
081: protected static final int MATCHED_DESCENDANT_PREVIOUS = 13;
082:
083: //
084: // Data
085: //
086:
087: /** XPath location path. */
088: private final XPath.LocationPath[] fLocationPaths;
089:
090: /** True if XPath has been matched. */
091: private final int[] fMatched;
092:
093: /** The matching string. */
094: protected Object fMatchedString;
095:
096: /** Integer stack of step indexes. */
097: private final IntStack[] fStepIndexes;
098:
099: /** Current step. */
100: private final int[] fCurrentStep;
101:
102: /**
103: * No match depth. The value of this field will be zero while
104: * matching is successful for the given xpath expression.
105: */
106: private final int[] fNoMatchDepth;
107:
108: final QName fQName = new QName();
109:
110: //
111: // Constructors
112: //
113:
114: /**
115: * Constructs an XPath matcher that implements a document fragment
116: * handler.
117: *
118: * @param xpath The xpath.
119: */
120: public XPathMatcher(XPath xpath) {
121: fLocationPaths = xpath.getLocationPaths();
122: fStepIndexes = new IntStack[fLocationPaths.length];
123: for (int i = 0; i < fStepIndexes.length; i++)
124: fStepIndexes[i] = new IntStack();
125: fCurrentStep = new int[fLocationPaths.length];
126: fNoMatchDepth = new int[fLocationPaths.length];
127: fMatched = new int[fLocationPaths.length];
128: } // <init>(XPath)
129:
130: //
131: // Public methods
132: //
133:
134: /**
135: * Returns value of first member of fMatched that
136: * is nonzero.
137: */
138: public boolean isMatched() {
139: // xpath has been matched if any one of the members of the union have matched.
140: for (int i = 0; i < fLocationPaths.length; i++)
141: if (((fMatched[i] & MATCHED) == MATCHED)
142: && ((fMatched[i] & MATCHED_DESCENDANT_PREVIOUS) != MATCHED_DESCENDANT_PREVIOUS)
143: && ((fNoMatchDepth[i] == 0) || ((fMatched[i] & MATCHED_DESCENDANT) == MATCHED_DESCENDANT)))
144: return true;
145:
146: return false;
147: } // isMatched():int
148:
149: //
150: // Protected methods
151: //
152:
153: // a place-holder method; to be overridden by subclasses
154: // that care about matching element content.
155: protected void handleContent(XSTypeDefinition type,
156: boolean nillable, Object value, short valueType,
157: ShortList itemValueType) {
158: }
159:
160: /**
161: * This method is called when the XPath handler matches the
162: * XPath expression. Subclasses can override this method to
163: * provide default handling upon a match.
164: */
165: protected void matched(Object actualValue, short valueType,
166: ShortList itemValueType, boolean isNil) {
167: if (DEBUG_METHODS3) {
168: System.out.println(toString() + "#matched(\"" + actualValue
169: + "\")");
170: }
171: } // matched(String content, XSSimpleType val)
172:
173: //
174: // ~XMLDocumentFragmentHandler methods
175: //
176:
177: /**
178: * The start of the document fragment.
179: */
180: public void startDocumentFragment() {
181: if (DEBUG_METHODS) {
182: System.out.println(toString() + "#startDocumentFragment("
183: + ")");
184: }
185:
186: // reset state
187: fMatchedString = null;
188: for (int i = 0; i < fLocationPaths.length; i++) {
189: fStepIndexes[i].clear();
190: fCurrentStep[i] = 0;
191: fNoMatchDepth[i] = 0;
192: fMatched[i] = 0;
193: }
194:
195: } // startDocumentFragment()
196:
197: /**
198: * The start of an element. If the document specifies the start element
199: * by using an empty tag, then the startElement method will immediately
200: * be followed by the endElement method, with no intervening methods.
201: *
202: * @param element The name of the element.
203: * @param attributes The element attributes.
204: *
205: * @throws SAXException Thrown by handler to signal an error.
206: */
207: public void startElement(QName element, XMLAttributes attributes) {
208: if (DEBUG_METHODS2) {
209: System.out.println(toString() + "#startElement("
210: + "element={" + element + "}," + "attributes=..."
211: + attributes + ")");
212: }
213:
214: for (int i = 0; i < fLocationPaths.length; i++) {
215: // push context
216: int startStep = fCurrentStep[i];
217: fStepIndexes[i].push(startStep);
218:
219: // try next xpath, if not matching
220: if ((fMatched[i] & MATCHED_DESCENDANT) == MATCHED
221: || fNoMatchDepth[i] > 0) {
222: fNoMatchDepth[i]++;
223: continue;
224: }
225: if ((fMatched[i] & MATCHED_DESCENDANT) == MATCHED_DESCENDANT) {
226: fMatched[i] = MATCHED_DESCENDANT_PREVIOUS;
227: }
228:
229: if (DEBUG_STACK) {
230: System.out.println(toString() + ": " + fStepIndexes[i]);
231: }
232:
233: // consume self::node() steps
234: XPath.Step[] steps = fLocationPaths[i].steps;
235: while (fCurrentStep[i] < steps.length
236: && steps[fCurrentStep[i]].axis.type == XPath.Axis.SELF) {
237: if (DEBUG_MATCH) {
238: XPath.Step step = steps[fCurrentStep[i]];
239: System.out.println(toString() + " [SELF] MATCHED!");
240: }
241: fCurrentStep[i]++;
242: }
243: if (fCurrentStep[i] == steps.length) {
244: if (DEBUG_MATCH) {
245: System.out.println(toString() + " XPath MATCHED!");
246: }
247: fMatched[i] = MATCHED;
248: continue;
249: }
250:
251: // now if the current step is a descendant step, we let the next
252: // step do its thing; if it fails, we reset ourselves
253: // to look at this step for next time we're called.
254: // so first consume all descendants:
255: int descendantStep = fCurrentStep[i];
256: while (fCurrentStep[i] < steps.length
257: && steps[fCurrentStep[i]].axis.type == XPath.Axis.DESCENDANT) {
258: if (DEBUG_MATCH) {
259: XPath.Step step = steps[fCurrentStep[i]];
260: System.out.println(toString()
261: + " [DESCENDANT] MATCHED!");
262: }
263: fCurrentStep[i]++;
264: }
265: boolean sawDescendant = fCurrentStep[i] > descendantStep;
266: if (fCurrentStep[i] == steps.length) {
267: if (DEBUG_MATCH) {
268: System.out.println(toString()
269: + " XPath DIDN'T MATCH!");
270: }
271: fNoMatchDepth[i]++;
272: if (DEBUG_MATCH) {
273: System.out.println(toString()
274: + " [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 (!matches(nodeTest, element)) {
288: if (fCurrentStep[i] > descendantStep) {
289: fCurrentStep[i] = descendantStep;
290: continue;
291: }
292: fNoMatchDepth[i]++;
293: if (DEBUG_MATCH) {
294: System.out.println(toString()
295: + " [CHILD] after NO MATCH");
296: }
297: continue;
298: }
299: fCurrentStep[i]++;
300: if (DEBUG_MATCH) {
301: System.out.println(toString()
302: + " [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()
320: + " [ATTRIBUTE] before");
321: }
322: int attrCount = attributes.getLength();
323: if (attrCount > 0) {
324: XPath.NodeTest nodeTest = steps[fCurrentStep[i]].nodeTest;
325:
326: for (int aIndex = 0; aIndex < attrCount; aIndex++) {
327: attributes.getName(aIndex, fQName);
328: if (matches(nodeTest, fQName)) {
329: fCurrentStep[i]++;
330: if (fCurrentStep[i] == steps.length) {
331: fMatched[i] = MATCHED_ATTRIBUTE;
332: int j = 0;
333: for (; j < i
334: && ((fMatched[j] & MATCHED) != MATCHED); j++)
335: ;
336: if (j == i) {
337: AttributePSVI attrPSVI = (AttributePSVI) attributes
338: .getAugmentations(aIndex)
339: .getItem(
340: Constants.ATTRIBUTE_PSVI);
341: fMatchedString = attrPSVI
342: .getActualNormalizedValue();
343: matched(
344: fMatchedString,
345: attrPSVI
346: .getActualNormalizedValueType(),
347: attrPSVI
348: .getItemValueTypes(),
349: false);
350: }
351: }
352: break;
353: }
354: }
355: }
356: if ((fMatched[i] & MATCHED) != MATCHED) {
357: if (fCurrentStep[i] > descendantStep) {
358: fCurrentStep[i] = descendantStep;
359: continue;
360: }
361: fNoMatchDepth[i]++;
362: if (DEBUG_MATCH) {
363: System.out.println(toString()
364: + " [ATTRIBUTE] after");
365: }
366: continue;
367: }
368: if (DEBUG_MATCH) {
369: System.out.println(toString()
370: + " [ATTRIBUTE] after MATCHED!");
371: }
372: }
373: }
374:
375: }
376:
377: // startElement(QName,XMLAttrList,int)
378:
379: /**
380: * @param element
381: * name of the element.
382: * @param type
383: * content type of this element. IOW, the XML schema type
384: * of the <tt>value</tt>. Note that this may not be the type declared
385: * in the element declaration, but it is "the actual type". For example,
386: * if the XML is <foo xsi:type="xs:string">aaa</foo>, this
387: * parameter will be "xs:string".
388: * @param nillable - nillable
389: * true if the element declaration is nillable.
390: * @param value - actual value
391: * the typed value of the content of this element.
392: */
393: public void endElement(QName element, XSTypeDefinition type,
394: boolean nillable, Object value, short valueType,
395: ShortList itemValueType) {
396: if (DEBUG_METHODS2) {
397: System.out.println(toString() + "#endElement("
398: + "element={" + element + "}," + ")");
399: }
400: for (int i = 0; i < fLocationPaths.length; i++) {
401: // go back a step
402: fCurrentStep[i] = fStepIndexes[i].pop();
403:
404: // don't do anything, if not matching
405: if (fNoMatchDepth[i] > 0) {
406: fNoMatchDepth[i]--;
407: }
408:
409: // signal match, if appropriate
410: else {
411: int j = 0;
412: for (; j < i && ((fMatched[j] & MATCHED) != MATCHED); j++)
413: ;
414: if ((j < i) || (fMatched[j] == 0)) {
415: continue;
416: }
417: if ((fMatched[j] & MATCHED_ATTRIBUTE) == MATCHED_ATTRIBUTE) {
418: fMatched[i] = 0;
419: continue;
420: }
421: // only certain kinds of matchers actually
422: // match element content. This permits
423: // them a way to override this to do nothing
424: // and hopefully save a few operations.
425: handleContent(type, nillable, value, valueType,
426: itemValueType);
427: fMatched[i] = 0;
428: }
429:
430: if (DEBUG_STACK) {
431: System.out.println(toString() + ": " + fStepIndexes[i]);
432: }
433: }
434:
435: } // endElement(QName)
436:
437: //
438: // Object methods
439: //
440:
441: /** Returns a string representation of this object. */
442: public String toString() {
443: /***
444: return fLocationPath.toString();
445: /***/
446: StringBuffer str = new StringBuffer();
447: String s = super .toString();
448: int index2 = s.lastIndexOf('.');
449: if (index2 != -1) {
450: s = s.substring(index2 + 1);
451: }
452: str.append(s);
453: for (int i = 0; i < fLocationPaths.length; i++) {
454: str.append('[');
455: XPath.Step[] steps = fLocationPaths[i].steps;
456: for (int j = 0; j < steps.length; j++) {
457: if (j == fCurrentStep[i]) {
458: str.append('^');
459: }
460: str.append(steps[j].toString());
461: if (j < steps.length - 1) {
462: str.append('/');
463: }
464: }
465: if (fCurrentStep[i] == steps.length) {
466: str.append('^');
467: }
468: str.append(']');
469: str.append(',');
470: }
471: return str.toString();
472: } // toString():String
473:
474: //
475: // Private methods
476: //
477:
478: /** Normalizes text. */
479: private String normalize(String s) {
480: StringBuffer str = new StringBuffer();
481: int length = s.length();
482: for (int i = 0; i < length; i++) {
483: char c = s.charAt(i);
484: switch (c) {
485: case '\n': {
486: str.append("\\n");
487: break;
488: }
489: default: {
490: str.append(c);
491: }
492: }
493: }
494: return str.toString();
495: } // normalize(String):String
496:
497: /** Returns true if the given QName matches the node test. **/
498: private static boolean matches(XPath.NodeTest nodeTest, QName value) {
499: if (nodeTest.type == XPath.NodeTest.QNAME) {
500: return nodeTest.name.equals(value);
501: }
502: if (nodeTest.type == XPath.NodeTest.NAMESPACE) {
503: return nodeTest.name.uri == value.uri;
504: }
505: // XPath.NodeTest.WILDCARD
506: return true;
507: } // matches(XPath.NodeTest,QName):boolean
508:
509: //
510: // MAIN
511: //
512:
513: // NOTE: The main of this class is here for debugging purposes.
514: // However, javac (JDK 1.1.8) has an internal compiler
515: // error when compiling. Jikes has no problem, though.
516: //
517: // If you want to use this main, use Jikes to compile but
518: // *never* check in this code to CVS without commenting it
519: // out. -Ac
520:
521: /** Main program. */
522: /***
523: public static void main(String[] argv) throws XNIException {
524:
525: if (DEBUG_ANY) {
526: for (int i = 0; i < argv.length; i++) {
527: final String expr = argv[i];
528: final XPath xpath = new XPath(expr, symbols, null);
529: final XPathMatcher matcher = new XPathMatcher(xpath, true);
530: org.apache.xerces.parsers.SAXParser parser =
531: new org.apache.xerces.parsers.SAXParser(symbols) {
532: public void startDocument() throws XNIException {
533: matcher.startDocumentFragment(symbols, null);
534: }
535: public void startElement(QName element, XMLAttrList attributes, int handle) throws XNIException {
536: matcher.startElement(element, attributes, handle);
537: }
538: public void characters(char[] ch, int offset, int length) throws XNIException {
539: matcher.characters(ch, offset, length);
540: }
541: public void endElement(QName element) throws XNIException {
542: matcher.endElement(element);
543: }
544: public void endDocument() throws XNIException {
545: matcher.endDocumentFragment();
546: }
547: };
548: System.out.println("#### argv["+i+"]: \""+expr+"\" -> \""+xpath.toString()+'"');
549: final String uri = argv[++i];
550: System.out.println("#### argv["+i+"]: "+uri);
551: parser.parse(uri);
552: }
553: }
554:
555: } // main(String[])
556: /***/
557:
558: } // class XPathMatcher
|