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: ExsltSets.java 469688 2006-10-31 22:39:43Z minchau $
020     */
021    package org.apache.xalan.lib;
022    
023    import java.util.HashMap;
024    import java.util.Map;
025    
026    import org.apache.xml.utils.DOMHelper;
027    import org.apache.xpath.NodeSet;
028    import org.w3c.dom.Node;
029    import org.w3c.dom.NodeList;
030    
031    /**
032     * This class contains EXSLT set extension functions.
033     * It is accessed by specifying a namespace URI as follows:
034     * <pre>
035     *    xmlns:set="http://exslt.org/sets"
036     * </pre>
037     * 
038     * The documentation for each function has been copied from the relevant
039     * EXSLT Implementer page. 
040     * 
041     * @see <a href="http://www.exslt.org/">EXSLT</a>
042     * @xsl.usage general
043     */
044    public class ExsltSets extends ExsltBase
045    {   
046      /**
047       * The set:leading function returns the nodes in the node set passed as the first argument that
048       * precede, in document order, the first node in the node set passed as the second argument. If
049       * the first node in the second node set is not contained in the first node set, then an empty
050       * node set is returned. If the second node set is empty, then the first node set is returned.
051       * 
052       * @param nl1 NodeList for first node-set.
053       * @param nl2 NodeList for second node-set.
054       * @return a NodeList containing the nodes in nl1 that precede in document order the first
055       * node in nl2; an empty node-set if the first node in nl2 is not in nl1; all of nl1 if nl2
056       * is empty.
057       * 
058       * @see <a href="http://www.exslt.org/">EXSLT</a>
059       */
060      public static NodeList leading (NodeList nl1, NodeList nl2)
061      {
062        if (nl2.getLength() == 0)
063          return nl1;
064          
065        NodeSet ns1 = new NodeSet(nl1);
066        NodeSet leadNodes = new NodeSet();
067        Node endNode = nl2.item(0);
068        if (!ns1.contains(endNode))
069          return leadNodes; // empty NodeSet
070          
071        for (int i = 0; i < nl1.getLength(); i++)
072        {
073          Node testNode = nl1.item(i);
074          if (DOMHelper.isNodeAfter(testNode, endNode) 
075              && !DOMHelper.isNodeTheSame(testNode, endNode))
076            leadNodes.addElement(testNode);
077        }
078        return leadNodes;
079      }
080      
081      /**
082       * The set:trailing function returns the nodes in the node set passed as the first argument that 
083       * follow, in document order, the first node in the node set passed as the second argument. If 
084       * the first node in the second node set is not contained in the first node set, then an empty 
085       * node set is returned. If the second node set is empty, then the first node set is returned. 
086       * 
087       * @param nl1 NodeList for first node-set.
088       * @param nl2 NodeList for second node-set.
089       * @return a NodeList containing the nodes in nl1 that follow in document order the first
090       * node in nl2; an empty node-set if the first node in nl2 is not in nl1; all of nl1 if nl2
091       * is empty.
092       * 
093       * @see <a href="http://www.exslt.org/">EXSLT</a>
094       */
095      public static NodeList trailing (NodeList nl1, NodeList nl2)
096      {
097        if (nl2.getLength() == 0)
098          return nl1;
099          
100        NodeSet ns1 = new NodeSet(nl1);
101        NodeSet trailNodes = new NodeSet();
102        Node startNode = nl2.item(0);
103        if (!ns1.contains(startNode))
104          return trailNodes; // empty NodeSet
105          
106        for (int i = 0; i < nl1.getLength(); i++)
107        {
108          Node testNode = nl1.item(i);
109          if (DOMHelper.isNodeAfter(startNode, testNode) 
110              && !DOMHelper.isNodeTheSame(startNode, testNode))
111            trailNodes.addElement(testNode);          
112        }
113        return trailNodes;
114      }
115      
116      /**
117       * The set:intersection function returns a node set comprising the nodes that are within 
118       * both the node sets passed as arguments to it.
119       * 
120       * @param nl1 NodeList for first node-set.
121       * @param nl2 NodeList for second node-set.
122       * @return a NodeList containing the nodes in nl1 that are also
123       * in nl2.
124       * 
125       * @see <a href="http://www.exslt.org/">EXSLT</a>
126       */
127      public static NodeList intersection(NodeList nl1, NodeList nl2)
128      {
129        NodeSet ns1 = new NodeSet(nl1);
130        NodeSet ns2 = new NodeSet(nl2);
131        NodeSet inter = new NodeSet();
132    
133        inter.setShouldCacheNodes(true);
134    
135        for (int i = 0; i < ns1.getLength(); i++)
136        {
137          Node n = ns1.elementAt(i);
138    
139          if (ns2.contains(n))
140            inter.addElement(n);
141        }
142    
143        return inter;
144      }
145      
146      /**
147       * The set:difference function returns the difference between two node sets - those nodes that 
148       * are in the node set passed as the first argument that are not in the node set passed as the 
149       * second argument.
150       * 
151       * @param nl1 NodeList for first node-set.
152       * @param nl2 NodeList for second node-set.
153       * @return a NodeList containing the nodes in nl1 that are not in nl2.
154       * 
155       * @see <a href="http://www.exslt.org/">EXSLT</a>
156       */
157      public static NodeList difference(NodeList nl1, NodeList nl2)
158      {
159        NodeSet ns1 = new NodeSet(nl1);
160        NodeSet ns2 = new NodeSet(nl2);
161    
162        NodeSet diff = new NodeSet();
163    
164        diff.setShouldCacheNodes(true);
165    
166        for (int i = 0; i < ns1.getLength(); i++)
167        {
168          Node n = ns1.elementAt(i);
169    
170          if (!ns2.contains(n))
171            diff.addElement(n);
172        }
173    
174        return diff;
175      }
176      
177      /**
178       * The set:distinct function returns a subset of the nodes contained in the node-set NS passed 
179       * as the first argument. Specifically, it selects a node N if there is no node in NS that has 
180       * the same string value as N, and that precedes N in document order. 
181       * 
182       * @param nl NodeList for the node-set.
183       * @return a NodeList with nodes from nl containing distinct string values.
184       * In other words, if more than one node in nl contains the same string value,
185       * only include the first such node found.
186       * 
187       * @see <a href="http://www.exslt.org/">EXSLT</a>
188       */
189      public static NodeList distinct(NodeList nl)
190      {
191        NodeSet dist = new NodeSet();
192        dist.setShouldCacheNodes(true);
193    
194        Map stringTable = new HashMap();
195        
196        for (int i = 0; i < nl.getLength(); i++)
197        {
198          Node currNode = nl.item(i);
199          String key = toString(currNode);
200          
201          if (key == null)
202            dist.addElement(currNode);
203          else if (!stringTable.containsKey(key))
204          {
205            stringTable.put(key, currNode);
206            dist.addElement(currNode);              
207          }
208        }
209    
210        return dist;
211      }
212      
213      /**
214       * The set:has-same-node function returns true if the node set passed as the first argument shares 
215       * any nodes with the node set passed as the second argument. If there are no nodes that are in both
216       * node sets, then it returns false. 
217       * 
218       * The Xalan extensions MethodResolver converts 'has-same-node' to 'hasSameNode'.
219       * 
220       * Note: Not to be confused with hasSameNodes in the Xalan namespace, which returns true if
221       * the two node sets contain the exactly the same nodes (perhaps in a different order), 
222       * otherwise false.
223       * 
224       * @see <a href="http://www.exslt.org/">EXSLT</a>
225       */
226      public static boolean hasSameNode(NodeList nl1, NodeList nl2)
227      {
228        
229        NodeSet ns1 = new NodeSet(nl1);
230        NodeSet ns2 = new NodeSet(nl2);
231    
232        for (int i = 0; i < ns1.getLength(); i++)
233        {
234          if (ns2.contains(ns1.elementAt(i)))
235            return true;
236        }
237        return false;
238      }
239      
240    }