sboag       99/12/15 19:50:36

  Modified:    src/org/apache/xalan/xpath SimpleNodeLocator.java
                        XBoolean.java XNodeSet.java XNull.java XNumber.java
                        XObject.java XPath.java XPathSupport.java
                        XPathSupportDefault.java XRTreeFrag.java
                        XString.java
               src/org/apache/xalan/xpath/dtm DTMNodeLocator.java
               src/org/apache/xalan/xpath/xml XMLParserLiaisonDefault.java
               src/org/apache/xalan/xslt ElemAttributeSet.java
                        Stylesheet.java StylesheetRoot.java
                        XSLTEngineImpl.java
  Log:
  Fixed order of comparisons with nodesets, i.e. "2 < foo" vs. "foo < 2".
  
  Revision  Changes    Path
  1.5       +3 -3      
xml-xalan/src/org/apache/xalan/xpath/SimpleNodeLocator.java
  
  Index: SimpleNodeLocator.java
  ===================================================================
  RCS file: 
/home/cvs/xml-xalan/src/org/apache/xalan/xpath/SimpleNodeLocator.java,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- SimpleNodeLocator.java    1999/12/13 07:52:10     1.4
  +++ SimpleNodeLocator.java    1999/12/16 03:50:32     1.5
  @@ -1618,7 +1618,7 @@
         try
         {
           // BUG: m_throwFoundIndex is not threadsafe
  -        xpath.m_throwFoundIndex = true;
  +        execContext.setThrowFoundIndex(true);
           int startPredicates = opPos;
           opPos = startPredicates;
           nextStepType = xpath.m_opMap[opPos];
  @@ -1637,7 +1637,7 @@
             opPos = xpath.getNextOpPos(opPos);
             nextStepType = xpath.m_opMap[opPos];
           }
  -        xpath.m_throwFoundIndex = false;
  +        execContext.setThrowFoundIndex(false);
         }
         catch(FoundIndex fi)
         {
  @@ -1646,7 +1646,7 @@
           // context, then see if the current context is found in the 
           // node set.  Seems crazy, but, so far, it seems like the 
           // easiest way.
  -        xpath.m_throwFoundIndex = false;
  +        execContext.setThrowFoundIndex(false);
           Node parentContext = execContext.getParentOfNode(context);
           MutableNodeList mnl = step(xpath, execContext, parentContext, 
startOpPos);
           int nNodes = mnl.getLength();
  
  
  
  1.5       +6 -0      xml-xalan/src/org/apache/xalan/xpath/XBoolean.java
  
  Index: XBoolean.java
  ===================================================================
  RCS file: /home/cvs/xml-xalan/src/org/apache/xalan/xpath/XBoolean.java,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- XBoolean.java     1999/11/29 06:25:32     1.4
  +++ XBoolean.java     1999/12/16 03:50:32     1.5
  @@ -130,6 +130,12 @@
     public boolean equals(XObject obj2)
       throws org.xml.sax.SAXException
     {
  +    // In order to handle the 'all' semantics of 
  +    // nodeset comparisons, we always call the 
  +    // nodeset function.
  +    if(obj2.getType() == XObject.CLASS_NODESET)
  +      return obj2.equals(this);
  +
       return m_val == obj2.bool();
  }
   
   }
  
  
  
  1.6       +597 -21   xml-xalan/src/org/apache/xalan/xpath/XNodeSet.java
  
  Index: XNodeSet.java
  ===================================================================
  RCS file: /home/cvs/xml-xalan/src/org/apache/xalan/xpath/XNodeSet.java,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- XNodeSet.java     1999/12/13 18:39:22     1.5
  +++ XNodeSet.java     1999/12/16 03:50:32     1.6
  @@ -252,22 +252,168 @@
       
       return mnl;
     }  
  +  
  +  /**
  +   * Tell if one object is less than the other.
  +   */
  +  public boolean lessThan(XObject obj2)
  +    throws org.xml.sax.SAXException
  +  {
  +    boolean isLT = false;
  +    int type = obj2.getType();
  +    if(XObject.CLASS_NODESET == type)
  +    {
  +      // From http://www.w3.org/TR/xpath: 
  +      // If both objects to be compared are node-sets, then the comparison 
  +      // will be true if and only if there is a node in the first node-set 
  +      // and a node in the second node-set such that the result of 
performing 
  +      // the comparison on the string-values of the two nodes is true.
  +
  +      // Note this little gem from the draft:
  +      // NOTE: If $x is bound to a node-set, then $x="foo" 
  +      // does not mean the same as not($x!="foo"): the former 
  +      // is true if and only if some node in $x has the string-value 
  +      // foo; the latter is true if and only if all nodes in $x have 
  +      // the string-value foo.
  +
  +      NodeList list1 = nodeset();
  +      NodeList list2 = ((XNodeSet)obj2).nodeset();
  +      int len1 = list1.getLength();
  +      int len2 = list2.getLength();
  +      for(int i = 0; i < len1; i++)
  +      {
  +        String s1 = getStringFromNode(list1.item(i));
  +        for(int k = 0; k < len2; k++)
  +        {
  +          String s2 = getStringFromNode(list2.item(k));
  +          if(s1.compareTo(s2) < 0)
  +          {
  +            isLT = true;
  +            break;
  +          }
  +        }
  +      }
  +    }
  +    else if(XObject.CLASS_BOOLEAN == type)
  +    {
  +      // From http://www.w3.org/TR/xpath: 
  +      // If one object to be compared is a node-set and the other is a 
boolean, 
  +      // then the comparison will be true if and only if the result of 
  +      // performing the comparison on the boolean and on the result of 
  +      // converting the node-set to a boolean using the boolean function 
  +      // is true.
  +      double num1 = bool() ? 1.0 : 0.0;
  +      double num2 = obj2.num();
  +      isLT = (num1 < num2);
  +    }
  +    else if(XObject.CLASS_NUMBER == type)
  +    {
  +      // From http://www.w3.org/TR/xpath: 
  +      // If one object to be compared is a node-set and the other is a 
number, 
  +      // then the comparison will be true if and only if there is a 
  +      // node in the node-set such that the result of performing the 
  +      // comparison on the number to be compared and on the result of 
  +      // converting the string-value of that node to a number using 
  +      // the number function is true. 
  +            
  +      NodeList list1 = nodeset();
  +      int len1 = list1.getLength();
  +      double num2 = obj2.num();
  +      for(int i = 0; i < len1; i++)
  +      {
  +        double num1 = getNumberFromNode(list1.item(i));
  +        if(num1 < num2)
  +        {
  +          isLT = true;
  +          break;
  +        }
  +      }
  +    }
  +    else if(XObject.CLASS_RTREEFRAG == type)
  +    {
  +      // hmmm... 
  +      double num2 = obj2.num();
  +      if(num2 != Double.NaN)
  +      {
  +        NodeList list1 = nodeset();
  +        int len1 = list1.getLength();
  +        for(int i = 0; i < len1; i++)
  +        {
  +          double num1 = getNumberFromNode(list1.item(i));
  +          if(num1 < num2)
  +          {
  +            isLT = true;
  +            break;
  +          }
  +        }
  +      }
  +      else
  +      {
  +        NodeList list1 = nodeset();
  +        int len1 = list1.getLength();
  +        String s2 = obj2.str();
  +        for(int i = 0; i < len1; i++)
  +        {
  +          String s1 = getStringFromNode(list1.item(i));
  +          if(s1.compareTo(s2) < 0)
  +          {
  +            isLT = true;
  +            break;
  +          }
  +        }
  +      }
  +    }
  +    else if(XObject.CLASS_STRING == type)
  +    {
  +      // From http://www.w3.org/TR/xpath: 
  +      // If one object to be compared is a node-set and the other is a 
  +      // string, then the comparison will be true if and only if there 
  +      // is a node in the node-set such that the result of performing 
  +      // the comparison on the string-value of the node and the other 
  +      // string is true. 
  +      NodeList list1 = nodeset();
  +      int len1 = list1.getLength();
  +      String s2 = obj2.str();
  +      for(int i = 0; i < len1; i++)
  +      {
  +        String s1 = getStringFromNode(list1.item(i));
  +        if(s1.compareTo(s2) < 0)
  +        {
  +          isLT = true;
  +          break;
  +        }
  +      }
  +    }
  +    else
  +    {
  +      isLT = this.num() < obj2.num();
  +    }
  +    return isLT;
  +  }
   
     /**
  -   * Tell if two objects are functionally equal.
  +   * Tell if one object is less than or equal to the other.
      */
  -  public boolean equals(XObject obj2)
  +  public boolean lessThanOrEqual(XObject obj2)
       throws org.xml.sax.SAXException
     {
  -    boolean isEqual = false;
  -    if(XObject.CLASS_NODESET == obj2.getType())
  +    boolean isLTE = false;
  +    int type = obj2.getType();
  +    if(XObject.CLASS_NODESET == type)
       {
         // From http://www.w3.org/TR/xpath: 
  -      // If both objects to be compared are node-sets, then the 
  -      // comparison will be true if and only if there is a node 
  -      // in the first node-set and a node in the second node-set 
  -      // such that the result of performing the comparison on the 
  -      // string-values of the two nodes is true. 
  +      // If both objects to be compared are node-sets, then the comparison 
  +      // will be true if and only if there is a node in the first node-set 
  +      // and a node in the second node-set such that the result of 
performing 
  +      // the comparison on the string-values of the two nodes is true.
  +
  +      // Note this little gem from the draft:
  +      // NOTE: If $x is bound to a node-set, then $x="foo" 
  +      // does not mean the same as not($x!="foo"): the former 
  +      // is true if and only if some node in $x has the string-value 
  +      // foo; the latter is true if and only if all nodes in $x have 
  +      // the string-value foo.
  +
         NodeList list1 = nodeset();
         NodeList list2 = ((XNodeSet)obj2).nodeset();
         int len1 = list1.getLength();
  @@ -278,73 +424,503 @@
           for(int k = 0; k < len2; k++)
           {
             String s2 = getStringFromNode(list2.item(k));
  -          if(s2.equals(s1))
  +          if(s1.compareTo(s2) <= 0)
             {
  -            isEqual = true;
  +            isLTE = true;
               break;
             }
           }
         }
       }
  -    else if(XObject.CLASS_BOOLEAN == obj2.getType())
  +    else if(XObject.CLASS_BOOLEAN == type)
       {
  +      // From http://www.w3.org/TR/xpath: 
         // If one object to be compared is a node-set and the other is a 
boolean, 
         // then the comparison will be true if and only if the result of 
         // performing the comparison on the boolean and on the result of 
         // converting the node-set to a boolean using the boolean function 
         // is true.
  -      isEqual = (bool() == obj2.bool());
  +      double num1 = bool() ? 1.0 : 0.0;
  +      double num2 = obj2.num();
  +      isLTE = (num1 <= num2);
       }
  -    else if(XObject.CLASS_NUMBER == obj2.getType())
  +    else if(XObject.CLASS_NUMBER == type)
       {
  +      // From http://www.w3.org/TR/xpath: 
         // If one object to be compared is a node-set and the other is a 
number, 
         // then the comparison will be true if and only if there is a 
         // node in the node-set such that the result of performing the 
         // comparison on the number to be compared and on the result of 
         // converting the string-value of that node to a number using 
         // the number function is true. 
  +            
         NodeList list1 = nodeset();
         int len1 = list1.getLength();
         double num2 = obj2.num();
         for(int i = 0; i < len1; i++)
         {
           double num1 = getNumberFromNode(list1.item(i));
  -        if(num1 == num2)
  +        if(num1 <= num2)
           {
  -          isEqual = true;
  +          isLTE = true;
             break;
           }
         }
  +    }
  +    else if(XObject.CLASS_RTREEFRAG == type)
  +    {
  +      // hmmm... 
  +      double num2 = obj2.num();
  +      if(num2 != Double.NaN)
  +      {
  +        NodeList list1 = nodeset();
  +        int len1 = list1.getLength();
  +        for(int i = 0; i < len1; i++)
  +        {
  +          double num1 = getNumberFromNode(list1.item(i));
  +          if(num1 <= num2)
  +          {
  +            isLTE = true;
  +            break;
  +          }
  +        }
  +      }
  +      else
  +      {
  +        NodeList list1 = nodeset();
  +        int len1 = list1.getLength();
  +        String s2 = obj2.str();
  +        for(int i = 0; i < len1; i++)
  +        {
  +          String s1 = getStringFromNode(list1.item(i));
  +          if(s1.compareTo(s2) <= 0)
  +          {
  +            isLTE = true;
  +            break;
  +          }
  +        }
  +      }
       }
  -    else if(XObject.CLASS_RTREEFRAG == obj2.getType())
  +    else if(XObject.CLASS_STRING == type)
       {
  -      // hmmm...
  +      // From http://www.w3.org/TR/xpath: 
  +      // If one object to be compared is a node-set and the other is a 
  +      // string, then the comparison will be true if and only if there 
  +      // is a node in the node-set such that the result of performing 
  +      // the comparison on the string-value of the node and the other 
  +      // string is true. 
         NodeList list1 = nodeset();
         int len1 = list1.getLength();
         String s2 = obj2.str();
         for(int i = 0; i < len1; i++)
         {
           String s1 = getStringFromNode(list1.item(i));
  -        if(s1.equals(s2))
  +        if(s1.compareTo(s2) <= 0)
           {
  -          isEqual = true;
  +          isLTE = true;
  +          break;
  +        }
  +      }
  +    }
  +    else
  +    {
  +      isLTE = this.num() <= obj2.num();
  +    }
  +    return isLTE;
  +  }
  +
  +  /**
  +   * Tell if one object is less than the other.
  +   */
  +  public boolean greaterThan(XObject obj2)
  +    throws org.xml.sax.SAXException
  +  {
  +    boolean isGT = false;
  +    int type = obj2.getType();
  +    if(XObject.CLASS_NODESET == type)
  +    {
  +      // From http://www.w3.org/TR/xpath: 
  +      // If both objects to be compared are node-sets, then the comparison 
  +      // will be true if and only if there is a node in the first node-set 
  +      // and a node in the second node-set such that the result of 
performing 
  +      // the comparison on the string-values of the two nodes is true.
  +
  +      // Note this little gem from the draft:
  +      // NOTE: If $x is bound to a node-set, then $x="foo" 
  +      // does not mean the same as not($x!="foo"): the former 
  +      // is true if and only if some node in $x has the string-value 
  +      // foo; the latter is true if and only if all nodes in $x have 
  +      // the string-value foo.
  +
  +      NodeList list1 = nodeset();
  +      NodeList list2 = ((XNodeSet)obj2).nodeset();
  +      int len1 = list1.getLength();
  +      int len2 = list2.getLength();
  +      for(int i = 0; i < len1; i++)
  +      {
  +        String s1 = getStringFromNode(list1.item(i));
  +        for(int k = 0; k < len2; k++)
  +        {
  +          String s2 = getStringFromNode(list2.item(k));
  +          if(s1.compareTo(s2) > 0)
  +          {
  +            isGT = true;
  +            break;
  +          }
  +        }
  +      }
  +    }
  +    else if(XObject.CLASS_BOOLEAN == type)
  +    {
  +      // From http://www.w3.org/TR/xpath: 
  +      // If one object to be compared is a node-set and the other is a 
boolean, 
  +      // then the comparison will be true if and only if the result of 
  +      // performing the comparison on the boolean and on the result of 
  +      // converting the node-set to a boolean using the boolean function 
  +      // is true.
  +      double num1 = bool() ? 1.0 : 0.0;
  +      double num2 = obj2.num();
  +      isGT = (num1 > num2);
  +    }
  +    else if(XObject.CLASS_NUMBER == type)
  +    {
  +      // From http://www.w3.org/TR/xpath: 
  +      // If one object to be compared is a node-set and the other is a 
number, 
  +      // then the comparison will be true if and only if there is a 
  +      // node in the node-set such that the result of performing the 
  +      // comparison on the number to be compared and on the result of 
  +      // converting the string-value of that node to a number using 
  +      // the number function is true. 
  +            
  +      NodeList list1 = nodeset();
  +      int len1 = list1.getLength();
  +      double num2 = obj2.num();
  +      for(int i = 0; i < len1; i++)
  +      {
  +        double num1 = getNumberFromNode(list1.item(i));
  +        if(num1 > num2)
  +        {
  +          isGT = true;
  +          break;
  +        }
  +      }
  +    }
  +    else if(XObject.CLASS_RTREEFRAG == type)
  +    {
  +      // hmmm... 
  +      double num2 = obj2.num();
  +      if(num2 != Double.NaN)
  +      {
  +        NodeList list1 = nodeset();
  +        int len1 = list1.getLength();
  +        for(int i = 0; i < len1; i++)
  +        {
  +          double num1 = getNumberFromNode(list1.item(i));
  +          if(num1 > num2)
  +          {
  +            isGT = true;
  +            break;
  +          }
  +        }
  +      }
  +      else
  +      {
  +        NodeList list1 = nodeset();
  +        int len1 = list1.getLength();
  +        String s2 = obj2.str();
  +        for(int i = 0; i < len1; i++)
  +        {
  +          String s1 = getStringFromNode(list1.item(i));
  +          if(s1.compareTo(s2) > 0)
  +          {
  +            isGT = true;
  +            break;
  +          }
  +        }
  +      }
  +    }
  +    else if(XObject.CLASS_STRING == type)
  +    {
  +      // From http://www.w3.org/TR/xpath: 
  +      // If one object to be compared is a node-set and the other is a 
  +      // string, then the comparison will be true if and only if there 
  +      // is a node in the node-set such that the result of performing 
  +      // the comparison on the string-value of the node and the other 
  +      // string is true. 
  +      NodeList list1 = nodeset();
  +      int len1 = list1.getLength();
  +      String s2 = obj2.str();
  +      for(int i = 0; i < len1; i++)
  +      {
  +        String s1 = getStringFromNode(list1.item(i));
  +        if(s1.compareTo(s2) > 0)
  +        {
  +          isGT = true;
  +          break;
  +        }
  +      }
  +    }
  +    else
  +    {
  +      isGT = this.num() > obj2.num();
  +    }
  +    return isGT;
  +  }
  +
  +  /**
  +   * Tell if one object is less than the other.
  +   */
  +  public boolean greaterThanOrEqual(XObject obj2)
  +    throws org.xml.sax.SAXException
  +  {
  +    boolean isGTE = false;
  +    int type = obj2.getType();
  +    if(XObject.CLASS_NODESET == type)
  +    {
  +      // From http://www.w3.org/TR/xpath: 
  +      // If both objects to be compared are node-sets, then the comparison 
  +      // will be true if and only if there is a node in the first node-set 
  +      // and a node in the second node-set such that the result of 
performing 
  +      // the comparison on the string-values of the two nodes is true.
  +
  +      // Note this little gem from the draft:
  +      // NOTE: If $x is bound to a node-set, then $x="foo" 
  +      // does not mean the same as not($x!="foo"): the former 
  +      // is true if and only if some node in $x has the string-value 
  +      // foo; the latter is true if and only if all nodes in $x have 
  +      // the string-value foo.
  +
  +      NodeList list1 = nodeset();
  +      NodeList list2 = ((XNodeSet)obj2).nodeset();
  +      int len1 = list1.getLength();
  +      int len2 = list2.getLength();
  +      for(int i = 0; i < len1; i++)
  +      {
  +        String s1 = getStringFromNode(list1.item(i));
  +        for(int k = 0; k < len2; k++)
  +        {
  +          String s2 = getStringFromNode(list2.item(k));
  +          if(s1.compareTo(s2) >= 0)
  +          {
  +            isGTE = true;
  +            break;
  +          }
  +        }
  +      }
  +    }
  +    else if(XObject.CLASS_BOOLEAN == type)
  +    {
  +      // From http://www.w3.org/TR/xpath: 
  +      // If one object to be compared is a node-set and the other is a 
boolean, 
  +      // then the comparison will be true if and only if the result of 
  +      // performing the comparison on the boolean and on the result of 
  +      // converting the node-set to a boolean using the boolean function 
  +      // is true.
  +      double num1 = bool() ? 1.0 : 0.0;
  +      double num2 = obj2.num();
  +      isGTE = (num1 >= num2);
  +    }
  +    else if(XObject.CLASS_NUMBER == type)
  +    {
  +      // From http://www.w3.org/TR/xpath: 
  +      // If one object to be compared is a node-set and the other is a 
number, 
  +      // then the comparison will be true if and only if there is a 
  +      // node in the node-set such that the result of performing the 
  +      // comparison on the number to be compared and on the result of 
  +      // converting the string-value of that node to a number using 
  +      // the number function is true. 
  +            
  +      NodeList list1 = nodeset();
  +      int len1 = list1.getLength();
  +      double num2 = obj2.num();
  +      for(int i = 0; i < len1; i++)
  +      {
  +        double num1 = getNumberFromNode(list1.item(i));
  +        if(num1 >= num2)
  +        {
  +          isGTE = true;
             break;
           }
         }
       }
  -    else if(XObject.CLASS_STRING == obj2.getType())
  +    else if(XObject.CLASS_RTREEFRAG == type)
       {
  +      // hmmm... 
  +      double num2 = obj2.num();
  +      if(num2 != Double.NaN)
  +      {
  +        NodeList list1 = nodeset();
  +        int len1 = list1.getLength();
  +        for(int i = 0; i < len1; i++)
  +        {
  +          double num1 = getNumberFromNode(list1.item(i));
  +          if(num1 >= num2)
  +          {
  +            isGTE = true;
  +            break;
  +          }
  +        }
  +      }
  +      else
  +      {
  +        NodeList list1 = nodeset();
  +        int len1 = list1.getLength();
  +        String s2 = obj2.str();
  +        for(int i = 0; i < len1; i++)
  +        {
  +          String s1 = getStringFromNode(list1.item(i));
  +          if(s1.compareTo(s2) >= 0)
  +          {
  +            isGTE = true;
  +            break;
  +          }
  +        }
  +      }
  +    }
  +    else if(XObject.CLASS_STRING == type)
  +    {
  +      // From http://www.w3.org/TR/xpath: 
         // If one object to be compared is a node-set and the other is a 
         // string, then the comparison will be true if and only if there 
         // is a node in the node-set such that the result of performing 
         // the comparison on the string-value of the node and the other 
         // string is true. 
  +      NodeList list1 = nodeset();
  +      int len1 = list1.getLength();
  +      String s2 = obj2.str();
  +      for(int i = 0; i < len1; i++)
  +      {
  +        String s1 = getStringFromNode(list1.item(i));
  +        if(s1.compareTo(s2) >= 0)
  +        {
  +          isGTE = true;
  +          break;
  +        }
  +      }
  +    }
  +    else
  +    {
  +      isGTE = this.num() >= obj2.num();
  +    }
  +    return isGTE;
  +  }
  +
  +  /**
  +   * Tell if two objects are functionally equal.
  +   */
  +  public boolean equals(XObject obj2)
  +    throws org.xml.sax.SAXException
  +  {
  +    boolean isEqual = false;
  +    int type = obj2.getType();
  +    if(XObject.CLASS_NODESET == type)
  +    {
  +      // From http://www.w3.org/TR/xpath: 
  +      // If both objects to be compared are node-sets, then the comparison 
  +      // will be true if and only if there is a node in the first node-set 
  +      // and a node in the second node-set such that the result of 
performing 
  +      // the comparison on the string-values of the two nodes is true.
  +
  +      // Note this little gem from the draft:
  +      // NOTE: If $x is bound to a node-set, then $x="foo" 
  +      // does not mean the same as not($x!="foo"): the former 
  +      // is true if and only if some node in $x has the string-value 
  +      // foo; the latter is true if and only if all nodes in $x have 
  +      // the string-value foo.
  +
  +      NodeList list1 = nodeset();
  +      NodeList list2 = ((XNodeSet)obj2).nodeset();
  +      int len1 = list1.getLength();
  +      int len2 = list2.getLength();
  +      for(int i = 0; i < len1; i++)
  +      {
  +        String s1 = getStringFromNode(list1.item(i));
  +        for(int k = 0; k < len2; k++)
  +        {
  +          String s2 = getStringFromNode(list2.item(k));
  +          if(s2.equals(s1))
  +          {
  +            isEqual = true;
  +            break;
  +          }
  +        }
  +      }
  +    }
  +    else if(XObject.CLASS_BOOLEAN == type)
  +    {
  +      // From http://www.w3.org/TR/xpath: 
  +      // If one object to be compared is a node-set and the other is a 
boolean, 
  +      // then the comparison will be true if and only if the result of 
  +      // performing the comparison on the boolean and on the result of 
  +      // converting the node-set to a boolean using the boolean function 
  +      // is true.
  +      isEqual = (bool() == obj2.bool());
  +    }
  +    else if(XObject.CLASS_NUMBER == type)
  +    {
  +      // From http://www.w3.org/TR/xpath: 
         // If one object to be compared is a node-set and the other is a 
number, 
         // then the comparison will be true if and only if there is a 
         // node in the node-set such that the result of performing the 
         // comparison on the number to be compared and on the result of 
         // converting the string-value of that node to a number using 
         // the number function is true. 
  +            
  +      NodeList list1 = nodeset();
  +      int len1 = list1.getLength();
  +      double num2 = obj2.num();
  +      for(int i = 0; i < len1; i++)
  +      {
  +        double num1 = getNumberFromNode(list1.item(i));
  +        if(num1 == num2)
  +        {
  +          isEqual = true;
  +          break;
  +        }
  +      }
  +    }
  +    else if(XObject.CLASS_RTREEFRAG == type)
  +    {
  +      // hmmm... 
  +      double num2 = obj2.num();
  +      if(num2 != Double.NaN)
  +      {
  +        NodeList list1 = nodeset();
  +        int len1 = list1.getLength();
  +        for(int i = 0; i < len1; i++)
  +        {
  +          double num1 = getNumberFromNode(list1.item(i));
  +          if(num1 == num2)
  +          {
  +            isEqual = true;
  +            break;
  +          }
  +        }
  +      }
  +      else
  +      {
  +        NodeList list1 = nodeset();
  +        int len1 = list1.getLength();
  +        String s2 = obj2.str();
  +        for(int i = 0; i < len1; i++)
  +        {
  +          String s1 = getStringFromNode(list1.item(i));
  +          if(s1.equals(s2))
  +          {
  +            isEqual = true;
  +            break;
  +          }
  +        }
  +      }
  +    }
  +    else if(XObject.CLASS_STRING == type)
  +    {
  +      // From http://www.w3.org/TR/xpath: 
  +      // If one object to be compared is a node-set and the other is a 
  +      // string, then the comparison will be true if and only if there 
  +      // is a node in the node-set such that the result of performing 
  +      // the comparison on the string-value of the node and the other 
  +      // string is true. 
         NodeList list1 = nodeset();
         int len1 = list1.getLength();
         String s2 = obj2.str();
  
  
  
  1.4       +1 -1      xml-xalan/src/org/apache/xalan/xpath/XNull.java
  
  Index: XNull.java
  ===================================================================
  RCS file: /home/cvs/xml-xalan/src/org/apache/xalan/xpath/XNull.java,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- XNull.java        1999/11/29 06:25:32     1.3
  +++ XNull.java        1999/12/16 03:50:32     1.4
  @@ -133,5 +133,5 @@
      */
     public boolean equals(XObject obj2)
     {
  -      return obj2.getType() == CLASS_NULL;
  }
  +    return obj2.getType() == CLASS_NULL;
  }
   }
  
  
  
  1.5       +6 -0      xml-xalan/src/org/apache/xalan/xpath/XNumber.java
  
  Index: XNumber.java
  ===================================================================
  RCS file: /home/cvs/xml-xalan/src/org/apache/xalan/xpath/XNumber.java,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- XNumber.java      1999/11/29 06:25:32     1.4
  +++ XNumber.java      1999/12/16 03:50:32     1.5
  @@ -157,6 +157,12 @@
     public boolean equals(XObject obj2)
       throws org.xml.sax.SAXException
     {
  +    // In order to handle the 'all' semantics of 
  +    // nodeset comparisons, we always call the 
  +    // nodeset function.
  +    if(obj2.getType() == XObject.CLASS_NODESET)
  +      return obj2.equals(this);
  +
       return m_val == obj2.num();
     }
   
  
  
  
  1.7       +76 -2     xml-xalan/src/org/apache/xalan/xpath/XObject.java
  
  Index: XObject.java
  ===================================================================
  RCS file: /home/cvs/xml-xalan/src/org/apache/xalan/xpath/XObject.java,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- XObject.java      1999/12/03 08:43:12     1.6
  +++ XObject.java      1999/12/16 03:50:33     1.7
  @@ -231,14 +231,88 @@
       }
       return result;
     }
  -  
  +
     /**
  +   * Tell if one object is less than the other.
  +   */
  +  public boolean lessThan(XObject obj2)
  +    throws org.xml.sax.SAXException
  +  {
  +    // In order to handle the 'all' semantics of 
  +    // nodeset comparisons, we always call the 
  +    // nodeset function.  Because the arguments 
  +    // are backwards, we call the opposite comparison
  +    // function.
  +    if(obj2.getType() == XObject.CLASS_NODESET)
  +      return obj2.greaterThan(this);
  +
  +    return this.num() < obj2.num();
  +  }
  +
  +  /**
  +   * Tell if one object is less than or equal to the other.
  +   */
  +  public boolean lessThanOrEqual(XObject obj2)
  +    throws org.xml.sax.SAXException
  +  {
  +    // In order to handle the 'all' semantics of 
  +    // nodeset comparisons, we always call the 
  +    // nodeset function.  Because the arguments 
  +    // are backwards, we call the opposite comparison
  +    // function.
  +    if(obj2.getType() == XObject.CLASS_NODESET)
  +      return obj2.greaterThanOrEqual(this);
  +    
  +    return this.num() <= obj2.num();
  +  }
  +
  +  /**
  +   * Tell if one object is less than the other.
  +   */
  +  public boolean greaterThan(XObject obj2)
  +    throws org.xml.sax.SAXException
  +  {
  +    // In order to handle the 'all' semantics of 
  +    // nodeset comparisons, we always call the 
  +    // nodeset function.  Because the arguments 
  +    // are backwards, we call the opposite comparison
  +    // function.
  +    if(obj2.getType() == XObject.CLASS_NODESET)
  +      return obj2.lessThan(this);
  +
  +    return this.num() > obj2.num();
  +  }
  +
  +  /**
  +   * Tell if one object is less than the other.
  +   */
  +  public boolean greaterThanOrEqual(XObject obj2)
  +    throws org.xml.sax.SAXException
  +  {
  +    // In order to handle the 'all' semantics of 
  +    // nodeset comparisons, we always call the 
  +    // nodeset function.  Because the arguments 
  +    // are backwards, we call the opposite comparison
  +    // function.
  +    if(obj2.getType() == XObject.CLASS_NODESET)
  +      return obj2.lessThanOrEqual(this);
  +
  +    return this.num() >= obj2.num();
  +  }
  +
  +  /**
      * Tell if two objects are functionally equal.
      */
     public boolean equals(XObject obj2)
       throws org.xml.sax.SAXException
     {
  -      return m_obj.equals(obj2.m_obj);
  +    // In order to handle the 'all' semantics of 
  +    // nodeset comparisons, we always call the 
  +    // nodeset function.
  +    if(obj2.getType() == XObject.CLASS_NODESET)
  +      return obj2.equals(this);
  +
  +    return m_obj.equals(obj2.m_obj);
     }
   
     /**
  
  
  
  1.10      +7 -13     xml-xalan/src/org/apache/xalan/xpath/XPath.java
  
  Index: XPath.java
  ===================================================================
  RCS file: /home/cvs/xml-xalan/src/org/apache/xalan/xpath/XPath.java,v
  retrieving revision 1.9
  retrieving revision 1.10
  diff -u -r1.9 -r1.10
  --- XPath.java        1999/12/13 08:06:01     1.9
  +++ XPath.java        1999/12/16 03:50:33     1.10
  @@ -91,13 +91,7 @@
      * The default XLocator.
      */
     public XLocator m_defaultXLocator= null;
  -  
  -  /**
  -   * Tells if FoundIndex should be thrown if index is found.
  -   * This is an optimization for match patterns.
  -   */
  -  public boolean m_throwFoundIndex = false;
  -    
  +      
     /**
      * The current pattern string, for diagnostics purposes
      */
  @@ -351,7 +345,7 @@
     {
   //    assert(null != m_contextNodeList, "m_contextNodeList must be 
non-null");
       
  -    if(m_throwFoundIndex)
  +    if(execContext.getThrowFoundIndex())
         throw new FoundIndex();
       
       return execContext.getContextNodeList().getLength();
  @@ -364,7 +358,7 @@
     {
       // assert(null != m_contextNodeList, "m_contextNodeList must be 
non-null");
       
  -    if(m_throwFoundIndex)
  +    if(execContext.getThrowFoundIndex())
         throw new FoundIndex();
       
       int pos = -1;
  @@ -582,7 +576,7 @@
       XObject expr1 = execute(execContext, context, opPos);
       XObject expr2 = execute(execContext, context, expr2Pos);
       
  -    return (expr1.num() <=  expr2.num()) ? m_true : m_false;
  +    return expr1.lessThanOrEqual(expr2) ? m_true : m_false;
     }
   
     /**
  @@ -600,7 +594,7 @@
       XObject expr1 = execute(execContext, context, opPos);
       XObject expr2 = execute(execContext, context, expr2Pos);
       
  -    return (expr1.num() <  expr2.num()) ? m_true : m_false;
  +    return expr1.lessThan(expr2) ? m_true : m_false;
     }
   
     /**
  @@ -618,7 +612,7 @@
       XObject expr1 = execute(execContext, context, opPos);
       XObject expr2 = execute(execContext, context, expr2Pos);
       
  -    return (expr1.num() >=  expr2.num()) ? m_true : m_false;
  +    return expr1.greaterThanOrEqual(expr2) ? m_true : m_false;
     }
   
   
  @@ -637,7 +631,7 @@
       XObject expr1 = execute(execContext, context, opPos);
       XObject expr2 = execute(execContext, context, expr2Pos);
       
  -    return (expr1.num() >  expr2.num()) ? m_true : m_false;
  +    return expr1.greaterThan(expr2) ? m_true : m_false;
     }
   
     /**
  
  
  
  1.5       +17 -0     xml-xalan/src/org/apache/xalan/xpath/XPathSupport.java
  
  Index: XPathSupport.java
  ===================================================================
  RCS file: /home/cvs/xml-xalan/src/org/apache/xalan/xpath/XPathSupport.java,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- XPathSupport.java 1999/12/13 08:06:01     1.4
  +++ XPathSupport.java 1999/12/16 03:50:33     1.5
  @@ -210,6 +210,23 @@
     public Object getCallbackInfo();
     
     /**
  +   * ThrowFoundIndex tells if FoundIndex should be thrown 
  +   * if index is found.
  +   * This is an optimization for match patterns, and 
  +   * is used internally by the XPath engine.
  +   */
  +  public boolean getThrowFoundIndex();
  +
  +  /**
  +   * ThrowFoundIndex tells if FoundIndex should be thrown 
  +   * if index is found.
  +   * This is an optimization for match patterns, and 
  +   * is used internally by the XPath engine.
  +   */
  +  public void setThrowFoundIndex(boolean b);
  +
  +  
  +  /**
      * Function that is called when a problem event occurs.
      * 
      * @param   where             Either and XMLPARSER, XSLPROCESSOR, or 
QUERYENGINE.
  
  
  
  1.6       +29 -0     
xml-xalan/src/org/apache/xalan/xpath/XPathSupportDefault.java
  
  Index: XPathSupportDefault.java
  ===================================================================
  RCS file: 
/home/cvs/xml-xalan/src/org/apache/xalan/xpath/XPathSupportDefault.java,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- XPathSupportDefault.java  1999/12/13 08:06:01     1.5
  +++ XPathSupportDefault.java  1999/12/16 03:50:33     1.6
  @@ -71,6 +71,35 @@
   public class XPathSupportDefault implements XPathSupport
   {
     /**
  +   * Tells if FoundIndex should be thrown if index is found.
  +   * This is an optimization for match patterns.
  +   */
  +  private boolean m_throwFoundIndex = false;
  +  
  +  
  +  /**
  +   * ThrowFoundIndex tells if FoundIndex should be thrown 
  +   * if index is found.
  +   * This is an optimization for match patterns, and 
  +   * is used internally by the XPath engine.
  +   */
  +  public boolean getThrowFoundIndex()
  +  {
  +    return m_throwFoundIndex;
  +  }
  +
  +  /**
  +   * ThrowFoundIndex tells if FoundIndex should be thrown 
  +   * if index is found.
  +   * This is an optimization for match patterns, and 
  +   * is used internally by the XPath engine.
  +   */
  +  public void setThrowFoundIndex(boolean b)
  +  {
  +    m_throwFoundIndex = b;
  +  }
  +
  +  /**
      * The current context node.
      */
     private NodeList m_contextNodeList = null;
  
  
  
  1.7       +4 -1      xml-xalan/src/org/apache/xalan/xpath/XRTreeFrag.java
  
  Index: XRTreeFrag.java
  ===================================================================
  RCS file: /home/cvs/xml-xalan/src/org/apache/xalan/xpath/XRTreeFrag.java,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- XRTreeFrag.java   1999/12/14 22:25:09     1.6
  +++ XRTreeFrag.java   1999/12/16 03:50:33     1.7
  @@ -177,7 +177,10 @@
     {
       if(XObject.CLASS_NODESET == obj2.getType())
       {
  -      return str().equals(obj2.str());
  +      // In order to handle the 'all' semantics of 
  +      // nodeset comparisons, we always call the 
  +      // nodeset function.
  +      return obj2.equals(this);
       }
       else if(XObject.CLASS_BOOLEAN == obj2.getType())
       {
  
  
  
  1.4       +7 -0      xml-xalan/src/org/apache/xalan/xpath/XString.java
  
  Index: XString.java
  ===================================================================
  RCS file: /home/cvs/xml-xalan/src/org/apache/xalan/xpath/XString.java,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- XString.java      1999/11/29 06:25:32     1.3
  +++ XString.java      1999/12/16 03:50:33     1.4
  @@ -149,7 +149,14 @@
      * Tell if two objects are functionally equal.
      */
     public boolean equals(XObject obj2)
  +    throws org.xml.sax.SAXException
     {
  +    // In order to handle the 'all' semantics of 
  +    // nodeset comparisons, we always call the 
  +    // nodeset function.
  +    if(obj2.getType() == XObject.CLASS_NODESET)
  +      return obj2.equals(this);
  +
       return str().equals(obj2.str());
     }
   
  
  
  
  1.6       +3 -3      
xml-xalan/src/org/apache/xalan/xpath/dtm/DTMNodeLocator.java
  
  Index: DTMNodeLocator.java
  ===================================================================
  RCS file: 
/home/cvs/xml-xalan/src/org/apache/xalan/xpath/dtm/DTMNodeLocator.java,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- DTMNodeLocator.java       1999/12/13 07:46:58     1.5
  +++ DTMNodeLocator.java       1999/12/16 03:50:34     1.6
  @@ -440,7 +440,7 @@
         // if I could sense this condition earlier...
         try
         {
  -        xpath.m_throwFoundIndex = true;
  +        execContext.setThrowFoundIndex(true);
           int startPredicates = opPos;
           opPos = startPredicates;
           nextStepType = xpath.m_opMap[opPos];
  @@ -459,7 +459,7 @@
             opPos = xpath.getNextOpPos(opPos);
             nextStepType = xpath.m_opMap[opPos];
           }
  -        xpath.m_throwFoundIndex = false;
  +        execContext.setThrowFoundIndex(false);
         }
         catch(FoundIndex fi)
         {
  @@ -468,7 +468,7 @@
           // context, then see if the current context is found in the 
           // node set.  Seems crazy, but, so far, it seems like the 
           // easiest way.
  -        xpath.m_throwFoundIndex = false;
  +        execContext.setThrowFoundIndex(true);
           int parentContext = dtm.getParent(context);
           MutableNodeList mnl = step(xpath, execContext, 
dtm.getNode(dtm.getParent(parentContext)), startOpPos);
           int nNodes = mnl.getLength();
  
  
  
  1.14      +30 -0     
xml-xalan/src/org/apache/xalan/xpath/xml/XMLParserLiaisonDefault.java
  
  Index: XMLParserLiaisonDefault.java
  ===================================================================
  RCS file: 
/home/cvs/xml-xalan/src/org/apache/xalan/xpath/xml/XMLParserLiaisonDefault.java,v
  retrieving revision 1.13
  retrieving revision 1.14
  diff -u -r1.13 -r1.14
  --- XMLParserLiaisonDefault.java      1999/12/14 00:21:53     1.13
  +++ XMLParserLiaisonDefault.java      1999/12/16 03:50:34     1.14
  @@ -1984,5 +1984,35 @@
       }
       return xlocator;
     }
  +  
  +  /**
  +   * Tells if FoundIndex should be thrown if index is found.
  +   * This is an optimization for match patterns.
  +   */
  +  private boolean m_throwFoundIndex = false;
  +  
  +  
  +  /**
  +   * ThrowFoundIndex tells if FoundIndex should be thrown 
  +   * if index is found.
  +   * This is an optimization for match patterns, and 
  +   * is used internally by the XPath engine.
  +   */
  +  public boolean getThrowFoundIndex()
  +  {
  +    return m_throwFoundIndex;
  +  }
  +
  +  /**
  +   * ThrowFoundIndex tells if FoundIndex should be thrown 
  +   * if index is found.
  +   * This is an optimization for match patterns, and 
  +   * is used internally by the XPath engine.
  +   */
  +  public void setThrowFoundIndex(boolean b)
  +  {
  +    m_throwFoundIndex = b;
  +  }
  +
   }
   
  
  
  
  1.3       +6 -6      xml-xalan/src/org/apache/xalan/xslt/ElemAttributeSet.java
  
  Index: ElemAttributeSet.java
  ===================================================================
  RCS file: 
