2 * pattern.c: Implemetation of the template match compilation and lookup
5 * http://www.w3.org/TR/1999/REC-xslt-19991116
7 * See Copyright for the status of this software.
9 * Daniel.Veillard@imag.fr
13 * TODO: handle pathological cases like *[*[@a="b"]]
14 * TODO: detect [number] at compilation, optimize accordingly
17 #include "xsltconfig.h"
21 #include <libxml/xmlmemory.h>
22 #include <libxml/tree.h>
23 #include <libxml/valid.h>
24 #include <libxml/hash.h>
25 #include <libxml/xmlerror.h>
26 #include <libxml/parserInternals.h>
28 #include "xsltInternals.h"
29 #include "xsltutils.h"
31 #include "templates.h"
35 #ifdef WITH_XSLT_DEBUG
36 #define WITH_XSLT_DEBUG_PATTERN
63 typedef struct _xsltStepOp xsltStepOp;
64 typedef xsltStepOp *xsltStepOpPtr;
70 xmlXPathCompExprPtr comp;
72 * Optimisations for count
79 struct _xsltCompMatch {
80 struct _xsltCompMatch *next; /* siblings in the name hash */
81 float priority; /* the priority */
82 const xmlChar *mode; /* the mode */
83 const xmlChar *modeURI; /* the mode URI */
84 xsltTemplatePtr template; /* the associated template */
86 /* TODO fix the statically allocated size steps[] */
89 xsltStepOp steps[20]; /* ops for computation */
92 typedef struct _xsltParserContext xsltParserContext;
93 typedef xsltParserContext *xsltParserContextPtr;
94 struct _xsltParserContext {
95 const xmlChar *cur; /* the current char being parsed */
96 const xmlChar *base; /* the full expression */
97 xmlDocPtr doc; /* the source document */
98 xmlNodePtr elem; /* the source element */
99 int error; /* error code */
100 xsltCompMatchPtr comp; /* the result */
103 /************************************************************************
107 ************************************************************************/
112 * Create a new XSLT CompMatch
114 * Returns the newly allocated xsltCompMatchPtr or NULL in case of error
116 static xsltCompMatchPtr
117 xsltNewCompMatch(void) {
118 xsltCompMatchPtr cur;
120 cur = (xsltCompMatchPtr) xmlMalloc(sizeof(xsltCompMatch));
122 xsltGenericError(xsltGenericErrorContext,
123 "xsltNewCompMatch : malloc failed\n");
126 memset(cur, 0, sizeof(xsltCompMatch));
133 * @comp: an XSLT comp
135 * Free up the memory allocated by @comp
138 xsltFreeCompMatch(xsltCompMatchPtr comp) {
144 if (comp->mode != NULL)
145 xmlFree((xmlChar *)comp->mode);
146 if (comp->modeURI != NULL)
147 xmlFree((xmlChar *)comp->modeURI);
148 for (i = 0;i < comp->nbStep;i++) {
149 op = &comp->steps[i];
150 if (op->value != NULL)
152 if (op->value2 != NULL)
154 if (op->value3 != NULL)
156 if (op->comp != NULL)
157 xmlXPathFreeCompExpr(op->comp);
159 memset(comp, -1, sizeof(xsltCompMatch));
164 * xsltFreeCompMatchList:
165 * @comp: an XSLT comp list
167 * Free up the memory allocated by all the elements of @comp
170 xsltFreeCompMatchList(xsltCompMatchPtr comp) {
171 xsltCompMatchPtr cur;
173 while (comp != NULL) {
176 xsltFreeCompMatch(cur);
181 * xsltNewParserContext:
183 * Create a new XSLT ParserContext
185 * Returns the newly allocated xsltParserContextPtr or NULL in case of error
187 static xsltParserContextPtr
188 xsltNewParserContext(void) {
189 xsltParserContextPtr cur;
191 cur = (xsltParserContextPtr) xmlMalloc(sizeof(xsltParserContext));
193 xsltGenericError(xsltGenericErrorContext,
194 "xsltNewParserContext : malloc failed\n");
197 memset(cur, 0, sizeof(xsltParserContext));
202 * xsltFreeParserContext:
203 * @ctxt: an XSLT parser context
205 * Free up the memory allocated by @ctxt
208 xsltFreeParserContext(xsltParserContextPtr ctxt) {
211 memset(ctxt, -1, sizeof(xsltParserContext));
217 * @comp: the compiled match expression
219 * @value: the first value
220 * @value2: the second value
222 * Add an step to an XSLT Compiled Match
224 * Returns -1 in case of failure, 0 otherwise.
227 xsltCompMatchAdd(xsltCompMatchPtr comp, xsltOp op, xmlChar *value,
229 if (comp->nbStep >= 20) {
230 xsltGenericError(xsltGenericErrorContext,
231 "xsltCompMatchAddOp: overflow\n");
234 comp->steps[comp->nbStep].op = op;
235 comp->steps[comp->nbStep].value = value;
236 comp->steps[comp->nbStep].value2 = value2;
242 * xsltSwapTopCompMatch:
243 * @comp: the compiled match expression
245 * reverse the two top steps.
248 xsltSwapTopCompMatch(xsltCompMatchPtr comp) {
250 int j = comp->nbStep - 1;
253 register xmlChar *tmp;
256 tmp = comp->steps[i].value;
257 comp->steps[i].value = comp->steps[j].value;
258 comp->steps[j].value = tmp;
259 tmp = comp->steps[i].value2;
260 comp->steps[i].value2 = comp->steps[j].value2;
261 comp->steps[j].value2 = tmp;
262 op = comp->steps[i].op;
263 comp->steps[i].op = comp->steps[j].op;
264 comp->steps[j].op = op;
269 * xsltReverseCompMatch:
270 * @comp: the compiled match expression
272 * reverse all the stack of expressions
275 xsltReverseCompMatch(xsltCompMatchPtr comp) {
277 int j = comp->nbStep - 1;
280 register xmlChar *tmp;
282 tmp = comp->steps[i].value;
283 comp->steps[i].value = comp->steps[j].value;
284 comp->steps[j].value = tmp;
285 tmp = comp->steps[i].value2;
286 comp->steps[i].value2 = comp->steps[j].value2;
287 comp->steps[j].value2 = tmp;
288 op = comp->steps[i].op;
289 comp->steps[i].op = comp->steps[j].op;
290 comp->steps[j].op = op;
294 comp->steps[comp->nbStep++].op = XSLT_OP_END;
298 * xsltCleanupCompMatch:
299 * @comp: the compiled match expression
301 * remove all computation state from the pattern
304 xsltCleanupCompMatch(xsltCompMatchPtr comp) {
307 for (i = 0;i < comp->nbStep;i++) {
308 comp->steps[i].previous = NULL;
312 /************************************************************************
314 * The interpreter for the precompiled patterns *
316 ************************************************************************/
320 * @ctxt: a XSLT process context
321 * @comp: the precompiled pattern
323 * @mode: the mode name or NULL
324 * @modeURI: the mode URI or NULL
326 * Test wether the node matches the pattern
328 * Returns 1 if it matches, 0 if it doesn't and -1 in case of failure
331 xsltTestCompMatch(xsltTransformContextPtr ctxt, xsltCompMatchPtr comp,
332 xmlNodePtr node, const xmlChar *mode,
333 const xmlChar *modeURI) {
335 xsltStepOpPtr step, select = NULL;
337 if ((comp == NULL) || (node == NULL) || (ctxt == NULL)) {
338 xsltGenericError(xsltGenericErrorContext,
339 "xsltTestCompMatch: null arg\n");
343 if (comp->mode == NULL)
345 if ((comp->mode != mode) && (!xmlStrEqual(comp->mode, mode)))
348 if (comp->mode != NULL)
351 if (modeURI != NULL) {
352 if (comp->modeURI == NULL)
354 if ((comp->modeURI != modeURI) &&
355 (!xmlStrEqual(comp->modeURI, modeURI)))
358 if (comp->modeURI != NULL)
361 for (i = 0;i < comp->nbStep;i++) {
362 step = &comp->steps[i];
363 if (step->op != XSLT_OP_PREDICATE)
369 if ((node->type == XML_DOCUMENT_NODE) ||
370 (node->type == XML_HTML_DOCUMENT_NODE))
374 if (node->type != XML_ELEMENT_NODE)
376 if (step->value == NULL)
378 if (!xmlStrEqual(step->value, node->name))
382 if (node->ns == NULL) {
383 if (step->value2 != NULL)
385 } else if (node->ns->href != NULL) {
386 if (step->value2 == NULL)
388 if (!xmlStrEqual(step->value2, node->ns->href))
393 TODO /* Handle OP_CHILD */
396 if (node->type != XML_ATTRIBUTE_NODE)
398 if (step->value == NULL)
400 if (!xmlStrEqual(step->value, node->name))
404 if (node->ns == NULL) {
405 if (step->value2 != NULL)
407 } else if (node->ns->href != NULL) {
408 if (step->value2 == NULL)
410 if (!xmlStrEqual(step->value2, node->ns->href))
418 if (step->value == NULL)
420 if (!xmlStrEqual(step->value, node->name))
423 if (node->ns == NULL) {
424 if (step->value2 != NULL)
426 } else if (node->ns->href != NULL) {
427 if (step->value2 == NULL)
429 if (!xmlStrEqual(step->value2, node->ns->href))
433 case XSLT_OP_ANCESTOR:
434 /* TODO: implement coalescing of ANCESTOR/NODE ops */
435 if (step->value == NULL) {
437 step = &comp->steps[i];
438 if (step->op == XSLT_OP_ROOT)
440 if (step->op != XSLT_OP_ELEM)
442 if (step->value == NULL)
448 while (node != NULL) {
451 if (xmlStrEqual(step->value, node->name)) {
453 if (node->ns == NULL) {
454 if (step->value2 == NULL)
456 } else if (node->ns->href != NULL) {
457 if ((step->value2 != NULL) &&
458 (xmlStrEqual(step->value2, node->ns->href)))
468 /* TODO Handle IDs decently, must be done differently */
471 id = xmlGetID(node->doc, step->value);
472 if ((id == NULL) || (id->parent != node))
480 list = xsltGetKey(ctxt, step->value,
481 step->value3, step->value2);
484 for (indx = 0;indx < list->nodeNr;indx++)
485 if (list->nodeTab[indx] == node)
487 if (indx >= list->nodeNr)
493 if (node->ns == NULL) {
494 if (step->value != NULL)
496 } else if (node->ns->href != NULL) {
497 if (step->value == NULL)
499 if (!xmlStrEqual(step->value, node->ns->href))
504 switch (node->type) {
505 case XML_DOCUMENT_NODE:
506 case XML_HTML_DOCUMENT_NODE:
507 case XML_ELEMENT_NODE:
513 case XSLT_OP_PREDICATE: {
516 int pos = 0, len = 0;
518 * Depending on the last selection, one may need to
519 * recompute contextSize and proximityPosition.
521 oldCS = ctxt->xpathCtxt->contextSize;
522 oldCP = ctxt->xpathCtxt->proximityPosition;
523 if ((select != NULL) &&
524 (select->op == XSLT_OP_ELEM) &&
525 (select->value != NULL) &&
526 (node->type == XML_ELEMENT_NODE) &&
527 (node->parent != NULL)) {
529 if ((select->previous != NULL) &&
530 (select->previous->parent == node->parent)) {
532 * just walk back to adjust the index
535 xmlNodePtr sibling = node;
537 while (sibling != NULL) {
538 if (sibling == select->previous)
540 if (xmlStrEqual(node->name, sibling->name)) {
541 if ((select->value2 == NULL) ||
542 ((sibling->ns != NULL) &&
543 (xmlStrEqual(select->value2,
544 sibling->ns->href))))
547 sibling = sibling->prev;
549 if (sibling == NULL) {
550 /* hum going backward in document order ... */
553 while (sibling != NULL) {
554 if (sibling == select->previous)
556 if ((select->value2 == NULL) ||
557 ((sibling->ns != NULL) &&
558 (xmlStrEqual(select->value2,
559 sibling->ns->href))))
561 sibling = sibling->next;
564 if (sibling != NULL) {
565 pos = select->index + indx;
567 select->previous = node;
573 * recompute the index
575 xmlNodePtr siblings = node->parent->children;
577 while (siblings != NULL) {
578 if (siblings->type == XML_ELEMENT_NODE) {
579 if (siblings == node) {
582 } else if (xmlStrEqual(node->name,
584 if ((select->value2 == NULL) ||
585 ((siblings->ns != NULL) &&
586 (xmlStrEqual(select->value2,
587 siblings->ns->href))))
591 siblings = siblings->next;
595 ctxt->xpathCtxt->contextSize = len;
596 ctxt->xpathCtxt->proximityPosition = pos;
597 select->previous = node;
601 } else if ((select != NULL) && (select->op == XSLT_OP_ALL)) {
602 if ((select->previous != NULL) &&
603 (select->previous->parent == node->parent)) {
605 * just walk back to adjust the index
608 xmlNodePtr sibling = node;
610 while (sibling != NULL) {
611 if (sibling == select->previous)
613 if (sibling->type == XML_ELEMENT_NODE)
615 sibling = sibling->prev;
617 if (sibling == NULL) {
618 /* hum going backward in document order ... */
621 while (sibling != NULL) {
622 if (sibling == select->previous)
624 if (sibling->type == XML_ELEMENT_NODE)
626 sibling = sibling->next;
629 if (sibling != NULL) {
630 pos = select->index + indx;
632 select->previous = node;
638 * recompute the index
640 xmlNodePtr siblings = node->parent->children;
642 while (siblings != NULL) {
643 if (siblings->type == XML_ELEMENT_NODE) {
645 if (siblings == node) {
649 siblings = siblings->next;
653 ctxt->xpathCtxt->contextSize = len;
654 ctxt->xpathCtxt->proximityPosition = pos;
655 select->previous = node;
660 oldNode = ctxt->node;
663 if (step->value == NULL)
666 if (step->comp == NULL) {
667 step->comp = xmlXPathCompile(step->value);
668 if (step->comp == NULL)
671 if (!xsltEvalXPathPredicate(ctxt, step->comp))
675 ctxt->xpathCtxt->contextSize = oldCS;
676 ctxt->xpathCtxt->proximityPosition = oldCP;
678 ctxt->node = oldNode;
682 ctxt->xpathCtxt->contextSize = oldCS;
683 ctxt->xpathCtxt->proximityPosition = oldCP;
685 ctxt->node = oldNode;
689 if (node->type != XML_PI_NODE)
691 if (step->value != NULL) {
692 if (!xmlStrEqual(step->value, node->name))
696 case XSLT_OP_COMMENT:
697 if (node->type != XML_COMMENT_NODE)
701 if ((node->type != XML_TEXT_NODE) &&
702 (node->type != XML_CDATA_SECTION_NODE))
706 switch (node->type) {
707 case XML_DOCUMENT_NODE:
708 case XML_HTML_DOCUMENT_NODE:
709 case XML_ELEMENT_NODE:
710 case XML_CDATA_SECTION_NODE:
712 case XML_COMMENT_NODE:
714 case XML_ATTRIBUTE_NODE:
726 * xsltTestCompMatchList:
727 * @ctxt: a XSLT process context
729 * @comp: the precompiled pattern list
731 * Test wether the node matches one of the patterns in the list
733 * Returns 1 if it matches, 0 if it doesn't and -1 in case of failure
736 xsltTestCompMatchList(xsltTransformContextPtr ctxt, xmlNodePtr node,
737 xsltCompMatchPtr comp) {
740 if ((ctxt == NULL) || (node == NULL))
742 while (comp != NULL) {
743 ret = xsltTestCompMatch(ctxt, comp, node, NULL, NULL);
751 /************************************************************************
753 * Dedicated parser for templates *
755 ************************************************************************/
757 #define CUR (*ctxt->cur)
758 #define SKIP(val) ctxt->cur += (val)
759 #define NXT(val) ctxt->cur[(val)]
760 #define CUR_PTR ctxt->cur
762 #define SKIP_BLANKS \
763 while (IS_BLANK(CUR)) NEXT
765 #define CURRENT (*ctxt->cur)
766 #define NEXT ((*ctxt->cur) ? ctxt->cur++: ctxt->cur)
769 #define PUSH(op, val, val2) \
770 if (xsltCompMatchAdd(ctxt->comp, (op), (val), (val2))) goto error;
773 xsltSwapTopCompMatch(ctxt->comp);
775 #define XSLT_ERROR(X) \
776 { xsltError(ctxt, __FILE__, __LINE__, X); \
777 ctxt->error = (X); return; }
779 #define XSLT_ERROR0(X) \
780 { xsltError(ctxt, __FILE__, __LINE__, X); \
781 ctxt->error = (X); return(0); }
785 * @ctxt: the XPath Parser context
787 * Parse an XPath Litteral:
789 * [29] Literal ::= '"' [^"]* '"'
792 * Returns the Literal parsed or NULL
796 xsltScanLiteral(xsltParserContextPtr ctxt) {
804 while ((IS_CHAR(CUR)) && (CUR != '"'))
807 /* XP_ERROR(XPATH_UNFINISHED_LITERAL_ERROR); */
811 ret = xmlStrndup(q, CUR_PTR - q);
814 } else if (CUR == '\'') {
817 while ((IS_CHAR(CUR)) && (CUR != '\''))
820 /* XP_ERROR(XPATH_UNFINISHED_LITERAL_ERROR); */
824 ret = xmlStrndup(q, CUR_PTR - q);
828 /* XP_ERROR(XPATH_START_LITERAL_ERROR); */
837 * @ctxt: the XPath Parser context
839 * Trickery: parse an XML name but without consuming the input flow
840 * Needed to avoid insanity in the parser state.
842 * [4] NameChar ::= Letter | Digit | '.' | '-' | '_' | ':' |
843 * CombiningChar | Extender
845 * [5] Name ::= (Letter | '_' | ':') (NameChar)*
847 * [6] Names ::= Name (S Name)*
849 * Returns the Name parsed or NULL
853 xsltScanName(xsltParserContextPtr ctxt) {
854 xmlChar buf[XML_MAX_NAMELEN];
858 if (!IS_LETTER(CUR) && (CUR != '_') &&
863 while ((IS_LETTER(NXT(len))) || (IS_DIGIT(NXT(len))) ||
864 (NXT(len) == '.') || (NXT(len) == '-') ||
865 (NXT(len) == '_') || (NXT(len) == ':') ||
866 (IS_COMBINING(NXT(len))) ||
867 (IS_EXTENDER(NXT(len)))) {
870 if (len >= XML_MAX_NAMELEN) {
871 xmlGenericError(xmlGenericErrorContext,
872 "xmlScanName: reached XML_MAX_NAMELEN limit\n");
873 while ((IS_LETTER(NXT(len))) || (IS_DIGIT(NXT(len))) ||
874 (NXT(len) == '.') || (NXT(len) == '-') ||
875 (NXT(len) == '_') || (NXT(len) == ':') ||
876 (IS_COMBINING(NXT(len))) ||
877 (IS_EXTENDER(NXT(len))))
883 return(xmlStrndup(buf, len));
886 * xsltCompileIdKeyPattern:
887 * @comp: the compilation context
888 * @name: a preparsed name
889 * @aid: whether id/key are allowed there
891 * Compile the XSLT LocationIdKeyPattern
892 * [3] IdKeyPattern ::= 'id' '(' Literal ')'
893 * | 'key' '(' Literal ',' Literal ')'
895 * also handle NodeType and PI from:
897 * [7] NodeTest ::= NameTest
899 * | 'processing-instruction' '(' Literal ')'
902 xsltCompileIdKeyPattern(xsltParserContextPtr ctxt, xmlChar *name, int aid) {
904 xmlChar *lit2 = NULL;
907 xsltGenericError(xsltGenericErrorContext,
908 "xsltCompileIdKeyPattern : ( expected\n");
912 if ((aid) && (xmlStrEqual(name, (const xmlChar *)"id"))) {
915 lit = xsltScanLiteral(ctxt);
920 xsltGenericError(xsltGenericErrorContext,
921 "xsltCompileIdKeyPattern : ) expected\n");
926 PUSH(XSLT_OP_ID, lit, NULL);
927 } else if ((aid) && (xmlStrEqual(name, (const xmlChar *)"key"))) {
930 lit = xsltScanLiteral(ctxt);
935 xsltGenericError(xsltGenericErrorContext,
936 "xsltCompileIdKeyPattern : , expected\n");
942 lit2 = xsltScanLiteral(ctxt);
947 xsltGenericError(xsltGenericErrorContext,
948 "xsltCompileIdKeyPattern : ) expected\n");
953 /* TODO: support namespace in keys */
954 PUSH(XSLT_OP_KEY, lit, lit2);
955 } else if (xmlStrEqual(name, (const xmlChar *)"processing-instruction")) {
959 lit = xsltScanLiteral(ctxt);
964 xsltGenericError(xsltGenericErrorContext,
965 "xsltCompileIdKeyPattern : ) expected\n");
971 PUSH(XSLT_OP_PI, lit, NULL);
972 } else if (xmlStrEqual(name, (const xmlChar *)"text")) {
976 xsltGenericError(xsltGenericErrorContext,
977 "xsltCompileIdKeyPattern : ) expected\n");
982 PUSH(XSLT_OP_TEXT, NULL, NULL);
983 } else if (xmlStrEqual(name, (const xmlChar *)"comment")) {
987 xsltGenericError(xsltGenericErrorContext,
988 "xsltCompileIdKeyPattern : ) expected\n");
993 PUSH(XSLT_OP_COMMENT, NULL, NULL);
994 } else if (xmlStrEqual(name, (const xmlChar *)"node")) {
998 xsltGenericError(xsltGenericErrorContext,
999 "xsltCompileIdKeyPattern : ) expected\n");
1004 PUSH(XSLT_OP_NODE, NULL, NULL);
1006 xsltGenericError(xsltGenericErrorContext,
1007 "xsltCompileIdKeyPattern : expecting 'key' or 'id' or node type\n");
1011 xsltGenericError(xsltGenericErrorContext,
1012 "xsltCompileIdKeyPattern : node type\n");
1022 * xsltCompileStepPattern:
1023 * @comp: the compilation context
1024 * @token: a posible precompiled name
1026 * Compile the XSLT StepPattern and generates a precompiled
1027 * form suitable for fast matching.
1029 * [5] StepPattern ::= ChildOrAttributeAxisSpecifier NodeTest Predicate*
1030 * [6] ChildOrAttributeAxisSpecifier ::= AbbreviatedAxisSpecifier
1031 * | ('child' | 'attribute') '::'
1033 * [7] NodeTest ::= NameTest
1034 * | NodeType '(' ')'
1035 * | 'processing-instruction' '(' Literal ')'
1036 * [8] Predicate ::= '[' PredicateExpr ']'
1037 * [9] PredicateExpr ::= Expr
1038 * [13] AbbreviatedAxisSpecifier ::= '@'?
1039 * [37] NameTest ::= '*' | NCName ':' '*' | QName
1043 xsltCompileStepPattern(xsltParserContextPtr ctxt, xmlChar *token) {
1044 xmlChar *name = NULL;
1045 xmlChar *prefix = NULL;
1046 xmlChar *ncname = NULL;
1047 xmlChar *URL = NULL;
1051 if ((token == NULL) && (CUR == '@')) {
1055 PUSH(XSLT_OP_ATTR, NULL, NULL);
1058 token = xsltScanName(ctxt);
1059 if (token == NULL) {
1060 xsltGenericError(xsltGenericErrorContext,
1061 "xsltCompileStepPattern : Name expected\n");
1065 PUSH(XSLT_OP_ATTR, token, NULL);
1069 token = xsltScanName(ctxt);
1070 if (token == NULL) {
1073 PUSH(XSLT_OP_ALL, token, NULL);
1074 goto parse_predicate;
1076 xsltGenericError(xsltGenericErrorContext,
1077 "xsltCompileStepPattern : Name expected\n");
1086 xsltCompileIdKeyPattern(ctxt, token, 0);
1089 } else if (CUR == ':') {
1091 if (NXT(1) != ':') {
1092 xsltGenericError(xsltGenericErrorContext,
1093 "xsltCompileStepPattern : sequence '::' expected\n");
1098 if (xmlStrEqual(token, (const xmlChar *) "child")) {
1099 name = xsltScanName(ctxt);
1101 xsltGenericError(xsltGenericErrorContext,
1102 "xsltCompileStepPattern : QName expected\n");
1106 ncname = xmlSplitQName2(name, &prefix);
1107 if (ncname != NULL) {
1108 if (prefix != NULL) {
1111 ns = xmlSearchNs(ctxt->doc, ctxt->elem, prefix);
1113 xsltGenericError(xsltGenericErrorContext,
1114 "xsl: pattern, no namespace bound to prefix %s\n",
1117 URL = xmlStrdup(ns->href);
1124 PUSH(XSLT_OP_CHILD, name, URL);
1125 } else if (xmlStrEqual(token, (const xmlChar *) "attribute")) {
1126 name = xsltScanName(ctxt);
1128 xsltGenericError(xsltGenericErrorContext,
1129 "xsltCompileStepPattern : QName expected\n");
1133 ncname = xmlSplitQName2(name, &prefix);
1134 if (ncname != NULL) {
1135 if (prefix != NULL) {
1138 ns = xmlSearchNs(ctxt->doc, ctxt->elem, prefix);
1140 xsltGenericError(xsltGenericErrorContext,
1141 "xsl: pattern, no namespace bound to prefix %s\n",
1144 URL = xmlStrdup(ns->href);
1151 PUSH(XSLT_OP_ATTR, name, URL);
1153 xsltGenericError(xsltGenericErrorContext,
1154 "xsltCompileStepPattern : 'child' or 'attribute' expected\n");
1159 } else if (CUR == '*') {
1161 PUSH(XSLT_OP_ALL, token, NULL);
1163 ncname = xmlSplitQName2(token, &prefix);
1164 if (ncname != NULL) {
1165 if (prefix != NULL) {
1168 ns = xmlSearchNs(ctxt->doc, ctxt->elem, prefix);
1170 xsltGenericError(xsltGenericErrorContext,
1171 "xsl: pattern, no namespace bound to prefix %s\n",
1174 URL = xmlStrdup(ns->href);
1181 PUSH(XSLT_OP_ELEM, token, URL);
1186 while (CUR == '[') {
1188 xmlChar *ret = NULL;
1193 /* TODO: avoid breaking in strings ... */
1194 while (IS_CHAR(CUR)) {
1195 /* Skip over nested predicates */
1205 if (!IS_CHAR(CUR)) {
1206 xsltGenericError(xsltGenericErrorContext,
1207 "xsltCompileStepPattern : ']' expected\n");
1211 ret = xmlStrndup(q, CUR_PTR - q);
1212 PUSH(XSLT_OP_PREDICATE, ret, NULL);
1213 /* push the predicate lower than local test */
1226 * xsltCompileRelativePathPattern:
1227 * @comp: the compilation context
1228 * @token: a posible precompiled name
1230 * Compile the XSLT RelativePathPattern and generates a precompiled
1231 * form suitable for fast matching.
1233 * [4] RelativePathPattern ::= StepPattern
1234 * | RelativePathPattern '/' StepPattern
1235 * | RelativePathPattern '//' StepPattern
1238 xsltCompileRelativePathPattern(xsltParserContextPtr ctxt, xmlChar *token) {
1239 xsltCompileStepPattern(ctxt, token);
1243 while ((CUR != 0) && (CUR != '|')) {
1244 if ((CUR == '/') && (NXT(1) == '/')) {
1245 PUSH(XSLT_OP_ANCESTOR, NULL, NULL);
1249 xsltCompileStepPattern(ctxt, NULL);
1250 } else if (CUR == '/') {
1251 PUSH(XSLT_OP_PARENT, NULL, NULL);
1254 if ((CUR != 0) || (CUR == '|')) {
1255 xsltCompileRelativePathPattern(ctxt, NULL);
1269 * xsltCompileLocationPathPattern:
1270 * @comp: the compilation context
1272 * Compile the XSLT LocationPathPattern and generates a precompiled
1273 * form suitable for fast matching.
1275 * [2] LocationPathPattern ::= '/' RelativePathPattern?
1276 * | IdKeyPattern (('/' | '//') RelativePathPattern)?
1277 * | '//'? RelativePathPattern
1280 xsltCompileLocationPathPattern(xsltParserContextPtr ctxt) {
1282 if ((CUR == '/') && (NXT(1) == '/')) {
1284 * since we reverse the query
1285 * a leading // can be safely ignored
1289 xsltCompileRelativePathPattern(ctxt, NULL);
1290 } else if (CUR == '/') {
1292 * We need to find root as the parent
1296 PUSH(XSLT_OP_ROOT, NULL, NULL);
1297 if ((CUR != 0) || (CUR == '|')) {
1298 PUSH(XSLT_OP_PARENT, NULL, NULL);
1299 xsltCompileRelativePathPattern(ctxt, NULL);
1301 } else if (CUR == '*') {
1302 xsltCompileRelativePathPattern(ctxt, NULL);
1303 } else if (CUR == '@') {
1304 xsltCompileRelativePathPattern(ctxt, NULL);
1307 name = xsltScanName(ctxt);
1309 xsltGenericError(xsltGenericErrorContext,
1310 "xsltCompileLocationPathPattern : Name expected\n");
1315 if ((CUR == '(') && !xmlXPathIsNodeType(name)) {
1316 xsltCompileIdKeyPattern(ctxt, name, 1);
1317 if ((CUR == '/') && (NXT(1) == '/')) {
1318 PUSH(XSLT_OP_ANCESTOR, NULL, NULL);
1322 xsltCompileRelativePathPattern(ctxt, NULL);
1323 } else if (CUR == '/') {
1324 PUSH(XSLT_OP_PARENT, NULL, NULL);
1327 xsltCompileRelativePathPattern(ctxt, NULL);
1331 xsltCompileRelativePathPattern(ctxt, name);
1338 * xsltCompilePattern:
1339 * @pattern an XSLT pattern
1340 * @doc: the containing document
1341 * @node: the containing element
1343 * Compile the XSLT pattern and generates a list of precompiled form suitable
1344 * for fast matching.
1346 * [1] Pattern ::= LocationPathPattern | Pattern '|' LocationPathPattern
1348 * Returns the generated pattern list or NULL in case of failure
1352 xsltCompilePattern(const xmlChar *pattern, xmlDocPtr doc, xmlNodePtr node) {
1353 xsltParserContextPtr ctxt = NULL;
1354 xsltCompMatchPtr element, first = NULL, previous = NULL;
1355 int current, start, end;
1357 if (pattern == NULL) {
1358 xsltGenericError(xsltGenericErrorContext,
1359 "xsltCompilePattern : NULL pattern\n");
1363 #ifdef WITH_XSLT_DEBUG_PATTERN
1364 xsltGenericDebug(xsltGenericDebugContext,
1365 "xsltCompilePattern : parsing '%s'\n", pattern);
1368 ctxt = xsltNewParserContext();
1374 while (pattern[current] != 0) {
1376 while (IS_BLANK(pattern[current]))
1379 while ((pattern[end] != 0) && (pattern[end] != '|'))
1381 if (current == end) {
1382 xsltGenericError(xsltGenericErrorContext,
1383 "xsltCompilePattern : NULL pattern\n");
1386 element = xsltNewCompMatch();
1387 if (element == NULL) {
1392 else if (previous != NULL)
1393 previous->next = element;
1396 ctxt->comp = element;
1397 ctxt->base = xmlStrndup(&pattern[start], end - start);
1398 ctxt->cur = &(ctxt->base)[current - start];
1399 xsltCompileLocationPathPattern(ctxt);
1401 xmlFree((xmlChar *)ctxt->base);
1406 * Reverse for faster interpretation.
1408 xsltReverseCompMatch(element);
1411 * Set-up the priority
1413 if (((element->steps[0].op == XSLT_OP_ELEM) ||
1414 (element->steps[0].op == XSLT_OP_ATTR)) &&
1415 (element->steps[0].value != NULL) &&
1416 (element->steps[1].op == XSLT_OP_END)) {
1417 element->priority = 0;
1418 } else if ((element->steps[0].op == XSLT_OP_ROOT) &&
1419 (element->steps[1].op == XSLT_OP_END)) {
1420 element->priority = 0;
1421 } else if ((element->steps[0].op == XSLT_OP_PI) &&
1422 (element->steps[0].value != NULL) &&
1423 (element->steps[1].op == XSLT_OP_END)) {
1424 element->priority = 0;
1425 } else if ((element->steps[0].op == XSLT_OP_NS) &&
1426 (element->steps[0].value != NULL) &&
1427 (element->steps[1].op == XSLT_OP_END)) {
1428 element->priority = -0.25;
1429 } else if (((element->steps[0].op == XSLT_OP_PI) ||
1430 (element->steps[0].op == XSLT_OP_TEXT) ||
1431 (element->steps[0].op == XSLT_OP_ALL) ||
1432 (element->steps[0].op == XSLT_OP_NODE) ||
1433 (element->steps[0].op == XSLT_OP_COMMENT)) &&
1434 (element->steps[1].op == XSLT_OP_END)) {
1435 element->priority = -0.5;
1437 element->priority = 0.5;
1439 if (pattern[end] == '|')
1444 xsltGenericError(xsltGenericErrorContext,
1445 "xsltCompilePattern : NULL pattern\n");
1449 xsltFreeParserContext(ctxt);
1454 xsltFreeParserContext(ctxt);
1456 xsltFreeCompMatchList(first);
1460 /************************************************************************
1462 * Module interfaces *
1464 ************************************************************************/
1468 * @style: an XSLT stylesheet
1469 * @cur: an XSLT template
1470 * @mode: the mode name or NULL
1471 * @modeURI: the mode URI or NULL
1473 * Register the XSLT pattern associated to @cur
1475 * Returns -1 in case of error, 0 otherwise
1478 xsltAddTemplate(xsltStylesheetPtr style, xsltTemplatePtr cur,
1479 const xmlChar *mode, const xmlChar *modeURI) {
1480 xsltCompMatchPtr pat, list, *top = NULL, next;
1481 const xmlChar *name = NULL;
1483 if ((style == NULL) || (cur == NULL) || (cur->match == NULL))
1486 pat = xsltCompilePattern(cur->match, style->doc, cur->elem);
1491 pat->template = cur;
1493 pat->mode = xmlStrdup(mode);
1494 if (modeURI != NULL)
1495 pat->modeURI = xmlStrdup(modeURI);
1496 if (cur->priority == XSLT_PAT_NO_PRIORITY)
1497 cur->priority = pat->priority;
1499 pat->priority = cur->priority;
1502 * insert it in the hash table list corresponding to its lookup name
1504 switch (pat->steps[0].op) {
1506 if (pat->steps[0].value != NULL)
1507 name = pat->steps[0].value;
1509 top = (xsltCompMatchPtr *) &(style->attrMatch);
1513 case XSLT_OP_PARENT:
1514 case XSLT_OP_ANCESTOR:
1516 name = pat->steps[0].value;
1519 top = (xsltCompMatchPtr *) &(style->rootMatch);
1522 top = (xsltCompMatchPtr *) &(style->keyMatch);
1525 /* TODO optimize ID !!! */
1527 top = (xsltCompMatchPtr *) &(style->elemMatch);
1530 case XSLT_OP_PREDICATE:
1531 xsltGenericError(xsltGenericErrorContext,
1532 "xsltAddTemplate: invalid compiled pattern\n");
1533 xsltFreeCompMatch(pat);
1536 * TODO: some flags at the top level about type based patterns
1537 * would be faster than inclusion in the hash table.
1540 if (pat->steps[0].value != NULL)
1541 name = pat->steps[0].value;
1543 top = (xsltCompMatchPtr *) &(style->piMatch);
1545 case XSLT_OP_COMMENT:
1546 top = (xsltCompMatchPtr *) &(style->commentMatch);
1549 top = (xsltCompMatchPtr *) &(style->textMatch);
1552 if (pat->steps[0].value != NULL)
1553 name = pat->steps[0].value;
1555 top = (xsltCompMatchPtr *) &(style->elemMatch);
1560 if (style->templatesHash == NULL) {
1561 style->templatesHash = xmlHashCreate(1024);
1562 if (style->templatesHash == NULL) {
1563 xsltFreeCompMatch(pat);
1566 xmlHashAddEntry3(style->templatesHash, name, mode, modeURI, pat);
1568 list = (xsltCompMatchPtr) xmlHashLookup3(style->templatesHash,
1569 name, mode, modeURI);
1571 xmlHashAddEntry3(style->templatesHash, name,
1572 mode, modeURI, pat);
1575 * Note '<=' since one must choose among the matching
1576 * template rules that are left, the one that occurs
1577 * last in the stylesheet
1579 if (list->priority <= pat->priority) {
1581 xmlHashUpdateEntry3(style->templatesHash, name,
1582 mode, modeURI, pat, NULL);
1584 while (list->next != NULL) {
1585 if (list->next->priority <= pat->priority)
1589 pat->next = list->next;
1594 } else if (top != NULL) {
1599 } else if (list->priority <= pat->priority) {
1603 while (list->next != NULL) {
1604 if (list->next->priority <= pat->priority)
1608 pat->next = list->next;
1612 xsltGenericError(xsltGenericErrorContext,
1613 "xsltAddTemplate: invalid compiled pattern\n");
1614 xsltFreeCompMatch(pat);
1617 #ifdef WITH_XSLT_DEBUG_PATTERN
1619 xsltGenericDebug(xsltGenericDebugContext,
1620 "added pattern : '%s' mode '%s' priority %f\n",
1621 pat->template->match, pat->mode, pat->priority);
1623 xsltGenericDebug(xsltGenericDebugContext,
1624 "added pattern : '%s' priority %f\n",
1625 pat->template->match, pat->priority);
1635 * @ctxt: a XSLT process context
1637 * @style: the current style
1639 * Finds the template applying to this node, if @style is non-NULL
1640 * it means one need to look for the next imported template in scope.
1642 * Returns the xsltTemplatePtr or NULL if not found
1645 xsltGetTemplate(xsltTransformContextPtr ctxt, xmlNodePtr node,
1646 xsltStylesheetPtr style) {
1647 xsltStylesheetPtr curstyle;
1648 xsltTemplatePtr ret = NULL;
1649 const xmlChar *name = NULL;
1650 xsltCompMatchPtr list = NULL;
1652 if ((ctxt == NULL) || (node == NULL))
1655 if (style == NULL) {
1656 curstyle = ctxt->style;
1658 curstyle = xsltNextImport(style);
1661 while ((curstyle != NULL) && (curstyle != style)) {
1662 /* TODO : handle IDs/keys here ! */
1663 if (curstyle->templatesHash != NULL) {
1665 * Use the top name as selector
1667 switch (node->type) {
1668 case XML_ELEMENT_NODE:
1669 case XML_ATTRIBUTE_NODE:
1673 case XML_DOCUMENT_NODE:
1674 case XML_HTML_DOCUMENT_NODE:
1676 case XML_CDATA_SECTION_NODE:
1677 case XML_COMMENT_NODE:
1678 case XML_ENTITY_REF_NODE:
1679 case XML_ENTITY_NODE:
1680 case XML_DOCUMENT_TYPE_NODE:
1681 case XML_DOCUMENT_FRAG_NODE:
1682 case XML_NOTATION_NODE:
1684 case XML_ELEMENT_DECL:
1685 case XML_ATTRIBUTE_DECL:
1686 case XML_ENTITY_DECL:
1687 case XML_NAMESPACE_DECL:
1688 case XML_XINCLUDE_START:
1689 case XML_XINCLUDE_END:
1698 * find the list of appliable expressions based on the name
1700 list = (xsltCompMatchPtr) xmlHashLookup3(curstyle->templatesHash,
1701 name, ctxt->mode, ctxt->modeURI);
1704 while (list != NULL) {
1705 if (xsltTestCompMatch(ctxt, list, node,
1706 ctxt->mode, ctxt->modeURI)) {
1707 ret = list->template;
1715 * find alternate generic matches
1717 switch (node->type) {
1718 case XML_ELEMENT_NODE:
1719 list = curstyle->elemMatch;
1721 case XML_ATTRIBUTE_NODE:
1722 list = curstyle->attrMatch;
1725 list = curstyle->piMatch;
1727 case XML_DOCUMENT_NODE:
1728 case XML_HTML_DOCUMENT_NODE:
1729 list = curstyle->rootMatch;
1732 case XML_CDATA_SECTION_NODE:
1733 list = curstyle->textMatch;
1735 case XML_COMMENT_NODE:
1736 list = curstyle->commentMatch;
1738 case XML_ENTITY_REF_NODE:
1739 case XML_ENTITY_NODE:
1740 case XML_DOCUMENT_TYPE_NODE:
1741 case XML_DOCUMENT_FRAG_NODE:
1742 case XML_NOTATION_NODE:
1744 case XML_ELEMENT_DECL:
1745 case XML_ATTRIBUTE_DECL:
1746 case XML_ENTITY_DECL:
1747 case XML_NAMESPACE_DECL:
1748 case XML_XINCLUDE_START:
1749 case XML_XINCLUDE_END:
1755 while ((list != NULL) &&
1756 ((ret == NULL) || (list->priority > ret->priority))) {
1757 if (xsltTestCompMatch(ctxt, list, node,
1758 ctxt->mode, ctxt->modeURI)) {
1759 ret = list->template;
1764 if (node->_private != NULL) {
1765 list = curstyle->keyMatch;
1766 while ((list != NULL) &&
1767 ((ret == NULL) || (list->priority > ret->priority))) {
1768 if (xsltTestCompMatch(ctxt, list, node,
1769 ctxt->mode, ctxt->modeURI)) {
1770 ret = list->template;
1780 * Cycle on next curstylesheet import.
1782 curstyle = xsltNextImport(curstyle);
1788 * xsltCleanupTemplates:
1789 * @style: an XSLT stylesheet
1791 * Cleanup the state of the templates used by the stylesheet and
1792 * the ones it imports.
1795 xsltCleanupTemplates(xsltStylesheetPtr style) {
1796 while (style != NULL) {
1797 xmlHashScan((xmlHashTablePtr) style->templatesHash,
1798 (xmlHashScanner) xsltCleanupCompMatch, NULL);
1800 style = xsltNextImport(style);
1805 * xsltFreeTemplateHashes:
1806 * @style: an XSLT stylesheet
1808 * Free up the memory used by xsltAddTemplate/xsltGetTemplate mechanism
1811 xsltFreeTemplateHashes(xsltStylesheetPtr style) {
1812 if (style->templatesHash != NULL)
1813 xmlHashFree((xmlHashTablePtr) style->templatesHash,
1814 (xmlHashDeallocator) xsltFreeCompMatchList);
1815 if (style->rootMatch != NULL)
1816 xsltFreeCompMatchList(style->rootMatch);
1817 if (style->keyMatch != NULL)
1818 xsltFreeCompMatchList(style->keyMatch);
1819 if (style->elemMatch != NULL)
1820 xsltFreeCompMatchList(style->elemMatch);
1821 if (style->attrMatch != NULL)
1822 xsltFreeCompMatchList(style->attrMatch);
1823 if (style->parentMatch != NULL)
1824 xsltFreeCompMatchList(style->parentMatch);
1825 if (style->textMatch != NULL)
1826 xsltFreeCompMatchList(style->textMatch);
1827 if (style->piMatch != NULL)
1828 xsltFreeCompMatchList(style->piMatch);
1829 if (style->commentMatch != NULL)
1830 xsltFreeCompMatchList(style->commentMatch);
1836 * @node: a node in the source tree
1837 * @pattern: an XSLT pattern
1839 * Determine if a node matches a pattern.
1842 xsltMatchPattern(xsltTransformContextPtr context,
1844 const xmlChar *pattern)
1847 xsltCompMatchPtr first, comp;
1849 if ((context != NULL) && (pattern != NULL)) {
1850 first = xsltCompilePattern(pattern);
1851 for (comp = first; comp != NULL; comp = comp->next) {
1852 match = xsltTestCompMatch(context, comp, node, NULL, NULL);
1857 xsltFreeCompMatchList(first);