1#define IN_LIBEXSLT
2#include "libexslt/libexslt.h"
3
4#if defined(WIN32) && !defined (__CYGWIN__) && (!__MINGW32__)
5#include <win32config.h>
6#else
7#include "config.h"
8#endif
9
10#include <libxml/tree.h>
11#include <libxml/xpath.h>
12#include <libxml/xpathInternals.h>
13#include <libxml/parser.h>
14#include <libxml/encoding.h>
15#include <libxml/uri.h>
16
17#include <libxslt/xsltconfig.h>
18#include <libxslt/xsltutils.h>
19#include <libxslt/xsltInternals.h>
20#include <libxslt/extensions.h>
21
22#include "exslt.h"
23
24/**
25 * exsltStrTokenizeFunction:
26 * @ctxt: an XPath parser context
27 * @nargs: the number of arguments
28 *
29 * Splits up a string on the characters of the delimiter string and returns a
30 * node set of token elements, each containing one token from the string.
31 */
32static void
33exsltStrTokenizeFunction(xmlXPathParserContextPtr ctxt, int nargs)
34{
35    xsltTransformContextPtr tctxt;
36    xmlChar *str, *delimiters, *cur;
37    const xmlChar *token, *delimiter;
38    xmlNodePtr node;
39    xmlDocPtr container;
40    xmlXPathObjectPtr ret = NULL;
41    int clen;
42
43    if ((nargs < 1) || (nargs > 2)) {
44        xmlXPathSetArityError(ctxt);
45        return;
46    }
47
48    if (nargs == 2) {
49        delimiters = xmlXPathPopString(ctxt);
50        if (xmlXPathCheckError(ctxt))
51            return;
52    } else {
53        delimiters = xmlStrdup((const xmlChar *) "\t\r\n ");
54    }
55    if (delimiters == NULL)
56        return;
57
58    str = xmlXPathPopString(ctxt);
59    if (xmlXPathCheckError(ctxt) || (str == NULL)) {
60        xmlFree(delimiters);
61        return;
62    }
63
64    /* Return a result tree fragment */
65    tctxt = xsltXPathGetTransformContext(ctxt);
66    if (tctxt == NULL) {
67        xsltTransformError(xsltXPathGetTransformContext(ctxt), NULL, NULL,
68	      "exslt:tokenize : internal error tctxt == NULL\n");
69	goto fail;
70    }
71
72    container = xsltCreateRVT(tctxt);
73    if (container != NULL) {
74        xsltRegisterLocalRVT(tctxt, container);
75        ret = xmlXPathNewNodeSet(NULL);
76        if (ret != NULL) {
77            for (cur = str, token = str; *cur != 0; cur += clen) {
78	        clen = xmlUTF8Size(cur);
79		if (*delimiters == 0) {	/* empty string case */
80		    xmlChar ctmp;
81		    ctmp = *(cur+clen);
82		    *(cur+clen) = 0;
83                    node = xmlNewDocRawNode(container, NULL,
84                                       (const xmlChar *) "token", cur);
85		    xmlAddChild((xmlNodePtr) container, node);
86		    xmlXPathNodeSetAddUnique(ret->nodesetval, node);
87                    *(cur+clen) = ctmp; /* restore the changed byte */
88                    token = cur + clen;
89                } else for (delimiter = delimiters; *delimiter != 0;
90				delimiter += xmlUTF8Size(delimiter)) {
91                    if (!xmlUTF8Charcmp(cur, delimiter)) {
92                        if (cur == token) {
93                            /* discard empty tokens */
94                            token = cur + clen;
95                            break;
96                        }
97                        *cur = 0;	/* terminate the token */
98                        node = xmlNewDocRawNode(container, NULL,
99                                           (const xmlChar *) "token", token);
100			xmlAddChild((xmlNodePtr) container, node);
101			xmlXPathNodeSetAddUnique(ret->nodesetval, node);
102                        *cur = *delimiter; /* restore the changed byte */
103                        token = cur + clen;
104                        break;
105                    }
106                }
107            }
108            if (token != cur) {
109	    	node = xmlNewDocRawNode(container, NULL,
110				    (const xmlChar *) "token", token);
111                xmlAddChild((xmlNodePtr) container, node);
112	        xmlXPathNodeSetAddUnique(ret->nodesetval, node);
113            }
114	    /*
115	     * Mark it as a function result in order to avoid garbage
116	     * collecting of tree fragments
117	     */
118	    xsltExtensionInstructionResultRegister(tctxt, ret);
119        }
120    }
121
122fail:
123    if (str != NULL)
124        xmlFree(str);
125    if (delimiters != NULL)
126        xmlFree(delimiters);
127    if (ret != NULL)
128        valuePush(ctxt, ret);
129    else
130        valuePush(ctxt, xmlXPathNewNodeSet(NULL));
131}
132
133/**
134 * exsltStrSplitFunction:
135 * @ctxt: an XPath parser context
136 * @nargs: the number of arguments
137 *
138 * Splits up a string on a delimiting string and returns a node set of token
139 * elements, each containing one token from the string.
140 */
141static void
142exsltStrSplitFunction(xmlXPathParserContextPtr ctxt, int nargs) {
143    xsltTransformContextPtr tctxt;
144    xmlChar *str, *delimiter, *cur;
145    const xmlChar *token;
146    xmlNodePtr node;
147    xmlDocPtr container;
148    xmlXPathObjectPtr ret = NULL;
149    int delimiterLength;
150
151    if ((nargs < 1) || (nargs > 2)) {
152        xmlXPathSetArityError(ctxt);
153        return;
154    }
155
156    if (nargs == 2) {
157        delimiter = xmlXPathPopString(ctxt);
158        if (xmlXPathCheckError(ctxt))
159            return;
160    } else {
161        delimiter = xmlStrdup((const xmlChar *) " ");
162    }
163    if (delimiter == NULL)
164        return;
165    delimiterLength = xmlStrlen (delimiter);
166
167    str = xmlXPathPopString(ctxt);
168    if (xmlXPathCheckError(ctxt) || (str == NULL)) {
169        xmlFree(delimiter);
170        return;
171    }
172
173    /* Return a result tree fragment */
174    tctxt = xsltXPathGetTransformContext(ctxt);
175    if (tctxt == NULL) {
176        xsltTransformError(xsltXPathGetTransformContext(ctxt), NULL, NULL,
177	      "exslt:tokenize : internal error tctxt == NULL\n");
178	goto fail;
179    }
180
181    /*
182    * OPTIMIZE TODO: We are creating an xmlDoc for every split!
183    */
184    container = xsltCreateRVT(tctxt);
185    if (container != NULL) {
186        xsltRegisterLocalRVT(tctxt, container);
187        ret = xmlXPathNewNodeSet(NULL);
188        if (ret != NULL) {
189            for (cur = str, token = str; *cur != 0; cur++) {
190		if (delimiterLength == 0) {
191		    if (cur != token) {
192			xmlChar tmp = *cur;
193			*cur = 0;
194                        node = xmlNewDocRawNode(container, NULL,
195                                           (const xmlChar *) "token", token);
196			xmlAddChild((xmlNodePtr) container, node);
197			xmlXPathNodeSetAddUnique(ret->nodesetval, node);
198			*cur = tmp;
199			token++;
200		    }
201		}
202		else if (!xmlStrncasecmp(cur, delimiter, delimiterLength)) {
203		    if (cur == token) {
204			/* discard empty tokens */
205			cur = cur + delimiterLength - 1;
206			token = cur + 1;
207			continue;
208		    }
209		    *cur = 0;
210		    node = xmlNewDocRawNode(container, NULL,
211				       (const xmlChar *) "token", token);
212		    xmlAddChild((xmlNodePtr) container, node);
213		    xmlXPathNodeSetAddUnique(ret->nodesetval, node);
214		    *cur = *delimiter;
215		    cur = cur + delimiterLength - 1;
216		    token = cur + 1;
217                }
218            }
219	    if (token != cur) {
220		node = xmlNewDocRawNode(container, NULL,
221				   (const xmlChar *) "token", token);
222		xmlAddChild((xmlNodePtr) container, node);
223		xmlXPathNodeSetAddUnique(ret->nodesetval, node);
224	    }
225	    /*
226	     * Mark it as a function result in order to avoid garbage
227	     * collecting of tree fragments
228	     */
229	    xsltExtensionInstructionResultRegister(tctxt, ret);
230        }
231    }
232
233fail:
234    if (str != NULL)
235        xmlFree(str);
236    if (delimiter != NULL)
237        xmlFree(delimiter);
238    if (ret != NULL)
239        valuePush(ctxt, ret);
240    else
241        valuePush(ctxt, xmlXPathNewNodeSet(NULL));
242}
243
244/**
245 * exsltStrEncodeUriFunction:
246 * @ctxt: an XPath parser context
247 * @nargs: the number of arguments
248 *
249 * URI-Escapes a string
250 */
251static void
252exsltStrEncodeUriFunction (xmlXPathParserContextPtr ctxt, int nargs) {
253    int escape_all = 1, str_len = 0;
254    xmlChar *str = NULL, *ret = NULL, *tmp;
255
256    if ((nargs < 2) || (nargs > 3)) {
257	xmlXPathSetArityError(ctxt);
258	return;
259    }
260
261    if (nargs >= 3) {
262        /* check for UTF-8 if encoding was explicitly given;
263           we don't support anything else yet */
264        tmp = xmlXPathPopString(ctxt);
265        if (xmlUTF8Strlen(tmp) != 5 || xmlStrcmp((const xmlChar *)"UTF-8",tmp)) {
266	    xmlXPathReturnEmptyString(ctxt);
267	    xmlFree(tmp);
268	    return;
269	}
270	xmlFree(tmp);
271    }
272
273    escape_all = xmlXPathPopBoolean(ctxt);
274
275    str = xmlXPathPopString(ctxt);
276    str_len = xmlUTF8Strlen(str);
277
278    if (str_len == 0) {
279	xmlXPathReturnEmptyString(ctxt);
280	xmlFree(str);
281	return;
282    }
283
284    ret = xmlURIEscapeStr(str,(const xmlChar *)(escape_all?"-_.!~*'()":"-_.!~*'();/?:@&=+$,[]"));
285    xmlXPathReturnString(ctxt, ret);
286
287    if (str != NULL)
288	xmlFree(str);
289}
290
291/**
292 * exsltStrDecodeUriFunction:
293 * @ctxt: an XPath parser context
294 * @nargs: the number of arguments
295 *
296 * reverses URI-Escaping of a string
297 */
298static void
299exsltStrDecodeUriFunction (xmlXPathParserContextPtr ctxt, int nargs) {
300    int str_len = 0;
301    xmlChar *str = NULL, *ret = NULL, *tmp;
302
303    if ((nargs < 1) || (nargs > 2)) {
304	xmlXPathSetArityError(ctxt);
305	return;
306    }
307
308    if (nargs >= 2) {
309        /* check for UTF-8 if encoding was explicitly given;
310           we don't support anything else yet */
311        tmp = xmlXPathPopString(ctxt);
312        if (xmlUTF8Strlen(tmp) != 5 || xmlStrcmp((const xmlChar *)"UTF-8",tmp)) {
313	    xmlXPathReturnEmptyString(ctxt);
314	    xmlFree(tmp);
315	    return;
316	}
317	xmlFree(tmp);
318    }
319
320    str = xmlXPathPopString(ctxt);
321    str_len = xmlUTF8Strlen(str);
322
323    if (str_len == 0) {
324	xmlXPathReturnEmptyString(ctxt);
325	xmlFree(str);
326	return;
327    }
328
329    ret = (xmlChar *) xmlURIUnescapeString((const char *)str,0,NULL);
330    if (!xmlCheckUTF8(ret)) {
331	/* FIXME: instead of throwing away the whole URI, we should
332        only discard the invalid sequence(s). How to do that? */
333	xmlXPathReturnEmptyString(ctxt);
334	xmlFree(str);
335	xmlFree(ret);
336	return;
337    }
338
339    xmlXPathReturnString(ctxt, ret);
340
341    if (str != NULL)
342	xmlFree(str);
343}
344
345/**
346 * exsltStrPaddingFunction:
347 * @ctxt: an XPath parser context
348 * @nargs: the number of arguments
349 *
350 * Creates a padding string of a certain length.
351 */
352static void
353exsltStrPaddingFunction (xmlXPathParserContextPtr ctxt, int nargs) {
354    int number, str_len = 0;
355    xmlChar *str = NULL, *ret = NULL, *tmp;
356
357    if ((nargs < 1) || (nargs > 2)) {
358	xmlXPathSetArityError(ctxt);
359	return;
360    }
361
362    if (nargs == 2) {
363	str = xmlXPathPopString(ctxt);
364	str_len = xmlUTF8Strlen(str);
365    }
366    if (str_len == 0) {
367	if (str != NULL) xmlFree(str);
368	str = xmlStrdup((const xmlChar *) " ");
369	str_len = 1;
370    }
371
372    number = (int) xmlXPathPopNumber(ctxt);
373
374    if (number <= 0) {
375	xmlXPathReturnEmptyString(ctxt);
376	xmlFree(str);
377	return;
378    }
379
380    while (number >= str_len) {
381	ret = xmlStrncat(ret, str, str_len);
382	number -= str_len;
383    }
384    tmp = xmlUTF8Strndup (str, number);
385    ret = xmlStrcat(ret, tmp);
386    if (tmp != NULL)
387	xmlFree (tmp);
388
389    xmlXPathReturnString(ctxt, ret);
390
391    if (str != NULL)
392	xmlFree(str);
393}
394
395/**
396 * exsltStrAlignFunction:
397 * @ctxt: an XPath parser context
398 * @nargs: the number of arguments
399 *
400 * Aligns a string within another string.
401 */
402static void
403exsltStrAlignFunction (xmlXPathParserContextPtr ctxt, int nargs) {
404    xmlChar *str, *padding, *alignment, *ret;
405    int str_l, padding_l;
406
407    if ((nargs < 2) || (nargs > 3)) {
408	xmlXPathSetArityError(ctxt);
409	return;
410    }
411
412    if (nargs == 3)
413	alignment = xmlXPathPopString(ctxt);
414    else
415	alignment = NULL;
416
417    padding = xmlXPathPopString(ctxt);
418    str = xmlXPathPopString(ctxt);
419
420    str_l = xmlUTF8Strlen (str);
421    padding_l = xmlUTF8Strlen (padding);
422
423    if (str_l == padding_l) {
424	xmlXPathReturnString (ctxt, str);
425	xmlFree(padding);
426	xmlFree(alignment);
427	return;
428    }
429
430    if (str_l > padding_l) {
431	ret = xmlUTF8Strndup (str, padding_l);
432    } else {
433	if (xmlStrEqual(alignment, (const xmlChar *) "right")) {
434	    ret = xmlUTF8Strndup (padding, padding_l - str_l);
435	    ret = xmlStrcat (ret, str);
436	} else if (xmlStrEqual(alignment, (const xmlChar *) "center")) {
437	    int left = (padding_l - str_l) / 2;
438	    int right_start;
439
440	    ret = xmlUTF8Strndup (padding, left);
441	    ret = xmlStrcat (ret, str);
442
443	    right_start = xmlUTF8Strsize (padding, left + str_l);
444	    ret = xmlStrcat (ret, padding + right_start);
445	} else {
446	    int str_s;
447
448	    str_s = xmlStrlen (str);
449	    ret = xmlStrdup (str);
450	    ret = xmlStrcat (ret, padding + str_s);
451	}
452    }
453
454    xmlXPathReturnString (ctxt, ret);
455
456    xmlFree(str);
457    xmlFree(padding);
458    xmlFree(alignment);
459}
460
461/**
462 * exsltStrConcatFunction:
463 * @ctxt: an XPath parser context
464 * @nargs: the number of arguments
465 *
466 * Takes a node set and returns the concatenation of the string values
467 * of the nodes in that node set.  If the node set is empty, it
468 * returns an empty string.
469 */
470static void
471exsltStrConcatFunction (xmlXPathParserContextPtr ctxt, int nargs) {
472    xmlXPathObjectPtr obj;
473    xmlChar *ret = NULL;
474    int i;
475
476    if (nargs  != 1) {
477	xmlXPathSetArityError(ctxt);
478	return;
479    }
480
481    if (!xmlXPathStackIsNodeSet(ctxt)) {
482	xmlXPathSetTypeError(ctxt);
483	return;
484    }
485
486    obj = valuePop (ctxt);
487
488    if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
489	xmlXPathReturnEmptyString(ctxt);
490	return;
491    }
492
493    for (i = 0; i < obj->nodesetval->nodeNr; i++) {
494	xmlChar *tmp;
495	tmp = xmlXPathCastNodeToString(obj->nodesetval->nodeTab[i]);
496
497	ret = xmlStrcat (ret, tmp);
498
499	xmlFree(tmp);
500    }
501
502    xmlXPathFreeObject (obj);
503
504    xmlXPathReturnString(ctxt, ret);
505}
506
507/**
508 * exsltStrReplaceInternal:
509 * @str: string to modify
510 * @searchStr: string to find
511 * @replaceStr: string to replace occurrences of searchStr
512 *
513 * Search and replace string function used by exsltStrReplaceFunction
514 */
515static xmlChar*
516exsltStrReplaceInternal(const xmlChar* str, const xmlChar* searchStr,
517                        const xmlChar* replaceStr)
518{
519    const xmlChar *curr, *next;
520    xmlChar *ret = NULL;
521    int searchStrSize;
522
523    curr = str;
524    searchStrSize = xmlStrlen(searchStr);
525
526    do {
527      next = xmlStrstr(curr, searchStr);
528      if (next == NULL) {
529        ret = xmlStrcat (ret, curr);
530        break;
531      }
532
533      ret = xmlStrncat (ret, curr, next - curr);
534      ret = xmlStrcat (ret, replaceStr);
535      curr = next + searchStrSize;
536    } while (*curr != 0);
537
538    return ret;
539}
540/**
541 * exsltStrReplaceFunction:
542 * @ctxt: an XPath parser context
543 * @nargs: the number of arguments
544 *
545 * Takes a string, and two node sets and returns the string with all strings in
546 * the first node set replaced by all strings in the second node set.
547 */
548static void
549exsltStrReplaceFunction (xmlXPathParserContextPtr ctxt, int nargs) {
550    xmlChar *str = NULL, *searchStr = NULL, *replaceStr = NULL;
551    xmlNodeSetPtr replaceSet = NULL, searchSet = NULL;
552    xmlChar *ret = NULL, *retSwap = NULL;
553    int i;
554
555    if (nargs  != 3) {
556      xmlXPathSetArityError(ctxt);
557      return;
558    }
559
560    /* pull out replace argument */
561    if (!xmlXPathStackIsNodeSet(ctxt)) {
562      replaceStr = xmlXPathPopString(ctxt);
563    }
564		else {
565      replaceSet = xmlXPathPopNodeSet(ctxt);
566      if (xmlXPathCheckError(ctxt)) {
567        xmlXPathSetTypeError(ctxt);
568        goto fail;
569      }
570    }
571
572    /* behavior driven by search argument from here on */
573    if (!xmlXPathStackIsNodeSet(ctxt)) {
574      searchStr = xmlXPathPopString(ctxt);
575      str = xmlXPathPopString(ctxt);
576
577      if (replaceStr == NULL) {
578        xmlXPathSetTypeError(ctxt);
579        goto fail;
580      }
581
582      ret = exsltStrReplaceInternal(str, searchStr, replaceStr);
583    }
584		else {
585      searchSet = xmlXPathPopNodeSet(ctxt);
586      if (searchSet == NULL || xmlXPathCheckError(ctxt)) {
587        xmlXPathSetTypeError(ctxt);
588        goto fail;
589      }
590
591      str = xmlXPathPopString(ctxt);
592      ret = xmlStrdup(str);
593
594      for (i = 0; i < searchSet->nodeNr; i++) {
595	searchStr = xmlXPathCastNodeToString(searchSet->nodeTab[i]);
596
597        if (replaceSet != NULL) {
598          replaceStr = NULL;
599          if (i < replaceSet->nodeNr) {
600            replaceStr = xmlXPathCastNodeToString(replaceSet->nodeTab[i]);
601          }
602
603          retSwap = exsltStrReplaceInternal(ret, searchStr, replaceStr);
604
605          if (replaceStr != NULL) {
606            xmlFree(replaceStr);
607            replaceStr = NULL;
608          }
609        }
610        else {
611          retSwap = exsltStrReplaceInternal(ret, searchStr, replaceStr);
612        }
613
614				xmlFree(ret);
615        if (searchStr != NULL) {
616          xmlFree(searchStr);
617          searchStr = NULL;
618        }
619
620				ret = retSwap;
621			}
622
623      if (replaceSet != NULL)
624        xmlXPathFreeNodeSet(replaceSet);
625
626      if (searchSet != NULL)
627        xmlXPathFreeNodeSet(searchSet);
628		}
629
630    xmlXPathReturnString(ctxt, ret);
631
632 fail:
633    if (replaceStr != NULL)
634      xmlFree(replaceStr);
635
636    if (searchStr != NULL)
637      xmlFree(searchStr);
638
639    if (str != NULL)
640      xmlFree(str);
641}
642
643/**
644 * exsltStrRegister:
645 *
646 * Registers the EXSLT - Strings module
647 */
648
649void
650exsltStrRegister (void) {
651    xsltRegisterExtModuleFunction ((const xmlChar *) "tokenize",
652				   EXSLT_STRINGS_NAMESPACE,
653				   exsltStrTokenizeFunction);
654    xsltRegisterExtModuleFunction ((const xmlChar *) "split",
655				   EXSLT_STRINGS_NAMESPACE,
656				   exsltStrSplitFunction);
657    xsltRegisterExtModuleFunction ((const xmlChar *) "encode-uri",
658				   EXSLT_STRINGS_NAMESPACE,
659				   exsltStrEncodeUriFunction);
660    xsltRegisterExtModuleFunction ((const xmlChar *) "decode-uri",
661				   EXSLT_STRINGS_NAMESPACE,
662				   exsltStrDecodeUriFunction);
663    xsltRegisterExtModuleFunction ((const xmlChar *) "padding",
664				   EXSLT_STRINGS_NAMESPACE,
665				   exsltStrPaddingFunction);
666    xsltRegisterExtModuleFunction ((const xmlChar *) "align",
667				   EXSLT_STRINGS_NAMESPACE,
668				   exsltStrAlignFunction);
669    xsltRegisterExtModuleFunction ((const xmlChar *) "concat",
670				   EXSLT_STRINGS_NAMESPACE,
671				   exsltStrConcatFunction);
672    xsltRegisterExtModuleFunction ((const xmlChar *) "replace",
673				   EXSLT_STRINGS_NAMESPACE,
674				   exsltStrReplaceFunction);
675}
676
677/**
678 * exsltStrXpathCtxtRegister:
679 *
680 * Registers the EXSLT - Strings module for use outside XSLT
681 */
682int
683exsltStrXpathCtxtRegister (xmlXPathContextPtr ctxt, const xmlChar *prefix)
684{
685    if (ctxt
686        && prefix
687        && !xmlXPathRegisterNs(ctxt,
688                               prefix,
689                               (const xmlChar *) EXSLT_STRINGS_NAMESPACE)
690        && !xmlXPathRegisterFuncNS(ctxt,
691                                   (const xmlChar *) "encode-uri",
692                                   (const xmlChar *) EXSLT_STRINGS_NAMESPACE,
693                                   exsltStrEncodeUriFunction)
694        && !xmlXPathRegisterFuncNS(ctxt,
695                                   (const xmlChar *) "decode-uri",
696                                   (const xmlChar *) EXSLT_STRINGS_NAMESPACE,
697                                   exsltStrDecodeUriFunction)
698        && !xmlXPathRegisterFuncNS(ctxt,
699                                   (const xmlChar *) "padding",
700                                   (const xmlChar *) EXSLT_STRINGS_NAMESPACE,
701                                   exsltStrPaddingFunction)
702        && !xmlXPathRegisterFuncNS(ctxt,
703                                   (const xmlChar *) "align",
704                                   (const xmlChar *) EXSLT_STRINGS_NAMESPACE,
705                                   exsltStrAlignFunction)
706        && !xmlXPathRegisterFuncNS(ctxt,
707                                   (const xmlChar *) "concat",
708                                   (const xmlChar *) EXSLT_STRINGS_NAMESPACE,
709                                   exsltStrConcatFunction)
710        && !xmlXPathRegisterFuncNS(ctxt,
711                                   (const xmlChar *) "replace",
712                                   (const xmlChar *) EXSLT_STRINGS_NAMESPACE,
713                                   exsltStrReplaceFunction)) {
714        return 0;
715    }
716    return -1;
717}
718