2 * transform.c: Implemetation of the XSL Transformation 1.0 engine
3 * transform part, i.e. applying a Stylesheet to a document
6 * http://www.w3.org/TR/1999/REC-xslt-19991116
8 * See Copyright for the status of this software.
10 * Daniel.Veillard@imag.fr
13 #include "xsltconfig.h"
17 #include <libxml/xmlmemory.h>
18 #include <libxml/parser.h>
19 #include <libxml/tree.h>
20 #include <libxml/valid.h>
21 #include <libxml/hash.h>
22 #include <libxml/encoding.h>
23 #include <libxml/xmlerror.h>
24 #include <libxml/xpath.h>
25 #include <libxml/xpathInternals.h>
26 #include <libxml/HTMLtree.h>
28 #include "xsltInternals.h"
29 #include "xsltutils.h"
31 #include "transform.h"
39 #define IS_BLANK_NODE(n) \
40 (((n)->type == XML_TEXT_NODE) && (xsltIsBlank((n)->content)))
46 typedef enum xsltOutputType {
52 typedef struct _xsltTransformContext xsltTransformContext;
53 typedef xsltTransformContext *xsltTransformContextPtr;
54 struct _xsltTransformContext {
55 xsltStylesheetPtr style; /* the stylesheet used */
56 xsltOutputType type; /* the type of output */
58 xmlDocPtr doc; /* the current doc */
59 xmlNodePtr node; /* the current node */
60 xmlNodeSetPtr nodeList; /* the current node list */
62 xmlDocPtr output; /* the resulting document */
63 xmlNodePtr insert; /* the insertion node */
65 xmlXPathContextPtr xpathCtxt; /* the XPath context */
68 /************************************************************************
72 ************************************************************************/
75 * xsltNewTransformContext:
77 * Create a new XSLT TransformContext
79 * Returns the newly allocated xsltTransformContextPtr or NULL in case of error
81 xsltTransformContextPtr
82 xsltNewTransformContext(void) {
83 xsltTransformContextPtr cur;
85 cur = (xsltTransformContextPtr) xmlMalloc(sizeof(xsltTransformContext));
87 xsltGenericError(xsltGenericErrorContext,
88 "xsltNewTransformContext : malloc failed\n");
91 memset(cur, 0, sizeof(xsltTransformContext));
96 * xsltFreeTransformContext:
97 * @ctxt: an XSLT parser context
99 * Free up the memory allocated by @ctxt
102 xsltFreeTransformContext(xsltTransformContextPtr ctxt) {
105 if (ctxt->xpathCtxt != NULL)
106 xmlXPathFreeContext(ctxt->xpathCtxt);
107 memset(ctxt, -1, sizeof(xsltTransformContext));
111 /************************************************************************
115 ************************************************************************/
117 void xsltProcessOneNode(xsltTransformContextPtr ctxt, xmlNodePtr node);
118 void xsltForEach(xsltTransformContextPtr ctxt, xmlNodePtr node,
120 void xsltIf(xsltTransformContextPtr ctxt, xmlNodePtr node, xmlNodePtr inst);
124 * @ctxt: a XSLT process context
125 * @node: the node in the source tree.
126 * @inst: the xslt attribute node
128 * Process the xslt attribute node on the source node
131 xsltAttribute(xsltTransformContextPtr ctxt, xmlNodePtr node,
133 xmlChar *prop = NULL;
134 xmlChar *ncname = NULL;
135 xmlChar *prefix = NULL;
136 xmlChar *value = NULL;
141 if (ctxt->insert == NULL)
143 if (ctxt->insert->children != NULL) {
144 xsltGenericError(xsltGenericErrorContext,
145 "xslt:attribute : node has already children\n");
148 prop = xmlGetNsProp(inst, (const xmlChar *)"namespace", XSLT_NAMESPACE);
150 /* TODO: attribute value template */
155 prop = xmlGetNsProp(inst, (const xmlChar *)"name", XSLT_NAMESPACE);
157 xsltGenericError(xsltGenericErrorContext,
158 "xslt:attribute : name is missing\n");
162 ncname = xmlSplitQName2(prop, &prefix);
163 if (ncname == NULL) {
168 if (xmlStrEqual(ncname, (const xmlChar *) "xmlns")) {
169 xsltGenericError(xsltGenericErrorContext,
170 "xslt:attribute : xmlns forbidden\n");
173 if ((prefix != NULL) && (ns == NULL)) {
174 ns = xmlSearchNs(ctxt->insert->doc, ctxt->insert, prefix);
176 xsltGenericError(xsltGenericErrorContext,
177 "no namespace bound to prefix %s\n", prefix);
181 value = xmlNodeListGetString(inst->doc, inst->children, 1);
184 #if LIBXML_VERSION > 202111
185 attr = xmlSetNsProp(ctxt->insert, ncname, ns->href,
186 (const xmlChar *)"");
188 xsltGenericError(xsltGenericErrorContext,
189 "xsl:attribute: recompile against newer libxml version\n");
190 attr = xmlSetProp(ctxt->insert, ncname, (const xmlChar *)"");
193 attr = xmlSetProp(ctxt->insert, ncname, (const xmlChar *)"");
195 /* TODO: attribute value template */
197 #if LIBXML_VERSION > 202111
198 attr = xmlSetNsProp(ctxt->insert, ncname, ns->href, value);
200 xsltGenericError(xsltGenericErrorContext,
201 "xsl:attribute: recompile against newer libxml version\n");
202 attr = xmlSetProp(ctxt->insert, ncname, value);
205 attr = xmlSetProp(ctxt->insert, ncname, value);
221 * @ctxt: a XSLT process context
222 * @node: the node in the source tree.
223 * @inst: the xsltValueOf node
225 * Process the xsltValueOf node on the source node
228 xsltValueOf(xsltTransformContextPtr ctxt, xmlNodePtr node,
231 int disableEscaping = 0;
232 xmlXPathObjectPtr res, tmp;
233 xmlXPathParserContextPtr xpathParserCtxt;
234 xmlNodePtr copy = NULL;
236 if ((ctxt == NULL) || (node == NULL) || (inst == NULL))
239 prop = xmlGetNsProp(inst, (const xmlChar *)"disable-output-escaping",
242 if (xmlStrEqual(prop, (const xmlChar *)"yes"))
244 else if (xmlStrEqual(prop, (const xmlChar *)"no"))
247 xsltGenericError(xsltGenericErrorContext,
248 "invalud value %s for disable-output-escaping\n", prop);
251 if (disableEscaping) {
252 TODO /* disable-output-escaping */
255 prop = xmlGetNsProp(inst, (const xmlChar *)"select", XSLT_NAMESPACE);
257 xsltGenericError(xsltGenericErrorContext,
258 "xsltValueOf: select is not defined\n");
262 xsltGenericDebug(xsltGenericDebugContext,
263 "xsltValueOf: select %s\n", prop);
266 if (ctxt->xpathCtxt == NULL) {
268 ctxt->xpathCtxt = xmlXPathNewContext(ctxt->doc);
269 if (ctxt->xpathCtxt == NULL)
273 xmlXPathNewParserContext(prop, ctxt->xpathCtxt);
274 if (xpathParserCtxt == NULL)
276 ctxt->xpathCtxt->node = node;
277 valuePush(xpathParserCtxt, xmlXPathNewNodeSet(node));
278 xmlXPathEvalExpr(xpathParserCtxt);
279 xmlXPathStringFunction(xpathParserCtxt, 1);
280 res = valuePop(xpathParserCtxt);
282 tmp = valuePop(xpathParserCtxt);
284 xmlXPathFreeObject(tmp);
286 } while (tmp != NULL);
288 if (res->type == XPATH_STRING) {
289 copy = xmlNewText(res->stringval);
291 xmlAddChild(ctxt->insert, copy);
296 xsltGenericError(xsltGenericErrorContext,
297 "xsltDefaultProcessOneNode: text copy failed\n");
301 xsltGenericDebug(xsltGenericDebugContext,
302 "xsltValueOf: result %s\n", res->stringval);
305 if (xpathParserCtxt != NULL) {
306 xmlXPathFreeParserContext(xpathParserCtxt);
307 xpathParserCtxt = NULL;
312 xmlXPathFreeObject(res);
317 * @ctxt: a XSLT process context
318 * @node: the element node in the source tree.
319 * @insert: the parent in the result tree.
321 * Make a copy of the element node @node
322 * and insert it as last child of @insert
324 * Returns a pointer to the new node, or NULL in case of error
327 xsltCopyNode(xsltTransformContextPtr ctxt, xmlNodePtr node,
331 copy = xmlCopyNode(node, 0);
332 copy->doc = ctxt->output;
334 xmlAddChild(insert, copy);
336 * Add namespaces as they are needed
338 if (node->nsDef != NULL)
339 copy->nsDef = xmlCopyNamespaceList(node->nsDef);
340 if (node->ns != NULL) {
342 * optimization, if the namespace is already the
343 * on on the parent node, reuse it directly
345 * TODO: check possible mess with xmlCopyNamespaceList
347 if ((insert->type == XML_ELEMENT_NODE) &&
348 (insert->ns != NULL) &&
349 (xmlStrEqual(insert->ns->href, node->ns->href))) {
350 copy->ns = insert->ns;
355 * Look in the output tree if the namespace is
358 ns = xmlSearchNsByHref(ctxt->output, copy,
363 ns = xmlNewNs(copy, node->ns->href,
369 xsltGenericError(xsltGenericErrorContext,
370 "xsltCopyNode: copy %s failed\n", node->name);
376 * xsltDefaultProcessOneNode:
377 * @ctxt: a XSLT process context
378 * @node: the node in the source tree.
380 * Process the source node with the default built-in template rule:
381 * <xsl:template match="*|/">
382 * <xsl:apply-templates/>
387 * <xsl:template match="text()|@*">
388 * <xsl:value-of select="."/>
391 * Note also that namespaces declarations are copied directly:
393 * the built-in template rule is the only template rule that is applied
394 * for namespace nodes.
397 xsltDefaultProcessOneNode(xsltTransformContextPtr ctxt, xmlNodePtr node) {
399 xmlNodePtr delete = NULL;
401 switch (node->type) {
402 case XML_DOCUMENT_NODE:
403 case XML_HTML_DOCUMENT_NODE:
404 case XML_ELEMENT_NODE:
409 node = node->children;
410 while (node != NULL) {
411 switch (node->type) {
412 case XML_DOCUMENT_NODE:
413 case XML_HTML_DOCUMENT_NODE:
414 case XML_ELEMENT_NODE:
415 xsltProcessOneNode(ctxt, node);
418 /* TODO: check the whitespace stripping rules ! */
419 if ((IS_BLANK_NODE(node)) &&
420 (node->parent != NULL) &&
421 (ctxt->style->stripSpaces != NULL)) {
424 val = (const xmlChar *)
425 xmlHashLookup(ctxt->style->stripSpaces,
428 (xmlStrEqual(val, (xmlChar *) "strip"))) {
433 /* no break on purpose */
434 case XML_CDATA_SECTION_NODE:
435 copy = xmlCopyNode(node, 0);
437 xmlAddChild(ctxt->insert, copy);
439 xsltGenericError(xsltGenericErrorContext,
440 "xsltDefaultProcessOneNode: text copy failed\n");
445 xsltGenericDebug(xsltGenericDebugContext,
446 "xsltDefaultProcessOneNode: skipping node type %d\n",
452 if (delete != NULL) {
454 xsltGenericDebug(xsltGenericDebugContext,
455 "xsltDefaultProcessOneNode: removing ignorable blank node\n");
457 xmlUnlinkNode(delete);
465 * xsltApplyTemplates:
466 * @ctxt: a XSLT process context
467 * @node: the node in the source tree.
468 * @inst: the apply-templates node
470 * Process the apply-templates node on the source node
473 xsltApplyTemplates(xsltTransformContextPtr ctxt, xmlNodePtr node,
477 if ((ctxt == NULL) || (node == NULL) || (inst == NULL))
481 xsltGenericDebug(xsltGenericDebugContext,
482 "xsltApplyTemplates: node: %s\n", node->name);
484 prop = xmlGetNsProp(inst, (const xmlChar *)"select", XSLT_NAMESPACE);
488 xsltDefaultProcessOneNode(ctxt, node);
493 * xsltApplyOneTemplate:
494 * @ctxt: a XSLT process context
495 * @node: the node in the source tree.
496 * @list: the template replacement nodelist
498 * Process the apply-templates node on the source node
501 xsltApplyOneTemplate(xsltTransformContextPtr ctxt, xmlNodePtr node,
503 xmlNodePtr cur, insert, copy, delete = NULL;
504 xmlNodePtr oldInsert;
506 oldInsert = insert = ctxt->insert;
508 * Insert all non-XSLT nodes found in the template
511 while (cur != NULL) {
513 * test, we must have a valid insertion point
515 if (insert == NULL) {
517 xsltGenericDebug(xsltGenericDebugContext,
518 "xsltApplyOneTemplate: insert == NULL !\n");
524 * Cleanup of ignorable blank node detected
526 if (delete != NULL) {
528 xsltGenericDebug(xsltGenericDebugContext,
529 "xsltApplyOneTemplate: removing ignorable blank node\n");
531 xmlUnlinkNode(delete);
535 if (IS_XSLT_ELEM(cur)) {
536 if (IS_XSLT_NAME(cur, "apply-templates")) {
537 ctxt->insert = insert;
538 xsltApplyTemplates(ctxt, node, cur);
539 ctxt->insert = oldInsert;
540 } else if (IS_XSLT_NAME(cur, "value-of")) {
541 ctxt->insert = insert;
542 xsltValueOf(ctxt, node, cur);
543 ctxt->insert = oldInsert;
544 } else if (IS_XSLT_NAME(cur, "if")) {
545 ctxt->insert = insert;
546 xsltIf(ctxt, node, cur);
547 ctxt->insert = oldInsert;
548 } else if (IS_XSLT_NAME(cur, "for-each")) {
549 ctxt->insert = insert;
550 xsltForEach(ctxt, node, cur);
551 ctxt->insert = oldInsert;
552 } else if (IS_XSLT_NAME(cur, "attribute")) {
553 ctxt->insert = insert;
554 xsltAttribute(ctxt, node, cur);
555 ctxt->insert = oldInsert;
556 } else if (IS_XSLT_NAME(cur, "element")) {
557 ctxt->insert = insert;
558 xsltAttribute(ctxt, node, cur);
559 ctxt->insert = oldInsert;
562 xsltGenericDebug(xsltGenericDebugContext,
563 "xsltApplyOneTemplate: found xslt:%s\n", cur->name);
568 } else if (cur->type == XML_TEXT_NODE) {
570 * This text comes from the stylesheet
571 * For stylesheets, the set of whitespace-preserving
572 * element names consists of just xsl:text.
574 if (!(IS_BLANK_NODE(cur))) {
576 xsltGenericDebug(xsltGenericDebugContext,
577 "xsltApplyOneTemplate: copy text %s\n", cur->content);
579 copy = xmlCopyNode(cur, 0);
581 xmlAddChild(insert, copy);
583 xsltGenericError(xsltGenericErrorContext,
584 "xsltApplyOneTemplate: text copy failed\n");
589 } else if (cur->type == XML_ELEMENT_NODE) {
591 xsltGenericDebug(xsltGenericDebugContext,
592 "xsltApplyOneTemplate: copy node %s\n", cur->name);
594 copy = xsltCopyNode(ctxt, cur, insert);
596 * all the attributes are directly inherited
597 * TODO: Do the substitution of {} XPath expressions !!!
599 if (cur->properties != NULL)
600 copy->properties = xmlCopyPropList(copy, cur->properties);
604 * Skip to next node, in document order.
606 if (cur->children != NULL) {
607 if (cur->children->type != XML_ENTITY_DECL) {
615 if (cur->next != NULL) {
622 insert = insert->parent;
625 if (cur == list->parent) {
629 if (cur->next != NULL) {
633 } while (cur != NULL);
639 * @ctxt: a XSLT process context
640 * @node: the node in the source tree.
641 * @inst: the xslt if node
643 * Process the xslt if node on the source node
646 xsltIf(xsltTransformContextPtr ctxt, xmlNodePtr node,
649 xmlXPathObjectPtr res, tmp;
650 xmlXPathParserContextPtr xpathParserCtxt;
653 if ((ctxt == NULL) || (node == NULL) || (inst == NULL))
656 prop = xmlGetNsProp(inst, (const xmlChar *)"test", XSLT_NAMESPACE);
658 xsltGenericError(xsltGenericErrorContext,
659 "xsltIf: test is not defined\n");
663 xsltGenericDebug(xsltGenericDebugContext,
664 "xsltIf: test %s\n", prop);
667 if (ctxt->xpathCtxt == NULL) {
669 ctxt->xpathCtxt = xmlXPathNewContext(ctxt->doc);
670 if (ctxt->xpathCtxt == NULL)
673 xpathParserCtxt = xmlXPathNewParserContext(prop, ctxt->xpathCtxt);
674 if (xpathParserCtxt == NULL)
676 ctxt->xpathCtxt->node = node;
677 valuePush(xpathParserCtxt, xmlXPathNewNodeSet(node));
678 xmlXPathEvalExpr(xpathParserCtxt);
679 xmlXPathBooleanFunction(xpathParserCtxt, 1);
680 res = valuePop(xpathParserCtxt);
682 tmp = valuePop(xpathParserCtxt);
684 xmlXPathFreeObject(tmp);
686 } while (tmp != NULL);
689 if (res->type == XPATH_BOOLEAN)
693 xsltGenericDebug(xsltGenericDebugContext,
694 "xsltIf: test didn't evaluate to a boolean\n");
701 xsltGenericDebug(xsltGenericDebugContext,
702 "xsltIf: test evaluate to %d\n", doit);
705 xsltApplyOneTemplate(ctxt, ctxt->node, inst->children);
709 if (xpathParserCtxt != NULL)
710 xmlXPathFreeParserContext(xpathParserCtxt);
714 xmlXPathFreeObject(res);
719 * @ctxt: a XSLT process context
720 * @node: the node in the source tree.
721 * @inst: the xslt for-each node
723 * Process the xslt for-each node on the source node
726 xsltForEach(xsltTransformContextPtr ctxt, xmlNodePtr node,
729 xmlXPathObjectPtr res, tmp;
730 xmlNodePtr replacement;
731 xmlNodeSetPtr list = NULL, oldlist;
732 xmlXPathParserContextPtr xpathParserCtxt;
733 int i, oldProximityPosition, oldContextSize;
735 if ((ctxt == NULL) || (node == NULL) || (inst == NULL))
738 prop = xmlGetNsProp(inst, (const xmlChar *)"select", XSLT_NAMESPACE);
740 xsltGenericError(xsltGenericErrorContext,
741 "xsltForEach: select is not defined\n");
745 xsltGenericDebug(xsltGenericDebugContext,
746 "xsltForEach: select %s\n", prop);
749 if (ctxt->xpathCtxt == NULL) {
751 ctxt->xpathCtxt = xmlXPathNewContext(ctxt->doc);
752 if (ctxt->xpathCtxt == NULL)
755 xpathParserCtxt = xmlXPathNewParserContext(prop, ctxt->xpathCtxt);
756 if (xpathParserCtxt == NULL)
758 ctxt->xpathCtxt->node = node;
759 valuePush(xpathParserCtxt, xmlXPathNewNodeSet(node));
760 xmlXPathEvalExpr(xpathParserCtxt);
761 res = valuePop(xpathParserCtxt);
763 tmp = valuePop(xpathParserCtxt);
765 xmlXPathFreeObject(tmp);
767 } while (tmp != NULL);
770 if (res->type == XPATH_NODESET)
771 list = res->nodesetval;
774 xsltGenericDebug(xsltGenericDebugContext,
775 "xsltForEach: select didn't evaluate to a node list\n");
782 xsltGenericDebug(xsltGenericDebugContext,
783 "xsltForEach: select evaluate to %d nodes\n", list->nodeNr);
785 /* TODO: handle and skip the xsl:sort */
786 replacement = inst->children;
788 oldlist = ctxt->nodeList;
789 ctxt->nodeList = list;
790 oldContextSize = ctxt->xpathCtxt->contextSize;
791 oldProximityPosition = ctxt->xpathCtxt->proximityPosition;
792 ctxt->xpathCtxt->contextSize = list->nodeNr;
793 for (i = 0;i < list->nodeNr;i++) {
794 ctxt->node = list->nodeTab[i];
795 ctxt->xpathCtxt->proximityPosition = i + 1;
796 xsltApplyOneTemplate(ctxt, list->nodeTab[i], replacement);
798 ctxt->nodeList = oldlist;
799 ctxt->xpathCtxt->contextSize = oldContextSize;
800 ctxt->xpathCtxt->proximityPosition = oldProximityPosition;
803 if (xpathParserCtxt != NULL)
804 xmlXPathFreeParserContext(xpathParserCtxt);
808 xmlXPathFreeObject(res);
812 * xsltProcessOneNode:
813 * @ctxt: a XSLT process context
814 * @node: the node in the source tree.
816 * Process the source node.
819 xsltProcessOneNode(xsltTransformContextPtr ctxt, xmlNodePtr node) {
820 xsltTemplatePtr template;
821 template = xsltGetTemplate(ctxt->style, node);
824 * If no template is found, apply the default rule.
826 if (template == NULL) {
828 if (node->type == XML_DOCUMENT_NODE)
829 xsltGenericDebug(xsltGenericDebugContext,
830 "xsltProcessOneNode: no template found for /\n");
832 xsltGenericDebug(xsltGenericDebugContext,
833 "xsltProcessOneNode: no template found for %s\n", node->name);
836 xsltDefaultProcessOneNode(ctxt, node);
840 xsltApplyOneTemplate(ctxt, node, template->content);
844 * xsltApplyStylesheet:
845 * @style: a parsed XSLT stylesheet
846 * @doc: a parsed XML document
848 * Apply the stylesheet to the document
849 * NOTE: This may lead to a non-wellformed output XML wise !
851 * Returns the result document or NULL in case of error
854 xsltApplyStylesheet(xsltStylesheetPtr style, xmlDocPtr doc) {
855 xmlDocPtr res = NULL;
856 xsltTransformContextPtr ctxt = NULL;
859 if ((style == NULL) || (doc == NULL))
861 ctxt = xsltNewTransformContext();
866 if ((style->method != NULL) &&
867 (!xmlStrEqual(style->method, (const xmlChar *) "xml"))) {
868 if (xmlStrEqual(style->method, (const xmlChar *) "html")) {
869 ctxt->type = XSLT_OUTPUT_HTML;
870 res = htmlNewDoc(style->doctypePublic, style->doctypeSystem);
873 } else if (xmlStrEqual(style->method, (const xmlChar *) "text")) {
874 ctxt->type = XSLT_OUTPUT_TEXT;
875 res = xmlNewDoc(style->version);
879 xsltGenericError(xsltGenericErrorContext,
880 "xsltApplyStylesheet: insupported method %s\n",
885 ctxt->type = XSLT_OUTPUT_XML;
886 res = xmlNewDoc(style->version);
890 res->charset = XML_CHAR_ENCODING_UTF8;
891 if (style->encoding != NULL)
892 res->encoding = xmlStrdup(style->encoding);
898 ctxt->insert = (xmlNodePtr) res;
899 ctxt->node = (xmlNodePtr) doc;
900 xsltProcessOneNode(ctxt, ctxt->node);
903 if ((ctxt->type = XSLT_OUTPUT_XML) &&
904 ((style->doctypePublic != NULL) ||
905 (style->doctypeSystem != NULL))) {
906 root = xmlDocGetRootElement(res);
908 res->intSubset = xmlCreateIntSubset(res, root->name,
909 style->doctypePublic, style->doctypeSystem);
911 xmlXPathFreeNodeSet(ctxt->nodeList);
912 xsltFreeTransformContext(ctxt);
919 xsltFreeTransformContext(ctxt);