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