/*
 * pattern.c: Implemetation of the template match compilation and lookup
 *
 * Reference:
 *   http://www.w3.org/TR/1999/REC-xslt-19991116
 *
 * See Copyright for the status of this software.
 *
 * daniel@veillard.com
 */

/*
 * TODO: handle pathological cases like *[*[@a="b"]]
 * TODO: detect [number] at compilation, optimize accordingly
 */

#define IN_LIBXSLT
#include "libxslt.h"

#include <string.h>

#include <libxml/xmlmemory.h>
#include <libxml/tree.h>
#include <libxml/valid.h>
#include <libxml/hash.h>
#include <libxml/xmlerror.h>
#include <libxml/parserInternals.h>
#include "xslt.h"
#include "xsltInternals.h"
#include "xsltutils.h"
#include "imports.h"
#include "templates.h"
#include "keys.h"
#include "pattern.h"

#ifdef WITH_XSLT_DEBUG
#define WITH_XSLT_DEBUG_PATTERN
#endif

/*
 * Types are private:
 */

typedef enum {
    XSLT_OP_END=0,
    XSLT_OP_ROOT,
    XSLT_OP_ELEM,
    XSLT_OP_CHILD,
    XSLT_OP_ATTR,
    XSLT_OP_PARENT,
    XSLT_OP_ANCESTOR,
    XSLT_OP_ID,
    XSLT_OP_KEY,
    XSLT_OP_NS,
    XSLT_OP_ALL,
    XSLT_OP_PI,
    XSLT_OP_COMMENT,
    XSLT_OP_TEXT,
    XSLT_OP_NODE,
    XSLT_OP_PREDICATE
} xsltOp;


typedef struct _xsltStepOp xsltStepOp;
typedef xsltStepOp *xsltStepOpPtr;
struct _xsltStepOp {
    xsltOp op;
    xmlChar *value;
    xmlChar *value2;
    xmlChar *value3;
    xmlXPathCompExprPtr comp;
    /*
     * Optimisations for count
     */
    int        previousExtra;
    int        indexExtra;
    int        lenExtra;
};

struct _xsltCompMatch {
    struct _xsltCompMatch *next; /* siblings in the name hash */
    float priority;              /* the priority */
    const xmlChar *pattern;       /* the pattern */
    const xmlChar *mode;         /* the mode */
    const xmlChar *modeURI;      /* the mode URI */
    xsltTemplatePtr template;    /* the associated template */

    /* TODO fix the statically allocated size steps[] */
    int nbStep;
    int maxStep;
    xmlNsPtr *nsList;		/* the namespaces in scope */
    int nsNr;			/* the number of namespaces in scope */
    xsltStepOp steps[40];        /* ops for computation */
};

typedef struct _xsltParserContext xsltParserContext;
typedef xsltParserContext *xsltParserContextPtr;
struct _xsltParserContext {
    xsltStylesheetPtr style;		/* the stylesheet */
    xsltTransformContextPtr ctxt;	/* the transformation or NULL */
    const xmlChar *cur;			/* the current char being parsed */
    const xmlChar *base;		/* the full expression */
    xmlDocPtr      doc;			/* the source document */
    xmlNodePtr    elem;			/* the source element */
    int error;				/* error code */
    xsltCompMatchPtr comp;		/* the result */
};

/************************************************************************
 * 									*
 * 			Type functions 					*
 * 									*
 ************************************************************************/

/**
 * xsltNewCompMatch:
 *
 * Create a new XSLT CompMatch
 *
 * Returns the newly allocated xsltCompMatchPtr or NULL in case of error
 */
static xsltCompMatchPtr
xsltNewCompMatch(void) {
    xsltCompMatchPtr cur;

    cur = (xsltCompMatchPtr) xmlMalloc(sizeof(xsltCompMatch));
    if (cur == NULL) {
	xsltTransformError(NULL, NULL, NULL,
		"xsltNewCompMatch : malloc failed\n");
	return(NULL);
    }
    memset(cur, 0, sizeof(xsltCompMatch));
    cur->maxStep = 40;
    cur->nsNr = 0;
    cur->nsList = NULL;
    return(cur);
}

/**
 * xsltFreeCompMatch:
 * @comp:  an XSLT comp
 *
 * Free up the memory allocated by @comp
 */
static void
xsltFreeCompMatch(xsltCompMatchPtr comp) {
    xsltStepOpPtr op;
    int i;

    if (comp == NULL)
	return;
    if (comp->pattern != NULL)
	xmlFree((xmlChar *)comp->pattern);
    if (comp->mode != NULL)
	xmlFree((xmlChar *)comp->mode);
    if (comp->modeURI != NULL)
	xmlFree((xmlChar *)comp->modeURI);
    if (comp->nsList != NULL)
	xmlFree(comp->nsList);
    for (i = 0;i < comp->nbStep;i++) {
	op = &comp->steps[i];
	if (op->value != NULL)
	    xmlFree(op->value);
	if (op->value2 != NULL)
	    xmlFree(op->value2);
	if (op->value3 != NULL)
	    xmlFree(op->value3);
	if (op->comp != NULL)
	    xmlXPathFreeCompExpr(op->comp);
    }
    memset(comp, -1, sizeof(xsltCompMatch));
    xmlFree(comp);
}

/**
 * xsltFreeCompMatchList:
 * @comp:  an XSLT comp list
 *
 * Free up the memory allocated by all the elements of @comp
 */
void
xsltFreeCompMatchList(xsltCompMatchPtr comp) {
    xsltCompMatchPtr cur;

    while (comp != NULL) {
	cur = comp;
	comp = comp->next;
	xsltFreeCompMatch(cur);
    }
}

/**
 * xsltNewParserContext:
 * @style:  the stylesheet
 * @ctxt:  the transformation context, if done at run-time
 *
 * Create a new XSLT ParserContext
 *
 * Returns the newly allocated xsltParserContextPtr or NULL in case of error
 */
static xsltParserContextPtr
xsltNewParserContext(xsltStylesheetPtr style, xsltTransformContextPtr ctxt) {
    xsltParserContextPtr cur;

    cur = (xsltParserContextPtr) xmlMalloc(sizeof(xsltParserContext));
    if (cur == NULL) {
	xsltTransformError(NULL, NULL, NULL,
		"xsltNewParserContext : malloc failed\n");
	return(NULL);
    }
    memset(cur, 0, sizeof(xsltParserContext));
    cur->style = style;
    cur->ctxt = ctxt;
    return(cur);
}

/**
 * xsltFreeParserContext:
 * @ctxt:  an XSLT parser context
 *
 * Free up the memory allocated by @ctxt
 */
static void
xsltFreeParserContext(xsltParserContextPtr ctxt) {
    if (ctxt == NULL)
	return;
    memset(ctxt, -1, sizeof(xsltParserContext));
    xmlFree(ctxt);
}

/**
 * xsltCompMatchAdd:
 * @comp:  the compiled match expression
 * @op:  an op
 * @value:  the first value
 * @value2:  the second value
 *
 * Add an step to an XSLT Compiled Match
 *
 * Returns -1 in case of failure, 0 otherwise.
 */
static int
xsltCompMatchAdd(xsltParserContextPtr ctxt, xsltCompMatchPtr comp,
                 xsltOp op, xmlChar * value, xmlChar * value2)
{
    if (comp->nbStep >= 40) {
        xsltTransformError(NULL, NULL, NULL,
                         "xsltCompMatchAdd: overflow\n");
        return (-1);
    }
    comp->steps[comp->nbStep].op = op;
    comp->steps[comp->nbStep].value = value;
    comp->steps[comp->nbStep].value2 = value2;
    if (ctxt->ctxt != NULL) {
	comp->steps[comp->nbStep].previousExtra =
	    xsltAllocateExtraCtxt(ctxt->ctxt);
	comp->steps[comp->nbStep].indexExtra =
	    xsltAllocateExtraCtxt(ctxt->ctxt);
	comp->steps[comp->nbStep].lenExtra =
	    xsltAllocateExtraCtxt(ctxt->ctxt);
    } else {
	comp->steps[comp->nbStep].previousExtra =
	    xsltAllocateExtra(ctxt->style);
	comp->steps[comp->nbStep].indexExtra =
	    xsltAllocateExtra(ctxt->style);
	comp->steps[comp->nbStep].lenExtra =
	    xsltAllocateExtra(ctxt->style);
    }
    if (op == XSLT_OP_PREDICATE) {
	comp->steps[comp->nbStep].comp = xmlXPathCompile(value);
    }
    comp->nbStep++;
    return (0);
}

/**
 * xsltSwapTopCompMatch:
 * @comp:  the compiled match expression
 *
 * reverse the two top steps.
 */
