1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the  "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18/*
19 * $Id: XNodeSet.java 469368 2006-10-31 04:41:36Z minchau $
20 */
21package org.apache.xpath.objects;
22
23import org.apache.xml.dtm.DTM;
24import org.apache.xml.dtm.DTMIterator;
25import org.apache.xml.dtm.DTMManager;
26import org.apache.xml.utils.XMLString;
27import org.apache.xpath.NodeSetDTM;
28import org.apache.xpath.axes.NodeSequence;
29
30import org.w3c.dom.NodeList;
31import org.w3c.dom.traversal.NodeIterator;
32
33/**
34 * This class represents an XPath nodeset object, and is capable of
35 * converting the nodeset to other types, such as a string.
36 * @xsl.usage general
37 */
38public class XNodeSet extends NodeSequence
39{
40    static final long serialVersionUID = 1916026368035639667L;
41  /**
42   * Default constructor for derived objects.
43   */
44  protected XNodeSet()
45  {
46  }
47
48  /**
49   * Construct a XNodeSet object.
50   *
51   * @param val Value of the XNodeSet object
52   */
53  public XNodeSet(DTMIterator val)
54  {
55  	super();
56  	if(val instanceof XNodeSet)
57  	{
58        final XNodeSet nodeSet = (XNodeSet) val;
59	    setIter(nodeSet.m_iter);
60	    m_dtmMgr = nodeSet.m_dtmMgr;
61	    m_last = nodeSet.m_last;
62        // First make sure the DTMIterator val has a cache,
63        // so if it doesn't have one, make one.
64	    if(!nodeSet.hasCache())
65	        nodeSet.setShouldCacheNodes(true);
66
67        // Get the cache from val and use it ourselves (we share it).
68	    setObject(nodeSet.getIteratorCache());
69  	}
70  	else
71    	setIter(val);
72  }
73
74  /**
75   * Construct a XNodeSet object.
76   *
77   * @param val Value of the XNodeSet object
78   */
79  public XNodeSet(XNodeSet val)
80  {
81  	super();
82    setIter(val.m_iter);
83    m_dtmMgr = val.m_dtmMgr;
84    m_last = val.m_last;
85    if(!val.hasCache())
86    	val.setShouldCacheNodes(true);
87    setObject(val.m_obj);
88  }
89
90
91  /**
92   * Construct an empty XNodeSet object.  This is used to create a mutable
93   * nodeset to which random nodes may be added.
94   */
95  public XNodeSet(DTMManager dtmMgr)
96  {
97     this(DTM.NULL,dtmMgr);
98  }
99
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 */
731abstract 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 */
760class 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 */
796class 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 */
832class 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 */
868class 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 */
904class 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 */
939class 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}
970