1/*
2 * attrvt.c: Implementation of the XSL Transformation 1.0 engine
3 *           attribute value template handling part.
4 *
5 * References:
6 *   http://www.w3.org/TR/1999/REC-xslt-19991116
7 *
8 *   Michael Kay "XSLT Programmer's Reference" pp 637-643
9 *   Writing Multiple Output Files
10 *
11 * See Copyright for the status of this software.
12 *
13 * daniel@veillard.com
14 */
15
16#define IN_LIBXSLT
17#include "libxslt.h"
18
19#include <string.h>
20
21#include <libxml/xmlmemory.h>
22#include <libxml/tree.h>
23#include <libxml/xpath.h>
24#include <libxml/xpathInternals.h>
25#include "xslt.h"
26#include "xsltutils.h"
27#include "xsltInternals.h"
28#include "templates.h"
29
30#ifdef WITH_XSLT_DEBUG
31#define WITH_XSLT_DEBUG_AVT
32#endif
33
34#define MAX_AVT_SEG 10
35
36typedef struct _xsltAttrVT xsltAttrVT;
37typedef xsltAttrVT *xsltAttrVTPtr;
38struct _xsltAttrVT {
39    struct _xsltAttrVT *next; /* next xsltAttrVT */
40    int nb_seg;		/* Number of segments */
41    int max_seg;	/* max capacity before re-alloc needed */
42    int strstart;	/* is the start a string */
43    /*
44     * the namespaces in scope
45     */
46    xmlNsPtr *nsList;
47    int nsNr;
48    /*
49     * the content is an alternate of string and xmlXPathCompExprPtr
50     */
51    void *segments[MAX_AVT_SEG];
52};
53
54/**
55 * xsltNewAttrVT:
56 * @style:  a XSLT process context
57 *
58 * Build a new xsltAttrVT structure
59 *
60 * Returns the structure or NULL in case of error
61 */
62static xsltAttrVTPtr
63xsltNewAttrVT(xsltStylesheetPtr style) {
64    xsltAttrVTPtr cur;
65
66    cur = (xsltAttrVTPtr) xmlMalloc(sizeof(xsltAttrVT));
67    if (cur == NULL) {
68	xsltTransformError(NULL, style, NULL,
69		"xsltNewAttrVTPtr : malloc failed\n");
70	if (style != NULL) style->errors++;
71	return(NULL);
72    }
73    memset(cur, 0, sizeof(xsltAttrVT));
74
75    cur->nb_seg = 0;
76    cur->max_seg = MAX_AVT_SEG;
77    cur->strstart = 0;
78    cur->next = style->attVTs;
79    /*
80     * Note: this pointer may be changed by a re-alloc within xsltCompileAttr,
81     * so that code may change the stylesheet pointer also!
82     */
83    style->attVTs = (xsltAttrVTPtr) cur;
84
85    return(cur);
86}
87
88/**
89 * xsltFreeAttrVT:
90 * @avt: pointer to an xsltAttrVT structure
91 *
92 * Free up the memory associated to the attribute value template
93 */
94static void
95xsltFreeAttrVT(xsltAttrVTPtr avt) {
96    int i;
97
98    if (avt == NULL) return;
99
100    if (avt->strstart == 1) {
101	for (i = 0;i < avt->nb_seg; i += 2)
102	    if (avt->segments[i] != NULL)
103		xmlFree((xmlChar *) avt->segments[i]);
104	for (i = 1;i < avt->nb_seg; i += 2)
105	    xmlXPathFreeCompExpr((xmlXPathCompExprPtr) avt->segments[i]);
106    } else {
107	for (i = 0;i < avt->nb_seg; i += 2)
108	    xmlXPathFreeCompExpr((xmlXPathCompExprPtr) avt->segments[i]);
109	for (i = 1;i < avt->nb_seg; i += 2)
110	    if (avt->segments[i] != NULL)
111		xmlFree((xmlChar *) avt->segments[i]);
112    }
113    if (avt->nsList != NULL)
114        xmlFree(avt->nsList);
115    xmlFree(avt);
116}
117
118/**
119 * xsltFreeAVTList:
120 * @avt: pointer to an list of AVT structures
121 *
122 * Free up the memory associated to the attribute value templates
123 */
124void
125xsltFreeAVTList(void *avt) {
126    xsltAttrVTPtr cur = (xsltAttrVTPtr) avt, next;
127
128    while (cur != NULL) {
129        next = cur->next;
130	xsltFreeAttrVT(cur);
131	cur = next;
132    }
133}
134/**
135 * xsltSetAttrVTsegment:
136 * @ avt: pointer to an xsltAttrVT structure
137 * @ val: the value to be set to the next available segment
138 *
139 * Within xsltCompileAttr there are several places where a value
140 * needs to be added to the 'segments' array within the xsltAttrVT
141 * structure, and at each place the allocated size may have to be
142 * re-allocated.  This routine takes care of that situation.
143 *
144 * Returns the avt pointer, which may have been changed by a re-alloc
145 */
146static xsltAttrVTPtr
147xsltSetAttrVTsegment(xsltAttrVTPtr avt, void *val) {
148    if (avt->nb_seg >= avt->max_seg) {
149	avt = (xsltAttrVTPtr) xmlRealloc(avt, sizeof(xsltAttrVT) +
150			avt->max_seg * sizeof(void *));
151	if (avt == NULL) {
152	    return NULL;
153	}
154	memset(&avt->segments[avt->nb_seg], 0, MAX_AVT_SEG*sizeof(void *));
155	avt->max_seg += MAX_AVT_SEG;
156    }
157    avt->segments[avt->nb_seg++] = val;
158    return avt;
159}
160
161/**
162 * xsltCompileAttr:
163 * @style:  a XSLT process context
164 * @attr: the attribute coming from the stylesheet.
165 *
166 * Precompile an attribute in a stylesheet, basically it checks if it is
167 * an attrubute value template, and if yes establish some structures needed
168 * to process it at transformation time.
169 */
170void
171xsltCompileAttr(xsltStylesheetPtr style, xmlAttrPtr attr) {
172    const xmlChar *str;
173    const xmlChar *cur;
174    xmlChar *ret = NULL;
175    xmlChar *expr = NULL;
176    xsltAttrVTPtr avt;
177    int i = 0, lastavt = 0;
178
179    if ((style == NULL) || (attr == NULL) || (attr->children == NULL))
180        return;
181    if ((attr->children->type != XML_TEXT_NODE) ||
182        (attr->children->next != NULL)) {
183        xsltTransformError(NULL, style, attr->parent,
184	    "Attribute '%s': The content is expected to be a single text "
185	    "node when compiling an AVT.\n", attr->name);
186	style->errors++;
187	return;
188    }
189    str = attr->children->content;
190    if ((xmlStrchr(str, '{') == NULL) &&
191        (xmlStrchr(str, '}') == NULL)) return;
192
193#ifdef WITH_XSLT_DEBUG_AVT
194    xsltGenericDebug(xsltGenericDebugContext,
195		    "Found AVT %s: %s\n", attr->name, str);
196#endif
197    if (attr->psvi != NULL) {
198#ifdef WITH_XSLT_DEBUG_AVT
199	xsltGenericDebug(xsltGenericDebugContext,
200			"AVT %s: already compiled\n", attr->name);
201#endif
202        return;
203    }
204    /*
205    * Create a new AVT object.
206    */
207    avt = xsltNewAttrVT(style);
208    if (avt == NULL)
209	return;
210    attr->psvi = avt;
211
212    avt->nsList = xmlGetNsList(attr->doc, attr->parent);
213    if (avt->nsList != NULL) {
214	while (avt->nsList[i] != NULL)
215	    i++;
216    }
217    avt->nsNr = i;
218
219    cur = str;
220    while (*cur != 0) {
221	if (*cur == '{') {
222	    if (*(cur+1) == '{') {	/* escaped '{' */
223	        cur++;
224		ret = xmlStrncat(ret, str, cur - str);
225		cur++;
226		str = cur;
227		continue;
228	    }
229	    if (*(cur+1) == '}') {	/* skip empty AVT */
230		ret = xmlStrncat(ret, str, cur - str);
231	        cur += 2;
232		str = cur;
233		continue;
234	    }
235	    if ((ret != NULL) || (cur - str > 0)) {
236		ret = xmlStrncat(ret, str, cur - str);
237		str = cur;
238		if (avt->nb_seg == 0)
239		    avt->strstart = 1;
240		if ((avt = xsltSetAttrVTsegment(avt, (void *) ret)) == NULL)
241		    goto error;
242		ret = NULL;
243		lastavt = 0;
244	    }
245
246	    cur++;
247	    while ((*cur != 0) && (*cur != '}')) {
248		/* Need to check for literal (bug539741) */
249		if ((*cur == '\'') || (*cur == '"')) {
250		    char delim = *(cur++);
251		    while ((*cur != 0) && (*cur != delim))
252			cur++;
253		    if (*cur != 0)
254			cur++;	/* skip the ending delimiter */
255		} else
256		    cur++;
257	    }
258	    if (*cur == 0) {
259	        xsltTransformError(NULL, style, attr->parent,
260		     "Attribute '%s': The AVT has an unmatched '{'.\n",
261		     attr->name);
262		style->errors++;
263		goto error;
264	    }
265	    str++;
266	    expr = xmlStrndup(str, cur - str);
267	    if (expr == NULL) {
268		/*
269		* TODO: What needs to be done here?
270		*/
271	        XSLT_TODO
272		goto error;
273	    } else {
274		xmlXPathCompExprPtr comp;
275
276		comp = xsltXPathCompile(style, expr);
277		if (comp == NULL) {
278		    xsltTransformError(NULL, style, attr->parent,
279			 "Attribute '%s': Failed to compile the expression "
280			 "'%s' in the AVT.\n", attr->name, expr);
281		    style->errors++;
282		    goto error;
283		}
284		if (avt->nb_seg == 0)
285		    avt->strstart = 0;
286		if (lastavt == 1) {
287		    if ((avt = xsltSetAttrVTsegment(avt, NULL)) == NULL)
288		        goto error;
289		}
290		if ((avt = xsltSetAttrVTsegment(avt, (void *) comp)) == NULL)
291		    goto error;
292		lastavt = 1;
293		xmlFree(expr);
294		expr = NULL;
295	    }
296	    cur++;
297	    str = cur;
298	} else if (*cur == '}') {
299	    cur++;
300	    if (*cur == '}') {	/* escaped '}' */
301		ret = xmlStrncat(ret, str, cur - str);
302		cur++;
303		str = cur;
304		continue;
305	    } else {
306	        xsltTransformError(NULL, style, attr->parent,
307		     "Attribute '%s': The AVT has an unmatched '}'.\n",
308		     attr->name);
309		goto error;
310	    }
311	} else
312	    cur++;
313    }
314    if ((ret != NULL) || (cur - str > 0)) {
315	ret = xmlStrncat(ret, str, cur - str);
316	str = cur;
317	if (avt->nb_seg == 0)
318	    avt->strstart = 1;
319	if ((avt = xsltSetAttrVTsegment(avt, (void *) ret)) == NULL)
320	    goto error;
321	ret = NULL;
322    }
323
324error:
325    if (avt == NULL) {
326        xsltTransformError(NULL, style, attr->parent,
327		"xsltCompileAttr: malloc problem\n");
328    } else {
329        if (attr->psvi != avt) {  /* may have changed from realloc */
330            attr->psvi = avt;
331	    /*
332	     * This is a "hack", but I can't see any clean method of
333	     * doing it.  If a re-alloc has taken place, then the pointer
334	     * for this AVT may have changed.  style->attVTs was set by
335	     * xsltNewAttrVT, so it needs to be re-set to the new value!
336	     */
337	    style->attVTs = avt;
338	}
339    }
340    if (ret != NULL)
341	xmlFree(ret);
342    if (expr != NULL)
343	xmlFree(expr);
344}
345
346
347/**
348 * xsltEvalAVT:
349 * @ctxt: the XSLT transformation context
350 * @avt: the prevompiled attribute value template info
351 * @node: the node hosting the attribute
352 *
353 * Process the given AVT, and return the new string value.
354 *
355 * Returns the computed string value or NULL, must be deallocated by the
356 *         caller.
357 */
358xmlChar *
359xsltEvalAVT(xsltTransformContextPtr ctxt, void *avt, xmlNodePtr node) {
360    xmlChar *ret = NULL, *tmp;
361    xmlXPathCompExprPtr comp;
362    xsltAttrVTPtr cur = (xsltAttrVTPtr) avt;
363    int i;
364    int str;
365
366    if ((ctxt == NULL) || (avt == NULL) || (node == NULL))
367        return(NULL);
368    str = cur->strstart;
369    for (i = 0;i < cur->nb_seg;i++) {
370        if (str) {
371	    ret = xmlStrcat(ret, (const xmlChar *) cur->segments[i]);
372	} else {
373	    comp = (xmlXPathCompExprPtr) cur->segments[i];
374	    tmp = xsltEvalXPathStringNs(ctxt, comp, cur->nsNr, cur->nsList);
375	    if (tmp != NULL) {
376	        if (ret != NULL) {
377		    ret = xmlStrcat(ret, tmp);
378		    xmlFree(tmp);
379		} else {
380		    ret = tmp;
381		}
382	    }
383	}
384	str = !str;
385    }
386    return(ret);
387}
388