static void
xsltSwapTopCompMatch(xsltCompMatchPtr comp) {
    int i;
    int j = comp->nbStep - 1;

    if (j > 0) {
	register xmlChar *tmp;
	register xsltOp op;
	register xmlXPathCompExprPtr expr; 
	i = j - 1;
	tmp = comp->steps[i].value;
	comp->steps[i].value = comp->steps[j].value;
	comp->steps[j].value = tmp;
	tmp = comp->steps[i].value2;
	comp->steps[i].value2 = comp->steps[j].value2;
	comp->steps[j].value2 = tmp;
	op = comp->steps[i].op;
	comp->steps[i].op = comp->steps[j].op;
	comp->steps[j].op = op;
	expr = comp->steps[i].comp;
	comp->steps[i].comp = comp->steps[j].comp;
	comp->steps[j].comp = expr;
    }
}

/**
 * xsltReverseCompMatch:
 * @comp:  the compiled match expression
 *
 * reverse all the stack of expressions
 */
static void
xsltReverseCompMatch(xsltCompMatchPtr comp) {
    int i = 0;
    int j = comp->nbStep - 1;

    while (j > i) {
	register xmlChar *tmp;
	register xsltOp op;
	register xmlXPathCompExprPtr expr; 
	tmp = comp->steps[i].value;
	comp->steps[i].value = comp->steps[j].value;
	comp->steps[j].value = tmp;
	tmp = comp->steps[i].value2;
	comp->steps[i].value2 = comp->steps[j].value2;
	comp->steps[j].value2 = tmp;
	op = comp->steps[i].op;
	comp->steps[i].op = comp->steps[j].op;
	comp->steps[j].op = op;
	expr = comp->steps[i].comp;
	comp->steps[i].comp = comp->steps[j].comp;
	comp->steps[j].comp = expr;
	j--;
	i++;
    }
    comp->steps[comp->nbStep++].op = XSLT_OP_END;
}

/************************************************************************
 * 									*
 * 		The interpreter for the precompiled patterns		*
 * 									*
 ************************************************************************/

/**
 * xsltTestCompMatch:
 * @ctxt:  a XSLT process context
 * @comp: the precompiled pattern
 * @node: a node
 * @mode:  the mode name or NULL
 * @modeURI:  the mode URI or NULL
 *
 * Test wether the node matches the pattern
 *
 * Returns 1 if it matches, 0 if it doesn't and -1 in case of failure
 */
