001: /*
002: * Copyright 1999-2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: /*
017: * $Id: XNodeSet.java,v 1.30 2004/12/15 17:35:55 jycli Exp $
018: */
019: package org.apache.xpath.objects;
020:
021: import org.apache.xml.dtm.DTM;
022: import org.apache.xml.dtm.DTMIterator;
023: import org.apache.xml.dtm.DTMManager;
024: import org.apache.xml.utils.XMLString;
025: import org.apache.xpath.NodeSetDTM;
026: import org.apache.xpath.axes.NodeSequence;
027:
028: import org.w3c.dom.NodeList;
029: import org.w3c.dom.traversal.NodeIterator;
030:
031: /**
032: * This class represents an XPath nodeset object, and is capable of
033: * converting the nodeset to other types, such as a string.
034: * @xsl.usage general
035: */
036: public class XNodeSet extends NodeSequence {
037: static final long serialVersionUID = 1916026368035639667L;
038:
039: /**
040: * Default constructor for derived objects.
041: */
042: protected XNodeSet() {
043: }
044:
045: /**
046: * Construct a XNodeSet object.
047: *
048: * @param val Value of the XNodeSet object
049: */
050: public XNodeSet(DTMIterator val) {
051: super ();
052: if (val instanceof XNodeSet) {
053: setIter(((XNodeSet) val).m_iter);
054: m_dtmMgr = ((XNodeSet) val).m_dtmMgr;
055: m_last = ((XNodeSet) val).m_last;
056: if (!((XNodeSet) val).hasCache())
057: ((XNodeSet) val).setShouldCacheNodes(true);
058: m_obj = ((XNodeSet) val).m_obj;
059: } else
060: setIter(val);
061: }
062:
063: /**
064: * Construct a XNodeSet object.
065: *
066: * @param val Value of the XNodeSet object
067: */
068: public XNodeSet(XNodeSet val) {
069: super ();
070: setIter(val.m_iter);
071: m_dtmMgr = val.m_dtmMgr;
072: m_last = val.m_last;
073: if (!val.hasCache())
074: val.setShouldCacheNodes(true);
075: m_obj = val.m_obj;
076: }
077:
078: /**
079: * Construct an empty XNodeSet object. This is used to create a mutable
080: * nodeset to which random nodes may be added.
081: */
082: public XNodeSet(DTMManager dtmMgr) {
083: this (DTM.NULL, dtmMgr);
084: }
085:
086: /**
087: * Construct a XNodeSet object for one node.
088: *
089: * @param n Node to add to the new XNodeSet object
090: */
091: public XNodeSet(int n, DTMManager dtmMgr) {
092:
093: super (new NodeSetDTM(dtmMgr));
094: m_dtmMgr = dtmMgr;
095:
096: if (DTM.NULL != n) {
097: ((NodeSetDTM) m_obj).addNode(n);
098: m_last = 1;
099: } else
100: m_last = 0;
101: }
102:
103: /**
104: * Tell that this is a CLASS_NODESET.
105: *
106: * @return type CLASS_NODESET
107: */
108: public int getType() {
109: return CLASS_NODESET;
110: }
111:
112: /**
113: * Given a request type, return the equivalent string.
114: * For diagnostic purposes.
115: *
116: * @return type string "#NODESET"
117: */
118: public String getTypeString() {
119: return "#NODESET";
120: }
121:
122: /**
123: * Get numeric value of the string conversion from a single node.
124: *
125: * @param n Node to convert
126: *
127: * @return numeric value of the string conversion from a single node.
128: */
129: public double getNumberFromNode(int n) {
130: XMLString xstr = m_dtmMgr.getDTM(n).getStringValue(n);
131: return xstr.toDouble();
132: }
133:
134: /**
135: * Cast result object to a number.
136: *
137: * @return numeric value of the string conversion from the
138: * next node in the NodeSetDTM, or NAN if no node was found
139: */
140: public double num() {
141:
142: int node = item(0);
143: return (node != DTM.NULL) ? getNumberFromNode(node)
144: : Double.NaN;
145: }
146:
147: /**
148: * Cast result object to a number, but allow side effects, such as the
149: * incrementing of an iterator.
150: *
151: * @return numeric value of the string conversion from the
152: * next node in the NodeSetDTM, or NAN if no node was found
153: */
154: public double numWithSideEffects() {
155: int node = nextNode();
156:
157: return (node != DTM.NULL) ? getNumberFromNode(node)
158: : Double.NaN;
159: }
160:
161: /**
162: * Cast result object to a boolean.
163: *
164: * @return True if there is a next node in the nodeset
165: */
166: public boolean bool() {
167: return (item(0) != DTM.NULL);
168: }
169:
170: /**
171: * Cast result object to a boolean, but allow side effects, such as the
172: * incrementing of an iterator.
173: *
174: * @return True if there is a next node in the nodeset
175: */
176: public boolean boolWithSideEffects() {
177: return (nextNode() != DTM.NULL);
178: }
179:
180: /**
181: * Get the string conversion from a single node.
182: *
183: * @param n Node to convert
184: *
185: * @return the string conversion from a single node.
186: */
187: public XMLString getStringFromNode(int n) {
188: // %OPT%
189: // I guess we'll have to get a static instance of the DTM manager...
190: if (DTM.NULL != n) {
191: return m_dtmMgr.getDTM(n).getStringValue(n);
192: } else {
193: return org.apache.xpath.objects.XString.EMPTYSTRING;
194: }
195: }
196:
197: /**
198: * Directly call the
199: * characters method on the passed ContentHandler for the
200: * string-value. Multiple calls to the
201: * ContentHandler's characters methods may well occur for a single call to
202: * this method.
203: *
204: * @param ch A non-null reference to a ContentHandler.
205: *
206: * @throws org.xml.sax.SAXException
207: */
208: public void dispatchCharactersEvents(org.xml.sax.ContentHandler ch)
209: throws org.xml.sax.SAXException {
210: int node = item(0);
211:
212: if (node != DTM.NULL) {
213: m_dtmMgr.getDTM(node).dispatchCharactersEvents(node, ch,
214: false);
215: }
216:
217: }
218:
219: /**
220: * Cast result object to an XMLString.
221: *
222: * @return The document fragment node data or the empty string.
223: */
224: public XMLString xstr() {
225: int node = item(0);
226: return (node != DTM.NULL) ? getStringFromNode(node)
227: : XString.EMPTYSTRING;
228: }
229:
230: /**
231: * Cast result object to a string.
232: *
233: * @return The string this wraps or the empty string if null
234: */
235: public void appendToFsb(org.apache.xml.utils.FastStringBuffer fsb) {
236: XString xstring = (XString) xstr();
237: xstring.appendToFsb(fsb);
238: }
239:
240: /**
241: * Cast result object to a string.
242: *
243: * @return the string conversion from the next node in the nodeset
244: * or "" if there is no next node
245: */
246: public String str() {
247: int node = item(0);
248: return (node != DTM.NULL) ? getStringFromNode(node).toString()
249: : "";
250: }
251:
252: /**
253: * Return a java object that's closest to the representation
254: * that should be handed to an extension.
255: *
256: * @return The object that this class wraps
257: */
258: public Object object() {
259: if (null == m_obj)
260: return this ;
261: else
262: return m_obj;
263: }
264:
265: // %REVIEW%
266: // hmmm...
267: // /**
268: // * Cast result object to a result tree fragment.
269: // *
270: // * @param support The XPath context to use for the conversion
271: // *
272: // * @return the nodeset as a result tree fragment.
273: // */
274: // public DocumentFragment rtree(XPathContext support)
275: // {
276: // DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
277: // DocumentBuilder db = dbf.newDocumentBuilder();
278: // Document myDoc = db.newDocument();
279: //
280: // DocumentFragment docFrag = myDoc.createDocumentFragment();
281: //
282: // DTMIterator nl = iter();
283: // int node;
284: //
285: // while (DTM.NULL != (node = nl.nextNode()))
286: // {
287: // frag.appendChild(node, true, true);
288: // }
289: //
290: // return frag.getDocument();
291: // }
292:
293: /**
294: * Cast result object to a nodelist.
295: *
296: * @return a NodeIterator.
297: *
298: * @throws javax.xml.transform.TransformerException
299: */
300: public NodeIterator nodeset()
301: throws javax.xml.transform.TransformerException {
302: return new org.apache.xml.dtm.ref.DTMNodeIterator(iter());
303: }
304:
305: /**
306: * Cast result object to a nodelist.
307: *
308: * @return a NodeList.
309: *
310: * @throws javax.xml.transform.TransformerException
311: */
312: public NodeList nodelist()
313: throws javax.xml.transform.TransformerException {
314: org.apache.xml.dtm.ref.DTMNodeList nodelist = new org.apache.xml.dtm.ref.DTMNodeList(
315: this );
316: // Creating a DTMNodeList has the side-effect that it will create a clone
317: // XNodeSet with cache and run m_iter to the end. You cannot get any node
318: // from m_iter after this call. As a fix, we call SetVector() on the clone's
319: // cache. See Bugzilla 14406.
320: XNodeSet clone = (XNodeSet) nodelist.getDTMIterator();
321: SetVector(clone.getVector());
322: return nodelist;
323: }
324:
325: // /**
326: // * Return a java object that's closest to the representation
327: // * that should be handed to an extension.
328: // *
329: // * @return The object that this class wraps
330: // */
331: // public Object object()
332: // {
333: // return new org.apache.xml.dtm.ref.DTMNodeList(iter());
334: // }
335:
336: /**
337: * Return the iterator without cloning, etc.
338: */
339: public DTMIterator iterRaw() {
340: return this ;
341: }
342:
343: public void release(DTMIterator iter) {
344: }
345:
346: /**
347: * Cast result object to a nodelist.
348: *
349: * @return The nodeset as a nodelist
350: */
351: public DTMIterator iter() {
352: try {
353: if (hasCache())
354: return cloneWithReset();
355: else
356: return this ; // don't bother to clone... won't do any good!
357: } catch (CloneNotSupportedException cnse) {
358: throw new RuntimeException(cnse.getMessage());
359: }
360: }
361:
362: /**
363: * Get a fresh copy of the object. For use with variables.
364: *
365: * @return A fresh nodelist.
366: */
367: public XObject getFresh() {
368: try {
369: if (hasCache())
370: return (XObject) cloneWithReset();
371: else
372: return this ; // don't bother to clone... won't do any good!
373: } catch (CloneNotSupportedException cnse) {
374: throw new RuntimeException(cnse.getMessage());
375: }
376: }
377:
378: /**
379: * Cast result object to a mutableNodeset.
380: *
381: * @return The nodeset as a mutableNodeset
382: */
383: public NodeSetDTM mutableNodeset() {
384: NodeSetDTM mnl;
385:
386: if (m_obj instanceof NodeSetDTM) {
387: mnl = (NodeSetDTM) m_obj;
388: } else {
389: mnl = new NodeSetDTM(iter());
390: m_obj = mnl;
391: setCurrentPos(0);
392: }
393:
394: return mnl;
395: }
396:
397: /** Less than comparator */
398: static final LessThanComparator S_LT = new LessThanComparator();
399:
400: /** Less than or equal comparator */
401: static final LessThanOrEqualComparator S_LTE = new LessThanOrEqualComparator();
402:
403: /** Greater than comparator */
404: static final GreaterThanComparator S_GT = new GreaterThanComparator();
405:
406: /** Greater than or equal comparator */
407: static final GreaterThanOrEqualComparator S_GTE = new GreaterThanOrEqualComparator();
408:
409: /** Equal comparator */
410: static final EqualComparator S_EQ = new EqualComparator();
411:
412: /** Not equal comparator */
413: static final NotEqualComparator S_NEQ = new NotEqualComparator();
414:
415: /**
416: * Tell if one object is less than the other.
417: *
418: * @param obj2 Object to compare this nodeset to
419: * @param comparator Comparator to use
420: *
421: * @return See the comments below for each object type comparison
422: *
423: * @throws javax.xml.transform.TransformerException
424: */
425: public boolean compare(XObject obj2, Comparator comparator)
426: throws javax.xml.transform.TransformerException {
427:
428: boolean result = false;
429: int type = obj2.getType();
430:
431: if (XObject.CLASS_NODESET == type) {
432: // %OPT% This should be XMLString based instead of string based...
433:
434: // From http://www.w3.org/TR/xpath:
435: // If both objects to be compared are node-sets, then the comparison
436: // will be true if and only if there is a node in the first node-set
437: // and a node in the second node-set such that the result of performing
438: // the comparison on the string-values of the two nodes is true.
439: // Note this little gem from the draft:
440: // NOTE: If $x is bound to a node-set, then $x="foo"
441: // does not mean the same as not($x!="foo"): the former
442: // is true if and only if some node in $x has the string-value
443: // foo; the latter is true if and only if all nodes in $x have
444: // the string-value foo.
445: DTMIterator list1 = iterRaw();
446: DTMIterator list2 = ((XNodeSet) obj2).iterRaw();
447: int node1;
448: java.util.Vector node2Strings = null;
449:
450: while (DTM.NULL != (node1 = list1.nextNode())) {
451: XMLString s1 = getStringFromNode(node1);
452:
453: if (null == node2Strings) {
454: int node2;
455:
456: while (DTM.NULL != (node2 = list2.nextNode())) {
457: XMLString s2 = getStringFromNode(node2);
458:
459: if (comparator.compareStrings(s1, s2)) {
460: result = true;
461:
462: break;
463: }
464:
465: if (null == node2Strings)
466: node2Strings = new java.util.Vector();
467:
468: node2Strings.addElement(s2);
469: }
470: } else {
471: int n = node2Strings.size();
472:
473: for (int i = 0; i < n; i++) {
474: if (comparator.compareStrings(s1,
475: (XMLString) node2Strings.elementAt(i))) {
476: result = true;
477:
478: break;
479: }
480: }
481: }
482: }
483: list1.reset();
484: list2.reset();
485: } else if (XObject.CLASS_BOOLEAN == type) {
486:
487: // From http://www.w3.org/TR/xpath:
488: // If one object to be compared is a node-set and the other is a boolean,
489: // then the comparison will be true if and only if the result of
490: // performing the comparison on the boolean and on the result of
491: // converting the node-set to a boolean using the boolean function
492: // is true.
493: double num1 = bool() ? 1.0 : 0.0;
494: double num2 = obj2.num();
495:
496: result = comparator.compareNumbers(num1, num2);
497: } else if (XObject.CLASS_NUMBER == type) {
498:
499: // From http://www.w3.org/TR/xpath:
500: // If one object to be compared is a node-set and the other is a number,
501: // then the comparison will be true if and only if there is a
502: // node in the node-set such that the result of performing the
503: // comparison on the number to be compared and on the result of
504: // converting the string-value of that node to a number using
505: // the number function is true.
506: DTMIterator list1 = iterRaw();
507: double num2 = obj2.num();
508: int node;
509:
510: while (DTM.NULL != (node = list1.nextNode())) {
511: double num1 = getNumberFromNode(node);
512:
513: if (comparator.compareNumbers(num1, num2)) {
514: result = true;
515:
516: break;
517: }
518: }
519: list1.reset();
520: } else if (XObject.CLASS_RTREEFRAG == type) {
521: XMLString s2 = obj2.xstr();
522: DTMIterator list1 = iterRaw();
523: int node;
524:
525: while (DTM.NULL != (node = list1.nextNode())) {
526: XMLString s1 = getStringFromNode(node);
527:
528: if (comparator.compareStrings(s1, s2)) {
529: result = true;
530:
531: break;
532: }
533: }
534: list1.reset();
535: } else if (XObject.CLASS_STRING == type) {
536:
537: // From http://www.w3.org/TR/xpath:
538: // If one object to be compared is a node-set and the other is a
539: // string, then the comparison will be true if and only if there
540: // is a node in the node-set such that the result of performing
541: // the comparison on the string-value of the node and the other
542: // string is true.
543: XMLString s2 = obj2.xstr();
544: DTMIterator list1 = iterRaw();
545: int node;
546:
547: while (DTM.NULL != (node = list1.nextNode())) {
548: XMLString s1 = getStringFromNode(node);
549: if (comparator.compareStrings(s1, s2)) {
550: result = true;
551:
552: break;
553: }
554: }
555: list1.reset();
556: } else {
557: result = comparator.compareNumbers(this .num(), obj2.num());
558: }
559:
560: return result;
561: }
562:
563: /**
564: * Tell if one object is less than the other.
565: *
566: * @param obj2 object to compare this nodeset to
567: *
568: * @return see this.compare(...)
569: *
570: * @throws javax.xml.transform.TransformerException
571: */
572: public boolean lessThan(XObject obj2)
573: throws javax.xml.transform.TransformerException {
574: return compare(obj2, S_LT);
575: }
576:
577: /**
578: * Tell if one object is less than or equal to the other.
579: *
580: * @param obj2 object to compare this nodeset to
581: *
582: * @return see this.compare(...)
583: *
584: * @throws javax.xml.transform.TransformerException
585: */
586: public boolean lessThanOrEqual(XObject obj2)
587: throws javax.xml.transform.TransformerException {
588: return compare(obj2, S_LTE);
589: }
590:
591: /**
592: * Tell if one object is less than the other.
593: *
594: * @param obj2 object to compare this nodeset to
595: *
596: * @return see this.compare(...)
597: *
598: * @throws javax.xml.transform.TransformerException
599: */
600: public boolean greaterThan(XObject obj2)
601: throws javax.xml.transform.TransformerException {
602: return compare(obj2, S_GT);
603: }
604:
605: /**
606: * Tell if one object is less than the other.
607: *
608: * @param obj2 object to compare this nodeset to
609: *
610: * @return see this.compare(...)
611: *
612: * @throws javax.xml.transform.TransformerException
613: */
614: public boolean greaterThanOrEqual(XObject obj2)
615: throws javax.xml.transform.TransformerException {
616: return compare(obj2, S_GTE);
617: }
618:
619: /**
620: * Tell if two objects are functionally equal.
621: *
622: * @param obj2 object to compare this nodeset to
623: *
624: * @return see this.compare(...)
625: *
626: * @throws javax.xml.transform.TransformerException
627: */
628: public boolean equals(XObject obj2) {
629: try {
630: return compare(obj2, S_EQ);
631: } catch (javax.xml.transform.TransformerException te) {
632: throw new org.apache.xml.utils.WrappedRuntimeException(te);
633: }
634: }
635:
636: /**
637: * Tell if two objects are functionally not equal.
638: *
639: * @param obj2 object to compare this nodeset to
640: *
641: * @return see this.compare(...)
642: *
643: * @throws javax.xml.transform.TransformerException
644: */
645: public boolean notEquals(XObject obj2)
646: throws javax.xml.transform.TransformerException {
647: return compare(obj2, S_NEQ);
648: }
649: }
650:
651: /**
652: * compares nodes for various boolean operations.
653: */
654: abstract class Comparator {
655:
656: /**
657: * Compare two strings
658: *
659: *
660: * @param s1 First string to compare
661: * @param s2 Second String to compare
662: *
663: * @return Whether the strings are equal or not
664: */
665: abstract boolean compareStrings(XMLString s1, XMLString s2);
666:
667: /**
668: * Compare two numbers
669: *
670: *
671: * @param n1 First number to compare
672: * @param n2 Second number to compare
673: *
674: * @return Whether the numbers are equal or not
675: */
676: abstract boolean compareNumbers(double n1, double n2);
677: }
678:
679: /**
680: * Compare strings or numbers for less than.
681: */
682: class LessThanComparator extends Comparator {
683:
684: /**
685: * Compare two strings for less than.
686: *
687: *
688: * @param s1 First string to compare
689: * @param s2 Second String to compare
690: *
691: * @return True if s1 is less than s2
692: */
693: boolean compareStrings(XMLString s1, XMLString s2) {
694: return (s1.toDouble() < s2.toDouble());
695: // return s1.compareTo(s2) < 0;
696: }
697:
698: /**
699: * Compare two numbers for less than.
700: *
701: *
702: * @param n1 First number to compare
703: * @param n2 Second number to compare
704: *
705: * @return true if n1 is less than n2
706: */
707: boolean compareNumbers(double n1, double n2) {
708: return n1 < n2;
709: }
710: }
711:
712: /**
713: * Compare strings or numbers for less than or equal.
714: */
715: class LessThanOrEqualComparator extends Comparator {
716:
717: /**
718: * Compare two strings for less than or equal.
719: *
720: *
721: * @param s1 First string to compare
722: * @param s2 Second String to compare
723: *
724: * @return true if s1 is less than or equal to s2
725: */
726: boolean compareStrings(XMLString s1, XMLString s2) {
727: return (s1.toDouble() <= s2.toDouble());
728: // return s1.compareTo(s2) <= 0;
729: }
730:
731: /**
732: * Compare two numbers for less than or equal.
733: *
734: *
735: * @param n1 First number to compare
736: * @param n2 Second number to compare
737: *
738: * @return true if n1 is less than or equal to n2
739: */
740: boolean compareNumbers(double n1, double n2) {
741: return n1 <= n2;
742: }
743: }
744:
745: /**
746: * Compare strings or numbers for greater than.
747: */
748: class GreaterThanComparator extends Comparator {
749:
750: /**
751: * Compare two strings for greater than.
752: *
753: *
754: * @param s1 First string to compare
755: * @param s2 Second String to compare
756: *
757: * @return true if s1 is greater than s2
758: */
759: boolean compareStrings(XMLString s1, XMLString s2) {
760: return (s1.toDouble() > s2.toDouble());
761: // return s1.compareTo(s2) > 0;
762: }
763:
764: /**
765: * Compare two numbers for greater than.
766: *
767: *
768: * @param n1 First number to compare
769: * @param n2 Second number to compare
770: *
771: * @return true if n1 is greater than n2
772: */
773: boolean compareNumbers(double n1, double n2) {
774: return n1 > n2;
775: }
776: }
777:
778: /**
779: * Compare strings or numbers for greater than or equal.
780: */
781: class GreaterThanOrEqualComparator extends Comparator {
782:
783: /**
784: * Compare two strings for greater than or equal.
785: *
786: *
787: * @param s1 First string to compare
788: * @param s2 Second String to compare
789: *
790: * @return true if s1 is greater than or equal to s2
791: */
792: boolean compareStrings(XMLString s1, XMLString s2) {
793: return (s1.toDouble() >= s2.toDouble());
794: // return s1.compareTo(s2) >= 0;
795: }
796:
797: /**
798: * Compare two numbers for greater than or equal.
799: *
800: *
801: * @param n1 First number to compare
802: * @param n2 Second number to compare
803: *
804: * @return true if n1 is greater than or equal to n2
805: */
806: boolean compareNumbers(double n1, double n2) {
807: return n1 >= n2;
808: }
809: }
810:
811: /**
812: * Compare strings or numbers for equality.
813: */
814: class EqualComparator extends Comparator {
815:
816: /**
817: * Compare two strings for equality.
818: *
819: *
820: * @param s1 First string to compare
821: * @param s2 Second String to compare
822: *
823: * @return true if s1 is equal to s2
824: */
825: boolean compareStrings(XMLString s1, XMLString s2) {
826: return s1.equals(s2);
827: }
828:
829: /**
830: * Compare two numbers for equality.
831: *
832: *
833: * @param n1 First number to compare
834: * @param n2 Second number to compare
835: *
836: * @return true if n1 is equal to n2
837: */
838: boolean compareNumbers(double n1, double n2) {
839: return n1 == n2;
840: }
841: }
842:
843: /**
844: * Compare strings or numbers for non-equality.
845: */
846: class NotEqualComparator extends Comparator {
847:
848: /**
849: * Compare two strings for non-equality.
850: *
851: *
852: * @param s1 First string to compare
853: * @param s2 Second String to compare
854: *
855: * @return true if s1 is not equal to s2
856: */
857: boolean compareStrings(XMLString s1, XMLString s2) {
858: return !s1.equals(s2);
859: }
860:
861: /**
862: * Compare two numbers for non-equality.
863: *
864: *
865: * @param n1 First number to compare
866: * @param n2 Second number to compare
867: *
868: * @return true if n1 is not equal to n2
869: */
870: boolean compareNumbers(double n1, double n2) {
871: return n1 != n2;
872: }
873: }
|