001: /*
002: * The Apache Software License, Version 1.1
003: *
004: *
005: * Copyright (c) 2001 The Apache Software Foundation.
006: * All rights reserved.
007: *
008: * Redistribution and use in source and binary forms, with or without
009: * modification, are permitted provided that the following conditions
010: * are met:
011: *
012: * 1. Redistributions of source code must retain the above copyright
013: * notice, this list of conditions and the following disclaimer.
014: *
015: * 2. Redistributions in binary form must reproduce the above copyright
016: * notice, this list of conditions and the following disclaimer in
017: * the documentation and/or other materials provided with the
018: * distribution.
019: *
020: * 3. The end-user documentation included with the redistribution,
021: * if any, must include the following acknowledgment:
022: * "This product includes software developed by the
023: * Apache Software Foundation (http://www.apache.org/)."
024: * Alternately, this acknowledgment may appear in the software itself,
025: * if and wherever such third-party acknowledgments normally appear.
026: *
027: * 4. The names "Xerces" and "Apache Software Foundation" must
028: * not be used to endorse or promote products derived from this
029: * software without prior written permission. For written
030: * permission, please contact apache@apache.org.
031: *
032: * 5. Products derived from this software may not be called "Apache",
033: * nor may "Apache" appear in their name, without prior written
034: * permission of the Apache Software Foundation.
035: *
036: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
040: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: * SUCH DAMAGE.
048: * ====================================================================
049: *
050: * This software consists of voluntary contributions made by many
051: * individuals on behalf of the Apache Software Foundation and was
052: * originally based on software copyright (c) 1999, International
053: * Business Machines, Inc., http://www.apache.org. For more
054: * information on the Apache Software Foundation, please see
055: * <http://www.apache.org/>.
056: */
057:
058: package org.apache.xerces.validators.schema.identity;
059:
060: import org.apache.xerces.framework.XMLAttrList;
061: import org.apache.xerces.validators.common.XMLAttributeDecl;
062: import org.apache.xerces.validators.common.XMLElementDecl;
063: import org.apache.xerces.validators.schema.SchemaGrammar;
064: import org.apache.xerces.validators.schema.SchemaSymbols;
065: import org.apache.xerces.validators.datatype.DatatypeValidator;
066:
067: import org.apache.xerces.utils.IntStack;
068: import org.apache.xerces.utils.QName;
069: import org.apache.xerces.utils.NamespacesScope;
070: import org.apache.xerces.utils.StringPool;
071:
072: /***
073: import org.xml.sax.SAXException;
074: import org.xml.sax.SAXNotRecognizedException;
075: import org.xml.sax.SAXNotSupportedException;
076: /***/
077:
078: /**
079: * XPath matcher.
080: *
081: * @author Andy Clark, IBM
082: *
083: * @version $Id: XPathMatcher.java,v 1.14 2001/05/15 22:18:12 neilg Exp $
084: */
085: public class XPathMatcher {
086:
087: //
088: // Constants
089: //
090:
091: // debugging
092:
093: /** Compile to true to debug everything. */
094: protected static final boolean DEBUG_ALL = false;
095:
096: /** Compile to true to debug method callbacks. */
097: protected static final boolean DEBUG_METHODS = false || DEBUG_ALL;
098:
099: /** Compile to true to debug important method callbacks. */
100: protected static final boolean DEBUG_METHODS2 = false
101: || DEBUG_METHODS || DEBUG_ALL;
102:
103: /** Compile to true to debug the <em>really</em> important methods. */
104: protected static final boolean DEBUG_METHODS3 = false
105: || DEBUG_METHODS || DEBUG_ALL;
106:
107: /** Compile to true to debug match. */
108: protected static final boolean DEBUG_MATCH = false || DEBUG_ALL;
109:
110: /** Compile to true to debug step index stack. */
111: protected static final boolean DEBUG_STACK = false || DEBUG_ALL;
112:
113: /** Don't touch this value unless you add more debug constants. */
114: protected static final boolean DEBUG_ANY = DEBUG_METHODS
115: || DEBUG_METHODS2 || DEBUG_METHODS3 || DEBUG_MATCH
116: || DEBUG_STACK;
117:
118: //
119: // Data
120: //
121:
122: /** XPath location path. */
123: private XPath.LocationPath[] fLocationPaths;
124:
125: /** Application preference to buffer content or not. */
126: private boolean fShouldBufferContent;
127:
128: /** True if should buffer character content <em>at this time</em>. */
129: private boolean fBufferContent;
130:
131: /** Buffer to hold match text. */
132: private StringBuffer fMatchedBuffer = new StringBuffer();
133:
134: /** True if XPath has been matched. */
135: private boolean[] fMatched;
136:
137: /** The matching string. */
138: private String fMatchedString;
139:
140: /** Integer stack of step indexes. */
141: private IntStack[] fStepIndexes;
142:
143: /** Current step. */
144: private int[] fCurrentStep;
145:
146: /**
147: * No match depth. The value of this field will be zero while
148: * matching is successful for the given xpath expression.
149: */
150: private int[] fNoMatchDepth;
151:
152: // Xerces 1.x framework
153:
154: /** String pool. */
155: protected StringPool fStringPool;
156:
157: /** Namespace scope. */
158: protected NamespacesScope fNamespacesScope;
159:
160: // the Identity constraint we're the matcher for. Only
161: // used for selectors!
162: protected IdentityConstraint fIDConstraint;
163:
164: //
165: // Constructors
166: //
167:
168: /**
169: * Constructs an XPath matcher that implements a document fragment
170: * handler.
171: *
172: * @param xpath The xpath.
173: */
174: public XPathMatcher(XPath xpath) {
175: this (xpath, false, null);
176: } // <init>(XPath)
177:
178: /**
179: * Constructs an XPath matcher that implements a document fragment
180: * handler.
181: *
182: * @param xpath The xpath.
183: * @param shouldBufferContent True if the matcher should buffer the
184: * matched content.
185: * @param idConstraint: the identity constraint we're matching for;
186: * null unless it's a Selector.
187: */
188: public XPathMatcher(XPath xpath, boolean shouldBufferContent,
189: IdentityConstraint idConstraint) {
190: fLocationPaths = xpath.getLocationPaths();
191: fShouldBufferContent = shouldBufferContent;
192: fIDConstraint = idConstraint;
193: fStepIndexes = new IntStack[fLocationPaths.length];
194: for (int i = 0; i < fStepIndexes.length; i++)
195: fStepIndexes[i] = new IntStack();
196: fCurrentStep = new int[fLocationPaths.length];
197: fNoMatchDepth = new int[fLocationPaths.length];
198: fMatched = new boolean[fLocationPaths.length];
199: if (DEBUG_METHODS) {
200: System.out.println(toString() + "#<init>()");
201: }
202: } // <init>(XPath,boolean)
203:
204: //
205: // Public methods
206: //
207:
208: /** Returns true if XPath has been matched. */
209: public boolean isMatched() {
210: // xpath has been matched if any one of the members of the union have matched.
211: for (int i = 0; i < fLocationPaths.length; i++)
212: if (fMatched[i])
213: return true;
214: return false;
215: } // isMatched():boolean
216:
217: // returns whether this XPathMatcher was matching a Selector
218: public boolean getIsSelector() {
219: return (fIDConstraint == null);
220: } // end getIsSelector():boolean
221:
222: // returns the ID constraint
223: public IdentityConstraint getIDConstraint() {
224: return fIDConstraint;
225: } // end getIDConstraint():IdentityConstraint
226:
227: /** Returns the matched string. */
228: public String getMatchedString() {
229: return fMatchedString;
230: } // getMatchedString():String
231:
232: //
233: // Protected methods
234: //
235:
236: /**
237: * This method is called when the XPath handler matches the
238: * XPath expression. Subclasses can override this method to
239: * provide default handling upon a match.
240: */
241: protected void matched(String content, DatatypeValidator val,
242: boolean isNil) throws Exception {
243: if (DEBUG_METHODS3) {
244: System.out.println(toString() + "#matched(\""
245: + normalize(content) + "\")");
246: }
247: } // matched(String content, DatatypeValidator val)
248:
249: //
250: // XMLDocumentFragmentHandler methods
251: //
252:
253: /**
254: * The start of the document fragment.
255: *
256: * @param namespaceScope The namespace scope in effect at the
257: * start of this document fragment.
258: * @param grammar: the schema grammar we're validating against.
259: *
260: * @throws SAXException Thrown by handler to signal an error.
261: */
262: public void startDocumentFragment(StringPool stringPool)
263: throws Exception {
264: if (DEBUG_METHODS) {
265: System.out.println(toString() + "#startDocumentFragment("
266: + "stringPool=" + stringPool + ',' + ")");
267: }
268:
269: // reset state
270: clear();
271: for (int i = 0; i < fLocationPaths.length; i++) {
272: fStepIndexes[i].clear();
273: fCurrentStep[i] = 0;
274: fNoMatchDepth[i] = 0;
275: fMatched[i] = false;
276: }
277:
278: // keep values
279: fStringPool = stringPool;
280:
281: } // startDocumentFragment(StringPool,SchemaGrammar)
282:
283: /**
284: * The start of an element. If the document specifies the start element
285: * by using an empty tag, then the startElement method will immediately
286: * be followed by the endElement method, with no intervening methods.
287: *
288: * @param element The name of the element.
289: * @param attributes The element attributes.
290: * @param eIndex: the element index of the current element
291: * @param grammar: the currently-active Schema Grammar
292: *
293: * @throws SAXException Thrown by handler to signal an error.
294: */
295: public void startElement(QName element, XMLAttrList attributes,
296: int handle, int eIndex, SchemaGrammar grammar)
297: throws Exception {
298: if (DEBUG_METHODS2) {
299: System.out.println(toString() + "#startElement("
300: + "element={" + "prefix="
301: + fStringPool.toString(element.prefix) + ','
302: + "localpart="
303: + fStringPool.toString(element.localpart) + ','
304: + "rawname="
305: + fStringPool.toString(element.rawname) + ','
306: + "uri=" + fStringPool.toString(element.uri) + "},"
307: + "attributes=..." + //attributes+
308: ")");
309: }
310:
311: for (int i = 0; i < fLocationPaths.length; i++) {
312: // push context
313: int startStep = fCurrentStep[i];
314: fStepIndexes[i].push(startStep);
315:
316: // try next xpath, if not matching
317: if (fMatched[i] || fNoMatchDepth[i] > 0) {
318: fNoMatchDepth[i]++;
319: continue;
320: }
321:
322: if (DEBUG_STACK) {
323: System.out.println(toString() + ": " + fStepIndexes[i]);
324: }
325:
326: // consume self::node() steps
327: XPath.Step[] steps = fLocationPaths[i].steps;
328: while (fCurrentStep[i] < steps.length
329: && steps[fCurrentStep[i]].axis.type == XPath.Axis.SELF) {
330: if (DEBUG_MATCH) {
331: XPath.Step step = steps[fCurrentStep[i]];
332: System.out.println(toString() + " [SELF] MATCHED!");
333: }
334: fCurrentStep[i]++;
335: }
336: if (fCurrentStep[i] == steps.length) {
337: if (DEBUG_MATCH) {
338: System.out.println(toString() + " XPath MATCHED!");
339: }
340: fMatched[i] = true;
341: int j = 0;
342: for (; j < i && !fMatched[j]; j++)
343: ;
344: if (j == i)
345: fBufferContent = fShouldBufferContent;
346: continue;
347: }
348:
349: // now if the current step is a descendant step, we let the next
350: // step do its thing; if it fails, we reset ourselves
351: // to look at this step for next time we're called.
352: // so first consume all descendants:
353: int descendantStep = fCurrentStep[i];
354: while (fCurrentStep[i] < steps.length
355: && steps[fCurrentStep[i]].axis.type == XPath.Axis.DESCENDANT) {
356: if (DEBUG_MATCH) {
357: XPath.Step step = steps[fCurrentStep[i]];
358: System.out.println(toString()
359: + " [DESCENDANT] MATCHED!");
360: }
361: fCurrentStep[i]++;
362: }
363: if (fCurrentStep[i] == steps.length) {
364: if (DEBUG_MATCH) {
365: System.out.println(toString()
366: + " XPath DIDN'T MATCH!");
367: }
368: fNoMatchDepth[i]++;
369: if (DEBUG_MATCH) {
370: System.out.println(toString()
371: + " [CHILD] after NO MATCH");
372: }
373: continue;
374: }
375:
376: // match child::... step, if haven't consumed any self::node()
377: if ((fCurrentStep[i] == startStep || fCurrentStep[i] > descendantStep)
378: && steps[fCurrentStep[i]].axis.type == XPath.Axis.CHILD) {
379: XPath.Step step = steps[fCurrentStep[i]];
380: XPath.NodeTest nodeTest = step.nodeTest;
381: if (DEBUG_MATCH) {
382: System.out.println(toString() + " [CHILD] before");
383: }
384: if (nodeTest.type == XPath.NodeTest.QNAME) {
385: if (!nodeTest.name.equals(element)) {
386: if (fCurrentStep[i] > descendantStep) {
387: fCurrentStep[i] = descendantStep;
388: continue;
389: }
390: fNoMatchDepth[i]++;
391: if (DEBUG_MATCH) {
392: System.out.println(toString()
393: + " [CHILD] after NO MATCH");
394: }
395: continue;
396: }
397: }
398: fCurrentStep[i]++;
399: if (DEBUG_MATCH) {
400: System.out.println(toString()
401: + " [CHILD] after MATCHED!");
402: }
403: }
404: if (fCurrentStep[i] == steps.length) {
405: fMatched[i] = true;
406: int j = 0;
407: for (; j < i && !fMatched[j]; j++)
408: ;
409: if (j == i)
410: fBufferContent = fShouldBufferContent;
411: continue;
412: }
413:
414: // match attribute::... step
415: if (fCurrentStep[i] < steps.length
416: && steps[fCurrentStep[i]].axis.type == XPath.Axis.ATTRIBUTE) {
417: if (DEBUG_MATCH) {
418: System.out.println(toString()
419: + " [ATTRIBUTE] before");
420: }
421: int aindex = attributes.getFirstAttr(handle);
422: if (aindex != -1) {
423: XPath.NodeTest nodeTest = steps[fCurrentStep[i]].nodeTest;
424: QName aname = new QName(); // REVISIT: cache this
425: while (aindex != -1) {
426: int aprefix = attributes.getAttrPrefix(aindex);
427: int alocalpart = attributes
428: .getAttrLocalpart(aindex);
429: int arawname = attributes.getAttrName(aindex);
430: int auri = attributes.getAttrURI(aindex);
431: aname.setValues(aprefix, alocalpart, arawname,
432: auri);
433: if (nodeTest.type != XPath.NodeTest.QNAME
434: || nodeTest.name.equals(aname)) {
435: fCurrentStep[i]++;
436: if (fCurrentStep[i] == steps.length) {
437: fMatched[i] = true;
438: int j = 0;
439: for (; j < i && !fMatched[j]; j++)
440: ;
441: if (j == i) {
442: int avalue = attributes
443: .getAttValue(aindex);
444: fMatchedString = fStringPool
445: .toString(avalue);
446: // now, we have to go on the hunt for
447: // datatype validator; not an easy or pleasant task...
448: int attIndex = grammar
449: .getAttributeDeclIndex(
450: eIndex, aname);
451: XMLAttributeDecl tempAttDecl = new XMLAttributeDecl();
452: grammar.getAttributeDecl(attIndex,
453: tempAttDecl);
454: DatatypeValidator aValidator = tempAttDecl.datatypeValidator;
455: matched(fMatchedString, aValidator,
456: false);
457: }
458: }
459: break;
460: }
461: aindex = attributes.getNextAttr(aindex);
462: }
463: }
464: if (!fMatched[i]) {
465: if (fCurrentStep[i] > descendantStep) {
466: fCurrentStep[i] = descendantStep;
467: continue;
468: }
469: fNoMatchDepth[i]++;
470: if (DEBUG_MATCH) {
471: System.out.println(toString()
472: + " [ATTRIBUTE] after");
473: }
474: continue;
475: }
476: if (DEBUG_MATCH) {
477: System.out.println(toString()
478: + " [ATTRIBUTE] after MATCHED!");
479: }
480: }
481: }
482:
483: } // startElement(QName,XMLAttrList,int)
484:
485: /** Character content. */
486: public void characters(char[] ch, int offset, int length)
487: throws Exception {
488: if (DEBUG_METHODS) {
489: System.out.println(toString() + "#characters(" + "text="
490: + normalize(new String(ch, offset, length)) + ")");
491: }
492:
493: // collect match content
494: // so long as one of our paths is matching, store the content
495: for (int i = 0; i < fLocationPaths.length; i++)
496: if (fBufferContent && fNoMatchDepth[i] == 0) {
497: if (!DEBUG_METHODS && DEBUG_METHODS2) {
498: System.out.println(toString() + "#characters("
499: + "text="
500: + normalize(new String(ch, offset, length))
501: + ")");
502: }
503: fMatchedBuffer.append(ch, offset, length);
504: break;
505: }
506:
507: } // characters(char[],int,int)
508:
509: /**
510: * The end of an element.
511: *
512: * @param element The name of the element.
513: * @param eIndex: the elementDeclIndex of the current element;
514: * needed so that we can look up its datatypeValidator.
515: *
516: * @throws SAXException Thrown by handler to signal an error.
517: */
518: public void endElement(QName element, int eIndex,
519: SchemaGrammar grammar) throws Exception {
520: if (DEBUG_METHODS2) {
521: System.out.println(toString() + "#endElement("
522: + "element={" + "prefix="
523: + fStringPool.toString(element.prefix) + ','
524: + "localpart="
525: + fStringPool.toString(element.localpart) + ','
526: + "rawname="
527: + fStringPool.toString(element.rawname) + ','
528: + "uri=" + fStringPool.toString(element.uri)
529: + "ID constraint=" + fIDConstraint + "})");
530: }
531:
532: for (int i = 0; i < fLocationPaths.length; i++) {
533: // don't do anything, if not matching
534: if (fNoMatchDepth[i] > 0) {
535: fNoMatchDepth[i]--;
536: }
537:
538: // signal match, if appropriate
539: else {
540: int j = 0;
541: for (; j < i && !fMatched[j]; j++)
542: ;
543: if (j < i)
544: continue;
545: if (fBufferContent) {
546: fBufferContent = false;
547: fMatchedString = fMatchedBuffer.toString();
548: XMLElementDecl temp = new XMLElementDecl();
549: grammar.getElementDecl(eIndex, temp);
550: DatatypeValidator val = temp.datatypeValidator;
551: if (temp != null) {
552: matched(
553: fMatchedString,
554: val,
555: (grammar
556: .getElementDeclMiscFlags(eIndex) & SchemaSymbols.NILLABLE) != 0);
557: } else
558: matched(fMatchedString, null, false);
559: }
560: clear();
561: }
562:
563: // go back a step
564: fCurrentStep[i] = fStepIndexes[i].pop();
565:
566: if (DEBUG_STACK) {
567: System.out.println(toString() + ": " + fStepIndexes[i]);
568: }
569: }
570:
571: } // endElement(QName)
572:
573: /**
574: * The end of the document fragment.
575: *
576: * @throws SAXException Thrown by handler to signal an error.
577: */
578: public void endDocumentFragment() throws Exception {
579: if (DEBUG_METHODS) {
580: System.out.println(toString() + "#endDocumentFragment()");
581: }
582: clear();
583: } // endDocumentFragment()
584:
585: //
586: // Object methods
587: //
588:
589: /** Returns a string representation of this object. */
590: public String toString() {
591: /***
592: return fLocationPath.toString();
593: /***/
594: StringBuffer str = new StringBuffer();
595: String s = super .toString();
596: int index2 = s.lastIndexOf('.');
597: if (index2 != -1) {
598: s = s.substring(index2 + 1);
599: }
600: str.append(s);
601: for (int i = 0; i < fLocationPaths.length; i++) {
602: str.append('[');
603: XPath.Step[] steps = fLocationPaths[i].steps;
604: for (int j = 0; j < steps.length; j++) {
605: if (j == fCurrentStep[i]) {
606: str.append('^');
607: }
608: str.append(steps[i].toString());
609: if (j < steps.length - 1) {
610: str.append('/');
611: }
612: }
613: if (fCurrentStep[i] == steps.length) {
614: str.append('^');
615: }
616: str.append(']');
617: str.append(',');
618: }
619: return str.toString();
620: } // toString():String
621:
622: //
623: // Private methods
624: //
625:
626: /** Clears the match values. */
627: private void clear() {
628: fBufferContent = false;
629: fMatchedBuffer.setLength(0);
630: fMatchedString = null;
631: for (int i = 0; i < fLocationPaths.length; i++)
632: fMatched[i] = false;
633: } // clear()
634:
635: /** Normalizes text. */
636: private String normalize(String s) {
637: StringBuffer str = new StringBuffer();
638: int length = s.length();
639: for (int i = 0; i < length; i++) {
640: char c = s.charAt(i);
641: switch (c) {
642: case '\n': {
643: str.append("\\n");
644: break;
645: }
646: default: {
647: str.append(c);
648: }
649: }
650: }
651: return str.toString();
652: } // normalize(String):String
653:
654: //
655: // MAIN
656: //
657:
658: // NOTE: The main of this class is here for debugging purposes.
659: // However, javac (JDK 1.1.8) has an internal compiler
660: // error when compiling. Jikes has no problem, though.
661: //
662: // If you want to use this main, use Jikes to compile but
663: // *never* check in this code to CVS without commenting it
664: // out. -Ac
665:
666: /** Main program. */
667: /***
668: public static void main(String[] argv) throws Exception {
669:
670: if (DEBUG_ANY) {
671: for (int i = 0; i < argv.length; i++) {
672: final String expr = argv[i];
673: final StringPool symbols = new StringPool();
674: final XPath xpath = new XPath(expr, symbols, null);
675: final XPathMatcher matcher = new XPathMatcher(xpath, true);
676: org.apache.xerces.parsers.SAXParser parser =
677: new org.apache.xerces.parsers.SAXParser(symbols) {
678: public void startDocument() throws Exception {
679: matcher.startDocumentFragment(symbols, null);
680: }
681: public void startElement(QName element, XMLAttrList attributes, int handle) throws Exception {
682: matcher.startElement(element, attributes, handle);
683: }
684: public void characters(char[] ch, int offset, int length) throws Exception {
685: matcher.characters(ch, offset, length);
686: }
687: public void endElement(QName element) throws Exception {
688: matcher.endElement(element);
689: }
690: public void endDocument() throws Exception {
691: matcher.endDocumentFragment();
692: }
693: };
694: System.out.println("#### argv["+i+"]: \""+expr+"\" -> \""+xpath.toString()+'"');
695: final String uri = argv[++i];
696: System.out.println("#### argv["+i+"]: "+uri);
697: parser.parse(uri);
698: }
699: }
700:
701: } // main(String[])
702: /***/
703:
704: } // class XPathMatcher
|