/home/cvs/xml-xalan/src/org/apache/xalan/xslt/ElemAttributeSet.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- ElemAttributeSet.java     1999/12/15 16:25:13     1.2
  +++ ElemAttributeSet.java     1999/12/16 03:50:35     1.3
  @@ -110,13 +110,13 @@
              java.io.IOException,
              SAXException
     {
  -    if(null == m_stylesheet.m_stylesheetRoot.m_attrSetStack)
  +    if(null == processor.m_attrSetStack)
       {
  -      m_stylesheet.m_stylesheetRoot.m_attrSetStack = new Stack();
  +      processor.m_attrSetStack = new Stack();
       }
  -    if(!m_stylesheet.m_stylesheetRoot.m_attrSetStack.empty())
  +    if(!processor.m_attrSetStack.empty())
       {
  -      int loc = m_stylesheet.m_stylesheetRoot.m_attrSetStack.search(this);
  +      int loc = processor.m_attrSetStack.search(this);
         if(loc > -1)
         {
           throw new SAXException("xsl:attribute-set '"+m_qname.m_localpart+
  @@ -124,7 +124,7 @@
         }
       }
       
  -    m_stylesheet.m_stylesheetRoot.m_attrSetStack.push(this);
  +    processor.m_attrSetStack.push(this);
       super.execute(processor, sourceTree, sourceNode, mode);
       ElemAttribute attr = (ElemAttribute)getFirstChild();
       while(null != attr)
  @@ -132,7 +132,7 @@
         attr.execute(processor, sourceTree, sourceNode, mode);
         attr = (ElemAttribute)attr.getNextSibling();
       }
  -    m_stylesheet.m_stylesheetRoot.m_attrSetStack.pop();
  +    processor.m_attrSetStack.pop();
     }
     
     /**
  
  
  
  1.16      +5 -29     xml-xalan/src/org/apache/xalan/xslt/Stylesheet.java
  
  Index: Stylesheet.java
  ===================================================================
  RCS file: /home/cvs/xml-xalan/src/org/apache/xalan/xslt/Stylesheet.java,v
  retrieving revision 1.15
  retrieving revision 1.16
  diff -u -r1.15 -r1.16
  --- Stylesheet.java   1999/12/14 23:42:44     1.15
  +++ Stylesheet.java   1999/12/16 03:50:35     1.16
  @@ -545,32 +545,8 @@
     {
       m_keyDeclarations = v;
     }
  -
  -  /**
  -   * Table of tables of element keys.
  -   * @see KeyTable.
  -   */
  -  transient Vector m_key_tables = null;
     
     /**
  -   * Get table of tables of element keys.
  -   * @see KeyTable.
  -   */
  -  public Vector getKeyTables()
  -  {
  -    return m_key_tables;
  -  }
  -
  -  /**
  -   * Set table of tables of element keys.
  -   * @see KeyTable.
  -   */
  -  public void setKeyTables(Vector v)
  -  {
  -    m_key_tables = v;
  -  }
  -
  -  /**
      * This is set to true if an xsl:key directive is found.
      * Mainly for use by the XMLParserLiaison classes for 
      * optimized processing of ids.
  @@ -1634,16 +1610,16 @@
       if(null != m_keyDeclarations)
       {
         boolean foundDoc = false;
  -      if(null == m_key_tables)
  +      if(null == tcontext.m_key_tables)
         {
  -        m_key_tables = new Vector(4);
  +        tcontext.m_key_tables = new Vector(4);
         }
         else
         {
  -        int nKeyTables = m_key_tables.size();
  +        int nKeyTables = tcontext.m_key_tables.size();
           for(int i = 0; i < nKeyTables; i++)
           {
  -          KeyTable kt = (KeyTable)m_key_tables.elementAt(i);
  +          KeyTable kt = (KeyTable)tcontext.m_key_tables.elementAt(i);
             if(doc == kt.m_docKey)
             {
               foundDoc = true;
  @@ -1656,7 +1632,7 @@
         {
           KeyTable kt = new KeyTable(doc, doc, nscontext, m_keyDeclarations, 
                                      tcontext.m_parserLiaison);
  -        m_key_tables.addElement(kt);
  +        tcontext.m_key_tables.addElement(kt);
           if(doc == kt.m_docKey)
           {
             foundDoc = true;
  
  
  
  1.14      +1 -8      xml-xalan/src/org/apache/xalan/xslt/StylesheetRoot.java
  
  Index: StylesheetRoot.java
  ===================================================================
  RCS file: /home/cvs/xml-xalan/src/org/apache/xalan/xslt/StylesheetRoot.java,v
  retrieving revision 1.13
  retrieving revision 1.14
  diff -u -r1.13 -r1.14
  --- StylesheetRoot.java       1999/12/13 08:19:31     1.13
  +++ StylesheetRoot.java       1999/12/16 03:50:35     1.14
  @@ -87,13 +87,7 @@
      * List of listeners who are interested in tracing what's going on.
      */
     transient Vector m_traceListeners = null;
  -  
  -  /**
  -   * Stack for the purposes of flagging infinite recursion with 
  -   * attribute sets.
  -   */
  -  transient Stack m_attrSetStack = null;
  -    
  +      
     static final String DEFAULT_ENCODING = "UTF-8";
       
     /**
  @@ -112,7 +106,6 @@
         throw new XSLProcessorException(cnfe);
       }
       m_traceListeners = null;
  -    m_attrSetStack = null;
       // System.out.println("Done reading Stylesheet");
     }
     
  
  
  
  1.23      +12 -1     xml-xalan/src/org/apache/xalan/xslt/XSLTEngineImpl.java
  
  Index: XSLTEngineImpl.java
  ===================================================================
  RCS file: /home/cvs/xml-xalan/src/org/apache/xalan/xslt/XSLTEngineImpl.java,v
  retrieving revision 1.22
  retrieving revision 1.23
  diff -u -r1.22 -r1.23
  --- XSLTEngineImpl.java       1999/12/15 21:53:08     1.22
  +++ XSLTEngineImpl.java       1999/12/16 03:50:35     1.23
  @@ -338,7 +338,18 @@
      * Static false for pushing on the stack and the like.
      */
     private static final Boolean FALSE = new Boolean(false);
  -
  +  
  +  /**
  +   * Table of tables of element keys.
  +   * @see KeyTable.
  +   */
  +  transient Vector m_key_tables = null;
  +  
  +  /**
  +   * Stack for the purposes of flagging infinite recursion with 
  +   * attribute sets.
  +   */
  +  transient Stack m_attrSetStack = null;
   
     //==========================================================
     // SECTION: Constructors
  
  
  

Reply via email to