0aad6fdb5dce036d4c91ad7ffd3a860604f67f24
[platform/upstream/libxslt.git] / libxslt / functions.c
1 /*
2  * functions.c: Implementation of the XSLT extra functions
3  *
4  * Reference:
5  *   http://www.w3.org/TR/1999/REC-xslt-19991116
6  *
7  * See Copyright for the status of this software.
8  *
9  * Daniel.Veillard@imag.fr
10  * Bjorn Reese <breese@users.sourceforge.net> for number formatting
11  */
12
13 #include "xsltconfig.h"
14
15 #include <string.h>
16
17 #ifdef HAVE_SYS_TYPES_H
18 #include <sys/types.h>
19 #endif
20 #ifdef HAVE_CTYPE_H
21 #include <ctype.h>
22 #endif
23
24 #include <libxml/xmlmemory.h>
25 #include <libxml/parser.h>
26 #include <libxml/tree.h>
27 #include <libxml/valid.h>
28 #include <libxml/hash.h>
29 #include <libxml/xmlerror.h>
30 #include <libxml/xpath.h>
31 #include <libxml/xpathInternals.h>
32 #include <libxml/parserInternals.h>
33 #include <libxml/uri.h>
34 #include "xslt.h"
35 #include "xsltInternals.h"
36 #include "xsltutils.h"
37 #include "functions.h"
38 #include "numbersInternals.h"
39 #include "keys.h"
40 #include "documents.h"
41
42 #define DEBUG_FUNCTION
43
44
45 /************************************************************************
46  *                                                                      *
47  *                      Module interfaces                               *
48  *                                                                      *
49  ************************************************************************/
50
51 /**
52  * xsltDocumentFunction:
53  * @ctxt:  the XPath Parser context
54  * @nargs:  the number of arguments
55  *
56  * Implement the document() XSLT function
57  *   node-set document(object, node-set?)
58  */
59 void
60 xsltDocumentFunction(xmlXPathParserContextPtr ctxt, int nargs){
61     xsltDocumentPtr doc;
62     xmlXPathObjectPtr obj, obj2=NULL;
63     xmlChar *base, *URI;
64
65
66     if ((nargs < 1) || (nargs > 2)) {
67         xsltGenericError(xsltGenericErrorContext,
68                 "document() : invalid number of args %d\n", nargs);
69         ctxt->error = XPATH_INVALID_ARITY;
70         return;
71     }
72     if (ctxt->value == NULL) {
73         xsltGenericError(xsltGenericErrorContext,
74             "document() : invalid arg value\n");
75         ctxt->error = XPATH_INVALID_TYPE;
76         return;
77     }
78
79     if (nargs == 2) {
80         if (ctxt->value->type != XPATH_NODESET) {
81             xsltGenericError(xsltGenericErrorContext,
82                 "document() : invalid arg expecting a nodeset\n");
83             ctxt->error = XPATH_INVALID_TYPE;
84             return;
85         }
86
87         obj2 = valuePop(ctxt);
88     }
89
90     if (ctxt->value->type == XPATH_NODESET) {
91         int i;
92         xmlXPathObjectPtr newobj, ret;
93
94         obj = valuePop(ctxt);
95         ret = xmlXPathNewNodeSet(NULL);
96
97         if (obj->nodesetval) {
98             for (i = 0; i < obj->nodesetval->nodeNr; i++) {
99                 valuePush(ctxt,
100                           xmlXPathNewNodeSet(obj->nodesetval->nodeTab[i]));
101                 xmlXPathStringFunction(ctxt, 1);
102                 if (nargs == 2) {
103                     valuePush(ctxt, xmlXPathObjectCopy(obj2));
104                 } else {
105                     valuePush(ctxt,
106                               xmlXPathNewNodeSet(obj->nodesetval->nodeTab[i]));
107                 }
108                 xsltDocumentFunction(ctxt, 2);
109                 newobj = valuePop(ctxt);
110                 ret->nodesetval = xmlXPathNodeSetMerge(ret->nodesetval,
111                                                        newobj->nodesetval);
112                 xmlXPathFreeObject(newobj);
113             }
114         }
115
116         xmlXPathFreeObject(obj);
117         if (obj2 != NULL)
118             xmlXPathFreeObject(obj2);
119         valuePush(ctxt, ret);
120         return;
121     }
122     /*
123      * Make sure it's converted to a string
124      */
125     xmlXPathStringFunction(ctxt, 1);
126     if (ctxt->value->type != XPATH_STRING) {
127         xsltGenericError(xsltGenericErrorContext,
128             "document() : invalid arg expecting a string\n");
129         ctxt->error = XPATH_INVALID_TYPE;
130         if (obj2 != NULL)
131             xmlXPathFreeObject(obj2);
132         return;
133     }
134     obj = valuePop(ctxt);
135     if (obj->stringval == NULL) {
136         valuePush(ctxt, xmlXPathNewNodeSet(NULL));
137     } else {
138         if (obj2 != NULL) {
139             /* obj2 should be ordered in document order !!!!! */
140             base = xmlNodeGetBase(obj2->nodesetval->nodeTab[0]->doc,
141                                   obj->nodesetval->nodeTab[0]);
142         } else {
143             base = xmlNodeGetBase(
144                 ((xsltTransformContextPtr)ctxt->context->extra)->style->doc,
145                                   ctxt->context->node);
146         }
147         URI = xmlBuildURI(obj->stringval, base);
148         if (base != NULL)
149             xmlFree(base);
150         if (URI == NULL) {
151             valuePush(ctxt, xmlXPathNewNodeSet(NULL));
152         } else {
153             xsltTransformContextPtr tctxt;
154
155             tctxt = (xsltTransformContextPtr) ctxt->context->extra;
156             if (tctxt == NULL) {
157                 xsltGenericError(xsltGenericErrorContext,
158                         "document() : internal error tctxt == NULL\n");
159                 valuePush(ctxt, xmlXPathNewNodeSet(NULL));
160             } else {
161                 doc = xsltLoadDocument(tctxt, URI);
162                 if (doc == NULL)
163                     valuePush(ctxt, xmlXPathNewNodeSet(NULL));
164                 else {
165                     /* TODO: use XPointer of HTML location for fragment ID */
166                     /* pbm #xxx can lead to location sets, not nodesets :-) */
167                     valuePush(ctxt, xmlXPathNewNodeSet((xmlNodePtr) doc->doc));
168                 }
169             }
170             xmlFree(URI);
171         }
172     }
173     xmlXPathFreeObject(obj);
174     if (obj2 != NULL)
175         xmlXPathFreeObject(obj2);
176 }
177
178 /**
179  * xsltKeyFunction:
180  * @ctxt:  the XPath Parser context
181  * @nargs:  the number of arguments
182  *
183  * Implement the key() XSLT function
184  *   node-set key(string, object)
185  */
186 void
187 xsltKeyFunction(xmlXPathParserContextPtr ctxt, int nargs){
188     xmlNodeSetPtr nodelist;
189     xmlXPathObjectPtr obj1, obj2;
190     xmlChar *key = NULL, *value;
191     const xmlChar *keyURI;
192     xsltTransformContextPtr tctxt;
193
194     tctxt = ((xsltTransformContextPtr)ctxt->context->extra);
195
196     if (nargs != 2) {
197         xsltGenericError(xsltGenericErrorContext,
198                 "key() : expects two arguments\n");
199         ctxt->error = XPATH_INVALID_ARITY;
200         return;
201     }
202
203     obj2 = valuePop(ctxt);
204     if ((obj2 == NULL) ||
205         (ctxt->value == NULL) || (ctxt->value->type != XPATH_STRING)) {
206         xsltGenericError(xsltGenericErrorContext,
207             "key() : invalid arg expecting a string\n");
208         ctxt->error = XPATH_INVALID_TYPE;
209         xmlXPathFreeObject(obj2);
210
211         return;
212     }
213     obj1 = valuePop(ctxt);
214
215     if (obj2->type == XPATH_NODESET) {
216         int i;
217         xmlXPathObjectPtr newobj, ret;
218
219         ret = xmlXPathNewNodeSet(NULL);
220
221         if (obj2->nodesetval != NULL) {
222             for (i = 0; i < obj2->nodesetval->nodeNr; i++) {
223                 valuePush(ctxt, xmlXPathObjectCopy(obj1));
224                 valuePush(ctxt,
225                           xmlXPathNewNodeSet(obj2->nodesetval->nodeTab[i]));
226                 xmlXPathStringFunction(ctxt, 1);
227                 xsltKeyFunction(ctxt, 2);
228                 newobj = valuePop(ctxt);
229                 ret->nodesetval = xmlXPathNodeSetMerge(ret->nodesetval,
230                                                        newobj->nodesetval);
231                 xmlXPathFreeObject(newobj);
232             }
233         }
234         valuePush(ctxt, ret);
235     } else {
236         xmlChar *qname, *prefix;
237
238         /*
239          * Get the associated namespace URI if qualified name
240          */
241         qname = obj1->stringval;
242         key = xmlSplitQName2(qname, &prefix);
243         if (key == NULL) {
244             key = xmlStrdup(obj1->stringval);
245             keyURI = NULL;
246             if (prefix != NULL)
247                 xmlFree(prefix);
248         } else {
249             if (prefix != NULL) {
250                 keyURI = xmlXPathNsLookup(ctxt->context, prefix);
251                 if (keyURI == NULL) {
252                     xsltGenericError(xsltGenericErrorContext,
253                         "key() : prefix %s is not bound\n", prefix);
254                 }
255                 xmlFree(prefix);
256             } else {
257                 keyURI = NULL;
258             }
259         }
260
261         /*
262          * Force conversion of first arg to string
263          */
264         valuePush(ctxt, obj2);
265         xmlXPathStringFunction(ctxt, 1);
266         if ((ctxt->value == NULL) || (ctxt->value->type != XPATH_STRING)) {
267             xsltGenericError(xsltGenericErrorContext,
268                 "key() : invalid arg expecting a string\n");
269             ctxt->error = XPATH_INVALID_TYPE;
270             xmlXPathFreeObject(obj1);
271
272             return;
273         }
274         obj2 = valuePop(ctxt);
275         value = obj2->stringval;
276
277         nodelist = xsltGetKey(tctxt, key, keyURI, value);
278         valuePush(ctxt, xmlXPathWrapNodeSet(
279                         xmlXPathNodeSetMerge(NULL, nodelist)));
280     }
281
282
283     if (obj1 != NULL)
284         xmlXPathFreeObject(obj1);
285     if (obj2 != NULL)
286         xmlXPathFreeObject(obj2);
287     if (key != NULL)
288         xmlFree(key);
289 }
290
291 /**
292  * xsltUnparsedEntityURIFunction:
293  * @ctxt:  the XPath Parser context
294  * @nargs:  the number of arguments
295  *
296  * Implement the unparsed-entity-uri() XSLT function
297  *   string unparsed-entity-uri(string)
298  */
299 void
300 xsltUnparsedEntityURIFunction(xmlXPathParserContextPtr ctxt, int nargs){
301     xmlXPathObjectPtr obj;
302     xmlChar *str;
303
304     if ((nargs != 1) || (ctxt->value == NULL)) {
305         xsltGenericError(xsltGenericErrorContext,
306                 "unparsed-entity-uri() : expects one string arg\n");
307         ctxt->error = XPATH_INVALID_ARITY;
308         return;
309     }
310     obj = valuePop(ctxt);
311     if (obj->type != XPATH_STRING) {
312         obj = xmlXPathConvertString(obj);
313     }
314
315     str = obj->stringval;
316     if (str == NULL) {
317         valuePush(ctxt, xmlXPathNewString((const xmlChar *)""));
318     } else {
319         xmlEntityPtr entity;
320
321         entity = xmlGetDocEntity(ctxt->context->doc, str);
322         if (entity == NULL) {
323             valuePush(ctxt, xmlXPathNewString((const xmlChar *)""));
324         } else {
325             if (entity->URI != NULL)
326                 valuePush(ctxt, xmlXPathNewString(entity->URI));
327             else
328                 valuePush(ctxt, xmlXPathNewString((const xmlChar *)""));
329         }
330     }
331     xmlXPathFreeObject(obj);
332 }
333
334 /**
335  * xsltFormatNumberFunction:
336  * @ctxt:  the XPath Parser context
337  * @nargs:  the number of arguments
338  *
339  * Implement the format-number() XSLT function
340  *   string format-number(number, string, string?)
341  */
342 void
343 xsltFormatNumberFunction(xmlXPathParserContextPtr ctxt, int nargs)
344 {
345     xmlXPathObjectPtr numberObj = NULL;
346     xmlXPathObjectPtr formatObj = NULL;
347     xmlXPathObjectPtr decimalObj = NULL;
348     xsltStylesheetPtr sheet;
349     xsltDecimalFormatPtr formatValues;
350     xmlChar *result;
351
352     sheet = ((xsltTransformContextPtr)ctxt->context->extra)->style;
353     formatValues = sheet->decimalFormat;
354     
355     switch (nargs) {
356     case 3:
357         CAST_TO_STRING;
358         decimalObj = valuePop(ctxt);
359         formatValues = xsltDecimalFormatGetByName(sheet, decimalObj->stringval);
360         /* Intentional fall-through */
361     case 2:
362         CAST_TO_STRING;
363         formatObj = valuePop(ctxt);
364         CAST_TO_NUMBER;
365         numberObj = valuePop(ctxt);
366         break;
367     default:
368         XP_ERROR(XPATH_INVALID_ARITY);
369         return;
370     }
371
372     if (xsltFormatNumberConversion(formatValues,
373                                    formatObj->stringval,
374                                    numberObj->floatval,
375                                    &result) == XPATH_EXPRESSION_OK) {
376         valuePush(ctxt, xmlXPathNewString(result));
377         xmlFree(result);
378     }
379     
380     xmlXPathFreeObject(numberObj);
381     xmlXPathFreeObject(formatObj);
382     xmlXPathFreeObject(decimalObj);
383 }
384
385 /**
386  * xsltGenerateIdFunction:
387  * @ctxt:  the XPath Parser context
388  * @nargs:  the number of arguments
389  *
390  * Implement the generate-id() XSLT function
391  *   string generate-id(node-set?)
392  */
393 void
394 xsltGenerateIdFunction(xmlXPathParserContextPtr ctxt, int nargs){
395     xmlNodePtr cur = NULL;
396     unsigned long val;
397     xmlChar str[20];
398
399     if (nargs == 0) {
400         cur = ctxt->context->node;
401     } else if (nargs == 1) {
402         xmlXPathObjectPtr obj;
403         xmlNodeSetPtr nodelist;
404         int i, ret;
405
406         if ((ctxt->value == NULL) || (ctxt->value->type != XPATH_NODESET)) {
407             ctxt->error = XPATH_INVALID_TYPE;
408             xsltGenericError(xsltGenericErrorContext,
409                 "generate-id() : invalid arg expecting a node-set\n");
410             return;
411         }
412         obj = valuePop(ctxt);
413         nodelist = obj->nodesetval;
414         if ((nodelist == NULL) || (nodelist->nodeNr <= 0)) {
415             xmlXPathFreeObject(obj);
416             valuePush(ctxt, xmlXPathNewCString(""));
417             return;
418         }
419         cur = nodelist->nodeTab[0];
420         for (i = 1;i < nodelist->nodeNr;i++) {
421             ret = xmlXPathCmpNodes(cur, nodelist->nodeTab[i]);
422             if (ret == -1)
423                 cur = nodelist->nodeTab[i];
424         }
425         xmlXPathFreeObject(obj);
426     } else {
427         xsltGenericError(xsltGenericErrorContext,
428                 "generate-id() : invalid number of args %d\n", nargs);
429         ctxt->error = XPATH_INVALID_ARITY;
430         return;
431     }
432     /*
433      * Okay this is ugly but should work, use the NodePtr address
434      * to forge the ID
435      */
436     val = (unsigned long)((char *)cur - (char *)0);
437     val /= sizeof(xmlNode);
438     sprintf((char *)str, "id%ld", val);
439     valuePush(ctxt, xmlXPathNewString(str));
440 }
441
442 /**
443  * xsltSystemPropertyFunction:
444  * @ctxt:  the XPath Parser context
445  * @nargs:  the number of arguments
446  *
447  * Implement the system-property() XSLT function
448  *   object system-property(string)
449  */
450 void
451 xsltSystemPropertyFunction(xmlXPathParserContextPtr ctxt, int nargs){
452     xmlXPathObjectPtr obj;
453     xmlChar *prefix, *name;
454     const xmlChar *nsURI = NULL;
455
456     if (nargs != 1) {
457         xsltGenericError(xsltGenericErrorContext,
458                 "system-property() : expects one string arg\n");
459         ctxt->error = XPATH_INVALID_ARITY;
460         return;
461     }
462     if ((ctxt->value == NULL) || (ctxt->value->type != XPATH_STRING)) {
463         xsltGenericError(xsltGenericErrorContext,
464             "system-property() : invalid arg expecting a string\n");
465         ctxt->error = XPATH_INVALID_TYPE;
466         return;
467     }
468     obj = valuePop(ctxt);
469     if (obj->stringval == NULL) {
470         valuePush(ctxt, xmlXPathNewString((const xmlChar *)""));
471     } else {
472         name = xmlSplitQName2(obj->stringval, &prefix);
473         if (name == NULL) {
474             name = xmlStrdup(obj->stringval);
475         } else {
476             nsURI = xmlXPathNsLookup(ctxt->context, prefix);
477             if (nsURI == NULL) {
478                 xsltGenericError(xsltGenericErrorContext,
479                     "system-property(): prefix %s is not bound\n", prefix);
480             }
481         }
482
483         if (!xmlStrcmp(nsURI, XSLT_NAMESPACE)) {
484             if (!xmlStrcmp(name, (const xmlChar *)"version")) {
485                 valuePush(ctxt, xmlXPathNewString(
486                     (const xmlChar *)XSLT_DEFAULT_VERSION));
487             } else if (!xmlStrcmp(name, (const xmlChar *)"vendor")) {
488                 valuePush(ctxt, xmlXPathNewString(
489                     (const xmlChar *)XSLT_DEFAULT_VENDOR));
490             } else if (!xmlStrcmp(name, (const xmlChar *)"vendor-url")) {
491                 valuePush(ctxt, xmlXPathNewString(
492                     (const xmlChar *)XSLT_DEFAULT_URL));
493             } else {
494                 valuePush(ctxt, xmlXPathNewString((const xmlChar *)""));
495             }
496         }
497         if (name != NULL)
498             xmlFree(name);
499         if (prefix != NULL)
500             xmlFree(prefix);
501     }
502     xmlXPathFreeObject(obj);
503 }
504
505 /**
506  * xsltElementAvailableFunction:
507  * @ctxt:  the XPath Parser context
508  * @nargs:  the number of arguments
509  *
510  * Implement the element-available() XSLT function
511  *   boolean element-available(string)
512  */
513 void
514 xsltElementAvailableFunction(xmlXPathParserContextPtr ctxt, int nargs){
515     xmlXPathObjectPtr obj;
516
517     if (nargs != 1) {
518         xsltGenericError(xsltGenericErrorContext,
519                 "element-available() : expects one string arg\n");
520         ctxt->error = XPATH_INVALID_ARITY;
521         return;
522     }
523     if ((ctxt->value == NULL) || (ctxt->value->type != XPATH_STRING)) {
524         xsltGenericError(xsltGenericErrorContext,
525             "element-available invalid arg expecting a string\n");
526         ctxt->error = XPATH_INVALID_TYPE;
527         return;
528     }
529     obj = valuePop(ctxt);
530     xmlXPathFreeObject(obj);
531     valuePush(ctxt, xmlXPathNewBoolean(0));
532 }
533
534 /**
535  * xsltFunctionAvailableFunction:
536  * @ctxt:  the XPath Parser context
537  * @nargs:  the number of arguments
538  *
539  * Implement the function-available() XSLT function
540  *   boolean function-available(string)
541  */
542 void
543 xsltFunctionAvailableFunction(xmlXPathParserContextPtr ctxt, int nargs){
544     xmlXPathObjectPtr obj;
545     xmlChar *prefix, *name;
546     const xmlChar *nsURI = NULL;
547
548     if (nargs != 1) {
549         xsltGenericError(xsltGenericErrorContext,
550                 "function-available() : expects one string arg\n");
551         ctxt->error = XPATH_INVALID_ARITY;
552         return;
553     }
554     if ((ctxt->value == NULL) || (ctxt->value->type != XPATH_STRING)) {
555         xsltGenericError(xsltGenericErrorContext,
556             "function-available invalid arg expecting a string\n");
557         ctxt->error = XPATH_INVALID_TYPE;
558         return;
559     }
560     obj = valuePop(ctxt);
561
562     name = xmlSplitQName2(obj->stringval, &prefix);
563     if (name == NULL) {
564         name = xmlStrdup(obj->stringval);
565     } else {
566         nsURI = xmlXPathNsLookup(ctxt->context, prefix);
567         if (nsURI == NULL) {
568             xsltGenericError(xsltGenericErrorContext,
569                 "function-available(): prefix %s is not bound\n", prefix);
570         }
571     }
572
573     if (xmlXPathFunctionLookupNS(ctxt->context, name, nsURI) != NULL) {
574         valuePush(ctxt, xmlXPathNewBoolean(1));
575     } else {
576         valuePush(ctxt, xmlXPathNewBoolean(0));
577     }
578
579     xmlXPathFreeObject(obj);
580     if (name != NULL)
581         xmlFree(name);
582     if (prefix != NULL)
583         xmlFree(prefix);
584 }
585
586 /**
587  * xsltCurrentFunction:
588  * @ctxt:  the XPath Parser context
589  * @nargs:  the number of arguments
590  *
591  * Implement the current() XSLT function
592  *   node-set current()
593  */
594 static void
595 xsltCurrentFunction(xmlXPathParserContextPtr ctxt, int nargs){
596     xsltTransformContextPtr tctxt;
597
598     if (nargs != 0) {
599         xsltGenericError(xsltGenericErrorContext,
600                 "document() : function uses no argument\n");
601         ctxt->error = XPATH_INVALID_ARITY;
602         return;
603     }
604     tctxt = (xsltTransformContextPtr) ctxt->context->extra;
605     if (tctxt == NULL) {
606         xsltGenericError(xsltGenericErrorContext,
607                 "current() : internal error tctxt == NULL\n");
608         valuePush(ctxt, xmlXPathNewNodeSet(NULL));
609     } else {
610         valuePush(ctxt, xmlXPathNewNodeSet(tctxt->node)); /* current */
611     }
612 }
613
614 /**
615  * xsltRegisterAllFunctions:
616  * @ctxt:  the XPath context
617  *
618  * Registers all default XSLT functions in this context
619  */
620 void
621 xsltRegisterAllFunctions(xmlXPathContextPtr ctxt) {
622     xmlXPathRegisterFunc(ctxt, (const xmlChar *)"current",
623                          xsltCurrentFunction);
624     xmlXPathRegisterFunc(ctxt, (const xmlChar *)"document",
625                          xsltDocumentFunction);
626     xmlXPathRegisterFunc(ctxt, (const xmlChar *)"key",
627                          xsltKeyFunction);
628     xmlXPathRegisterFunc(ctxt, (const xmlChar *)"unparsed-entity-uri",
629                          xsltUnparsedEntityURIFunction);
630     xmlXPathRegisterFunc(ctxt, (const xmlChar *)"format-number",
631                          xsltFormatNumberFunction);
632     xmlXPathRegisterFunc(ctxt, (const xmlChar *)"generate-id",
633                          xsltGenerateIdFunction);
634     xmlXPathRegisterFunc(ctxt, (const xmlChar *)"system-property",
635                          xsltSystemPropertyFunction);
636     xmlXPathRegisterFunc(ctxt, (const xmlChar *)"element-available",
637                          xsltElementAvailableFunction);
638     xmlXPathRegisterFunc(ctxt, (const xmlChar *)"function-available",
639                          xsltFunctionAvailableFunction);
640 }