static int
xsltTestCompMatch(xsltTransformContextPtr ctxt, xsltCompMatchPtr comp,
	          xmlNodePtr node, const xmlChar *mode,
		  const xmlChar *modeURI) {
    int i;
    xsltStepOpPtr step, select = NULL;

    if ((comp == NULL) || (node == NULL) || (ctxt == NULL)) {
	xsltTransformError(ctxt, NULL, node,
		"xsltTestCompMatch: null arg\n");
        return(-1);
    }
    if (mode != NULL) {
	if (comp->mode == NULL)
	    return(0);
	if ((comp->mode != mode) && (!xmlStrEqual(comp->mode, mode)))
	    return(0);
    } else {
	if (comp->mode != NULL)
	    return(0);
    }
    if (modeURI != NULL) {
	if (comp->modeURI == NULL)
	    return(0);
	if ((comp->modeURI != modeURI) &&
	    (!xmlStrEqual(comp->modeURI, modeURI)))
	    return(0);
    } else {
	if (comp->modeURI != NULL)
	    return(0);
    }
    for (i = 0;i < comp->nbStep;i++) {
	step = &comp->steps[i];
	if (step->op != XSLT_OP_PREDICATE)
	    select = step;
	switch (step->op) {
            case XSLT_OP_END:
		return(1);
            case XSLT_OP_ROOT:
		if ((node->type == XML_DOCUMENT_NODE) ||
#ifdef LIBXML_DOCB_ENABLED
		    (node->type == XML_DOCB_DOCUMENT_NODE) ||
#endif
		    (node->type == XML_HTML_DOCUMENT_NODE))
		    continue;
		return(0);
            case XSLT_OP_ELEM:
		if (node->type != XML_ELEMENT_NODE)
		    return(0);
		if (step->value == NULL)
		    continue;
		if (step->value[0] != node->name[0])
		    return(0);
		if (!xmlStrEqual(step->value, node->name))
		    return(0);

		/* Namespace test */
		if (node->ns == NULL) {
		    if (step->value2 != NULL)
			return(0);
		} else if (node->ns->href != NULL) {
		    if (step->value2 == NULL)
			return(0);
		    if (!xmlStrEqual(step->value2, node->ns->href))
			return(0);
		}
		continue;
            case XSLT_OP_CHILD: {
		xmlNodePtr lst;

		if ((node->type != XML_ELEMENT_NODE) &&
		    (node->type != XML_DOCUMENT_NODE) &&
#ifdef LIBXML_DOCB_ENABLED
		    (node->type != XML_DOCB_DOCUMENT_NODE) &&
#endif
		    (node->type != XML_HTML_DOCUMENT_NODE))
		    return(0);

		lst = node->children;

		if (step->value != NULL) {
		    while (lst != NULL) {
			if ((lst->type == XML_ELEMENT_NODE) &&
			    (step->value[0] == lst->name[0]) &&
			    (xmlStrEqual(step->value, lst->name)))
			    break;
			lst = lst->next;
		    }
		    if (lst != NULL)
			continue;
		}
		return(0);
	    }
            case XSLT_OP_ATTR:
		if (node->type != XML_ATTRIBUTE_NODE)
		    return(0);
		if (step->value != NULL) {
		    if (step->value[0] != node->name[0])
			return(0);
		    if (!xmlStrEqual(step->value, node->name))
			return(0);
		}
		/* Namespace test */
		if (node->ns == NULL) {
		    if (step->value2 != NULL)
			return(0);
		} else if (step->value2 != NULL) {
		    if (!xmlStrEqual(step->value2, node->ns->href))
			return(0);
		}
		continue;
            case XSLT_OP_PARENT:
		if ((node->type == XML_DOCUMENT_NODE) ||
		    (node->type == XML_HTML_DOCUMENT_NODE) ||
#ifdef LIBXML_DOCB_ENABLED
		    (node->type == XML_DOCB_DOCUMENT_NODE) ||
#endif
		    (node->type == XML_NAMESPACE_DECL))
		    return(0);
		node = node->parent;
		if (node == NULL)
		    return(0);
		if (step->value == NULL)
		    continue;
		if (step->value[0] != node->name[0])
		    return(0);
		if (!xmlStrEqual(step->value, node->name))
		    return(0);
		/* Namespace test */
		if (node->ns == NULL) {
		    if (step->value2 != NULL)
			return(0);
		} else if (node->ns->href != NULL) {
		    if (step->value2 == NULL)
			return(0);
		    if (!xmlStrEqual(step->value2, node->ns->href))
			return(0);
		}
		continue;
            case XSLT_OP_ANCESTOR:
		/* TODO: implement coalescing of ANCESTOR/NODE ops */
		if (step->value == NULL) {
		    i++;
		    step = &comp->steps[i];
		    if (step->op == XSLT_OP_ROOT)
			return(1);
		    if (step->op != XSLT_OP_ELEM)
			return(0);
		    if (step->value == NULL)
			return(-1);
		}
		if (node == NULL)
		    return(0);
		if ((node->type == XML_DOCUMENT_NODE) ||
		    (node->type == XML_HTML_DOCUMENT_NODE) ||
#ifdef LIBXML_DOCB_ENABLED
		    (node->type == XML_DOCB_DOCUMENT_NODE) ||
#endif
		    (node->type == XML_NAMESPACE_DECL))
		    return(0);
		node = node->parent;
		while (node != NULL) {
		    if (node == NULL)
			return(0);
		    if ((node->type == XML_ELEMENT_NODE) &&
			(step->value[0] == node->name[0]) &&
			(xmlStrEqual(step->value, node->name))) {
			/* Namespace test */
			if (node->ns == NULL) {
			    if (step->value2 == NULL)
				break;
			} else if (node->ns->href != NULL) {
			    if ((step->value2 != NULL) &&
			        (xmlStrEqual(step->value2, node->ns->href)))
				break;
			}
		    }
		    node = node->parent;
		}
		if (node == NULL)
		    return(0);
		continue;
            case XSLT_OP_ID: {
		/* TODO Handle IDs decently, must be done differently */
		xmlAttrPtr id;

		if (node->type != XML_ELEMENT_NODE)
		    return(0);

		id = xmlGetID(node->doc, step->value);
		if ((id == NULL) || (id->parent != node))
		    return(0);
		break;
	    }
            case XSLT_OP_KEY: {
		xmlNodeSetPtr list;
		int indx;

		list = xsltGetKey(ctxt, step->value,
			          step->value3, step->value2);
		if (list == NULL)
		    return(0);
		for (indx = 0;indx < list->nodeNr;indx++)
		    if (list->nodeTab[indx] == node)
			break;
		if (indx >= list->nodeNr)
		    return(0);
		break;
	    }
            case XSLT_OP_NS:
		if (node->type != XML_ELEMENT_NODE)
		    return(0);
		if (node->ns == NULL) {
		    if (step->value != NULL)
			return(0);
		} else if (node->ns->href != NULL) {
		    if (step->value == NULL)
			return(0);
		    if (!xmlStrEqual(step->value, node->ns->href))
			return(0);
		}
		break;
            case XSLT_OP_ALL:
		if (node->type != XML_ELEMENT_NODE)
		    return(0);
		break;
	    case XSLT_OP_PREDICATE: {
		xmlNodePtr oldNode;
		int oldCS, oldCP;
		int pos = 0, len = 0;
		/*
		 * The simple existing predicate code cannot handle
		 * properly cascaded predicates. If in this situation
		 * compute directly the full node list once and check
		 * if the node is in the result list.
		 */
		if (comp->steps[i + 1].op == XSLT_OP_PREDICATE) {
		    xmlDocPtr prevdoc, doc;
		    xmlXPathObjectPtr list;
		    int index, j;
		    int nocache = 0;

		    prevdoc = (xmlDocPtr)
			XSLT_RUNTIME_EXTRA(ctxt, select->previousExtra);
		    index = (int)
			XSLT_RUNTIME_EXTRA(ctxt, select->indexExtra);
		    list = (xmlXPathObjectPtr)
			XSLT_RUNTIME_EXTRA_LST(ctxt, select->lenExtra);
		    
		    doc = node->doc;
		    if ((list == NULL) || (prevdoc != doc)) {
			xmlChar *query;
			xmlXPathObjectPtr newlist;
			xmlNodePtr parent = node->parent;

			if (comp->pattern[0] == '/')
			    query = xmlStrdup(comp->pattern);
			else {
			    query = xmlStrdup((const xmlChar *)"//");
			    query = xmlStrcat(query, comp->pattern);
			}
			newlist = xmlXPathEval(query, ctxt->xpathCtxt);
			xmlFree(query);
			if (newlist == NULL)
			    return(-1);
			if (newlist->type != XPATH_NODESET) {
			    xmlXPathFreeObject(newlist);
			    return(-1);
			}
			
			if ((parent == NULL) || (node->doc == NULL))
			    nocache = 1;
			else {
			    while (parent->parent != NULL)
				parent = parent->parent;
			    if (((parent->type != XML_DOCUMENT_NODE) &&
				 (parent->type != XML_HTML_DOCUMENT_NODE)) ||
				 (parent != (xmlNodePtr) node->doc))
				nocache = 1;
			}
			if (nocache == 0) {
			    if (list != NULL)
				xmlXPathFreeObject(list);
			    list = newlist;

			    XSLT_RUNTIME_EXTRA_LST(ctxt, select->lenExtra) =
				(void *) list;
			    XSLT_RUNTIME_EXTRA(ctxt, select->previousExtra) =
				(void *) doc;
			    XSLT_RUNTIME_EXTRA_FREE(ctxt, select->lenExtra) =
				(xmlFreeFunc) xmlXPathFreeObject;
			} else
			    list = newlist;
		    }
		    if ((list->nodesetval == NULL) ||
			(list->nodesetval->nodeNr <= 0)) {
			if (nocache == 1)
			    xmlXPathFreeObject(list);
			return(0);
		    }
		    /* TODO: store the index and use it for the scan */
		    if (index == 0) {
			for (j = 0;j < list->nodesetval->nodeNr;j++) {
			    if (list->nodesetval->nodeTab[j] == node) {
				if (nocache == 1)
				    xmlXPathFreeObject(list);
				return(1);
			    }
			}
		    } else {
		    }
		    if (nocache == 1)
			xmlXPathFreeObject(list);
		    return(0);
		}
		/*
		 * Depending on the last selection, one may need to
		 * recompute contextSize and proximityPosition.
		 *
		 * TODO: make this thread safe !
		 */
		oldCS = ctxt->xpathCtxt->contextSize;
		oldCP = ctxt->xpathCtxt->proximityPosition;
		if ((select != NULL) &&
		    (select->op == XSLT_OP_ELEM) &&
		    (select->value != NULL) &&
		    (node->type == XML_ELEMENT_NODE) &&
		    (node->parent != NULL)) {
		    xmlNodePtr previous;
		    int index, nocache = 0;

		    previous = (xmlNodePtr)
			XSLT_RUNTIME_EXTRA(ctxt, select->previousExtra);
		    index = (int)
			XSLT_RUNTIME_EXTRA(ctxt, select->indexExtra);
		    if ((previous != NULL) &&
			(previous->parent == node->parent)) {
			/*
			 * just walk back to adjust the index
			 */
			int indx = 0;
			xmlNodePtr sibling = node;

			while (sibling != NULL) {
			    if (sibling == previous)
				break;
			    if ((node->type == XML_ELEMENT_NODE) &&
				(node->name != NULL) &&
				(sibling->name != NULL) &&
				(node->name[0] == sibling->name[0]) &&
				(xmlStrEqual(node->name, sibling->name))) {
				if ((select->value2 == NULL) ||
				    ((sibling->ns != NULL) &&
				     (xmlStrEqual(select->value2,
						  sibling->ns->href))))
				    indx++;
			    }
			    sibling = sibling->prev;
			}
			if (sibling == NULL) {
			    /* hum going backward in document order ... */
			    indx = 0;
			    sibling = node;
			    while (sibling != NULL) {
				if (sibling == previous)
				    break;
				if ((select->value2 == NULL) ||
				    ((sibling->ns != NULL) &&
				     (xmlStrEqual(select->value2,
						  sibling->ns->href))))
				    indx--;
				sibling = sibling->next;
			    }
			}
			if (sibling != NULL) {
			    pos = index + indx;
			    /*
			     * If the node is in a Value Tree we cannot
			     * cache it !
			     */
			    if (node->doc != NULL) {
				len = (int)
				    XSLT_RUNTIME_EXTRA(ctxt, select->lenExtra);
				XSLT_RUNTIME_EXTRA(ctxt,
					select->previousExtra) = node;
				XSLT_RUNTIME_EXTRA(ctxt, select->indexExtra) =
				    (void *) pos;
			    }
			    index = pos;
			} else
			    pos = 0;
		    } else {
			/*
			 * recompute the index
			 */
			xmlNodePtr siblings = node->parent->children;
			xmlNodePtr parent = node->parent;

			while (siblings != NULL) {
			    if (siblings->type == XML_ELEMENT_NODE) {
				if (siblings == node) {
				    len++;
				    pos = len;
				} else if ((node->name != NULL) &&
					   (siblings->name != NULL) &&
				    (node->name[0] == siblings->name[0]) &&
				    (xmlStrEqual(node->name, siblings->name))) {
				    if ((select->value2 == NULL) ||
					((siblings->ns != NULL) &&
					 (xmlStrEqual(select->value2,
						      siblings->ns->href))))
					len++;
				}
			    }
			    siblings = siblings->next;
			}
			if ((parent == NULL) || (node->doc == NULL))
			    nocache = 1;
			else {
			    while (parent->parent != NULL)
				parent = parent->parent;
			    if (((parent->type != XML_DOCUMENT_NODE) &&
				 (parent->type != XML_HTML_DOCUMENT_NODE)) ||
				 (parent != (xmlNodePtr) node->doc))
				nocache = 1;
			}
		    }
		    if (pos != 0) {
			ctxt->xpathCtxt->contextSize = len;
			ctxt->xpathCtxt->proximityPosition = pos;
			/*
			 * If the node is in a Value Tree we cannot
			 * cache it !
			 */
			if ((node->doc != NULL) && (nocache == 0)) {
			    XSLT_RUNTIME_EXTRA(ctxt, select->previousExtra) =
				node;
			    XSLT_RUNTIME_EXTRA(ctxt, select->indexExtra) =
				(void *) pos;
			    XSLT_RUNTIME_EXTRA(ctxt, select->lenExtra) =
				(void *) len;
			}
		    }
		} else if ((select != NULL) && (select->op == XSLT_OP_ALL) &&
			   (node->type == XML_ELEMENT_NODE)) {
		    xmlNodePtr previous;
		    int index, nocache = 0;

		    previous = (xmlNodePtr)
			XSLT_RUNTIME_EXTRA(ctxt, select->previousExtra);
		    index = (int)
			XSLT_RUNTIME_EXTRA(ctxt, select->indexExtra);
		    if ((previous != NULL) &&
			(previous->parent == node->parent)) {
			/*
			 * just walk back to adjust the index
			 */
			int indx = 0;
			xmlNodePtr sibling = node;

			while (sibling != NULL) {
			    if (sibling == previous)
				break;
			    if (sibling->type == XML_ELEMENT_NODE)
				indx++;
			    sibling = sibling->prev;
			}
			if (sibling == NULL) {
			    /* hum going backward in document order ... */
			    indx = 0;
			    sibling = node;
			    while (sibling != NULL) {
				if (sibling == previous)
				    break;
				if (sibling->type == XML_ELEMENT_NODE)
				    indx--;
				sibling = sibling->next;
			    }
			}
			if (sibling != NULL) {
			    pos = index + indx;
			    /*
			     * If the node is in a Value Tree we cannot
			     * cache it !
			     */
			    if (node->doc != NULL) {
				len = (int)
				    XSLT_RUNTIME_EXTRA(ctxt, select->lenExtra);
				XSLT_RUNTIME_EXTRA(ctxt,
					select->previousExtra) = node;
				XSLT_RUNTIME_EXTRA(ctxt, select->indexExtra) =
				    (void *) pos;
			    }
			} else
			    pos = 0;
		    } else {
			/*
			 * recompute the index
			 */
			xmlNodePtr siblings = node->parent->children;
			xmlNodePtr parent = node->parent;

			while (siblings != NULL) {
			    if (siblings->type == XML_ELEMENT_NODE) {
				len++;
				if (siblings == node) {
				    pos = len;
				}
			    }
			    siblings = siblings->next;
			}
			if ((parent == NULL) || (node->doc == NULL))
			    nocache = 1;
			else {
			    while (parent->parent != NULL)
				parent = parent->parent;
			    if (((parent->type != XML_DOCUMENT_NODE) &&
				 (parent->type != XML_HTML_DOCUMENT_NODE)) ||
				 (parent != (xmlNodePtr) node->doc))
				nocache = 1;
			}
		    }
		    if (pos != 0) {
			ctxt->xpathCtxt->contextSize = len;
			ctxt->xpathCtxt->proximityPosition = pos;
			/*
			 * If the node is in a Value Tree we cannot
			 * cache it !
			 */
			if ((node->doc != NULL) && (nocache == 0)) {
			    XSLT_RUNTIME_EXTRA(ctxt, select->previousExtra) =
				node;
			    XSLT_RUNTIME_EXTRA(ctxt, select->indexExtra) =
				(void *) pos;
			    XSLT_RUNTIME_EXTRA(ctxt, select->lenExtra) =
				(void *) len;
			}
		    }
		}
		oldNode = ctxt->node;
		ctxt->node = node;

		if (step->value == NULL)
		    goto wrong_index;
		if (step->comp == NULL)
		    goto wrong_index;

		if (!xsltEvalXPathPredicate(ctxt, step->comp, comp->nsList,
			                    comp->nsNr))
		    goto wrong_index;

		if (pos != 0) {
		    ctxt->xpathCtxt->contextSize = oldCS;
		    ctxt->xpathCtxt->proximityPosition = oldCP;
		}
		ctxt->node = oldNode;
		break;
wrong_index:
		if (pos != 0) {
		    ctxt->xpathCtxt->contextSize = oldCS;
		    ctxt->xpathCtxt->proximityPosition = oldCP;
		}
		ctxt->node = oldNode;
		return(0);
	    }
            case XSLT_OP_PI:
		if (node->type != XML_PI_NODE)
		    return(0);
		if (step->value != NULL) {
		    if (!xmlStrEqual(step->value, node->name))
			return(0);
		}
		break;
            case XSLT_OP_COMMENT:
		if (node->type != XML_COMMENT_NODE)
		    return(0);
		break;
            case XSLT_OP_TEXT:
		if ((node->type != XML_TEXT_NODE) &&
		    (node->type != XML_CDATA_SECTION_NODE))
		    return(0);
		break;
            case XSLT_OP_NODE:
		switch (node->type) {
		    case XML_ELEMENT_NODE:
		    case XML_CDATA_SECTION_NODE:
		    case XML_PI_NODE:
		    case XML_COMMENT_NODE:
		    case XML_TEXT_NODE:
			break;
		    default:
			return(0);
		}
		break;
	}
    }
    return(1);
}

