1 /*
2  * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.
3  */
4 /*
5  * Licensed to the Apache Software Foundation (ASF) under one or more
6  * contributor license agreements.  See the NOTICE file distributed with
7  * this work for additional information regarding copyright ownership.
8  * The ASF licenses this file to You under the Apache License, Version 2.0
9  * (the "License"); you may not use this file except in compliance with
10  * the License.  You may obtain a copy of the License at
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 /*
21  * $Id: ExsltSets.java,v 1.1.2.1 2005/08/01 02:08:50 jeffsuttor Exp $
22  */
23 package com.sun.org.apache.xalan.internal.lib;
24 
25 import com.sun.org.apache.xml.internal.utils.DOM2Helper;
26 import com.sun.org.apache.xpath.internal.NodeSet;
27 import java.util.HashMap;
28 import java.util.Map;
29 import org.w3c.dom.Node;
30 import org.w3c.dom.NodeList;
31 
32 /**
33  * This class contains EXSLT set extension functions.
34  * It is accessed by specifying a namespace URI as follows:
35  * <pre>
36  *    xmlns:set="http://exslt.org/sets"
37  * </pre>
38  *
39  * The documentation for each function has been copied from the relevant
40  * EXSLT Implementer page.
41  *
42  * @see <a href="http://www.exslt.org/">EXSLT</a>
43  * @xsl.usage general
44  */
45 public class ExsltSets extends ExsltBase
46 {
47   /**
48    * The set:leading function returns the nodes in the node set passed as the first argument that
49    * precede, in document order, the first node in the node set passed as the second argument. If
50    * the first node in the second node set is not contained in the first node set, then an empty
51    * node set is returned. If the second node set is empty, then the first node set is returned.
52    *
53    * @param nl1 NodeList for first node-set.
54    * @param nl2 NodeList for second node-set.
55    * @return a NodeList containing the nodes in nl1 that precede in document order the first
56    * node in nl2; an empty node-set if the first node in nl2 is not in nl1; all of nl1 if nl2
57    * is empty.
58    *
59    * @see <a href="http://www.exslt.org/">EXSLT</a>
60    */
leading(NodeList nl1, NodeList nl2)61   public static NodeList leading (NodeList nl1, NodeList nl2)
62   {
63     if (nl2.getLength() == 0)
64       return nl1;
65 
66     NodeSet ns1 = new NodeSet(nl1);
67     NodeSet leadNodes = new NodeSet();
68     Node endNode = nl2.item(0);
69     if (!ns1.contains(endNode))
70       return leadNodes; // empty NodeSet
71 
72     for (int i = 0; i < nl1.getLength(); i++)
73     {
74       Node testNode = nl1.item(i);
75       if (DOM2Helper.isNodeAfter(testNode, endNode)
76           && !DOM2Helper.isNodeTheSame(testNode, endNode))
77         leadNodes.addElement(testNode);
78     }
79     return leadNodes;
80   }
81 
82   /**
83    * The set:trailing function returns the nodes in the node set passed as the first argument that
84    * follow, in document order, the first node in the node set passed as the second argument. If
85    * the first node in the second node set is not contained in the first node set, then an empty
86    * node set is returned. If the second node set is empty, then the first node set is returned.
87    *
88    * @param nl1 NodeList for first node-set.
89    * @param nl2 NodeList for second node-set.
90    * @return a NodeList containing the nodes in nl1 that follow in document order the first
91    * node in nl2; an empty node-set if the first node in nl2 is not in nl1; all of nl1 if nl2
92    * is empty.
93    *
94    * @see <a href="http://www.exslt.org/">EXSLT</a>
95    */
trailing(NodeList nl1, NodeList nl2)96   public static NodeList trailing (NodeList nl1, NodeList nl2)
97   {
98     if (nl2.getLength() == 0)
99       return nl1;
100 
101     NodeSet ns1 = new NodeSet(nl1);
102     NodeSet trailNodes = new NodeSet();
103     Node startNode = nl2.item(0);
104     if (!ns1.contains(startNode))
105       return trailNodes; // empty NodeSet
106 
107     for (int i = 0; i < nl1.getLength(); i++)
108     {
109       Node testNode = nl1.item(i);
110       if (DOM2Helper.isNodeAfter(startNode, testNode)
111           && !DOM2Helper.isNodeTheSame(startNode, testNode))
112         trailNodes.addElement(testNode);
113     }
114     return trailNodes;
115   }
116 
117   /**
118    * The set:intersection function returns a node set comprising the nodes that are within
119    * both the node sets passed as arguments to it.
120    *
121    * @param nl1 NodeList for first node-set.
122    * @param nl2 NodeList for second node-set.
123    * @return a NodeList containing the nodes in nl1 that are also
124    * in nl2.
125    *
126    * @see <a href="http://www.exslt.org/">EXSLT</a>
127    */
intersection(NodeList nl1, NodeList nl2)128   public static NodeList intersection(NodeList nl1, NodeList nl2)
129   {
130     NodeSet ns1 = new NodeSet(nl1);
131     NodeSet ns2 = new NodeSet(nl2);
132     NodeSet inter = new NodeSet();
133 
134     inter.setShouldCacheNodes(true);
135 
136     for (int i = 0; i < ns1.getLength(); i++)
137     {
138       Node n = ns1.elementAt(i);
139 
140       if (ns2.contains(n))
141         inter.addElement(n);
142     }
143 
144     return inter;
145   }
146 
147   /**
148    * The set:difference function returns the difference between two node sets - those nodes that
149    * are in the node set passed as the first argument that are not in the node set passed as the
150    * second argument.
151    *
152    * @param nl1 NodeList for first node-set.
153    * @param nl2 NodeList for second node-set.
154    * @return a NodeList containing the nodes in nl1 that are not in nl2.
155    *
156    * @see <a href="http://www.exslt.org/">EXSLT</a>
157    */
difference(NodeList nl1, NodeList nl2)158   public static NodeList difference(NodeList nl1, NodeList nl2)
159   {
160     NodeSet ns1 = new NodeSet(nl1);
161     NodeSet ns2 = new NodeSet(nl2);
162 
163     NodeSet diff = new NodeSet();
164 
165     diff.setShouldCacheNodes(true);
166 
167     for (int i = 0; i < ns1.getLength(); i++)
168     {
169       Node n = ns1.elementAt(i);
170 
171       if (!ns2.contains(n))
172         diff.addElement(n);
173     }
174 
175     return diff;
176   }
177 
178   /**
179    * The set:distinct function returns a subset of the nodes contained in the node-set NS passed
180    * as the first argument. Specifically, it selects a node N if there is no node in NS that has
181    * the same string value as N, and that precedes N in document order.
182    *
183    * @param nl NodeList for the node-set.
184    * @return a NodeList with nodes from nl containing distinct string values.
185    * In other words, if more than one node in nl contains the same string value,
186    * only include the first such node found.
187    *
188    * @see <a href="http://www.exslt.org/">EXSLT</a>
189    */
distinct(NodeList nl)190   public static NodeList distinct(NodeList nl)
191   {
192     NodeSet dist = new NodeSet();
193     dist.setShouldCacheNodes(true);
194 
195     Map<String, Node> stringTable = new HashMap<>();
196 
197     for (int i = 0; i < nl.getLength(); i++)
198     {
199       Node currNode = nl.item(i);
200       String key = toString(currNode);
201 
202       if (key == null)
203         dist.addElement(currNode);
204       else if (!stringTable.containsKey(key))
205       {
206         stringTable.put(key, currNode);
207         dist.addElement(currNode);
208       }
209     }
210 
211     return dist;
212   }
213 
214   /**
215    * The set:has-same-node function returns true if the node set passed as the first argument shares
216    * any nodes with the node set passed as the second argument. If there are no nodes that are in both
217    * node sets, then it returns false.
218    *
219    * The Xalan extensions MethodResolver converts 'has-same-node' to 'hasSameNode'.
220    *
221    * Note: Not to be confused with hasSameNodes in the Xalan namespace, which returns true if
222    * the two node sets contain the exactly the same nodes (perhaps in a different order),
223    * otherwise false.
224    *
225    * @see <a href="http://www.exslt.org/">EXSLT</a>
226    */
hasSameNode(NodeList nl1, NodeList nl2)227   public static boolean hasSameNode(NodeList nl1, NodeList nl2)
228   {
229 
230     NodeSet ns1 = new NodeSet(nl1);
231     NodeSet ns2 = new NodeSet(nl2);
232 
233     for (int i = 0; i < ns1.getLength(); i++)
234     {
235       if (ns2.contains(ns1.elementAt(i)))
236         return true;
237     }
238     return false;
239   }
240 
241 }
242