/src/libxslt/libxslt/templates.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * templates.c: Implementation of the template processing |
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.com |
10 | | */ |
11 | | |
12 | | #define IN_LIBXSLT |
13 | | #include "libxslt.h" |
14 | | |
15 | | #include <string.h> |
16 | | |
17 | | #include <libxml/xmlmemory.h> |
18 | | #include <libxml/globals.h> |
19 | | #include <libxml/xmlerror.h> |
20 | | #include <libxml/tree.h> |
21 | | #include <libxml/dict.h> |
22 | | #include <libxml/xpathInternals.h> |
23 | | #include <libxml/parserInternals.h> |
24 | | #include "xslt.h" |
25 | | #include "xsltInternals.h" |
26 | | #include "xsltutils.h" |
27 | | #include "variables.h" |
28 | | #include "functions.h" |
29 | | #include "templates.h" |
30 | | #include "transform.h" |
31 | | #include "namespaces.h" |
32 | | #include "attributes.h" |
33 | | |
34 | | #ifdef WITH_XSLT_DEBUG |
35 | | #define WITH_XSLT_DEBUG_TEMPLATES |
36 | | #endif |
37 | | |
38 | | /************************************************************************ |
39 | | * * |
40 | | * Module interfaces * |
41 | | * * |
42 | | ************************************************************************/ |
43 | | |
44 | | /** |
45 | | * xsltEvalXPathPredicate: |
46 | | * @ctxt: the XSLT transformation context |
47 | | * @comp: the XPath compiled expression |
48 | | * @nsList: the namespaces in scope |
49 | | * @nsNr: the number of namespaces in scope |
50 | | * |
51 | | * Process the expression using XPath and evaluate the result as |
52 | | * an XPath predicate |
53 | | * |
54 | | * Returns 1 is the predicate was true, 0 otherwise |
55 | | */ |
56 | | int |
57 | | xsltEvalXPathPredicate(xsltTransformContextPtr ctxt, xmlXPathCompExprPtr comp, |
58 | 0 | xmlNsPtr *nsList, int nsNr) { |
59 | 0 | int ret; |
60 | 0 | xmlXPathObjectPtr res; |
61 | 0 | int oldNsNr; |
62 | 0 | xmlNsPtr *oldNamespaces; |
63 | 0 | xmlNodePtr oldInst; |
64 | 0 | int oldProximityPosition, oldContextSize; |
65 | |
|
66 | 0 | if ((ctxt == NULL) || (ctxt->inst == NULL)) { |
67 | 0 | xsltTransformError(ctxt, NULL, NULL, |
68 | 0 | "xsltEvalXPathPredicate: No context or instruction\n"); |
69 | 0 | return(0); |
70 | 0 | } |
71 | | |
72 | 0 | oldContextSize = ctxt->xpathCtxt->contextSize; |
73 | 0 | oldProximityPosition = ctxt->xpathCtxt->proximityPosition; |
74 | 0 | oldNsNr = ctxt->xpathCtxt->nsNr; |
75 | 0 | oldNamespaces = ctxt->xpathCtxt->namespaces; |
76 | 0 | oldInst = ctxt->inst; |
77 | |
|
78 | 0 | ctxt->xpathCtxt->node = ctxt->node; |
79 | 0 | ctxt->xpathCtxt->namespaces = nsList; |
80 | 0 | ctxt->xpathCtxt->nsNr = nsNr; |
81 | |
|
82 | 0 | res = xmlXPathCompiledEval(comp, ctxt->xpathCtxt); |
83 | |
|
84 | 0 | if (res != NULL) { |
85 | 0 | ret = xmlXPathEvalPredicate(ctxt->xpathCtxt, res); |
86 | 0 | xmlXPathFreeObject(res); |
87 | | #ifdef WITH_XSLT_DEBUG_TEMPLATES |
88 | | XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext, |
89 | | "xsltEvalXPathPredicate: returns %d\n", ret)); |
90 | | #endif |
91 | 0 | } else { |
92 | | #ifdef WITH_XSLT_DEBUG_TEMPLATES |
93 | | XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext, |
94 | | "xsltEvalXPathPredicate: failed\n")); |
95 | | #endif |
96 | 0 | ctxt->state = XSLT_STATE_STOPPED; |
97 | 0 | ret = 0; |
98 | 0 | } |
99 | 0 | ctxt->xpathCtxt->nsNr = oldNsNr; |
100 | |
|
101 | 0 | ctxt->xpathCtxt->namespaces = oldNamespaces; |
102 | 0 | ctxt->inst = oldInst; |
103 | 0 | ctxt->xpathCtxt->contextSize = oldContextSize; |
104 | 0 | ctxt->xpathCtxt->proximityPosition = oldProximityPosition; |
105 | |
|
106 | 0 | return(ret); |
107 | 0 | } |
108 | | |
109 | | /** |
110 | | * xsltEvalXPathStringNs: |
111 | | * @ctxt: the XSLT transformation context |
112 | | * @comp: the compiled XPath expression |
113 | | * @nsNr: the number of namespaces in the list |
114 | | * @nsList: the list of in-scope namespaces to use |
115 | | * |
116 | | * Process the expression using XPath, allowing to pass a namespace mapping |
117 | | * context and get a string |
118 | | * |
119 | | * Returns the computed string value or NULL, must be deallocated by the |
120 | | * caller. |
121 | | */ |
122 | | xmlChar * |
123 | | xsltEvalXPathStringNs(xsltTransformContextPtr ctxt, xmlXPathCompExprPtr comp, |
124 | 0 | int nsNr, xmlNsPtr *nsList) { |
125 | 0 | xmlChar *ret = NULL; |
126 | 0 | xmlXPathObjectPtr res; |
127 | 0 | xmlNodePtr oldInst; |
128 | 0 | xmlNodePtr oldNode; |
129 | 0 | int oldPos, oldSize; |
130 | 0 | int oldNsNr; |
131 | 0 | xmlNsPtr *oldNamespaces; |
132 | |
|
133 | 0 | if ((ctxt == NULL) || (ctxt->inst == NULL)) { |
134 | 0 | xsltTransformError(ctxt, NULL, NULL, |
135 | 0 | "xsltEvalXPathStringNs: No context or instruction\n"); |
136 | 0 | return(0); |
137 | 0 | } |
138 | | |
139 | 0 | oldInst = ctxt->inst; |
140 | 0 | oldNode = ctxt->node; |
141 | 0 | oldPos = ctxt->xpathCtxt->proximityPosition; |
142 | 0 | oldSize = ctxt->xpathCtxt->contextSize; |
143 | 0 | oldNsNr = ctxt->xpathCtxt->nsNr; |
144 | 0 | oldNamespaces = ctxt->xpathCtxt->namespaces; |
145 | |
|
146 | 0 | ctxt->xpathCtxt->node = ctxt->node; |
147 | | /* TODO: do we need to propagate the namespaces here ? */ |
148 | 0 | ctxt->xpathCtxt->namespaces = nsList; |
149 | 0 | ctxt->xpathCtxt->nsNr = nsNr; |
150 | 0 | res = xmlXPathCompiledEval(comp, ctxt->xpathCtxt); |
151 | 0 | if (res != NULL) { |
152 | 0 | if (res->type != XPATH_STRING) |
153 | 0 | res = xmlXPathConvertString(res); |
154 | 0 | if (res->type == XPATH_STRING) { |
155 | 0 | ret = res->stringval; |
156 | 0 | res->stringval = NULL; |
157 | 0 | } else { |
158 | 0 | xsltTransformError(ctxt, NULL, NULL, |
159 | 0 | "xpath : string() function didn't return a String\n"); |
160 | 0 | } |
161 | 0 | xmlXPathFreeObject(res); |
162 | 0 | } else { |
163 | 0 | ctxt->state = XSLT_STATE_STOPPED; |
164 | 0 | } |
165 | | #ifdef WITH_XSLT_DEBUG_TEMPLATES |
166 | | XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext, |
167 | | "xsltEvalXPathString: returns %s\n", ret)); |
168 | | #endif |
169 | 0 | ctxt->inst = oldInst; |
170 | 0 | ctxt->node = oldNode; |
171 | 0 | ctxt->xpathCtxt->contextSize = oldSize; |
172 | 0 | ctxt->xpathCtxt->proximityPosition = oldPos; |
173 | 0 | ctxt->xpathCtxt->nsNr = oldNsNr; |
174 | 0 | ctxt->xpathCtxt->namespaces = oldNamespaces; |
175 | 0 | return(ret); |
176 | 0 | } |
177 | | |
178 | | /** |
179 | | * xsltEvalXPathString: |
180 | | * @ctxt: the XSLT transformation context |
181 | | * @comp: the compiled XPath expression |
182 | | * |
183 | | * Process the expression using XPath and get a string |
184 | | * |
185 | | * Returns the computed string value or NULL, must be deallocated by the |
186 | | * caller. |
187 | | */ |
188 | | xmlChar * |
189 | 0 | xsltEvalXPathString(xsltTransformContextPtr ctxt, xmlXPathCompExprPtr comp) { |
190 | 0 | return(xsltEvalXPathStringNs(ctxt, comp, 0, NULL)); |
191 | 0 | } |
192 | | |
193 | | /** |
194 | | * xsltEvalTemplateString: |
195 | | * @ctxt: the XSLT transformation context |
196 | | * @contextNode: the current node in the source tree |
197 | | * @inst: the XSLT instruction (xsl:comment, xsl:processing-instruction) |
198 | | * |
199 | | * Processes the sequence constructor of the given instruction on |
200 | | * @contextNode and converts the resulting tree to a string. |
201 | | * This is needed by e.g. xsl:comment and xsl:processing-instruction. |
202 | | * |
203 | | * Returns the computed string value or NULL; it's up to the caller to |
204 | | * free the result. |
205 | | */ |
206 | | xmlChar * |
207 | | xsltEvalTemplateString(xsltTransformContextPtr ctxt, |
208 | | xmlNodePtr contextNode, |
209 | | xmlNodePtr inst) |
210 | 0 | { |
211 | 0 | xmlNodePtr oldInsert, insert = NULL; |
212 | 0 | xmlChar *ret; |
213 | 0 | const xmlChar *oldLastText; |
214 | 0 | int oldLastTextSize, oldLastTextUse; |
215 | |
|
216 | 0 | if ((ctxt == NULL) || (contextNode == NULL) || (inst == NULL) || |
217 | 0 | (inst->type != XML_ELEMENT_NODE)) |
218 | 0 | return(NULL); |
219 | | |
220 | 0 | if (inst->children == NULL) |
221 | 0 | return(NULL); |
222 | | |
223 | | /* |
224 | | * This creates a temporary element-node to add the resulting |
225 | | * text content to. |
226 | | * OPTIMIZE TODO: Keep such an element-node in the transformation |
227 | | * context to avoid creating it every time. |
228 | | */ |
229 | 0 | insert = xmlNewDocNode(ctxt->output, NULL, |
230 | 0 | (const xmlChar *)"fake", NULL); |
231 | 0 | if (insert == NULL) { |
232 | 0 | xsltTransformError(ctxt, NULL, contextNode, |
233 | 0 | "Failed to create temporary node\n"); |
234 | 0 | return(NULL); |
235 | 0 | } |
236 | 0 | oldInsert = ctxt->insert; |
237 | 0 | ctxt->insert = insert; |
238 | 0 | oldLastText = ctxt->lasttext; |
239 | 0 | oldLastTextSize = ctxt->lasttsize; |
240 | 0 | oldLastTextUse = ctxt->lasttuse; |
241 | | /* |
242 | | * OPTIMIZE TODO: if inst->children consists only of text-nodes. |
243 | | */ |
244 | 0 | xsltApplyOneTemplate(ctxt, contextNode, inst->children, NULL, NULL); |
245 | |
|
246 | 0 | ctxt->insert = oldInsert; |
247 | 0 | ctxt->lasttext = oldLastText; |
248 | 0 | ctxt->lasttsize = oldLastTextSize; |
249 | 0 | ctxt->lasttuse = oldLastTextUse; |
250 | |
|
251 | 0 | ret = xmlNodeGetContent(insert); |
252 | 0 | if (insert != NULL) |
253 | 0 | xmlFreeNode(insert); |
254 | 0 | return(ret); |
255 | 0 | } |
256 | | |
257 | | /** |
258 | | * xsltAttrTemplateValueProcessNode: |
259 | | * @ctxt: the XSLT transformation context |
260 | | * @str: the attribute template node value |
261 | | * @inst: the instruction (or LRE) in the stylesheet holding the |
262 | | * attribute with an AVT |
263 | | * |
264 | | * Process the given string, allowing to pass a namespace mapping |
265 | | * context and return the new string value. |
266 | | * |
267 | | * Called by: |
268 | | * - xsltAttrTemplateValueProcess() (templates.c) |
269 | | * - xsltEvalAttrValueTemplate() (templates.c) |
270 | | * |
271 | | * QUESTION: Why is this function public? It is not used outside |
272 | | * of templates.c. |
273 | | * |
274 | | * Returns the computed string value or NULL, must be deallocated by the |
275 | | * caller. |
276 | | */ |
277 | | xmlChar * |
278 | | xsltAttrTemplateValueProcessNode(xsltTransformContextPtr ctxt, |
279 | | const xmlChar *str, xmlNodePtr inst) |
280 | 0 | { |
281 | 0 | xmlChar *ret = NULL; |
282 | 0 | const xmlChar *cur; |
283 | 0 | xmlChar *expr, *val; |
284 | 0 | xmlNsPtr *nsList = NULL; |
285 | 0 | int nsNr = 0; |
286 | |
|
287 | 0 | if (str == NULL) return(NULL); |
288 | 0 | if (*str == 0) |
289 | 0 | return(xmlStrndup((xmlChar *)"", 0)); |
290 | | |
291 | 0 | cur = str; |
292 | 0 | while (*cur != 0) { |
293 | 0 | if (*cur == '{') { |
294 | 0 | if (*(cur+1) == '{') { /* escaped '{' */ |
295 | 0 | cur++; |
296 | 0 | ret = xmlStrncat(ret, str, cur - str); |
297 | 0 | cur++; |
298 | 0 | str = cur; |
299 | 0 | continue; |
300 | 0 | } |
301 | 0 | ret = xmlStrncat(ret, str, cur - str); |
302 | 0 | str = cur; |
303 | 0 | cur++; |
304 | 0 | while ((*cur != 0) && (*cur != '}')) { |
305 | | /* Need to check for literal (bug539741) */ |
306 | 0 | if ((*cur == '\'') || (*cur == '"')) { |
307 | 0 | char delim = *(cur++); |
308 | 0 | while ((*cur != 0) && (*cur != delim)) |
309 | 0 | cur++; |
310 | 0 | if (*cur != 0) |
311 | 0 | cur++; /* skip the ending delimiter */ |
312 | 0 | } else |
313 | 0 | cur++; |
314 | 0 | } |
315 | 0 | if (*cur == 0) { |
316 | 0 | xsltTransformError(ctxt, NULL, inst, |
317 | 0 | "xsltAttrTemplateValueProcessNode: unmatched '{'\n"); |
318 | 0 | ret = xmlStrncat(ret, str, cur - str); |
319 | 0 | goto exit; |
320 | 0 | } |
321 | 0 | str++; |
322 | 0 | expr = xmlStrndup(str, cur - str); |
323 | 0 | if (expr == NULL) |
324 | 0 | goto exit; |
325 | 0 | else if (*expr == '{') { |
326 | 0 | ret = xmlStrcat(ret, expr); |
327 | 0 | xmlFree(expr); |
328 | 0 | } else { |
329 | 0 | xmlXPathCompExprPtr comp; |
330 | | /* |
331 | | * TODO: keep precompiled form around |
332 | | */ |
333 | 0 | if ((nsList == NULL) && (inst != NULL)) { |
334 | 0 | int i = 0; |
335 | |
|
336 | 0 | nsList = xmlGetNsList(inst->doc, inst); |
337 | 0 | if (nsList != NULL) { |
338 | 0 | while (nsList[i] != NULL) |
339 | 0 | i++; |
340 | 0 | nsNr = i; |
341 | 0 | } |
342 | 0 | } |
343 | 0 | comp = xmlXPathCtxtCompile(ctxt->xpathCtxt, expr); |
344 | 0 | val = xsltEvalXPathStringNs(ctxt, comp, nsNr, nsList); |
345 | 0 | xmlXPathFreeCompExpr(comp); |
346 | 0 | xmlFree(expr); |
347 | 0 | if (val != NULL) { |
348 | 0 | ret = xmlStrcat(ret, val); |
349 | 0 | xmlFree(val); |
350 | 0 | } |
351 | 0 | } |
352 | 0 | cur++; |
353 | 0 | str = cur; |
354 | 0 | } else if (*cur == '}') { |
355 | 0 | cur++; |
356 | 0 | if (*cur == '}') { /* escaped '}' */ |
357 | 0 | ret = xmlStrncat(ret, str, cur - str); |
358 | 0 | cur++; |
359 | 0 | str = cur; |
360 | 0 | continue; |
361 | 0 | } else { |
362 | 0 | xsltTransformError(ctxt, NULL, inst, |
363 | 0 | "xsltAttrTemplateValueProcessNode: unmatched '}'\n"); |
364 | 0 | } |
365 | 0 | } else |
366 | 0 | cur++; |
367 | 0 | } |
368 | 0 | if (cur != str) { |
369 | 0 | ret = xmlStrncat(ret, str, cur - str); |
370 | 0 | } |
371 | |
|
372 | 0 | exit: |
373 | 0 | if (nsList != NULL) |
374 | 0 | xmlFree(nsList); |
375 | |
|
376 | 0 | return(ret); |
377 | 0 | } |
378 | | |
379 | | /** |
380 | | * xsltAttrTemplateValueProcess: |
381 | | * @ctxt: the XSLT transformation context |
382 | | * @str: the attribute template node value |
383 | | * |
384 | | * Process the given node and return the new string value. |
385 | | * |
386 | | * Returns the computed string value or NULL, must be deallocated by the |
387 | | * caller. |
388 | | */ |
389 | | xmlChar * |
390 | 0 | xsltAttrTemplateValueProcess(xsltTransformContextPtr ctxt, const xmlChar *str) { |
391 | 0 | return(xsltAttrTemplateValueProcessNode(ctxt, str, NULL)); |
392 | 0 | } |
393 | | |
394 | | /** |
395 | | * xsltEvalAttrValueTemplate: |
396 | | * @ctxt: the XSLT transformation context |
397 | | * @inst: the instruction (or LRE) in the stylesheet holding the |
398 | | * attribute with an AVT |
399 | | * @name: the attribute QName |
400 | | * @ns: the attribute namespace URI |
401 | | * |
402 | | * Evaluate a attribute value template, i.e. the attribute value can |
403 | | * contain expressions contained in curly braces ({}) and those are |
404 | | * substituted by they computed value. |
405 | | * |
406 | | * Returns the computed string value or NULL, must be deallocated by the |
407 | | * caller. |
408 | | */ |
409 | | xmlChar * |
410 | | xsltEvalAttrValueTemplate(xsltTransformContextPtr ctxt, xmlNodePtr inst, |
411 | | const xmlChar *name, const xmlChar *ns) |
412 | 0 | { |
413 | 0 | xmlChar *ret; |
414 | 0 | xmlChar *expr; |
415 | |
|
416 | 0 | if ((ctxt == NULL) || (inst == NULL) || (name == NULL) || |
417 | 0 | (inst->type != XML_ELEMENT_NODE)) |
418 | 0 | return(NULL); |
419 | | |
420 | 0 | expr = xsltGetNsProp(inst, name, ns); |
421 | 0 | if (expr == NULL) |
422 | 0 | return(NULL); |
423 | | |
424 | | /* |
425 | | * TODO: though now {} is detected ahead, it would still be good to |
426 | | * optimize both functions to keep the splitted value if the |
427 | | * attribute content and the XPath precompiled expressions around |
428 | | */ |
429 | | |
430 | 0 | ret = xsltAttrTemplateValueProcessNode(ctxt, expr, inst); |
431 | | #ifdef WITH_XSLT_DEBUG_TEMPLATES |
432 | | XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext, |
433 | | "xsltEvalAttrValueTemplate: %s returns %s\n", expr, ret)); |
434 | | #endif |
435 | 0 | if (expr != NULL) |
436 | 0 | xmlFree(expr); |
437 | 0 | return(ret); |
438 | 0 | } |
439 | | |
440 | | /** |
441 | | * xsltEvalStaticAttrValueTemplate: |
442 | | * @style: the XSLT stylesheet |
443 | | * @inst: the instruction (or LRE) in the stylesheet holding the |
444 | | * attribute with an AVT |
445 | | * @name: the attribute Name |
446 | | * @ns: the attribute namespace URI |
447 | | * @found: indicator whether the attribute is present |
448 | | * |
449 | | * Check if an attribute value template has a static value, i.e. the |
450 | | * attribute value does not contain expressions contained in curly braces ({}) |
451 | | * |
452 | | * Returns the static string value or NULL, must be deallocated by the |
453 | | * caller. |
454 | | */ |
455 | | const xmlChar * |
456 | | xsltEvalStaticAttrValueTemplate(xsltStylesheetPtr style, xmlNodePtr inst, |
457 | 0 | const xmlChar *name, const xmlChar *ns, int *found) { |
458 | 0 | const xmlChar *ret; |
459 | 0 | xmlChar *expr; |
460 | |
|
461 | 0 | if ((style == NULL) || (inst == NULL) || (name == NULL) || |
462 | 0 | (inst->type != XML_ELEMENT_NODE)) |
463 | 0 | return(NULL); |
464 | | |
465 | 0 | expr = xsltGetNsProp(inst, name, ns); |
466 | 0 | if (expr == NULL) { |
467 | 0 | *found = 0; |
468 | 0 | return(NULL); |
469 | 0 | } |
470 | 0 | *found = 1; |
471 | |
|
472 | 0 | ret = xmlStrchr(expr, '{'); |
473 | 0 | if (ret != NULL) { |
474 | 0 | xmlFree(expr); |
475 | 0 | return(NULL); |
476 | 0 | } |
477 | 0 | ret = xmlDictLookup(style->dict, expr, -1); |
478 | 0 | xmlFree(expr); |
479 | 0 | return(ret); |
480 | 0 | } |
481 | | |
482 | | /** |
483 | | * xsltAttrTemplateProcess: |
484 | | * @ctxt: the XSLT transformation context |
485 | | * @target: the element where the attribute will be grafted |
486 | | * @attr: the attribute node of a literal result element |
487 | | * |
488 | | * Process one attribute of a Literal Result Element (in the stylesheet). |
489 | | * Evaluates Attribute Value Templates and copies the attribute over to |
490 | | * the result element. |
491 | | * This does *not* process attribute sets (xsl:use-attribute-set). |
492 | | * |
493 | | * |
494 | | * Returns the generated attribute node. |
495 | | */ |
496 | | xmlAttrPtr |
497 | | xsltAttrTemplateProcess(xsltTransformContextPtr ctxt, xmlNodePtr target, |
498 | | xmlAttrPtr attr) |
499 | 0 | { |
500 | 0 | const xmlChar *value; |
501 | 0 | xmlAttrPtr ret; |
502 | |
|
503 | 0 | if ((ctxt == NULL) || (attr == NULL) || (target == NULL) || |
504 | 0 | (target->type != XML_ELEMENT_NODE)) |
505 | 0 | return(NULL); |
506 | | |
507 | 0 | if (attr->type != XML_ATTRIBUTE_NODE) |
508 | 0 | return(NULL); |
509 | | |
510 | | /* |
511 | | * Skip all XSLT attributes. |
512 | | */ |
513 | | #ifdef XSLT_REFACTORED |
514 | | if (attr->psvi == xsltXSLTAttrMarker) |
515 | | return(NULL); |
516 | | #else |
517 | 0 | if ((attr->ns != NULL) && xmlStrEqual(attr->ns->href, XSLT_NAMESPACE)) |
518 | 0 | return(NULL); |
519 | 0 | #endif |
520 | | /* |
521 | | * Get the value. |
522 | | */ |
523 | 0 | if (attr->children != NULL) { |
524 | 0 | if ((attr->children->type != XML_TEXT_NODE) || |
525 | 0 | (attr->children->next != NULL)) |
526 | 0 | { |
527 | 0 | xsltTransformError(ctxt, NULL, attr->parent, |
528 | 0 | "Internal error: The children of an attribute node of a " |
529 | 0 | "literal result element are not in the expected form.\n"); |
530 | 0 | return(NULL); |
531 | 0 | } |
532 | 0 | value = attr->children->content; |
533 | 0 | if (value == NULL) |
534 | 0 | value = xmlDictLookup(ctxt->dict, BAD_CAST "", 0); |
535 | 0 | } else |
536 | 0 | value = xmlDictLookup(ctxt->dict, BAD_CAST "", 0); |
537 | | /* |
538 | | * Overwrite duplicates. |
539 | | */ |
540 | 0 | ret = target->properties; |
541 | 0 | while (ret != NULL) { |
542 | 0 | if (((attr->ns != NULL) == (ret->ns != NULL)) && |
543 | 0 | xmlStrEqual(ret->name, attr->name) && |
544 | 0 | ((attr->ns == NULL) || xmlStrEqual(ret->ns->href, attr->ns->href))) |
545 | 0 | { |
546 | 0 | break; |
547 | 0 | } |
548 | 0 | ret = ret->next; |
549 | 0 | } |
550 | 0 | if (ret != NULL) { |
551 | | /* free the existing value */ |
552 | 0 | xmlFreeNodeList(ret->children); |
553 | 0 | ret->children = ret->last = NULL; |
554 | | /* |
555 | | * Adjust ns-prefix if needed. |
556 | | */ |
557 | 0 | if ((ret->ns != NULL) && |
558 | 0 | (! xmlStrEqual(ret->ns->prefix, attr->ns->prefix))) |
559 | 0 | { |
560 | 0 | ret->ns = xsltGetNamespace(ctxt, attr->parent, attr->ns, target); |
561 | 0 | } |
562 | 0 | } else { |
563 | | /* create a new attribute */ |
564 | 0 | if (attr->ns != NULL) |
565 | 0 | ret = xmlNewNsProp(target, |
566 | 0 | xsltGetNamespace(ctxt, attr->parent, attr->ns, target), |
567 | 0 | attr->name, NULL); |
568 | 0 | else |
569 | 0 | ret = xmlNewNsProp(target, NULL, attr->name, NULL); |
570 | 0 | } |
571 | | /* |
572 | | * Set the value. |
573 | | */ |
574 | 0 | if (ret != NULL) { |
575 | 0 | xmlNodePtr text; |
576 | |
|
577 | 0 | text = xmlNewText(NULL); |
578 | 0 | if (text != NULL) { |
579 | 0 | ret->last = ret->children = text; |
580 | 0 | text->parent = (xmlNodePtr) ret; |
581 | 0 | text->doc = ret->doc; |
582 | |
|
583 | 0 | if (attr->psvi != NULL) { |
584 | | /* |
585 | | * Evaluate the Attribute Value Template. |
586 | | */ |
587 | 0 | xmlChar *val; |
588 | 0 | val = xsltEvalAVT(ctxt, attr->psvi, attr->parent); |
589 | 0 | if (val == NULL) { |
590 | | /* |
591 | | * TODO: Damn, we need an easy mechanism to report |
592 | | * qualified names! |
593 | | */ |
594 | 0 | if (attr->ns) { |
595 | 0 | xsltTransformError(ctxt, NULL, attr->parent, |
596 | 0 | "Internal error: Failed to evaluate the AVT " |
597 | 0 | "of attribute '{%s}%s'.\n", |
598 | 0 | attr->ns->href, attr->name); |
599 | 0 | } else { |
600 | 0 | xsltTransformError(ctxt, NULL, attr->parent, |
601 | 0 | "Internal error: Failed to evaluate the AVT " |
602 | 0 | "of attribute '%s'.\n", |
603 | 0 | attr->name); |
604 | 0 | } |
605 | 0 | text->content = xmlStrdup(BAD_CAST ""); |
606 | 0 | } else { |
607 | 0 | text->content = val; |
608 | 0 | } |
609 | 0 | } else if ((ctxt->internalized) && (target != NULL) && |
610 | 0 | (target->doc != NULL) && |
611 | 0 | (target->doc->dict == ctxt->dict) && |
612 | 0 | xmlDictOwns(ctxt->dict, value)) { |
613 | 0 | text->content = (xmlChar *) value; |
614 | 0 | } else { |
615 | 0 | text->content = xmlStrdup(value); |
616 | 0 | } |
617 | 0 | } |
618 | 0 | } else { |
619 | 0 | if (attr->ns) { |
620 | 0 | xsltTransformError(ctxt, NULL, attr->parent, |
621 | 0 | "Internal error: Failed to create attribute '{%s}%s'.\n", |
622 | 0 | attr->ns->href, attr->name); |
623 | 0 | } else { |
624 | 0 | xsltTransformError(ctxt, NULL, attr->parent, |
625 | 0 | "Internal error: Failed to create attribute '%s'.\n", |
626 | 0 | attr->name); |
627 | 0 | } |
628 | 0 | } |
629 | 0 | return(ret); |
630 | 0 | } |
631 | | |
632 | | |
633 | | /** |
634 | | * xsltAttrListTemplateProcess: |
635 | | * @ctxt: the XSLT transformation context |
636 | | * @target: the element where the attributes will be grafted |
637 | | * @attrs: the first attribute |
638 | | * |
639 | | * Processes all attributes of a Literal Result Element. |
640 | | * Attribute references are applied via xsl:use-attribute-set |
641 | | * attributes. |
642 | | * Copies all non XSLT-attributes over to the @target element |
643 | | * and evaluates Attribute Value Templates. |
644 | | * |
645 | | * Called by xsltApplySequenceConstructor() (transform.c). |
646 | | * |
647 | | * Returns a new list of attribute nodes, or NULL in case of error. |
648 | | * (Don't assign the result to @target->properties; if |
649 | | * the result is NULL, you'll get memory leaks, since the |
650 | | * attributes will be disattached.) |
651 | | */ |
652 | | xmlAttrPtr |
653 | | xsltAttrListTemplateProcess(xsltTransformContextPtr ctxt, |
654 | | xmlNodePtr target, xmlAttrPtr attrs) |
655 | 0 | { |
656 | 0 | xmlAttrPtr attr, copy, last = NULL; |
657 | 0 | xmlNodePtr oldInsert, text; |
658 | 0 | xmlNsPtr origNs = NULL, copyNs = NULL; |
659 | 0 | const xmlChar *value; |
660 | 0 | xmlChar *valueAVT; |
661 | 0 | int hasAttr = 0; |
662 | |
|
663 | 0 | if ((ctxt == NULL) || (target == NULL) || (attrs == NULL) || |
664 | 0 | (target->type != XML_ELEMENT_NODE)) |
665 | 0 | return(NULL); |
666 | | |
667 | 0 | oldInsert = ctxt->insert; |
668 | 0 | ctxt->insert = target; |
669 | | |
670 | | /* |
671 | | * Apply attribute-sets. |
672 | | */ |
673 | 0 | attr = attrs; |
674 | 0 | do { |
675 | | #ifdef XSLT_REFACTORED |
676 | | if ((attr->psvi == xsltXSLTAttrMarker) && |
677 | | xmlStrEqual(attr->name, (const xmlChar *)"use-attribute-sets")) |
678 | | { |
679 | | xsltApplyAttributeSet(ctxt, ctxt->node, (xmlNodePtr) attr, NULL); |
680 | | } |
681 | | #else |
682 | 0 | if ((attr->ns != NULL) && |
683 | 0 | xmlStrEqual(attr->name, (const xmlChar *)"use-attribute-sets") && |
684 | 0 | xmlStrEqual(attr->ns->href, XSLT_NAMESPACE)) |
685 | 0 | { |
686 | 0 | xsltApplyAttributeSet(ctxt, ctxt->node, (xmlNodePtr) attr, NULL); |
687 | 0 | } |
688 | 0 | #endif |
689 | 0 | attr = attr->next; |
690 | 0 | } while (attr != NULL); |
691 | |
|
692 | 0 | if (target->properties != NULL) { |
693 | 0 | hasAttr = 1; |
694 | 0 | } |
695 | | |
696 | | /* |
697 | | * Instantiate LRE-attributes. |
698 | | */ |
699 | 0 | attr = attrs; |
700 | 0 | do { |
701 | | /* |
702 | | * Skip XSLT attributes. |
703 | | */ |
704 | | #ifdef XSLT_REFACTORED |
705 | | if (attr->psvi == xsltXSLTAttrMarker) { |
706 | | goto next_attribute; |
707 | | } |
708 | | #else |
709 | 0 | if ((attr->ns != NULL) && |
710 | 0 | xmlStrEqual(attr->ns->href, XSLT_NAMESPACE)) |
711 | 0 | { |
712 | 0 | goto next_attribute; |
713 | 0 | } |
714 | 0 | #endif |
715 | | /* |
716 | | * Get the value. |
717 | | */ |
718 | 0 | if (attr->children != NULL) { |
719 | 0 | if ((attr->children->type != XML_TEXT_NODE) || |
720 | 0 | (attr->children->next != NULL)) |
721 | 0 | { |
722 | 0 | xsltTransformError(ctxt, NULL, attr->parent, |
723 | 0 | "Internal error: The children of an attribute node of a " |
724 | 0 | "literal result element are not in the expected form.\n"); |
725 | 0 | goto error; |
726 | 0 | } |
727 | 0 | value = attr->children->content; |
728 | 0 | if (value == NULL) |
729 | 0 | value = xmlDictLookup(ctxt->dict, BAD_CAST "", 0); |
730 | 0 | } else |
731 | 0 | value = xmlDictLookup(ctxt->dict, BAD_CAST "", 0); |
732 | | |
733 | | /* |
734 | | * Get the namespace. Avoid lookups of same namespaces. |
735 | | */ |
736 | 0 | if (attr->ns != origNs) { |
737 | 0 | origNs = attr->ns; |
738 | 0 | if (attr->ns != NULL) { |
739 | | #ifdef XSLT_REFACTORED |
740 | | copyNs = xsltGetSpecialNamespace(ctxt, attr->parent, |
741 | | attr->ns->href, attr->ns->prefix, target); |
742 | | #else |
743 | 0 | copyNs = xsltGetNamespace(ctxt, attr->parent, |
744 | 0 | attr->ns, target); |
745 | 0 | #endif |
746 | 0 | if (copyNs == NULL) |
747 | 0 | goto error; |
748 | 0 | } else |
749 | 0 | copyNs = NULL; |
750 | 0 | } |
751 | | /* |
752 | | * Create a new attribute. |
753 | | */ |
754 | 0 | if (hasAttr) { |
755 | 0 | copy = xmlSetNsProp(target, copyNs, attr->name, NULL); |
756 | 0 | } else { |
757 | | /* |
758 | | * Avoid checking for duplicate attributes if there aren't |
759 | | * any attribute sets. |
760 | | */ |
761 | 0 | copy = xmlNewDocProp(target->doc, attr->name, NULL); |
762 | |
|
763 | 0 | if (copy != NULL) { |
764 | 0 | copy->ns = copyNs; |
765 | | |
766 | | /* |
767 | | * Attach it to the target element. |
768 | | */ |
769 | 0 | copy->parent = target; |
770 | 0 | if (last == NULL) { |
771 | 0 | target->properties = copy; |
772 | 0 | last = copy; |
773 | 0 | } else { |
774 | 0 | last->next = copy; |
775 | 0 | copy->prev = last; |
776 | 0 | last = copy; |
777 | 0 | } |
778 | 0 | } |
779 | 0 | } |
780 | 0 | if (copy == NULL) { |
781 | 0 | if (attr->ns) { |
782 | 0 | xsltTransformError(ctxt, NULL, attr->parent, |
783 | 0 | "Internal error: Failed to create attribute '{%s}%s'.\n", |
784 | 0 | attr->ns->href, attr->name); |
785 | 0 | } else { |
786 | 0 | xsltTransformError(ctxt, NULL, attr->parent, |
787 | 0 | "Internal error: Failed to create attribute '%s'.\n", |
788 | 0 | attr->name); |
789 | 0 | } |
790 | 0 | goto error; |
791 | 0 | } |
792 | | |
793 | | /* |
794 | | * Set the value. |
795 | | */ |
796 | 0 | text = xmlNewText(NULL); |
797 | 0 | if (text != NULL) { |
798 | 0 | copy->last = copy->children = text; |
799 | 0 | text->parent = (xmlNodePtr) copy; |
800 | 0 | text->doc = copy->doc; |
801 | |
|
802 | 0 | if (attr->psvi != NULL) { |
803 | | /* |
804 | | * Evaluate the Attribute Value Template. |
805 | | */ |
806 | 0 | valueAVT = xsltEvalAVT(ctxt, attr->psvi, attr->parent); |
807 | 0 | if (valueAVT == NULL) { |
808 | | /* |
809 | | * TODO: Damn, we need an easy mechanism to report |
810 | | * qualified names! |
811 | | */ |
812 | 0 | if (attr->ns) { |
813 | 0 | xsltTransformError(ctxt, NULL, attr->parent, |
814 | 0 | "Internal error: Failed to evaluate the AVT " |
815 | 0 | "of attribute '{%s}%s'.\n", |
816 | 0 | attr->ns->href, attr->name); |
817 | 0 | } else { |
818 | 0 | xsltTransformError(ctxt, NULL, attr->parent, |
819 | 0 | "Internal error: Failed to evaluate the AVT " |
820 | 0 | "of attribute '%s'.\n", |
821 | 0 | attr->name); |
822 | 0 | } |
823 | 0 | text->content = xmlStrdup(BAD_CAST ""); |
824 | 0 | goto error; |
825 | 0 | } else { |
826 | 0 | text->content = valueAVT; |
827 | 0 | } |
828 | 0 | } else if ((ctxt->internalized) && |
829 | 0 | (target->doc != NULL) && |
830 | 0 | (target->doc->dict == ctxt->dict) && |
831 | 0 | xmlDictOwns(ctxt->dict, value)) |
832 | 0 | { |
833 | 0 | text->content = (xmlChar *) value; |
834 | 0 | } else { |
835 | 0 | text->content = xmlStrdup(value); |
836 | 0 | } |
837 | 0 | if ((copy != NULL) && (text != NULL) && |
838 | 0 | (xmlIsID(copy->doc, copy->parent, copy))) |
839 | 0 | xmlAddID(NULL, copy->doc, text->content, copy); |
840 | 0 | } |
841 | | |
842 | 0 | next_attribute: |
843 | 0 | attr = attr->next; |
844 | 0 | } while (attr != NULL); |
845 | | |
846 | 0 | ctxt->insert = oldInsert; |
847 | 0 | return(target->properties); |
848 | | |
849 | 0 | error: |
850 | 0 | ctxt->insert = oldInsert; |
851 | 0 | return(NULL); |
852 | 0 | } |
853 | | |
854 | | |
855 | | /** |
856 | | * xsltTemplateProcess: |
857 | | * @ctxt: the XSLT transformation context |
858 | | * @node: the attribute template node |
859 | | * |
860 | | * Obsolete. Don't use it. |
861 | | * |
862 | | * Returns NULL. |
863 | | */ |
864 | | xmlNodePtr * |
865 | 0 | xsltTemplateProcess(xsltTransformContextPtr ctxt ATTRIBUTE_UNUSED, xmlNodePtr node) { |
866 | 0 | if (node == NULL) |
867 | 0 | return(NULL); |
868 | | |
869 | 0 | return(0); |
870 | 0 | } |
871 | | |
872 | | |