/**
 * xsltTestCompMatchList:
 * @ctxt:  a XSLT process context
 * @node: a node
 * @comp: the precompiled pattern list
 *
 * Test wether the node matches one of the patterns in the list
 *
 * Returns 1 if it matches, 0 if it doesn't and -1 in case of failure
 */
int
xsltTestCompMatchList(xsltTransformContextPtr ctxt, xmlNodePtr node,
	              xsltCompMatchPtr comp) {
    int ret;

    if ((ctxt == NULL) || (node == NULL))
	return(-1);
    while (comp != NULL) {
	ret = xsltTestCompMatch(ctxt, comp, node, NULL, NULL);
	if (ret == 1)
	    return(1);
	comp = comp->next;
    }
    return(0);
}

/************************************************************************
 *									*
 *			Dedicated parser for templates			*
 *									*
 ************************************************************************/

#define CUR (*ctxt->cur)
#define SKIP(val) ctxt->cur += (val)
#define NXT(val) ctxt->cur[(val)]
#define CUR_PTR ctxt->cur

#define SKIP_BLANKS 							\
    while (IS_BLANK(CUR)) NEXT

#define CURRENT (*ctxt->cur)
#define NEXT ((*ctxt->cur) ?  ctxt->cur++: ctxt->cur)


#define PUSH(op, val, val2) 						\
    if (xsltCompMatchAdd(ctxt, ctxt->comp, (op), (val), (val2))) goto error;

#define SWAP() 						\
    xsltSwapTopCompMatch(ctxt->comp);

#define XSLT_ERROR(X)							\
    { xsltError(ctxt, __FILE__, __LINE__, X);			\
      ctxt->error = (X); return; }

#define XSLT_ERROR0(X)							\
    { xsltError(ctxt, __FILE__, __LINE__, X);			\
      ctxt->error = (X); return(0); }

/**
 * xsltScanLiteral:
 * @ctxt:  the XPath Parser context
 *
 * Parse an XPath Litteral:
 *
 * [29] Literal ::= '"' [^"]* '"'
 *                | "'" [^']* "'"
 *
 * Returns the Literal parsed or NULL
 */

static xmlChar *
xsltScanLiteral(xsltParserContextPtr ctxt) {
    const xmlChar *q, *cur;
    xmlChar *ret = NULL;
    int val, len;

    SKIP_BLANKS;
    if (CUR == '"') {
        NEXT;
	cur = q = CUR_PTR;
	val = xmlStringCurrentChar(NULL, cur, &len);
	while ((IS_CHAR(val)) && (val != '"')) {
	    cur += len;
	    val = xmlStringCurrentChar(NULL, cur, &len);
	}
	if (!IS_CHAR(val)) {
	    ctxt->error = 1;
	    return(NULL);
	} else {
	    ret = xmlStrndup(q, cur - q);
        }
	cur += len;
	CUR_PTR = cur;
    } else if (CUR == '\'') {
        NEXT;
	cur = q = CUR_PTR;
	val = xmlStringCurrentChar(NULL, cur, &len);
	while ((IS_CHAR(val)) && (val != '\'')) {
	    cur += len;
	    val = xmlStringCurrentChar(NULL, cur, &len);
	}
	if (!IS_CHAR(val)) {
	    ctxt->error = 1;
	    return(NULL);
	} else {
	    ret = xmlStrndup(q, cur - q);
        }
	cur += len;
	CUR_PTR = cur;
    } else {
	/* XP_ERROR(XPATH_START_LITERAL_ERROR); */
	ctxt->error = 1;
	return(NULL);
    }
    return(ret);
}

/**
 * xsltScanName:
 * @ctxt:  the XPath Parser context
 *
 * [4] NameChar ::= Letter | Digit | '.' | '-' | '_' | 
 *                  CombiningChar | Extender
 *
 * [5] Name ::= (Letter | '_' | ':') (NameChar)*
 *
 * [6] Names ::= Name (S Name)*
 *
 * Returns the Name parsed or NULL
 */

static xmlChar *
xsltScanName(xsltParserContextPtr ctxt) {
    const xmlChar *q, *cur;
    xmlChar *ret = NULL;
    int val, len;

    SKIP_BLANKS;

    cur = q = CUR_PTR;
    val = xmlStringCurrentChar(NULL, cur, &len);
    if (!IS_LETTER(val) && (val != '_') && (val != ':'))
	return(NULL);

    while ((IS_LETTER(val)) || (IS_DIGIT(val)) ||
           (val == '.') || (val == '-') ||
	   (val == '_') || 
	   (IS_COMBINING(val)) ||
	   (IS_EXTENDER(val))) {
	cur += len;
	val = xmlStringCurrentChar(NULL, cur, &len);
    }
    ret = xmlStrndup(q, cur - q);
    CUR_PTR = cur;
    return(ret);
}

/**
 * xsltScanNCName:
 * @ctxt:  the XPath Parser context
 *
 * Parses a non qualified name
 *
 * Returns the Name parsed or NULL
 */

