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/xpathInternals.h> 22#include <libxml/parserInternals.h> 23#include "xslt.h" 24#include "xsltInternals.h" 25#include "xsltutils.h" 26#include "variables.h" 27#include "functions.h" 28#include "templates.h" 29#include "transform.h" 30#include "namespaces.h" 31#include "attributes.h" 32 33#ifdef WITH_XSLT_DEBUG 34#define WITH_XSLT_DEBUG_TEMPLATES 35#endif 36 37/************************************************************************ 38 * * 39 * Module interfaces * 40 * * 41 ************************************************************************/ 42 43/** 44 * xsltEvalXPathPredicate: 45 * @ctxt: the XSLT transformation context 46 * @comp: the XPath compiled expression 47 * @nsList: the namespaces in scope 48 * @nsNr: the number of namespaces in scope 49 * 50 * Process the expression using XPath and evaluate the result as 51 * an XPath predicate 52 * 53 * Returns 1 is the predicate was true, 0 otherwise 54 */ 55int 56xsltEvalXPathPredicate(xsltTransformContextPtr ctxt, xmlXPathCompExprPtr comp, 57 xmlNsPtr *nsList, int nsNr) { 58 int ret; 59 xmlXPathObjectPtr res; 60 int oldNsNr; 61 xmlNsPtr *oldNamespaces; 62 xmlNodePtr oldInst; 63 int oldProximityPosition, oldContextSize; 64 65 oldContextSize = ctxt->xpathCtxt->contextSize; 66 oldProximityPosition = ctxt->xpathCtxt->proximityPosition; 67 oldNsNr = ctxt->xpathCtxt->nsNr; 68 oldNamespaces = ctxt->xpathCtxt->namespaces; 69 oldInst = ctxt->inst; 70 71 ctxt->xpathCtxt->node = ctxt->node; 72 ctxt->xpathCtxt->namespaces = nsList; 73 ctxt->xpathCtxt->nsNr = nsNr; 74 75 res = xmlXPathCompiledEval(comp, ctxt->xpathCtxt); 76 77 if (res != NULL) { 78 ret = xmlXPathEvalPredicate(ctxt->xpathCtxt, res); 79 xmlXPathFreeObject(res); 80#ifdef WITH_XSLT_DEBUG_TEMPLATES 81 XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext, 82 "xsltEvalXPathPredicate: returns %d\n", ret)); 83#endif 84 } else { 85#ifdef WITH_XSLT_DEBUG_TEMPLATES 86 XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext, 87 "xsltEvalXPathPredicate: failed\n")); 88#endif 89 ctxt->state = XSLT_STATE_STOPPED; 90 ret = 0; 91 } 92 ctxt->xpathCtxt->nsNr = oldNsNr; 93 94 ctxt->xpathCtxt->namespaces = oldNamespaces; 95 ctxt->inst = oldInst; 96 ctxt->xpathCtxt->contextSize = oldContextSize; 97 ctxt->xpathCtxt->proximityPosition = oldProximityPosition; 98 99 return(ret); 100} 101 102/** 103 * xsltEvalXPathStringNs: 104 * @ctxt: the XSLT transformation context 105 * @comp: the compiled XPath expression 106 * @nsNr: the number of namespaces in the list 107 * @nsList: the list of in-scope namespaces to use 108 * 109 * Process the expression using XPath, allowing to pass a namespace mapping 110 * context and get a string 111 * 112 * Returns the computed string value or NULL, must be deallocated by the 113 * caller. 114 */ 115xmlChar * 116xsltEvalXPathStringNs(xsltTransformContextPtr ctxt, xmlXPathCompExprPtr comp, 117 int nsNr, xmlNsPtr *nsList) { 118 xmlChar *ret = NULL; 119 xmlXPathObjectPtr res; 120 xmlNodePtr oldInst; 121 xmlNodePtr oldNode; 122 int oldPos, oldSize; 123 int oldNsNr; 124 xmlNsPtr *oldNamespaces; 125 126 oldInst = ctxt->inst; 127 oldNode = ctxt->node; 128 oldPos = ctxt->xpathCtxt->proximityPosition; 129 oldSize = ctxt->xpathCtxt->contextSize; 130 oldNsNr = ctxt->xpathCtxt->nsNr; 131 oldNamespaces = ctxt->xpathCtxt->namespaces; 132 133 ctxt->xpathCtxt->node = ctxt->node; 134 /* TODO: do we need to propagate the namespaces here ? */ 135 ctxt->xpathCtxt->namespaces = nsList; 136 ctxt->xpathCtxt->nsNr = nsNr; 137 res = xmlXPathCompiledEval(comp, ctxt->xpathCtxt); 138 if (res != NULL) { 139 if (res->type != XPATH_STRING) 140 res = xmlXPathConvertString(res); 141 if (res->type == XPATH_STRING) { 142 ret = res->stringval; 143 res->stringval = NULL; 144 } else { 145 xsltTransformError(ctxt, NULL, NULL, 146 "xpath : string() function didn't return a String\n"); 147 } 148 xmlXPathFreeObject(res); 149 } else { 150 ctxt->state = XSLT_STATE_STOPPED; 151 } 152#ifdef WITH_XSLT_DEBUG_TEMPLATES 153 XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext, 154 "xsltEvalXPathString: returns %s\n", ret)); 155#endif 156 ctxt->inst = oldInst; 157 ctxt->node = oldNode; 158 ctxt->xpathCtxt->contextSize = oldSize; 159 ctxt->xpathCtxt->proximityPosition = oldPos; 160 ctxt->xpathCtxt->nsNr = oldNsNr; 161 ctxt->xpathCtxt->namespaces = oldNamespaces; 162 return(ret); 163} 164 165/** 166 * xsltEvalXPathString: 167 * @ctxt: the XSLT transformation context 168 * @comp: the compiled XPath expression 169 * 170 * Process the expression using XPath and get a string 171 * 172 * Returns the computed string value or NULL, must be deallocated by the 173 * caller. 174 */ 175xmlChar * 176xsltEvalXPathString(xsltTransformContextPtr ctxt, xmlXPathCompExprPtr comp) { 177 return(xsltEvalXPathStringNs(ctxt, comp, 0, NULL)); 178} 179 180/** 181 * xsltEvalTemplateString: 182 * @ctxt: the XSLT transformation context 183 * @node: the stylesheet node 184 * @parent: the content parent 185 * 186 * Evaluate a template string value, i.e. the parent list is interpreter 187 * as template content and the resulting tree string value is returned 188 * This is needed for example by xsl:comment and xsl:processing-instruction 189 * 190 * Returns the computed string value or NULL, must be deallocated by the 191 * caller. 192 */ 193xmlChar * 194xsltEvalTemplateString(xsltTransformContextPtr ctxt, xmlNodePtr node, 195 xmlNodePtr parent) { 196 xmlNodePtr oldInsert, insert = NULL; 197 xmlChar *ret; 198 199 if ((ctxt == NULL) || (node == NULL) || (parent == NULL)) 200 return(NULL); 201 202 if (parent->children == NULL) 203 return(NULL); 204 205 /* 206 * This creates a temporary element-node to add the resulting 207 * text content to. 208 * OPTIMIZE TODO: Keep such an element-node in the transformation 209 * context to avoid creating it every time. 210 */ 211 insert = xmlNewDocNode(ctxt->output, NULL, 212 (const xmlChar *)"fake", NULL); 213 if (insert == NULL) { 214 xsltTransformError(ctxt, NULL, node, 215 "Failed to create temporary node\n"); 216 return(NULL); 217 } 218 oldInsert = ctxt->insert; 219 ctxt->insert = insert; 220 /* OPTIMIZE TODO: if parent->children consists only of text-nodes. */ 221 xsltApplyOneTemplate(ctxt, node, parent->children, NULL, NULL); 222 223 ctxt->insert = oldInsert; 224 225 ret = xmlNodeGetContent(insert); 226 if (insert != NULL) 227 xmlFreeNode(insert); 228 return(ret); 229} 230 231/** 232 * xsltAttrTemplateValueProcessNode: 233 * @ctxt: the XSLT transformation context 234 * @str: the attribute template node value 235 * @node: the node hosting the attribute 236 * 237 * Process the given string, allowing to pass a namespace mapping 238 * context and return the new string value. 239 * 240 * Returns the computed string value or NULL, must be deallocated by the 241 * caller. 242 */ 243xmlChar * 244xsltAttrTemplateValueProcessNode(xsltTransformContextPtr ctxt, 245 const xmlChar *str, xmlNodePtr node) { 246 xmlChar *ret = NULL; 247 const xmlChar *cur; 248 xmlChar *expr, *val; 249 xmlNsPtr *nsList = NULL; 250 int nsNr = 0; 251 252 if (str == NULL) return(NULL); 253 if (*str == 0) 254 return(xmlStrndup((xmlChar *)"", 0)); 255 256 cur = str; 257 while (*cur != 0) { 258 if (*cur == '{') { 259 if (*(cur+1) == '{') { /* escaped '{' */ 260 cur++; 261 ret = xmlStrncat(ret, str, cur - str); 262 cur++; 263 str = cur; 264 continue; 265 } 266 ret = xmlStrncat(ret, str, cur - str); 267 str = cur; 268 cur++; 269 while ((*cur != 0) && (*cur != '}')) cur++; 270 if (*cur == 0) { 271 xsltTransformError(ctxt, NULL, NULL, 272 "xsltAttrTemplateValueProcessNode: unmatched '{'\n"); 273 ret = xmlStrncat(ret, str, cur - str); 274 return(ret); 275 } 276 str++; 277 expr = xmlStrndup(str, cur - str); 278 if (expr == NULL) 279 return(ret); 280 else if (*expr == '{') { 281 ret = xmlStrcat(ret, expr); 282 xmlFree(expr); 283 } else { 284 xmlXPathCompExprPtr comp; 285 /* 286 * TODO: keep precompiled form around 287 */ 288 if ((nsList == NULL) && (node != NULL)) { 289 int i = 0; 290 291 nsList = xmlGetNsList(node->doc, node); 292 if (nsList != NULL) { 293 while (nsList[i] != NULL) 294 i++; 295 nsNr = i; 296 } 297 } 298 comp = xmlXPathCompile(expr); 299 val = xsltEvalXPathStringNs(ctxt, comp, nsNr, nsList); 300 xmlXPathFreeCompExpr(comp); 301 xmlFree(expr); 302 if (val != NULL) { 303 ret = xmlStrcat(ret, val); 304 xmlFree(val); 305 } 306 } 307 cur++; 308 str = cur; 309 } else if (*cur == '}') { 310 cur++; 311 if (*cur == '}') { /* escaped '}' */ 312 ret = xmlStrncat(ret, str, cur - str); 313 cur++; 314 str = cur; 315 continue; 316 } else { 317 xsltTransformError(ctxt, NULL, NULL, 318 "xsltAttrTemplateValueProcessNode: unmatched '}'\n"); 319 } 320 } else 321 cur++; 322 } 323 if (cur != str) { 324 ret = xmlStrncat(ret, str, cur - str); 325 } 326 327 if (nsList != NULL) 328 xmlFree(nsList); 329 330 return(ret); 331} 332 333/** 334 * xsltAttrTemplateValueProcess: 335 * @ctxt: the XSLT transformation context 336 * @str: the attribute template node value 337 * 338 * Process the given node and return the new string value. 339 * 340 * Returns the computed string value or NULL, must be deallocated by the 341 * caller. 342 */ 343xmlChar * 344xsltAttrTemplateValueProcess(xsltTransformContextPtr ctxt, const xmlChar *str) { 345 return(xsltAttrTemplateValueProcessNode(ctxt, str, NULL)); 346} 347 348/** 349 * xsltEvalAttrValueTemplate: 350 * @ctxt: the XSLT transformation context 351 * @node: the stylesheet node 352 * @name: the attribute QName 353 * @ns: the attribute namespace URI 354 * 355 * Evaluate a attribute value template, i.e. the attribute value can 356 * contain expressions contained in curly braces ({}) and those are 357 * substituted by they computed value. 358 * 359 * Returns the computed string value or NULL, must be deallocated by the 360 * caller. 361 */ 362xmlChar * 363xsltEvalAttrValueTemplate(xsltTransformContextPtr ctxt, xmlNodePtr node, 364 const xmlChar *name, const xmlChar *ns) { 365 xmlChar *ret; 366 xmlChar *expr; 367 368 if ((ctxt == NULL) || (node == NULL) || (name == NULL)) 369 return(NULL); 370 371 expr = xsltGetNsProp(node, name, ns); 372 if (expr == NULL) 373 return(NULL); 374 375 /* 376 * TODO: though now {} is detected ahead, it would still be good to 377 * optimize both functions to keep the splitted value if the 378 * attribute content and the XPath precompiled expressions around 379 */ 380 381 ret = xsltAttrTemplateValueProcessNode(ctxt, expr, node); 382#ifdef WITH_XSLT_DEBUG_TEMPLATES 383 XSLT_TRACE(ctxt,XSLT_TRACE_TEMPLATES,xsltGenericDebug(xsltGenericDebugContext, 384 "xsltEvalAttrValueTemplate: %s returns %s\n", expr, ret)); 385#endif 386 if (expr != NULL) 387 xmlFree(expr); 388 return(ret); 389} 390 391/** 392 * xsltEvalStaticAttrValueTemplate: 393 * @style: the XSLT stylesheet 394 * @node: the stylesheet node 395 * @name: the attribute Name 396 * @ns: the attribute namespace URI 397 * @found: indicator whether the attribute is present 398 * 399 * Check if an attribute value template has a static value, i.e. the 400 * attribute value does not contain expressions contained in curly braces ({}) 401 * 402 * Returns the static string value or NULL, must be deallocated by the 403 * caller. 404 */ 405const xmlChar * 406xsltEvalStaticAttrValueTemplate(xsltStylesheetPtr style, xmlNodePtr node, 407 const xmlChar *name, const xmlChar *ns, int *found) { 408 const xmlChar *ret; 409 xmlChar *expr; 410 411 if ((style == NULL) || (node == NULL) || (name == NULL)) 412 return(NULL); 413 414 expr = xsltGetNsProp(node, name, ns); 415 if (expr == NULL) { 416 *found = 0; 417 return(NULL); 418 } 419 *found = 1; 420 421 ret = xmlStrchr(expr, '{'); 422 if (ret != NULL) { 423 xmlFree(expr); 424 return(NULL); 425 } 426 ret = xmlDictLookup(style->dict, expr, -1); 427 xmlFree(expr); 428 return(ret); 429} 430 431/** 432 * xsltAttrTemplateProcess: 433 * @ctxt: the XSLT transformation context 434 * @target: the result node 435 * @cur: the attribute template node 436 * 437 * Process the given attribute and return the new processed copy. 438 * 439 * Returns the attribute replacement. 440 */ 441xmlAttrPtr 442xsltAttrTemplateProcess(xsltTransformContextPtr ctxt, xmlNodePtr target, 443 xmlAttrPtr cur) { 444 const xmlChar *value; 445 xmlNsPtr ns; 446 xmlAttrPtr ret; 447 if ((ctxt == NULL) || (cur == NULL) || (target == NULL)) 448 return(NULL); 449 450 if (cur->type != XML_ATTRIBUTE_NODE) 451 return(NULL); 452 453 if ((cur->children == NULL) || (cur->children->type != XML_TEXT_NODE) || 454 (cur->children->next != NULL)) { 455 xsltTransformError(ctxt, NULL, cur->parent, 456 "attribute %s content problem\n", cur->name); 457 return(NULL); 458 } 459 value = cur->children->content; 460 if (value == NULL) 461 value = xmlDictLookup(ctxt->dict, BAD_CAST "", 0); 462 if ((cur->ns != NULL) && 463 (xmlStrEqual(cur->ns->href, XSLT_NAMESPACE))) { 464 if (xmlStrEqual(cur->name, (const xmlChar *)"use-attribute-sets")) { 465 xsltApplyAttributeSet(ctxt, ctxt->node, NULL, value); 466 } 467 return(NULL); 468 } 469 470 ret = target->properties; 471 while (ret != NULL) { 472 if (xmlStrEqual(ret->name, cur->name)) { 473 if (cur->ns == NULL) { 474 if (ret->ns == NULL) 475 break; 476 } else { 477 if ((ret->ns != NULL) && 478 (xmlStrEqual(ret->ns->href, cur->ns->href))) 479 break; 480 } 481 } 482 ret = ret->next; 483 } 484 if (ret != NULL) { 485 /* free the existing value */ 486 xmlFreeNodeList(ret->children); 487 ret->children = ret->last = NULL; 488 } else { 489 /* create a new attribute */ 490 if (cur->ns != NULL) 491 ns = xsltGetPlainNamespace(ctxt, cur->parent, cur->ns, target); 492 else 493 ns = NULL; 494 ret = xmlNewNsProp(target, ns, cur->name, NULL); 495 } 496 if (ret != NULL) { 497 xmlNodePtr text; 498 499 text = xmlNewText(NULL); 500 if (text != NULL) { 501 ret->last = ret->children = text; 502 text->parent = (xmlNodePtr) ret; 503 text->doc = ret->doc; 504 if (cur->psvi != NULL) { 505 xmlChar *val; 506 val = xsltEvalAVT(ctxt, cur->psvi, cur->parent); 507 if (val == NULL) { 508 text->content = xmlStrdup(BAD_CAST "runtime error"); 509 } else { 510 text->content = val; 511 } 512 } else if ((ctxt->internalized) && (target != NULL) && 513 (target->doc != NULL) && 514 (target->doc->dict == ctxt->dict)) { 515 text->content = (xmlChar *) value; 516 } else { 517 text->content = xmlStrdup(value); 518 } 519 } 520 } else { 521 xsltTransformError(ctxt, NULL, cur->parent, 522 "Failed to create attribute %s\n", cur->name); 523 } 524 return(ret); 525} 526 527 528/** 529 * xsltAttrListTemplateProcess: 530 * @ctxt: the XSLT transformation context 531 * @target: the element where the attributes will be grafted 532 * @cur: the first attribute 533 * 534 * Do a copy of an attribute list with attribute template processing 535 * 536 * Returns: a new xmlAttrPtr, or NULL in case of error. 537 */ 538xmlAttrPtr 539xsltAttrListTemplateProcess(xsltTransformContextPtr ctxt, 540 xmlNodePtr target, xmlAttrPtr cur) { 541 xmlAttrPtr ret = NULL; 542 xmlAttrPtr q; 543 xmlNodePtr oldInsert; 544 545 oldInsert = ctxt->insert; 546 ctxt->insert = target; 547 while (cur != NULL) { 548 q = xsltAttrTemplateProcess(ctxt, target, cur); 549 if (q != NULL) { 550 q->parent = target; 551 q->doc = target->doc; 552 if (ret == NULL) { 553 ret = q; 554 } 555 } 556 cur = cur->next; 557 } 558 ctxt->insert = oldInsert; 559 return(ret); 560} 561 562 563/** 564 * xsltTemplateProcess: 565 * @ctxt: the XSLT transformation context 566 * @node: the attribute template node 567 * 568 * Process the given node and return the new string value. 569 * 570 * Returns the computed tree replacement 571 */ 572xmlNodePtr * 573xsltTemplateProcess(xsltTransformContextPtr ctxt ATTRIBUTE_UNUSED, xmlNodePtr node) { 574 if (node == NULL) 575 return(NULL); 576 577 return(0); 578} 579 580 581