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        xsltRegisterTmpRVT(tctxt, container);
75        ret = xmlXPathNewNodeSet(NULL);
76        if (ret != NULL) {
77            ret->boolval = 0; /* Freeing is not handled there anymore */
78            for (cur = str, token = str; *cur != 0; cur += clen) {
79	        clen = xmlUTF8Size(cur);
80		if (*delimiters == 0) {	/* empty string case */
81		    xmlChar ctmp;
82		    ctmp = *(cur+clen);
83		    *(cur+clen) = 0;
84                    node = xmlNewDocRawNode(container, NULL,
85                                       (const xmlChar *) "token", cur);
86		    xmlAddChild((xmlNodePtr) container, node);
87		    xmlXPathNodeSetAddUnique(ret->nodesetval, node);
88                    *(cur+clen) = ctmp; /* restore the changed byte */
89                    token = cur + clen;
90                } else for (delimiter = delimiters; *delimiter != 0;
91				delimiter += xmlUTF8Size(delimiter)) {
92                    if (!xmlUTF8Charcmp(cur, delimiter)) {
93                        if (cur == token) {
94                            /* discard empty tokens */
95                            token = cur + clen;
96                            break;
97                        }
98                        *cur = 0;	/* terminate the token */
99                        node = xmlNewDocRawNode(container, NULL,
100                                           (const xmlChar *) "token", token);
101			xmlAddChild((xmlNodePtr) container, node);
102			xmlXPathNodeSetAddUnique(ret->nodesetval, node);
103                        *cur = *delimiter; /* restore the changed byte */
104                        token = cur + clen;
105                        break;
106                    }
107                }
108            }
109            if (token != cur) {
110	    	node = xmlNewDocRawNode(container, NULL,
111				    (const xmlChar *) "token", token);
112                xmlAddChild((xmlNodePtr) container, node);
113	        xmlXPathNodeSetAddUnique(ret->nodesetval, node);
114            }
115        }
116    }
117
118fail:
119    if (str != NULL)
120        xmlFree(str);
121    if (delimiters != NULL)
122        xmlFree(delimiters);
123    if (ret != NULL)
124        valuePush(ctxt, ret);
125    else
126        valuePush(ctxt, xmlXPathNewNodeSet(NULL));
127}
128
129/**
130 * exsltStrSplitFunction:
131 * @ctxt: an XPath parser context
132 * @nargs: the number of arguments
133 *
134 * Splits up a string on a delimiting string and returns a node set of token
135 * elements, each containing one token from the string.
136 */
137static void
138exsltStrSplitFunction(xmlXPathParserContextPtr ctxt, int nargs) {
139    xsltTransformContextPtr tctxt;
140    xmlChar *str, *delimiter, *cur;
141    const xmlChar *token;
142    xmlNodePtr node;
143    xmlDocPtr container;
144    xmlXPathObjectPtr ret = NULL;
145    int delimiterLength;
146
147    if ((nargs < 1) || (nargs > 2)) {
148        xmlXPathSetArityError(ctxt);
149        return;
150    }
151
152    if (nargs == 2) {
153        delimiter = xmlXPathPopString(ctxt);
154        if (xmlXPathCheckError(ctxt))
155            return;
156    } else {
157        delimiter = xmlStrdup((const xmlChar *) " ");
158    }
159    if (delimiter == NULL)
160        return;
161    delimiterLength = xmlStrlen (delimiter);
162
163    str = xmlXPathPopString(ctxt);
164    if (xmlXPathCheckError(ctxt) || (str == NULL)) {
165        xmlFree(delimiter);
166        return;
167    }
168
169    /* Return a result tree fragment */
170    tctxt = xsltXPathGetTransformContext(ctxt);
171    if (tctxt == NULL) {
172        xsltTransformError(xsltXPathGetTransformContext(ctxt), NULL, NULL,
173	      "exslt:tokenize : internal error tctxt == NULL\n");
174	goto fail;
175    }
176
177    container = xsltCreateRVT(tctxt);
178    if (container != NULL) {
179        xsltRegisterTmpRVT(tctxt, container);
180        ret = xmlXPathNewNodeSet(NULL);
181        if (ret != NULL) {
182            ret->boolval = 0; /* Freeing is not handled there anymore */
183            for (cur = str, token = str; *cur != 0; cur++) {
184		if (delimiterLength == 0) {
185		    if (cur != token) {
186			xmlChar tmp = *cur;
187			*cur = 0;
188                        node = xmlNewDocRawNode(container, NULL,
189                                           (const xmlChar *) "token", token);
190			xmlAddChild((xmlNodePtr) container, node);
191			xmlXPathNodeSetAddUnique(ret->nodesetval, node);
192			*cur = tmp;
193			token++;
194		    }
195		}
196		else if (!xmlStrncasecmp(cur, delimiter, delimiterLength)) {
197		    if (cur == token) {
198			/* discard empty tokens */
199			cur = cur + delimiterLength - 1;
200			token = cur + 1;
201			continue;
202		    }
203		    *cur = 0;
204		    node = xmlNewDocRawNode(container, NULL,
205				       (const xmlChar *) "token", token);
206		    xmlAddChild((xmlNodePtr) container, node);
207		    xmlXPathNodeSetAddUnique(ret->nodesetval, node);
208		    *cur = *delimiter;
209		    cur = cur + delimiterLength - 1;
210		    token = cur + 1;
211                }
212            }
213	    if (token != cur) {
214		node = xmlNewDocRawNode(container, NULL,
215				   (const xmlChar *) "token", token);
216		xmlAddChild((xmlNodePtr) container, node);
217		xmlXPathNodeSetAddUnique(ret->nodesetval, node);
218	    }
219        }
220    }
221
222fail:
223    if (str != NULL)
224        xmlFree(str);
225    if (delimiter != NULL)
226        xmlFree(delimiter);
227    if (ret != NULL)
228        valuePush(ctxt, ret);
229    else
230        valuePush(ctxt, xmlXPathNewNodeSet(NULL));
231}
232
233/**
234 * exsltStrEncodeUriFunction:
235 * @ctxt: an XPath parser context
236 * @nargs: the number of arguments
237 *
238 * URI-Escapes a string
239 */
240static void
241exsltStrEncodeUriFunction (xmlXPathParserContextPtr ctxt, int nargs) {
242    int escape_all = 1, str_len = 0;
243    xmlChar *str = NULL, *ret = NULL, *tmp;
244
245    if ((nargs < 2) || (nargs > 3)) {
246	xmlXPathSetArityError(ctxt);
247	return;
248    }
249
250    if (nargs >= 3) {
251        /* check for UTF-8 if encoding was explicitly given;
252           we don't support anything else yet */
253        tmp = xmlXPathPopString(ctxt);
254        if (xmlUTF8Strlen(tmp) != 5 || xmlStrcmp((const xmlChar *)"UTF-8",tmp)) {
255	    xmlXPathReturnEmptyString(ctxt);
256	    xmlFree(tmp);
257	    return;
258	}
259	xmlFree(tmp);
260    }
261
262    escape_all = xmlXPathPopBoolean(ctxt);
263
264    str = xmlXPathPopString(ctxt);
265    str_len = xmlUTF8Strlen(str);
266
267    if (str_len == 0) {
268	xmlXPathReturnEmptyString(ctxt);
269	xmlFree(str);
270	return;
271    }
272
273    ret = xmlURIEscapeStr(str,(const xmlChar *)(escape_all?"-_.!~*'()":"-_.!~*'();/?:@&=+$,[]"));
274    xmlXPathReturnString(ctxt, ret);
275
276    if (str != NULL)
277	xmlFree(str);
278}
279
280/**
281 * exsltStrDecodeUriFunction:
282 * @ctxt: an XPath parser context
283 * @nargs: the number of arguments
284 *
285 * reverses URI-Escaping of a string
286 */
287static void
288exsltStrDecodeUriFunction (xmlXPathParserContextPtr ctxt, int nargs) {
289    int str_len = 0;
290    xmlChar *str = NULL, *ret = NULL, *tmp;
291
292    if ((nargs < 1) || (nargs > 2)) {
293	xmlXPathSetArityError(ctxt);
294	return;
295    }
296
297    if (nargs >= 2) {
298        /* check for UTF-8 if encoding was explicitly given;
299           we don't support anything else yet */
300        tmp = xmlXPathPopString(ctxt);
301        if (xmlUTF8Strlen(tmp) != 5 || xmlStrcmp((const xmlChar *)"UTF-8",tmp)) {
302	    xmlXPathReturnEmptyString(ctxt);
303	    xmlFree(tmp);
304	    return;
305	}
306	xmlFree(tmp);
307    }
308
309    str = xmlXPathPopString(ctxt);
310    str_len = xmlUTF8Strlen(str);
311
312    if (str_len == 0) {
313	xmlXPathReturnEmptyString(ctxt);
314	xmlFree(str);
315	return;
316    }
317
318    ret = (xmlChar *) xmlURIUnescapeString((const char *)str,0,NULL);
319    if (!xmlCheckUTF8(ret)) {
320	/* FIXME: instead of throwing away the whole URI, we should
321        only discard the invalid sequence(s). How to do that? */
322	xmlXPathReturnEmptyString(ctxt);
323	xmlFree(str);
324	xmlFree(ret);
325	return;
326    }
327
328    xmlXPathReturnString(ctxt, ret);
329
330    if (str != NULL)
331	xmlFree(str);
332}
333
334/**
335 * exsltStrPaddingFunction:
336 * @ctxt: an XPath parser context
337 * @nargs: the number of arguments
338 *
339 * Creates a padding string of a certain length.
340 */
341static void
342exsltStrPaddingFunction (xmlXPathParserContextPtr ctxt, int nargs) {
343    int number, str_len = 0;
344    xmlChar *str = NULL, *ret = NULL, *tmp;
345
346    if ((nargs < 1) || (nargs > 2)) {
347	xmlXPathSetArityError(ctxt);
348	return;
349    }
350
351    if (nargs == 2) {
352	str = xmlXPathPopString(ctxt);
353	str_len = xmlUTF8Strlen(str);
354    }
355    if (str_len == 0) {
356	if (str != NULL) xmlFree(str);
357	str = xmlStrdup((const xmlChar *) " ");
358	str_len = 1;
359    }
360
361    number = (int) xmlXPathPopNumber(ctxt);
362
363    if (number <= 0) {
364	xmlXPathReturnEmptyString(ctxt);
365	xmlFree(str);
366	return;
367    }
368
369    while (number >= str_len) {
370	ret = xmlStrncat(ret, str, str_len);
371	number -= str_len;
372    }
373    tmp = xmlUTF8Strndup (str, number);
374    ret = xmlStrcat(ret, tmp);
375    if (tmp != NULL)
376	xmlFree (tmp);
377
378    xmlXPathReturnString(ctxt, ret);
379
380    if (str != NULL)
381	xmlFree(str);
382}
383
384/**
385 * exsltStrAlignFunction:
386 * @ctxt: an XPath parser context
387 * @nargs: the number of arguments
388 *
389 * Aligns a string within another string.
390 */
391static void
392exsltStrAlignFunction (xmlXPathParserContextPtr ctxt, int nargs) {
393    xmlChar *str, *padding, *alignment, *ret;
394    int str_l, padding_l;
395
396    if ((nargs < 2) || (nargs > 3)) {
397	xmlXPathSetArityError(ctxt);
398	return;
399    }
400
401    if (nargs == 3)
402	alignment = xmlXPathPopString(ctxt);
403    else
404	alignment = NULL;
405
406    padding = xmlXPathPopString(ctxt);
407    str = xmlXPathPopString(ctxt);
408
409    str_l = xmlUTF8Strlen (str);
410    padding_l = xmlUTF8Strlen (padding);
411
412    if (str_l == padding_l) {
413	xmlXPathReturnString (ctxt, str);
414	xmlFree(padding);
415	xmlFree(alignment);
416	return;
417    }
418
419    if (str_l > padding_l) {
420	ret = xmlUTF8Strndup (str, padding_l);
421    } else {
422	if (xmlStrEqual(alignment, (const xmlChar *) "right")) {
423	    ret = xmlUTF8Strndup (padding, padding_l - str_l);
424	    ret = xmlStrcat (ret, str);
425	} else if (xmlStrEqual(alignment, (const xmlChar *) "center")) {
426	    int left = (padding_l - str_l) / 2;
427	    int right_start;
428
429	    ret = xmlUTF8Strndup (padding, left);
430	    ret = xmlStrcat (ret, str);
431
432	    right_start = xmlUTF8Strsize (padding, left + str_l);
433	    ret = xmlStrcat (ret, padding + right_start);
434	} else {
435	    int str_s;
436
437	    str_s = xmlStrlen (str);
438	    ret = xmlStrdup (str);
439	    ret = xmlStrcat (ret, padding + str_s);
440	}
441    }
442
443    xmlXPathReturnString (ctxt, ret);
444
445    xmlFree(str);
446    xmlFree(padding);
447    xmlFree(alignment);
448}
449
450/**
451 * exsltStrConcatFunction:
452 * @ctxt: an XPath parser context
453 * @nargs: the number of arguments
454 *
455 * Takes a node set and returns the concatenation of the string values
456 * of the nodes in that node set.  If the node set is empty, it
457 * returns an empty string.
458 */
459static void
460exsltStrConcatFunction (xmlXPathParserContextPtr ctxt, int nargs) {
461    xmlXPathObjectPtr obj;
462    xmlChar *ret = NULL;
463    int i;
464
465    if (nargs  != 1) {
466	xmlXPathSetArityError(ctxt);
467	return;
468    }
469
470    if (!xmlXPathStackIsNodeSet(ctxt)) {
471	xmlXPathSetTypeError(ctxt);
472	return;
473    }
474
475    obj = valuePop (ctxt);
476
477    if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
478	xmlXPathReturnEmptyString(ctxt);
479	return;
480    }
481
482    for (i = 0; i < obj->nodesetval->nodeNr; i++) {
483	xmlChar *tmp;
484	tmp = xmlXPathCastNodeToString(obj->nodesetval->nodeTab[i]);
485
486	ret = xmlStrcat (ret, tmp);
487
488	xmlFree(tmp);
489    }
490
491    xmlXPathFreeObject (obj);
492
493    xmlXPathReturnString(ctxt, ret);
494}
495
496/**
497 * exsltStrRegister:
498 *
499 * Registers the EXSLT - Strings module
500 */
501
502void
503exsltStrRegister (void) {
504    xsltRegisterExtModuleFunction ((const xmlChar *) "tokenize",
505				   EXSLT_STRINGS_NAMESPACE,
506				   exsltStrTokenizeFunction);
507    xsltRegisterExtModuleFunction ((const xmlChar *) "split",
508				   EXSLT_STRINGS_NAMESPACE,
509				   exsltStrSplitFunction);
510    xsltRegisterExtModuleFunction ((const xmlChar *) "encode-uri",
511				   EXSLT_STRINGS_NAMESPACE,
512				   exsltStrEncodeUriFunction);
513    xsltRegisterExtModuleFunction ((const xmlChar *) "decode-uri",
514				   EXSLT_STRINGS_NAMESPACE,
515				   exsltStrDecodeUriFunction);
516    xsltRegisterExtModuleFunction ((const xmlChar *) "padding",
517				   EXSLT_STRINGS_NAMESPACE,
518				   exsltStrPaddingFunction);
519    xsltRegisterExtModuleFunction ((const xmlChar *) "align",
520				   EXSLT_STRINGS_NAMESPACE,
521				   exsltStrAlignFunction);
522    xsltRegisterExtModuleFunction ((const xmlChar *) "concat",
523				   EXSLT_STRINGS_NAMESPACE,
524				   exsltStrConcatFunction);
525}
526