static xmlChar *
xsltScanNCName(xsltParserContextPtr ctxt) {
    const xmlChar *q, *cur;
    xmlChar *ret = NULL;
    int val, len;

    SKIP_BLANKS;

    cur = q = CUR_PTR;
    val = xmlStringCurrentChar(NULL, cur, &len);
    if (!IS_LETTER(val) && (val != '_'))
	return(NULL);

    while ((IS_LETTER(val)) || (IS_DIGIT(val)) ||
           (val == '.') || (val == '-') ||
	   (val == '_') ||
	   (IS_COMBINING(val)) ||
	   (IS_EXTENDER(val))) {
	cur += len;
	val = xmlStringCurrentChar(NULL, cur, &len);
    }
    ret = xmlStrndup(q, cur - q);
    CUR_PTR = cur;
    return(ret);
}

/**
 * xsltScanQName:
 * @ctxt:  the XPath Parser context
 * @prefix:  the place to store the prefix
 *
 * Parse a qualified name
 *
 * Returns the Name parsed or NULL
 */

static xmlChar *
xsltScanQName(xsltParserContextPtr ctxt, xmlChar **prefix) {
    xmlChar *ret = NULL;

    *prefix = NULL;
    ret = xsltScanNCName(ctxt);
    if (CUR == ':') {
        *prefix = ret;
	NEXT;
	ret = xsltScanNCName(ctxt);
    }
    return(ret);
}

/*
 * xsltCompileIdKeyPattern:
 * @ctxt:  the compilation context
 * @name:  a preparsed name
 * @aid:  whether id/key are allowed there
 *
 * Compile the XSLT LocationIdKeyPattern
 * [3] IdKeyPattern ::= 'id' '(' Literal ')'
 *                    | 'key' '(' Literal ',' Literal ')'
 *
 * also handle NodeType and PI from:
 *
 * [7]  NodeTest ::= NameTest
 *                 | NodeType '(' ')'
 *                 | 'processing-instruction' '(' Literal ')'
 */
static void
xsltCompileIdKeyPattern(xsltParserContextPtr ctxt, xmlChar *name, int aid) {
    xmlChar *lit = NULL;
    xmlChar *lit2 = NULL;

    if (CUR != '(') {
	xsltTransformError(NULL, NULL, NULL,
		"xsltCompileIdKeyPattern : ( expected\n");
	ctxt->error = 1;
	return;
    }
    if ((aid) && (xmlStrEqual(name, (const xmlChar *)"id"))) {
	NEXT;
	SKIP_BLANKS;
        lit = xsltScanLiteral(ctxt);
	if (ctxt->error)
	    return;
	SKIP_BLANKS;
	if (CUR != ')') {
	    xsltTransformError(NULL, NULL, NULL,
		    "xsltCompileIdKeyPattern : ) expected\n");
	    ctxt->error = 1;
	    return;
	}
	NEXT;
	PUSH(XSLT_OP_ID, lit, NULL);
    } else if ((aid) && (xmlStrEqual(name, (const xmlChar *)"key"))) {
	NEXT;
	SKIP_BLANKS;
        lit = xsltScanLiteral(ctxt);
	if (ctxt->error)
	    return;
	SKIP_BLANKS;
	if (CUR != ',') {
	    xsltTransformError(NULL, NULL, NULL,
		    "xsltCompileIdKeyPattern : , expected\n");
	    ctxt->error = 1;
	    return;
	}
	NEXT;
	SKIP_BLANKS;
        lit2 = xsltScanLiteral(ctxt);
	if (ctxt->error)
	    return;
	SKIP_BLANKS;
	if (CUR != ')') {
	    xsltTransformError(NULL, NULL, NULL,
		    "xsltCompileIdKeyPattern : ) expected\n");
	    ctxt->error = 1;
	    return;
	}
	NEXT;
	/* TODO: support namespace in keys */
	PUSH(XSLT_OP_KEY, lit, lit2);
    } else if (xmlStrEqual(name, (const xmlChar *)"processing-instruction")) {
	NEXT;
	SKIP_BLANKS;
	if (CUR != ')') {
	    lit = xsltScanLiteral(ctxt);
	    if (ctxt->error)
		return;
	    SKIP_BLANKS;
	    if (CUR != ')') {
		xsltTransformError(NULL, NULL, NULL,
			"xsltCompileIdKeyPattern : ) expected\n");
		ctxt->error = 1;
		return;
	    }
	}
	NEXT;
	PUSH(XSLT_OP_PI, lit, NULL);
    } else if (xmlStrEqual(name, (const xmlChar *)"text")) {
	NEXT;
	SKIP_BLANKS;
	if (CUR != ')') {
	    xsltTransformError(NULL, NULL, NULL,
		    "xsltCompileIdKeyPattern : ) expected\n");
	    ctxt->error = 1;
	    return;
	}
	NEXT;
	PUSH(XSLT_OP_TEXT, NULL, NULL);
    } else if (xmlStrEqual(name, (const xmlChar *)"comment")) {
	NEXT;
	SKIP_BLANKS;
	if (CUR != ')') {
	    xsltTransformError(NULL, NULL, NULL,
		    "xsltCompileIdKeyPattern : ) expected\n");
	    ctxt->error = 1;
	    return;
	}
	NEXT;
	PUSH(XSLT_OP_COMMENT, NULL, NULL);
    } else if (xmlStrEqual(name, (const xmlChar *)"node")) {
	NEXT;
	SKIP_BLANKS;
	if (CUR != ')') {
	    xsltTransformError(NULL, NULL, NULL,
		    "xsltCompileIdKeyPattern : ) expected\n");
	    ctxt->error = 1;
	    return;
	}
	NEXT;
	PUSH(XSLT_OP_NODE, NULL, NULL);
    } else if (aid) {
	xsltTransformError(NULL, NULL, NULL,
	    "xsltCompileIdKeyPattern : expecting 'key' or 'id' or node type\n");
	ctxt->error = 1;
	return;
    } else {
	xsltTransformError(NULL, NULL, NULL,
	    "xsltCompileIdKeyPattern : node type\n");
	ctxt->error = 1;
	return;
    }
error:
    if (name != NULL)
	xmlFree(name);
}

/**
 * xsltCompileStepPattern:
 * @ctxt:  the compilation context
 * @token:  a posible precompiled name
 *
 * Compile the XSLT StepPattern and generates a precompiled
 * form suitable for fast matching.
 *
 * [5] StepPattern ::= ChildOrAttributeAxisSpecifier NodeTest Predicate* 
 * [6] ChildOrAttributeAxisSpecifier ::= AbbreviatedAxisSpecifier
 *                                     | ('child' | 'attribute') '::'
 * from XPath
 * [7]  NodeTest ::= NameTest
 *                 | NodeType '(' ')'
 *                 | 'processing-instruction' '(' Literal ')'
 * [8] Predicate ::= '[' PredicateExpr ']'
 * [9] PredicateExpr ::= Expr
 * [13] AbbreviatedAxisSpecifier ::= '@'?
 * [37] NameTest ::= '*' | NCName ':' '*' | QName
 */

