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: ExsltDynamic.java 468639 2006-10-28 06:52:33Z minchau $
020 */
021 package org.apache.xalan.lib;
022
023 import javax.xml.parsers.DocumentBuilder;
024 import javax.xml.parsers.DocumentBuilderFactory;
025 import javax.xml.transform.TransformerException;
026
027 import org.apache.xalan.extensions.ExpressionContext;
028 import org.apache.xalan.res.XSLMessages;
029 import org.apache.xalan.res.XSLTErrorResources;
030 import org.apache.xpath.NodeSet;
031 import org.apache.xpath.NodeSetDTM;
032 import org.apache.xpath.XPath;
033 import org.apache.xpath.XPathContext;
034 import org.apache.xpath.objects.XBoolean;
035 import org.apache.xpath.objects.XNodeSet;
036 import org.apache.xpath.objects.XNumber;
037 import org.apache.xpath.objects.XObject;
038
039 import org.w3c.dom.Document;
040 import org.w3c.dom.Element;
041 import org.w3c.dom.Node;
042 import org.w3c.dom.NodeList;
043 import org.w3c.dom.Text;
044
045 import org.xml.sax.SAXNotSupportedException;
046
047 /**
048 * This class contains EXSLT dynamic extension functions.
049 *
050 * It is accessed by specifying a namespace URI as follows:
051 * <pre>
052 * xmlns:dyn="http://exslt.org/dynamic"
053 * </pre>
054 * The documentation for each function has been copied from the relevant
055 * EXSLT Implementer page.
056 *
057 * @see <a href="http://www.exslt.org/">EXSLT</a>
058
059 * @xsl.usage general
060 */
061 public class ExsltDynamic extends ExsltBase
062 {
063
064 public static final String EXSL_URI = "http://exslt.org/common";
065
066 /**
067 * The dyn:max function calculates the maximum value for the nodes passed as
068 * the first argument, where the value of each node is calculated dynamically
069 * using an XPath expression passed as a string as the second argument.
070 * <p>
071 * The expressions are evaluated relative to the nodes passed as the first argument.
072 * In other words, the value for each node is calculated by evaluating the XPath
073 * expression with all context information being the same as that for the call to
074 * the dyn:max function itself, except for the following:
075 * <p>
076 * <ul>
077 * <li>the context node is the node whose value is being calculated.</li>
078 * <li>the context position is the position of the node within the node set passed as
079 * the first argument to the dyn:max function, arranged in document order.</li>
080 * <li>the context size is the number of nodes passed as the first argument to the
081 * dyn:max function.</li>
082 * </ul>
083 * <p>
084 * The dyn:max function returns the maximum of these values, calculated in exactly
085 * the same way as for math:max.
086 * <p>
087 * If the expression string passed as the second argument is an invalid XPath
088 * expression (including an empty string), this function returns NaN.
089 * <p>
090 * This function must take a second argument. To calculate the maximum of a set of
091 * nodes based on their string values, you should use the math:max function.
092 *
093 * @param myContext The ExpressionContext passed by the extension processor
094 * @param nl The node set
095 * @param expr The expression string
096 *
097 * @return The maximum evaluation value
098 */
099 public static double max(ExpressionContext myContext, NodeList nl, String expr)
100 throws SAXNotSupportedException
101 {
102
103 XPathContext xctxt = null;
104 if (myContext instanceof XPathContext.XPathExpressionContext)
105 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
106 else
107 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext }));
108
109 if (expr == null || expr.length() == 0)
110 return Double.NaN;
111
112 NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt);
113 xctxt.pushContextNodeList(contextNodes);
114
115 double maxValue = - Double.MAX_VALUE;
116 for (int i = 0; i < contextNodes.getLength(); i++)
117 {
118 int contextNode = contextNodes.item(i);
119 xctxt.pushCurrentNode(contextNode);
120
121 double result = 0;
122 try
123 {
124 XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(),
125 xctxt.getNamespaceContext(),
126 XPath.SELECT);
127 result = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()).num();
128 }
129 catch (TransformerException e)
130 {
131 xctxt.popCurrentNode();
132 xctxt.popContextNodeList();
133 return Double.NaN;
134 }
135
136 xctxt.popCurrentNode();
137
138 if (result > maxValue)
139 maxValue = result;
140 }
141
142 xctxt.popContextNodeList();
143 return maxValue;
144
145 }
146
147 /**
148 * The dyn:min function calculates the minimum value for the nodes passed as the
149 * first argument, where the value of each node is calculated dynamically using
150 * an XPath expression passed as a string as the second argument.
151 * <p>
152 * The expressions are evaluated relative to the nodes passed as the first argument.
153 * In other words, the value for each node is calculated by evaluating the XPath
154 * expression with all context information being the same as that for the call to
155 * the dyn:min function itself, except for the following:
156 * <p>
157 * <ul>
158 * <li>the context node is the node whose value is being calculated.</li>
159 * <li>the context position is the position of the node within the node set passed
160 * as the first argument to the dyn:min function, arranged in document order.</li>
161 * <li>the context size is the number of nodes passed as the first argument to the
162 * dyn:min function.</li>
163 * </ul>
164 * <p>
165 * The dyn:min function returns the minimum of these values, calculated in exactly
166 * the same way as for math:min.
167 * <p>
168 * If the expression string passed as the second argument is an invalid XPath expression
169 * (including an empty string), this function returns NaN.
170 * <p>
171 * This function must take a second argument. To calculate the minimum of a set of
172 * nodes based on their string values, you should use the math:min function.
173 *
174 * @param myContext The ExpressionContext passed by the extension processor
175 * @param nl The node set
176 * @param expr The expression string
177 *
178 * @return The minimum evaluation value
179 */
180 public static double min(ExpressionContext myContext, NodeList nl, String expr)
181 throws SAXNotSupportedException
182 {
183
184 XPathContext xctxt = null;
185 if (myContext instanceof XPathContext.XPathExpressionContext)
186 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
187 else
188 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext }));
189
190 if (expr == null || expr.length() == 0)
191 return Double.NaN;
192
193 NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt);
194 xctxt.pushContextNodeList(contextNodes);
195
196 double minValue = Double.MAX_VALUE;
197 for (int i = 0; i < nl.getLength(); i++)
198 {
199 int contextNode = contextNodes.item(i);
200 xctxt.pushCurrentNode(contextNode);
201
202 double result = 0;
203 try
204 {
205 XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(),
206 xctxt.getNamespaceContext(),
207 XPath.SELECT);
208 result = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()).num();
209 }
210 catch (TransformerException e)
211 {
212 xctxt.popCurrentNode();
213 xctxt.popContextNodeList();
214 return Double.NaN;
215 }
216
217 xctxt.popCurrentNode();
218
219 if (result < minValue)
220 minValue = result;
221 }
222
223 xctxt.popContextNodeList();
224 return minValue;
225
226 }
227
228 /**
229 * The dyn:sum function calculates the sum for the nodes passed as the first argument,
230 * where the value of each node is calculated dynamically using an XPath expression
231 * passed as a string as the second argument.
232 * <p>
233 * The expressions are evaluated relative to the nodes passed as the first argument.
234 * In other words, the value for each node is calculated by evaluating the XPath
235 * expression with all context information being the same as that for the call to
236 * the dyn:sum function itself, except for the following:
237 * <p>
238 * <ul>
239 * <li>the context node is the node whose value is being calculated.</li>
240 * <li>the context position is the position of the node within the node set passed as
241 * the first argument to the dyn:sum function, arranged in document order.</li>
242 * <li>the context size is the number of nodes passed as the first argument to the
243 * dyn:sum function.</li>
244 * </ul>
245 * <p>
246 * The dyn:sum function returns the sumimum of these values, calculated in exactly
247 * the same way as for sum.
248 * <p>
249 * If the expression string passed as the second argument is an invalid XPath
250 * expression (including an empty string), this function returns NaN.
251 * <p>
252 * This function must take a second argument. To calculate the sumimum of a set of
253 * nodes based on their string values, you should use the sum function.
254 *
255 * @param myContext The ExpressionContext passed by the extension processor
256 * @param nl The node set
257 * @param expr The expression string
258 *
259 * @return The sum of the evaluation value on each node
260 */
261 public static double sum(ExpressionContext myContext, NodeList nl, String expr)
262 throws SAXNotSupportedException
263 {
264 XPathContext xctxt = null;
265 if (myContext instanceof XPathContext.XPathExpressionContext)
266 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
267 else
268 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext }));
269
270 if (expr == null || expr.length() == 0)
271 return Double.NaN;
272
273 NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt);
274 xctxt.pushContextNodeList(contextNodes);
275
276 double sum = 0;
277 for (int i = 0; i < nl.getLength(); i++)
278 {
279 int contextNode = contextNodes.item(i);
280 xctxt.pushCurrentNode(contextNode);
281
282 double result = 0;
283 try
284 {
285 XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(),
286 xctxt.getNamespaceContext(),
287 XPath.SELECT);
288 result = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext()).num();
289 }
290 catch (TransformerException e)
291 {
292 xctxt.popCurrentNode();
293 xctxt.popContextNodeList();
294 return Double.NaN;
295 }
296
297 xctxt.popCurrentNode();
298
299 sum = sum + result;
300
301 }
302
303 xctxt.popContextNodeList();
304 return sum;
305 }
306
307 /**
308 * The dyn:map function evaluates the expression passed as the second argument for
309 * each of the nodes passed as the first argument, and returns a node set of those values.
310 * <p>
311 * The expressions are evaluated relative to the nodes passed as the first argument.
312 * In other words, the value for each node is calculated by evaluating the XPath
313 * expression with all context information being the same as that for the call to
314 * the dyn:map function itself, except for the following:
315 * <p>
316 * <ul>
317 * <li>The context node is the node whose value is being calculated.</li>
318 * <li>the context position is the position of the node within the node set passed
319 * as the first argument to the dyn:map function, arranged in document order.</li>
320 * <li>the context size is the number of nodes passed as the first argument to the
321 * dyn:map function.</li>
322 * </ul>
323 * <p>
324 * If the expression string passed as the second argument is an invalid XPath
325 * expression (including an empty string), this function returns an empty node set.
326 * <p>
327 * If the XPath expression evaluates as a node set, the dyn:map function returns
328 * the union of the node sets returned by evaluating the expression for each of the
329 * nodes in the first argument. Note that this may mean that the node set resulting
330 * from the call to the dyn:map function contains a different number of nodes from
331 * the number in the node set passed as the first argument to the function.
332 * <p>
333 * If the XPath expression evaluates as a number, the dyn:map function returns a
334 * node set containing one exsl:number element (namespace http://exslt.org/common)
335 * for each node in the node set passed as the first argument to the dyn:map function,
336 * in document order. The string value of each exsl:number element is the same as
337 * the result of converting the number resulting from evaluating the expression to
338 * a string as with the number function, with the exception that Infinity results
339 * in an exsl:number holding the highest number the implementation can store, and
340 * -Infinity results in an exsl:number holding the lowest number the implementation
341 * can store.
342 * <p>
343 * If the XPath expression evaluates as a boolean, the dyn:map function returns a
344 * node set containing one exsl:boolean element (namespace http://exslt.org/common)
345 * for each node in the node set passed as the first argument to the dyn:map function,
346 * in document order. The string value of each exsl:boolean element is 'true' if the
347 * expression evaluates as true for the node, and '' if the expression evaluates as
348 * false.
349 * <p>
350 * Otherwise, the dyn:map function returns a node set containing one exsl:string
351 * element (namespace http://exslt.org/common) for each node in the node set passed
352 * as the first argument to the dyn:map function, in document order. The string
353 * value of each exsl:string element is the same as the result of converting the
354 * result of evaluating the expression for the relevant node to a string as with
355 * the string function.
356 *
357 * @param myContext The ExpressionContext passed by the extension processor
358 * @param nl The node set
359 * @param expr The expression string
360 *
361 * @return The node set after evaluation
362 */
363 public static NodeList map(ExpressionContext myContext, NodeList nl, String expr)
364 throws SAXNotSupportedException
365 {
366 XPathContext xctxt = null;
367 Document lDoc = null;
368
369 if (myContext instanceof XPathContext.XPathExpressionContext)
370 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
371 else
372 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext }));
373
374 if (expr == null || expr.length() == 0)
375 return new NodeSet();
376
377 NodeSetDTM contextNodes = new NodeSetDTM(nl, xctxt);
378 xctxt.pushContextNodeList(contextNodes);
379
380 NodeSet resultSet = new NodeSet();
381 resultSet.setShouldCacheNodes(true);
382
383 for (int i = 0; i < nl.getLength(); i++)
384 {
385 int contextNode = contextNodes.item(i);
386 xctxt.pushCurrentNode(contextNode);
387
388 XObject object = null;
389 try
390 {
391 XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(),
392 xctxt.getNamespaceContext(),
393 XPath.SELECT);
394 object = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext());
395
396 if (object instanceof XNodeSet)
397 {
398 NodeList nodelist = null;
399 nodelist = ((XNodeSet)object).nodelist();
400
401 for (int k = 0; k < nodelist.getLength(); k++)
402 {
403 Node n = nodelist.item(k);
404 if (!resultSet.contains(n))
405 resultSet.addNode(n);
406 }
407 }
408 else
409 {
410 if (lDoc == null)
411 {
412 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
413 dbf.setNamespaceAware(true);
414 DocumentBuilder db = dbf.newDocumentBuilder();
415 lDoc = db.newDocument();
416 }
417
418 Element element = null;
419 if (object instanceof XNumber)
420 element = lDoc.createElementNS(EXSL_URI, "exsl:number");
421 else if (object instanceof XBoolean)
422 element = lDoc.createElementNS(EXSL_URI, "exsl:boolean");
423 else
424 element = lDoc.createElementNS(EXSL_URI, "exsl:string");
425
426 Text textNode = lDoc.createTextNode(object.str());
427 element.appendChild(textNode);
428 resultSet.addNode(element);
429 }
430 }
431 catch (Exception e)
432 {
433 xctxt.popCurrentNode();
434 xctxt.popContextNodeList();
435 return new NodeSet();
436 }
437
438 xctxt.popCurrentNode();
439
440 }
441
442 xctxt.popContextNodeList();
443 return resultSet;
444 }
445
446 /**
447 * The dyn:evaluate function evaluates a string as an XPath expression and returns
448 * the resulting value, which might be a boolean, number, string, node set, result
449 * tree fragment or external object. The sole argument is the string to be evaluated.
450 * <p>
451 * If the expression string passed as the second argument is an invalid XPath
452 * expression (including an empty string), this function returns an empty node set.
453 * <p>
454 * You should only use this function if the expression must be constructed dynamically,
455 * otherwise it is much more efficient to use the expression literally.
456 *
457 * @param myContext The ExpressionContext passed by the extension processor
458 * @param xpathExpr The XPath expression string
459 *
460 * @return The evaluation result
461 */
462 public static XObject evaluate(ExpressionContext myContext, String xpathExpr)
463 throws SAXNotSupportedException
464 {
465 if (myContext instanceof XPathContext.XPathExpressionContext)
466 {
467 XPathContext xctxt = null;
468 try
469 {
470 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
471 XPath dynamicXPath = new XPath(xpathExpr, xctxt.getSAXLocator(),
472 xctxt.getNamespaceContext(),
473 XPath.SELECT);
474
475 return dynamicXPath.execute(xctxt, myContext.getContextNode(),
476 xctxt.getNamespaceContext());
477 }
478 catch (TransformerException e)
479 {
480 return new XNodeSet(xctxt.getDTMManager());
481 }
482 }
483 else
484 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext })); //"Invalid context passed to evaluate "
485 }
486
487 /**
488 * The dyn:closure function creates a node set resulting from transitive closure of
489 * evaluating the expression passed as the second argument on each of the nodes passed
490 * as the first argument, then on the node set resulting from that and so on until no
491 * more nodes are found. For example:
492 * <pre>
493 * dyn:closure(., '*')
494 * </pre>
495 * returns all the descendant elements of the node (its element children, their
496 * children, their children's children and so on).
497 * <p>
498 * The expression is thus evaluated several times, each with a different node set
499 * acting as the context of the expression. The first time the expression is
500 * evaluated, the context node set is the first argument passed to the dyn:closure
501 * function. In other words, the node set for each node is calculated by evaluating
502 * the XPath expression with all context information being the same as that for
503 * the call to the dyn:closure function itself, except for the following:
504 * <p>
505 * <ul>
506 * <li>the context node is the node whose value is being calculated.</li>
507 * <li>the context position is the position of the node within the node set passed
508 * as the first argument to the dyn:closure function, arranged in document order.</li>
509 * <li>the context size is the number of nodes passed as the first argument to the
510 * dyn:closure function.</li>
511 * <li>the current node is the node whose value is being calculated.</li>
512 * </ul>
513 * <p>
514 * The result for a particular iteration is the union of the node sets resulting
515 * from evaluting the expression for each of the nodes in the source node set for
516 * that iteration. This result is then used as the source node set for the next
517 * iteration, and so on. The result of the function as a whole is the union of
518 * the node sets generated by each iteration.
519 * <p>
520 * If the expression string passed as the second argument is an invalid XPath
521 * expression (including an empty string) or an expression that does not return a
522 * node set, this function returns an empty node set.
523 *
524 * @param myContext The ExpressionContext passed by the extension processor
525 * @param nl The node set
526 * @param expr The expression string
527 *
528 * @return The node set after evaluation
529 */
530 public static NodeList closure(ExpressionContext myContext, NodeList nl, String expr)
531 throws SAXNotSupportedException
532 {
533 XPathContext xctxt = null;
534 if (myContext instanceof XPathContext.XPathExpressionContext)
535 xctxt = ((XPathContext.XPathExpressionContext) myContext).getXPathContext();
536 else
537 throw new SAXNotSupportedException(XSLMessages.createMessage(XSLTErrorResources.ER_INVALID_CONTEXT_PASSED, new Object[]{myContext }));
538
539 if (expr == null || expr.length() == 0)
540 return new NodeSet();
541
542 NodeSet closureSet = new NodeSet();
543 closureSet.setShouldCacheNodes(true);
544
545 NodeList iterationList = nl;
546 do
547 {
548
549 NodeSet iterationSet = new NodeSet();
550
551 NodeSetDTM contextNodes = new NodeSetDTM(iterationList, xctxt);
552 xctxt.pushContextNodeList(contextNodes);
553
554 for (int i = 0; i < iterationList.getLength(); i++)
555 {
556 int contextNode = contextNodes.item(i);
557 xctxt.pushCurrentNode(contextNode);
558
559 XObject object = null;
560 try
561 {
562 XPath dynamicXPath = new XPath(expr, xctxt.getSAXLocator(),
563 xctxt.getNamespaceContext(),
564 XPath.SELECT);
565 object = dynamicXPath.execute(xctxt, contextNode, xctxt.getNamespaceContext());
566
567 if (object instanceof XNodeSet)
568 {
569 NodeList nodelist = null;
570 nodelist = ((XNodeSet)object).nodelist();
571
572 for (int k = 0; k < nodelist.getLength(); k++)
573 {
574 Node n = nodelist.item(k);
575 if (!iterationSet.contains(n))
576 iterationSet.addNode(n);
577 }
578 }
579 else
580 {
581 xctxt.popCurrentNode();
582 xctxt.popContextNodeList();
583 return new NodeSet();
584 }
585 }
586 catch (TransformerException e)
587 {
588 xctxt.popCurrentNode();
589 xctxt.popContextNodeList();
590 return new NodeSet();
591 }
592
593 xctxt.popCurrentNode();
594
595 }
596
597 xctxt.popContextNodeList();
598
599 iterationList = iterationSet;
600
601 for (int i = 0; i < iterationList.getLength(); i++)
602 {
603 Node n = iterationList.item(i);
604 if (!closureSet.contains(n))
605 closureSet.addNode(n);
606 }
607
608 } while(iterationList.getLength() > 0);
609
610 return closureSet;
611
612 }
613
614 }