static void
xsltCompileStepPattern(xsltParserContextPtr ctxt, xmlChar *token) {
    xmlChar *name = NULL;
    const xmlChar *URI = NULL;
    xmlChar *URL = NULL;
    int level;

    SKIP_BLANKS;
    if ((token == NULL) && (CUR == '@')) {
	xmlChar *prefix = NULL;

	NEXT;
	if (CUR == '*') {
	    NEXT;
	    PUSH(XSLT_OP_ATTR, NULL, NULL);
	    goto parse_predicate;
	}
	token = xsltScanQName(ctxt, &prefix);
	if (prefix != NULL) {
	    xmlNsPtr ns;

	    ns = xmlSearchNs(ctxt->doc, ctxt->elem, prefix);
	    if (ns == NULL) {
		xsltTransformError(NULL, NULL, NULL,
		"xsltCompileStepPattern : no namespace bound to prefix %s\n",
				 prefix);
	    } else {
		URL = xmlStrdup(ns->href);
	    }
	    xmlFree(prefix);
	}
	if (token == NULL) {
	    if (CUR == '*') {
		NEXT;
		PUSH(XSLT_OP_ATTR, NULL, URL);
		return;
	    }
	    xsltTransformError(NULL, NULL, NULL,
		    "xsltCompileStepPattern : Name expected\n");
	    ctxt->error = 1;
	    goto error;
	}
	PUSH(XSLT_OP_ATTR, token, URL);
	goto parse_predicate;
    }
    if (token == NULL)
	token = xsltScanName(ctxt);
    if (token == NULL) {
	if (CUR == '*') {
	    NEXT;
	    PUSH(XSLT_OP_ALL, token, NULL);
	    goto parse_predicate;
	} else {
	    xsltTransformError(NULL, NULL, NULL,
		    "xsltCompileStepPattern : Name expected\n");
	    ctxt->error = 1;
	    goto error;
	}
    }


    SKIP_BLANKS;
    if (CUR == '(') {
	xsltCompileIdKeyPattern(ctxt, token, 0);
	if (ctxt->error)
	    goto error;
    } else if (CUR == ':') {
	NEXT;
	if (CUR != ':') {
	    xmlChar *prefix = token;
	    xmlNsPtr ns;

	    /*
	     * This is a namespace match
	     */
	    token = xsltScanName(ctxt);
	    ns = xmlSearchNs(ctxt->doc, ctxt->elem, prefix);
	    if (ns == NULL) {
		xsltTransformError(NULL, NULL, NULL,
	    "xsltCompileStepPattern : no namespace bound to prefix %s\n",
				 prefix);
		ctxt->error = 1;
		goto error;
	    } else {
		URL = xmlStrdup(ns->href);
	    }
	    xmlFree(prefix);
	    if (token == NULL) {
		if (CUR == '*') {
		    NEXT;
		    PUSH(XSLT_OP_NS, URL, NULL);
		} else {
		    xsltTransformError(NULL, NULL, NULL,
			    "xsltCompileStepPattern : Name expected\n");
		    ctxt->error = 1;
		    goto error;
		}
	    } else {
		PUSH(XSLT_OP_ELEM, token, URL);
	    }
	} else {
	    NEXT;
	    if (xmlStrEqual(token, (const xmlChar *) "child")) {
		xmlFree(token);
		token = xsltScanName(ctxt);
		if (token == NULL) {
	            if (CUR == '*') {
            	        NEXT;
	                PUSH(XSLT_OP_ALL, token, NULL);
	                goto parse_predicate;
	            } else {
		        xsltTransformError(NULL, NULL, NULL,
			    "xsltCompileStepPattern : QName expected\n");
		        ctxt->error = 1;
		        goto error;
		    }
		}
		URI = xsltGetQNameURI(ctxt->elem, &token);
		if (token == NULL) {
		    ctxt->error = 1;
		    goto error;
		} else {
		    name = xmlStrdup(token);
		    if (URI != NULL)
			URL = xmlStrdup(URI);
		}
		PUSH(XSLT_OP_CHILD, name, URL);
	    } else if (xmlStrEqual(token, (const xmlChar *) "attribute")) {
		xmlFree(token);
		token = xsltScanName(ctxt);
		if (token == NULL) {
		    xsltTransformError(NULL, NULL, NULL,
			    "xsltCompileStepPattern : QName expected\n");
		    ctxt->error = 1;
		    goto error;
		}
		URI = xsltGetQNameURI(ctxt->elem, &token);
		if (token == NULL) {
		    ctxt->error = 1;
		    goto error;
		} else {
		    name = xmlStrdup(token);
		    if (URI != NULL)
			URL = xmlStrdup(URI);
		}
		PUSH(XSLT_OP_ATTR, name, URL);
	    } else {
		xsltTransformError(NULL, NULL, NULL,
		    "xsltCompileStepPattern : 'child' or 'attribute' expected\n");
		ctxt->error = 1;
		goto error;
	    }
	    xmlFree(token);
	}
    } else if (CUR == '*') {
	NEXT;
	PUSH(XSLT_OP_ALL, token, NULL);
    } else {
	URI = xsltGetQNameURI(ctxt->elem, &token);
	if (token == NULL) {
	    ctxt->error = 1;
	    goto error;
	}
	if (URI != NULL)
	    URL = xmlStrdup(URI);
	PUSH(XSLT_OP_ELEM, token, URL);
    }
parse_predicate:
    SKIP_BLANKS;
    level = 0;
    while (CUR == '[') {
	const xmlChar *q;
	xmlChar *ret = NULL;

	level++;
	NEXT;
	q = CUR_PTR;
	while (CUR != 0) {
	    /* Skip over nested predicates */
	    if (CUR == '[')
		level++;
	    else if (CUR == ']') {
		level--;
		if (level == 0)
		    break;
	    } else if (CUR == '"') {
		NEXT;
		while ((CUR != 0) && (CUR != '"'))
		    NEXT;
	    } else if (CUR == '\'') {
		NEXT;
		while ((CUR != 0) && (CUR != '\''))
		    NEXT;
	    }
	    NEXT;
	}
	if (CUR == 0) {
	    xsltTransformError(NULL, NULL, NULL,
		    "xsltCompileStepPattern : ']' expected\n");
	    ctxt->error = 1;
	    goto error;
        }
	ret = xmlStrndup(q, CUR_PTR - q);
	PUSH(XSLT_OP_PREDICATE, ret, NULL);
	/* push the predicate lower than local test */
	SWAP();
	NEXT;
	SKIP_BLANKS;
    }
    return;
error:
    if (token != NULL)
	xmlFree(token);
    if (name != NULL)
	xmlFree(name);
}

/**
 * xsltCompileRelativePathPattern:
 * @comp:  the compilation context
 * @token:  a posible precompiled name
 *
 * Compile the XSLT RelativePathPattern and generates a precompiled
 * form suitable for fast matching.
 *
 * [4] RelativePathPattern ::= StepPattern
 *                           | RelativePathPattern '/' StepPattern
 *                           | RelativePathPattern '//' StepPattern
 */
static void
xsltCompileRelativePathPattern(xsltParserContextPtr ctxt, xmlChar *token) {
    xsltCompileStepPattern(ctxt, token);
    if (ctxt->error)
	goto error;
    SKIP_BLANKS;
    while ((CUR != 0) && (CUR != '|')) {
	if ((CUR == '/') && (NXT(1) == '/')) {
	    PUSH(XSLT_OP_ANCESTOR, NULL, NULL);
	    NEXT;
	    NEXT;
	    SKIP_BLANKS;
	    xsltCompileStepPattern(ctxt, NULL);
	} else if (CUR == '/') {
	    PUSH(XSLT_OP_PARENT, NULL, NULL);
	    NEXT;
	    SKIP_BLANKS;
	    if ((CUR != 0) || (CUR == '|')) {
		xsltCompileRelativePathPattern(ctxt, NULL);
	    }
	} else {
	    ctxt->error = 1;
	}
	if (ctxt->error)
	    goto error;
	SKIP_BLANKS;
    }
error:
    return;
}

/**
 * xsltCompileLocationPathPattern:
 * @ctxt:  the compilation context
 *
 * Compile the XSLT LocationPathPattern and generates a precompiled
 * form suitable for fast matching.
 *
 * [2] LocationPathPattern ::= '/' RelativePathPattern?
 *                           | IdKeyPattern (('/' | '//') RelativePathPattern)?
 *                           | '//'? RelativePathPattern
 */
static void
xsltCompileLocationPathPattern(xsltParserContextPtr ctxt) {
    SKIP_BLANKS;
    if ((CUR == '/') && (NXT(1) == '/')) {
	/*
	 * since we reverse the query
	 * a leading // can be safely ignored
	 */
	NEXT;
	NEXT;
	xsltCompileRelativePathPattern(ctxt, NULL);
    } else if (CUR == '/') {
	/*
	 * We need to find root as the parent
	 */
	NEXT;
	SKIP_BLANKS;
	PUSH(XSLT_OP_ROOT, NULL, NULL);
	if ((CUR != 0) || (CUR == '|')) {
	    PUSH(XSLT_OP_PARENT, NULL, NULL);
	    xsltCompileRelativePathPattern(ctxt, NULL);
	}
    } else if (CUR == '*') {
	xsltCompileRelativePathPattern(ctxt, NULL);
    } else if (CUR == '@') {
	xsltCompileRelativePathPattern(ctxt, NULL);
    } else {
	xmlChar *name;
	name = xsltScanName(ctxt);
	if (name == NULL) {
	    xsltTransformError(NULL, NULL, NULL,
		    "xsltCompileLocationPathPattern : Name expected\n");
	    ctxt->error = 1;
	    return;
	}
	SKIP_BLANKS;
	if ((CUR == '(') && !xmlXPathIsNodeType(name)) {
	    xsltCompileIdKeyPattern(ctxt, name, 1);
	    if ((CUR == '/') && (NXT(1) == '/')) {
		PUSH(XSLT_OP_ANCESTOR, NULL, NULL);
		NEXT;
		NEXT;
		SKIP_BLANKS;
		xsltCompileRelativePathPattern(ctxt, NULL);
	    } else if (CUR == '/') {
		PUSH(XSLT_OP_PARENT, NULL, NULL);
		NEXT;
		SKIP_BLANKS;
		xsltCompileRelativePathPattern(ctxt, NULL);
	    }
	    return;
	}
	xsltCompileRelativePathPattern(ctxt, name);
    }
error:
    return;
}

/**
 * xsltCompilePattern:
 * @pattern: an XSLT pattern
 * @doc:  the containing document
 * @node:  the containing element
 * @style:  the stylesheet
 * @runtime:  the transformation context, if done at run-time
 *
 * Compile the XSLT pattern and generates a list of precompiled form suitable
 * for fast matching.
 *
 * [1] Pattern ::= LocationPathPattern | Pattern '|' LocationPathPattern
 *
 * Returns the generated pattern list or NULL in case of failure
 */

xsltCompMatchPtr
xsltCompilePattern(const xmlChar *pattern, xmlDocPtr doc,
	           xmlNodePtr node, xsltStylesheetPtr style,
		   xsltTransformContextPtr runtime) {
    xsltParserContextPtr ctxt = NULL;
    xsltCompMatchPtr element, first = NULL, previous = NULL;
    int current, start, end, level, j;

    if (pattern == NULL) {
	xsltTransformError(NULL, NULL, node,
			 "xsltCompilePattern : NULL pattern\n");
	return(NULL);
    }

    ctxt = xsltNewParserContext(style, runtime);
    if (ctxt == NULL)
	return(NULL);
    ctxt->doc = doc;
    ctxt->elem = node;
    current = end = 0;
    while (pattern[current] != 0) {
	start = current;
	while (IS_BLANK(pattern[current]))
	    current++;
	end = current;
	level = 0;
	while ((pattern[end] != 0) && ((pattern[end] != '|') || (level != 0))) {
	    if (pattern[end] == '[')
		level++;
	    else if (pattern[end] == ']')
		level--;
	    else if (pattern[end] == '\'') {
		end++;
		while ((pattern[end] != 0) && (pattern[end] != '\''))
		    end++;
	    } else if (pattern[end] == '"') {
		end++;
		while ((pattern[end] != 0) && (pattern[end] != '"'))
		    end++;
	    }
	    end++;
	}
	if (current == end) {
	    xsltTransformError(NULL, NULL, node,
			     "xsltCompilePattern : NULL pattern\n");
	    goto error;
	}
	element = xsltNewCompMatch();
	if (element == NULL) {
	    goto error;
	}
	if (first == NULL)
	    first = element;
	else if (previous != NULL)
	    previous->next = element;
	previous = element;

	ctxt->comp = element;
	ctxt->base = xmlStrndup(&pattern[start], end - start);
	if (ctxt->base == NULL)
	    goto error;
	ctxt->cur = &(ctxt->base)[current - start];
	element->pattern = ctxt->base;
	element->nsList = xmlGetNsList(doc, node);
	j = 0;
	if (element->nsList != NULL) {
	    while (element->nsList[j] != NULL)
		j++;
	}
	element->nsNr = j;


#ifdef WITH_XSLT_DEBUG_PATTERN
	xsltGenericDebug(xsltGenericDebugContext,
			 "xsltCompilePattern : parsing '%s'\n",
			 element->pattern);
#endif
	xsltCompileLocationPathPattern(ctxt);
	if (ctxt->error) {
	    xsltTransformError(NULL, style, node,
			     "xsltCompilePattern : failed to compile '%s'\n",
			     element->pattern);
	    style->errors++;
	    goto error;
	}

	/*
	 * Reverse for faster interpretation.
	 */
	xsltReverseCompMatch(element);

	/*
	 * Set-up the priority
	 */
	if (((element->steps[0].op == XSLT_OP_ELEM) ||
	     (element->steps[0].op == XSLT_OP_ATTR)) &&
	    (element->steps[0].value != NULL) &&
	    (element->steps[1].op == XSLT_OP_END)) {
	    element->priority = 0;
#if 0
	} else if ((element->steps[0].op == XSLT_OP_ROOT) &&
		   (element->steps[1].op == XSLT_OP_END)) {
	    element->priority = 0;
#endif
	} else if ((element->steps[0].op == XSLT_OP_PI) &&
		   (element->steps[0].value != NULL) &&
		   (element->steps[1].op == XSLT_OP_END)) {
	    element->priority = 0;
	} else if ((element->steps[0].op == XSLT_OP_ATTR) &&
		   (element->steps[0].value2 != NULL) &&
		   (element->steps[1].op == XSLT_OP_END)) {
	    element->priority = -0.25;
	} else if ((element->steps[0].op == XSLT_OP_NS) &&
		   (element->steps[0].value != NULL) &&
		   (element->steps[1].op == XSLT_OP_END)) {
	    element->priority = -0.25;
	} else if ((element->steps[0].op == XSLT_OP_ATTR) &&
		   (element->steps[0].value == NULL) &&
		   (element->steps[0].value2 == NULL) &&
		   (element->steps[1].op == XSLT_OP_END)) {
	    element->priority = -0.5;
	} else if (((element->steps[0].op == XSLT_OP_PI) ||
		    (element->steps[0].op == XSLT_OP_TEXT) ||
		    (element->steps[0].op == XSLT_OP_ALL) ||
		    (element->steps[0].op == XSLT_OP_NODE) ||
		    (element->steps[0].op == XSLT_OP_COMMENT)) &&
		   (element->steps[1].op == XSLT_OP_END)) {
	    element->priority = -0.5;
	} else {
	    element->priority = 0.5;
	}
#ifdef WITH_XSLT_DEBUG_PATTERN
	xsltGenericDebug(xsltGenericDebugContext,
		     "xsltCompilePattern : parsed %s, default priority %f\n",
			 element->pattern, element->priority);
#endif
	if (pattern[end] == '|')
	    end++;
	current = end;
    }
    if (end == 0) {
	xsltTransformError(NULL, style, node,
			 "xsltCompilePattern : NULL pattern\n");
	style->errors++;
	goto error;
    }

    xsltFreeParserContext(ctxt);
    return(first);

error:
    if (ctxt != NULL)
	xsltFreeParserContext(ctxt);
    if (first != NULL)
	xsltFreeCompMatchList(first);
    return(NULL);
}

/************************************************************************
 *									*
 *			Module interfaces				*
 *									*
 ************************************************************************/

/**
 * xsltAddTemplate:
 * @style: an XSLT stylesheet
 * @cur: an XSLT template
 * @mode:  the mode name or NULL
 * @modeURI:  the mode URI or NULL
 *
 * Register the XSLT pattern associated to @cur
 *
 * Returns -1 in case of error, 0 otherwise
 */
int
xsltAddTemplate(xsltStylesheetPtr style, xsltTemplatePtr cur,
	        const xmlChar *mode, const xmlChar *modeURI) {
    xsltCompMatchPtr pat, list, *top = NULL, next;
    const xmlChar *name = NULL;
    float priority;              /* the priority */

    if ((style == NULL) || (cur == NULL) || (cur->match == NULL))
	return(-1);

    priority = cur->priority;
    pat = xsltCompilePattern(cur->match, style->doc, cur->elem, style, NULL);
    while (pat) {
	next = pat->next;
	pat->next = NULL;
	name = NULL;
	
	pat->template = cur;
	if (mode != NULL)
	    pat->mode = xmlStrdup(mode);
	if (modeURI != NULL)
	    pat->modeURI = xmlStrdup(modeURI);
	if (priority != XSLT_PAT_NO_PRIORITY)
	    pat->priority = priority;

	/*
	 * insert it in the hash table list corresponding to its lookup name
	 */
	switch (pat->steps[0].op) {
        case XSLT_OP_ATTR:
	    if (pat->steps[0].value != NULL)
		name = pat->steps[0].value;
	    else
		top = (xsltCompMatchPtr *) &(style->attrMatch);
	    break;
        case XSLT_OP_CHILD:
        case XSLT_OP_PARENT:
        case XSLT_OP_ANCESTOR:
	    top = (xsltCompMatchPtr *) &(style->elemMatch);
	    break;
        case XSLT_OP_ROOT:
	    top = (xsltCompMatchPtr *) &(style->rootMatch);
	    break;
        case XSLT_OP_KEY:
	    top = (xsltCompMatchPtr *) &(style->keyMatch);
	    break;
        case XSLT_OP_ID:
	    /* TODO optimize ID !!! */
        case XSLT_OP_NS:
        case XSLT_OP_ALL:
	    top = (xsltCompMatchPtr *) &(style->elemMatch);
	    break;
        case XSLT_OP_END:
	case XSLT_OP_PREDICATE:
	    xsltTransformError(NULL, style, NULL,
			     "xsltAddTemplate: invalid compiled pattern\n");
	    xsltFreeCompMatch(pat);
	    return(-1);
	    /*
	     * TODO: some flags at the top level about type based patterns
	     *       would be faster than inclusion in the hash table.
	     */
	case XSLT_OP_PI:
	    if (pat->steps[0].value != NULL)
		name = pat->steps[0].value;
	    else
		top = (xsltCompMatchPtr *) &(style->piMatch);
	    break;
	case XSLT_OP_COMMENT:
	    top = (xsltCompMatchPtr *) &(style->commentMatch);
	    break;
	case XSLT_OP_TEXT:
	    top = (xsltCompMatchPtr *) &(style->textMatch);
	    break;
        case XSLT_OP_ELEM:
	case XSLT_OP_NODE:
	    if (pat->steps[0].value != NULL)
		name = pat->steps[0].value;
	    else
		top = (xsltCompMatchPtr *) &(style->elemMatch);
	    break;
	}
	if (name != NULL) {
	    if (style->templatesHash == NULL) {
		style->templatesHash = xmlHashCreate(1024);
		if (style->templatesHash == NULL) {
		    xsltFreeCompMatch(pat);
		    return(-1);
		}
		xmlHashAddEntry3(style->templatesHash, name, mode, modeURI, pat);
	    } else {
		list = (xsltCompMatchPtr) xmlHashLookup3(style->templatesHash,
							 name, mode, modeURI);
		if (list == NULL) {
		    xmlHashAddEntry3(style->templatesHash, name,
				     mode, modeURI, pat);
		} else {
		    /*
		     * Note '<=' since one must choose among the matching
		     * template rules that are left, the one that occurs
		     * last in the stylesheet
		     */
		    if (list->priority <= pat->priority) {
			pat->next = list;
			xmlHashUpdateEntry3(style->templatesHash, name,
					    mode, modeURI, pat, NULL);
		    } else {
			while (list->next != NULL) {
			    if (list->next->priority <= pat->priority)
				break;
			    list = list->next;
			}
			pat->next = list->next;
			list->next = pat;
		    }
		}
	    }
	} else if (top != NULL) {
	    list = *top;
	    if (list == NULL) {
		*top = pat;
		pat->next = NULL;
	    } else if (list->priority <= pat->priority) {
		pat->next = list;
		*top = pat;
	    } else {
		while (list->next != NULL) {
		    if (list->next->priority <= pat->priority)
			break;
		    list = list->next;
		}
		pat->next = list->next;
		list->next = pat;
	    }
	} else {
	    xsltTransformError(NULL, style, NULL,
			     "xsltAddTemplate: invalid compiled pattern\n");
	    xsltFreeCompMatch(pat);
	    return(-1);
	}
#ifdef WITH_XSLT_DEBUG_PATTERN
	if (mode)
	    xsltGenericDebug(xsltGenericDebugContext,
			 "added pattern : '%s' mode '%s' priority %f\n",
			     pat->pattern, pat->mode, pat->priority);
	else
	    xsltGenericDebug(xsltGenericDebugContext,
			 "added pattern : '%s' priority %f\n",
			     pat->pattern, pat->priority);
#endif

	pat = next;
    }
    return(0);
}

/**
 * xsltGetTemplate:
 * @ctxt:  a XSLT process context
 * @node:  the node being processed
 * @style:  the current style
 *
 * Finds the template applying to this node, if @style is non-NULL
 * it means one needs to look for the next imported template in scope.
 *
 * Returns the xsltTemplatePtr or NULL if not found
 */
xsltTemplatePtr
xsltGetTemplate(xsltTransformContextPtr ctxt, xmlNodePtr node,
	        xsltStylesheetPtr style) {
    xsltStylesheetPtr curstyle;
    xsltTemplatePtr ret = NULL;
    const xmlChar *name = NULL;
    xsltCompMatchPtr list = NULL;
    float priority;

    if ((ctxt == NULL) || (node == NULL))
	return(NULL);

    if (style == NULL) {
	curstyle = ctxt->style;
    } else {
	curstyle = xsltNextImport(style);
    }

    while ((curstyle != NULL) && (curstyle != style)) {
	priority = XSLT_PAT_NO_PRIORITY;
	/* TODO : handle IDs/keys here ! */
	if (curstyle->templatesHash != NULL) {
	    /*
	     * Use the top name as selector
	     */
	    switch (node->type) {
		case XML_ELEMENT_NODE:
		case XML_ATTRIBUTE_NODE:
		case XML_PI_NODE:
		    name = node->name;
		    break;
		case XML_DOCUMENT_NODE:
		case XML_HTML_DOCUMENT_NODE:
		case XML_TEXT_NODE:
		case XML_CDATA_SECTION_NODE:
		case XML_COMMENT_NODE:
		case XML_ENTITY_REF_NODE:
		case XML_ENTITY_NODE:
		case XML_DOCUMENT_TYPE_NODE:
		case XML_DOCUMENT_FRAG_NODE:
		case XML_NOTATION_NODE:
		case XML_DTD_NODE:
		case XML_ELEMENT_DECL:
		case XML_ATTRIBUTE_DECL:
		case XML_ENTITY_DECL:
		case XML_NAMESPACE_DECL:
		case XML_XINCLUDE_START:
		case XML_XINCLUDE_END:
		    break;
		default:
		    return(NULL);

	    }
	}
	if (name != NULL) {
	    /*
	     * find the list of appliable expressions based on the name
	     */
	    list = (xsltCompMatchPtr) xmlHashLookup3(curstyle->templatesHash,
					     name, ctxt->mode, ctxt->modeURI);
	} else
	    list = NULL;
	while (list != NULL) {
	    if (xsltTestCompMatch(ctxt, list, node,
			          ctxt->mode, ctxt->modeURI)) {
		ret = list->template;
		priority = list->priority;
		break;
	    }
	    list = list->next;
	}
	list = NULL;

	/*
	 * find alternate generic matches
	 */
	switch (node->type) {
	    case XML_ELEMENT_NODE:
		list = curstyle->elemMatch;
		break;
	    case XML_ATTRIBUTE_NODE:
		list = curstyle->attrMatch;
		break;
	    case XML_PI_NODE:
		list = curstyle->piMatch;
		break;
	    case XML_DOCUMENT_NODE:
	    case XML_HTML_DOCUMENT_NODE:
		list = curstyle->rootMatch;
		break;
	    case XML_TEXT_NODE:
	    case XML_CDATA_SECTION_NODE:
		list = curstyle->textMatch;
		break;
	    case XML_COMMENT_NODE:
		list = curstyle->commentMatch;
		break;
	    case XML_ENTITY_REF_NODE:
	    case XML_ENTITY_NODE:
	    case XML_DOCUMENT_TYPE_NODE:
	    case XML_DOCUMENT_FRAG_NODE:
	    case XML_NOTATION_NODE:
	    case XML_DTD_NODE:
	    case XML_ELEMENT_DECL:
	    case XML_ATTRIBUTE_DECL:
	    case XML_ENTITY_DECL:
	    case XML_NAMESPACE_DECL:
	    case XML_XINCLUDE_START:
	    case XML_XINCLUDE_END:
		break;
	    default:
		break;

	}
	while ((list != NULL) &&
	       ((ret == NULL)  || (list->priority > priority))) {
	    if (xsltTestCompMatch(ctxt, list, node,
			          ctxt->mode, ctxt->modeURI)) {
		ret = list->template;
		priority = list->priority;
		break;
	    }
	    list = list->next;
	}
	/*
	 * Some of the tests for elements can also apply to documents
	 */
	if ((node->type == XML_DOCUMENT_NODE) ||
	    (node->type == XML_HTML_DOCUMENT_NODE) ||
	    (node->type == XML_TEXT_NODE)) {
	    list = curstyle->elemMatch;
	    while ((list != NULL) &&
		   ((ret == NULL)  || (list->priority > priority))) {
		if (xsltTestCompMatch(ctxt, list, node,
				      ctxt->mode, ctxt->modeURI)) {
		    ret = list->template;
		    priority = list->priority;
		    break;
		}
		list = list->next;
	    }
	}

	if (node->_private != NULL) {
	    list = curstyle->keyMatch;
	    while ((list != NULL) &&
		   ((ret == NULL)  || (list->priority > priority))) {
		if (xsltTestCompMatch(ctxt, list, node,
				      ctxt->mode, ctxt->modeURI)) {
		    ret = list->template;
		    priority = list->priority;
		    break;
		}
		list = list->next;
	    }
	}
	if (ret != NULL)
	    return(ret);

	/*
	 * Cycle on next curstylesheet import.
	 */
	curstyle = xsltNextImport(curstyle);
    }
    return(NULL);
}

/**
 * xsltCleanupTemplates:
 * @style: an XSLT stylesheet
 *
 * Cleanup the state of the templates used by the stylesheet and
 * the ones it imports.
 */
void
xsltCleanupTemplates(xsltStylesheetPtr style ATTRIBUTE_UNUSED) {
}

/**
 * xsltFreeTemplateHashes:
 * @style: an XSLT stylesheet
 *
 * Free up the memory used by xsltAddTemplate/xsltGetTemplate mechanism
 */
void
xsltFreeTemplateHashes(xsltStylesheetPtr style) {
    if (style->templatesHash != NULL)
	xmlHashFree((xmlHashTablePtr) style->templatesHash,
		    (xmlHashDeallocator) xsltFreeCompMatchList);
    if (style->rootMatch != NULL)
        xsltFreeCompMatchList(style->rootMatch);
    if (style->keyMatch != NULL)
        xsltFreeCompMatchList(style->keyMatch);
    if (style->elemMatch != NULL)
        xsltFreeCompMatchList(style->elemMatch);
    if (style->attrMatch != NULL)
        xsltFreeCompMatchList(style->attrMatch);
    if (style->parentMatch != NULL)
        xsltFreeCompMatchList(style->parentMatch);
    if (style->textMatch != NULL)
        xsltFreeCompMatchList(style->textMatch);
    if (style->piMatch != NULL)
        xsltFreeCompMatchList(style->piMatch);
    if (style->commentMatch != NULL)
        xsltFreeCompMatchList(style->commentMatch);
}

