You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
15304 lines
416 KiB
15304 lines
416 KiB
/* |
|
* xpath.c: XML Path Language implementation |
|
* XPath is a language for addressing parts of an XML document, |
|
* designed to be used by both XSLT and XPointer |
|
*f |
|
* Reference: W3C Recommendation 16 November 1999 |
|
* http://www.w3.org/TR/1999/REC-xpath-19991116 |
|
* Public reference: |
|
* http://www.w3.org/TR/xpath |
|
* |
|
* See Copyright for the status of this software |
|
* |
|
* Author: daniel@veillard.com |
|
* |
|
*/ |
|
|
|
/* To avoid EBCDIC trouble when parsing on zOS */ |
|
#if defined(__MVS__) |
|
#pragma convert("ISO8859-1") |
|
#endif |
|
|
|
#define IN_LIBXML |
|
#include "libxml.h" |
|
|
|
#include <limits.h> |
|
#include <string.h> |
|
#include <stddef.h> |
|
|
|
#ifdef HAVE_SYS_TYPES_H |
|
#include <sys/types.h> |
|
#endif |
|
#ifdef HAVE_MATH_H |
|
#include <math.h> |
|
#endif |
|
#ifdef HAVE_FLOAT_H |
|
#include <float.h> |
|
#endif |
|
#ifdef HAVE_CTYPE_H |
|
#include <ctype.h> |
|
#endif |
|
#ifdef HAVE_SIGNAL_H |
|
#include <signal.h> |
|
#endif |
|
|
|
#include <libxml/xmlmemory.h> |
|
#include <libxml/tree.h> |
|
#include <libxml/valid.h> |
|
#include <libxml/xpath.h> |
|
#include <libxml/xpathInternals.h> |
|
#include <libxml/parserInternals.h> |
|
#include <libxml/hash.h> |
|
#ifdef LIBXML_XPTR_ENABLED |
|
#include <libxml/xpointer.h> |
|
#endif |
|
#ifdef LIBXML_DEBUG_ENABLED |
|
#include <libxml/debugXML.h> |
|
#endif |
|
#include <libxml/xmlerror.h> |
|
#include <libxml/threads.h> |
|
#include <libxml/globals.h> |
|
#ifdef LIBXML_PATTERN_ENABLED |
|
#include <libxml/pattern.h> |
|
#endif |
|
|
|
#include "buf.h" |
|
|
|
#ifdef LIBXML_PATTERN_ENABLED |
|
#define XPATH_STREAMING |
|
#endif |
|
|
|
#define TODO \ |
|
xmlGenericError(xmlGenericErrorContext, \ |
|
"Unimplemented block at %s:%d\n", \ |
|
__FILE__, __LINE__); |
|
|
|
/** |
|
* WITH_TIM_SORT: |
|
* |
|
* Use the Timsort algorithm provided in timsort.h to sort |
|
* nodeset as this is a great improvement over the old Shell sort |
|
* used in xmlXPathNodeSetSort() |
|
*/ |
|
#define WITH_TIM_SORT |
|
|
|
/* |
|
* XP_OPTIMIZED_NON_ELEM_COMPARISON: |
|
* If defined, this will use xmlXPathCmpNodesExt() instead of |
|
* xmlXPathCmpNodes(). The new function is optimized comparison of |
|
* non-element nodes; actually it will speed up comparison only if |
|
* xmlXPathOrderDocElems() was called in order to index the elements of |
|
* a tree in document order; Libxslt does such an indexing, thus it will |
|
* benefit from this optimization. |
|
*/ |
|
#define XP_OPTIMIZED_NON_ELEM_COMPARISON |
|
|
|
/* |
|
* XP_OPTIMIZED_FILTER_FIRST: |
|
* If defined, this will optimize expressions like "key('foo', 'val')[b][1]" |
|
* in a way, that it stop evaluation at the first node. |
|
*/ |
|
#define XP_OPTIMIZED_FILTER_FIRST |
|
|
|
/* |
|
* XP_DEBUG_OBJ_USAGE: |
|
* Internal flag to enable tracking of how much XPath objects have been |
|
* created. |
|
*/ |
|
/* #define XP_DEBUG_OBJ_USAGE */ |
|
|
|
/* |
|
* XPATH_MAX_STEPS: |
|
* when compiling an XPath expression we arbitrary limit the maximum |
|
* number of step operation in the compiled expression. 1000000 is |
|
* an insanely large value which should never be reached under normal |
|
* circumstances |
|
*/ |
|
#define XPATH_MAX_STEPS 1000000 |
|
|
|
/* |
|
* XPATH_MAX_STACK_DEPTH: |
|
* when evaluating an XPath expression we arbitrary limit the maximum |
|
* number of object allowed to be pushed on the stack. 1000000 is |
|
* an insanely large value which should never be reached under normal |
|
* circumstances |
|
*/ |
|
#define XPATH_MAX_STACK_DEPTH 1000000 |
|
|
|
/* |
|
* XPATH_MAX_NODESET_LENGTH: |
|
* when evaluating an XPath expression nodesets are created and we |
|
* arbitrary limit the maximum length of those node set. 10000000 is |
|
* an insanely large value which should never be reached under normal |
|
* circumstances, one would first need to construct an in memory tree |
|
* with more than 10 millions nodes. |
|
*/ |
|
#define XPATH_MAX_NODESET_LENGTH 10000000 |
|
|
|
/* |
|
* TODO: |
|
* There are a few spots where some tests are done which depend upon ascii |
|
* data. These should be enhanced for full UTF8 support (see particularly |
|
* any use of the macros IS_ASCII_CHARACTER and IS_ASCII_DIGIT) |
|
*/ |
|
|
|
#ifdef XP_OPTIMIZED_NON_ELEM_COMPARISON |
|
/** |
|
* xmlXPathCmpNodesExt: |
|
* @node1: the first node |
|
* @node2: the second node |
|
* |
|
* Compare two nodes w.r.t document order. |
|
* This one is optimized for handling of non-element nodes. |
|
* |
|
* Returns -2 in case of error 1 if first point < second point, 0 if |
|
* it's the same node, -1 otherwise |
|
*/ |
|
static int |
|
xmlXPathCmpNodesExt(xmlNodePtr node1, xmlNodePtr node2) { |
|
int depth1, depth2; |
|
int misc = 0, precedence1 = 0, precedence2 = 0; |
|
xmlNodePtr miscNode1 = NULL, miscNode2 = NULL; |
|
xmlNodePtr cur, root; |
|
ptrdiff_t l1, l2; |
|
|
|
if ((node1 == NULL) || (node2 == NULL)) |
|
return(-2); |
|
|
|
if (node1 == node2) |
|
return(0); |
|
|
|
/* |
|
* a couple of optimizations which will avoid computations in most cases |
|
*/ |
|
switch (node1->type) { |
|
case XML_ELEMENT_NODE: |
|
if (node2->type == XML_ELEMENT_NODE) { |
|
if ((0 > (ptrdiff_t) node1->content) && |
|
(0 > (ptrdiff_t) node2->content) && |
|
(node1->doc == node2->doc)) |
|
{ |
|
l1 = -((ptrdiff_t) node1->content); |
|
l2 = -((ptrdiff_t) node2->content); |
|
if (l1 < l2) |
|
return(1); |
|
if (l1 > l2) |
|
return(-1); |
|
} else |
|
goto turtle_comparison; |
|
} |
|
break; |
|
case XML_ATTRIBUTE_NODE: |
|
precedence1 = 1; /* element is owner */ |
|
miscNode1 = node1; |
|
node1 = node1->parent; |
|
misc = 1; |
|
break; |
|
case XML_TEXT_NODE: |
|
case XML_CDATA_SECTION_NODE: |
|
case XML_COMMENT_NODE: |
|
case XML_PI_NODE: { |
|
miscNode1 = node1; |
|
/* |
|
* Find nearest element node. |
|
*/ |
|
if (node1->prev != NULL) { |
|
do { |
|
node1 = node1->prev; |
|
if (node1->type == XML_ELEMENT_NODE) { |
|
precedence1 = 3; /* element in prev-sibl axis */ |
|
break; |
|
} |
|
if (node1->prev == NULL) { |
|
precedence1 = 2; /* element is parent */ |
|
/* |
|
* URGENT TODO: Are there any cases, where the |
|
* parent of such a node is not an element node? |
|
*/ |
|
node1 = node1->parent; |
|
break; |
|
} |
|
} while (1); |
|
} else { |
|
precedence1 = 2; /* element is parent */ |
|
node1 = node1->parent; |
|
} |
|
if ((node1 == NULL) || (node1->type != XML_ELEMENT_NODE) || |
|
(0 <= (ptrdiff_t) node1->content)) { |
|
/* |
|
* Fallback for whatever case. |
|
*/ |
|
node1 = miscNode1; |
|
precedence1 = 0; |
|
} else |
|
misc = 1; |
|
} |
|
break; |
|
case XML_NAMESPACE_DECL: |
|
/* |
|
* TODO: why do we return 1 for namespace nodes? |
|
*/ |
|
return(1); |
|
default: |
|
break; |
|
} |
|
switch (node2->type) { |
|
case XML_ELEMENT_NODE: |
|
break; |
|
case XML_ATTRIBUTE_NODE: |
|
precedence2 = 1; /* element is owner */ |
|
miscNode2 = node2; |
|
node2 = node2->parent; |
|
misc = 1; |
|
break; |
|
case XML_TEXT_NODE: |
|
case XML_CDATA_SECTION_NODE: |
|
case XML_COMMENT_NODE: |
|
case XML_PI_NODE: { |
|
miscNode2 = node2; |
|
if (node2->prev != NULL) { |
|
do { |
|
node2 = node2->prev; |
|
if (node2->type == XML_ELEMENT_NODE) { |
|
precedence2 = 3; /* element in prev-sibl axis */ |
|
break; |
|
} |
|
if (node2->prev == NULL) { |
|
precedence2 = 2; /* element is parent */ |
|
node2 = node2->parent; |
|
break; |
|
} |
|
} while (1); |
|
} else { |
|
precedence2 = 2; /* element is parent */ |
|
node2 = node2->parent; |
|
} |
|
if ((node2 == NULL) || (node2->type != XML_ELEMENT_NODE) || |
|
(0 <= (ptrdiff_t) node2->content)) |
|
{ |
|
node2 = miscNode2; |
|
precedence2 = 0; |
|
} else |
|
misc = 1; |
|
} |
|
break; |
|
case XML_NAMESPACE_DECL: |
|
return(1); |
|
default: |
|
break; |
|
} |
|
if (misc) { |
|
if (node1 == node2) { |
|
if (precedence1 == precedence2) { |
|
/* |
|
* The ugly case; but normally there aren't many |
|
* adjacent non-element nodes around. |
|
*/ |
|
cur = miscNode2->prev; |
|
while (cur != NULL) { |
|
if (cur == miscNode1) |
|
return(1); |
|
if (cur->type == XML_ELEMENT_NODE) |
|
return(-1); |
|
cur = cur->prev; |
|
} |
|
return (-1); |
|
} else { |
|
/* |
|
* Evaluate based on higher precedence wrt to the element. |
|
* TODO: This assumes attributes are sorted before content. |
|
* Is this 100% correct? |
|
*/ |
|
if (precedence1 < precedence2) |
|
return(1); |
|
else |
|
return(-1); |
|
} |
|
} |
|
/* |
|
* Special case: One of the helper-elements is contained by the other. |
|
* <foo> |
|
* <node2> |
|
* <node1>Text-1(precedence1 == 2)</node1> |
|
* </node2> |
|
* Text-6(precedence2 == 3) |
|
* </foo> |
|
*/ |
|
if ((precedence2 == 3) && (precedence1 > 1)) { |
|
cur = node1->parent; |
|
while (cur) { |
|
if (cur == node2) |
|
return(1); |
|
cur = cur->parent; |
|
} |
|
} |
|
if ((precedence1 == 3) && (precedence2 > 1)) { |
|
cur = node2->parent; |
|
while (cur) { |
|
if (cur == node1) |
|
return(-1); |
|
cur = cur->parent; |
|
} |
|
} |
|
} |
|
|
|
/* |
|
* Speedup using document order if availble. |
|
*/ |
|
if ((node1->type == XML_ELEMENT_NODE) && |
|
(node2->type == XML_ELEMENT_NODE) && |
|
(0 > (ptrdiff_t) node1->content) && |
|
(0 > (ptrdiff_t) node2->content) && |
|
(node1->doc == node2->doc)) { |
|
|
|
l1 = -((ptrdiff_t) node1->content); |
|
l2 = -((ptrdiff_t) node2->content); |
|
if (l1 < l2) |
|
return(1); |
|
if (l1 > l2) |
|
return(-1); |
|
} |
|
|
|
turtle_comparison: |
|
|
|
if (node1 == node2->prev) |
|
return(1); |
|
if (node1 == node2->next) |
|
return(-1); |
|
/* |
|
* compute depth to root |
|
*/ |
|
for (depth2 = 0, cur = node2; cur->parent != NULL; cur = cur->parent) { |
|
if (cur->parent == node1) |
|
return(1); |
|
depth2++; |
|
} |
|
root = cur; |
|
for (depth1 = 0, cur = node1; cur->parent != NULL; cur = cur->parent) { |
|
if (cur->parent == node2) |
|
return(-1); |
|
depth1++; |
|
} |
|
/* |
|
* Distinct document (or distinct entities :-( ) case. |
|
*/ |
|
if (root != cur) { |
|
return(-2); |
|
} |
|
/* |
|
* get the nearest common ancestor. |
|
*/ |
|
while (depth1 > depth2) { |
|
depth1--; |
|
node1 = node1->parent; |
|
} |
|
while (depth2 > depth1) { |
|
depth2--; |
|
node2 = node2->parent; |
|
} |
|
while (node1->parent != node2->parent) { |
|
node1 = node1->parent; |
|
node2 = node2->parent; |
|
/* should not happen but just in case ... */ |
|
if ((node1 == NULL) || (node2 == NULL)) |
|
return(-2); |
|
} |
|
/* |
|
* Find who's first. |
|
*/ |
|
if (node1 == node2->prev) |
|
return(1); |
|
if (node1 == node2->next) |
|
return(-1); |
|
/* |
|
* Speedup using document order if availble. |
|
*/ |
|
if ((node1->type == XML_ELEMENT_NODE) && |
|
(node2->type == XML_ELEMENT_NODE) && |
|
(0 > (ptrdiff_t) node1->content) && |
|
(0 > (ptrdiff_t) node2->content) && |
|
(node1->doc == node2->doc)) { |
|
|
|
l1 = -((ptrdiff_t) node1->content); |
|
l2 = -((ptrdiff_t) node2->content); |
|
if (l1 < l2) |
|
return(1); |
|
if (l1 > l2) |
|
return(-1); |
|
} |
|
|
|
for (cur = node1->next;cur != NULL;cur = cur->next) |
|
if (cur == node2) |
|
return(1); |
|
return(-1); /* assume there is no sibling list corruption */ |
|
} |
|
#endif /* XP_OPTIMIZED_NON_ELEM_COMPARISON */ |
|
|
|
/* |
|
* Wrapper for the Timsort argorithm from timsort.h |
|
*/ |
|
#ifdef WITH_TIM_SORT |
|
#define SORT_NAME libxml_domnode |
|
#define SORT_TYPE xmlNodePtr |
|
/** |
|
* wrap_cmp: |
|
* @x: a node |
|
* @y: another node |
|
* |
|
* Comparison function for the Timsort implementation |
|
* |
|
* Returns -2 in case of error -1 if first point < second point, 0 if |
|
* it's the same node, +1 otherwise |
|
*/ |
|
static |
|
int wrap_cmp( xmlNodePtr x, xmlNodePtr y ); |
|
#ifdef XP_OPTIMIZED_NON_ELEM_COMPARISON |
|
static int wrap_cmp( xmlNodePtr x, xmlNodePtr y ) |
|
{ |
|
int res = xmlXPathCmpNodesExt(x, y); |
|
return res == -2 ? res : -res; |
|
} |
|
#else |
|
static int wrap_cmp( xmlNodePtr x, xmlNodePtr y ) |
|
{ |
|
int res = xmlXPathCmpNodes(x, y); |
|
return res == -2 ? res : -res; |
|
} |
|
#endif |
|
#define SORT_CMP(x, y) (wrap_cmp(x, y)) |
|
#include "timsort.h" |
|
#endif /* WITH_TIM_SORT */ |
|
|
|
#if defined(LIBXML_XPATH_ENABLED) || defined(LIBXML_SCHEMAS_ENABLED) |
|
|
|
/************************************************************************ |
|
* * |
|
* Floating point stuff * |
|
* * |
|
************************************************************************/ |
|
|
|
#ifndef NAN |
|
#define NAN (0.0 / 0.0) |
|
#endif |
|
|
|
#ifndef INFINITY |
|
#define INFINITY HUGE_VAL |
|
#endif |
|
|
|
double xmlXPathNAN = NAN; |
|
double xmlXPathPINF = INFINITY; |
|
double xmlXPathNINF = -INFINITY; |
|
|
|
/** |
|
* xmlXPathInit: |
|
* |
|
* Initialize the XPath environment |
|
* |
|
* Does nothing but must be kept as public function. |
|
*/ |
|
void |
|
xmlXPathInit(void) { |
|
} |
|
|
|
/** |
|
* xmlXPathIsNaN: |
|
* @val: a double value |
|
* |
|
* Returns 1 if the value is a NaN, 0 otherwise |
|
*/ |
|
int |
|
xmlXPathIsNaN(double val) { |
|
#ifdef isnan |
|
return isnan(val); |
|
#else |
|
return !(val == val); |
|
#endif |
|
} |
|
|
|
/** |
|
* xmlXPathIsInf: |
|
* @val: a double value |
|
* |
|
* Returns 1 if the value is +Infinite, -1 if -Infinite, 0 otherwise |
|
*/ |
|
int |
|
xmlXPathIsInf(double val) { |
|
#ifdef isinf |
|
return isinf(val) ? (val > 0 ? 1 : -1) : 0; |
|
#else |
|
if (val >= HUGE_VAL) |
|
return 1; |
|
if (val <= -HUGE_VAL) |
|
return -1; |
|
return 0; |
|
#endif |
|
} |
|
|
|
#endif /* SCHEMAS or XPATH */ |
|
|
|
#ifdef LIBXML_XPATH_ENABLED |
|
|
|
/* |
|
* TODO: when compatibility allows remove all "fake node libxslt" strings |
|
* the test should just be name[0] = ' ' |
|
*/ |
|
#ifdef DEBUG_XPATH_EXPRESSION |
|
#define DEBUG_STEP |
|
#define DEBUG_EXPR |
|
#define DEBUG_EVAL_COUNTS |
|
#endif |
|
|
|
static xmlNs xmlXPathXMLNamespaceStruct = { |
|
NULL, |
|
XML_NAMESPACE_DECL, |
|
XML_XML_NAMESPACE, |
|
BAD_CAST "xml", |
|
NULL, |
|
NULL |
|
}; |
|
static xmlNsPtr xmlXPathXMLNamespace = &xmlXPathXMLNamespaceStruct; |
|
#ifndef LIBXML_THREAD_ENABLED |
|
/* |
|
* Optimizer is disabled only when threaded apps are detected while |
|
* the library ain't compiled for thread safety. |
|
*/ |
|
static int xmlXPathDisableOptimizer = 0; |
|
#endif |
|
|
|
/************************************************************************ |
|
* * |
|
* Error handling routines * |
|
* * |
|
************************************************************************/ |
|
|
|
/** |
|
* XP_ERRORNULL: |
|
* @X: the error code |
|
* |
|
* Macro to raise an XPath error and return NULL. |
|
*/ |
|
#define XP_ERRORNULL(X) \ |
|
{ xmlXPathErr(ctxt, X); return(NULL); } |
|
|
|
/* |
|
* The array xmlXPathErrorMessages corresponds to the enum xmlXPathError |
|
*/ |
|
static const char *xmlXPathErrorMessages[] = { |
|
"Ok\n", |
|
"Number encoding\n", |
|
"Unfinished literal\n", |
|
"Start of literal\n", |
|
"Expected $ for variable reference\n", |
|
"Undefined variable\n", |
|
"Invalid predicate\n", |
|
"Invalid expression\n", |
|
"Missing closing curly brace\n", |
|
"Unregistered function\n", |
|
"Invalid operand\n", |
|
"Invalid type\n", |
|
"Invalid number of arguments\n", |
|
"Invalid context size\n", |
|
"Invalid context position\n", |
|
"Memory allocation error\n", |
|
"Syntax error\n", |
|
"Resource error\n", |
|
"Sub resource error\n", |
|
"Undefined namespace prefix\n", |
|
"Encoding error\n", |
|
"Char out of XML range\n", |
|
"Invalid or incomplete context\n", |
|
"Stack usage error\n", |
|
"Forbidden variable\n", |
|
"?? Unknown error ??\n" /* Must be last in the list! */ |
|
}; |
|
#define MAXERRNO ((int)(sizeof(xmlXPathErrorMessages) / \ |
|
sizeof(xmlXPathErrorMessages[0])) - 1) |
|
/** |
|
* xmlXPathErrMemory: |
|
* @ctxt: an XPath context |
|
* @extra: extra informations |
|
* |
|
* Handle a redefinition of attribute error |
|
*/ |
|
static void |
|
xmlXPathErrMemory(xmlXPathContextPtr ctxt, const char *extra) |
|
{ |
|
if (ctxt != NULL) { |
|
if (extra) { |
|
xmlChar buf[200]; |
|
|
|
xmlStrPrintf(buf, 200, |
|
"Memory allocation failed : %s\n", |
|
extra); |
|
ctxt->lastError.message = (char *) xmlStrdup(buf); |
|
} else { |
|
ctxt->lastError.message = (char *) |
|
xmlStrdup(BAD_CAST "Memory allocation failed\n"); |
|
} |
|
ctxt->lastError.domain = XML_FROM_XPATH; |
|
ctxt->lastError.code = XML_ERR_NO_MEMORY; |
|
if (ctxt->error != NULL) |
|
ctxt->error(ctxt->userData, &ctxt->lastError); |
|
} else { |
|
if (extra) |
|
__xmlRaiseError(NULL, NULL, NULL, |
|
NULL, NULL, XML_FROM_XPATH, |
|
XML_ERR_NO_MEMORY, XML_ERR_FATAL, NULL, 0, |
|
extra, NULL, NULL, 0, 0, |
|
"Memory allocation failed : %s\n", extra); |
|
else |
|
__xmlRaiseError(NULL, NULL, NULL, |
|
NULL, NULL, XML_FROM_XPATH, |
|
XML_ERR_NO_MEMORY, XML_ERR_FATAL, NULL, 0, |
|
NULL, NULL, NULL, 0, 0, |
|
"Memory allocation failed\n"); |
|
} |
|
} |
|
|
|
/** |
|
* xmlXPathPErrMemory: |
|
* @ctxt: an XPath parser context |
|
* @extra: extra informations |
|
* |
|
* Handle a redefinition of attribute error |
|
*/ |
|
static void |
|
xmlXPathPErrMemory(xmlXPathParserContextPtr ctxt, const char *extra) |
|
{ |
|
if (ctxt == NULL) |
|
xmlXPathErrMemory(NULL, extra); |
|
else { |
|
ctxt->error = XPATH_MEMORY_ERROR; |
|
xmlXPathErrMemory(ctxt->context, extra); |
|
} |
|
} |
|
|
|
/** |
|
* xmlXPathErr: |
|
* @ctxt: a XPath parser context |
|
* @error: the error code |
|
* |
|
* Handle an XPath error |
|
*/ |
|
void |
|
xmlXPathErr(xmlXPathParserContextPtr ctxt, int error) |
|
{ |
|
if ((error < 0) || (error > MAXERRNO)) |
|
error = MAXERRNO; |
|
if (ctxt == NULL) { |
|
__xmlRaiseError(NULL, NULL, NULL, |
|
NULL, NULL, XML_FROM_XPATH, |
|
error + XML_XPATH_EXPRESSION_OK - XPATH_EXPRESSION_OK, |
|
XML_ERR_ERROR, NULL, 0, |
|
NULL, NULL, NULL, 0, 0, |
|
"%s", xmlXPathErrorMessages[error]); |
|
return; |
|
} |
|
ctxt->error = error; |
|
if (ctxt->context == NULL) { |
|
__xmlRaiseError(NULL, NULL, NULL, |
|
NULL, NULL, XML_FROM_XPATH, |
|
error + XML_XPATH_EXPRESSION_OK - XPATH_EXPRESSION_OK, |
|
XML_ERR_ERROR, NULL, 0, |
|
(const char *) ctxt->base, NULL, NULL, |
|
ctxt->cur - ctxt->base, 0, |
|
"%s", xmlXPathErrorMessages[error]); |
|
return; |
|
} |
|
|
|
/* cleanup current last error */ |
|
xmlResetError(&ctxt->context->lastError); |
|
|
|
ctxt->context->lastError.domain = XML_FROM_XPATH; |
|
ctxt->context->lastError.code = error + XML_XPATH_EXPRESSION_OK - |
|
XPATH_EXPRESSION_OK; |
|
ctxt->context->lastError.level = XML_ERR_ERROR; |
|
ctxt->context->lastError.str1 = (char *) xmlStrdup(ctxt->base); |
|
ctxt->context->lastError.int1 = ctxt->cur - ctxt->base; |
|
ctxt->context->lastError.node = ctxt->context->debugNode; |
|
if (ctxt->context->error != NULL) { |
|
ctxt->context->error(ctxt->context->userData, |
|
&ctxt->context->lastError); |
|
} else { |
|
__xmlRaiseError(NULL, NULL, NULL, |
|
NULL, ctxt->context->debugNode, XML_FROM_XPATH, |
|
error + XML_XPATH_EXPRESSION_OK - XPATH_EXPRESSION_OK, |
|
XML_ERR_ERROR, NULL, 0, |
|
(const char *) ctxt->base, NULL, NULL, |
|
ctxt->cur - ctxt->base, 0, |
|
"%s", xmlXPathErrorMessages[error]); |
|
} |
|
|
|
} |
|
|
|
/** |
|
* xmlXPatherror: |
|
* @ctxt: the XPath Parser context |
|
* @file: the file name |
|
* @line: the line number |
|
* @no: the error number |
|
* |
|
* Formats an error message. |
|
*/ |
|
void |
|
xmlXPatherror(xmlXPathParserContextPtr ctxt, const char *file ATTRIBUTE_UNUSED, |
|
int line ATTRIBUTE_UNUSED, int no) { |
|
xmlXPathErr(ctxt, no); |
|
} |
|
|
|
/************************************************************************ |
|
* * |
|
* Utilities * |
|
* * |
|
************************************************************************/ |
|
|
|
/** |
|
* xsltPointerList: |
|
* |
|
* Pointer-list for various purposes. |
|
*/ |
|
typedef struct _xmlPointerList xmlPointerList; |
|
typedef xmlPointerList *xmlPointerListPtr; |
|
struct _xmlPointerList { |
|
void **items; |
|
int number; |
|
int size; |
|
}; |
|
/* |
|
* TODO: Since such a list-handling is used in xmlschemas.c and libxslt |
|
* and here, we should make the functions public. |
|
*/ |
|
static int |
|
xmlPointerListAddSize(xmlPointerListPtr list, |
|
void *item, |
|
int initialSize) |
|
{ |
|
if (list->items == NULL) { |
|
if (initialSize <= 0) |
|
initialSize = 1; |
|
list->items = (void **) xmlMalloc(initialSize * sizeof(void *)); |
|
if (list->items == NULL) { |
|
xmlXPathErrMemory(NULL, |
|
"xmlPointerListCreate: allocating item\n"); |
|
return(-1); |
|
} |
|
list->number = 0; |
|
list->size = initialSize; |
|
} else if (list->size <= list->number) { |
|
if (list->size > 50000000) { |
|
xmlXPathErrMemory(NULL, |
|
"xmlPointerListAddSize: re-allocating item\n"); |
|
return(-1); |
|
} |
|
list->size *= 2; |
|
list->items = (void **) xmlRealloc(list->items, |
|
list->size * sizeof(void *)); |
|
if (list->items == NULL) { |
|
xmlXPathErrMemory(NULL, |
|
"xmlPointerListAddSize: re-allocating item\n"); |
|
list->size = 0; |
|
return(-1); |
|
} |
|
} |
|
list->items[list->number++] = item; |
|
return(0); |
|
} |
|
|
|
/** |
|
* xsltPointerListCreate: |
|
* |
|
* Creates an xsltPointerList structure. |
|
* |
|
* Returns a xsltPointerList structure or NULL in case of an error. |
|
*/ |
|
static xmlPointerListPtr |
|
xmlPointerListCreate(int initialSize) |
|
{ |
|
xmlPointerListPtr ret; |
|
|
|
ret = xmlMalloc(sizeof(xmlPointerList)); |
|
if (ret == NULL) { |
|
xmlXPathErrMemory(NULL, |
|
"xmlPointerListCreate: allocating item\n"); |
|
return (NULL); |
|
} |
|
memset(ret, 0, sizeof(xmlPointerList)); |
|
if (initialSize > 0) { |
|
xmlPointerListAddSize(ret, NULL, initialSize); |
|
ret->number = 0; |
|
} |
|
return (ret); |
|
} |
|
|
|
/** |
|
* xsltPointerListFree: |
|
* |
|
* Frees the xsltPointerList structure. This does not free |
|
* the content of the list. |
|
*/ |
|
static void |
|
xmlPointerListFree(xmlPointerListPtr list) |
|
{ |
|
if (list == NULL) |
|
return; |
|
if (list->items != NULL) |
|
xmlFree(list->items); |
|
xmlFree(list); |
|
} |
|
|
|
/************************************************************************ |
|
* * |
|
* Parser Types * |
|
* * |
|
************************************************************************/ |
|
|
|
/* |
|
* Types are private: |
|
*/ |
|
|
|
typedef enum { |
|
XPATH_OP_END=0, |
|
XPATH_OP_AND, |
|
XPATH_OP_OR, |
|
XPATH_OP_EQUAL, |
|
XPATH_OP_CMP, |
|
XPATH_OP_PLUS, |
|
XPATH_OP_MULT, |
|
XPATH_OP_UNION, |
|
XPATH_OP_ROOT, |
|
XPATH_OP_NODE, |
|
XPATH_OP_RESET, /* 10 */ |
|
XPATH_OP_COLLECT, |
|
XPATH_OP_VALUE, /* 12 */ |
|
XPATH_OP_VARIABLE, |
|
XPATH_OP_FUNCTION, |
|
XPATH_OP_ARG, |
|
XPATH_OP_PREDICATE, |
|
XPATH_OP_FILTER, /* 17 */ |
|
XPATH_OP_SORT /* 18 */ |
|
#ifdef LIBXML_XPTR_ENABLED |
|
,XPATH_OP_RANGETO |
|
#endif |
|
} xmlXPathOp; |
|
|
|
typedef enum { |
|
AXIS_ANCESTOR = 1, |
|
AXIS_ANCESTOR_OR_SELF, |
|
AXIS_ATTRIBUTE, |
|
AXIS_CHILD, |
|
AXIS_DESCENDANT, |
|
AXIS_DESCENDANT_OR_SELF, |
|
AXIS_FOLLOWING, |
|
AXIS_FOLLOWING_SIBLING, |
|
AXIS_NAMESPACE, |
|
AXIS_PARENT, |
|
AXIS_PRECEDING, |
|
AXIS_PRECEDING_SIBLING, |
|
AXIS_SELF |
|
} xmlXPathAxisVal; |
|
|
|
typedef enum { |
|
NODE_TEST_NONE = 0, |
|
NODE_TEST_TYPE = 1, |
|
NODE_TEST_PI = 2, |
|
NODE_TEST_ALL = 3, |
|
NODE_TEST_NS = 4, |
|
NODE_TEST_NAME = 5 |
|
} xmlXPathTestVal; |
|
|
|
typedef enum { |
|
NODE_TYPE_NODE = 0, |
|
NODE_TYPE_COMMENT = XML_COMMENT_NODE, |
|
NODE_TYPE_TEXT = XML_TEXT_NODE, |
|
NODE_TYPE_PI = XML_PI_NODE |
|
} xmlXPathTypeVal; |
|
|
|
typedef struct _xmlXPathStepOp xmlXPathStepOp; |
|
typedef xmlXPathStepOp *xmlXPathStepOpPtr; |
|
struct _xmlXPathStepOp { |
|
xmlXPathOp op; /* The identifier of the operation */ |
|
int ch1; /* First child */ |
|
int ch2; /* Second child */ |
|
int value; |
|
int value2; |
|
int value3; |
|
void *value4; |
|
void *value5; |
|
xmlXPathFunction cache; |
|
void *cacheURI; |
|
}; |
|
|
|
struct _xmlXPathCompExpr { |
|
int nbStep; /* Number of steps in this expression */ |
|
int maxStep; /* Maximum number of steps allocated */ |
|
xmlXPathStepOp *steps; /* ops for computation of this expression */ |
|
int last; /* index of last step in expression */ |
|
xmlChar *expr; /* the expression being computed */ |
|
xmlDictPtr dict; /* the dictionary to use if any */ |
|
#ifdef DEBUG_EVAL_COUNTS |
|
int nb; |
|
xmlChar *string; |
|
#endif |
|
#ifdef XPATH_STREAMING |
|
xmlPatternPtr stream; |
|
#endif |
|
}; |
|
|
|
/************************************************************************ |
|
* * |
|
* Forward declarations * |
|
* * |
|
************************************************************************/ |
|
static void |
|
xmlXPathFreeValueTree(xmlNodeSetPtr obj); |
|
static void |
|
xmlXPathReleaseObject(xmlXPathContextPtr ctxt, xmlXPathObjectPtr obj); |
|
static int |
|
xmlXPathCompOpEvalFirst(xmlXPathParserContextPtr ctxt, |
|
xmlXPathStepOpPtr op, xmlNodePtr *first); |
|
static int |
|
xmlXPathCompOpEvalToBoolean(xmlXPathParserContextPtr ctxt, |
|
xmlXPathStepOpPtr op, |
|
int isPredicate); |
|
static void |
|
xmlXPathFreeObjectEntry(void *obj, const xmlChar *name); |
|
|
|
/************************************************************************ |
|
* * |
|
* Parser Type functions * |
|
* * |
|
************************************************************************/ |
|
|
|
/** |
|
* xmlXPathNewCompExpr: |
|
* |
|
* Create a new Xpath component |
|
* |
|
* Returns the newly allocated xmlXPathCompExprPtr or NULL in case of error |
|
*/ |
|
static xmlXPathCompExprPtr |
|
xmlXPathNewCompExpr(void) { |
|
xmlXPathCompExprPtr cur; |
|
|
|
cur = (xmlXPathCompExprPtr) xmlMalloc(sizeof(xmlXPathCompExpr)); |
|
if (cur == NULL) { |
|
xmlXPathErrMemory(NULL, "allocating component\n"); |
|
return(NULL); |
|
} |
|
memset(cur, 0, sizeof(xmlXPathCompExpr)); |
|
cur->maxStep = 10; |
|
cur->nbStep = 0; |
|
cur->steps = (xmlXPathStepOp *) xmlMalloc(cur->maxStep * |
|
sizeof(xmlXPathStepOp)); |
|
if (cur->steps == NULL) { |
|
xmlXPathErrMemory(NULL, "allocating steps\n"); |
|
xmlFree(cur); |
|
return(NULL); |
|
} |
|
memset(cur->steps, 0, cur->maxStep * sizeof(xmlXPathStepOp)); |
|
cur->last = -1; |
|
#ifdef DEBUG_EVAL_COUNTS |
|
cur->nb = 0; |
|
#endif |
|
return(cur); |
|
} |
|
|
|
/** |
|
* xmlXPathFreeCompExpr: |
|
* @comp: an XPATH comp |
|
* |
|
* Free up the memory allocated by @comp |
|
*/ |
|
void |
|
xmlXPathFreeCompExpr(xmlXPathCompExprPtr comp) |
|
{ |
|
xmlXPathStepOpPtr op; |
|
int i; |
|
|
|
if (comp == NULL) |
|
return; |
|
if (comp->dict == NULL) { |
|
for (i = 0; i < comp->nbStep; i++) { |
|
op = &comp->steps[i]; |
|
if (op->value4 != NULL) { |
|
if (op->op == XPATH_OP_VALUE) |
|
xmlXPathFreeObject(op->value4); |
|
else |
|
xmlFree(op->value4); |
|
} |
|
if (op->value5 != NULL) |
|
xmlFree(op->value5); |
|
} |
|
} else { |
|
for (i = 0; i < comp->nbStep; i++) { |
|
op = &comp->steps[i]; |
|
if (op->value4 != NULL) { |
|
if (op->op == XPATH_OP_VALUE) |
|
xmlXPathFreeObject(op->value4); |
|
} |
|
} |
|
xmlDictFree(comp->dict); |
|
} |
|
if (comp->steps != NULL) { |
|
xmlFree(comp->steps); |
|
} |
|
#ifdef DEBUG_EVAL_COUNTS |
|
if (comp->string != NULL) { |
|
xmlFree(comp->string); |
|
} |
|
#endif |
|
#ifdef XPATH_STREAMING |
|
if (comp->stream != NULL) { |
|
xmlFreePatternList(comp->stream); |
|
} |
|
#endif |
|
if (comp->expr != NULL) { |
|
xmlFree(comp->expr); |
|
} |
|
|
|
xmlFree(comp); |
|
} |
|
|
|
/** |
|
* xmlXPathCompExprAdd: |
|
* @comp: the compiled expression |
|
* @ch1: first child index |
|
* @ch2: second child index |
|
* @op: an op |
|
* @value: the first int value |
|
* @value2: the second int value |
|
* @value3: the third int value |
|
* @value4: the first string value |
|
* @value5: the second string value |
|
* |
|
* Add a step to an XPath Compiled Expression |
|
* |
|
* Returns -1 in case of failure, the index otherwise |
|
*/ |
|
static int |
|
xmlXPathCompExprAdd(xmlXPathCompExprPtr comp, int ch1, int ch2, |
|
xmlXPathOp op, int value, |
|
int value2, int value3, void *value4, void *value5) { |
|
if (comp->nbStep >= comp->maxStep) { |
|
xmlXPathStepOp *real; |
|
|
|
if (comp->maxStep >= XPATH_MAX_STEPS) { |
|
xmlXPathErrMemory(NULL, "adding step\n"); |
|
return(-1); |
|
} |
|
comp->maxStep *= 2; |
|
real = (xmlXPathStepOp *) xmlRealloc(comp->steps, |
|
comp->maxStep * sizeof(xmlXPathStepOp)); |
|
if (real == NULL) { |
|
comp->maxStep /= 2; |
|
xmlXPathErrMemory(NULL, "adding step\n"); |
|
return(-1); |
|
} |
|
comp->steps = real; |
|
} |
|
comp->last = comp->nbStep; |
|
comp->steps[comp->nbStep].ch1 = ch1; |
|
comp->steps[comp->nbStep].ch2 = ch2; |
|
comp->steps[comp->nbStep].op = op; |
|
comp->steps[comp->nbStep].value = value; |
|
comp->steps[comp->nbStep].value2 = value2; |
|
comp->steps[comp->nbStep].value3 = value3; |
|
if ((comp->dict != NULL) && |
|
((op == XPATH_OP_FUNCTION) || (op == XPATH_OP_VARIABLE) || |
|
(op == XPATH_OP_COLLECT))) { |
|
if (value4 != NULL) { |
|
comp->steps[comp->nbStep].value4 = (xmlChar *) |
|
(void *)xmlDictLookup(comp->dict, value4, -1); |
|
xmlFree(value4); |
|
} else |
|
comp->steps[comp->nbStep].value4 = NULL; |
|
if (value5 != NULL) { |
|
comp->steps[comp->nbStep].value5 = (xmlChar *) |
|
(void *)xmlDictLookup(comp->dict, value5, -1); |
|
xmlFree(value5); |
|
} else |
|
comp->steps[comp->nbStep].value5 = NULL; |
|
} else { |
|
comp->steps[comp->nbStep].value4 = value4; |
|
comp->steps[comp->nbStep].value5 = value5; |
|
} |
|
comp->steps[comp->nbStep].cache = NULL; |
|
return(comp->nbStep++); |
|
} |
|
|
|
/** |
|
* xmlXPathCompSwap: |
|
* @comp: the compiled expression |
|
* @op: operation index |
|
* |
|
* Swaps 2 operations in the compiled expression |
|
*/ |
|
static void |
|
xmlXPathCompSwap(xmlXPathStepOpPtr op) { |
|
int tmp; |
|
|
|
#ifndef LIBXML_THREAD_ENABLED |
|
/* |
|
* Since this manipulates possibly shared variables, this is |
|
* disabled if one detects that the library is used in a multithreaded |
|
* application |
|
*/ |
|
if (xmlXPathDisableOptimizer) |
|
return; |
|
#endif |
|
|
|
tmp = op->ch1; |
|
op->ch1 = op->ch2; |
|
op->ch2 = tmp; |
|
} |
|
|
|
#define PUSH_FULL_EXPR(op, op1, op2, val, val2, val3, val4, val5) \ |
|
xmlXPathCompExprAdd(ctxt->comp, (op1), (op2), \ |
|
(op), (val), (val2), (val3), (val4), (val5)) |
|
#define PUSH_LONG_EXPR(op, val, val2, val3, val4, val5) \ |
|
xmlXPathCompExprAdd(ctxt->comp, ctxt->comp->last, -1, \ |
|
(op), (val), (val2), (val3), (val4), (val5)) |
|
|
|
#define PUSH_LEAVE_EXPR(op, val, val2) \ |
|
xmlXPathCompExprAdd(ctxt->comp, -1, -1, (op), (val), (val2), 0 ,NULL ,NULL) |
|
|
|
#define PUSH_UNARY_EXPR(op, ch, val, val2) \ |
|
xmlXPathCompExprAdd(ctxt->comp, (ch), -1, (op), (val), (val2), 0 ,NULL ,NULL) |
|
|
|
#define PUSH_BINARY_EXPR(op, ch1, ch2, val, val2) \ |
|
xmlXPathCompExprAdd(ctxt->comp, (ch1), (ch2), (op), \ |
|
(val), (val2), 0 ,NULL ,NULL) |
|
|
|
/************************************************************************ |
|
* * |
|
* XPath object cache structures * |
|
* * |
|
************************************************************************/ |
|
|
|
/* #define XP_DEFAULT_CACHE_ON */ |
|
|
|
#define XP_HAS_CACHE(c) ((c != NULL) && ((c)->cache != NULL)) |
|
|
|
typedef struct _xmlXPathContextCache xmlXPathContextCache; |
|
typedef xmlXPathContextCache *xmlXPathContextCachePtr; |
|
struct _xmlXPathContextCache { |
|
xmlPointerListPtr nodesetObjs; /* contains xmlXPathObjectPtr */ |
|
xmlPointerListPtr stringObjs; /* contains xmlXPathObjectPtr */ |
|
xmlPointerListPtr booleanObjs; /* contains xmlXPathObjectPtr */ |
|
xmlPointerListPtr numberObjs; /* contains xmlXPathObjectPtr */ |
|
xmlPointerListPtr miscObjs; /* contains xmlXPathObjectPtr */ |
|
int maxNodeset; |
|
int maxString; |
|
int maxBoolean; |
|
int maxNumber; |
|
int maxMisc; |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
int dbgCachedAll; |
|
int dbgCachedNodeset; |
|
int dbgCachedString; |
|
int dbgCachedBool; |
|
int dbgCachedNumber; |
|
int dbgCachedPoint; |
|
int dbgCachedRange; |
|
int dbgCachedLocset; |
|
int dbgCachedUsers; |
|
int dbgCachedXSLTTree; |
|
int dbgCachedUndefined; |
|
|
|
|
|
int dbgReusedAll; |
|
int dbgReusedNodeset; |
|
int dbgReusedString; |
|
int dbgReusedBool; |
|
int dbgReusedNumber; |
|
int dbgReusedPoint; |
|
int dbgReusedRange; |
|
int dbgReusedLocset; |
|
int dbgReusedUsers; |
|
int dbgReusedXSLTTree; |
|
int dbgReusedUndefined; |
|
|
|
#endif |
|
}; |
|
|
|
/************************************************************************ |
|
* * |
|
* Debugging related functions * |
|
* * |
|
************************************************************************/ |
|
|
|
#define STRANGE \ |
|
xmlGenericError(xmlGenericErrorContext, \ |
|
"Internal error at %s:%d\n", \ |
|
__FILE__, __LINE__); |
|
|
|
#ifdef LIBXML_DEBUG_ENABLED |
|
static void |
|
xmlXPathDebugDumpNode(FILE *output, xmlNodePtr cur, int depth) { |
|
int i; |
|
char shift[100]; |
|
|
|
for (i = 0;((i < depth) && (i < 25));i++) |
|
shift[2 * i] = shift[2 * i + 1] = ' '; |
|
shift[2 * i] = shift[2 * i + 1] = 0; |
|
if (cur == NULL) { |
|
fprintf(output, "%s", shift); |
|
fprintf(output, "Node is NULL !\n"); |
|
return; |
|
|
|
} |
|
|
|
if ((cur->type == XML_DOCUMENT_NODE) || |
|
(cur->type == XML_HTML_DOCUMENT_NODE)) { |
|
fprintf(output, "%s", shift); |
|
fprintf(output, " /\n"); |
|
} else if (cur->type == XML_ATTRIBUTE_NODE) |
|
xmlDebugDumpAttr(output, (xmlAttrPtr)cur, depth); |
|
else |
|
xmlDebugDumpOneNode(output, cur, depth); |
|
} |
|
static void |
|
xmlXPathDebugDumpNodeList(FILE *output, xmlNodePtr cur, int depth) { |
|
xmlNodePtr tmp; |
|
int i; |
|
char shift[100]; |
|
|
|
for (i = 0;((i < depth) && (i < 25));i++) |
|
shift[2 * i] = shift[2 * i + 1] = ' '; |
|
shift[2 * i] = shift[2 * i + 1] = 0; |
|
if (cur == NULL) { |
|
fprintf(output, "%s", shift); |
|
fprintf(output, "Node is NULL !\n"); |
|
return; |
|
|
|
} |
|
|
|
while (cur != NULL) { |
|
tmp = cur; |
|
cur = cur->next; |
|
xmlDebugDumpOneNode(output, tmp, depth); |
|
} |
|
} |
|
|
|
static void |
|
xmlXPathDebugDumpNodeSet(FILE *output, xmlNodeSetPtr cur, int depth) { |
|
int i; |
|
char shift[100]; |
|
|
|
for (i = 0;((i < depth) && (i < 25));i++) |
|
shift[2 * i] = shift[2 * i + 1] = ' '; |
|
shift[2 * i] = shift[2 * i + 1] = 0; |
|
|
|
if (cur == NULL) { |
|
fprintf(output, "%s", shift); |
|
fprintf(output, "NodeSet is NULL !\n"); |
|
return; |
|
|
|
} |
|
|
|
if (cur != NULL) { |
|
fprintf(output, "Set contains %d nodes:\n", cur->nodeNr); |
|
for (i = 0;i < cur->nodeNr;i++) { |
|
fprintf(output, "%s", shift); |
|
fprintf(output, "%d", i + 1); |
|
xmlXPathDebugDumpNode(output, cur->nodeTab[i], depth + 1); |
|
} |
|
} |
|
} |
|
|
|
static void |
|
xmlXPathDebugDumpValueTree(FILE *output, xmlNodeSetPtr cur, int depth) { |
|
int i; |
|
char shift[100]; |
|
|
|
for (i = 0;((i < depth) && (i < 25));i++) |
|
shift[2 * i] = shift[2 * i + 1] = ' '; |
|
shift[2 * i] = shift[2 * i + 1] = 0; |
|
|
|
if ((cur == NULL) || (cur->nodeNr == 0) || (cur->nodeTab[0] == NULL)) { |
|
fprintf(output, "%s", shift); |
|
fprintf(output, "Value Tree is NULL !\n"); |
|
return; |
|
|
|
} |
|
|
|
fprintf(output, "%s", shift); |
|
fprintf(output, "%d", i + 1); |
|
xmlXPathDebugDumpNodeList(output, cur->nodeTab[0]->children, depth + 1); |
|
} |
|
#if defined(LIBXML_XPTR_ENABLED) |
|
static void |
|
xmlXPathDebugDumpLocationSet(FILE *output, xmlLocationSetPtr cur, int depth) { |
|
int i; |
|
char shift[100]; |
|
|
|
for (i = 0;((i < depth) && (i < 25));i++) |
|
shift[2 * i] = shift[2 * i + 1] = ' '; |
|
shift[2 * i] = shift[2 * i + 1] = 0; |
|
|
|
if (cur == NULL) { |
|
fprintf(output, "%s", shift); |
|
fprintf(output, "LocationSet is NULL !\n"); |
|
return; |
|
|
|
} |
|
|
|
for (i = 0;i < cur->locNr;i++) { |
|
fprintf(output, "%s", shift); |
|
fprintf(output, "%d : ", i + 1); |
|
xmlXPathDebugDumpObject(output, cur->locTab[i], depth + 1); |
|
} |
|
} |
|
#endif /* LIBXML_XPTR_ENABLED */ |
|
|
|
/** |
|
* xmlXPathDebugDumpObject: |
|
* @output: the FILE * to dump the output |
|
* @cur: the object to inspect |
|
* @depth: indentation level |
|
* |
|
* Dump the content of the object for debugging purposes |
|
*/ |
|
void |
|
xmlXPathDebugDumpObject(FILE *output, xmlXPathObjectPtr cur, int depth) { |
|
int i; |
|
char shift[100]; |
|
|
|
if (output == NULL) return; |
|
|
|
for (i = 0;((i < depth) && (i < 25));i++) |
|
shift[2 * i] = shift[2 * i + 1] = ' '; |
|
shift[2 * i] = shift[2 * i + 1] = 0; |
|
|
|
|
|
fprintf(output, "%s", shift); |
|
|
|
if (cur == NULL) { |
|
fprintf(output, "Object is empty (NULL)\n"); |
|
return; |
|
} |
|
switch(cur->type) { |
|
case XPATH_UNDEFINED: |
|
fprintf(output, "Object is uninitialized\n"); |
|
break; |
|
case XPATH_NODESET: |
|
fprintf(output, "Object is a Node Set :\n"); |
|
xmlXPathDebugDumpNodeSet(output, cur->nodesetval, depth); |
|
break; |
|
case XPATH_XSLT_TREE: |
|
fprintf(output, "Object is an XSLT value tree :\n"); |
|
xmlXPathDebugDumpValueTree(output, cur->nodesetval, depth); |
|
break; |
|
case XPATH_BOOLEAN: |
|
fprintf(output, "Object is a Boolean : "); |
|
if (cur->boolval) fprintf(output, "true\n"); |
|
else fprintf(output, "false\n"); |
|
break; |
|
case XPATH_NUMBER: |
|
switch (xmlXPathIsInf(cur->floatval)) { |
|
case 1: |
|
fprintf(output, "Object is a number : Infinity\n"); |
|
break; |
|
case -1: |
|
fprintf(output, "Object is a number : -Infinity\n"); |
|
break; |
|
default: |
|
if (xmlXPathIsNaN(cur->floatval)) { |
|
fprintf(output, "Object is a number : NaN\n"); |
|
} else if (cur->floatval == 0) { |
|
/* Omit sign for negative zero. */ |
|
fprintf(output, "Object is a number : 0\n"); |
|
} else { |
|
fprintf(output, "Object is a number : %0g\n", cur->floatval); |
|
} |
|
} |
|
break; |
|
case XPATH_STRING: |
|
fprintf(output, "Object is a string : "); |
|
xmlDebugDumpString(output, cur->stringval); |
|
fprintf(output, "\n"); |
|
break; |
|
case XPATH_POINT: |
|
fprintf(output, "Object is a point : index %d in node", cur->index); |
|
xmlXPathDebugDumpNode(output, (xmlNodePtr) cur->user, depth + 1); |
|
fprintf(output, "\n"); |
|
break; |
|
case XPATH_RANGE: |
|
if ((cur->user2 == NULL) || |
|
((cur->user2 == cur->user) && (cur->index == cur->index2))) { |
|
fprintf(output, "Object is a collapsed range :\n"); |
|
fprintf(output, "%s", shift); |
|
if (cur->index >= 0) |
|
fprintf(output, "index %d in ", cur->index); |
|
fprintf(output, "node\n"); |
|
xmlXPathDebugDumpNode(output, (xmlNodePtr) cur->user, |
|
depth + 1); |
|
} else { |
|
fprintf(output, "Object is a range :\n"); |
|
fprintf(output, "%s", shift); |
|
fprintf(output, "From "); |
|
if (cur->index >= 0) |
|
fprintf(output, "index %d in ", cur->index); |
|
fprintf(output, "node\n"); |
|
xmlXPathDebugDumpNode(output, (xmlNodePtr) cur->user, |
|
depth + 1); |
|
fprintf(output, "%s", shift); |
|
fprintf(output, "To "); |
|
if (cur->index2 >= 0) |
|
fprintf(output, "index %d in ", cur->index2); |
|
fprintf(output, "node\n"); |
|
xmlXPathDebugDumpNode(output, (xmlNodePtr) cur->user2, |
|
depth + 1); |
|
fprintf(output, "\n"); |
|
} |
|
break; |
|
case XPATH_LOCATIONSET: |
|
#if defined(LIBXML_XPTR_ENABLED) |
|
fprintf(output, "Object is a Location Set:\n"); |
|
xmlXPathDebugDumpLocationSet(output, |
|
(xmlLocationSetPtr) cur->user, depth); |
|
#endif |
|
break; |
|
case XPATH_USERS: |
|
fprintf(output, "Object is user defined\n"); |
|
break; |
|
} |
|
} |
|
|
|
static void |
|
xmlXPathDebugDumpStepOp(FILE *output, xmlXPathCompExprPtr comp, |
|
xmlXPathStepOpPtr op, int depth) { |
|
int i; |
|
char shift[100]; |
|
|
|
for (i = 0;((i < depth) && (i < 25));i++) |
|
shift[2 * i] = shift[2 * i + 1] = ' '; |
|
shift[2 * i] = shift[2 * i + 1] = 0; |
|
|
|
fprintf(output, "%s", shift); |
|
if (op == NULL) { |
|
fprintf(output, "Step is NULL\n"); |
|
return; |
|
} |
|
switch (op->op) { |
|
case XPATH_OP_END: |
|
fprintf(output, "END"); break; |
|
case XPATH_OP_AND: |
|
fprintf(output, "AND"); break; |
|
case XPATH_OP_OR: |
|
fprintf(output, "OR"); break; |
|
case XPATH_OP_EQUAL: |
|
if (op->value) |
|
fprintf(output, "EQUAL ="); |
|
else |
|
fprintf(output, "EQUAL !="); |
|
break; |
|
case XPATH_OP_CMP: |
|
if (op->value) |
|
fprintf(output, "CMP <"); |
|
else |
|
fprintf(output, "CMP >"); |
|
if (!op->value2) |
|
fprintf(output, "="); |
|
break; |
|
case XPATH_OP_PLUS: |
|
if (op->value == 0) |
|
fprintf(output, "PLUS -"); |
|
else if (op->value == 1) |
|
fprintf(output, "PLUS +"); |
|
else if (op->value == 2) |
|
fprintf(output, "PLUS unary -"); |
|
else if (op->value == 3) |
|
fprintf(output, "PLUS unary - -"); |
|
break; |
|
case XPATH_OP_MULT: |
|
if (op->value == 0) |
|
fprintf(output, "MULT *"); |
|
else if (op->value == 1) |
|
fprintf(output, "MULT div"); |
|
else |
|
fprintf(output, "MULT mod"); |
|
break; |
|
case XPATH_OP_UNION: |
|
fprintf(output, "UNION"); break; |
|
case XPATH_OP_ROOT: |
|
fprintf(output, "ROOT"); break; |
|
case XPATH_OP_NODE: |
|
fprintf(output, "NODE"); break; |
|
case XPATH_OP_RESET: |
|
fprintf(output, "RESET"); break; |
|
case XPATH_OP_SORT: |
|
fprintf(output, "SORT"); break; |
|
case XPATH_OP_COLLECT: { |
|
xmlXPathAxisVal axis = (xmlXPathAxisVal)op->value; |
|
xmlXPathTestVal test = (xmlXPathTestVal)op->value2; |
|
xmlXPathTypeVal type = (xmlXPathTypeVal)op->value3; |
|
const xmlChar *prefix = op->value4; |
|
const xmlChar *name = op->value5; |
|
|
|
fprintf(output, "COLLECT "); |
|
switch (axis) { |
|
case AXIS_ANCESTOR: |
|
fprintf(output, " 'ancestors' "); break; |
|
case AXIS_ANCESTOR_OR_SELF: |
|
fprintf(output, " 'ancestors-or-self' "); break; |
|
case AXIS_ATTRIBUTE: |
|
fprintf(output, " 'attributes' "); break; |
|
case AXIS_CHILD: |
|
fprintf(output, " 'child' "); break; |
|
case AXIS_DESCENDANT: |
|
fprintf(output, " 'descendant' "); break; |
|
case AXIS_DESCENDANT_OR_SELF: |
|
fprintf(output, " 'descendant-or-self' "); break; |
|
case AXIS_FOLLOWING: |
|
fprintf(output, " 'following' "); break; |
|
case AXIS_FOLLOWING_SIBLING: |
|
fprintf(output, " 'following-siblings' "); break; |
|
case AXIS_NAMESPACE: |
|
fprintf(output, " 'namespace' "); break; |
|
case AXIS_PARENT: |
|
fprintf(output, " 'parent' "); break; |
|
case AXIS_PRECEDING: |
|
fprintf(output, " 'preceding' "); break; |
|
case AXIS_PRECEDING_SIBLING: |
|
fprintf(output, " 'preceding-sibling' "); break; |
|
case AXIS_SELF: |
|
fprintf(output, " 'self' "); break; |
|
} |
|
switch (test) { |
|
case NODE_TEST_NONE: |
|
fprintf(output, "'none' "); break; |
|
case NODE_TEST_TYPE: |
|
fprintf(output, "'type' "); break; |
|
case NODE_TEST_PI: |
|
fprintf(output, "'PI' "); break; |
|
case NODE_TEST_ALL: |
|
fprintf(output, "'all' "); break; |
|
case NODE_TEST_NS: |
|
fprintf(output, "'namespace' "); break; |
|
case NODE_TEST_NAME: |
|
fprintf(output, "'name' "); break; |
|
} |
|
switch (type) { |
|
case NODE_TYPE_NODE: |
|
fprintf(output, "'node' "); break; |
|
case NODE_TYPE_COMMENT: |
|
fprintf(output, "'comment' "); break; |
|
case NODE_TYPE_TEXT: |
|
fprintf(output, "'text' "); break; |
|
case NODE_TYPE_PI: |
|
fprintf(output, "'PI' "); break; |
|
} |
|
if (prefix != NULL) |
|
fprintf(output, "%s:", prefix); |
|
if (name != NULL) |
|
fprintf(output, "%s", (const char *) name); |
|
break; |
|
|
|
} |
|
case XPATH_OP_VALUE: { |
|
xmlXPathObjectPtr object = (xmlXPathObjectPtr) op->value4; |
|
|
|
fprintf(output, "ELEM "); |
|
xmlXPathDebugDumpObject(output, object, 0); |
|
goto finish; |
|
} |
|
case XPATH_OP_VARIABLE: { |
|
const xmlChar *prefix = op->value5; |
|
const xmlChar *name = op->value4; |
|
|
|
if (prefix != NULL) |
|
fprintf(output, "VARIABLE %s:%s", prefix, name); |
|
else |
|
fprintf(output, "VARIABLE %s", name); |
|
break; |
|
} |
|
case XPATH_OP_FUNCTION: { |
|
int nbargs = op->value; |
|
const xmlChar *prefix = op->value5; |
|
const xmlChar *name = op->value4; |
|
|
|
if (prefix != NULL) |
|
fprintf(output, "FUNCTION %s:%s(%d args)", |
|
prefix, name, nbargs); |
|
else |
|
fprintf(output, "FUNCTION %s(%d args)", name, nbargs); |
|
break; |
|
} |
|
case XPATH_OP_ARG: fprintf(output, "ARG"); break; |
|
case XPATH_OP_PREDICATE: fprintf(output, "PREDICATE"); break; |
|
case XPATH_OP_FILTER: fprintf(output, "FILTER"); break; |
|
#ifdef LIBXML_XPTR_ENABLED |
|
case XPATH_OP_RANGETO: fprintf(output, "RANGETO"); break; |
|
#endif |
|
default: |
|
fprintf(output, "UNKNOWN %d\n", op->op); return; |
|
} |
|
fprintf(output, "\n"); |
|
finish: |
|
if (op->ch1 >= 0) |
|
xmlXPathDebugDumpStepOp(output, comp, &comp->steps[op->ch1], depth + 1); |
|
if (op->ch2 >= 0) |
|
xmlXPathDebugDumpStepOp(output, comp, &comp->steps[op->ch2], depth + 1); |
|
} |
|
|
|
/** |
|
* xmlXPathDebugDumpCompExpr: |
|
* @output: the FILE * for the output |
|
* @comp: the precompiled XPath expression |
|
* @depth: the indentation level. |
|
* |
|
* Dumps the tree of the compiled XPath expression. |
|
*/ |
|
void |
|
xmlXPathDebugDumpCompExpr(FILE *output, xmlXPathCompExprPtr comp, |
|
int depth) { |
|
int i; |
|
char shift[100]; |
|
|
|
if ((output == NULL) || (comp == NULL)) return; |
|
|
|
for (i = 0;((i < depth) && (i < 25));i++) |
|
shift[2 * i] = shift[2 * i + 1] = ' '; |
|
shift[2 * i] = shift[2 * i + 1] = 0; |
|
|
|
fprintf(output, "%s", shift); |
|
|
|
#ifdef XPATH_STREAMING |
|
if (comp->stream) { |
|
fprintf(output, "Streaming Expression\n"); |
|
} else |
|
#endif |
|
{ |
|
fprintf(output, "Compiled Expression : %d elements\n", |
|
comp->nbStep); |
|
i = comp->last; |
|
xmlXPathDebugDumpStepOp(output, comp, &comp->steps[i], depth + 1); |
|
} |
|
} |
|
|
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
|
|
/* |
|
* XPath object usage related debugging variables. |
|
*/ |
|
static int xmlXPathDebugObjCounterUndefined = 0; |
|
static int xmlXPathDebugObjCounterNodeset = 0; |
|
static int xmlXPathDebugObjCounterBool = 0; |
|
static int xmlXPathDebugObjCounterNumber = 0; |
|
static int xmlXPathDebugObjCounterString = 0; |
|
static int xmlXPathDebugObjCounterPoint = 0; |
|
static int xmlXPathDebugObjCounterRange = 0; |
|
static int xmlXPathDebugObjCounterLocset = 0; |
|
static int xmlXPathDebugObjCounterUsers = 0; |
|
static int xmlXPathDebugObjCounterXSLTTree = 0; |
|
static int xmlXPathDebugObjCounterAll = 0; |
|
|
|
static int xmlXPathDebugObjTotalUndefined = 0; |
|
static int xmlXPathDebugObjTotalNodeset = 0; |
|
static int xmlXPathDebugObjTotalBool = 0; |
|
static int xmlXPathDebugObjTotalNumber = 0; |
|
static int xmlXPathDebugObjTotalString = 0; |
|
static int xmlXPathDebugObjTotalPoint = 0; |
|
static int xmlXPathDebugObjTotalRange = 0; |
|
static int xmlXPathDebugObjTotalLocset = 0; |
|
static int xmlXPathDebugObjTotalUsers = 0; |
|
static int xmlXPathDebugObjTotalXSLTTree = 0; |
|
static int xmlXPathDebugObjTotalAll = 0; |
|
|
|
static int xmlXPathDebugObjMaxUndefined = 0; |
|
static int xmlXPathDebugObjMaxNodeset = 0; |
|
static int xmlXPathDebugObjMaxBool = 0; |
|
static int xmlXPathDebugObjMaxNumber = 0; |
|
static int xmlXPathDebugObjMaxString = 0; |
|
static int xmlXPathDebugObjMaxPoint = 0; |
|
static int xmlXPathDebugObjMaxRange = 0; |
|
static int xmlXPathDebugObjMaxLocset = 0; |
|
static int xmlXPathDebugObjMaxUsers = 0; |
|
static int xmlXPathDebugObjMaxXSLTTree = 0; |
|
static int xmlXPathDebugObjMaxAll = 0; |
|
|
|
/* REVISIT TODO: Make this static when committing */ |
|
static void |
|
xmlXPathDebugObjUsageReset(xmlXPathContextPtr ctxt) |
|
{ |
|
if (ctxt != NULL) { |
|
if (ctxt->cache != NULL) { |
|
xmlXPathContextCachePtr cache = |
|
(xmlXPathContextCachePtr) ctxt->cache; |
|
|
|
cache->dbgCachedAll = 0; |
|
cache->dbgCachedNodeset = 0; |
|
cache->dbgCachedString = 0; |
|
cache->dbgCachedBool = 0; |
|
cache->dbgCachedNumber = 0; |
|
cache->dbgCachedPoint = 0; |
|
cache->dbgCachedRange = 0; |
|
cache->dbgCachedLocset = 0; |
|
cache->dbgCachedUsers = 0; |
|
cache->dbgCachedXSLTTree = 0; |
|
cache->dbgCachedUndefined = 0; |
|
|
|
cache->dbgReusedAll = 0; |
|
cache->dbgReusedNodeset = 0; |
|
cache->dbgReusedString = 0; |
|
cache->dbgReusedBool = 0; |
|
cache->dbgReusedNumber = 0; |
|
cache->dbgReusedPoint = 0; |
|
cache->dbgReusedRange = 0; |
|
cache->dbgReusedLocset = 0; |
|
cache->dbgReusedUsers = 0; |
|
cache->dbgReusedXSLTTree = 0; |
|
cache->dbgReusedUndefined = 0; |
|
} |
|
} |
|
|
|
xmlXPathDebugObjCounterUndefined = 0; |
|
xmlXPathDebugObjCounterNodeset = 0; |
|
xmlXPathDebugObjCounterBool = 0; |
|
xmlXPathDebugObjCounterNumber = 0; |
|
xmlXPathDebugObjCounterString = 0; |
|
xmlXPathDebugObjCounterPoint = 0; |
|
xmlXPathDebugObjCounterRange = 0; |
|
xmlXPathDebugObjCounterLocset = 0; |
|
xmlXPathDebugObjCounterUsers = 0; |
|
xmlXPathDebugObjCounterXSLTTree = 0; |
|
xmlXPathDebugObjCounterAll = 0; |
|
|
|
xmlXPathDebugObjTotalUndefined = 0; |
|
xmlXPathDebugObjTotalNodeset = 0; |
|
xmlXPathDebugObjTotalBool = 0; |
|
xmlXPathDebugObjTotalNumber = 0; |
|
xmlXPathDebugObjTotalString = 0; |
|
xmlXPathDebugObjTotalPoint = 0; |
|
xmlXPathDebugObjTotalRange = 0; |
|
xmlXPathDebugObjTotalLocset = 0; |
|
xmlXPathDebugObjTotalUsers = 0; |
|
xmlXPathDebugObjTotalXSLTTree = 0; |
|
xmlXPathDebugObjTotalAll = 0; |
|
|
|
xmlXPathDebugObjMaxUndefined = 0; |
|
xmlXPathDebugObjMaxNodeset = 0; |
|
xmlXPathDebugObjMaxBool = 0; |
|
xmlXPathDebugObjMaxNumber = 0; |
|
xmlXPathDebugObjMaxString = 0; |
|
xmlXPathDebugObjMaxPoint = 0; |
|
xmlXPathDebugObjMaxRange = 0; |
|
xmlXPathDebugObjMaxLocset = 0; |
|
xmlXPathDebugObjMaxUsers = 0; |
|
xmlXPathDebugObjMaxXSLTTree = 0; |
|
xmlXPathDebugObjMaxAll = 0; |
|
|
|
} |
|
|
|
static void |
|
xmlXPathDebugObjUsageRequested(xmlXPathContextPtr ctxt, |
|
xmlXPathObjectType objType) |
|
{ |
|
int isCached = 0; |
|
|
|
if (ctxt != NULL) { |
|
if (ctxt->cache != NULL) { |
|
xmlXPathContextCachePtr cache = |
|
(xmlXPathContextCachePtr) ctxt->cache; |
|
|
|
isCached = 1; |
|
|
|
cache->dbgReusedAll++; |
|
switch (objType) { |
|
case XPATH_UNDEFINED: |
|
cache->dbgReusedUndefined++; |
|
break; |
|
case XPATH_NODESET: |
|
cache->dbgReusedNodeset++; |
|
break; |
|
case XPATH_BOOLEAN: |
|
cache->dbgReusedBool++; |
|
break; |
|
case XPATH_NUMBER: |
|
cache->dbgReusedNumber++; |
|
break; |
|
case XPATH_STRING: |
|
cache->dbgReusedString++; |
|
break; |
|
case XPATH_POINT: |
|
cache->dbgReusedPoint++; |
|
break; |
|
case XPATH_RANGE: |
|
cache->dbgReusedRange++; |
|
break; |
|
case XPATH_LOCATIONSET: |
|
cache->dbgReusedLocset++; |
|
break; |
|
case XPATH_USERS: |
|
cache->dbgReusedUsers++; |
|
break; |
|
case XPATH_XSLT_TREE: |
|
cache->dbgReusedXSLTTree++; |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
} |
|
|
|
switch (objType) { |
|
case XPATH_UNDEFINED: |
|
if (! isCached) |
|
xmlXPathDebugObjTotalUndefined++; |
|
xmlXPathDebugObjCounterUndefined++; |
|
if (xmlXPathDebugObjCounterUndefined > |
|
xmlXPathDebugObjMaxUndefined) |
|
xmlXPathDebugObjMaxUndefined = |
|
xmlXPathDebugObjCounterUndefined; |
|
break; |
|
case XPATH_NODESET: |
|
if (! isCached) |
|
xmlXPathDebugObjTotalNodeset++; |
|
xmlXPathDebugObjCounterNodeset++; |
|
if (xmlXPathDebugObjCounterNodeset > |
|
xmlXPathDebugObjMaxNodeset) |
|
xmlXPathDebugObjMaxNodeset = |
|
xmlXPathDebugObjCounterNodeset; |
|
break; |
|
case XPATH_BOOLEAN: |
|
if (! isCached) |
|
xmlXPathDebugObjTotalBool++; |
|
xmlXPathDebugObjCounterBool++; |
|
if (xmlXPathDebugObjCounterBool > |
|
xmlXPathDebugObjMaxBool) |
|
xmlXPathDebugObjMaxBool = |
|
xmlXPathDebugObjCounterBool; |
|
break; |
|
case XPATH_NUMBER: |
|
if (! isCached) |
|
xmlXPathDebugObjTotalNumber++; |
|
xmlXPathDebugObjCounterNumber++; |
|
if (xmlXPathDebugObjCounterNumber > |
|
xmlXPathDebugObjMaxNumber) |
|
xmlXPathDebugObjMaxNumber = |
|
xmlXPathDebugObjCounterNumber; |
|
break; |
|
case XPATH_STRING: |
|
if (! isCached) |
|
xmlXPathDebugObjTotalString++; |
|
xmlXPathDebugObjCounterString++; |
|
if (xmlXPathDebugObjCounterString > |
|
xmlXPathDebugObjMaxString) |
|
xmlXPathDebugObjMaxString = |
|
xmlXPathDebugObjCounterString; |
|
break; |
|
case XPATH_POINT: |
|
if (! isCached) |
|
xmlXPathDebugObjTotalPoint++; |
|
xmlXPathDebugObjCounterPoint++; |
|
if (xmlXPathDebugObjCounterPoint > |
|
xmlXPathDebugObjMaxPoint) |
|
xmlXPathDebugObjMaxPoint = |
|
xmlXPathDebugObjCounterPoint; |
|
break; |
|
case XPATH_RANGE: |
|
if (! isCached) |
|
xmlXPathDebugObjTotalRange++; |
|
xmlXPathDebugObjCounterRange++; |
|
if (xmlXPathDebugObjCounterRange > |
|
xmlXPathDebugObjMaxRange) |
|
xmlXPathDebugObjMaxRange = |
|
xmlXPathDebugObjCounterRange; |
|
break; |
|
case XPATH_LOCATIONSET: |
|
if (! isCached) |
|
xmlXPathDebugObjTotalLocset++; |
|
xmlXPathDebugObjCounterLocset++; |
|
if (xmlXPathDebugObjCounterLocset > |
|
xmlXPathDebugObjMaxLocset) |
|
xmlXPathDebugObjMaxLocset = |
|
xmlXPathDebugObjCounterLocset; |
|
break; |
|
case XPATH_USERS: |
|
if (! isCached) |
|
xmlXPathDebugObjTotalUsers++; |
|
xmlXPathDebugObjCounterUsers++; |
|
if (xmlXPathDebugObjCounterUsers > |
|
xmlXPathDebugObjMaxUsers) |
|
xmlXPathDebugObjMaxUsers = |
|
xmlXPathDebugObjCounterUsers; |
|
break; |
|
case XPATH_XSLT_TREE: |
|
if (! isCached) |
|
xmlXPathDebugObjTotalXSLTTree++; |
|
xmlXPathDebugObjCounterXSLTTree++; |
|
if (xmlXPathDebugObjCounterXSLTTree > |
|
xmlXPathDebugObjMaxXSLTTree) |
|
xmlXPathDebugObjMaxXSLTTree = |
|
xmlXPathDebugObjCounterXSLTTree; |
|
break; |
|
default: |
|
break; |
|
} |
|
if (! isCached) |
|
xmlXPathDebugObjTotalAll++; |
|
xmlXPathDebugObjCounterAll++; |
|
if (xmlXPathDebugObjCounterAll > |
|
xmlXPathDebugObjMaxAll) |
|
xmlXPathDebugObjMaxAll = |
|
xmlXPathDebugObjCounterAll; |
|
} |
|
|
|
static void |
|
xmlXPathDebugObjUsageReleased(xmlXPathContextPtr ctxt, |
|
xmlXPathObjectType objType) |
|
{ |
|
int isCached = 0; |
|
|
|
if (ctxt != NULL) { |
|
if (ctxt->cache != NULL) { |
|
xmlXPathContextCachePtr cache = |
|
(xmlXPathContextCachePtr) ctxt->cache; |
|
|
|
isCached = 1; |
|
|
|
cache->dbgCachedAll++; |
|
switch (objType) { |
|
case XPATH_UNDEFINED: |
|
cache->dbgCachedUndefined++; |
|
break; |
|
case XPATH_NODESET: |
|
cache->dbgCachedNodeset++; |
|
break; |
|
case XPATH_BOOLEAN: |
|
cache->dbgCachedBool++; |
|
break; |
|
case XPATH_NUMBER: |
|
cache->dbgCachedNumber++; |
|
break; |
|
case XPATH_STRING: |
|
cache->dbgCachedString++; |
|
break; |
|
case XPATH_POINT: |
|
cache->dbgCachedPoint++; |
|
break; |
|
case XPATH_RANGE: |
|
cache->dbgCachedRange++; |
|
break; |
|
case XPATH_LOCATIONSET: |
|
cache->dbgCachedLocset++; |
|
break; |
|
case XPATH_USERS: |
|
cache->dbgCachedUsers++; |
|
break; |
|
case XPATH_XSLT_TREE: |
|
cache->dbgCachedXSLTTree++; |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
} |
|
} |
|
switch (objType) { |
|
case XPATH_UNDEFINED: |
|
xmlXPathDebugObjCounterUndefined--; |
|
break; |
|
case XPATH_NODESET: |
|
xmlXPathDebugObjCounterNodeset--; |
|
break; |
|
case XPATH_BOOLEAN: |
|
xmlXPathDebugObjCounterBool--; |
|
break; |
|
case XPATH_NUMBER: |
|
xmlXPathDebugObjCounterNumber--; |
|
break; |
|
case XPATH_STRING: |
|
xmlXPathDebugObjCounterString--; |
|
break; |
|
case XPATH_POINT: |
|
xmlXPathDebugObjCounterPoint--; |
|
break; |
|
case XPATH_RANGE: |
|
xmlXPathDebugObjCounterRange--; |
|
break; |
|
case XPATH_LOCATIONSET: |
|
xmlXPathDebugObjCounterLocset--; |
|
break; |
|
case XPATH_USERS: |
|
xmlXPathDebugObjCounterUsers--; |
|
break; |
|
case XPATH_XSLT_TREE: |
|
xmlXPathDebugObjCounterXSLTTree--; |
|
break; |
|
default: |
|
break; |
|
} |
|
xmlXPathDebugObjCounterAll--; |
|
} |
|
|
|
/* REVISIT TODO: Make this static when committing */ |
|
static void |
|
xmlXPathDebugObjUsageDisplay(xmlXPathContextPtr ctxt) |
|
{ |
|
int reqAll, reqNodeset, reqString, reqBool, reqNumber, |
|
reqXSLTTree, reqUndefined; |
|
int caAll = 0, caNodeset = 0, caString = 0, caBool = 0, |
|
caNumber = 0, caXSLTTree = 0, caUndefined = 0; |
|
int reAll = 0, reNodeset = 0, reString = 0, reBool = 0, |
|
reNumber = 0, reXSLTTree = 0, reUndefined = 0; |
|
int leftObjs = xmlXPathDebugObjCounterAll; |
|
|
|
reqAll = xmlXPathDebugObjTotalAll; |
|
reqNodeset = xmlXPathDebugObjTotalNodeset; |
|
reqString = xmlXPathDebugObjTotalString; |
|
reqBool = xmlXPathDebugObjTotalBool; |
|
reqNumber = xmlXPathDebugObjTotalNumber; |
|
reqXSLTTree = xmlXPathDebugObjTotalXSLTTree; |
|
reqUndefined = xmlXPathDebugObjTotalUndefined; |
|
|
|
printf("# XPath object usage:\n"); |
|
|
|
if (ctxt != NULL) { |
|
if (ctxt->cache != NULL) { |
|
xmlXPathContextCachePtr cache = |
|
(xmlXPathContextCachePtr) ctxt->cache; |
|
|
|
reAll = cache->dbgReusedAll; |
|
reqAll += reAll; |
|
reNodeset = cache->dbgReusedNodeset; |
|
reqNodeset += reNodeset; |
|
reString = cache->dbgReusedString; |
|
reqString += reString; |
|
reBool = cache->dbgReusedBool; |
|
reqBool += reBool; |
|
reNumber = cache->dbgReusedNumber; |
|
reqNumber += reNumber; |
|
reXSLTTree = cache->dbgReusedXSLTTree; |
|
reqXSLTTree += reXSLTTree; |
|
reUndefined = cache->dbgReusedUndefined; |
|
reqUndefined += reUndefined; |
|
|
|
caAll = cache->dbgCachedAll; |
|
caBool = cache->dbgCachedBool; |
|
caNodeset = cache->dbgCachedNodeset; |
|
caString = cache->dbgCachedString; |
|
caNumber = cache->dbgCachedNumber; |
|
caXSLTTree = cache->dbgCachedXSLTTree; |
|
caUndefined = cache->dbgCachedUndefined; |
|
|
|
if (cache->nodesetObjs) |
|
leftObjs -= cache->nodesetObjs->number; |
|
if (cache->stringObjs) |
|
leftObjs -= cache->stringObjs->number; |
|
if (cache->booleanObjs) |
|
leftObjs -= cache->booleanObjs->number; |
|
if (cache->numberObjs) |
|
leftObjs -= cache->numberObjs->number; |
|
if (cache->miscObjs) |
|
leftObjs -= cache->miscObjs->number; |
|
} |
|
} |
|
|
|
printf("# all\n"); |
|
printf("# total : %d\n", reqAll); |
|
printf("# left : %d\n", leftObjs); |
|
printf("# created: %d\n", xmlXPathDebugObjTotalAll); |
|
printf("# reused : %d\n", reAll); |
|
printf("# max : %d\n", xmlXPathDebugObjMaxAll); |
|
|
|
printf("# node-sets\n"); |
|
printf("# total : %d\n", reqNodeset); |
|
printf("# created: %d\n", xmlXPathDebugObjTotalNodeset); |
|
printf("# reused : %d\n", reNodeset); |
|
printf("# max : %d\n", xmlXPathDebugObjMaxNodeset); |
|
|
|
printf("# strings\n"); |
|
printf("# total : %d\n", reqString); |
|
printf("# created: %d\n", xmlXPathDebugObjTotalString); |
|
printf("# reused : %d\n", reString); |
|
printf("# max : %d\n", xmlXPathDebugObjMaxString); |
|
|
|
printf("# booleans\n"); |
|
printf("# total : %d\n", reqBool); |
|
printf("# created: %d\n", xmlXPathDebugObjTotalBool); |
|
printf("# reused : %d\n", reBool); |
|
printf("# max : %d\n", xmlXPathDebugObjMaxBool); |
|
|
|
printf("# numbers\n"); |
|
printf("# total : %d\n", reqNumber); |
|
printf("# created: %d\n", xmlXPathDebugObjTotalNumber); |
|
printf("# reused : %d\n", reNumber); |
|
printf("# max : %d\n", xmlXPathDebugObjMaxNumber); |
|
|
|
printf("# XSLT result tree fragments\n"); |
|
printf("# total : %d\n", reqXSLTTree); |
|
printf("# created: %d\n", xmlXPathDebugObjTotalXSLTTree); |
|
printf("# reused : %d\n", reXSLTTree); |
|
printf("# max : %d\n", xmlXPathDebugObjMaxXSLTTree); |
|
|
|
printf("# undefined\n"); |
|
printf("# total : %d\n", reqUndefined); |
|
printf("# created: %d\n", xmlXPathDebugObjTotalUndefined); |
|
printf("# reused : %d\n", reUndefined); |
|
printf("# max : %d\n", xmlXPathDebugObjMaxUndefined); |
|
|
|
} |
|
|
|
#endif /* XP_DEBUG_OBJ_USAGE */ |
|
|
|
#endif /* LIBXML_DEBUG_ENABLED */ |
|
|
|
/************************************************************************ |
|
* * |
|
* XPath object caching * |
|
* * |
|
************************************************************************/ |
|
|
|
/** |
|
* xmlXPathNewCache: |
|
* |
|
* Create a new object cache |
|
* |
|
* Returns the xmlXPathCache just allocated. |
|
*/ |
|
static xmlXPathContextCachePtr |
|
xmlXPathNewCache(void) |
|
{ |
|
xmlXPathContextCachePtr ret; |
|
|
|
ret = (xmlXPathContextCachePtr) xmlMalloc(sizeof(xmlXPathContextCache)); |
|
if (ret == NULL) { |
|
xmlXPathErrMemory(NULL, "creating object cache\n"); |
|
return(NULL); |
|
} |
|
memset(ret, 0 , (size_t) sizeof(xmlXPathContextCache)); |
|
ret->maxNodeset = 100; |
|
ret->maxString = 100; |
|
ret->maxBoolean = 100; |
|
ret->maxNumber = 100; |
|
ret->maxMisc = 100; |
|
return(ret); |
|
} |
|
|
|
static void |
|
xmlXPathCacheFreeObjectList(xmlPointerListPtr list) |
|
{ |
|
int i; |
|
xmlXPathObjectPtr obj; |
|
|
|
if (list == NULL) |
|
return; |
|
|
|
for (i = 0; i < list->number; i++) { |
|
obj = list->items[i]; |
|
/* |
|
* Note that it is already assured that we don't need to |
|
* look out for namespace nodes in the node-set. |
|
*/ |
|
if (obj->nodesetval != NULL) { |
|
if (obj->nodesetval->nodeTab != NULL) |
|
xmlFree(obj->nodesetval->nodeTab); |
|
xmlFree(obj->nodesetval); |
|
} |
|
xmlFree(obj); |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjCounterAll--; |
|
#endif |
|
} |
|
xmlPointerListFree(list); |
|
} |
|
|
|
static void |
|
xmlXPathFreeCache(xmlXPathContextCachePtr cache) |
|
{ |
|
if (cache == NULL) |
|
return; |
|
if (cache->nodesetObjs) |
|
xmlXPathCacheFreeObjectList(cache->nodesetObjs); |
|
if (cache->stringObjs) |
|
xmlXPathCacheFreeObjectList(cache->stringObjs); |
|
if (cache->booleanObjs) |
|
xmlXPathCacheFreeObjectList(cache->booleanObjs); |
|
if (cache->numberObjs) |
|
xmlXPathCacheFreeObjectList(cache->numberObjs); |
|
if (cache->miscObjs) |
|
xmlXPathCacheFreeObjectList(cache->miscObjs); |
|
xmlFree(cache); |
|
} |
|
|
|
/** |
|
* xmlXPathContextSetCache: |
|
* |
|
* @ctxt: the XPath context |
|
* @active: enables/disables (creates/frees) the cache |
|
* @value: a value with semantics dependant on @options |
|
* @options: options (currently only the value 0 is used) |
|
* |
|
* Creates/frees an object cache on the XPath context. |
|
* If activates XPath objects (xmlXPathObject) will be cached internally |
|
* to be reused. |
|
* @options: |
|
* 0: This will set the XPath object caching: |
|
* @value: |
|
* This will set the maximum number of XPath objects |
|
* to be cached per slot |
|
* There are 5 slots for: node-set, string, number, boolean, and |
|
* misc objects. Use <0 for the default number (100). |
|
* Other values for @options have currently no effect. |
|
* |
|
* Returns 0 if the setting succeeded, and -1 on API or internal errors. |
|
*/ |
|
int |
|
xmlXPathContextSetCache(xmlXPathContextPtr ctxt, |
|
int active, |
|
int value, |
|
int options) |
|
{ |
|
if (ctxt == NULL) |
|
return(-1); |
|
if (active) { |
|
xmlXPathContextCachePtr cache; |
|
|
|
if (ctxt->cache == NULL) { |
|
ctxt->cache = xmlXPathNewCache(); |
|
if (ctxt->cache == NULL) |
|
return(-1); |
|
} |
|
cache = (xmlXPathContextCachePtr) ctxt->cache; |
|
if (options == 0) { |
|
if (value < 0) |
|
value = 100; |
|
cache->maxNodeset = value; |
|
cache->maxString = value; |
|
cache->maxNumber = value; |
|
cache->maxBoolean = value; |
|
cache->maxMisc = value; |
|
} |
|
} else if (ctxt->cache != NULL) { |
|
xmlXPathFreeCache((xmlXPathContextCachePtr) ctxt->cache); |
|
ctxt->cache = NULL; |
|
} |
|
return(0); |
|
} |
|
|
|
/** |
|
* xmlXPathCacheWrapNodeSet: |
|
* @ctxt: the XPath context |
|
* @val: the NodePtr value |
|
* |
|
* This is the cached version of xmlXPathWrapNodeSet(). |
|
* Wrap the Nodeset @val in a new xmlXPathObjectPtr |
|
* |
|
* Returns the created or reused object. |
|
*/ |
|
static xmlXPathObjectPtr |
|
xmlXPathCacheWrapNodeSet(xmlXPathContextPtr ctxt, xmlNodeSetPtr val) |
|
{ |
|
if ((ctxt != NULL) && (ctxt->cache != NULL)) { |
|
xmlXPathContextCachePtr cache = |
|
(xmlXPathContextCachePtr) ctxt->cache; |
|
|
|
if ((cache->miscObjs != NULL) && |
|
(cache->miscObjs->number != 0)) |
|
{ |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) |
|
cache->miscObjs->items[--cache->miscObjs->number]; |
|
ret->type = XPATH_NODESET; |
|
ret->nodesetval = val; |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(ctxt, XPATH_NODESET); |
|
#endif |
|
return(ret); |
|
} |
|
} |
|
|
|
return(xmlXPathWrapNodeSet(val)); |
|
|
|
} |
|
|
|
/** |
|
* xmlXPathCacheWrapString: |
|
* @ctxt: the XPath context |
|
* @val: the xmlChar * value |
|
* |
|
* This is the cached version of xmlXPathWrapString(). |
|
* Wraps the @val string into an XPath object. |
|
* |
|
* Returns the created or reused object. |
|
*/ |
|
static xmlXPathObjectPtr |
|
xmlXPathCacheWrapString(xmlXPathContextPtr ctxt, xmlChar *val) |
|
{ |
|
if ((ctxt != NULL) && (ctxt->cache != NULL)) { |
|
xmlXPathContextCachePtr cache = (xmlXPathContextCachePtr) ctxt->cache; |
|
|
|
if ((cache->stringObjs != NULL) && |
|
(cache->stringObjs->number != 0)) |
|
{ |
|
|
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) |
|
cache->stringObjs->items[--cache->stringObjs->number]; |
|
ret->type = XPATH_STRING; |
|
ret->stringval = val; |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(ctxt, XPATH_STRING); |
|
#endif |
|
return(ret); |
|
} else if ((cache->miscObjs != NULL) && |
|
(cache->miscObjs->number != 0)) |
|
{ |
|
xmlXPathObjectPtr ret; |
|
/* |
|
* Fallback to misc-cache. |
|
*/ |
|
ret = (xmlXPathObjectPtr) |
|
cache->miscObjs->items[--cache->miscObjs->number]; |
|
|
|
ret->type = XPATH_STRING; |
|
ret->stringval = val; |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(ctxt, XPATH_STRING); |
|
#endif |
|
return(ret); |
|
} |
|
} |
|
return(xmlXPathWrapString(val)); |
|
} |
|
|
|
/** |
|
* xmlXPathCacheNewNodeSet: |
|
* @ctxt: the XPath context |
|
* @val: the NodePtr value |
|
* |
|
* This is the cached version of xmlXPathNewNodeSet(). |
|
* Acquire an xmlXPathObjectPtr of type NodeSet and initialize |
|
* it with the single Node @val |
|
* |
|
* Returns the created or reused object. |
|
*/ |
|
static xmlXPathObjectPtr |
|
xmlXPathCacheNewNodeSet(xmlXPathContextPtr ctxt, xmlNodePtr val) |
|
{ |
|
if ((ctxt != NULL) && (ctxt->cache)) { |
|
xmlXPathContextCachePtr cache = (xmlXPathContextCachePtr) ctxt->cache; |
|
|
|
if ((cache->nodesetObjs != NULL) && |
|
(cache->nodesetObjs->number != 0)) |
|
{ |
|
xmlXPathObjectPtr ret; |
|
/* |
|
* Use the nodset-cache. |
|
*/ |
|
ret = (xmlXPathObjectPtr) |
|
cache->nodesetObjs->items[--cache->nodesetObjs->number]; |
|
ret->type = XPATH_NODESET; |
|
ret->boolval = 0; |
|
if (val) { |
|
if ((ret->nodesetval->nodeMax == 0) || |
|
(val->type == XML_NAMESPACE_DECL)) |
|
{ |
|
xmlXPathNodeSetAddUnique(ret->nodesetval, val); |
|
} else { |
|
ret->nodesetval->nodeTab[0] = val; |
|
ret->nodesetval->nodeNr = 1; |
|
} |
|
} |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(ctxt, XPATH_NODESET); |
|
#endif |
|
return(ret); |
|
} else if ((cache->miscObjs != NULL) && |
|
(cache->miscObjs->number != 0)) |
|
{ |
|
xmlXPathObjectPtr ret; |
|
/* |
|
* Fallback to misc-cache. |
|
*/ |
|
|
|
ret = (xmlXPathObjectPtr) |
|
cache->miscObjs->items[--cache->miscObjs->number]; |
|
|
|
ret->type = XPATH_NODESET; |
|
ret->boolval = 0; |
|
ret->nodesetval = xmlXPathNodeSetCreate(val); |
|
if (ret->nodesetval == NULL) { |
|
ctxt->lastError.domain = XML_FROM_XPATH; |
|
ctxt->lastError.code = XML_ERR_NO_MEMORY; |
|
return(NULL); |
|
} |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(ctxt, XPATH_NODESET); |
|
#endif |
|
return(ret); |
|
} |
|
} |
|
return(xmlXPathNewNodeSet(val)); |
|
} |
|
|
|
/** |
|
* xmlXPathCacheNewCString: |
|
* @ctxt: the XPath context |
|
* @val: the char * value |
|
* |
|
* This is the cached version of xmlXPathNewCString(). |
|
* Acquire an xmlXPathObjectPtr of type string and of value @val |
|
* |
|
* Returns the created or reused object. |
|
*/ |
|
static xmlXPathObjectPtr |
|
xmlXPathCacheNewCString(xmlXPathContextPtr ctxt, const char *val) |
|
{ |
|
if ((ctxt != NULL) && (ctxt->cache)) { |
|
xmlXPathContextCachePtr cache = (xmlXPathContextCachePtr) ctxt->cache; |
|
|
|
if ((cache->stringObjs != NULL) && |
|
(cache->stringObjs->number != 0)) |
|
{ |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) |
|
cache->stringObjs->items[--cache->stringObjs->number]; |
|
|
|
ret->type = XPATH_STRING; |
|
ret->stringval = xmlStrdup(BAD_CAST val); |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(ctxt, XPATH_STRING); |
|
#endif |
|
return(ret); |
|
} else if ((cache->miscObjs != NULL) && |
|
(cache->miscObjs->number != 0)) |
|
{ |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) |
|
cache->miscObjs->items[--cache->miscObjs->number]; |
|
|
|
ret->type = XPATH_STRING; |
|
ret->stringval = xmlStrdup(BAD_CAST val); |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(ctxt, XPATH_STRING); |
|
#endif |
|
return(ret); |
|
} |
|
} |
|
return(xmlXPathNewCString(val)); |
|
} |
|
|
|
/** |
|
* xmlXPathCacheNewString: |
|
* @ctxt: the XPath context |
|
* @val: the xmlChar * value |
|
* |
|
* This is the cached version of xmlXPathNewString(). |
|
* Acquire an xmlXPathObjectPtr of type string and of value @val |
|
* |
|
* Returns the created or reused object. |
|
*/ |
|
static xmlXPathObjectPtr |
|
xmlXPathCacheNewString(xmlXPathContextPtr ctxt, const xmlChar *val) |
|
{ |
|
if ((ctxt != NULL) && (ctxt->cache)) { |
|
xmlXPathContextCachePtr cache = (xmlXPathContextCachePtr) ctxt->cache; |
|
|
|
if ((cache->stringObjs != NULL) && |
|
(cache->stringObjs->number != 0)) |
|
{ |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) |
|
cache->stringObjs->items[--cache->stringObjs->number]; |
|
ret->type = XPATH_STRING; |
|
if (val != NULL) |
|
ret->stringval = xmlStrdup(val); |
|
else |
|
ret->stringval = xmlStrdup((const xmlChar *)""); |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(ctxt, XPATH_STRING); |
|
#endif |
|
return(ret); |
|
} else if ((cache->miscObjs != NULL) && |
|
(cache->miscObjs->number != 0)) |
|
{ |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) |
|
cache->miscObjs->items[--cache->miscObjs->number]; |
|
|
|
ret->type = XPATH_STRING; |
|
if (val != NULL) |
|
ret->stringval = xmlStrdup(val); |
|
else |
|
ret->stringval = xmlStrdup((const xmlChar *)""); |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(ctxt, XPATH_STRING); |
|
#endif |
|
return(ret); |
|
} |
|
} |
|
return(xmlXPathNewString(val)); |
|
} |
|
|
|
/** |
|
* xmlXPathCacheNewBoolean: |
|
* @ctxt: the XPath context |
|
* @val: the boolean value |
|
* |
|
* This is the cached version of xmlXPathNewBoolean(). |
|
* Acquires an xmlXPathObjectPtr of type boolean and of value @val |
|
* |
|
* Returns the created or reused object. |
|
*/ |
|
static xmlXPathObjectPtr |
|
xmlXPathCacheNewBoolean(xmlXPathContextPtr ctxt, int val) |
|
{ |
|
if ((ctxt != NULL) && (ctxt->cache)) { |
|
xmlXPathContextCachePtr cache = (xmlXPathContextCachePtr) ctxt->cache; |
|
|
|
if ((cache->booleanObjs != NULL) && |
|
(cache->booleanObjs->number != 0)) |
|
{ |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) |
|
cache->booleanObjs->items[--cache->booleanObjs->number]; |
|
ret->type = XPATH_BOOLEAN; |
|
ret->boolval = (val != 0); |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(ctxt, XPATH_BOOLEAN); |
|
#endif |
|
return(ret); |
|
} else if ((cache->miscObjs != NULL) && |
|
(cache->miscObjs->number != 0)) |
|
{ |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) |
|
cache->miscObjs->items[--cache->miscObjs->number]; |
|
|
|
ret->type = XPATH_BOOLEAN; |
|
ret->boolval = (val != 0); |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(ctxt, XPATH_BOOLEAN); |
|
#endif |
|
return(ret); |
|
} |
|
} |
|
return(xmlXPathNewBoolean(val)); |
|
} |
|
|
|
/** |
|
* xmlXPathCacheNewFloat: |
|
* @ctxt: the XPath context |
|
* @val: the double value |
|
* |
|
* This is the cached version of xmlXPathNewFloat(). |
|
* Acquires an xmlXPathObjectPtr of type double and of value @val |
|
* |
|
* Returns the created or reused object. |
|
*/ |
|
static xmlXPathObjectPtr |
|
xmlXPathCacheNewFloat(xmlXPathContextPtr ctxt, double val) |
|
{ |
|
if ((ctxt != NULL) && (ctxt->cache)) { |
|
xmlXPathContextCachePtr cache = (xmlXPathContextCachePtr) ctxt->cache; |
|
|
|
if ((cache->numberObjs != NULL) && |
|
(cache->numberObjs->number != 0)) |
|
{ |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) |
|
cache->numberObjs->items[--cache->numberObjs->number]; |
|
ret->type = XPATH_NUMBER; |
|
ret->floatval = val; |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(ctxt, XPATH_NUMBER); |
|
#endif |
|
return(ret); |
|
} else if ((cache->miscObjs != NULL) && |
|
(cache->miscObjs->number != 0)) |
|
{ |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) |
|
cache->miscObjs->items[--cache->miscObjs->number]; |
|
|
|
ret->type = XPATH_NUMBER; |
|
ret->floatval = val; |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(ctxt, XPATH_NUMBER); |
|
#endif |
|
return(ret); |
|
} |
|
} |
|
return(xmlXPathNewFloat(val)); |
|
} |
|
|
|
/** |
|
* xmlXPathCacheConvertString: |
|
* @ctxt: the XPath context |
|
* @val: an XPath object |
|
* |
|
* This is the cached version of xmlXPathConvertString(). |
|
* Converts an existing object to its string() equivalent |
|
* |
|
* Returns a created or reused object, the old one is freed (cached) |
|
* (or the operation is done directly on @val) |
|
*/ |
|
|
|
static xmlXPathObjectPtr |
|
xmlXPathCacheConvertString(xmlXPathContextPtr ctxt, xmlXPathObjectPtr val) { |
|
xmlChar *res = NULL; |
|
|
|
if (val == NULL) |
|
return(xmlXPathCacheNewCString(ctxt, "")); |
|
|
|
switch (val->type) { |
|
case XPATH_UNDEFINED: |
|
#ifdef DEBUG_EXPR |
|
xmlGenericError(xmlGenericErrorContext, "STRING: undefined\n"); |
|
#endif |
|
break; |
|
case XPATH_NODESET: |
|
case XPATH_XSLT_TREE: |
|
res = xmlXPathCastNodeSetToString(val->nodesetval); |
|
break; |
|
case XPATH_STRING: |
|
return(val); |
|
case XPATH_BOOLEAN: |
|
res = xmlXPathCastBooleanToString(val->boolval); |
|
break; |
|
case XPATH_NUMBER: |
|
res = xmlXPathCastNumberToString(val->floatval); |
|
break; |
|
case XPATH_USERS: |
|
case XPATH_POINT: |
|
case XPATH_RANGE: |
|
case XPATH_LOCATIONSET: |
|
TODO; |
|
break; |
|
} |
|
xmlXPathReleaseObject(ctxt, val); |
|
if (res == NULL) |
|
return(xmlXPathCacheNewCString(ctxt, "")); |
|
return(xmlXPathCacheWrapString(ctxt, res)); |
|
} |
|
|
|
/** |
|
* xmlXPathCacheObjectCopy: |
|
* @ctxt: the XPath context |
|
* @val: the original object |
|
* |
|
* This is the cached version of xmlXPathObjectCopy(). |
|
* Acquire a copy of a given object |
|
* |
|
* Returns a created or reused created object. |
|
*/ |
|
static xmlXPathObjectPtr |
|
xmlXPathCacheObjectCopy(xmlXPathContextPtr ctxt, xmlXPathObjectPtr val) |
|
{ |
|
if (val == NULL) |
|
return(NULL); |
|
|
|
if (XP_HAS_CACHE(ctxt)) { |
|
switch (val->type) { |
|
case XPATH_NODESET: |
|
return(xmlXPathCacheWrapNodeSet(ctxt, |
|
xmlXPathNodeSetMerge(NULL, val->nodesetval))); |
|
case XPATH_STRING: |
|
return(xmlXPathCacheNewString(ctxt, val->stringval)); |
|
case XPATH_BOOLEAN: |
|
return(xmlXPathCacheNewBoolean(ctxt, val->boolval)); |
|
case XPATH_NUMBER: |
|
return(xmlXPathCacheNewFloat(ctxt, val->floatval)); |
|
default: |
|
break; |
|
} |
|
} |
|
return(xmlXPathObjectCopy(val)); |
|
} |
|
|
|
/** |
|
* xmlXPathCacheConvertBoolean: |
|
* @ctxt: the XPath context |
|
* @val: an XPath object |
|
* |
|
* This is the cached version of xmlXPathConvertBoolean(). |
|
* Converts an existing object to its boolean() equivalent |
|
* |
|
* Returns a created or reused object, the old one is freed (or the operation |
|
* is done directly on @val) |
|
*/ |
|
static xmlXPathObjectPtr |
|
xmlXPathCacheConvertBoolean(xmlXPathContextPtr ctxt, xmlXPathObjectPtr val) { |
|
xmlXPathObjectPtr ret; |
|
|
|
if (val == NULL) |
|
return(xmlXPathCacheNewBoolean(ctxt, 0)); |
|
if (val->type == XPATH_BOOLEAN) |
|
return(val); |
|
ret = xmlXPathCacheNewBoolean(ctxt, xmlXPathCastToBoolean(val)); |
|
xmlXPathReleaseObject(ctxt, val); |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathCacheConvertNumber: |
|
* @ctxt: the XPath context |
|
* @val: an XPath object |
|
* |
|
* This is the cached version of xmlXPathConvertNumber(). |
|
* Converts an existing object to its number() equivalent |
|
* |
|
* Returns a created or reused object, the old one is freed (or the operation |
|
* is done directly on @val) |
|
*/ |
|
static xmlXPathObjectPtr |
|
xmlXPathCacheConvertNumber(xmlXPathContextPtr ctxt, xmlXPathObjectPtr val) { |
|
xmlXPathObjectPtr ret; |
|
|
|
if (val == NULL) |
|
return(xmlXPathCacheNewFloat(ctxt, 0.0)); |
|
if (val->type == XPATH_NUMBER) |
|
return(val); |
|
ret = xmlXPathCacheNewFloat(ctxt, xmlXPathCastToNumber(val)); |
|
xmlXPathReleaseObject(ctxt, val); |
|
return(ret); |
|
} |
|
|
|
/************************************************************************ |
|
* * |
|
* Parser stacks related functions and macros * |
|
* * |
|
************************************************************************/ |
|
|
|
/** |
|
* xmlXPathSetFrame: |
|
* @ctxt: an XPath parser context |
|
* |
|
* Set the callee evaluation frame |
|
* |
|
* Returns the previous frame value to be restored once done |
|
*/ |
|
static int |
|
xmlXPathSetFrame(xmlXPathParserContextPtr ctxt) { |
|
int ret; |
|
|
|
if (ctxt == NULL) |
|
return(0); |
|
ret = ctxt->valueFrame; |
|
ctxt->valueFrame = ctxt->valueNr; |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathPopFrame: |
|
* @ctxt: an XPath parser context |
|
* @frame: the previous frame value |
|
* |
|
* Remove the callee evaluation frame |
|
*/ |
|
static void |
|
xmlXPathPopFrame(xmlXPathParserContextPtr ctxt, int frame) { |
|
if (ctxt == NULL) |
|
return; |
|
if (ctxt->valueNr < ctxt->valueFrame) { |
|
xmlXPatherror(ctxt, __FILE__, __LINE__, XPATH_STACK_ERROR); |
|
} |
|
ctxt->valueFrame = frame; |
|
} |
|
|
|
/** |
|
* valuePop: |
|
* @ctxt: an XPath evaluation context |
|
* |
|
* Pops the top XPath object from the value stack |
|
* |
|
* Returns the XPath object just removed |
|
*/ |
|
xmlXPathObjectPtr |
|
valuePop(xmlXPathParserContextPtr ctxt) |
|
{ |
|
xmlXPathObjectPtr ret; |
|
|
|
if ((ctxt == NULL) || (ctxt->valueNr <= 0)) |
|
return (NULL); |
|
|
|
if (ctxt->valueNr <= ctxt->valueFrame) { |
|
xmlXPatherror(ctxt, __FILE__, __LINE__, XPATH_STACK_ERROR); |
|
return (NULL); |
|
} |
|
|
|
ctxt->valueNr--; |
|
if (ctxt->valueNr > 0) |
|
ctxt->value = ctxt->valueTab[ctxt->valueNr - 1]; |
|
else |
|
ctxt->value = NULL; |
|
ret = ctxt->valueTab[ctxt->valueNr]; |
|
ctxt->valueTab[ctxt->valueNr] = NULL; |
|
return (ret); |
|
} |
|
/** |
|
* valuePush: |
|
* @ctxt: an XPath evaluation context |
|
* @value: the XPath object |
|
* |
|
* Pushes a new XPath object on top of the value stack |
|
* |
|
* returns the number of items on the value stack |
|
*/ |
|
int |
|
valuePush(xmlXPathParserContextPtr ctxt, xmlXPathObjectPtr value) |
|
{ |
|
if ((ctxt == NULL) || (value == NULL)) return(-1); |
|
if (ctxt->valueNr >= ctxt->valueMax) { |
|
xmlXPathObjectPtr *tmp; |
|
|
|
if (ctxt->valueMax >= XPATH_MAX_STACK_DEPTH) { |
|
xmlXPathErrMemory(NULL, "XPath stack depth limit reached\n"); |
|
ctxt->error = XPATH_MEMORY_ERROR; |
|
return (0); |
|
} |
|
tmp = (xmlXPathObjectPtr *) xmlRealloc(ctxt->valueTab, |
|
2 * ctxt->valueMax * |
|
sizeof(ctxt->valueTab[0])); |
|
if (tmp == NULL) { |
|
xmlXPathErrMemory(NULL, "pushing value\n"); |
|
ctxt->error = XPATH_MEMORY_ERROR; |
|
return (0); |
|
} |
|
ctxt->valueMax *= 2; |
|
ctxt->valueTab = tmp; |
|
} |
|
ctxt->valueTab[ctxt->valueNr] = value; |
|
ctxt->value = value; |
|
return (ctxt->valueNr++); |
|
} |
|
|
|
/** |
|
* xmlXPathPopBoolean: |
|
* @ctxt: an XPath parser context |
|
* |
|
* Pops a boolean from the stack, handling conversion if needed. |
|
* Check error with #xmlXPathCheckError. |
|
* |
|
* Returns the boolean |
|
*/ |
|
int |
|
xmlXPathPopBoolean (xmlXPathParserContextPtr ctxt) { |
|
xmlXPathObjectPtr obj; |
|
int ret; |
|
|
|
obj = valuePop(ctxt); |
|
if (obj == NULL) { |
|
xmlXPathSetError(ctxt, XPATH_INVALID_OPERAND); |
|
return(0); |
|
} |
|
if (obj->type != XPATH_BOOLEAN) |
|
ret = xmlXPathCastToBoolean(obj); |
|
else |
|
ret = obj->boolval; |
|
xmlXPathReleaseObject(ctxt->context, obj); |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathPopNumber: |
|
* @ctxt: an XPath parser context |
|
* |
|
* Pops a number from the stack, handling conversion if needed. |
|
* Check error with #xmlXPathCheckError. |
|
* |
|
* Returns the number |
|
*/ |
|
double |
|
xmlXPathPopNumber (xmlXPathParserContextPtr ctxt) { |
|
xmlXPathObjectPtr obj; |
|
double ret; |
|
|
|
obj = valuePop(ctxt); |
|
if (obj == NULL) { |
|
xmlXPathSetError(ctxt, XPATH_INVALID_OPERAND); |
|
return(0); |
|
} |
|
if (obj->type != XPATH_NUMBER) |
|
ret = xmlXPathCastToNumber(obj); |
|
else |
|
ret = obj->floatval; |
|
xmlXPathReleaseObject(ctxt->context, obj); |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathPopString: |
|
* @ctxt: an XPath parser context |
|
* |
|
* Pops a string from the stack, handling conversion if needed. |
|
* Check error with #xmlXPathCheckError. |
|
* |
|
* Returns the string |
|
*/ |
|
xmlChar * |
|
xmlXPathPopString (xmlXPathParserContextPtr ctxt) { |
|
xmlXPathObjectPtr obj; |
|
xmlChar * ret; |
|
|
|
obj = valuePop(ctxt); |
|
if (obj == NULL) { |
|
xmlXPathSetError(ctxt, XPATH_INVALID_OPERAND); |
|
return(NULL); |
|
} |
|
ret = xmlXPathCastToString(obj); /* this does required strdup */ |
|
/* TODO: needs refactoring somewhere else */ |
|
if (obj->stringval == ret) |
|
obj->stringval = NULL; |
|
xmlXPathReleaseObject(ctxt->context, obj); |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathPopNodeSet: |
|
* @ctxt: an XPath parser context |
|
* |
|
* Pops a node-set from the stack, handling conversion if needed. |
|
* Check error with #xmlXPathCheckError. |
|
* |
|
* Returns the node-set |
|
*/ |
|
xmlNodeSetPtr |
|
xmlXPathPopNodeSet (xmlXPathParserContextPtr ctxt) { |
|
xmlXPathObjectPtr obj; |
|
xmlNodeSetPtr ret; |
|
|
|
if (ctxt == NULL) return(NULL); |
|
if (ctxt->value == NULL) { |
|
xmlXPathSetError(ctxt, XPATH_INVALID_OPERAND); |
|
return(NULL); |
|
} |
|
if (!xmlXPathStackIsNodeSet(ctxt)) { |
|
xmlXPathSetTypeError(ctxt); |
|
return(NULL); |
|
} |
|
obj = valuePop(ctxt); |
|
ret = obj->nodesetval; |
|
#if 0 |
|
/* to fix memory leak of not clearing obj->user */ |
|
if (obj->boolval && obj->user != NULL) |
|
xmlFreeNodeList((xmlNodePtr) obj->user); |
|
#endif |
|
obj->nodesetval = NULL; |
|
xmlXPathReleaseObject(ctxt->context, obj); |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathPopExternal: |
|
* @ctxt: an XPath parser context |
|
* |
|
* Pops an external object from the stack, handling conversion if needed. |
|
* Check error with #xmlXPathCheckError. |
|
* |
|
* Returns the object |
|
*/ |
|
void * |
|
xmlXPathPopExternal (xmlXPathParserContextPtr ctxt) { |
|
xmlXPathObjectPtr obj; |
|
void * ret; |
|
|
|
if ((ctxt == NULL) || (ctxt->value == NULL)) { |
|
xmlXPathSetError(ctxt, XPATH_INVALID_OPERAND); |
|
return(NULL); |
|
} |
|
if (ctxt->value->type != XPATH_USERS) { |
|
xmlXPathSetTypeError(ctxt); |
|
return(NULL); |
|
} |
|
obj = valuePop(ctxt); |
|
ret = obj->user; |
|
obj->user = NULL; |
|
xmlXPathReleaseObject(ctxt->context, obj); |
|
return(ret); |
|
} |
|
|
|
/* |
|
* Macros for accessing the content. Those should be used only by the parser, |
|
* and not exported. |
|
* |
|
* Dirty macros, i.e. one need to make assumption on the context to use them |
|
* |
|
* CUR_PTR return the current pointer to the xmlChar to be parsed. |
|
* CUR returns the current xmlChar value, i.e. a 8 bit value |
|
* in ISO-Latin or UTF-8. |
|
* This should be used internally by the parser |
|
* only to compare to ASCII values otherwise it would break when |
|
* running with UTF-8 encoding. |
|
* NXT(n) returns the n'th next xmlChar. Same as CUR is should be used only |
|
* to compare on ASCII based substring. |
|
* SKIP(n) Skip n xmlChar, and must also be used only to skip ASCII defined |
|
* strings within the parser. |
|
* CURRENT Returns the current char value, with the full decoding of |
|
* UTF-8 if we are using this mode. It returns an int. |
|
* NEXT Skip to the next character, this does the proper decoding |
|
* in UTF-8 mode. It also pop-up unfinished entities on the fly. |
|
* It returns the pointer to the current xmlChar. |
|
*/ |
|
|
|
#define CUR (*ctxt->cur) |
|
#define SKIP(val) ctxt->cur += (val) |
|
#define NXT(val) ctxt->cur[(val)] |
|
#define CUR_PTR ctxt->cur |
|
#define CUR_CHAR(l) xmlXPathCurrentChar(ctxt, &l) |
|
|
|
#define COPY_BUF(l,b,i,v) \ |
|
if (l == 1) b[i++] = (xmlChar) v; \ |
|
else i += xmlCopyChar(l,&b[i],v) |
|
|
|
#define NEXTL(l) ctxt->cur += l |
|
|
|
#define SKIP_BLANKS \ |
|
while (IS_BLANK_CH(*(ctxt->cur))) NEXT |
|
|
|
#define CURRENT (*ctxt->cur) |
|
#define NEXT ((*ctxt->cur) ? ctxt->cur++: ctxt->cur) |
|
|
|
|
|
#ifndef DBL_DIG |
|
#define DBL_DIG 16 |
|
#endif |
|
#ifndef DBL_EPSILON |
|
#define DBL_EPSILON 1E-9 |
|
#endif |
|
|
|
#define UPPER_DOUBLE 1E9 |
|
#define LOWER_DOUBLE 1E-5 |
|
#define LOWER_DOUBLE_EXP 5 |
|
|
|
#define INTEGER_DIGITS DBL_DIG |
|
#define FRACTION_DIGITS (DBL_DIG + 1 + (LOWER_DOUBLE_EXP)) |
|
#define EXPONENT_DIGITS (3 + 2) |
|
|
|
/** |
|
* xmlXPathFormatNumber: |
|
* @number: number to format |
|
* @buffer: output buffer |
|
* @buffersize: size of output buffer |
|
* |
|
* Convert the number into a string representation. |
|
*/ |
|
static void |
|
xmlXPathFormatNumber(double number, char buffer[], int buffersize) |
|
{ |
|
switch (xmlXPathIsInf(number)) { |
|
case 1: |
|
if (buffersize > (int)sizeof("Infinity")) |
|
snprintf(buffer, buffersize, "Infinity"); |
|
break; |
|
case -1: |
|
if (buffersize > (int)sizeof("-Infinity")) |
|
snprintf(buffer, buffersize, "-Infinity"); |
|
break; |
|
default: |
|
if (xmlXPathIsNaN(number)) { |
|
if (buffersize > (int)sizeof("NaN")) |
|
snprintf(buffer, buffersize, "NaN"); |
|
} else if (number == 0) { |
|
/* Omit sign for negative zero. */ |
|
snprintf(buffer, buffersize, "0"); |
|
} else if ((number > INT_MIN) && (number < INT_MAX) && |
|
(number == (int) number)) { |
|
char work[30]; |
|
char *ptr, *cur; |
|
int value = (int) number; |
|
|
|
ptr = &buffer[0]; |
|
if (value == 0) { |
|
*ptr++ = '0'; |
|
} else { |
|
snprintf(work, 29, "%d", value); |
|
cur = &work[0]; |
|
while ((*cur) && (ptr - buffer < buffersize)) { |
|
*ptr++ = *cur++; |
|
} |
|
} |
|
if (ptr - buffer < buffersize) { |
|
*ptr = 0; |
|
} else if (buffersize > 0) { |
|
ptr--; |
|
*ptr = 0; |
|
} |
|
} else { |
|
/* |
|
For the dimension of work, |
|
DBL_DIG is number of significant digits |
|
EXPONENT is only needed for "scientific notation" |
|
3 is sign, decimal point, and terminating zero |
|
LOWER_DOUBLE_EXP is max number of leading zeroes in fraction |
|
Note that this dimension is slightly (a few characters) |
|
larger than actually necessary. |
|
*/ |
|
char work[DBL_DIG + EXPONENT_DIGITS + 3 + LOWER_DOUBLE_EXP]; |
|
int integer_place, fraction_place; |
|
char *ptr; |
|
char *after_fraction; |
|
double absolute_value; |
|
int size; |
|
|
|
absolute_value = fabs(number); |
|
|
|
/* |
|
* First choose format - scientific or regular floating point. |
|
* In either case, result is in work, and after_fraction points |
|
* just past the fractional part. |
|
*/ |
|
if ( ((absolute_value > UPPER_DOUBLE) || |
|
(absolute_value < LOWER_DOUBLE)) && |
|
(absolute_value != 0.0) ) { |
|
/* Use scientific notation */ |
|
integer_place = DBL_DIG + EXPONENT_DIGITS + 1; |
|
fraction_place = DBL_DIG - 1; |
|
size = snprintf(work, sizeof(work),"%*.*e", |
|
integer_place, fraction_place, number); |
|
while ((size > 0) && (work[size] != 'e')) size--; |
|
|
|
} |
|
else { |
|
/* Use regular notation */ |
|
if (absolute_value > 0.0) { |
|
integer_place = (int)log10(absolute_value); |
|
if (integer_place > 0) |
|
fraction_place = DBL_DIG - integer_place - 1; |
|
else |
|
fraction_place = DBL_DIG - integer_place; |
|
} else { |
|
fraction_place = 1; |
|
} |
|
size = snprintf(work, sizeof(work), "%0.*f", |
|
fraction_place, number); |
|
} |
|
|
|
/* Remove leading spaces sometimes inserted by snprintf */ |
|
while (work[0] == ' ') { |
|
for (ptr = &work[0];(ptr[0] = ptr[1]);ptr++); |
|
size--; |
|
} |
|
|
|
/* Remove fractional trailing zeroes */ |
|
after_fraction = work + size; |
|
ptr = after_fraction; |
|
while (*(--ptr) == '0') |
|
; |
|
if (*ptr != '.') |
|
ptr++; |
|
while ((*ptr++ = *after_fraction++) != 0); |
|
|
|
/* Finally copy result back to caller */ |
|
size = strlen(work) + 1; |
|
if (size > buffersize) { |
|
work[buffersize - 1] = 0; |
|
size = buffersize; |
|
} |
|
memmove(buffer, work, size); |
|
} |
|
break; |
|
} |
|
} |
|
|
|
|
|
/************************************************************************ |
|
* * |
|
* Routines to handle NodeSets * |
|
* * |
|
************************************************************************/ |
|
|
|
/** |
|
* xmlXPathOrderDocElems: |
|
* @doc: an input document |
|
* |
|
* Call this routine to speed up XPath computation on static documents. |
|
* This stamps all the element nodes with the document order |
|
* Like for line information, the order is kept in the element->content |
|
* field, the value stored is actually - the node number (starting at -1) |
|
* to be able to differentiate from line numbers. |
|
* |
|
* Returns the number of elements found in the document or -1 in case |
|
* of error. |
|
*/ |
|
long |
|
xmlXPathOrderDocElems(xmlDocPtr doc) { |
|
ptrdiff_t count = 0; |
|
xmlNodePtr cur; |
|
|
|
if (doc == NULL) |
|
return(-1); |
|
cur = doc->children; |
|
while (cur != NULL) { |
|
if (cur->type == XML_ELEMENT_NODE) { |
|
cur->content = (void *) (-(++count)); |
|
if (cur->children != NULL) { |
|
cur = cur->children; |
|
continue; |
|
} |
|
} |
|
if (cur->next != NULL) { |
|
cur = cur->next; |
|
continue; |
|
} |
|
do { |
|
cur = cur->parent; |
|
if (cur == NULL) |
|
break; |
|
if (cur == (xmlNodePtr) doc) { |
|
cur = NULL; |
|
break; |
|
} |
|
if (cur->next != NULL) { |
|
cur = cur->next; |
|
break; |
|
} |
|
} while (cur != NULL); |
|
} |
|
return((long) count); |
|
} |
|
|
|
/** |
|
* xmlXPathCmpNodes: |
|
* @node1: the first node |
|
* @node2: the second node |
|
* |
|
* Compare two nodes w.r.t document order |
|
* |
|
* Returns -2 in case of error 1 if first point < second point, 0 if |
|
* it's the same node, -1 otherwise |
|
*/ |
|
int |
|
xmlXPathCmpNodes(xmlNodePtr node1, xmlNodePtr node2) { |
|
int depth1, depth2; |
|
int attr1 = 0, attr2 = 0; |
|
xmlNodePtr attrNode1 = NULL, attrNode2 = NULL; |
|
xmlNodePtr cur, root; |
|
|
|
if ((node1 == NULL) || (node2 == NULL)) |
|
return(-2); |
|
/* |
|
* a couple of optimizations which will avoid computations in most cases |
|
*/ |
|
if (node1 == node2) /* trivial case */ |
|
return(0); |
|
if (node1->type == XML_ATTRIBUTE_NODE) { |
|
attr1 = 1; |
|
attrNode1 = node1; |
|
node1 = node1->parent; |
|
} |
|
if (node2->type == XML_ATTRIBUTE_NODE) { |
|
attr2 = 1; |
|
attrNode2 = node2; |
|
node2 = node2->parent; |
|
} |
|
if (node1 == node2) { |
|
if (attr1 == attr2) { |
|
/* not required, but we keep attributes in order */ |
|
if (attr1 != 0) { |
|
cur = attrNode2->prev; |
|
while (cur != NULL) { |
|
if (cur == attrNode1) |
|
return (1); |
|
cur = cur->prev; |
|
} |
|
return (-1); |
|
} |
|
return(0); |
|
} |
|
if (attr2 == 1) |
|
return(1); |
|
return(-1); |
|
} |
|
if ((node1->type == XML_NAMESPACE_DECL) || |
|
(node2->type == XML_NAMESPACE_DECL)) |
|
return(1); |
|
if (node1 == node2->prev) |
|
return(1); |
|
if (node1 == node2->next) |
|
return(-1); |
|
|
|
/* |
|
* Speedup using document order if availble. |
|
*/ |
|
if ((node1->type == XML_ELEMENT_NODE) && |
|
(node2->type == XML_ELEMENT_NODE) && |
|
(0 > (ptrdiff_t) node1->content) && |
|
(0 > (ptrdiff_t) node2->content) && |
|
(node1->doc == node2->doc)) { |
|
ptrdiff_t l1, l2; |
|
|
|
l1 = -((ptrdiff_t) node1->content); |
|
l2 = -((ptrdiff_t) node2->content); |
|
if (l1 < l2) |
|
return(1); |
|
if (l1 > l2) |
|
return(-1); |
|
} |
|
|
|
/* |
|
* compute depth to root |
|
*/ |
|
for (depth2 = 0, cur = node2;cur->parent != NULL;cur = cur->parent) { |
|
if (cur->parent == node1) |
|
return(1); |
|
depth2++; |
|
} |
|
root = cur; |
|
for (depth1 = 0, cur = node1;cur->parent != NULL;cur = cur->parent) { |
|
if (cur->parent == node2) |
|
return(-1); |
|
depth1++; |
|
} |
|
/* |
|
* Distinct document (or distinct entities :-( ) case. |
|
*/ |
|
if (root != cur) { |
|
return(-2); |
|
} |
|
/* |
|
* get the nearest common ancestor. |
|
*/ |
|
while (depth1 > depth2) { |
|
depth1--; |
|
node1 = node1->parent; |
|
} |
|
while (depth2 > depth1) { |
|
depth2--; |
|
node2 = node2->parent; |
|
} |
|
while (node1->parent != node2->parent) { |
|
node1 = node1->parent; |
|
node2 = node2->parent; |
|
/* should not happen but just in case ... */ |
|
if ((node1 == NULL) || (node2 == NULL)) |
|
return(-2); |
|
} |
|
/* |
|
* Find who's first. |
|
*/ |
|
if (node1 == node2->prev) |
|
return(1); |
|
if (node1 == node2->next) |
|
return(-1); |
|
/* |
|
* Speedup using document order if availble. |
|
*/ |
|
if ((node1->type == XML_ELEMENT_NODE) && |
|
(node2->type == XML_ELEMENT_NODE) && |
|
(0 > (ptrdiff_t) node1->content) && |
|
(0 > (ptrdiff_t) node2->content) && |
|
(node1->doc == node2->doc)) { |
|
ptrdiff_t l1, l2; |
|
|
|
l1 = -((ptrdiff_t) node1->content); |
|
l2 = -((ptrdiff_t) node2->content); |
|
if (l1 < l2) |
|
return(1); |
|
if (l1 > l2) |
|
return(-1); |
|
} |
|
|
|
for (cur = node1->next;cur != NULL;cur = cur->next) |
|
if (cur == node2) |
|
return(1); |
|
return(-1); /* assume there is no sibling list corruption */ |
|
} |
|
|
|
/** |
|
* xmlXPathNodeSetSort: |
|
* @set: the node set |
|
* |
|
* Sort the node set in document order |
|
*/ |
|
void |
|
xmlXPathNodeSetSort(xmlNodeSetPtr set) { |
|
#ifndef WITH_TIM_SORT |
|
int i, j, incr, len; |
|
xmlNodePtr tmp; |
|
#endif |
|
|
|
if (set == NULL) |
|
return; |
|
|
|
#ifndef WITH_TIM_SORT |
|
/* |
|
* Use the old Shell's sort implementation to sort the node-set |
|
* Timsort ought to be quite faster |
|
*/ |
|
len = set->nodeNr; |
|
for (incr = len / 2; incr > 0; incr /= 2) { |
|
for (i = incr; i < len; i++) { |
|
j = i - incr; |
|
while (j >= 0) { |
|
#ifdef XP_OPTIMIZED_NON_ELEM_COMPARISON |
|
if (xmlXPathCmpNodesExt(set->nodeTab[j], |
|
set->nodeTab[j + incr]) == -1) |
|
#else |
|
if (xmlXPathCmpNodes(set->nodeTab[j], |
|
set->nodeTab[j + incr]) == -1) |
|
#endif |
|
{ |
|
tmp = set->nodeTab[j]; |
|
set->nodeTab[j] = set->nodeTab[j + incr]; |
|
set->nodeTab[j + incr] = tmp; |
|
j -= incr; |
|
} else |
|
break; |
|
} |
|
} |
|
} |
|
#else /* WITH_TIM_SORT */ |
|
libxml_domnode_tim_sort(set->nodeTab, set->nodeNr); |
|
#endif /* WITH_TIM_SORT */ |
|
} |
|
|
|
#define XML_NODESET_DEFAULT 10 |
|
/** |
|
* xmlXPathNodeSetDupNs: |
|
* @node: the parent node of the namespace XPath node |
|
* @ns: the libxml namespace declaration node. |
|
* |
|
* Namespace node in libxml don't match the XPath semantic. In a node set |
|
* the namespace nodes are duplicated and the next pointer is set to the |
|
* parent node in the XPath semantic. |
|
* |
|
* Returns the newly created object. |
|
*/ |
|
static xmlNodePtr |
|
xmlXPathNodeSetDupNs(xmlNodePtr node, xmlNsPtr ns) { |
|
xmlNsPtr cur; |
|
|
|
if ((ns == NULL) || (ns->type != XML_NAMESPACE_DECL)) |
|
return(NULL); |
|
if ((node == NULL) || (node->type == XML_NAMESPACE_DECL)) |
|
return((xmlNodePtr) ns); |
|
|
|
/* |
|
* Allocate a new Namespace and fill the fields. |
|
*/ |
|
cur = (xmlNsPtr) xmlMalloc(sizeof(xmlNs)); |
|
if (cur == NULL) { |
|
xmlXPathErrMemory(NULL, "duplicating namespace\n"); |
|
return(NULL); |
|
} |
|
memset(cur, 0, sizeof(xmlNs)); |
|
cur->type = XML_NAMESPACE_DECL; |
|
if (ns->href != NULL) |
|
cur->href = xmlStrdup(ns->href); |
|
if (ns->prefix != NULL) |
|
cur->prefix = xmlStrdup(ns->prefix); |
|
cur->next = (xmlNsPtr) node; |
|
return((xmlNodePtr) cur); |
|
} |
|
|
|
/** |
|
* xmlXPathNodeSetFreeNs: |
|
* @ns: the XPath namespace node found in a nodeset. |
|
* |
|
* Namespace nodes in libxml don't match the XPath semantic. In a node set |
|
* the namespace nodes are duplicated and the next pointer is set to the |
|
* parent node in the XPath semantic. Check if such a node needs to be freed |
|
*/ |
|
void |
|
xmlXPathNodeSetFreeNs(xmlNsPtr ns) { |
|
if ((ns == NULL) || (ns->type != XML_NAMESPACE_DECL)) |
|
return; |
|
|
|
if ((ns->next != NULL) && (ns->next->type != XML_NAMESPACE_DECL)) { |
|
if (ns->href != NULL) |
|
xmlFree((xmlChar *)ns->href); |
|
if (ns->prefix != NULL) |
|
xmlFree((xmlChar *)ns->prefix); |
|
xmlFree(ns); |
|
} |
|
} |
|
|
|
/** |
|
* xmlXPathNodeSetCreate: |
|
* @val: an initial xmlNodePtr, or NULL |
|
* |
|
* Create a new xmlNodeSetPtr of type double and of value @val |
|
* |
|
* Returns the newly created object. |
|
*/ |
|
xmlNodeSetPtr |
|
xmlXPathNodeSetCreate(xmlNodePtr val) { |
|
xmlNodeSetPtr ret; |
|
|
|
ret = (xmlNodeSetPtr) xmlMalloc(sizeof(xmlNodeSet)); |
|
if (ret == NULL) { |
|
xmlXPathErrMemory(NULL, "creating nodeset\n"); |
|
return(NULL); |
|
} |
|
memset(ret, 0 , (size_t) sizeof(xmlNodeSet)); |
|
if (val != NULL) { |
|
ret->nodeTab = (xmlNodePtr *) xmlMalloc(XML_NODESET_DEFAULT * |
|
sizeof(xmlNodePtr)); |
|
if (ret->nodeTab == NULL) { |
|
xmlXPathErrMemory(NULL, "creating nodeset\n"); |
|
xmlFree(ret); |
|
return(NULL); |
|
} |
|
memset(ret->nodeTab, 0 , |
|
XML_NODESET_DEFAULT * (size_t) sizeof(xmlNodePtr)); |
|
ret->nodeMax = XML_NODESET_DEFAULT; |
|
if (val->type == XML_NAMESPACE_DECL) { |
|
xmlNsPtr ns = (xmlNsPtr) val; |
|
|
|
ret->nodeTab[ret->nodeNr++] = |
|
xmlXPathNodeSetDupNs((xmlNodePtr) ns->next, ns); |
|
} else |
|
ret->nodeTab[ret->nodeNr++] = val; |
|
} |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathNodeSetCreateSize: |
|
* @size: the initial size of the set |
|
* |
|
* Create a new xmlNodeSetPtr of type double and of value @val |
|
* |
|
* Returns the newly created object. |
|
*/ |
|
static xmlNodeSetPtr |
|
xmlXPathNodeSetCreateSize(int size) { |
|
xmlNodeSetPtr ret; |
|
|
|
ret = (xmlNodeSetPtr) xmlMalloc(sizeof(xmlNodeSet)); |
|
if (ret == NULL) { |
|
xmlXPathErrMemory(NULL, "creating nodeset\n"); |
|
return(NULL); |
|
} |
|
memset(ret, 0 , (size_t) sizeof(xmlNodeSet)); |
|
if (size < XML_NODESET_DEFAULT) |
|
size = XML_NODESET_DEFAULT; |
|
ret->nodeTab = (xmlNodePtr *) xmlMalloc(size * sizeof(xmlNodePtr)); |
|
if (ret->nodeTab == NULL) { |
|
xmlXPathErrMemory(NULL, "creating nodeset\n"); |
|
xmlFree(ret); |
|
return(NULL); |
|
} |
|
memset(ret->nodeTab, 0 , size * (size_t) sizeof(xmlNodePtr)); |
|
ret->nodeMax = size; |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathNodeSetContains: |
|
* @cur: the node-set |
|
* @val: the node |
|
* |
|
* checks whether @cur contains @val |
|
* |
|
* Returns true (1) if @cur contains @val, false (0) otherwise |
|
*/ |
|
int |
|
xmlXPathNodeSetContains (xmlNodeSetPtr cur, xmlNodePtr val) { |
|
int i; |
|
|
|
if ((cur == NULL) || (val == NULL)) return(0); |
|
if (val->type == XML_NAMESPACE_DECL) { |
|
for (i = 0; i < cur->nodeNr; i++) { |
|
if (cur->nodeTab[i]->type == XML_NAMESPACE_DECL) { |
|
xmlNsPtr ns1, ns2; |
|
|
|
ns1 = (xmlNsPtr) val; |
|
ns2 = (xmlNsPtr) cur->nodeTab[i]; |
|
if (ns1 == ns2) |
|
return(1); |
|
if ((ns1->next != NULL) && (ns2->next == ns1->next) && |
|
(xmlStrEqual(ns1->prefix, ns2->prefix))) |
|
return(1); |
|
} |
|
} |
|
} else { |
|
for (i = 0; i < cur->nodeNr; i++) { |
|
if (cur->nodeTab[i] == val) |
|
return(1); |
|
} |
|
} |
|
return(0); |
|
} |
|
|
|
/** |
|
* xmlXPathNodeSetAddNs: |
|
* @cur: the initial node set |
|
* @node: the hosting node |
|
* @ns: a the namespace node |
|
* |
|
* add a new namespace node to an existing NodeSet |
|
* |
|
* Returns 0 in case of success and -1 in case of error |
|
*/ |
|
int |
|
xmlXPathNodeSetAddNs(xmlNodeSetPtr cur, xmlNodePtr node, xmlNsPtr ns) { |
|
int i; |
|
|
|
|
|
if ((cur == NULL) || (ns == NULL) || (node == NULL) || |
|
(ns->type != XML_NAMESPACE_DECL) || |
|
(node->type != XML_ELEMENT_NODE)) |
|
return(-1); |
|
|
|
/* @@ with_ns to check whether namespace nodes should be looked at @@ */ |
|
/* |
|
* prevent duplicates |
|
*/ |
|
for (i = 0;i < cur->nodeNr;i++) { |
|
if ((cur->nodeTab[i] != NULL) && |
|
(cur->nodeTab[i]->type == XML_NAMESPACE_DECL) && |
|
(((xmlNsPtr)cur->nodeTab[i])->next == (xmlNsPtr) node) && |
|
(xmlStrEqual(ns->prefix, ((xmlNsPtr)cur->nodeTab[i])->prefix))) |
|
return(0); |
|
} |
|
|
|
/* |
|
* grow the nodeTab if needed |
|
*/ |
|
if (cur->nodeMax == 0) { |
|
cur->nodeTab = (xmlNodePtr *) xmlMalloc(XML_NODESET_DEFAULT * |
|
sizeof(xmlNodePtr)); |
|
if (cur->nodeTab == NULL) { |
|
xmlXPathErrMemory(NULL, "growing nodeset\n"); |
|
return(-1); |
|
} |
|
memset(cur->nodeTab, 0 , |
|
XML_NODESET_DEFAULT * (size_t) sizeof(xmlNodePtr)); |
|
cur->nodeMax = XML_NODESET_DEFAULT; |
|
} else if (cur->nodeNr == cur->nodeMax) { |
|
xmlNodePtr *temp; |
|
|
|
if (cur->nodeMax >= XPATH_MAX_NODESET_LENGTH) { |
|
xmlXPathErrMemory(NULL, "growing nodeset hit limit\n"); |
|
return(-1); |
|
} |
|
temp = (xmlNodePtr *) xmlRealloc(cur->nodeTab, cur->nodeMax * 2 * |
|
sizeof(xmlNodePtr)); |
|
if (temp == NULL) { |
|
xmlXPathErrMemory(NULL, "growing nodeset\n"); |
|
return(-1); |
|
} |
|
cur->nodeMax *= 2; |
|
cur->nodeTab = temp; |
|
} |
|
cur->nodeTab[cur->nodeNr++] = xmlXPathNodeSetDupNs(node, ns); |
|
return(0); |
|
} |
|
|
|
/** |
|
* xmlXPathNodeSetAdd: |
|
* @cur: the initial node set |
|
* @val: a new xmlNodePtr |
|
* |
|
* add a new xmlNodePtr to an existing NodeSet |
|
* |
|
* Returns 0 in case of success, and -1 in case of error |
|
*/ |
|
int |
|
xmlXPathNodeSetAdd(xmlNodeSetPtr cur, xmlNodePtr val) { |
|
int i; |
|
|
|
if ((cur == NULL) || (val == NULL)) return(-1); |
|
|
|
/* @@ with_ns to check whether namespace nodes should be looked at @@ */ |
|
/* |
|
* prevent duplicates |
|
*/ |
|
for (i = 0;i < cur->nodeNr;i++) |
|
if (cur->nodeTab[i] == val) return(0); |
|
|
|
/* |
|
* grow the nodeTab if needed |
|
*/ |
|
if (cur->nodeMax == 0) { |
|
cur->nodeTab = (xmlNodePtr *) xmlMalloc(XML_NODESET_DEFAULT * |
|
sizeof(xmlNodePtr)); |
|
if (cur->nodeTab == NULL) { |
|
xmlXPathErrMemory(NULL, "growing nodeset\n"); |
|
return(-1); |
|
} |
|
memset(cur->nodeTab, 0 , |
|
XML_NODESET_DEFAULT * (size_t) sizeof(xmlNodePtr)); |
|
cur->nodeMax = XML_NODESET_DEFAULT; |
|
} else if (cur->nodeNr == cur->nodeMax) { |
|
xmlNodePtr *temp; |
|
|
|
if (cur->nodeMax >= XPATH_MAX_NODESET_LENGTH) { |
|
xmlXPathErrMemory(NULL, "growing nodeset hit limit\n"); |
|
return(-1); |
|
} |
|
temp = (xmlNodePtr *) xmlRealloc(cur->nodeTab, cur->nodeMax * 2 * |
|
sizeof(xmlNodePtr)); |
|
if (temp == NULL) { |
|
xmlXPathErrMemory(NULL, "growing nodeset\n"); |
|
return(-1); |
|
} |
|
cur->nodeMax *= 2; |
|
cur->nodeTab = temp; |
|
} |
|
if (val->type == XML_NAMESPACE_DECL) { |
|
xmlNsPtr ns = (xmlNsPtr) val; |
|
|
|
cur->nodeTab[cur->nodeNr++] = |
|
xmlXPathNodeSetDupNs((xmlNodePtr) ns->next, ns); |
|
} else |
|
cur->nodeTab[cur->nodeNr++] = val; |
|
return(0); |
|
} |
|
|
|
/** |
|
* xmlXPathNodeSetAddUnique: |
|
* @cur: the initial node set |
|
* @val: a new xmlNodePtr |
|
* |
|
* add a new xmlNodePtr to an existing NodeSet, optimized version |
|
* when we are sure the node is not already in the set. |
|
* |
|
* Returns 0 in case of success and -1 in case of failure |
|
*/ |
|
int |
|
xmlXPathNodeSetAddUnique(xmlNodeSetPtr cur, xmlNodePtr val) { |
|
if ((cur == NULL) || (val == NULL)) return(-1); |
|
|
|
/* @@ with_ns to check whether namespace nodes should be looked at @@ */ |
|
/* |
|
* grow the nodeTab if needed |
|
*/ |
|
if (cur->nodeMax == 0) { |
|
cur->nodeTab = (xmlNodePtr *) xmlMalloc(XML_NODESET_DEFAULT * |
|
sizeof(xmlNodePtr)); |
|
if (cur->nodeTab == NULL) { |
|
xmlXPathErrMemory(NULL, "growing nodeset\n"); |
|
return(-1); |
|
} |
|
memset(cur->nodeTab, 0 , |
|
XML_NODESET_DEFAULT * (size_t) sizeof(xmlNodePtr)); |
|
cur->nodeMax = XML_NODESET_DEFAULT; |
|
} else if (cur->nodeNr == cur->nodeMax) { |
|
xmlNodePtr *temp; |
|
|
|
if (cur->nodeMax >= XPATH_MAX_NODESET_LENGTH) { |
|
xmlXPathErrMemory(NULL, "growing nodeset hit limit\n"); |
|
return(-1); |
|
} |
|
temp = (xmlNodePtr *) xmlRealloc(cur->nodeTab, cur->nodeMax * 2 * |
|
sizeof(xmlNodePtr)); |
|
if (temp == NULL) { |
|
xmlXPathErrMemory(NULL, "growing nodeset\n"); |
|
return(-1); |
|
} |
|
cur->nodeTab = temp; |
|
cur->nodeMax *= 2; |
|
} |
|
if (val->type == XML_NAMESPACE_DECL) { |
|
xmlNsPtr ns = (xmlNsPtr) val; |
|
|
|
cur->nodeTab[cur->nodeNr++] = |
|
xmlXPathNodeSetDupNs((xmlNodePtr) ns->next, ns); |
|
} else |
|
cur->nodeTab[cur->nodeNr++] = val; |
|
return(0); |
|
} |
|
|
|
/** |
|
* xmlXPathNodeSetMerge: |
|
* @val1: the first NodeSet or NULL |
|
* @val2: the second NodeSet |
|
* |
|
* Merges two nodesets, all nodes from @val2 are added to @val1 |
|
* if @val1 is NULL, a new set is created and copied from @val2 |
|
* |
|
* Returns @val1 once extended or NULL in case of error. |
|
*/ |
|
xmlNodeSetPtr |
|
xmlXPathNodeSetMerge(xmlNodeSetPtr val1, xmlNodeSetPtr val2) { |
|
int i, j, initNr, skip; |
|
xmlNodePtr n1, n2; |
|
|
|
if (val2 == NULL) return(val1); |
|
if (val1 == NULL) { |
|
val1 = xmlXPathNodeSetCreate(NULL); |
|
if (val1 == NULL) |
|
return (NULL); |
|
#if 0 |
|
/* |
|
* TODO: The optimization won't work in every case, since |
|
* those nasty namespace nodes need to be added with |
|
* xmlXPathNodeSetDupNs() to the set; thus a pure |
|
* memcpy is not possible. |
|
* If there was a flag on the nodesetval, indicating that |
|
* some temporary nodes are in, that would be helpfull. |
|
*/ |
|
/* |
|
* Optimization: Create an equally sized node-set |
|
* and memcpy the content. |
|
*/ |
|
val1 = xmlXPathNodeSetCreateSize(val2->nodeNr); |
|
if (val1 == NULL) |
|
return(NULL); |
|
if (val2->nodeNr != 0) { |
|
if (val2->nodeNr == 1) |
|
*(val1->nodeTab) = *(val2->nodeTab); |
|
else { |
|
memcpy(val1->nodeTab, val2->nodeTab, |
|
val2->nodeNr * sizeof(xmlNodePtr)); |
|
} |
|
val1->nodeNr = val2->nodeNr; |
|
} |
|
return(val1); |
|
#endif |
|
} |
|
|
|
/* @@ with_ns to check whether namespace nodes should be looked at @@ */ |
|
initNr = val1->nodeNr; |
|
|
|
for (i = 0;i < val2->nodeNr;i++) { |
|
n2 = val2->nodeTab[i]; |
|
/* |
|
* check against duplicates |
|
*/ |
|
skip = 0; |
|
for (j = 0; j < initNr; j++) { |
|
n1 = val1->nodeTab[j]; |
|
if (n1 == n2) { |
|
skip = 1; |
|
break; |
|
} else if ((n1->type == XML_NAMESPACE_DECL) && |
|
(n2->type == XML_NAMESPACE_DECL)) { |
|
if ((((xmlNsPtr) n1)->next == ((xmlNsPtr) n2)->next) && |
|
(xmlStrEqual(((xmlNsPtr) n1)->prefix, |
|
((xmlNsPtr) n2)->prefix))) |
|
{ |
|
skip = 1; |
|
break; |
|
} |
|
} |
|
} |
|
if (skip) |
|
continue; |
|
|
|
/* |
|
* grow the nodeTab if needed |
|
*/ |
|
if (val1->nodeMax == 0) { |
|
val1->nodeTab = (xmlNodePtr *) xmlMalloc(XML_NODESET_DEFAULT * |
|
sizeof(xmlNodePtr)); |
|
if (val1->nodeTab == NULL) { |
|
xmlXPathErrMemory(NULL, "merging nodeset\n"); |
|
return(NULL); |
|
} |
|
memset(val1->nodeTab, 0 , |
|
XML_NODESET_DEFAULT * (size_t) sizeof(xmlNodePtr)); |
|
val1->nodeMax = XML_NODESET_DEFAULT; |
|
} else if (val1->nodeNr == val1->nodeMax) { |
|
xmlNodePtr *temp; |
|
|
|
if (val1->nodeMax >= XPATH_MAX_NODESET_LENGTH) { |
|
xmlXPathErrMemory(NULL, "merging nodeset hit limit\n"); |
|
return(NULL); |
|
} |
|
temp = (xmlNodePtr *) xmlRealloc(val1->nodeTab, val1->nodeMax * 2 * |
|
sizeof(xmlNodePtr)); |
|
if (temp == NULL) { |
|
xmlXPathErrMemory(NULL, "merging nodeset\n"); |
|
return(NULL); |
|
} |
|
val1->nodeTab = temp; |
|
val1->nodeMax *= 2; |
|
} |
|
if (n2->type == XML_NAMESPACE_DECL) { |
|
xmlNsPtr ns = (xmlNsPtr) n2; |
|
|
|
val1->nodeTab[val1->nodeNr++] = |
|
xmlXPathNodeSetDupNs((xmlNodePtr) ns->next, ns); |
|
} else |
|
val1->nodeTab[val1->nodeNr++] = n2; |
|
} |
|
|
|
return(val1); |
|
} |
|
|
|
|
|
/** |
|
* xmlXPathNodeSetMergeAndClear: |
|
* @set1: the first NodeSet or NULL |
|
* @set2: the second NodeSet |
|
* @hasSet2NsNodes: 1 if set2 contains namespaces nodes |
|
* |
|
* Merges two nodesets, all nodes from @set2 are added to @set1 |
|
* if @set1 is NULL, a new set is created and copied from @set2. |
|
* Checks for duplicate nodes. Clears set2. |
|
* |
|
* Returns @set1 once extended or NULL in case of error. |
|
*/ |
|
static xmlNodeSetPtr |
|
xmlXPathNodeSetMergeAndClear(xmlNodeSetPtr set1, xmlNodeSetPtr set2, |
|
int hasNullEntries) |
|
{ |
|
if ((set1 == NULL) && (hasNullEntries == 0)) { |
|
/* |
|
* Note that doing a memcpy of the list, namespace nodes are |
|
* just assigned to set1, since set2 is cleared anyway. |
|
*/ |
|
set1 = xmlXPathNodeSetCreateSize(set2->nodeNr); |
|
if (set1 == NULL) |
|
return(NULL); |
|
if (set2->nodeNr != 0) { |
|
memcpy(set1->nodeTab, set2->nodeTab, |
|
set2->nodeNr * sizeof(xmlNodePtr)); |
|
set1->nodeNr = set2->nodeNr; |
|
} |
|
} else { |
|
int i, j, initNbSet1; |
|
xmlNodePtr n1, n2; |
|
|
|
if (set1 == NULL) |
|
set1 = xmlXPathNodeSetCreate(NULL); |
|
if (set1 == NULL) |
|
return (NULL); |
|
|
|
initNbSet1 = set1->nodeNr; |
|
for (i = 0;i < set2->nodeNr;i++) { |
|
n2 = set2->nodeTab[i]; |
|
/* |
|
* Skip NULLed entries. |
|
*/ |
|
if (n2 == NULL) |
|
continue; |
|
/* |
|
* Skip duplicates. |
|
*/ |
|
for (j = 0; j < initNbSet1; j++) { |
|
n1 = set1->nodeTab[j]; |
|
if (n1 == n2) { |
|
goto skip_node; |
|
} else if ((n1->type == XML_NAMESPACE_DECL) && |
|
(n2->type == XML_NAMESPACE_DECL)) |
|
{ |
|
if ((((xmlNsPtr) n1)->next == ((xmlNsPtr) n2)->next) && |
|
(xmlStrEqual(((xmlNsPtr) n1)->prefix, |
|
((xmlNsPtr) n2)->prefix))) |
|
{ |
|
/* |
|
* Free the namespace node. |
|
*/ |
|
set2->nodeTab[i] = NULL; |
|
xmlXPathNodeSetFreeNs((xmlNsPtr) n2); |
|
goto skip_node; |
|
} |
|
} |
|
} |
|
/* |
|
* grow the nodeTab if needed |
|
*/ |
|
if (set1->nodeMax == 0) { |
|
set1->nodeTab = (xmlNodePtr *) xmlMalloc( |
|
XML_NODESET_DEFAULT * sizeof(xmlNodePtr)); |
|
if (set1->nodeTab == NULL) { |
|
xmlXPathErrMemory(NULL, "merging nodeset\n"); |
|
return(NULL); |
|
} |
|
memset(set1->nodeTab, 0, |
|
XML_NODESET_DEFAULT * (size_t) sizeof(xmlNodePtr)); |
|
set1->nodeMax = XML_NODESET_DEFAULT; |
|
} else if (set1->nodeNr >= set1->nodeMax) { |
|
xmlNodePtr *temp; |
|
|
|
if (set1->nodeMax >= XPATH_MAX_NODESET_LENGTH) { |
|
xmlXPathErrMemory(NULL, "merging nodeset hit limit\n"); |
|
return(NULL); |
|
} |
|
temp = (xmlNodePtr *) xmlRealloc( |
|
set1->nodeTab, set1->nodeMax * 2 * sizeof(xmlNodePtr)); |
|
if (temp == NULL) { |
|
xmlXPathErrMemory(NULL, "merging nodeset\n"); |
|
return(NULL); |
|
} |
|
set1->nodeTab = temp; |
|
set1->nodeMax *= 2; |
|
} |
|
set1->nodeTab[set1->nodeNr++] = n2; |
|
skip_node: |
|
{} |
|
} |
|
} |
|
set2->nodeNr = 0; |
|
return(set1); |
|
} |
|
|
|
/** |
|
* xmlXPathNodeSetMergeAndClearNoDupls: |
|
* @set1: the first NodeSet or NULL |
|
* @set2: the second NodeSet |
|
* @hasSet2NsNodes: 1 if set2 contains namespaces nodes |
|
* |
|
* Merges two nodesets, all nodes from @set2 are added to @set1 |
|
* if @set1 is NULL, a new set is created and copied from @set2. |
|
* Doesn't chack for duplicate nodes. Clears set2. |
|
* |
|
* Returns @set1 once extended or NULL in case of error. |
|
*/ |
|
static xmlNodeSetPtr |
|
xmlXPathNodeSetMergeAndClearNoDupls(xmlNodeSetPtr set1, xmlNodeSetPtr set2, |
|
int hasNullEntries) |
|
{ |
|
if (set2 == NULL) |
|
return(set1); |
|
if ((set1 == NULL) && (hasNullEntries == 0)) { |
|
/* |
|
* Note that doing a memcpy of the list, namespace nodes are |
|
* just assigned to set1, since set2 is cleared anyway. |
|
*/ |
|
set1 = xmlXPathNodeSetCreateSize(set2->nodeNr); |
|
if (set1 == NULL) |
|
return(NULL); |
|
if (set2->nodeNr != 0) { |
|
memcpy(set1->nodeTab, set2->nodeTab, |
|
set2->nodeNr * sizeof(xmlNodePtr)); |
|
set1->nodeNr = set2->nodeNr; |
|
} |
|
} else { |
|
int i; |
|
xmlNodePtr n2; |
|
|
|
if (set1 == NULL) |
|
set1 = xmlXPathNodeSetCreate(NULL); |
|
if (set1 == NULL) |
|
return (NULL); |
|
|
|
for (i = 0;i < set2->nodeNr;i++) { |
|
n2 = set2->nodeTab[i]; |
|
/* |
|
* Skip NULLed entries. |
|
*/ |
|
if (n2 == NULL) |
|
continue; |
|
if (set1->nodeMax == 0) { |
|
set1->nodeTab = (xmlNodePtr *) xmlMalloc( |
|
XML_NODESET_DEFAULT * sizeof(xmlNodePtr)); |
|
if (set1->nodeTab == NULL) { |
|
xmlXPathErrMemory(NULL, "merging nodeset\n"); |
|
return(NULL); |
|
} |
|
memset(set1->nodeTab, 0, |
|
XML_NODESET_DEFAULT * (size_t) sizeof(xmlNodePtr)); |
|
set1->nodeMax = XML_NODESET_DEFAULT; |
|
} else if (set1->nodeNr >= set1->nodeMax) { |
|
xmlNodePtr *temp; |
|
|
|
if (set1->nodeMax >= XPATH_MAX_NODESET_LENGTH) { |
|
xmlXPathErrMemory(NULL, "merging nodeset hit limit\n"); |
|
return(NULL); |
|
} |
|
temp = (xmlNodePtr *) xmlRealloc( |
|
set1->nodeTab, set1->nodeMax * 2 * sizeof(xmlNodePtr)); |
|
if (temp == NULL) { |
|
xmlXPathErrMemory(NULL, "merging nodeset\n"); |
|
return(NULL); |
|
} |
|
set1->nodeTab = temp; |
|
set1->nodeMax *= 2; |
|
} |
|
set1->nodeTab[set1->nodeNr++] = n2; |
|
} |
|
} |
|
set2->nodeNr = 0; |
|
return(set1); |
|
} |
|
|
|
/** |
|
* xmlXPathNodeSetDel: |
|
* @cur: the initial node set |
|
* @val: an xmlNodePtr |
|
* |
|
* Removes an xmlNodePtr from an existing NodeSet |
|
*/ |
|
void |
|
xmlXPathNodeSetDel(xmlNodeSetPtr cur, xmlNodePtr val) { |
|
int i; |
|
|
|
if (cur == NULL) return; |
|
if (val == NULL) return; |
|
|
|
/* |
|
* find node in nodeTab |
|
*/ |
|
for (i = 0;i < cur->nodeNr;i++) |
|
if (cur->nodeTab[i] == val) break; |
|
|
|
if (i >= cur->nodeNr) { /* not found */ |
|
#ifdef DEBUG |
|
xmlGenericError(xmlGenericErrorContext, |
|
"xmlXPathNodeSetDel: Node %s wasn't found in NodeList\n", |
|
val->name); |
|
#endif |
|
return; |
|
} |
|
if ((cur->nodeTab[i] != NULL) && |
|
(cur->nodeTab[i]->type == XML_NAMESPACE_DECL)) |
|
xmlXPathNodeSetFreeNs((xmlNsPtr) cur->nodeTab[i]); |
|
cur->nodeNr--; |
|
for (;i < cur->nodeNr;i++) |
|
cur->nodeTab[i] = cur->nodeTab[i + 1]; |
|
cur->nodeTab[cur->nodeNr] = NULL; |
|
} |
|
|
|
/** |
|
* xmlXPathNodeSetRemove: |
|
* @cur: the initial node set |
|
* @val: the index to remove |
|
* |
|
* Removes an entry from an existing NodeSet list. |
|
*/ |
|
void |
|
xmlXPathNodeSetRemove(xmlNodeSetPtr cur, int val) { |
|
if (cur == NULL) return; |
|
if (val >= cur->nodeNr) return; |
|
if ((cur->nodeTab[val] != NULL) && |
|
(cur->nodeTab[val]->type == XML_NAMESPACE_DECL)) |
|
xmlXPathNodeSetFreeNs((xmlNsPtr) cur->nodeTab[val]); |
|
cur->nodeNr--; |
|
for (;val < cur->nodeNr;val++) |
|
cur->nodeTab[val] = cur->nodeTab[val + 1]; |
|
cur->nodeTab[cur->nodeNr] = NULL; |
|
} |
|
|
|
/** |
|
* xmlXPathFreeNodeSet: |
|
* @obj: the xmlNodeSetPtr to free |
|
* |
|
* Free the NodeSet compound (not the actual nodes !). |
|
*/ |
|
void |
|
xmlXPathFreeNodeSet(xmlNodeSetPtr obj) { |
|
if (obj == NULL) return; |
|
if (obj->nodeTab != NULL) { |
|
int i; |
|
|
|
/* @@ with_ns to check whether namespace nodes should be looked at @@ */ |
|
for (i = 0;i < obj->nodeNr;i++) |
|
if ((obj->nodeTab[i] != NULL) && |
|
(obj->nodeTab[i]->type == XML_NAMESPACE_DECL)) |
|
xmlXPathNodeSetFreeNs((xmlNsPtr) obj->nodeTab[i]); |
|
xmlFree(obj->nodeTab); |
|
} |
|
xmlFree(obj); |
|
} |
|
|
|
/** |
|
* xmlXPathNodeSetClearFromPos: |
|
* @set: the node set to be cleared |
|
* @pos: the start position to clear from |
|
* |
|
* Clears the list from temporary XPath objects (e.g. namespace nodes |
|
* are feed) starting with the entry at @pos, but does *not* free the list |
|
* itself. Sets the length of the list to @pos. |
|
*/ |
|
static void |
|
xmlXPathNodeSetClearFromPos(xmlNodeSetPtr set, int pos, int hasNsNodes) |
|
{ |
|
if ((set == NULL) || (pos >= set->nodeNr)) |
|
return; |
|
else if ((hasNsNodes)) { |
|
int i; |
|
xmlNodePtr node; |
|
|
|
for (i = pos; i < set->nodeNr; i++) { |
|
node = set->nodeTab[i]; |
|
if ((node != NULL) && |
|
(node->type == XML_NAMESPACE_DECL)) |
|
xmlXPathNodeSetFreeNs((xmlNsPtr) node); |
|
} |
|
} |
|
set->nodeNr = pos; |
|
} |
|
|
|
/** |
|
* xmlXPathNodeSetClear: |
|
* @set: the node set to clear |
|
* |
|
* Clears the list from all temporary XPath objects (e.g. namespace nodes |
|
* are feed), but does *not* free the list itself. Sets the length of the |
|
* list to 0. |
|
*/ |
|
static void |
|
xmlXPathNodeSetClear(xmlNodeSetPtr set, int hasNsNodes) |
|
{ |
|
xmlXPathNodeSetClearFromPos(set, 0, hasNsNodes); |
|
} |
|
|
|
/** |
|
* xmlXPathNodeSetKeepLast: |
|
* @set: the node set to be cleared |
|
* |
|
* Move the last node to the first position and clear temporary XPath objects |
|
* (e.g. namespace nodes) from all other nodes. Sets the length of the list |
|
* to 1. |
|
*/ |
|
static void |
|
xmlXPathNodeSetKeepLast(xmlNodeSetPtr set) |
|
{ |
|
int i; |
|
xmlNodePtr node; |
|
|
|
if ((set == NULL) || (set->nodeNr <= 1)) |
|
return; |
|
for (i = 0; i < set->nodeNr - 1; i++) { |
|
node = set->nodeTab[i]; |
|
if ((node != NULL) && |
|
(node->type == XML_NAMESPACE_DECL)) |
|
xmlXPathNodeSetFreeNs((xmlNsPtr) node); |
|
} |
|
set->nodeTab[0] = set->nodeTab[set->nodeNr-1]; |
|
set->nodeNr = 1; |
|
} |
|
|
|
/** |
|
* xmlXPathFreeValueTree: |
|
* @obj: the xmlNodeSetPtr to free |
|
* |
|
* Free the NodeSet compound and the actual tree, this is different |
|
* from xmlXPathFreeNodeSet() |
|
*/ |
|
static void |
|
xmlXPathFreeValueTree(xmlNodeSetPtr obj) { |
|
int i; |
|
|
|
if (obj == NULL) return; |
|
|
|
if (obj->nodeTab != NULL) { |
|
for (i = 0;i < obj->nodeNr;i++) { |
|
if (obj->nodeTab[i] != NULL) { |
|
if (obj->nodeTab[i]->type == XML_NAMESPACE_DECL) { |
|
xmlXPathNodeSetFreeNs((xmlNsPtr) obj->nodeTab[i]); |
|
} else { |
|
xmlFreeNodeList(obj->nodeTab[i]); |
|
} |
|
} |
|
} |
|
xmlFree(obj->nodeTab); |
|
} |
|
xmlFree(obj); |
|
} |
|
|
|
#if defined(DEBUG) || defined(DEBUG_STEP) |
|
/** |
|
* xmlGenericErrorContextNodeSet: |
|
* @output: a FILE * for the output |
|
* @obj: the xmlNodeSetPtr to display |
|
* |
|
* Quick display of a NodeSet |
|
*/ |
|
void |
|
xmlGenericErrorContextNodeSet(FILE *output, xmlNodeSetPtr obj) { |
|
int i; |
|
|
|
if (output == NULL) output = xmlGenericErrorContext; |
|
if (obj == NULL) { |
|
fprintf(output, "NodeSet == NULL !\n"); |
|
return; |
|
} |
|
if (obj->nodeNr == 0) { |
|
fprintf(output, "NodeSet is empty\n"); |
|
return; |
|
} |
|
if (obj->nodeTab == NULL) { |
|
fprintf(output, " nodeTab == NULL !\n"); |
|
return; |
|
} |
|
for (i = 0; i < obj->nodeNr; i++) { |
|
if (obj->nodeTab[i] == NULL) { |
|
fprintf(output, " NULL !\n"); |
|
return; |
|
} |
|
if ((obj->nodeTab[i]->type == XML_DOCUMENT_NODE) || |
|
(obj->nodeTab[i]->type == XML_HTML_DOCUMENT_NODE)) |
|
fprintf(output, " /"); |
|
else if (obj->nodeTab[i]->name == NULL) |
|
fprintf(output, " noname!"); |
|
else fprintf(output, " %s", obj->nodeTab[i]->name); |
|
} |
|
fprintf(output, "\n"); |
|
} |
|
#endif |
|
|
|
/** |
|
* xmlXPathNewNodeSet: |
|
* @val: the NodePtr value |
|
* |
|
* Create a new xmlXPathObjectPtr of type NodeSet and initialize |
|
* it with the single Node @val |
|
* |
|
* Returns the newly created object. |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathNewNodeSet(xmlNodePtr val) { |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) xmlMalloc(sizeof(xmlXPathObject)); |
|
if (ret == NULL) { |
|
xmlXPathErrMemory(NULL, "creating nodeset\n"); |
|
return(NULL); |
|
} |
|
memset(ret, 0 , (size_t) sizeof(xmlXPathObject)); |
|
ret->type = XPATH_NODESET; |
|
ret->boolval = 0; |
|
ret->nodesetval = xmlXPathNodeSetCreate(val); |
|
/* @@ with_ns to check whether namespace nodes should be looked at @@ */ |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(NULL, XPATH_NODESET); |
|
#endif |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathNewValueTree: |
|
* @val: the NodePtr value |
|
* |
|
* Create a new xmlXPathObjectPtr of type Value Tree (XSLT) and initialize |
|
* it with the tree root @val |
|
* |
|
* Returns the newly created object. |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathNewValueTree(xmlNodePtr val) { |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) xmlMalloc(sizeof(xmlXPathObject)); |
|
if (ret == NULL) { |
|
xmlXPathErrMemory(NULL, "creating result value tree\n"); |
|
return(NULL); |
|
} |
|
memset(ret, 0 , (size_t) sizeof(xmlXPathObject)); |
|
ret->type = XPATH_XSLT_TREE; |
|
ret->boolval = 0; |
|
ret->user = (void *) val; |
|
ret->nodesetval = xmlXPathNodeSetCreate(val); |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(NULL, XPATH_XSLT_TREE); |
|
#endif |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathNewNodeSetList: |
|
* @val: an existing NodeSet |
|
* |
|
* Create a new xmlXPathObjectPtr of type NodeSet and initialize |
|
* it with the Nodeset @val |
|
* |
|
* Returns the newly created object. |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathNewNodeSetList(xmlNodeSetPtr val) |
|
{ |
|
xmlXPathObjectPtr ret; |
|
int i; |
|
|
|
if (val == NULL) |
|
ret = NULL; |
|
else if (val->nodeTab == NULL) |
|
ret = xmlXPathNewNodeSet(NULL); |
|
else { |
|
ret = xmlXPathNewNodeSet(val->nodeTab[0]); |
|
if (ret) { |
|
for (i = 1; i < val->nodeNr; ++i) { |
|
if (xmlXPathNodeSetAddUnique(ret->nodesetval, val->nodeTab[i]) |
|
< 0) break; |
|
} |
|
} |
|
} |
|
|
|
return (ret); |
|
} |
|
|
|
/** |
|
* xmlXPathWrapNodeSet: |
|
* @val: the NodePtr value |
|
* |
|
* Wrap the Nodeset @val in a new xmlXPathObjectPtr |
|
* |
|
* Returns the newly created object. |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathWrapNodeSet(xmlNodeSetPtr val) { |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) xmlMalloc(sizeof(xmlXPathObject)); |
|
if (ret == NULL) { |
|
xmlXPathErrMemory(NULL, "creating node set object\n"); |
|
return(NULL); |
|
} |
|
memset(ret, 0 , (size_t) sizeof(xmlXPathObject)); |
|
ret->type = XPATH_NODESET; |
|
ret->nodesetval = val; |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(NULL, XPATH_NODESET); |
|
#endif |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathFreeNodeSetList: |
|
* @obj: an existing NodeSetList object |
|
* |
|
* Free up the xmlXPathObjectPtr @obj but don't deallocate the objects in |
|
* the list contrary to xmlXPathFreeObject(). |
|
*/ |
|
void |
|
xmlXPathFreeNodeSetList(xmlXPathObjectPtr obj) { |
|
if (obj == NULL) return; |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageReleased(NULL, obj->type); |
|
#endif |
|
xmlFree(obj); |
|
} |
|
|
|
/** |
|
* xmlXPathDifference: |
|
* @nodes1: a node-set |
|
* @nodes2: a node-set |
|
* |
|
* Implements the EXSLT - Sets difference() function: |
|
* node-set set:difference (node-set, node-set) |
|
* |
|
* Returns the difference between the two node sets, or nodes1 if |
|
* nodes2 is empty |
|
*/ |
|
xmlNodeSetPtr |
|
xmlXPathDifference (xmlNodeSetPtr nodes1, xmlNodeSetPtr nodes2) { |
|
xmlNodeSetPtr ret; |
|
int i, l1; |
|
xmlNodePtr cur; |
|
|
|
if (xmlXPathNodeSetIsEmpty(nodes2)) |
|
return(nodes1); |
|
|
|
ret = xmlXPathNodeSetCreate(NULL); |
|
if (xmlXPathNodeSetIsEmpty(nodes1)) |
|
return(ret); |
|
|
|
l1 = xmlXPathNodeSetGetLength(nodes1); |
|
|
|
for (i = 0; i < l1; i++) { |
|
cur = xmlXPathNodeSetItem(nodes1, i); |
|
if (!xmlXPathNodeSetContains(nodes2, cur)) { |
|
if (xmlXPathNodeSetAddUnique(ret, cur) < 0) |
|
break; |
|
} |
|
} |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathIntersection: |
|
* @nodes1: a node-set |
|
* @nodes2: a node-set |
|
* |
|
* Implements the EXSLT - Sets intersection() function: |
|
* node-set set:intersection (node-set, node-set) |
|
* |
|
* Returns a node set comprising the nodes that are within both the |
|
* node sets passed as arguments |
|
*/ |
|
xmlNodeSetPtr |
|
xmlXPathIntersection (xmlNodeSetPtr nodes1, xmlNodeSetPtr nodes2) { |
|
xmlNodeSetPtr ret = xmlXPathNodeSetCreate(NULL); |
|
int i, l1; |
|
xmlNodePtr cur; |
|
|
|
if (ret == NULL) |
|
return(ret); |
|
if (xmlXPathNodeSetIsEmpty(nodes1)) |
|
return(ret); |
|
if (xmlXPathNodeSetIsEmpty(nodes2)) |
|
return(ret); |
|
|
|
l1 = xmlXPathNodeSetGetLength(nodes1); |
|
|
|
for (i = 0; i < l1; i++) { |
|
cur = xmlXPathNodeSetItem(nodes1, i); |
|
if (xmlXPathNodeSetContains(nodes2, cur)) { |
|
if (xmlXPathNodeSetAddUnique(ret, cur) < 0) |
|
break; |
|
} |
|
} |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathDistinctSorted: |
|
* @nodes: a node-set, sorted by document order |
|
* |
|
* Implements the EXSLT - Sets distinct() function: |
|
* node-set set:distinct (node-set) |
|
* |
|
* Returns a subset of the nodes contained in @nodes, or @nodes if |
|
* it is empty |
|
*/ |
|
xmlNodeSetPtr |
|
xmlXPathDistinctSorted (xmlNodeSetPtr nodes) { |
|
xmlNodeSetPtr ret; |
|
xmlHashTablePtr hash; |
|
int i, l; |
|
xmlChar * strval; |
|
xmlNodePtr cur; |
|
|
|
if (xmlXPathNodeSetIsEmpty(nodes)) |
|
return(nodes); |
|
|
|
ret = xmlXPathNodeSetCreate(NULL); |
|
if (ret == NULL) |
|
return(ret); |
|
l = xmlXPathNodeSetGetLength(nodes); |
|
hash = xmlHashCreate (l); |
|
for (i = 0; i < l; i++) { |
|
cur = xmlXPathNodeSetItem(nodes, i); |
|
strval = xmlXPathCastNodeToString(cur); |
|
if (xmlHashLookup(hash, strval) == NULL) { |
|
xmlHashAddEntry(hash, strval, strval); |
|
if (xmlXPathNodeSetAddUnique(ret, cur) < 0) |
|
break; |
|
} else { |
|
xmlFree(strval); |
|
} |
|
} |
|
xmlHashFree(hash, xmlHashDefaultDeallocator); |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathDistinct: |
|
* @nodes: a node-set |
|
* |
|
* Implements the EXSLT - Sets distinct() function: |
|
* node-set set:distinct (node-set) |
|
* @nodes is sorted by document order, then #exslSetsDistinctSorted |
|
* is called with the sorted node-set |
|
* |
|
* Returns a subset of the nodes contained in @nodes, or @nodes if |
|
* it is empty |
|
*/ |
|
xmlNodeSetPtr |
|
xmlXPathDistinct (xmlNodeSetPtr nodes) { |
|
if (xmlXPathNodeSetIsEmpty(nodes)) |
|
return(nodes); |
|
|
|
xmlXPathNodeSetSort(nodes); |
|
return(xmlXPathDistinctSorted(nodes)); |
|
} |
|
|
|
/** |
|
* xmlXPathHasSameNodes: |
|
* @nodes1: a node-set |
|
* @nodes2: a node-set |
|
* |
|
* Implements the EXSLT - Sets has-same-nodes function: |
|
* boolean set:has-same-node(node-set, node-set) |
|
* |
|
* Returns true (1) if @nodes1 shares any node with @nodes2, false (0) |
|
* otherwise |
|
*/ |
|
int |
|
xmlXPathHasSameNodes (xmlNodeSetPtr nodes1, xmlNodeSetPtr nodes2) { |
|
int i, l; |
|
xmlNodePtr cur; |
|
|
|
if (xmlXPathNodeSetIsEmpty(nodes1) || |
|
xmlXPathNodeSetIsEmpty(nodes2)) |
|
return(0); |
|
|
|
l = xmlXPathNodeSetGetLength(nodes1); |
|
for (i = 0; i < l; i++) { |
|
cur = xmlXPathNodeSetItem(nodes1, i); |
|
if (xmlXPathNodeSetContains(nodes2, cur)) |
|
return(1); |
|
} |
|
return(0); |
|
} |
|
|
|
/** |
|
* xmlXPathNodeLeadingSorted: |
|
* @nodes: a node-set, sorted by document order |
|
* @node: a node |
|
* |
|
* Implements the EXSLT - Sets leading() function: |
|
* node-set set:leading (node-set, node-set) |
|
* |
|
* Returns the nodes in @nodes that precede @node in document order, |
|
* @nodes if @node is NULL or an empty node-set if @nodes |
|
* doesn't contain @node |
|
*/ |
|
xmlNodeSetPtr |
|
xmlXPathNodeLeadingSorted (xmlNodeSetPtr nodes, xmlNodePtr node) { |
|
int i, l; |
|
xmlNodePtr cur; |
|
xmlNodeSetPtr ret; |
|
|
|
if (node == NULL) |
|
return(nodes); |
|
|
|
ret = xmlXPathNodeSetCreate(NULL); |
|
if (ret == NULL) |
|
return(ret); |
|
if (xmlXPathNodeSetIsEmpty(nodes) || |
|
(!xmlXPathNodeSetContains(nodes, node))) |
|
return(ret); |
|
|
|
l = xmlXPathNodeSetGetLength(nodes); |
|
for (i = 0; i < l; i++) { |
|
cur = xmlXPathNodeSetItem(nodes, i); |
|
if (cur == node) |
|
break; |
|
if (xmlXPathNodeSetAddUnique(ret, cur) < 0) |
|
break; |
|
} |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathNodeLeading: |
|
* @nodes: a node-set |
|
* @node: a node |
|
* |
|
* Implements the EXSLT - Sets leading() function: |
|
* node-set set:leading (node-set, node-set) |
|
* @nodes is sorted by document order, then #exslSetsNodeLeadingSorted |
|
* is called. |
|
* |
|
* Returns the nodes in @nodes that precede @node in document order, |
|
* @nodes if @node is NULL or an empty node-set if @nodes |
|
* doesn't contain @node |
|
*/ |
|
xmlNodeSetPtr |
|
xmlXPathNodeLeading (xmlNodeSetPtr nodes, xmlNodePtr node) { |
|
xmlXPathNodeSetSort(nodes); |
|
return(xmlXPathNodeLeadingSorted(nodes, node)); |
|
} |
|
|
|
/** |
|
* xmlXPathLeadingSorted: |
|
* @nodes1: a node-set, sorted by document order |
|
* @nodes2: a node-set, sorted by document order |
|
* |
|
* Implements the EXSLT - Sets leading() function: |
|
* node-set set:leading (node-set, node-set) |
|
* |
|
* Returns the nodes in @nodes1 that precede the first node in @nodes2 |
|
* in document order, @nodes1 if @nodes2 is NULL or empty or |
|
* an empty node-set if @nodes1 doesn't contain @nodes2 |
|
*/ |
|
xmlNodeSetPtr |
|
xmlXPathLeadingSorted (xmlNodeSetPtr nodes1, xmlNodeSetPtr nodes2) { |
|
if (xmlXPathNodeSetIsEmpty(nodes2)) |
|
return(nodes1); |
|
return(xmlXPathNodeLeadingSorted(nodes1, |
|
xmlXPathNodeSetItem(nodes2, 1))); |
|
} |
|
|
|
/** |
|
* xmlXPathLeading: |
|
* @nodes1: a node-set |
|
* @nodes2: a node-set |
|
* |
|
* Implements the EXSLT - Sets leading() function: |
|
* node-set set:leading (node-set, node-set) |
|
* @nodes1 and @nodes2 are sorted by document order, then |
|
* #exslSetsLeadingSorted is called. |
|
* |
|
* Returns the nodes in @nodes1 that precede the first node in @nodes2 |
|
* in document order, @nodes1 if @nodes2 is NULL or empty or |
|
* an empty node-set if @nodes1 doesn't contain @nodes2 |
|
*/ |
|
xmlNodeSetPtr |
|
xmlXPathLeading (xmlNodeSetPtr nodes1, xmlNodeSetPtr nodes2) { |
|
if (xmlXPathNodeSetIsEmpty(nodes2)) |
|
return(nodes1); |
|
if (xmlXPathNodeSetIsEmpty(nodes1)) |
|
return(xmlXPathNodeSetCreate(NULL)); |
|
xmlXPathNodeSetSort(nodes1); |
|
xmlXPathNodeSetSort(nodes2); |
|
return(xmlXPathNodeLeadingSorted(nodes1, |
|
xmlXPathNodeSetItem(nodes2, 1))); |
|
} |
|
|
|
/** |
|
* xmlXPathNodeTrailingSorted: |
|
* @nodes: a node-set, sorted by document order |
|
* @node: a node |
|
* |
|
* Implements the EXSLT - Sets trailing() function: |
|
* node-set set:trailing (node-set, node-set) |
|
* |
|
* Returns the nodes in @nodes that follow @node in document order, |
|
* @nodes if @node is NULL or an empty node-set if @nodes |
|
* doesn't contain @node |
|
*/ |
|
xmlNodeSetPtr |
|
xmlXPathNodeTrailingSorted (xmlNodeSetPtr nodes, xmlNodePtr node) { |
|
int i, l; |
|
xmlNodePtr cur; |
|
xmlNodeSetPtr ret; |
|
|
|
if (node == NULL) |
|
return(nodes); |
|
|
|
ret = xmlXPathNodeSetCreate(NULL); |
|
if (ret == NULL) |
|
return(ret); |
|
if (xmlXPathNodeSetIsEmpty(nodes) || |
|
(!xmlXPathNodeSetContains(nodes, node))) |
|
return(ret); |
|
|
|
l = xmlXPathNodeSetGetLength(nodes); |
|
for (i = l - 1; i >= 0; i--) { |
|
cur = xmlXPathNodeSetItem(nodes, i); |
|
if (cur == node) |
|
break; |
|
if (xmlXPathNodeSetAddUnique(ret, cur) < 0) |
|
break; |
|
} |
|
xmlXPathNodeSetSort(ret); /* bug 413451 */ |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathNodeTrailing: |
|
* @nodes: a node-set |
|
* @node: a node |
|
* |
|
* Implements the EXSLT - Sets trailing() function: |
|
* node-set set:trailing (node-set, node-set) |
|
* @nodes is sorted by document order, then #xmlXPathNodeTrailingSorted |
|
* is called. |
|
* |
|
* Returns the nodes in @nodes that follow @node in document order, |
|
* @nodes if @node is NULL or an empty node-set if @nodes |
|
* doesn't contain @node |
|
*/ |
|
xmlNodeSetPtr |
|
xmlXPathNodeTrailing (xmlNodeSetPtr nodes, xmlNodePtr node) { |
|
xmlXPathNodeSetSort(nodes); |
|
return(xmlXPathNodeTrailingSorted(nodes, node)); |
|
} |
|
|
|
/** |
|
* xmlXPathTrailingSorted: |
|
* @nodes1: a node-set, sorted by document order |
|
* @nodes2: a node-set, sorted by document order |
|
* |
|
* Implements the EXSLT - Sets trailing() function: |
|
* node-set set:trailing (node-set, node-set) |
|
* |
|
* Returns the nodes in @nodes1 that follow the first node in @nodes2 |
|
* in document order, @nodes1 if @nodes2 is NULL or empty or |
|
* an empty node-set if @nodes1 doesn't contain @nodes2 |
|
*/ |
|
xmlNodeSetPtr |
|
xmlXPathTrailingSorted (xmlNodeSetPtr nodes1, xmlNodeSetPtr nodes2) { |
|
if (xmlXPathNodeSetIsEmpty(nodes2)) |
|
return(nodes1); |
|
return(xmlXPathNodeTrailingSorted(nodes1, |
|
xmlXPathNodeSetItem(nodes2, 0))); |
|
} |
|
|
|
/** |
|
* xmlXPathTrailing: |
|
* @nodes1: a node-set |
|
* @nodes2: a node-set |
|
* |
|
* Implements the EXSLT - Sets trailing() function: |
|
* node-set set:trailing (node-set, node-set) |
|
* @nodes1 and @nodes2 are sorted by document order, then |
|
* #xmlXPathTrailingSorted is called. |
|
* |
|
* Returns the nodes in @nodes1 that follow the first node in @nodes2 |
|
* in document order, @nodes1 if @nodes2 is NULL or empty or |
|
* an empty node-set if @nodes1 doesn't contain @nodes2 |
|
*/ |
|
xmlNodeSetPtr |
|
xmlXPathTrailing (xmlNodeSetPtr nodes1, xmlNodeSetPtr nodes2) { |
|
if (xmlXPathNodeSetIsEmpty(nodes2)) |
|
return(nodes1); |
|
if (xmlXPathNodeSetIsEmpty(nodes1)) |
|
return(xmlXPathNodeSetCreate(NULL)); |
|
xmlXPathNodeSetSort(nodes1); |
|
xmlXPathNodeSetSort(nodes2); |
|
return(xmlXPathNodeTrailingSorted(nodes1, |
|
xmlXPathNodeSetItem(nodes2, 0))); |
|
} |
|
|
|
/************************************************************************ |
|
* * |
|
* Routines to handle extra functions * |
|
* * |
|
************************************************************************/ |
|
|
|
/** |
|
* xmlXPathRegisterFunc: |
|
* @ctxt: the XPath context |
|
* @name: the function name |
|
* @f: the function implementation or NULL |
|
* |
|
* Register a new function. If @f is NULL it unregisters the function |
|
* |
|
* Returns 0 in case of success, -1 in case of error |
|
*/ |
|
int |
|
xmlXPathRegisterFunc(xmlXPathContextPtr ctxt, const xmlChar *name, |
|
xmlXPathFunction f) { |
|
return(xmlXPathRegisterFuncNS(ctxt, name, NULL, f)); |
|
} |
|
|
|
/** |
|
* xmlXPathRegisterFuncNS: |
|
* @ctxt: the XPath context |
|
* @name: the function name |
|
* @ns_uri: the function namespace URI |
|
* @f: the function implementation or NULL |
|
* |
|
* Register a new function. If @f is NULL it unregisters the function |
|
* |
|
* Returns 0 in case of success, -1 in case of error |
|
*/ |
|
int |
|
xmlXPathRegisterFuncNS(xmlXPathContextPtr ctxt, const xmlChar *name, |
|
const xmlChar *ns_uri, xmlXPathFunction f) { |
|
if (ctxt == NULL) |
|
return(-1); |
|
if (name == NULL) |
|
return(-1); |
|
|
|
if (ctxt->funcHash == NULL) |
|
ctxt->funcHash = xmlHashCreate(0); |
|
if (ctxt->funcHash == NULL) |
|
return(-1); |
|
if (f == NULL) |
|
return(xmlHashRemoveEntry2(ctxt->funcHash, name, ns_uri, NULL)); |
|
XML_IGNORE_PEDANTIC_WARNINGS |
|
return(xmlHashAddEntry2(ctxt->funcHash, name, ns_uri, (void *) f)); |
|
XML_POP_WARNINGS |
|
} |
|
|
|
/** |
|
* xmlXPathRegisterFuncLookup: |
|
* @ctxt: the XPath context |
|
* @f: the lookup function |
|
* @funcCtxt: the lookup data |
|
* |
|
* Registers an external mechanism to do function lookup. |
|
*/ |
|
void |
|
xmlXPathRegisterFuncLookup (xmlXPathContextPtr ctxt, |
|
xmlXPathFuncLookupFunc f, |
|
void *funcCtxt) { |
|
if (ctxt == NULL) |
|
return; |
|
ctxt->funcLookupFunc = f; |
|
ctxt->funcLookupData = funcCtxt; |
|
} |
|
|
|
/** |
|
* xmlXPathFunctionLookup: |
|
* @ctxt: the XPath context |
|
* @name: the function name |
|
* |
|
* Search in the Function array of the context for the given |
|
* function. |
|
* |
|
* Returns the xmlXPathFunction or NULL if not found |
|
*/ |
|
xmlXPathFunction |
|
xmlXPathFunctionLookup(xmlXPathContextPtr ctxt, const xmlChar *name) { |
|
if (ctxt == NULL) |
|
return (NULL); |
|
|
|
if (ctxt->funcLookupFunc != NULL) { |
|
xmlXPathFunction ret; |
|
xmlXPathFuncLookupFunc f; |
|
|
|
f = ctxt->funcLookupFunc; |
|
ret = f(ctxt->funcLookupData, name, NULL); |
|
if (ret != NULL) |
|
return(ret); |
|
} |
|
return(xmlXPathFunctionLookupNS(ctxt, name, NULL)); |
|
} |
|
|
|
/** |
|
* xmlXPathFunctionLookupNS: |
|
* @ctxt: the XPath context |
|
* @name: the function name |
|
* @ns_uri: the function namespace URI |
|
* |
|
* Search in the Function array of the context for the given |
|
* function. |
|
* |
|
* Returns the xmlXPathFunction or NULL if not found |
|
*/ |
|
xmlXPathFunction |
|
xmlXPathFunctionLookupNS(xmlXPathContextPtr ctxt, const xmlChar *name, |
|
const xmlChar *ns_uri) { |
|
xmlXPathFunction ret; |
|
|
|
if (ctxt == NULL) |
|
return(NULL); |
|
if (name == NULL) |
|
return(NULL); |
|
|
|
if (ctxt->funcLookupFunc != NULL) { |
|
xmlXPathFuncLookupFunc f; |
|
|
|
f = ctxt->funcLookupFunc; |
|
ret = f(ctxt->funcLookupData, name, ns_uri); |
|
if (ret != NULL) |
|
return(ret); |
|
} |
|
|
|
if (ctxt->funcHash == NULL) |
|
return(NULL); |
|
|
|
XML_IGNORE_PEDANTIC_WARNINGS |
|
ret = (xmlXPathFunction) xmlHashLookup2(ctxt->funcHash, name, ns_uri); |
|
XML_POP_WARNINGS |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathRegisteredFuncsCleanup: |
|
* @ctxt: the XPath context |
|
* |
|
* Cleanup the XPath context data associated to registered functions |
|
*/ |
|
void |
|
xmlXPathRegisteredFuncsCleanup(xmlXPathContextPtr ctxt) { |
|
if (ctxt == NULL) |
|
return; |
|
|
|
xmlHashFree(ctxt->funcHash, NULL); |
|
ctxt->funcHash = NULL; |
|
} |
|
|
|
/************************************************************************ |
|
* * |
|
* Routines to handle Variables * |
|
* * |
|
************************************************************************/ |
|
|
|
/** |
|
* xmlXPathRegisterVariable: |
|
* @ctxt: the XPath context |
|
* @name: the variable name |
|
* @value: the variable value or NULL |
|
* |
|
* Register a new variable value. If @value is NULL it unregisters |
|
* the variable |
|
* |
|
* Returns 0 in case of success, -1 in case of error |
|
*/ |
|
int |
|
xmlXPathRegisterVariable(xmlXPathContextPtr ctxt, const xmlChar *name, |
|
xmlXPathObjectPtr value) { |
|
return(xmlXPathRegisterVariableNS(ctxt, name, NULL, value)); |
|
} |
|
|
|
/** |
|
* xmlXPathRegisterVariableNS: |
|
* @ctxt: the XPath context |
|
* @name: the variable name |
|
* @ns_uri: the variable namespace URI |
|
* @value: the variable value or NULL |
|
* |
|
* Register a new variable value. If @value is NULL it unregisters |
|
* the variable |
|
* |
|
* Returns 0 in case of success, -1 in case of error |
|
*/ |
|
int |
|
xmlXPathRegisterVariableNS(xmlXPathContextPtr ctxt, const xmlChar *name, |
|
const xmlChar *ns_uri, |
|
xmlXPathObjectPtr value) { |
|
if (ctxt == NULL) |
|
return(-1); |
|
if (name == NULL) |
|
return(-1); |
|
|
|
if (ctxt->varHash == NULL) |
|
ctxt->varHash = xmlHashCreate(0); |
|
if (ctxt->varHash == NULL) |
|
return(-1); |
|
if (value == NULL) |
|
return(xmlHashRemoveEntry2(ctxt->varHash, name, ns_uri, |
|
xmlXPathFreeObjectEntry)); |
|
return(xmlHashUpdateEntry2(ctxt->varHash, name, ns_uri, |
|
(void *) value, xmlXPathFreeObjectEntry)); |
|
} |
|
|
|
/** |
|
* xmlXPathRegisterVariableLookup: |
|
* @ctxt: the XPath context |
|
* @f: the lookup function |
|
* @data: the lookup data |
|
* |
|
* register an external mechanism to do variable lookup |
|
*/ |
|
void |
|
xmlXPathRegisterVariableLookup(xmlXPathContextPtr ctxt, |
|
xmlXPathVariableLookupFunc f, void *data) { |
|
if (ctxt == NULL) |
|
return; |
|
ctxt->varLookupFunc = f; |
|
ctxt->varLookupData = data; |
|
} |
|
|
|
/** |
|
* xmlXPathVariableLookup: |
|
* @ctxt: the XPath context |
|
* @name: the variable name |
|
* |
|
* Search in the Variable array of the context for the given |
|
* variable value. |
|
* |
|
* Returns a copy of the value or NULL if not found |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathVariableLookup(xmlXPathContextPtr ctxt, const xmlChar *name) { |
|
if (ctxt == NULL) |
|
return(NULL); |
|
|
|
if (ctxt->varLookupFunc != NULL) { |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = ((xmlXPathVariableLookupFunc)ctxt->varLookupFunc) |
|
(ctxt->varLookupData, name, NULL); |
|
return(ret); |
|
} |
|
return(xmlXPathVariableLookupNS(ctxt, name, NULL)); |
|
} |
|
|
|
/** |
|
* xmlXPathVariableLookupNS: |
|
* @ctxt: the XPath context |
|
* @name: the variable name |
|
* @ns_uri: the variable namespace URI |
|
* |
|
* Search in the Variable array of the context for the given |
|
* variable value. |
|
* |
|
* Returns the a copy of the value or NULL if not found |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathVariableLookupNS(xmlXPathContextPtr ctxt, const xmlChar *name, |
|
const xmlChar *ns_uri) { |
|
if (ctxt == NULL) |
|
return(NULL); |
|
|
|
if (ctxt->varLookupFunc != NULL) { |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = ((xmlXPathVariableLookupFunc)ctxt->varLookupFunc) |
|
(ctxt->varLookupData, name, ns_uri); |
|
if (ret != NULL) return(ret); |
|
} |
|
|
|
if (ctxt->varHash == NULL) |
|
return(NULL); |
|
if (name == NULL) |
|
return(NULL); |
|
|
|
return(xmlXPathCacheObjectCopy(ctxt, (xmlXPathObjectPtr) |
|
xmlHashLookup2(ctxt->varHash, name, ns_uri))); |
|
} |
|
|
|
/** |
|
* xmlXPathRegisteredVariablesCleanup: |
|
* @ctxt: the XPath context |
|
* |
|
* Cleanup the XPath context data associated to registered variables |
|
*/ |
|
void |
|
xmlXPathRegisteredVariablesCleanup(xmlXPathContextPtr ctxt) { |
|
if (ctxt == NULL) |
|
return; |
|
|
|
xmlHashFree(ctxt->varHash, xmlXPathFreeObjectEntry); |
|
ctxt->varHash = NULL; |
|
} |
|
|
|
/** |
|
* xmlXPathRegisterNs: |
|
* @ctxt: the XPath context |
|
* @prefix: the namespace prefix cannot be NULL or empty string |
|
* @ns_uri: the namespace name |
|
* |
|
* Register a new namespace. If @ns_uri is NULL it unregisters |
|
* the namespace |
|
* |
|
* Returns 0 in case of success, -1 in case of error |
|
*/ |
|
int |
|
xmlXPathRegisterNs(xmlXPathContextPtr ctxt, const xmlChar *prefix, |
|
const xmlChar *ns_uri) { |
|
if (ctxt == NULL) |
|
return(-1); |
|
if (prefix == NULL) |
|
return(-1); |
|
if (prefix[0] == 0) |
|
return(-1); |
|
|
|
if (ctxt->nsHash == NULL) |
|
ctxt->nsHash = xmlHashCreate(10); |
|
if (ctxt->nsHash == NULL) |
|
return(-1); |
|
if (ns_uri == NULL) |
|
return(xmlHashRemoveEntry(ctxt->nsHash, prefix, |
|
xmlHashDefaultDeallocator)); |
|
return(xmlHashUpdateEntry(ctxt->nsHash, prefix, (void *) xmlStrdup(ns_uri), |
|
xmlHashDefaultDeallocator)); |
|
} |
|
|
|
/** |
|
* xmlXPathNsLookup: |
|
* @ctxt: the XPath context |
|
* @prefix: the namespace prefix value |
|
* |
|
* Search in the namespace declaration array of the context for the given |
|
* namespace name associated to the given prefix |
|
* |
|
* Returns the value or NULL if not found |
|
*/ |
|
const xmlChar * |
|
xmlXPathNsLookup(xmlXPathContextPtr ctxt, const xmlChar *prefix) { |
|
if (ctxt == NULL) |
|
return(NULL); |
|
if (prefix == NULL) |
|
return(NULL); |
|
|
|
#ifdef XML_XML_NAMESPACE |
|
if (xmlStrEqual(prefix, (const xmlChar *) "xml")) |
|
return(XML_XML_NAMESPACE); |
|
#endif |
|
|
|
if (ctxt->namespaces != NULL) { |
|
int i; |
|
|
|
for (i = 0;i < ctxt->nsNr;i++) { |
|
if ((ctxt->namespaces[i] != NULL) && |
|
(xmlStrEqual(ctxt->namespaces[i]->prefix, prefix))) |
|
return(ctxt->namespaces[i]->href); |
|
} |
|
} |
|
|
|
return((const xmlChar *) xmlHashLookup(ctxt->nsHash, prefix)); |
|
} |
|
|
|
/** |
|
* xmlXPathRegisteredNsCleanup: |
|
* @ctxt: the XPath context |
|
* |
|
* Cleanup the XPath context data associated to registered variables |
|
*/ |
|
void |
|
xmlXPathRegisteredNsCleanup(xmlXPathContextPtr ctxt) { |
|
if (ctxt == NULL) |
|
return; |
|
|
|
xmlHashFree(ctxt->nsHash, xmlHashDefaultDeallocator); |
|
ctxt->nsHash = NULL; |
|
} |
|
|
|
/************************************************************************ |
|
* * |
|
* Routines to handle Values * |
|
* * |
|
************************************************************************/ |
|
|
|
/* Allocations are terrible, one needs to optimize all this !!! */ |
|
|
|
/** |
|
* xmlXPathNewFloat: |
|
* @val: the double value |
|
* |
|
* Create a new xmlXPathObjectPtr of type double and of value @val |
|
* |
|
* Returns the newly created object. |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathNewFloat(double val) { |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) xmlMalloc(sizeof(xmlXPathObject)); |
|
if (ret == NULL) { |
|
xmlXPathErrMemory(NULL, "creating float object\n"); |
|
return(NULL); |
|
} |
|
memset(ret, 0 , (size_t) sizeof(xmlXPathObject)); |
|
ret->type = XPATH_NUMBER; |
|
ret->floatval = val; |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(NULL, XPATH_NUMBER); |
|
#endif |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathNewBoolean: |
|
* @val: the boolean value |
|
* |
|
* Create a new xmlXPathObjectPtr of type boolean and of value @val |
|
* |
|
* Returns the newly created object. |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathNewBoolean(int val) { |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) xmlMalloc(sizeof(xmlXPathObject)); |
|
if (ret == NULL) { |
|
xmlXPathErrMemory(NULL, "creating boolean object\n"); |
|
return(NULL); |
|
} |
|
memset(ret, 0 , (size_t) sizeof(xmlXPathObject)); |
|
ret->type = XPATH_BOOLEAN; |
|
ret->boolval = (val != 0); |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(NULL, XPATH_BOOLEAN); |
|
#endif |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathNewString: |
|
* @val: the xmlChar * value |
|
* |
|
* Create a new xmlXPathObjectPtr of type string and of value @val |
|
* |
|
* Returns the newly created object. |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathNewString(const xmlChar *val) { |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) xmlMalloc(sizeof(xmlXPathObject)); |
|
if (ret == NULL) { |
|
xmlXPathErrMemory(NULL, "creating string object\n"); |
|
return(NULL); |
|
} |
|
memset(ret, 0 , (size_t) sizeof(xmlXPathObject)); |
|
ret->type = XPATH_STRING; |
|
if (val != NULL) |
|
ret->stringval = xmlStrdup(val); |
|
else |
|
ret->stringval = xmlStrdup((const xmlChar *)""); |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(NULL, XPATH_STRING); |
|
#endif |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathWrapString: |
|
* @val: the xmlChar * value |
|
* |
|
* Wraps the @val string into an XPath object. |
|
* |
|
* Returns the newly created object. |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathWrapString (xmlChar *val) { |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) xmlMalloc(sizeof(xmlXPathObject)); |
|
if (ret == NULL) { |
|
xmlXPathErrMemory(NULL, "creating string object\n"); |
|
return(NULL); |
|
} |
|
memset(ret, 0 , (size_t) sizeof(xmlXPathObject)); |
|
ret->type = XPATH_STRING; |
|
ret->stringval = val; |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(NULL, XPATH_STRING); |
|
#endif |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathNewCString: |
|
* @val: the char * value |
|
* |
|
* Create a new xmlXPathObjectPtr of type string and of value @val |
|
* |
|
* Returns the newly created object. |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathNewCString(const char *val) { |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) xmlMalloc(sizeof(xmlXPathObject)); |
|
if (ret == NULL) { |
|
xmlXPathErrMemory(NULL, "creating string object\n"); |
|
return(NULL); |
|
} |
|
memset(ret, 0 , (size_t) sizeof(xmlXPathObject)); |
|
ret->type = XPATH_STRING; |
|
ret->stringval = xmlStrdup(BAD_CAST val); |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(NULL, XPATH_STRING); |
|
#endif |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathWrapCString: |
|
* @val: the char * value |
|
* |
|
* Wraps a string into an XPath object. |
|
* |
|
* Returns the newly created object. |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathWrapCString (char * val) { |
|
return(xmlXPathWrapString((xmlChar *)(val))); |
|
} |
|
|
|
/** |
|
* xmlXPathWrapExternal: |
|
* @val: the user data |
|
* |
|
* Wraps the @val data into an XPath object. |
|
* |
|
* Returns the newly created object. |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathWrapExternal (void *val) { |
|
xmlXPathObjectPtr ret; |
|
|
|
ret = (xmlXPathObjectPtr) xmlMalloc(sizeof(xmlXPathObject)); |
|
if (ret == NULL) { |
|
xmlXPathErrMemory(NULL, "creating user object\n"); |
|
return(NULL); |
|
} |
|
memset(ret, 0 , (size_t) sizeof(xmlXPathObject)); |
|
ret->type = XPATH_USERS; |
|
ret->user = val; |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(NULL, XPATH_USERS); |
|
#endif |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathObjectCopy: |
|
* @val: the original object |
|
* |
|
* allocate a new copy of a given object |
|
* |
|
* Returns the newly created object. |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathObjectCopy(xmlXPathObjectPtr val) { |
|
xmlXPathObjectPtr ret; |
|
|
|
if (val == NULL) |
|
return(NULL); |
|
|
|
ret = (xmlXPathObjectPtr) xmlMalloc(sizeof(xmlXPathObject)); |
|
if (ret == NULL) { |
|
xmlXPathErrMemory(NULL, "copying object\n"); |
|
return(NULL); |
|
} |
|
memcpy(ret, val , (size_t) sizeof(xmlXPathObject)); |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageRequested(NULL, val->type); |
|
#endif |
|
switch (val->type) { |
|
case XPATH_BOOLEAN: |
|
case XPATH_NUMBER: |
|
case XPATH_POINT: |
|
case XPATH_RANGE: |
|
break; |
|
case XPATH_STRING: |
|
ret->stringval = xmlStrdup(val->stringval); |
|
break; |
|
case XPATH_XSLT_TREE: |
|
#if 0 |
|
/* |
|
Removed 11 July 2004 - the current handling of xslt tmpRVT nodes means that |
|
this previous handling is no longer correct, and can cause some serious |
|
problems (ref. bug 145547) |
|
*/ |
|
if ((val->nodesetval != NULL) && |
|
(val->nodesetval->nodeTab != NULL)) { |
|
xmlNodePtr cur, tmp; |
|
xmlDocPtr top; |
|
|
|
ret->boolval = 1; |
|
top = xmlNewDoc(NULL); |
|
top->name = (char *) |
|
xmlStrdup(val->nodesetval->nodeTab[0]->name); |
|
ret->user = top; |
|
if (top != NULL) { |
|
top->doc = top; |
|
cur = val->nodesetval->nodeTab[0]->children; |
|
while (cur != NULL) { |
|
tmp = xmlDocCopyNode(cur, top, 1); |
|
xmlAddChild((xmlNodePtr) top, tmp); |
|
cur = cur->next; |
|
} |
|
} |
|
|
|
ret->nodesetval = xmlXPathNodeSetCreate((xmlNodePtr) top); |
|
} else |
|
ret->nodesetval = xmlXPathNodeSetCreate(NULL); |
|
/* Deallocate the copied tree value */ |
|
break; |
|
#endif |
|
case XPATH_NODESET: |
|
ret->nodesetval = xmlXPathNodeSetMerge(NULL, val->nodesetval); |
|
/* Do not deallocate the copied tree value */ |
|
ret->boolval = 0; |
|
break; |
|
case XPATH_LOCATIONSET: |
|
#ifdef LIBXML_XPTR_ENABLED |
|
{ |
|
xmlLocationSetPtr loc = val->user; |
|
ret->user = (void *) xmlXPtrLocationSetMerge(NULL, loc); |
|
break; |
|
} |
|
#endif |
|
case XPATH_USERS: |
|
ret->user = val->user; |
|
break; |
|
case XPATH_UNDEFINED: |
|
xmlGenericError(xmlGenericErrorContext, |
|
"xmlXPathObjectCopy: unsupported type %d\n", |
|
val->type); |
|
break; |
|
} |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathFreeObject: |
|
* @obj: the object to free |
|
* |
|
* Free up an xmlXPathObjectPtr object. |
|
*/ |
|
void |
|
xmlXPathFreeObject(xmlXPathObjectPtr obj) { |
|
if (obj == NULL) return; |
|
if ((obj->type == XPATH_NODESET) || (obj->type == XPATH_XSLT_TREE)) { |
|
if (obj->boolval) { |
|
#if 0 |
|
if (obj->user != NULL) { |
|
xmlXPathFreeNodeSet(obj->nodesetval); |
|
xmlFreeNodeList((xmlNodePtr) obj->user); |
|
} else |
|
#endif |
|
obj->type = XPATH_XSLT_TREE; /* TODO: Just for debugging. */ |
|
if (obj->nodesetval != NULL) |
|
xmlXPathFreeValueTree(obj->nodesetval); |
|
} else { |
|
if (obj->nodesetval != NULL) |
|
xmlXPathFreeNodeSet(obj->nodesetval); |
|
} |
|
#ifdef LIBXML_XPTR_ENABLED |
|
} else if (obj->type == XPATH_LOCATIONSET) { |
|
if (obj->user != NULL) |
|
xmlXPtrFreeLocationSet(obj->user); |
|
#endif |
|
} else if (obj->type == XPATH_STRING) { |
|
if (obj->stringval != NULL) |
|
xmlFree(obj->stringval); |
|
} |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageReleased(NULL, obj->type); |
|
#endif |
|
xmlFree(obj); |
|
} |
|
|
|
static void |
|
xmlXPathFreeObjectEntry(void *obj, const xmlChar *name ATTRIBUTE_UNUSED) { |
|
xmlXPathFreeObject((xmlXPathObjectPtr) obj); |
|
} |
|
|
|
/** |
|
* xmlXPathReleaseObject: |
|
* @obj: the xmlXPathObjectPtr to free or to cache |
|
* |
|
* Depending on the state of the cache this frees the given |
|
* XPath object or stores it in the cache. |
|
*/ |
|
static void |
|
xmlXPathReleaseObject(xmlXPathContextPtr ctxt, xmlXPathObjectPtr obj) |
|
{ |
|
#define XP_CACHE_ADD(sl, o) if (sl == NULL) { \ |
|
sl = xmlPointerListCreate(10); if (sl == NULL) goto free_obj; } \ |
|
if (xmlPointerListAddSize(sl, obj, 0) == -1) goto free_obj; |
|
|
|
#define XP_CACHE_WANTS(sl, n) ((sl == NULL) || ((sl)->number < n)) |
|
|
|
if (obj == NULL) |
|
return; |
|
if ((ctxt == NULL) || (ctxt->cache == NULL)) { |
|
xmlXPathFreeObject(obj); |
|
} else { |
|
xmlXPathContextCachePtr cache = |
|
(xmlXPathContextCachePtr) ctxt->cache; |
|
|
|
switch (obj->type) { |
|
case XPATH_NODESET: |
|
case XPATH_XSLT_TREE: |
|
if (obj->nodesetval != NULL) { |
|
if (obj->boolval) { |
|
/* |
|
* It looks like the @boolval is used for |
|
* evaluation if this an XSLT Result Tree Fragment. |
|
* TODO: Check if this assumption is correct. |
|
*/ |
|
obj->type = XPATH_XSLT_TREE; /* just for debugging */ |
|
xmlXPathFreeValueTree(obj->nodesetval); |
|
obj->nodesetval = NULL; |
|
} else if ((obj->nodesetval->nodeMax <= 40) && |
|
(XP_CACHE_WANTS(cache->nodesetObjs, |
|
cache->maxNodeset))) |
|
{ |
|
XP_CACHE_ADD(cache->nodesetObjs, obj); |
|
goto obj_cached; |
|
} else { |
|
xmlXPathFreeNodeSet(obj->nodesetval); |
|
obj->nodesetval = NULL; |
|
} |
|
} |
|
break; |
|
case XPATH_STRING: |
|
if (obj->stringval != NULL) |
|
xmlFree(obj->stringval); |
|
|
|
if (XP_CACHE_WANTS(cache->stringObjs, cache->maxString)) { |
|
XP_CACHE_ADD(cache->stringObjs, obj); |
|
goto obj_cached; |
|
} |
|
break; |
|
case XPATH_BOOLEAN: |
|
if (XP_CACHE_WANTS(cache->booleanObjs, cache->maxBoolean)) { |
|
XP_CACHE_ADD(cache->booleanObjs, obj); |
|
goto obj_cached; |
|
} |
|
break; |
|
case XPATH_NUMBER: |
|
if (XP_CACHE_WANTS(cache->numberObjs, cache->maxNumber)) { |
|
XP_CACHE_ADD(cache->numberObjs, obj); |
|
goto obj_cached; |
|
} |
|
break; |
|
#ifdef LIBXML_XPTR_ENABLED |
|
case XPATH_LOCATIONSET: |
|
if (obj->user != NULL) { |
|
xmlXPtrFreeLocationSet(obj->user); |
|
} |
|
goto free_obj; |
|
#endif |
|
default: |
|
goto free_obj; |
|
} |
|
|
|
/* |
|
* Fallback to adding to the misc-objects slot. |
|
*/ |
|
if (XP_CACHE_WANTS(cache->miscObjs, cache->maxMisc)) { |
|
XP_CACHE_ADD(cache->miscObjs, obj); |
|
} else |
|
goto free_obj; |
|
|
|
obj_cached: |
|
|
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageReleased(ctxt, obj->type); |
|
#endif |
|
|
|
if (obj->nodesetval != NULL) { |
|
xmlNodeSetPtr tmpset = obj->nodesetval; |
|
|
|
/* |
|
* TODO: Due to those nasty ns-nodes, we need to traverse |
|
* the list and free the ns-nodes. |
|
* URGENT TODO: Check if it's actually slowing things down. |
|
* Maybe we shouldn't try to preserve the list. |
|
*/ |
|
if (tmpset->nodeNr > 1) { |
|
int i; |
|
xmlNodePtr node; |
|
|
|
for (i = 0; i < tmpset->nodeNr; i++) { |
|
node = tmpset->nodeTab[i]; |
|
if ((node != NULL) && |
|
(node->type == XML_NAMESPACE_DECL)) |
|
{ |
|
xmlXPathNodeSetFreeNs((xmlNsPtr) node); |
|
} |
|
} |
|
} else if (tmpset->nodeNr == 1) { |
|
if ((tmpset->nodeTab[0] != NULL) && |
|
(tmpset->nodeTab[0]->type == XML_NAMESPACE_DECL)) |
|
xmlXPathNodeSetFreeNs((xmlNsPtr) tmpset->nodeTab[0]); |
|
} |
|
tmpset->nodeNr = 0; |
|
memset(obj, 0, sizeof(xmlXPathObject)); |
|
obj->nodesetval = tmpset; |
|
} else |
|
memset(obj, 0, sizeof(xmlXPathObject)); |
|
|
|
return; |
|
|
|
free_obj: |
|
/* |
|
* Cache is full; free the object. |
|
*/ |
|
if (obj->nodesetval != NULL) |
|
xmlXPathFreeNodeSet(obj->nodesetval); |
|
#ifdef XP_DEBUG_OBJ_USAGE |
|
xmlXPathDebugObjUsageReleased(NULL, obj->type); |
|
#endif |
|
xmlFree(obj); |
|
} |
|
return; |
|
} |
|
|
|
|
|
/************************************************************************ |
|
* * |
|
* Type Casting Routines * |
|
* * |
|
************************************************************************/ |
|
|
|
/** |
|
* xmlXPathCastBooleanToString: |
|
* @val: a boolean |
|
* |
|
* Converts a boolean to its string value. |
|
* |
|
* Returns a newly allocated string. |
|
*/ |
|
xmlChar * |
|
xmlXPathCastBooleanToString (int val) { |
|
xmlChar *ret; |
|
if (val) |
|
ret = xmlStrdup((const xmlChar *) "true"); |
|
else |
|
ret = xmlStrdup((const xmlChar *) "false"); |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathCastNumberToString: |
|
* @val: a number |
|
* |
|
* Converts a number to its string value. |
|
* |
|
* Returns a newly allocated string. |
|
*/ |
|
xmlChar * |
|
xmlXPathCastNumberToString (double val) { |
|
xmlChar *ret; |
|
switch (xmlXPathIsInf(val)) { |
|
case 1: |
|
ret = xmlStrdup((const xmlChar *) "Infinity"); |
|
break; |
|
case -1: |
|
ret = xmlStrdup((const xmlChar *) "-Infinity"); |
|
break; |
|
default: |
|
if (xmlXPathIsNaN(val)) { |
|
ret = xmlStrdup((const xmlChar *) "NaN"); |
|
} else if (val == 0) { |
|
/* Omit sign for negative zero. */ |
|
ret = xmlStrdup((const xmlChar *) "0"); |
|
} else { |
|
/* could be improved */ |
|
char buf[100]; |
|
xmlXPathFormatNumber(val, buf, 99); |
|
buf[99] = 0; |
|
ret = xmlStrdup((const xmlChar *) buf); |
|
} |
|
} |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathCastNodeToString: |
|
* @node: a node |
|
* |
|
* Converts a node to its string value. |
|
* |
|
* Returns a newly allocated string. |
|
*/ |
|
xmlChar * |
|
xmlXPathCastNodeToString (xmlNodePtr node) { |
|
xmlChar *ret; |
|
if ((ret = xmlNodeGetContent(node)) == NULL) |
|
ret = xmlStrdup((const xmlChar *) ""); |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathCastNodeSetToString: |
|
* @ns: a node-set |
|
* |
|
* Converts a node-set to its string value. |
|
* |
|
* Returns a newly allocated string. |
|
*/ |
|
xmlChar * |
|
xmlXPathCastNodeSetToString (xmlNodeSetPtr ns) { |
|
if ((ns == NULL) || (ns->nodeNr == 0) || (ns->nodeTab == NULL)) |
|
return(xmlStrdup((const xmlChar *) "")); |
|
|
|
if (ns->nodeNr > 1) |
|
xmlXPathNodeSetSort(ns); |
|
return(xmlXPathCastNodeToString(ns->nodeTab[0])); |
|
} |
|
|
|
/** |
|
* xmlXPathCastToString: |
|
* @val: an XPath object |
|
* |
|
* Converts an existing object to its string() equivalent |
|
* |
|
* Returns the allocated string value of the object, NULL in case of error. |
|
* It's up to the caller to free the string memory with xmlFree(). |
|
*/ |
|
xmlChar * |
|
xmlXPathCastToString(xmlXPathObjectPtr val) { |
|
xmlChar *ret = NULL; |
|
|
|
if (val == NULL) |
|
return(xmlStrdup((const xmlChar *) "")); |
|
switch (val->type) { |
|
case XPATH_UNDEFINED: |
|
#ifdef DEBUG_EXPR |
|
xmlGenericError(xmlGenericErrorContext, "String: undefined\n"); |
|
#endif |
|
ret = xmlStrdup((const xmlChar *) ""); |
|
break; |
|
case XPATH_NODESET: |
|
case XPATH_XSLT_TREE: |
|
ret = xmlXPathCastNodeSetToString(val->nodesetval); |
|
break; |
|
case XPATH_STRING: |
|
return(xmlStrdup(val->stringval)); |
|
case XPATH_BOOLEAN: |
|
ret = xmlXPathCastBooleanToString(val->boolval); |
|
break; |
|
case XPATH_NUMBER: { |
|
ret = xmlXPathCastNumberToString(val->floatval); |
|
break; |
|
} |
|
case XPATH_USERS: |
|
case XPATH_POINT: |
|
case XPATH_RANGE: |
|
case XPATH_LOCATIONSET: |
|
TODO |
|
ret = xmlStrdup((const xmlChar *) ""); |
|
break; |
|
} |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathConvertString: |
|
* @val: an XPath object |
|
* |
|
* Converts an existing object to its string() equivalent |
|
* |
|
* Returns the new object, the old one is freed (or the operation |
|
* is done directly on @val) |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathConvertString(xmlXPathObjectPtr val) { |
|
xmlChar *res = NULL; |
|
|
|
if (val == NULL) |
|
return(xmlXPathNewCString("")); |
|
|
|
switch (val->type) { |
|
case XPATH_UNDEFINED: |
|
#ifdef DEBUG_EXPR |
|
xmlGenericError(xmlGenericErrorContext, "STRING: undefined\n"); |
|
#endif |
|
break; |
|
case XPATH_NODESET: |
|
case XPATH_XSLT_TREE: |
|
res = xmlXPathCastNodeSetToString(val->nodesetval); |
|
break; |
|
case XPATH_STRING: |
|
return(val); |
|
case XPATH_BOOLEAN: |
|
res = xmlXPathCastBooleanToString(val->boolval); |
|
break; |
|
case XPATH_NUMBER: |
|
res = xmlXPathCastNumberToString(val->floatval); |
|
break; |
|
case XPATH_USERS: |
|
case XPATH_POINT: |
|
case XPATH_RANGE: |
|
case XPATH_LOCATIONSET: |
|
TODO; |
|
break; |
|
} |
|
xmlXPathFreeObject(val); |
|
if (res == NULL) |
|
return(xmlXPathNewCString("")); |
|
return(xmlXPathWrapString(res)); |
|
} |
|
|
|
/** |
|
* xmlXPathCastBooleanToNumber: |
|
* @val: a boolean |
|
* |
|
* Converts a boolean to its number value |
|
* |
|
* Returns the number value |
|
*/ |
|
double |
|
xmlXPathCastBooleanToNumber(int val) { |
|
if (val) |
|
return(1.0); |
|
return(0.0); |
|
} |
|
|
|
/** |
|
* xmlXPathCastStringToNumber: |
|
* @val: a string |
|
* |
|
* Converts a string to its number value |
|
* |
|
* Returns the number value |
|
*/ |
|
double |
|
xmlXPathCastStringToNumber(const xmlChar * val) { |
|
return(xmlXPathStringEvalNumber(val)); |
|
} |
|
|
|
/** |
|
* xmlXPathCastNodeToNumber: |
|
* @node: a node |
|
* |
|
* Converts a node to its number value |
|
* |
|
* Returns the number value |
|
*/ |
|
double |
|
xmlXPathCastNodeToNumber (xmlNodePtr node) { |
|
xmlChar *strval; |
|
double ret; |
|
|
|
if (node == NULL) |
|
return(NAN); |
|
strval = xmlXPathCastNodeToString(node); |
|
if (strval == NULL) |
|
return(NAN); |
|
ret = xmlXPathCastStringToNumber(strval); |
|
xmlFree(strval); |
|
|
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathCastNodeSetToNumber: |
|
* @ns: a node-set |
|
* |
|
* Converts a node-set to its number value |
|
* |
|
* Returns the number value |
|
*/ |
|
double |
|
xmlXPathCastNodeSetToNumber (xmlNodeSetPtr ns) { |
|
xmlChar *str; |
|
double ret; |
|
|
|
if (ns == NULL) |
|
return(NAN); |
|
str = xmlXPathCastNodeSetToString(ns); |
|
ret = xmlXPathCastStringToNumber(str); |
|
xmlFree(str); |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathCastToNumber: |
|
* @val: an XPath object |
|
* |
|
* Converts an XPath object to its number value |
|
* |
|
* Returns the number value |
|
*/ |
|
double |
|
xmlXPathCastToNumber(xmlXPathObjectPtr val) { |
|
double ret = 0.0; |
|
|
|
if (val == NULL) |
|
return(NAN); |
|
switch (val->type) { |
|
case XPATH_UNDEFINED: |
|
#ifdef DEGUB_EXPR |
|
xmlGenericError(xmlGenericErrorContext, "NUMBER: undefined\n"); |
|
#endif |
|
ret = NAN; |
|
break; |
|
case XPATH_NODESET: |
|
case XPATH_XSLT_TREE: |
|
ret = xmlXPathCastNodeSetToNumber(val->nodesetval); |
|
break; |
|
case XPATH_STRING: |
|
ret = xmlXPathCastStringToNumber(val->stringval); |
|
break; |
|
case XPATH_NUMBER: |
|
ret = val->floatval; |
|
break; |
|
case XPATH_BOOLEAN: |
|
ret = xmlXPathCastBooleanToNumber(val->boolval); |
|
break; |
|
case XPATH_USERS: |
|
case XPATH_POINT: |
|
case XPATH_RANGE: |
|
case XPATH_LOCATIONSET: |
|
TODO; |
|
ret = NAN; |
|
break; |
|
} |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathConvertNumber: |
|
* @val: an XPath object |
|
* |
|
* Converts an existing object to its number() equivalent |
|
* |
|
* Returns the new object, the old one is freed (or the operation |
|
* is done directly on @val) |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathConvertNumber(xmlXPathObjectPtr val) { |
|
xmlXPathObjectPtr ret; |
|
|
|
if (val == NULL) |
|
return(xmlXPathNewFloat(0.0)); |
|
if (val->type == XPATH_NUMBER) |
|
return(val); |
|
ret = xmlXPathNewFloat(xmlXPathCastToNumber(val)); |
|
xmlXPathFreeObject(val); |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathCastNumberToBoolean: |
|
* @val: a number |
|
* |
|
* Converts a number to its boolean value |
|
* |
|
* Returns the boolean value |
|
*/ |
|
int |
|
xmlXPathCastNumberToBoolean (double val) { |
|
if (xmlXPathIsNaN(val) || (val == 0.0)) |
|
return(0); |
|
return(1); |
|
} |
|
|
|
/** |
|
* xmlXPathCastStringToBoolean: |
|
* @val: a string |
|
* |
|
* Converts a string to its boolean value |
|
* |
|
* Returns the boolean value |
|
*/ |
|
int |
|
xmlXPathCastStringToBoolean (const xmlChar *val) { |
|
if ((val == NULL) || (xmlStrlen(val) == 0)) |
|
return(0); |
|
return(1); |
|
} |
|
|
|
/** |
|
* xmlXPathCastNodeSetToBoolean: |
|
* @ns: a node-set |
|
* |
|
* Converts a node-set to its boolean value |
|
* |
|
* Returns the boolean value |
|
*/ |
|
int |
|
xmlXPathCastNodeSetToBoolean (xmlNodeSetPtr ns) { |
|
if ((ns == NULL) || (ns->nodeNr == 0)) |
|
return(0); |
|
return(1); |
|
} |
|
|
|
/** |
|
* xmlXPathCastToBoolean: |
|
* @val: an XPath object |
|
* |
|
* Converts an XPath object to its boolean value |
|
* |
|
* Returns the boolean value |
|
*/ |
|
int |
|
xmlXPathCastToBoolean (xmlXPathObjectPtr val) { |
|
int ret = 0; |
|
|
|
if (val == NULL) |
|
return(0); |
|
switch (val->type) { |
|
case XPATH_UNDEFINED: |
|
#ifdef DEBUG_EXPR |
|
xmlGenericError(xmlGenericErrorContext, "BOOLEAN: undefined\n"); |
|
#endif |
|
ret = 0; |
|
break; |
|
case XPATH_NODESET: |
|
case XPATH_XSLT_TREE: |
|
ret = xmlXPathCastNodeSetToBoolean(val->nodesetval); |
|
break; |
|
case XPATH_STRING: |
|
ret = xmlXPathCastStringToBoolean(val->stringval); |
|
break; |
|
case XPATH_NUMBER: |
|
ret = xmlXPathCastNumberToBoolean(val->floatval); |
|
break; |
|
case XPATH_BOOLEAN: |
|
ret = val->boolval; |
|
break; |
|
case XPATH_USERS: |
|
case XPATH_POINT: |
|
case XPATH_RANGE: |
|
case XPATH_LOCATIONSET: |
|
TODO; |
|
ret = 0; |
|
break; |
|
} |
|
return(ret); |
|
} |
|
|
|
|
|
/** |
|
* xmlXPathConvertBoolean: |
|
* @val: an XPath object |
|
* |
|
* Converts an existing object to its boolean() equivalent |
|
* |
|
* Returns the new object, the old one is freed (or the operation |
|
* is done directly on @val) |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathConvertBoolean(xmlXPathObjectPtr val) { |
|
xmlXPathObjectPtr ret; |
|
|
|
if (val == NULL) |
|
return(xmlXPathNewBoolean(0)); |
|
if (val->type == XPATH_BOOLEAN) |
|
return(val); |
|
ret = xmlXPathNewBoolean(xmlXPathCastToBoolean(val)); |
|
xmlXPathFreeObject(val); |
|
return(ret); |
|
} |
|
|
|
/************************************************************************ |
|
* * |
|
* Routines to handle XPath contexts * |
|
* * |
|
************************************************************************/ |
|
|
|
/** |
|
* xmlXPathNewContext: |
|
* @doc: the XML document |
|
* |
|
* Create a new xmlXPathContext |
|
* |
|
* Returns the xmlXPathContext just allocated. The caller will need to free it. |
|
*/ |
|
xmlXPathContextPtr |
|
xmlXPathNewContext(xmlDocPtr doc) { |
|
xmlXPathContextPtr ret; |
|
|
|
ret = (xmlXPathContextPtr) xmlMalloc(sizeof(xmlXPathContext)); |
|
if (ret == NULL) { |
|
xmlXPathErrMemory(NULL, "creating context\n"); |
|
return(NULL); |
|
} |
|
memset(ret, 0 , (size_t) sizeof(xmlXPathContext)); |
|
ret->doc = doc; |
|
ret->node = NULL; |
|
|
|
ret->varHash = NULL; |
|
|
|
ret->nb_types = 0; |
|
ret->max_types = 0; |
|
ret->types = NULL; |
|
|
|
ret->funcHash = xmlHashCreate(0); |
|
|
|
ret->nb_axis = 0; |
|
ret->max_axis = 0; |
|
ret->axis = NULL; |
|
|
|
ret->nsHash = NULL; |
|
ret->user = NULL; |
|
|
|
ret->contextSize = -1; |
|
ret->proximityPosition = -1; |
|
|
|
#ifdef XP_DEFAULT_CACHE_ON |
|
if (xmlXPathContextSetCache(ret, 1, -1, 0) == -1) { |
|
xmlXPathFreeContext(ret); |
|
return(NULL); |
|
} |
|
#endif |
|
|
|
xmlXPathRegisterAllFunctions(ret); |
|
|
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathFreeContext: |
|
* @ctxt: the context to free |
|
* |
|
* Free up an xmlXPathContext |
|
*/ |
|
void |
|
xmlXPathFreeContext(xmlXPathContextPtr ctxt) { |
|
if (ctxt == NULL) return; |
|
|
|
if (ctxt->cache != NULL) |
|
xmlXPathFreeCache((xmlXPathContextCachePtr) ctxt->cache); |
|
xmlXPathRegisteredNsCleanup(ctxt); |
|
xmlXPathRegisteredFuncsCleanup(ctxt); |
|
xmlXPathRegisteredVariablesCleanup(ctxt); |
|
xmlResetError(&ctxt->lastError); |
|
xmlFree(ctxt); |
|
} |
|
|
|
/************************************************************************ |
|
* * |
|
* Routines to handle XPath parser contexts * |
|
* * |
|
************************************************************************/ |
|
|
|
#define CHECK_CTXT(ctxt) \ |
|
if (ctxt == NULL) { \ |
|
__xmlRaiseError(NULL, NULL, NULL, \ |
|
NULL, NULL, XML_FROM_XPATH, \ |
|
XML_ERR_INTERNAL_ERROR, XML_ERR_FATAL, \ |
|
__FILE__, __LINE__, \ |
|
NULL, NULL, NULL, 0, 0, \ |
|
"NULL context pointer\n"); \ |
|
return(NULL); \ |
|
} \ |
|
|
|
#define CHECK_CTXT_NEG(ctxt) \ |
|
if (ctxt == NULL) { \ |
|
__xmlRaiseError(NULL, NULL, NULL, \ |
|
NULL, NULL, XML_FROM_XPATH, \ |
|
XML_ERR_INTERNAL_ERROR, XML_ERR_FATAL, \ |
|
__FILE__, __LINE__, \ |
|
NULL, NULL, NULL, 0, 0, \ |
|
"NULL context pointer\n"); \ |
|
return(-1); \ |
|
} \ |
|
|
|
|
|
#define CHECK_CONTEXT(ctxt) \ |
|
if ((ctxt == NULL) || (ctxt->doc == NULL) || \ |
|
(ctxt->doc->children == NULL)) { \ |
|
xmlXPatherror(ctxt, __FILE__, __LINE__, XPATH_INVALID_CTXT); \ |
|
return(NULL); \ |
|
} |
|
|
|
|
|
/** |
|
* xmlXPathNewParserContext: |
|
* @str: the XPath expression |
|
* @ctxt: the XPath context |
|
* |
|
* Create a new xmlXPathParserContext |
|
* |
|
* Returns the xmlXPathParserContext just allocated. |
|
*/ |
|
xmlXPathParserContextPtr |
|
xmlXPathNewParserContext(const xmlChar *str, xmlXPathContextPtr ctxt) { |
|
xmlXPathParserContextPtr ret; |
|
|
|
ret = (xmlXPathParserContextPtr) xmlMalloc(sizeof(xmlXPathParserContext)); |
|
if (ret == NULL) { |
|
xmlXPathErrMemory(ctxt, "creating parser context\n"); |
|
return(NULL); |
|
} |
|
memset(ret, 0 , (size_t) sizeof(xmlXPathParserContext)); |
|
ret->cur = ret->base = str; |
|
ret->context = ctxt; |
|
|
|
ret->comp = xmlXPathNewCompExpr(); |
|
if (ret->comp == NULL) { |
|
xmlFree(ret->valueTab); |
|
xmlFree(ret); |
|
return(NULL); |
|
} |
|
if ((ctxt != NULL) && (ctxt->dict != NULL)) { |
|
ret->comp->dict = ctxt->dict; |
|
xmlDictReference(ret->comp->dict); |
|
} |
|
|
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathCompParserContext: |
|
* @comp: the XPath compiled expression |
|
* @ctxt: the XPath context |
|
* |
|
* Create a new xmlXPathParserContext when processing a compiled expression |
|
* |
|
* Returns the xmlXPathParserContext just allocated. |
|
*/ |
|
static xmlXPathParserContextPtr |
|
xmlXPathCompParserContext(xmlXPathCompExprPtr comp, xmlXPathContextPtr ctxt) { |
|
xmlXPathParserContextPtr ret; |
|
|
|
ret = (xmlXPathParserContextPtr) xmlMalloc(sizeof(xmlXPathParserContext)); |
|
if (ret == NULL) { |
|
xmlXPathErrMemory(ctxt, "creating evaluation context\n"); |
|
return(NULL); |
|
} |
|
memset(ret, 0 , (size_t) sizeof(xmlXPathParserContext)); |
|
|
|
/* Allocate the value stack */ |
|
ret->valueTab = (xmlXPathObjectPtr *) |
|
xmlMalloc(10 * sizeof(xmlXPathObjectPtr)); |
|
if (ret->valueTab == NULL) { |
|
xmlFree(ret); |
|
xmlXPathErrMemory(ctxt, "creating evaluation context\n"); |
|
return(NULL); |
|
} |
|
ret->valueNr = 0; |
|
ret->valueMax = 10; |
|
ret->value = NULL; |
|
ret->valueFrame = 0; |
|
|
|
ret->context = ctxt; |
|
ret->comp = comp; |
|
|
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathFreeParserContext: |
|
* @ctxt: the context to free |
|
* |
|
* Free up an xmlXPathParserContext |
|
*/ |
|
void |
|
xmlXPathFreeParserContext(xmlXPathParserContextPtr ctxt) { |
|
int i; |
|
|
|
if (ctxt->valueTab != NULL) { |
|
for (i = 0; i < ctxt->valueNr; i++) { |
|
if (ctxt->context) |
|
xmlXPathReleaseObject(ctxt->context, ctxt->valueTab[i]); |
|
else |
|
xmlXPathFreeObject(ctxt->valueTab[i]); |
|
} |
|
xmlFree(ctxt->valueTab); |
|
} |
|
if (ctxt->comp != NULL) { |
|
#ifdef XPATH_STREAMING |
|
if (ctxt->comp->stream != NULL) { |
|
xmlFreePatternList(ctxt->comp->stream); |
|
ctxt->comp->stream = NULL; |
|
} |
|
#endif |
|
xmlXPathFreeCompExpr(ctxt->comp); |
|
} |
|
xmlFree(ctxt); |
|
} |
|
|
|
/************************************************************************ |
|
* * |
|
* The implicit core function library * |
|
* * |
|
************************************************************************/ |
|
|
|
/** |
|
* xmlXPathNodeValHash: |
|
* @node: a node pointer |
|
* |
|
* Function computing the beginning of the string value of the node, |
|
* used to speed up comparisons |
|
* |
|
* Returns an int usable as a hash |
|
*/ |
|
static unsigned int |
|
xmlXPathNodeValHash(xmlNodePtr node) { |
|
int len = 2; |
|
const xmlChar * string = NULL; |
|
xmlNodePtr tmp = NULL; |
|
unsigned int ret = 0; |
|
|
|
if (node == NULL) |
|
return(0); |
|
|
|
if (node->type == XML_DOCUMENT_NODE) { |
|
tmp = xmlDocGetRootElement((xmlDocPtr) node); |
|
if (tmp == NULL) |
|
node = node->children; |
|
else |
|
node = tmp; |
|
|
|
if (node == NULL) |
|
return(0); |
|
} |
|
|
|
switch (node->type) { |
|
case XML_COMMENT_NODE: |
|
case XML_PI_NODE: |
|
case XML_CDATA_SECTION_NODE: |
|
case XML_TEXT_NODE: |
|
string = node->content; |
|
if (string == NULL) |
|
return(0); |
|
if (string[0] == 0) |
|
return(0); |
|
return(((unsigned int) string[0]) + |
|
(((unsigned int) string[1]) << 8)); |
|
case XML_NAMESPACE_DECL: |
|
string = ((xmlNsPtr)node)->href; |
|
if (string == NULL) |
|
return(0); |
|
if (string[0] == 0) |
|
return(0); |
|
return(((unsigned int) string[0]) + |
|
(((unsigned int) string[1]) << 8)); |
|
case XML_ATTRIBUTE_NODE: |
|
tmp = ((xmlAttrPtr) node)->children; |
|
break; |
|
case XML_ELEMENT_NODE: |
|
tmp = node->children; |
|
break; |
|
default: |
|
return(0); |
|
} |
|
while (tmp != NULL) { |
|
switch (tmp->type) { |
|
case XML_CDATA_SECTION_NODE: |
|
case XML_TEXT_NODE: |
|
string = tmp->content; |
|
break; |
|
default: |
|
string = NULL; |
|
break; |
|
} |
|
if ((string != NULL) && (string[0] != 0)) { |
|
if (len == 1) { |
|
return(ret + (((unsigned int) string[0]) << 8)); |
|
} |
|
if (string[1] == 0) { |
|
len = 1; |
|
ret = (unsigned int) string[0]; |
|
} else { |
|
return(((unsigned int) string[0]) + |
|
(((unsigned int) string[1]) << 8)); |
|
} |
|
} |
|
/* |
|
* Skip to next node |
|
*/ |
|
if ((tmp->children != NULL) && (tmp->type != XML_DTD_NODE)) { |
|
if (tmp->children->type != XML_ENTITY_DECL) { |
|
tmp = tmp->children; |
|
continue; |
|
} |
|
} |
|
if (tmp == node) |
|
break; |
|
|
|
if (tmp->next != NULL) { |
|
tmp = tmp->next; |
|
continue; |
|
} |
|
|
|
do { |
|
tmp = tmp->parent; |
|
if (tmp == NULL) |
|
break; |
|
if (tmp == node) { |
|
tmp = NULL; |
|
break; |
|
} |
|
if (tmp->next != NULL) { |
|
tmp = tmp->next; |
|
break; |
|
} |
|
} while (tmp != NULL); |
|
} |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathStringHash: |
|
* @string: a string |
|
* |
|
* Function computing the beginning of the string value of the node, |
|
* used to speed up comparisons |
|
* |
|
* Returns an int usable as a hash |
|
*/ |
|
static unsigned int |
|
xmlXPathStringHash(const xmlChar * string) { |
|
if (string == NULL) |
|
return((unsigned int) 0); |
|
if (string[0] == 0) |
|
return(0); |
|
return(((unsigned int) string[0]) + |
|
(((unsigned int) string[1]) << 8)); |
|
} |
|
|
|
/** |
|
* xmlXPathCompareNodeSetFloat: |
|
* @ctxt: the XPath Parser context |
|
* @inf: less than (1) or greater than (0) |
|
* @strict: is the comparison strict |
|
* @arg: the node set |
|
* @f: the value |
|
* |
|
* Implement the compare operation between a nodeset and a number |
|
* @ns < @val (1, 1, ... |
|
* @ns <= @val (1, 0, ... |
|
* @ns > @val (0, 1, ... |
|
* @ns >= @val (0, 0, ... |
|
* |
|
* If one object to be compared is a node-set and the other is a number, |
|
* then the comparison will be true if and only if there is a node in the |
|
* node-set such that the result of performing the comparison on the number |
|
* to be compared and on the result of converting the string-value of that |
|
* node to a number using the number function is true. |
|
* |
|
* Returns 0 or 1 depending on the results of the test. |
|
*/ |
|
static int |
|
xmlXPathCompareNodeSetFloat(xmlXPathParserContextPtr ctxt, int inf, int strict, |
|
xmlXPathObjectPtr arg, xmlXPathObjectPtr f) { |
|
int i, ret = 0; |
|
xmlNodeSetPtr ns; |
|
xmlChar *str2; |
|
|
|
if ((f == NULL) || (arg == NULL) || |
|
((arg->type != XPATH_NODESET) && (arg->type != XPATH_XSLT_TREE))) { |
|
xmlXPathReleaseObject(ctxt->context, arg); |
|
xmlXPathReleaseObject(ctxt->context, f); |
|
return(0); |
|
} |
|
ns = arg->nodesetval; |
|
if (ns != NULL) { |
|
for (i = 0;i < ns->nodeNr;i++) { |
|
str2 = xmlXPathCastNodeToString(ns->nodeTab[i]); |
|
if (str2 != NULL) { |
|
valuePush(ctxt, |
|
xmlXPathCacheNewString(ctxt->context, str2)); |
|
xmlFree(str2); |
|
xmlXPathNumberFunction(ctxt, 1); |
|
valuePush(ctxt, xmlXPathCacheObjectCopy(ctxt->context, f)); |
|
ret = xmlXPathCompareValues(ctxt, inf, strict); |
|
if (ret) |
|
break; |
|
} |
|
} |
|
} |
|
xmlXPathReleaseObject(ctxt->context, arg); |
|
xmlXPathReleaseObject(ctxt->context, f); |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathCompareNodeSetString: |
|
* @ctxt: the XPath Parser context |
|
* @inf: less than (1) or greater than (0) |
|
* @strict: is the comparison strict |
|
* @arg: the node set |
|
* @s: the value |
|
* |
|
* Implement the compare operation between a nodeset and a string |
|
* @ns < @val (1, 1, ... |
|
* @ns <= @val (1, 0, ... |
|
* @ns > @val (0, 1, ... |
|
* @ns >= @val (0, 0, ... |
|
* |
|
* If one object to be compared is a node-set and the other is a string, |
|
* then the comparison will be true if and only if there is a node in |
|
* the node-set such that the result of performing the comparison on the |
|
* string-value of the node and the other string is true. |
|
* |
|
* Returns 0 or 1 depending on the results of the test. |
|
*/ |
|
static int |
|
xmlXPathCompareNodeSetString(xmlXPathParserContextPtr ctxt, int inf, int strict, |
|
xmlXPathObjectPtr arg, xmlXPathObjectPtr s) { |
|
int i, ret = 0; |
|
xmlNodeSetPtr ns; |
|
xmlChar *str2; |
|
|
|
if ((s == NULL) || (arg == NULL) || |
|
((arg->type != XPATH_NODESET) && (arg->type != XPATH_XSLT_TREE))) { |
|
xmlXPathReleaseObject(ctxt->context, arg); |
|
xmlXPathReleaseObject(ctxt->context, s); |
|
return(0); |
|
} |
|
ns = arg->nodesetval; |
|
if (ns != NULL) { |
|
for (i = 0;i < ns->nodeNr;i++) { |
|
str2 = xmlXPathCastNodeToString(ns->nodeTab[i]); |
|
if (str2 != NULL) { |
|
valuePush(ctxt, |
|
xmlXPathCacheNewString(ctxt->context, str2)); |
|
xmlFree(str2); |
|
valuePush(ctxt, xmlXPathCacheObjectCopy(ctxt->context, s)); |
|
ret = xmlXPathCompareValues(ctxt, inf, strict); |
|
if (ret) |
|
break; |
|
} |
|
} |
|
} |
|
xmlXPathReleaseObject(ctxt->context, arg); |
|
xmlXPathReleaseObject(ctxt->context, s); |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathCompareNodeSets: |
|
* @inf: less than (1) or greater than (0) |
|
* @strict: is the comparison strict |
|
* @arg1: the first node set object |
|
* @arg2: the second node set object |
|
* |
|
* Implement the compare operation on nodesets: |
|
* |
|
* If both objects to be compared are node-sets, then the comparison |
|
* will be true if and only if there is a node in the first node-set |
|
* and a node in the second node-set such that the result of performing |
|
* the comparison on the string-values of the two nodes is true. |
|
* .... |
|
* When neither object to be compared is a node-set and the operator |
|
* is <=, <, >= or >, then the objects are compared by converting both |
|
* objects to numbers and comparing the numbers according to IEEE 754. |
|
* .... |
|
* The number function converts its argument to a number as follows: |
|
* - a string that consists of optional whitespace followed by an |
|
* optional minus sign followed by a Number followed by whitespace |
|
* is converted to the IEEE 754 number that is nearest (according |
|
* to the IEEE 754 round-to-nearest rule) to the mathematical value |
|
* represented by the string; any other string is converted to NaN |
|
* |
|
* Conclusion all nodes need to be converted first to their string value |
|
* and then the comparison must be done when possible |
|
*/ |
|
static int |
|
xmlXPathCompareNodeSets(int inf, int strict, |
|
xmlXPathObjectPtr arg1, xmlXPathObjectPtr arg2) { |
|
int i, j, init = 0; |
|
double val1; |
|
double *values2; |
|
int ret = 0; |
|
xmlNodeSetPtr ns1; |
|
xmlNodeSetPtr ns2; |
|
|
|
if ((arg1 == NULL) || |
|
((arg1->type != XPATH_NODESET) && (arg1->type != XPATH_XSLT_TREE))) { |
|
xmlXPathFreeObject(arg2); |
|
return(0); |
|
} |
|
if ((arg2 == NULL) || |
|
((arg2->type != XPATH_NODESET) && (arg2->type != XPATH_XSLT_TREE))) { |
|
xmlXPathFreeObject(arg1); |
|
xmlXPathFreeObject(arg2); |
|
return(0); |
|
} |
|
|
|
ns1 = arg1->nodesetval; |
|
ns2 = arg2->nodesetval; |
|
|
|
if ((ns1 == NULL) || (ns1->nodeNr <= 0)) { |
|
xmlXPathFreeObject(arg1); |
|
xmlXPathFreeObject(arg2); |
|
return(0); |
|
} |
|
if ((ns2 == NULL) || (ns2->nodeNr <= 0)) { |
|
xmlXPathFreeObject(arg1); |
|
xmlXPathFreeObject(arg2); |
|
return(0); |
|
} |
|
|
|
values2 = (double *) xmlMalloc(ns2->nodeNr * sizeof(double)); |
|
if (values2 == NULL) { |
|
xmlXPathErrMemory(NULL, "comparing nodesets\n"); |
|
xmlXPathFreeObject(arg1); |
|
xmlXPathFreeObject(arg2); |
|
return(0); |
|
} |
|
for (i = 0;i < ns1->nodeNr;i++) { |
|
val1 = xmlXPathCastNodeToNumber(ns1->nodeTab[i]); |
|
if (xmlXPathIsNaN(val1)) |
|
continue; |
|
for (j = 0;j < ns2->nodeNr;j++) { |
|
if (init == 0) { |
|
values2[j] = xmlXPathCastNodeToNumber(ns2->nodeTab[j]); |
|
} |
|
if (xmlXPathIsNaN(values2[j])) |
|
continue; |
|
if (inf && strict) |
|
ret = (val1 < values2[j]); |
|
else if (inf && !strict) |
|
ret = (val1 <= values2[j]); |
|
else if (!inf && strict) |
|
ret = (val1 > values2[j]); |
|
else if (!inf && !strict) |
|
ret = (val1 >= values2[j]); |
|
if (ret) |
|
break; |
|
} |
|
if (ret) |
|
break; |
|
init = 1; |
|
} |
|
xmlFree(values2); |
|
xmlXPathFreeObject(arg1); |
|
xmlXPathFreeObject(arg2); |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathCompareNodeSetValue: |
|
* @ctxt: the XPath Parser context |
|
* @inf: less than (1) or greater than (0) |
|
* @strict: is the comparison strict |
|
* @arg: the node set |
|
* @val: the value |
|
* |
|
* Implement the compare operation between a nodeset and a value |
|
* @ns < @val (1, 1, ... |
|
* @ns <= @val (1, 0, ... |
|
* @ns > @val (0, 1, ... |
|
* @ns >= @val (0, 0, ... |
|
* |
|
* If one object to be compared is a node-set and the other is a boolean, |
|
* then the comparison will be true if and only if the result of performing |
|
* the comparison on the boolean and on the result of converting |
|
* the node-set to a boolean using the boolean function is true. |
|
* |
|
* Returns 0 or 1 depending on the results of the test. |
|
*/ |
|
static int |
|
xmlXPathCompareNodeSetValue(xmlXPathParserContextPtr ctxt, int inf, int strict, |
|
xmlXPathObjectPtr arg, xmlXPathObjectPtr val) { |
|
if ((val == NULL) || (arg == NULL) || |
|
((arg->type != XPATH_NODESET) && (arg->type != XPATH_XSLT_TREE))) |
|
return(0); |
|
|
|
switch(val->type) { |
|
case XPATH_NUMBER: |
|
return(xmlXPathCompareNodeSetFloat(ctxt, inf, strict, arg, val)); |
|
case XPATH_NODESET: |
|
case XPATH_XSLT_TREE: |
|
return(xmlXPathCompareNodeSets(inf, strict, arg, val)); |
|
case XPATH_STRING: |
|
return(xmlXPathCompareNodeSetString(ctxt, inf, strict, arg, val)); |
|
case XPATH_BOOLEAN: |
|
valuePush(ctxt, arg); |
|
xmlXPathBooleanFunction(ctxt, 1); |
|
valuePush(ctxt, val); |
|
return(xmlXPathCompareValues(ctxt, inf, strict)); |
|
default: |
|
xmlGenericError(xmlGenericErrorContext, |
|
"xmlXPathCompareNodeSetValue: Can't compare node set " |
|
"and object of type %d\n", |
|
val->type); |
|
xmlXPathReleaseObject(ctxt->context, arg); |
|
xmlXPathReleaseObject(ctxt->context, val); |
|
XP_ERROR0(XPATH_INVALID_TYPE); |
|
} |
|
return(0); |
|
} |
|
|
|
/** |
|
* xmlXPathEqualNodeSetString: |
|
* @arg: the nodeset object argument |
|
* @str: the string to compare to. |
|
* @neq: flag to show whether for '=' (0) or '!=' (1) |
|
* |
|
* Implement the equal operation on XPath objects content: @arg1 == @arg2 |
|
* If one object to be compared is a node-set and the other is a string, |
|
* then the comparison will be true if and only if there is a node in |
|
* the node-set such that the result of performing the comparison on the |
|
* string-value of the node and the other string is true. |
|
* |
|
* Returns 0 or 1 depending on the results of the test. |
|
*/ |
|
static int |
|
xmlXPathEqualNodeSetString(xmlXPathObjectPtr arg, const xmlChar * str, int neq) |
|
{ |
|
int i; |
|
xmlNodeSetPtr ns; |
|
xmlChar *str2; |
|
unsigned int hash; |
|
|
|
if ((str == NULL) || (arg == NULL) || |
|
((arg->type != XPATH_NODESET) && (arg->type != XPATH_XSLT_TREE))) |
|
return (0); |
|
ns = arg->nodesetval; |
|
/* |
|
* A NULL nodeset compared with a string is always false |
|
* (since there is no node equal, and no node not equal) |
|
*/ |
|
if ((ns == NULL) || (ns->nodeNr <= 0) ) |
|
return (0); |
|
hash = xmlXPathStringHash(str); |
|
for (i = 0; i < ns->nodeNr; i++) { |
|
if (xmlXPathNodeValHash(ns->nodeTab[i]) == hash) { |
|
str2 = xmlNodeGetContent(ns->nodeTab[i]); |
|
if ((str2 != NULL) && (xmlStrEqual(str, str2))) { |
|
xmlFree(str2); |
|
if (neq) |
|
continue; |
|
return (1); |
|
} else if ((str2 == NULL) && (xmlStrEqual(str, BAD_CAST ""))) { |
|
if (neq) |
|
continue; |
|
return (1); |
|
} else if (neq) { |
|
if (str2 != NULL) |
|
xmlFree(str2); |
|
return (1); |
|
} |
|
if (str2 != NULL) |
|
xmlFree(str2); |
|
} else if (neq) |
|
return (1); |
|
} |
|
return (0); |
|
} |
|
|
|
/** |
|
* xmlXPathEqualNodeSetFloat: |
|
* @arg: the nodeset object argument |
|
* @f: the float to compare to |
|
* @neq: flag to show whether to compare '=' (0) or '!=' (1) |
|
* |
|
* Implement the equal operation on XPath objects content: @arg1 == @arg2 |
|
* If one object to be compared is a node-set and the other is a number, |
|
* then the comparison will be true if and only if there is a node in |
|
* the node-set such that the result of performing the comparison on the |
|
* number to be compared and on the result of converting the string-value |
|
* of that node to a number using the number function is true. |
|
* |
|
* Returns 0 or 1 depending on the results of the test. |
|
*/ |
|
static int |
|
xmlXPathEqualNodeSetFloat(xmlXPathParserContextPtr ctxt, |
|
xmlXPathObjectPtr arg, double f, int neq) { |
|
int i, ret=0; |
|
xmlNodeSetPtr ns; |
|
xmlChar *str2; |
|
xmlXPathObjectPtr val; |
|
double v; |
|
|
|
if ((arg == NULL) || |
|
((arg->type != XPATH_NODESET) && (arg->type != XPATH_XSLT_TREE))) |
|
return(0); |
|
|
|
ns = arg->nodesetval; |
|
if (ns != NULL) { |
|
for (i=0;i<ns->nodeNr;i++) { |
|
str2 = xmlXPathCastNodeToString(ns->nodeTab[i]); |
|
if (str2 != NULL) { |
|
valuePush(ctxt, xmlXPathCacheNewString(ctxt->context, str2)); |
|
xmlFree(str2); |
|
xmlXPathNumberFunction(ctxt, 1); |
|
val = valuePop(ctxt); |
|
v = val->floatval; |
|
xmlXPathReleaseObject(ctxt->context, val); |
|
if (!xmlXPathIsNaN(v)) { |
|
if ((!neq) && (v==f)) { |
|
ret = 1; |
|
break; |
|
} else if ((neq) && (v!=f)) { |
|
ret = 1; |
|
break; |
|
} |
|
} else { /* NaN is unequal to any value */ |
|
if (neq) |
|
ret = 1; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return(ret); |
|
} |
|
|
|
|
|
/** |
|
* xmlXPathEqualNodeSets: |
|
* @arg1: first nodeset object argument |
|
* @arg2: second nodeset object argument |
|
* @neq: flag to show whether to test '=' (0) or '!=' (1) |
|
* |
|
* Implement the equal / not equal operation on XPath nodesets: |
|
* @arg1 == @arg2 or @arg1 != @arg2 |
|
* If both objects to be compared are node-sets, then the comparison |
|
* will be true if and only if there is a node in the first node-set and |
|
* a node in the second node-set such that the result of performing the |
|
* comparison on the string-values of the two nodes is true. |
|
* |
|
* (needless to say, this is a costly operation) |
|
* |
|
* Returns 0 or 1 depending on the results of the test. |
|
*/ |
|
static int |
|
xmlXPathEqualNodeSets(xmlXPathObjectPtr arg1, xmlXPathObjectPtr arg2, int neq) { |
|
int i, j; |
|
unsigned int *hashs1; |
|
unsigned int *hashs2; |
|
xmlChar **values1; |
|
xmlChar **values2; |
|
int ret = 0; |
|
xmlNodeSetPtr ns1; |
|
xmlNodeSetPtr ns2; |
|
|
|
if ((arg1 == NULL) || |
|
((arg1->type != XPATH_NODESET) && (arg1->type != XPATH_XSLT_TREE))) |
|
return(0); |
|
if ((arg2 == NULL) || |
|
((arg2->type != XPATH_NODESET) && (arg2->type != XPATH_XSLT_TREE))) |
|
return(0); |
|
|
|
ns1 = arg1->nodesetval; |
|
ns2 = arg2->nodesetval; |
|
|
|
if ((ns1 == NULL) || (ns1->nodeNr <= 0)) |
|
return(0); |
|
if ((ns2 == NULL) || (ns2->nodeNr <= 0)) |
|
return(0); |
|
|
|
/* |
|
* for equal, check if there is a node pertaining to both sets |
|
*/ |
|
if (neq == 0) |
|
for (i = 0;i < ns1->nodeNr;i++) |
|
for (j = 0;j < ns2->nodeNr;j++) |
|
if (ns1->nodeTab[i] == ns2->nodeTab[j]) |
|
return(1); |
|
|
|
values1 = (xmlChar **) xmlMalloc(ns1->nodeNr * sizeof(xmlChar *)); |
|
if (values1 == NULL) { |
|
xmlXPathErrMemory(NULL, "comparing nodesets\n"); |
|
return(0); |
|
} |
|
hashs1 = (unsigned int *) xmlMalloc(ns1->nodeNr * sizeof(unsigned int)); |
|
if (hashs1 == NULL) { |
|
xmlXPathErrMemory(NULL, "comparing nodesets\n"); |
|
xmlFree(values1); |
|
return(0); |
|
} |
|
memset(values1, 0, ns1->nodeNr * sizeof(xmlChar *)); |
|
values2 = (xmlChar **) xmlMalloc(ns2->nodeNr * sizeof(xmlChar *)); |
|
if (values2 == NULL) { |
|
xmlXPathErrMemory(NULL, "comparing nodesets\n"); |
|
xmlFree(hashs1); |
|
xmlFree(values1); |
|
return(0); |
|
} |
|
hashs2 = (unsigned int *) xmlMalloc(ns2->nodeNr * sizeof(unsigned int)); |
|
if (hashs2 == NULL) { |
|
xmlXPathErrMemory(NULL, "comparing nodesets\n"); |
|
xmlFree(hashs1); |
|
xmlFree(values1); |
|
xmlFree(values2); |
|
return(0); |
|
} |
|
memset(values2, 0, ns2->nodeNr * sizeof(xmlChar *)); |
|
for (i = 0;i < ns1->nodeNr;i++) { |
|
hashs1[i] = xmlXPathNodeValHash(ns1->nodeTab[i]); |
|
for (j = 0;j < ns2->nodeNr;j++) { |
|
if (i == 0) |
|
hashs2[j] = xmlXPathNodeValHash(ns2->nodeTab[j]); |
|
if (hashs1[i] != hashs2[j]) { |
|
if (neq) { |
|
ret = 1; |
|
break; |
|
} |
|
} |
|
else { |
|
if (values1[i] == NULL) |
|
values1[i] = xmlNodeGetContent(ns1->nodeTab[i]); |
|
if (values2[j] == NULL) |
|
values2[j] = xmlNodeGetContent(ns2->nodeTab[j]); |
|
ret = xmlStrEqual(values1[i], values2[j]) ^ neq; |
|
if (ret) |
|
break; |
|
} |
|
} |
|
if (ret) |
|
break; |
|
} |
|
for (i = 0;i < ns1->nodeNr;i++) |
|
if (values1[i] != NULL) |
|
xmlFree(values1[i]); |
|
for (j = 0;j < ns2->nodeNr;j++) |
|
if (values2[j] != NULL) |
|
xmlFree(values2[j]); |
|
xmlFree(values1); |
|
xmlFree(values2); |
|
xmlFree(hashs1); |
|
xmlFree(hashs2); |
|
return(ret); |
|
} |
|
|
|
static int |
|
xmlXPathEqualValuesCommon(xmlXPathParserContextPtr ctxt, |
|
xmlXPathObjectPtr arg1, xmlXPathObjectPtr arg2) { |
|
int ret = 0; |
|
/* |
|
*At this point we are assured neither arg1 nor arg2 |
|
*is a nodeset, so we can just pick the appropriate routine. |
|
*/ |
|
switch (arg1->type) { |
|
case XPATH_UNDEFINED: |
|
#ifdef DEBUG_EXPR |
|
xmlGenericError(xmlGenericErrorContext, |
|
"Equal: undefined\n"); |
|
#endif |
|
break; |
|
case XPATH_BOOLEAN: |
|
switch (arg2->type) { |
|
case XPATH_UNDEFINED: |
|
#ifdef DEBUG_EXPR |
|
xmlGenericError(xmlGenericErrorContext, |
|
"Equal: undefined\n"); |
|
#endif |
|
break; |
|
case XPATH_BOOLEAN: |
|
#ifdef DEBUG_EXPR |
|
xmlGenericError(xmlGenericErrorContext, |
|
"Equal: %d boolean %d \n", |
|
arg1->boolval, arg2->boolval); |
|
#endif |
|
ret = (arg1->boolval == arg2->boolval); |
|
break; |
|
case XPATH_NUMBER: |
|
ret = (arg1->boolval == |
|
xmlXPathCastNumberToBoolean(arg2->floatval)); |
|
break; |
|
case XPATH_STRING: |
|
if ((arg2->stringval == NULL) || |
|
(arg2->stringval[0] == 0)) ret = 0; |
|
else |
|
ret = 1; |
|
ret = (arg1->boolval == ret); |
|
break; |
|
case XPATH_USERS: |
|
case XPATH_POINT: |
|
case XPATH_RANGE: |
|
case XPATH_LOCATIONSET: |
|
TODO |
|
break; |
|
case XPATH_NODESET: |
|
case XPATH_XSLT_TREE: |
|
break; |
|
} |
|
break; |
|
case XPATH_NUMBER: |
|
switch (arg2->type) { |
|
case XPATH_UNDEFINED: |
|
#ifdef DEBUG_EXPR |
|
xmlGenericError(xmlGenericErrorContext, |
|
"Equal: undefined\n"); |
|
#endif |
|
break; |
|
case XPATH_BOOLEAN: |
|
ret = (arg2->boolval== |
|
xmlXPathCastNumberToBoolean(arg1->floatval)); |
|
break; |
|
case XPATH_STRING: |
|
valuePush(ctxt, arg2); |
|
xmlXPathNumberFunction(ctxt, 1); |
|
arg2 = valuePop(ctxt); |
|
/* Falls through. */ |
|
case XPATH_NUMBER: |
|
/* Hand check NaN and Infinity equalities */ |
|
if (xmlXPathIsNaN(arg1->floatval) || |
|
xmlXPathIsNaN(arg2->floatval)) { |
|
ret = 0; |
|
} else if (xmlXPathIsInf(arg1->floatval) == 1) { |
|
if (xmlXPathIsInf(arg2->floatval) == 1) |
|
ret = 1; |
|
else |
|
ret = 0; |
|
} else if (xmlXPathIsInf(arg1->floatval) == -1) { |
|
if (xmlXPathIsInf(arg2->floatval) == -1) |
|
ret = 1; |
|
else |
|
ret = 0; |
|
} else if (xmlXPathIsInf(arg2->floatval) == 1) { |
|
if (xmlXPathIsInf(arg1->floatval) == 1) |
|
ret = 1; |
|
else |
|
ret = 0; |
|
} else if (xmlXPathIsInf(arg2->floatval) == -1) { |
|
if (xmlXPathIsInf(arg1->floatval) == -1) |
|
ret = 1; |
|
else |
|
ret = 0; |
|
} else { |
|
ret = (arg1->floatval == arg2->floatval); |
|
} |
|
break; |
|
case XPATH_USERS: |
|
case XPATH_POINT: |
|
case XPATH_RANGE: |
|
case XPATH_LOCATIONSET: |
|
TODO |
|
break; |
|
case XPATH_NODESET: |
|
case XPATH_XSLT_TREE: |
|
break; |
|
} |
|
break; |
|
case XPATH_STRING: |
|
switch (arg2->type) { |
|
case XPATH_UNDEFINED: |
|
#ifdef DEBUG_EXPR |
|
xmlGenericError(xmlGenericErrorContext, |
|
"Equal: undefined\n"); |
|
#endif |
|
break; |
|
case XPATH_BOOLEAN: |
|
if ((arg1->stringval == NULL) || |
|
(arg1->stringval[0] == 0)) ret = 0; |
|
else |
|
ret = 1; |
|
ret = (arg2->boolval == ret); |
|
break; |
|
case XPATH_STRING: |
|
ret = xmlStrEqual(arg1->stringval, arg2->stringval); |
|
break; |
|
case XPATH_NUMBER: |
|
valuePush(ctxt, arg1); |
|
xmlXPathNumberFunction(ctxt, 1); |
|
arg1 = valuePop(ctxt); |
|
/* Hand check NaN and Infinity equalities */ |
|
if (xmlXPathIsNaN(arg1->floatval) || |
|
xmlXPathIsNaN(arg2->floatval)) { |
|
ret = 0; |
|
} else if (xmlXPathIsInf(arg1->floatval) == 1) { |
|
if (xmlXPathIsInf(arg2->floatval) == 1) |
|
ret = 1; |
|
else |
|
ret = 0; |
|
} else if (xmlXPathIsInf(arg1->floatval) == -1) { |
|
if (xmlXPathIsInf(arg2->floatval) == -1) |
|
ret = 1; |
|
else |
|
ret = 0; |
|
} else if (xmlXPathIsInf(arg2->floatval) == 1) { |
|
if (xmlXPathIsInf(arg1->floatval) == 1) |
|
ret = 1; |
|
else |
|
ret = 0; |
|
} else if (xmlXPathIsInf(arg2->floatval) == -1) { |
|
if (xmlXPathIsInf(arg1->floatval) == -1) |
|
ret = 1; |
|
else |
|
ret = 0; |
|
} else { |
|
ret = (arg1->floatval == arg2->floatval); |
|
} |
|
break; |
|
case XPATH_USERS: |
|
case XPATH_POINT: |
|
case XPATH_RANGE: |
|
case XPATH_LOCATIONSET: |
|
TODO |
|
break; |
|
case XPATH_NODESET: |
|
case XPATH_XSLT_TREE: |
|
break; |
|
} |
|
break; |
|
case XPATH_USERS: |
|
case XPATH_POINT: |
|
case XPATH_RANGE: |
|
case XPATH_LOCATIONSET: |
|
TODO |
|
break; |
|
case XPATH_NODESET: |
|
case XPATH_XSLT_TREE: |
|
break; |
|
} |
|
xmlXPathReleaseObject(ctxt->context, arg1); |
|
xmlXPathReleaseObject(ctxt->context, arg2); |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathEqualValues: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* Implement the equal operation on XPath objects content: @arg1 == @arg2 |
|
* |
|
* Returns 0 or 1 depending on the results of the test. |
|
*/ |
|
int |
|
xmlXPathEqualValues(xmlXPathParserContextPtr ctxt) { |
|
xmlXPathObjectPtr arg1, arg2, argtmp; |
|
int ret = 0; |
|
|
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(0); |
|
arg2 = valuePop(ctxt); |
|
arg1 = valuePop(ctxt); |
|
if ((arg1 == NULL) || (arg2 == NULL)) { |
|
if (arg1 != NULL) |
|
xmlXPathReleaseObject(ctxt->context, arg1); |
|
else |
|
xmlXPathReleaseObject(ctxt->context, arg2); |
|
XP_ERROR0(XPATH_INVALID_OPERAND); |
|
} |
|
|
|
if (arg1 == arg2) { |
|
#ifdef DEBUG_EXPR |
|
xmlGenericError(xmlGenericErrorContext, |
|
"Equal: by pointer\n"); |
|
#endif |
|
xmlXPathFreeObject(arg1); |
|
return(1); |
|
} |
|
|
|
/* |
|
*If either argument is a nodeset, it's a 'special case' |
|
*/ |
|
if ((arg2->type == XPATH_NODESET) || (arg2->type == XPATH_XSLT_TREE) || |
|
(arg1->type == XPATH_NODESET) || (arg1->type == XPATH_XSLT_TREE)) { |
|
/* |
|
*Hack it to assure arg1 is the nodeset |
|
*/ |
|
if ((arg1->type != XPATH_NODESET) && (arg1->type != XPATH_XSLT_TREE)) { |
|
argtmp = arg2; |
|
arg2 = arg1; |
|
arg1 = argtmp; |
|
} |
|
switch (arg2->type) { |
|
case XPATH_UNDEFINED: |
|
#ifdef DEBUG_EXPR |
|
xmlGenericError(xmlGenericErrorContext, |
|
"Equal: undefined\n"); |
|
#endif |
|
break; |
|
case XPATH_NODESET: |
|
case XPATH_XSLT_TREE: |
|
ret = xmlXPathEqualNodeSets(arg1, arg2, 0); |
|
break; |
|
case XPATH_BOOLEAN: |
|
if ((arg1->nodesetval == NULL) || |
|
(arg1->nodesetval->nodeNr == 0)) ret = 0; |
|
else |
|
ret = 1; |
|
ret = (ret == arg2->boolval); |
|
break; |
|
case XPATH_NUMBER: |
|
ret = xmlXPathEqualNodeSetFloat(ctxt, arg1, arg2->floatval, 0); |
|
break; |
|
case XPATH_STRING: |
|
ret = xmlXPathEqualNodeSetString(arg1, arg2->stringval, 0); |
|
break; |
|
case XPATH_USERS: |
|
case XPATH_POINT: |
|
case XPATH_RANGE: |
|
case XPATH_LOCATIONSET: |
|
TODO |
|
break; |
|
} |
|
xmlXPathReleaseObject(ctxt->context, arg1); |
|
xmlXPathReleaseObject(ctxt->context, arg2); |
|
return(ret); |
|
} |
|
|
|
return (xmlXPathEqualValuesCommon(ctxt, arg1, arg2)); |
|
} |
|
|
|
/** |
|
* xmlXPathNotEqualValues: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* Implement the equal operation on XPath objects content: @arg1 == @arg2 |
|
* |
|
* Returns 0 or 1 depending on the results of the test. |
|
*/ |
|
int |
|
xmlXPathNotEqualValues(xmlXPathParserContextPtr ctxt) { |
|
xmlXPathObjectPtr arg1, arg2, argtmp; |
|
int ret = 0; |
|
|
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(0); |
|
arg2 = valuePop(ctxt); |
|
arg1 = valuePop(ctxt); |
|
if ((arg1 == NULL) || (arg2 == NULL)) { |
|
if (arg1 != NULL) |
|
xmlXPathReleaseObject(ctxt->context, arg1); |
|
else |
|
xmlXPathReleaseObject(ctxt->context, arg2); |
|
XP_ERROR0(XPATH_INVALID_OPERAND); |
|
} |
|
|
|
if (arg1 == arg2) { |
|
#ifdef DEBUG_EXPR |
|
xmlGenericError(xmlGenericErrorContext, |
|
"NotEqual: by pointer\n"); |
|
#endif |
|
xmlXPathReleaseObject(ctxt->context, arg1); |
|
return(0); |
|
} |
|
|
|
/* |
|
*If either argument is a nodeset, it's a 'special case' |
|
*/ |
|
if ((arg2->type == XPATH_NODESET) || (arg2->type == XPATH_XSLT_TREE) || |
|
(arg1->type == XPATH_NODESET) || (arg1->type == XPATH_XSLT_TREE)) { |
|
/* |
|
*Hack it to assure arg1 is the nodeset |
|
*/ |
|
if ((arg1->type != XPATH_NODESET) && (arg1->type != XPATH_XSLT_TREE)) { |
|
argtmp = arg2; |
|
arg2 = arg1; |
|
arg1 = argtmp; |
|
} |
|
switch (arg2->type) { |
|
case XPATH_UNDEFINED: |
|
#ifdef DEBUG_EXPR |
|
xmlGenericError(xmlGenericErrorContext, |
|
"NotEqual: undefined\n"); |
|
#endif |
|
break; |
|
case XPATH_NODESET: |
|
case XPATH_XSLT_TREE: |
|
ret = xmlXPathEqualNodeSets(arg1, arg2, 1); |
|
break; |
|
case XPATH_BOOLEAN: |
|
if ((arg1->nodesetval == NULL) || |
|
(arg1->nodesetval->nodeNr == 0)) ret = 0; |
|
else |
|
ret = 1; |
|
ret = (ret != arg2->boolval); |
|
break; |
|
case XPATH_NUMBER: |
|
ret = xmlXPathEqualNodeSetFloat(ctxt, arg1, arg2->floatval, 1); |
|
break; |
|
case XPATH_STRING: |
|
ret = xmlXPathEqualNodeSetString(arg1, arg2->stringval,1); |
|
break; |
|
case XPATH_USERS: |
|
case XPATH_POINT: |
|
case XPATH_RANGE: |
|
case XPATH_LOCATIONSET: |
|
TODO |
|
break; |
|
} |
|
xmlXPathReleaseObject(ctxt->context, arg1); |
|
xmlXPathReleaseObject(ctxt->context, arg2); |
|
return(ret); |
|
} |
|
|
|
return (!xmlXPathEqualValuesCommon(ctxt, arg1, arg2)); |
|
} |
|
|
|
/** |
|
* xmlXPathCompareValues: |
|
* @ctxt: the XPath Parser context |
|
* @inf: less than (1) or greater than (0) |
|
* @strict: is the comparison strict |
|
* |
|
* Implement the compare operation on XPath objects: |
|
* @arg1 < @arg2 (1, 1, ... |
|
* @arg1 <= @arg2 (1, 0, ... |
|
* @arg1 > @arg2 (0, 1, ... |
|
* @arg1 >= @arg2 (0, 0, ... |
|
* |
|
* When neither object to be compared is a node-set and the operator is |
|
* <=, <, >=, >, then the objects are compared by converted both objects |
|
* to numbers and comparing the numbers according to IEEE 754. The < |
|
* comparison will be true if and only if the first number is less than the |
|
* second number. The <= comparison will be true if and only if the first |
|
* number is less than or equal to the second number. The > comparison |
|
* will be true if and only if the first number is greater than the second |
|
* number. The >= comparison will be true if and only if the first number |
|
* is greater than or equal to the second number. |
|
* |
|
* Returns 1 if the comparison succeeded, 0 if it failed |
|
*/ |
|
int |
|
xmlXPathCompareValues(xmlXPathParserContextPtr ctxt, int inf, int strict) { |
|
int ret = 0, arg1i = 0, arg2i = 0; |
|
xmlXPathObjectPtr arg1, arg2; |
|
|
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(0); |
|
arg2 = valuePop(ctxt); |
|
arg1 = valuePop(ctxt); |
|
if ((arg1 == NULL) || (arg2 == NULL)) { |
|
if (arg1 != NULL) |
|
xmlXPathReleaseObject(ctxt->context, arg1); |
|
else |
|
xmlXPathReleaseObject(ctxt->context, arg2); |
|
XP_ERROR0(XPATH_INVALID_OPERAND); |
|
} |
|
|
|
if ((arg2->type == XPATH_NODESET) || (arg2->type == XPATH_XSLT_TREE) || |
|
(arg1->type == XPATH_NODESET) || (arg1->type == XPATH_XSLT_TREE)) { |
|
/* |
|
* If either argument is a XPATH_NODESET or XPATH_XSLT_TREE the two arguments |
|
* are not freed from within this routine; they will be freed from the |
|
* called routine, e.g. xmlXPathCompareNodeSets or xmlXPathCompareNodeSetValue |
|
*/ |
|
if (((arg2->type == XPATH_NODESET) || (arg2->type == XPATH_XSLT_TREE)) && |
|
((arg1->type == XPATH_NODESET) || (arg1->type == XPATH_XSLT_TREE))){ |
|
ret = xmlXPathCompareNodeSets(inf, strict, arg1, arg2); |
|
} else { |
|
if ((arg1->type == XPATH_NODESET) || (arg1->type == XPATH_XSLT_TREE)) { |
|
ret = xmlXPathCompareNodeSetValue(ctxt, inf, strict, |
|
arg1, arg2); |
|
} else { |
|
ret = xmlXPathCompareNodeSetValue(ctxt, !inf, strict, |
|
arg2, arg1); |
|
} |
|
} |
|
return(ret); |
|
} |
|
|
|
if (arg1->type != XPATH_NUMBER) { |
|
valuePush(ctxt, arg1); |
|
xmlXPathNumberFunction(ctxt, 1); |
|
arg1 = valuePop(ctxt); |
|
} |
|
if (arg1->type != XPATH_NUMBER) { |
|
xmlXPathFreeObject(arg1); |
|
xmlXPathFreeObject(arg2); |
|
XP_ERROR0(XPATH_INVALID_OPERAND); |
|
} |
|
if (arg2->type != XPATH_NUMBER) { |
|
valuePush(ctxt, arg2); |
|
xmlXPathNumberFunction(ctxt, 1); |
|
arg2 = valuePop(ctxt); |
|
} |
|
if (arg2->type != XPATH_NUMBER) { |
|
xmlXPathReleaseObject(ctxt->context, arg1); |
|
xmlXPathReleaseObject(ctxt->context, arg2); |
|
XP_ERROR0(XPATH_INVALID_OPERAND); |
|
} |
|
/* |
|
* Add tests for infinity and nan |
|
* => feedback on 3.4 for Inf and NaN |
|
*/ |
|
/* Hand check NaN and Infinity comparisons */ |
|
if (xmlXPathIsNaN(arg1->floatval) || xmlXPathIsNaN(arg2->floatval)) { |
|
ret=0; |
|
} else { |
|
arg1i=xmlXPathIsInf(arg1->floatval); |
|
arg2i=xmlXPathIsInf(arg2->floatval); |
|
if (inf && strict) { |
|
if ((arg1i == -1 && arg2i != -1) || |
|
(arg2i == 1 && arg1i != 1)) { |
|
ret = 1; |
|
} else if (arg1i == 0 && arg2i == 0) { |
|
ret = (arg1->floatval < arg2->floatval); |
|
} else { |
|
ret = 0; |
|
} |
|
} |
|
else if (inf && !strict) { |
|
if (arg1i == -1 || arg2i == 1) { |
|
ret = 1; |
|
} else if (arg1i == 0 && arg2i == 0) { |
|
ret = (arg1->floatval <= arg2->floatval); |
|
} else { |
|
ret = 0; |
|
} |
|
} |
|
else if (!inf && strict) { |
|
if ((arg1i == 1 && arg2i != 1) || |
|
(arg2i == -1 && arg1i != -1)) { |
|
ret = 1; |
|
} else if (arg1i == 0 && arg2i == 0) { |
|
ret = (arg1->floatval > arg2->floatval); |
|
} else { |
|
ret = 0; |
|
} |
|
} |
|
else if (!inf && !strict) { |
|
if (arg1i == 1 || arg2i == -1) { |
|
ret = 1; |
|
} else if (arg1i == 0 && arg2i == 0) { |
|
ret = (arg1->floatval >= arg2->floatval); |
|
} else { |
|
ret = 0; |
|
} |
|
} |
|
} |
|
xmlXPathReleaseObject(ctxt->context, arg1); |
|
xmlXPathReleaseObject(ctxt->context, arg2); |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathValueFlipSign: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* Implement the unary - operation on an XPath object |
|
* The numeric operators convert their operands to numbers as if |
|
* by calling the number function. |
|
*/ |
|
void |
|
xmlXPathValueFlipSign(xmlXPathParserContextPtr ctxt) { |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return; |
|
CAST_TO_NUMBER; |
|
CHECK_TYPE(XPATH_NUMBER); |
|
ctxt->value->floatval = -ctxt->value->floatval; |
|
} |
|
|
|
/** |
|
* xmlXPathAddValues: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* Implement the add operation on XPath objects: |
|
* The numeric operators convert their operands to numbers as if |
|
* by calling the number function. |
|
*/ |
|
void |
|
xmlXPathAddValues(xmlXPathParserContextPtr ctxt) { |
|
xmlXPathObjectPtr arg; |
|
double val; |
|
|
|
arg = valuePop(ctxt); |
|
if (arg == NULL) |
|
XP_ERROR(XPATH_INVALID_OPERAND); |
|
val = xmlXPathCastToNumber(arg); |
|
xmlXPathReleaseObject(ctxt->context, arg); |
|
CAST_TO_NUMBER; |
|
CHECK_TYPE(XPATH_NUMBER); |
|
ctxt->value->floatval += val; |
|
} |
|
|
|
/** |
|
* xmlXPathSubValues: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* Implement the subtraction operation on XPath objects: |
|
* The numeric operators convert their operands to numbers as if |
|
* by calling the number function. |
|
*/ |
|
void |
|
xmlXPathSubValues(xmlXPathParserContextPtr ctxt) { |
|
xmlXPathObjectPtr arg; |
|
double val; |
|
|
|
arg = valuePop(ctxt); |
|
if (arg == NULL) |
|
XP_ERROR(XPATH_INVALID_OPERAND); |
|
val = xmlXPathCastToNumber(arg); |
|
xmlXPathReleaseObject(ctxt->context, arg); |
|
CAST_TO_NUMBER; |
|
CHECK_TYPE(XPATH_NUMBER); |
|
ctxt->value->floatval -= val; |
|
} |
|
|
|
/** |
|
* xmlXPathMultValues: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* Implement the multiply operation on XPath objects: |
|
* The numeric operators convert their operands to numbers as if |
|
* by calling the number function. |
|
*/ |
|
void |
|
xmlXPathMultValues(xmlXPathParserContextPtr ctxt) { |
|
xmlXPathObjectPtr arg; |
|
double val; |
|
|
|
arg = valuePop(ctxt); |
|
if (arg == NULL) |
|
XP_ERROR(XPATH_INVALID_OPERAND); |
|
val = xmlXPathCastToNumber(arg); |
|
xmlXPathReleaseObject(ctxt->context, arg); |
|
CAST_TO_NUMBER; |
|
CHECK_TYPE(XPATH_NUMBER); |
|
ctxt->value->floatval *= val; |
|
} |
|
|
|
/** |
|
* xmlXPathDivValues: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* Implement the div operation on XPath objects @arg1 / @arg2: |
|
* The numeric operators convert their operands to numbers as if |
|
* by calling the number function. |
|
*/ |
|
void |
|
xmlXPathDivValues(xmlXPathParserContextPtr ctxt) { |
|
xmlXPathObjectPtr arg; |
|
double val; |
|
|
|
arg = valuePop(ctxt); |
|
if (arg == NULL) |
|
XP_ERROR(XPATH_INVALID_OPERAND); |
|
val = xmlXPathCastToNumber(arg); |
|
xmlXPathReleaseObject(ctxt->context, arg); |
|
CAST_TO_NUMBER; |
|
CHECK_TYPE(XPATH_NUMBER); |
|
ctxt->value->floatval /= val; |
|
} |
|
|
|
/** |
|
* xmlXPathModValues: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* Implement the mod operation on XPath objects: @arg1 / @arg2 |
|
* The numeric operators convert their operands to numbers as if |
|
* by calling the number function. |
|
*/ |
|
void |
|
xmlXPathModValues(xmlXPathParserContextPtr ctxt) { |
|
xmlXPathObjectPtr arg; |
|
double arg1, arg2; |
|
|
|
arg = valuePop(ctxt); |
|
if (arg == NULL) |
|
XP_ERROR(XPATH_INVALID_OPERAND); |
|
arg2 = xmlXPathCastToNumber(arg); |
|
xmlXPathReleaseObject(ctxt->context, arg); |
|
CAST_TO_NUMBER; |
|
CHECK_TYPE(XPATH_NUMBER); |
|
arg1 = ctxt->value->floatval; |
|
if (arg2 == 0) |
|
ctxt->value->floatval = NAN; |
|
else { |
|
ctxt->value->floatval = fmod(arg1, arg2); |
|
} |
|
} |
|
|
|
/************************************************************************ |
|
* * |
|
* The traversal functions * |
|
* * |
|
************************************************************************/ |
|
|
|
/* |
|
* A traversal function enumerates nodes along an axis. |
|
* Initially it must be called with NULL, and it indicates |
|
* termination on the axis by returning NULL. |
|
*/ |
|
typedef xmlNodePtr (*xmlXPathTraversalFunction) |
|
(xmlXPathParserContextPtr ctxt, xmlNodePtr cur); |
|
|
|
/* |
|
* xmlXPathTraversalFunctionExt: |
|
* A traversal function enumerates nodes along an axis. |
|
* Initially it must be called with NULL, and it indicates |
|
* termination on the axis by returning NULL. |
|
* The context node of the traversal is specified via @contextNode. |
|
*/ |
|
typedef xmlNodePtr (*xmlXPathTraversalFunctionExt) |
|
(xmlNodePtr cur, xmlNodePtr contextNode); |
|
|
|
/* |
|
* xmlXPathNodeSetMergeFunction: |
|
* Used for merging node sets in xmlXPathCollectAndTest(). |
|
*/ |
|
typedef xmlNodeSetPtr (*xmlXPathNodeSetMergeFunction) |
|
(xmlNodeSetPtr, xmlNodeSetPtr, int); |
|
|
|
|
|
/** |
|
* xmlXPathNextSelf: |
|
* @ctxt: the XPath Parser context |
|
* @cur: the current node in the traversal |
|
* |
|
* Traversal function for the "self" direction |
|
* The self axis contains just the context node itself |
|
* |
|
* Returns the next element following that axis |
|
*/ |
|
xmlNodePtr |
|
xmlXPathNextSelf(xmlXPathParserContextPtr ctxt, xmlNodePtr cur) { |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(NULL); |
|
if (cur == NULL) |
|
return(ctxt->context->node); |
|
return(NULL); |
|
} |
|
|
|
/** |
|
* xmlXPathNextChild: |
|
* @ctxt: the XPath Parser context |
|
* @cur: the current node in the traversal |
|
* |
|
* Traversal function for the "child" direction |
|
* The child axis contains the children of the context node in document order. |
|
* |
|
* Returns the next element following that axis |
|
*/ |
|
xmlNodePtr |
|
xmlXPathNextChild(xmlXPathParserContextPtr ctxt, xmlNodePtr cur) { |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(NULL); |
|
if (cur == NULL) { |
|
if (ctxt->context->node == NULL) return(NULL); |
|
switch (ctxt->context->node->type) { |
|
case XML_ELEMENT_NODE: |
|
case XML_TEXT_NODE: |
|
case XML_CDATA_SECTION_NODE: |
|
case XML_ENTITY_REF_NODE: |
|
case XML_ENTITY_NODE: |
|
case XML_PI_NODE: |
|
case XML_COMMENT_NODE: |
|
case XML_NOTATION_NODE: |
|
case XML_DTD_NODE: |
|
return(ctxt->context->node->children); |
|
case XML_DOCUMENT_NODE: |
|
case XML_DOCUMENT_TYPE_NODE: |
|
case XML_DOCUMENT_FRAG_NODE: |
|
case XML_HTML_DOCUMENT_NODE: |
|
#ifdef LIBXML_DOCB_ENABLED |
|
case XML_DOCB_DOCUMENT_NODE: |
|
#endif |
|
return(((xmlDocPtr) ctxt->context->node)->children); |
|
case XML_ELEMENT_DECL: |
|
case XML_ATTRIBUTE_DECL: |
|
case XML_ENTITY_DECL: |
|
case XML_ATTRIBUTE_NODE: |
|
case XML_NAMESPACE_DECL: |
|
case XML_XINCLUDE_START: |
|
case XML_XINCLUDE_END: |
|
return(NULL); |
|
} |
|
return(NULL); |
|
} |
|
if ((cur->type == XML_DOCUMENT_NODE) || |
|
(cur->type == XML_HTML_DOCUMENT_NODE)) |
|
return(NULL); |
|
return(cur->next); |
|
} |
|
|
|
/** |
|
* xmlXPathNextChildElement: |
|
* @ctxt: the XPath Parser context |
|
* @cur: the current node in the traversal |
|
* |
|
* Traversal function for the "child" direction and nodes of type element. |
|
* The child axis contains the children of the context node in document order. |
|
* |
|
* Returns the next element following that axis |
|
*/ |
|
static xmlNodePtr |
|
xmlXPathNextChildElement(xmlXPathParserContextPtr ctxt, xmlNodePtr cur) { |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(NULL); |
|
if (cur == NULL) { |
|
cur = ctxt->context->node; |
|
if (cur == NULL) return(NULL); |
|
/* |
|
* Get the first element child. |
|
*/ |
|
switch (cur->type) { |
|
case XML_ELEMENT_NODE: |
|
case XML_DOCUMENT_FRAG_NODE: |
|
case XML_ENTITY_REF_NODE: /* URGENT TODO: entify-refs as well? */ |
|
case XML_ENTITY_NODE: |
|
cur = cur->children; |
|
if (cur != NULL) { |
|
if (cur->type == XML_ELEMENT_NODE) |
|
return(cur); |
|
do { |
|
cur = cur->next; |
|
} while ((cur != NULL) && |
|
(cur->type != XML_ELEMENT_NODE)); |
|
return(cur); |
|
} |
|
return(NULL); |
|
case XML_DOCUMENT_NODE: |
|
case XML_HTML_DOCUMENT_NODE: |
|
#ifdef LIBXML_DOCB_ENABLED |
|
case XML_DOCB_DOCUMENT_NODE: |
|
#endif |
|
return(xmlDocGetRootElement((xmlDocPtr) cur)); |
|
default: |
|
return(NULL); |
|
} |
|
return(NULL); |
|
} |
|
/* |
|
* Get the next sibling element node. |
|
*/ |
|
switch (cur->type) { |
|
case XML_ELEMENT_NODE: |
|
case XML_TEXT_NODE: |
|
case XML_ENTITY_REF_NODE: |
|
case XML_ENTITY_NODE: |
|
case XML_CDATA_SECTION_NODE: |
|
case XML_PI_NODE: |
|
case XML_COMMENT_NODE: |
|
case XML_XINCLUDE_END: |
|
break; |
|
/* case XML_DTD_NODE: */ /* URGENT TODO: DTD-node as well? */ |
|
default: |
|
return(NULL); |
|
} |
|
if (cur->next != NULL) { |
|
if (cur->next->type == XML_ELEMENT_NODE) |
|
return(cur->next); |
|
cur = cur->next; |
|
do { |
|
cur = cur->next; |
|
} while ((cur != NULL) && (cur->type != XML_ELEMENT_NODE)); |
|
return(cur); |
|
} |
|
return(NULL); |
|
} |
|
|
|
#if 0 |
|
/** |
|
* xmlXPathNextDescendantOrSelfElemParent: |
|
* @ctxt: the XPath Parser context |
|
* @cur: the current node in the traversal |
|
* |
|
* Traversal function for the "descendant-or-self" axis. |
|
* Additionally it returns only nodes which can be parents of |
|
* element nodes. |
|
* |
|
* |
|
* Returns the next element following that axis |
|
*/ |
|
static xmlNodePtr |
|
xmlXPathNextDescendantOrSelfElemParent(xmlNodePtr cur, |
|
xmlNodePtr contextNode) |
|
{ |
|
if (cur == NULL) { |
|
if (contextNode == NULL) |
|
return(NULL); |
|
switch (contextNode->type) { |
|
case XML_ELEMENT_NODE: |
|
case XML_XINCLUDE_START: |
|
case XML_DOCUMENT_FRAG_NODE: |
|
case XML_DOCUMENT_NODE: |
|
#ifdef LIBXML_DOCB_ENABLED |
|
case XML_DOCB_DOCUMENT_NODE: |
|
#endif |
|
case XML_HTML_DOCUMENT_NODE: |
|
return(contextNode); |
|
default: |
|
return(NULL); |
|
} |
|
return(NULL); |
|
} else { |
|
xmlNodePtr start = cur; |
|
|
|
while (cur != NULL) { |
|
switch (cur->type) { |
|
case XML_ELEMENT_NODE: |
|
/* TODO: OK to have XInclude here? */ |
|
case XML_XINCLUDE_START: |
|
case XML_DOCUMENT_FRAG_NODE: |
|
if (cur != start) |
|
return(cur); |
|
if (cur->children != NULL) { |
|
cur = cur->children; |
|
continue; |
|
} |
|
break; |
|
/* Not sure if we need those here. */ |
|
case XML_DOCUMENT_NODE: |
|
#ifdef LIBXML_DOCB_ENABLED |
|
case XML_DOCB_DOCUMENT_NODE: |
|
#endif |
|
case XML_HTML_DOCUMENT_NODE: |
|
if (cur != start) |
|
return(cur); |
|
return(xmlDocGetRootElement((xmlDocPtr) cur)); |
|
default: |
|
break; |
|
} |
|
|
|
next_sibling: |
|
if ((cur == NULL) || (cur == contextNode)) |
|
return(NULL); |
|
if (cur->next != NULL) { |
|
cur = cur->next; |
|
} else { |
|
cur = cur->parent; |
|
goto next_sibling; |
|
} |
|
} |
|
} |
|
return(NULL); |
|
} |
|
#endif |
|
|
|
/** |
|
* xmlXPathNextDescendant: |
|
* @ctxt: the XPath Parser context |
|
* @cur: the current node in the traversal |
|
* |
|
* Traversal function for the "descendant" direction |
|
* the descendant axis contains the descendants of the context node in document |
|
* order; a descendant is a child or a child of a child and so on. |
|
* |
|
* Returns the next element following that axis |
|
*/ |
|
xmlNodePtr |
|
xmlXPathNextDescendant(xmlXPathParserContextPtr ctxt, xmlNodePtr cur) { |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(NULL); |
|
if (cur == NULL) { |
|
if (ctxt->context->node == NULL) |
|
return(NULL); |
|
if ((ctxt->context->node->type == XML_ATTRIBUTE_NODE) || |
|
(ctxt->context->node->type == XML_NAMESPACE_DECL)) |
|
return(NULL); |
|
|
|
if (ctxt->context->node == (xmlNodePtr) ctxt->context->doc) |
|
return(ctxt->context->doc->children); |
|
return(ctxt->context->node->children); |
|
} |
|
|
|
if (cur->type == XML_NAMESPACE_DECL) |
|
return(NULL); |
|
if (cur->children != NULL) { |
|
/* |
|
* Do not descend on entities declarations |
|
*/ |
|
if (cur->children->type != XML_ENTITY_DECL) { |
|
cur = cur->children; |
|
/* |
|
* Skip DTDs |
|
*/ |
|
if (cur->type != XML_DTD_NODE) |
|
return(cur); |
|
} |
|
} |
|
|
|
if (cur == ctxt->context->node) return(NULL); |
|
|
|
while (cur->next != NULL) { |
|
cur = cur->next; |
|
if ((cur->type != XML_ENTITY_DECL) && |
|
(cur->type != XML_DTD_NODE)) |
|
return(cur); |
|
} |
|
|
|
do { |
|
cur = cur->parent; |
|
if (cur == NULL) break; |
|
if (cur == ctxt->context->node) return(NULL); |
|
if (cur->next != NULL) { |
|
cur = cur->next; |
|
return(cur); |
|
} |
|
} while (cur != NULL); |
|
return(cur); |
|
} |
|
|
|
/** |
|
* xmlXPathNextDescendantOrSelf: |
|
* @ctxt: the XPath Parser context |
|
* @cur: the current node in the traversal |
|
* |
|
* Traversal function for the "descendant-or-self" direction |
|
* the descendant-or-self axis contains the context node and the descendants |
|
* of the context node in document order; thus the context node is the first |
|
* node on the axis, and the first child of the context node is the second node |
|
* on the axis |
|
* |
|
* Returns the next element following that axis |
|
*/ |
|
xmlNodePtr |
|
xmlXPathNextDescendantOrSelf(xmlXPathParserContextPtr ctxt, xmlNodePtr cur) { |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(NULL); |
|
if (cur == NULL) |
|
return(ctxt->context->node); |
|
|
|
if (ctxt->context->node == NULL) |
|
return(NULL); |
|
if ((ctxt->context->node->type == XML_ATTRIBUTE_NODE) || |
|
(ctxt->context->node->type == XML_NAMESPACE_DECL)) |
|
return(NULL); |
|
|
|
return(xmlXPathNextDescendant(ctxt, cur)); |
|
} |
|
|
|
/** |
|
* xmlXPathNextParent: |
|
* @ctxt: the XPath Parser context |
|
* @cur: the current node in the traversal |
|
* |
|
* Traversal function for the "parent" direction |
|
* The parent axis contains the parent of the context node, if there is one. |
|
* |
|
* Returns the next element following that axis |
|
*/ |
|
xmlNodePtr |
|
xmlXPathNextParent(xmlXPathParserContextPtr ctxt, xmlNodePtr cur) { |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(NULL); |
|
/* |
|
* the parent of an attribute or namespace node is the element |
|
* to which the attribute or namespace node is attached |
|
* Namespace handling !!! |
|
*/ |
|
if (cur == NULL) { |
|
if (ctxt->context->node == NULL) return(NULL); |
|
switch (ctxt->context->node->type) { |
|
case XML_ELEMENT_NODE: |
|
case XML_TEXT_NODE: |
|
case XML_CDATA_SECTION_NODE: |
|
case XML_ENTITY_REF_NODE: |
|
case XML_ENTITY_NODE: |
|
case XML_PI_NODE: |
|
case XML_COMMENT_NODE: |
|
case XML_NOTATION_NODE: |
|
case XML_DTD_NODE: |
|
case XML_ELEMENT_DECL: |
|
case XML_ATTRIBUTE_DECL: |
|
case XML_XINCLUDE_START: |
|
case XML_XINCLUDE_END: |
|
case XML_ENTITY_DECL: |
|
if (ctxt->context->node->parent == NULL) |
|
return((xmlNodePtr) ctxt->context->doc); |
|
if ((ctxt->context->node->parent->type == XML_ELEMENT_NODE) && |
|
((ctxt->context->node->parent->name[0] == ' ') || |
|
(xmlStrEqual(ctxt->context->node->parent->name, |
|
BAD_CAST "fake node libxslt")))) |
|
return(NULL); |
|
return(ctxt->context->node->parent); |
|
case XML_ATTRIBUTE_NODE: { |
|
xmlAttrPtr att = (xmlAttrPtr) ctxt->context->node; |
|
|
|
return(att->parent); |
|
} |
|
case XML_DOCUMENT_NODE: |
|
case XML_DOCUMENT_TYPE_NODE: |
|
case XML_DOCUMENT_FRAG_NODE: |
|
case XML_HTML_DOCUMENT_NODE: |
|
#ifdef LIBXML_DOCB_ENABLED |
|
case XML_DOCB_DOCUMENT_NODE: |
|
#endif |
|
return(NULL); |
|
case XML_NAMESPACE_DECL: { |
|
xmlNsPtr ns = (xmlNsPtr) ctxt->context->node; |
|
|
|
if ((ns->next != NULL) && |
|
(ns->next->type != XML_NAMESPACE_DECL)) |
|
return((xmlNodePtr) ns->next); |
|
return(NULL); |
|
} |
|
} |
|
} |
|
return(NULL); |
|
} |
|
|
|
/** |
|
* xmlXPathNextAncestor: |
|
* @ctxt: the XPath Parser context |
|
* @cur: the current node in the traversal |
|
* |
|
* Traversal function for the "ancestor" direction |
|
* the ancestor axis contains the ancestors of the context node; the ancestors |
|
* of the context node consist of the parent of context node and the parent's |
|
* parent and so on; the nodes are ordered in reverse document order; thus the |
|
* parent is the first node on the axis, and the parent's parent is the second |
|
* node on the axis |
|
* |
|
* Returns the next element following that axis |
|
*/ |
|
xmlNodePtr |
|
xmlXPathNextAncestor(xmlXPathParserContextPtr ctxt, xmlNodePtr cur) { |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(NULL); |
|
/* |
|
* the parent of an attribute or namespace node is the element |
|
* to which the attribute or namespace node is attached |
|
* !!!!!!!!!!!!! |
|
*/ |
|
if (cur == NULL) { |
|
if (ctxt->context->node == NULL) return(NULL); |
|
switch (ctxt->context->node->type) { |
|
case XML_ELEMENT_NODE: |
|
case XML_TEXT_NODE: |
|
case XML_CDATA_SECTION_NODE: |
|
case XML_ENTITY_REF_NODE: |
|
case XML_ENTITY_NODE: |
|
case XML_PI_NODE: |
|
case XML_COMMENT_NODE: |
|
case XML_DTD_NODE: |
|
case XML_ELEMENT_DECL: |
|
case XML_ATTRIBUTE_DECL: |
|
case XML_ENTITY_DECL: |
|
case XML_NOTATION_NODE: |
|
case XML_XINCLUDE_START: |
|
case XML_XINCLUDE_END: |
|
if (ctxt->context->node->parent == NULL) |
|
return((xmlNodePtr) ctxt->context->doc); |
|
if ((ctxt->context->node->parent->type == XML_ELEMENT_NODE) && |
|
((ctxt->context->node->parent->name[0] == ' ') || |
|
(xmlStrEqual(ctxt->context->node->parent->name, |
|
BAD_CAST "fake node libxslt")))) |
|
return(NULL); |
|
return(ctxt->context->node->parent); |
|
case XML_ATTRIBUTE_NODE: { |
|
xmlAttrPtr tmp = (xmlAttrPtr) ctxt->context->node; |
|
|
|
return(tmp->parent); |
|
} |
|
case XML_DOCUMENT_NODE: |
|
case XML_DOCUMENT_TYPE_NODE: |
|
case XML_DOCUMENT_FRAG_NODE: |
|
case XML_HTML_DOCUMENT_NODE: |
|
#ifdef LIBXML_DOCB_ENABLED |
|
case XML_DOCB_DOCUMENT_NODE: |
|
#endif |
|
return(NULL); |
|
case XML_NAMESPACE_DECL: { |
|
xmlNsPtr ns = (xmlNsPtr) ctxt->context->node; |
|
|
|
if ((ns->next != NULL) && |
|
(ns->next->type != XML_NAMESPACE_DECL)) |
|
return((xmlNodePtr) ns->next); |
|
/* Bad, how did that namespace end up here ? */ |
|
return(NULL); |
|
} |
|
} |
|
return(NULL); |
|
} |
|
if (cur == ctxt->context->doc->children) |
|
return((xmlNodePtr) ctxt->context->doc); |
|
if (cur == (xmlNodePtr) ctxt->context->doc) |
|
return(NULL); |
|
switch (cur->type) { |
|
case XML_ELEMENT_NODE: |
|
case XML_TEXT_NODE: |
|
case XML_CDATA_SECTION_NODE: |
|
case XML_ENTITY_REF_NODE: |
|
case XML_ENTITY_NODE: |
|
case XML_PI_NODE: |
|
case XML_COMMENT_NODE: |
|
case XML_NOTATION_NODE: |
|
case XML_DTD_NODE: |
|
case XML_ELEMENT_DECL: |
|
case XML_ATTRIBUTE_DECL: |
|
case XML_ENTITY_DECL: |
|
case XML_XINCLUDE_START: |
|
case XML_XINCLUDE_END: |
|
if (cur->parent == NULL) |
|
return(NULL); |
|
if ((cur->parent->type == XML_ELEMENT_NODE) && |
|
((cur->parent->name[0] == ' ') || |
|
(xmlStrEqual(cur->parent->name, |
|
BAD_CAST "fake node libxslt")))) |
|
return(NULL); |
|
return(cur->parent); |
|
case XML_ATTRIBUTE_NODE: { |
|
xmlAttrPtr att = (xmlAttrPtr) cur; |
|
|
|
return(att->parent); |
|
} |
|
case XML_NAMESPACE_DECL: { |
|
xmlNsPtr ns = (xmlNsPtr) cur; |
|
|
|
if ((ns->next != NULL) && |
|
(ns->next->type != XML_NAMESPACE_DECL)) |
|
return((xmlNodePtr) ns->next); |
|
/* Bad, how did that namespace end up here ? */ |
|
return(NULL); |
|
} |
|
case XML_DOCUMENT_NODE: |
|
case XML_DOCUMENT_TYPE_NODE: |
|
case XML_DOCUMENT_FRAG_NODE: |
|
case XML_HTML_DOCUMENT_NODE: |
|
#ifdef LIBXML_DOCB_ENABLED |
|
case XML_DOCB_DOCUMENT_NODE: |
|
#endif |
|
return(NULL); |
|
} |
|
return(NULL); |
|
} |
|
|
|
/** |
|
* xmlXPathNextAncestorOrSelf: |
|
* @ctxt: the XPath Parser context |
|
* @cur: the current node in the traversal |
|
* |
|
* Traversal function for the "ancestor-or-self" direction |
|
* he ancestor-or-self axis contains the context node and ancestors of |
|
* the context node in reverse document order; thus the context node is |
|
* the first node on the axis, and the context node's parent the second; |
|
* parent here is defined the same as with the parent axis. |
|
* |
|
* Returns the next element following that axis |
|
*/ |
|
xmlNodePtr |
|
xmlXPathNextAncestorOrSelf(xmlXPathParserContextPtr ctxt, xmlNodePtr cur) { |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(NULL); |
|
if (cur == NULL) |
|
return(ctxt->context->node); |
|
return(xmlXPathNextAncestor(ctxt, cur)); |
|
} |
|
|
|
/** |
|
* xmlXPathNextFollowingSibling: |
|
* @ctxt: the XPath Parser context |
|
* @cur: the current node in the traversal |
|
* |
|
* Traversal function for the "following-sibling" direction |
|
* The following-sibling axis contains the following siblings of the context |
|
* node in document order. |
|
* |
|
* Returns the next element following that axis |
|
*/ |
|
xmlNodePtr |
|
xmlXPathNextFollowingSibling(xmlXPathParserContextPtr ctxt, xmlNodePtr cur) { |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(NULL); |
|
if ((ctxt->context->node->type == XML_ATTRIBUTE_NODE) || |
|
(ctxt->context->node->type == XML_NAMESPACE_DECL)) |
|
return(NULL); |
|
if (cur == (xmlNodePtr) ctxt->context->doc) |
|
return(NULL); |
|
if (cur == NULL) |
|
return(ctxt->context->node->next); |
|
return(cur->next); |
|
} |
|
|
|
/** |
|
* xmlXPathNextPrecedingSibling: |
|
* @ctxt: the XPath Parser context |
|
* @cur: the current node in the traversal |
|
* |
|
* Traversal function for the "preceding-sibling" direction |
|
* The preceding-sibling axis contains the preceding siblings of the context |
|
* node in reverse document order; the first preceding sibling is first on the |
|
* axis; the sibling preceding that node is the second on the axis and so on. |
|
* |
|
* Returns the next element following that axis |
|
*/ |
|
xmlNodePtr |
|
xmlXPathNextPrecedingSibling(xmlXPathParserContextPtr ctxt, xmlNodePtr cur) { |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(NULL); |
|
if ((ctxt->context->node->type == XML_ATTRIBUTE_NODE) || |
|
(ctxt->context->node->type == XML_NAMESPACE_DECL)) |
|
return(NULL); |
|
if (cur == (xmlNodePtr) ctxt->context->doc) |
|
return(NULL); |
|
if (cur == NULL) |
|
return(ctxt->context->node->prev); |
|
if ((cur->prev != NULL) && (cur->prev->type == XML_DTD_NODE)) { |
|
cur = cur->prev; |
|
if (cur == NULL) |
|
return(ctxt->context->node->prev); |
|
} |
|
return(cur->prev); |
|
} |
|
|
|
/** |
|
* xmlXPathNextFollowing: |
|
* @ctxt: the XPath Parser context |
|
* @cur: the current node in the traversal |
|
* |
|
* Traversal function for the "following" direction |
|
* The following axis contains all nodes in the same document as the context |
|
* node that are after the context node in document order, excluding any |
|
* descendants and excluding attribute nodes and namespace nodes; the nodes |
|
* are ordered in document order |
|
* |
|
* Returns the next element following that axis |
|
*/ |
|
xmlNodePtr |
|
xmlXPathNextFollowing(xmlXPathParserContextPtr ctxt, xmlNodePtr cur) { |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(NULL); |
|
if ((cur != NULL) && (cur->type != XML_ATTRIBUTE_NODE) && |
|
(cur->type != XML_NAMESPACE_DECL) && (cur->children != NULL)) |
|
return(cur->children); |
|
|
|
if (cur == NULL) { |
|
cur = ctxt->context->node; |
|
if (cur->type == XML_ATTRIBUTE_NODE) { |
|
cur = cur->parent; |
|
} else if (cur->type == XML_NAMESPACE_DECL) { |
|
xmlNsPtr ns = (xmlNsPtr) cur; |
|
|
|
if ((ns->next == NULL) || |
|
(ns->next->type == XML_NAMESPACE_DECL)) |
|
return (NULL); |
|
cur = (xmlNodePtr) ns->next; |
|
} |
|
} |
|
if (cur == NULL) return(NULL) ; /* ERROR */ |
|
if (cur->next != NULL) return(cur->next) ; |
|
do { |
|
cur = cur->parent; |
|
if (cur == NULL) break; |
|
if (cur == (xmlNodePtr) ctxt->context->doc) return(NULL); |
|
if (cur->next != NULL) return(cur->next); |
|
} while (cur != NULL); |
|
return(cur); |
|
} |
|
|
|
/* |
|
* xmlXPathIsAncestor: |
|
* @ancestor: the ancestor node |
|
* @node: the current node |
|
* |
|
* Check that @ancestor is a @node's ancestor |
|
* |
|
* returns 1 if @ancestor is a @node's ancestor, 0 otherwise. |
|
*/ |
|
static int |
|
xmlXPathIsAncestor(xmlNodePtr ancestor, xmlNodePtr node) { |
|
if ((ancestor == NULL) || (node == NULL)) return(0); |
|
if (node->type == XML_NAMESPACE_DECL) |
|
return(0); |
|
if (ancestor->type == XML_NAMESPACE_DECL) |
|
return(0); |
|
/* nodes need to be in the same document */ |
|
if (ancestor->doc != node->doc) return(0); |
|
/* avoid searching if ancestor or node is the root node */ |
|
if (ancestor == (xmlNodePtr) node->doc) return(1); |
|
if (node == (xmlNodePtr) ancestor->doc) return(0); |
|
while (node->parent != NULL) { |
|
if (node->parent == ancestor) |
|
return(1); |
|
node = node->parent; |
|
} |
|
return(0); |
|
} |
|
|
|
/** |
|
* xmlXPathNextPreceding: |
|
* @ctxt: the XPath Parser context |
|
* @cur: the current node in the traversal |
|
* |
|
* Traversal function for the "preceding" direction |
|
* the preceding axis contains all nodes in the same document as the context |
|
* node that are before the context node in document order, excluding any |
|
* ancestors and excluding attribute nodes and namespace nodes; the nodes are |
|
* ordered in reverse document order |
|
* |
|
* Returns the next element following that axis |
|
*/ |
|
xmlNodePtr |
|
xmlXPathNextPreceding(xmlXPathParserContextPtr ctxt, xmlNodePtr cur) |
|
{ |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(NULL); |
|
if (cur == NULL) { |
|
cur = ctxt->context->node; |
|
if (cur->type == XML_ATTRIBUTE_NODE) { |
|
cur = cur->parent; |
|
} else if (cur->type == XML_NAMESPACE_DECL) { |
|
xmlNsPtr ns = (xmlNsPtr) cur; |
|
|
|
if ((ns->next == NULL) || |
|
(ns->next->type == XML_NAMESPACE_DECL)) |
|
return (NULL); |
|
cur = (xmlNodePtr) ns->next; |
|
} |
|
} |
|
if ((cur == NULL) || (cur->type == XML_NAMESPACE_DECL)) |
|
return (NULL); |
|
if ((cur->prev != NULL) && (cur->prev->type == XML_DTD_NODE)) |
|
cur = cur->prev; |
|
do { |
|
if (cur->prev != NULL) { |
|
for (cur = cur->prev; cur->last != NULL; cur = cur->last) ; |
|
return (cur); |
|
} |
|
|
|
cur = cur->parent; |
|
if (cur == NULL) |
|
return (NULL); |
|
if (cur == ctxt->context->doc->children) |
|
return (NULL); |
|
} while (xmlXPathIsAncestor(cur, ctxt->context->node)); |
|
return (cur); |
|
} |
|
|
|
/** |
|
* xmlXPathNextPrecedingInternal: |
|
* @ctxt: the XPath Parser context |
|
* @cur: the current node in the traversal |
|
* |
|
* Traversal function for the "preceding" direction |
|
* the preceding axis contains all nodes in the same document as the context |
|
* node that are before the context node in document order, excluding any |
|
* ancestors and excluding attribute nodes and namespace nodes; the nodes are |
|
* ordered in reverse document order |
|
* This is a faster implementation but internal only since it requires a |
|
* state kept in the parser context: ctxt->ancestor. |
|
* |
|
* Returns the next element following that axis |
|
*/ |
|
static xmlNodePtr |
|
xmlXPathNextPrecedingInternal(xmlXPathParserContextPtr ctxt, |
|
xmlNodePtr cur) |
|
{ |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(NULL); |
|
if (cur == NULL) { |
|
cur = ctxt->context->node; |
|
if (cur == NULL) |
|
return (NULL); |
|
if (cur->type == XML_ATTRIBUTE_NODE) { |
|
cur = cur->parent; |
|
} else if (cur->type == XML_NAMESPACE_DECL) { |
|
xmlNsPtr ns = (xmlNsPtr) cur; |
|
|
|
if ((ns->next == NULL) || |
|
(ns->next->type == XML_NAMESPACE_DECL)) |
|
return (NULL); |
|
cur = (xmlNodePtr) ns->next; |
|
} |
|
ctxt->ancestor = cur->parent; |
|
} |
|
if (cur->type == XML_NAMESPACE_DECL) |
|
return(NULL); |
|
if ((cur->prev != NULL) && (cur->prev->type == XML_DTD_NODE)) |
|
cur = cur->prev; |
|
while (cur->prev == NULL) { |
|
cur = cur->parent; |
|
if (cur == NULL) |
|
return (NULL); |
|
if (cur == ctxt->context->doc->children) |
|
return (NULL); |
|
if (cur != ctxt->ancestor) |
|
return (cur); |
|
ctxt->ancestor = cur->parent; |
|
} |
|
cur = cur->prev; |
|
while (cur->last != NULL) |
|
cur = cur->last; |
|
return (cur); |
|
} |
|
|
|
/** |
|
* xmlXPathNextNamespace: |
|
* @ctxt: the XPath Parser context |
|
* @cur: the current attribute in the traversal |
|
* |
|
* Traversal function for the "namespace" direction |
|
* the namespace axis contains the namespace nodes of the context node; |
|
* the order of nodes on this axis is implementation-defined; the axis will |
|
* be empty unless the context node is an element |
|
* |
|
* We keep the XML namespace node at the end of the list. |
|
* |
|
* Returns the next element following that axis |
|
*/ |
|
xmlNodePtr |
|
xmlXPathNextNamespace(xmlXPathParserContextPtr ctxt, xmlNodePtr cur) { |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(NULL); |
|
if (ctxt->context->node->type != XML_ELEMENT_NODE) return(NULL); |
|
if (cur == NULL) { |
|
if (ctxt->context->tmpNsList != NULL) |
|
xmlFree(ctxt->context->tmpNsList); |
|
ctxt->context->tmpNsList = |
|
xmlGetNsList(ctxt->context->doc, ctxt->context->node); |
|
ctxt->context->tmpNsNr = 0; |
|
if (ctxt->context->tmpNsList != NULL) { |
|
while (ctxt->context->tmpNsList[ctxt->context->tmpNsNr] != NULL) { |
|
ctxt->context->tmpNsNr++; |
|
} |
|
} |
|
return((xmlNodePtr) xmlXPathXMLNamespace); |
|
} |
|
if (ctxt->context->tmpNsNr > 0) { |
|
return (xmlNodePtr)ctxt->context->tmpNsList[--ctxt->context->tmpNsNr]; |
|
} else { |
|
if (ctxt->context->tmpNsList != NULL) |
|
xmlFree(ctxt->context->tmpNsList); |
|
ctxt->context->tmpNsList = NULL; |
|
return(NULL); |
|
} |
|
} |
|
|
|
/** |
|
* xmlXPathNextAttribute: |
|
* @ctxt: the XPath Parser context |
|
* @cur: the current attribute in the traversal |
|
* |
|
* Traversal function for the "attribute" direction |
|
* TODO: support DTD inherited default attributes |
|
* |
|
* Returns the next element following that axis |
|
*/ |
|
xmlNodePtr |
|
xmlXPathNextAttribute(xmlXPathParserContextPtr ctxt, xmlNodePtr cur) { |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) return(NULL); |
|
if (ctxt->context->node == NULL) |
|
return(NULL); |
|
if (ctxt->context->node->type != XML_ELEMENT_NODE) |
|
return(NULL); |
|
if (cur == NULL) { |
|
if (ctxt->context->node == (xmlNodePtr) ctxt->context->doc) |
|
return(NULL); |
|
return((xmlNodePtr)ctxt->context->node->properties); |
|
} |
|
return((xmlNodePtr)cur->next); |
|
} |
|
|
|
/************************************************************************ |
|
* * |
|
* NodeTest Functions * |
|
* * |
|
************************************************************************/ |
|
|
|
#define IS_FUNCTION 200 |
|
|
|
|
|
/************************************************************************ |
|
* * |
|
* Implicit tree core function library * |
|
* * |
|
************************************************************************/ |
|
|
|
/** |
|
* xmlXPathRoot: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* Initialize the context to the root of the document |
|
*/ |
|
void |
|
xmlXPathRoot(xmlXPathParserContextPtr ctxt) { |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) |
|
return; |
|
ctxt->context->node = (xmlNodePtr) ctxt->context->doc; |
|
valuePush(ctxt, xmlXPathCacheNewNodeSet(ctxt->context, |
|
ctxt->context->node)); |
|
} |
|
|
|
/************************************************************************ |
|
* * |
|
* The explicit core function library * |
|
*http://www.w3.org/Style/XSL/Group/1999/07/xpath-19990705.html#corelib * |
|
* * |
|
************************************************************************/ |
|
|
|
|
|
/** |
|
* xmlXPathLastFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the last() XPath function |
|
* number last() |
|
* The last function returns the number of nodes in the context node list. |
|
*/ |
|
void |
|
xmlXPathLastFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
CHECK_ARITY(0); |
|
if (ctxt->context->contextSize >= 0) { |
|
valuePush(ctxt, |
|
xmlXPathCacheNewFloat(ctxt->context, |
|
(double) ctxt->context->contextSize)); |
|
#ifdef DEBUG_EXPR |
|
xmlGenericError(xmlGenericErrorContext, |
|
"last() : %d\n", ctxt->context->contextSize); |
|
#endif |
|
} else { |
|
XP_ERROR(XPATH_INVALID_CTXT_SIZE); |
|
} |
|
} |
|
|
|
/** |
|
* xmlXPathPositionFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the position() XPath function |
|
* number position() |
|
* The position function returns the position of the context node in the |
|
* context node list. The first position is 1, and so the last position |
|
* will be equal to last(). |
|
*/ |
|
void |
|
xmlXPathPositionFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
CHECK_ARITY(0); |
|
if (ctxt->context->proximityPosition >= 0) { |
|
valuePush(ctxt, |
|
xmlXPathCacheNewFloat(ctxt->context, |
|
(double) ctxt->context->proximityPosition)); |
|
#ifdef DEBUG_EXPR |
|
xmlGenericError(xmlGenericErrorContext, "position() : %d\n", |
|
ctxt->context->proximityPosition); |
|
#endif |
|
} else { |
|
XP_ERROR(XPATH_INVALID_CTXT_POSITION); |
|
} |
|
} |
|
|
|
/** |
|
* xmlXPathCountFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the count() XPath function |
|
* number count(node-set) |
|
*/ |
|
void |
|
xmlXPathCountFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr cur; |
|
|
|
CHECK_ARITY(1); |
|
if ((ctxt->value == NULL) || |
|
((ctxt->value->type != XPATH_NODESET) && |
|
(ctxt->value->type != XPATH_XSLT_TREE))) |
|
XP_ERROR(XPATH_INVALID_TYPE); |
|
cur = valuePop(ctxt); |
|
|
|
if ((cur == NULL) || (cur->nodesetval == NULL)) |
|
valuePush(ctxt, xmlXPathCacheNewFloat(ctxt->context, (double) 0)); |
|
else if ((cur->type == XPATH_NODESET) || (cur->type == XPATH_XSLT_TREE)) { |
|
valuePush(ctxt, xmlXPathCacheNewFloat(ctxt->context, |
|
(double) cur->nodesetval->nodeNr)); |
|
} else { |
|
if ((cur->nodesetval->nodeNr != 1) || |
|
(cur->nodesetval->nodeTab == NULL)) { |
|
valuePush(ctxt, xmlXPathCacheNewFloat(ctxt->context, (double) 0)); |
|
} else { |
|
xmlNodePtr tmp; |
|
int i = 0; |
|
|
|
tmp = cur->nodesetval->nodeTab[0]; |
|
if ((tmp != NULL) && (tmp->type != XML_NAMESPACE_DECL)) { |
|
tmp = tmp->children; |
|
while (tmp != NULL) { |
|
tmp = tmp->next; |
|
i++; |
|
} |
|
} |
|
valuePush(ctxt, xmlXPathCacheNewFloat(ctxt->context, (double) i)); |
|
} |
|
} |
|
xmlXPathReleaseObject(ctxt->context, cur); |
|
} |
|
|
|
/** |
|
* xmlXPathGetElementsByIds: |
|
* @doc: the document |
|
* @ids: a whitespace separated list of IDs |
|
* |
|
* Selects elements by their unique ID. |
|
* |
|
* Returns a node-set of selected elements. |
|
*/ |
|
static xmlNodeSetPtr |
|
xmlXPathGetElementsByIds (xmlDocPtr doc, const xmlChar *ids) { |
|
xmlNodeSetPtr ret; |
|
const xmlChar *cur = ids; |
|
xmlChar *ID; |
|
xmlAttrPtr attr; |
|
xmlNodePtr elem = NULL; |
|
|
|
if (ids == NULL) return(NULL); |
|
|
|
ret = xmlXPathNodeSetCreate(NULL); |
|
if (ret == NULL) |
|
return(ret); |
|
|
|
while (IS_BLANK_CH(*cur)) cur++; |
|
while (*cur != 0) { |
|
while ((!IS_BLANK_CH(*cur)) && (*cur != 0)) |
|
cur++; |
|
|
|
ID = xmlStrndup(ids, cur - ids); |
|
if (ID != NULL) { |
|
/* |
|
* We used to check the fact that the value passed |
|
* was an NCName, but this generated much troubles for |
|
* me and Aleksey Sanin, people blatantly violated that |
|
* constaint, like Visa3D spec. |
|
* if (xmlValidateNCName(ID, 1) == 0) |
|
*/ |
|
attr = xmlGetID(doc, ID); |
|
if (attr != NULL) { |
|
if (attr->type == XML_ATTRIBUTE_NODE) |
|
elem = attr->parent; |
|
else if (attr->type == XML_ELEMENT_NODE) |
|
elem = (xmlNodePtr) attr; |
|
else |
|
elem = NULL; |
|
if (elem != NULL) |
|
xmlXPathNodeSetAdd(ret, elem); |
|
} |
|
xmlFree(ID); |
|
} |
|
|
|
while (IS_BLANK_CH(*cur)) cur++; |
|
ids = cur; |
|
} |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathIdFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the id() XPath function |
|
* node-set id(object) |
|
* The id function selects elements by their unique ID |
|
* (see [5.2.1 Unique IDs]). When the argument to id is of type node-set, |
|
* then the result is the union of the result of applying id to the |
|
* string value of each of the nodes in the argument node-set. When the |
|
* argument to id is of any other type, the argument is converted to a |
|
* string as if by a call to the string function; the string is split |
|
* into a whitespace-separated list of tokens (whitespace is any sequence |
|
* of characters matching the production S); the result is a node-set |
|
* containing the elements in the same document as the context node that |
|
* have a unique ID equal to any of the tokens in the list. |
|
*/ |
|
void |
|
xmlXPathIdFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlChar *tokens; |
|
xmlNodeSetPtr ret; |
|
xmlXPathObjectPtr obj; |
|
|
|
CHECK_ARITY(1); |
|
obj = valuePop(ctxt); |
|
if (obj == NULL) XP_ERROR(XPATH_INVALID_OPERAND); |
|
if ((obj->type == XPATH_NODESET) || (obj->type == XPATH_XSLT_TREE)) { |
|
xmlNodeSetPtr ns; |
|
int i; |
|
|
|
ret = xmlXPathNodeSetCreate(NULL); |
|
/* |
|
* FIXME -- in an out-of-memory condition this will behave badly. |
|
* The solution is not clear -- we already popped an item from |
|
* ctxt, so the object is in a corrupt state. |
|
*/ |
|
|
|
if (obj->nodesetval != NULL) { |
|
for (i = 0; i < obj->nodesetval->nodeNr; i++) { |
|
tokens = |
|
xmlXPathCastNodeToString(obj->nodesetval->nodeTab[i]); |
|
ns = xmlXPathGetElementsByIds(ctxt->context->doc, tokens); |
|
ret = xmlXPathNodeSetMerge(ret, ns); |
|
xmlXPathFreeNodeSet(ns); |
|
if (tokens != NULL) |
|
xmlFree(tokens); |
|
} |
|
} |
|
xmlXPathReleaseObject(ctxt->context, obj); |
|
valuePush(ctxt, xmlXPathCacheWrapNodeSet(ctxt->context, ret)); |
|
return; |
|
} |
|
obj = xmlXPathCacheConvertString(ctxt->context, obj); |
|
ret = xmlXPathGetElementsByIds(ctxt->context->doc, obj->stringval); |
|
valuePush(ctxt, xmlXPathCacheWrapNodeSet(ctxt->context, ret)); |
|
xmlXPathReleaseObject(ctxt->context, obj); |
|
return; |
|
} |
|
|
|
/** |
|
* xmlXPathLocalNameFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the local-name() XPath function |
|
* string local-name(node-set?) |
|
* The local-name function returns a string containing the local part |
|
* of the name of the node in the argument node-set that is first in |
|
* document order. If the node-set is empty or the first node has no |
|
* name, an empty string is returned. If the argument is omitted it |
|
* defaults to the context node. |
|
*/ |
|
void |
|
xmlXPathLocalNameFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr cur; |
|
|
|
if (ctxt == NULL) return; |
|
|
|
if (nargs == 0) { |
|
valuePush(ctxt, xmlXPathCacheNewNodeSet(ctxt->context, |
|
ctxt->context->node)); |
|
nargs = 1; |
|
} |
|
|
|
CHECK_ARITY(1); |
|
if ((ctxt->value == NULL) || |
|
((ctxt->value->type != XPATH_NODESET) && |
|
(ctxt->value->type != XPATH_XSLT_TREE))) |
|
XP_ERROR(XPATH_INVALID_TYPE); |
|
cur = valuePop(ctxt); |
|
|
|
if ((cur->nodesetval == NULL) || (cur->nodesetval->nodeNr == 0)) { |
|
valuePush(ctxt, xmlXPathCacheNewCString(ctxt->context, "")); |
|
} else { |
|
int i = 0; /* Should be first in document order !!!!! */ |
|
switch (cur->nodesetval->nodeTab[i]->type) { |
|
case XML_ELEMENT_NODE: |
|
case XML_ATTRIBUTE_NODE: |
|
case XML_PI_NODE: |
|
if (cur->nodesetval->nodeTab[i]->name[0] == ' ') |
|
valuePush(ctxt, xmlXPathCacheNewCString(ctxt->context, "")); |
|
else |
|
valuePush(ctxt, |
|
xmlXPathCacheNewString(ctxt->context, |
|
cur->nodesetval->nodeTab[i]->name)); |
|
break; |
|
case XML_NAMESPACE_DECL: |
|
valuePush(ctxt, xmlXPathCacheNewString(ctxt->context, |
|
((xmlNsPtr)cur->nodesetval->nodeTab[i])->prefix)); |
|
break; |
|
default: |
|
valuePush(ctxt, xmlXPathCacheNewCString(ctxt->context, "")); |
|
} |
|
} |
|
xmlXPathReleaseObject(ctxt->context, cur); |
|
} |
|
|
|
/** |
|
* xmlXPathNamespaceURIFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the namespace-uri() XPath function |
|
* string namespace-uri(node-set?) |
|
* The namespace-uri function returns a string containing the |
|
* namespace URI of the expanded name of the node in the argument |
|
* node-set that is first in document order. If the node-set is empty, |
|
* the first node has no name, or the expanded name has no namespace |
|
* URI, an empty string is returned. If the argument is omitted it |
|
* defaults to the context node. |
|
*/ |
|
void |
|
xmlXPathNamespaceURIFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr cur; |
|
|
|
if (ctxt == NULL) return; |
|
|
|
if (nargs == 0) { |
|
valuePush(ctxt, xmlXPathCacheNewNodeSet(ctxt->context, |
|
ctxt->context->node)); |
|
nargs = 1; |
|
} |
|
CHECK_ARITY(1); |
|
if ((ctxt->value == NULL) || |
|
((ctxt->value->type != XPATH_NODESET) && |
|
(ctxt->value->type != XPATH_XSLT_TREE))) |
|
XP_ERROR(XPATH_INVALID_TYPE); |
|
cur = valuePop(ctxt); |
|
|
|
if ((cur->nodesetval == NULL) || (cur->nodesetval->nodeNr == 0)) { |
|
valuePush(ctxt, xmlXPathCacheNewCString(ctxt->context, "")); |
|
} else { |
|
int i = 0; /* Should be first in document order !!!!! */ |
|
switch (cur->nodesetval->nodeTab[i]->type) { |
|
case XML_ELEMENT_NODE: |
|
case XML_ATTRIBUTE_NODE: |
|
if (cur->nodesetval->nodeTab[i]->ns == NULL) |
|
valuePush(ctxt, xmlXPathCacheNewCString(ctxt->context, "")); |
|
else |
|
valuePush(ctxt, xmlXPathCacheNewString(ctxt->context, |
|
cur->nodesetval->nodeTab[i]->ns->href)); |
|
break; |
|
default: |
|
valuePush(ctxt, xmlXPathCacheNewCString(ctxt->context, "")); |
|
} |
|
} |
|
xmlXPathReleaseObject(ctxt->context, cur); |
|
} |
|
|
|
/** |
|
* xmlXPathNameFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the name() XPath function |
|
* string name(node-set?) |
|
* The name function returns a string containing a QName representing |
|
* the name of the node in the argument node-set that is first in document |
|
* order. The QName must represent the name with respect to the namespace |
|
* declarations in effect on the node whose name is being represented. |
|
* Typically, this will be the form in which the name occurred in the XML |
|
* source. This need not be the case if there are namespace declarations |
|
* in effect on the node that associate multiple prefixes with the same |
|
* namespace. However, an implementation may include information about |
|
* the original prefix in its representation of nodes; in this case, an |
|
* implementation can ensure that the returned string is always the same |
|
* as the QName used in the XML source. If the argument it omitted it |
|
* defaults to the context node. |
|
* Libxml keep the original prefix so the "real qualified name" used is |
|
* returned. |
|
*/ |
|
static void |
|
xmlXPathNameFunction(xmlXPathParserContextPtr ctxt, int nargs) |
|
{ |
|
xmlXPathObjectPtr cur; |
|
|
|
if (nargs == 0) { |
|
valuePush(ctxt, xmlXPathCacheNewNodeSet(ctxt->context, |
|
ctxt->context->node)); |
|
nargs = 1; |
|
} |
|
|
|
CHECK_ARITY(1); |
|
if ((ctxt->value == NULL) || |
|
((ctxt->value->type != XPATH_NODESET) && |
|
(ctxt->value->type != XPATH_XSLT_TREE))) |
|
XP_ERROR(XPATH_INVALID_TYPE); |
|
cur = valuePop(ctxt); |
|
|
|
if ((cur->nodesetval == NULL) || (cur->nodesetval->nodeNr == 0)) { |
|
valuePush(ctxt, xmlXPathCacheNewCString(ctxt->context, "")); |
|
} else { |
|
int i = 0; /* Should be first in document order !!!!! */ |
|
|
|
switch (cur->nodesetval->nodeTab[i]->type) { |
|
case XML_ELEMENT_NODE: |
|
case XML_ATTRIBUTE_NODE: |
|
if (cur->nodesetval->nodeTab[i]->name[0] == ' ') |
|
valuePush(ctxt, |
|
xmlXPathCacheNewCString(ctxt->context, "")); |
|
else if ((cur->nodesetval->nodeTab[i]->ns == NULL) || |
|
(cur->nodesetval->nodeTab[i]->ns->prefix == NULL)) { |
|
valuePush(ctxt, |
|
xmlXPathCacheNewString(ctxt->context, |
|
cur->nodesetval->nodeTab[i]->name)); |
|
} else { |
|
xmlChar *fullname; |
|
|
|
fullname = xmlBuildQName(cur->nodesetval->nodeTab[i]->name, |
|
cur->nodesetval->nodeTab[i]->ns->prefix, |
|
NULL, 0); |
|
if (fullname == cur->nodesetval->nodeTab[i]->name) |
|
fullname = xmlStrdup(cur->nodesetval->nodeTab[i]->name); |
|
if (fullname == NULL) { |
|
XP_ERROR(XPATH_MEMORY_ERROR); |
|
} |
|
valuePush(ctxt, xmlXPathCacheWrapString( |
|
ctxt->context, fullname)); |
|
} |
|
break; |
|
default: |
|
valuePush(ctxt, xmlXPathCacheNewNodeSet(ctxt->context, |
|
cur->nodesetval->nodeTab[i])); |
|
xmlXPathLocalNameFunction(ctxt, 1); |
|
} |
|
} |
|
xmlXPathReleaseObject(ctxt->context, cur); |
|
} |
|
|
|
|
|
/** |
|
* xmlXPathStringFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the string() XPath function |
|
* string string(object?) |
|
* The string function converts an object to a string as follows: |
|
* - A node-set is converted to a string by returning the value of |
|
* the node in the node-set that is first in document order. |
|
* If the node-set is empty, an empty string is returned. |
|
* - A number is converted to a string as follows |
|
* + NaN is converted to the string NaN |
|
* + positive zero is converted to the string 0 |
|
* + negative zero is converted to the string 0 |
|
* + positive infinity is converted to the string Infinity |
|
* + negative infinity is converted to the string -Infinity |
|
* + if the number is an integer, the number is represented in |
|
* decimal form as a Number with no decimal point and no leading |
|
* zeros, preceded by a minus sign (-) if the number is negative |
|
* + otherwise, the number is represented in decimal form as a |
|
* Number including a decimal point with at least one digit |
|
* before the decimal point and at least one digit after the |
|
* decimal point, preceded by a minus sign (-) if the number |
|
* is negative; there must be no leading zeros before the decimal |
|
* point apart possibly from the one required digit immediately |
|
* before the decimal point; beyond the one required digit |
|
* after the decimal point there must be as many, but only as |
|
* many, more digits as are needed to uniquely distinguish the |
|
* number from all other IEEE 754 numeric values. |
|
* - The boolean false value is converted to the string false. |
|
* The boolean true value is converted to the string true. |
|
* |
|
* If the argument is omitted, it defaults to a node-set with the |
|
* context node as its only member. |
|
*/ |
|
void |
|
xmlXPathStringFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr cur; |
|
|
|
if (ctxt == NULL) return; |
|
if (nargs == 0) { |
|
valuePush(ctxt, |
|
xmlXPathCacheWrapString(ctxt->context, |
|
xmlXPathCastNodeToString(ctxt->context->node))); |
|
return; |
|
} |
|
|
|
CHECK_ARITY(1); |
|
cur = valuePop(ctxt); |
|
if (cur == NULL) XP_ERROR(XPATH_INVALID_OPERAND); |
|
valuePush(ctxt, xmlXPathCacheConvertString(ctxt->context, cur)); |
|
} |
|
|
|
/** |
|
* xmlXPathStringLengthFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the string-length() XPath function |
|
* number string-length(string?) |
|
* The string-length returns the number of characters in the string |
|
* (see [3.6 Strings]). If the argument is omitted, it defaults to |
|
* the context node converted to a string, in other words the value |
|
* of the context node. |
|
*/ |
|
void |
|
xmlXPathStringLengthFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr cur; |
|
|
|
if (nargs == 0) { |
|
if ((ctxt == NULL) || (ctxt->context == NULL)) |
|
return; |
|
if (ctxt->context->node == NULL) { |
|
valuePush(ctxt, xmlXPathCacheNewFloat(ctxt->context, 0)); |
|
} else { |
|
xmlChar *content; |
|
|
|
content = xmlXPathCastNodeToString(ctxt->context->node); |
|
valuePush(ctxt, xmlXPathCacheNewFloat(ctxt->context, |
|
xmlUTF8Strlen(content))); |
|
xmlFree(content); |
|
} |
|
return; |
|
} |
|
CHECK_ARITY(1); |
|
CAST_TO_STRING; |
|
CHECK_TYPE(XPATH_STRING); |
|
cur = valuePop(ctxt); |
|
valuePush(ctxt, xmlXPathCacheNewFloat(ctxt->context, |
|
xmlUTF8Strlen(cur->stringval))); |
|
xmlXPathReleaseObject(ctxt->context, cur); |
|
} |
|
|
|
/** |
|
* xmlXPathConcatFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the concat() XPath function |
|
* string concat(string, string, string*) |
|
* The concat function returns the concatenation of its arguments. |
|
*/ |
|
void |
|
xmlXPathConcatFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr cur, newobj; |
|
xmlChar *tmp; |
|
|
|
if (ctxt == NULL) return; |
|
if (nargs < 2) { |
|
CHECK_ARITY(2); |
|
} |
|
|
|
CAST_TO_STRING; |
|
cur = valuePop(ctxt); |
|
if ((cur == NULL) || (cur->type != XPATH_STRING)) { |
|
xmlXPathReleaseObject(ctxt->context, cur); |
|
return; |
|
} |
|
nargs--; |
|
|
|
while (nargs > 0) { |
|
CAST_TO_STRING; |
|
newobj = valuePop(ctxt); |
|
if ((newobj == NULL) || (newobj->type != XPATH_STRING)) { |
|
xmlXPathReleaseObject(ctxt->context, newobj); |
|
xmlXPathReleaseObject(ctxt->context, cur); |
|
XP_ERROR(XPATH_INVALID_TYPE); |
|
} |
|
tmp = xmlStrcat(newobj->stringval, cur->stringval); |
|
newobj->stringval = cur->stringval; |
|
cur->stringval = tmp; |
|
xmlXPathReleaseObject(ctxt->context, newobj); |
|
nargs--; |
|
} |
|
valuePush(ctxt, cur); |
|
} |
|
|
|
/** |
|
* xmlXPathContainsFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the contains() XPath function |
|
* boolean contains(string, string) |
|
* The contains function returns true if the first argument string |
|
* contains the second argument string, and otherwise returns false. |
|
*/ |
|
void |
|
xmlXPathContainsFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr hay, needle; |
|
|
|
CHECK_ARITY(2); |
|
CAST_TO_STRING; |
|
CHECK_TYPE(XPATH_STRING); |
|
needle = valuePop(ctxt); |
|
CAST_TO_STRING; |
|
hay = valuePop(ctxt); |
|
|
|
if ((hay == NULL) || (hay->type != XPATH_STRING)) { |
|
xmlXPathReleaseObject(ctxt->context, hay); |
|
xmlXPathReleaseObject(ctxt->context, needle); |
|
XP_ERROR(XPATH_INVALID_TYPE); |
|
} |
|
if (xmlStrstr(hay->stringval, needle->stringval)) |
|
valuePush(ctxt, xmlXPathCacheNewBoolean(ctxt->context, 1)); |
|
else |
|
valuePush(ctxt, xmlXPathCacheNewBoolean(ctxt->context, 0)); |
|
xmlXPathReleaseObject(ctxt->context, hay); |
|
xmlXPathReleaseObject(ctxt->context, needle); |
|
} |
|
|
|
/** |
|
* xmlXPathStartsWithFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the starts-with() XPath function |
|
* boolean starts-with(string, string) |
|
* The starts-with function returns true if the first argument string |
|
* starts with the second argument string, and otherwise returns false. |
|
*/ |
|
void |
|
xmlXPathStartsWithFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr hay, needle; |
|
int n; |
|
|
|
CHECK_ARITY(2); |
|
CAST_TO_STRING; |
|
CHECK_TYPE(XPATH_STRING); |
|
needle = valuePop(ctxt); |
|
CAST_TO_STRING; |
|
hay = valuePop(ctxt); |
|
|
|
if ((hay == NULL) || (hay->type != XPATH_STRING)) { |
|
xmlXPathReleaseObject(ctxt->context, hay); |
|
xmlXPathReleaseObject(ctxt->context, needle); |
|
XP_ERROR(XPATH_INVALID_TYPE); |
|
} |
|
n = xmlStrlen(needle->stringval); |
|
if (xmlStrncmp(hay->stringval, needle->stringval, n)) |
|
valuePush(ctxt, xmlXPathCacheNewBoolean(ctxt->context, 0)); |
|
else |
|
valuePush(ctxt, xmlXPathCacheNewBoolean(ctxt->context, 1)); |
|
xmlXPathReleaseObject(ctxt->context, hay); |
|
xmlXPathReleaseObject(ctxt->context, needle); |
|
} |
|
|
|
/** |
|
* xmlXPathSubstringFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the substring() XPath function |
|
* string substring(string, number, number?) |
|
* The substring function returns the substring of the first argument |
|
* starting at the position specified in the second argument with |
|
* length specified in the third argument. For example, |
|
* substring("12345",2,3) returns "234". If the third argument is not |
|
* specified, it returns the substring starting at the position specified |
|
* in the second argument and continuing to the end of the string. For |
|
* example, substring("12345",2) returns "2345". More precisely, each |
|
* character in the string (see [3.6 Strings]) is considered to have a |
|
* numeric position: the position of the first character is 1, the position |
|
* of the second character is 2 and so on. The returned substring contains |
|
* those characters for which the position of the character is greater than |
|
* or equal to the second argument and, if the third argument is specified, |
|
* less than the sum of the second and third arguments; the comparisons |
|
* and addition used for the above follow the standard IEEE 754 rules. Thus: |
|
* - substring("12345", 1.5, 2.6) returns "234" |
|
* - substring("12345", 0, 3) returns "12" |
|
* - substring("12345", 0 div 0, 3) returns "" |
|
* - substring("12345", 1, 0 div 0) returns "" |
|
* - substring("12345", -42, 1 div 0) returns "12345" |
|
* - substring("12345", -1 div 0, 1 div 0) returns "" |
|
*/ |
|
void |
|
xmlXPathSubstringFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr str, start, len; |
|
double le=0, in; |
|
int i, l, m; |
|
xmlChar *ret; |
|
|
|
if (nargs < 2) { |
|
CHECK_ARITY(2); |
|
} |
|
if (nargs > 3) { |
|
CHECK_ARITY(3); |
|
} |
|
/* |
|
* take care of possible last (position) argument |
|
*/ |
|
if (nargs == 3) { |
|
CAST_TO_NUMBER; |
|
CHECK_TYPE(XPATH_NUMBER); |
|
len = valuePop(ctxt); |
|
le = len->floatval; |
|
xmlXPathReleaseObject(ctxt->context, len); |
|
} |
|
|
|
CAST_TO_NUMBER; |
|
CHECK_TYPE(XPATH_NUMBER); |
|
start = valuePop(ctxt); |
|
in = start->floatval; |
|
xmlXPathReleaseObject(ctxt->context, start); |
|
CAST_TO_STRING; |
|
CHECK_TYPE(XPATH_STRING); |
|
str = valuePop(ctxt); |
|
m = xmlUTF8Strlen((const unsigned char *)str->stringval); |
|
|
|
/* |
|
* If last pos not present, calculate last position |
|
*/ |
|
if (nargs != 3) { |
|
le = (double)m; |
|
if (in < 1.0) |
|
in = 1.0; |
|
} |
|
|
|
/* Need to check for the special cases where either |
|
* the index is NaN, the length is NaN, or both |
|
* arguments are infinity (relying on Inf + -Inf = NaN) |
|
*/ |
|
if (!xmlXPathIsInf(in) && !xmlXPathIsNaN(in + le)) { |
|
/* |
|
* To meet the requirements of the spec, the arguments |
|
* must be converted to integer format before |
|
* initial index calculations are done |
|
* |
|
* First we go to integer form, rounding up |
|
* and checking for special cases |
|
*/ |
|
i = (int) in; |
|
if (((double)i)+0.5 <= in) i++; |
|
|
|
if (xmlXPathIsInf(le) == 1) { |
|
l = m; |
|
if (i < 1) |
|
i = 1; |
|
} |
|
else if (xmlXPathIsInf(le) == -1 || le < 0.0) |
|
l = 0; |
|
else { |
|
l = (int) le; |
|
if (((double)l)+0.5 <= le) l++; |
|
} |
|
|
|
/* Now we normalize inidices */ |
|
i -= 1; |
|
l += i; |
|
if (i < 0) |
|
i = 0; |
|
if (l > m) |
|
l = m; |
|
|
|
/* number of chars to copy */ |
|
l -= i; |
|
|
|
ret = xmlUTF8Strsub(str->stringval, i, l); |
|
} |
|
else { |
|
ret = NULL; |
|
} |
|
if (ret == NULL) |
|
valuePush(ctxt, xmlXPathCacheNewCString(ctxt->context, "")); |
|
else { |
|
valuePush(ctxt, xmlXPathCacheNewString(ctxt->context, ret)); |
|
xmlFree(ret); |
|
} |
|
xmlXPathReleaseObject(ctxt->context, str); |
|
} |
|
|
|
/** |
|
* xmlXPathSubstringBeforeFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the substring-before() XPath function |
|
* string substring-before(string, string) |
|
* The substring-before function returns the substring of the first |
|
* argument string that precedes the first occurrence of the second |
|
* argument string in the first argument string, or the empty string |
|
* if the first argument string does not contain the second argument |
|
* string. For example, substring-before("1999/04/01","/") returns 1999. |
|
*/ |
|
void |
|
xmlXPathSubstringBeforeFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr str; |
|
xmlXPathObjectPtr find; |
|
xmlBufPtr target; |
|
const xmlChar *point; |
|
int offset; |
|
|
|
CHECK_ARITY(2); |
|
CAST_TO_STRING; |
|
find = valuePop(ctxt); |
|
CAST_TO_STRING; |
|
str = valuePop(ctxt); |
|
|
|
target = xmlBufCreate(); |
|
if (target) { |
|
point = xmlStrstr(str->stringval, find->stringval); |
|
if (point) { |
|
offset = (int)(point - str->stringval); |
|
xmlBufAdd(target, str->stringval, offset); |
|
} |
|
valuePush(ctxt, xmlXPathCacheNewString(ctxt->context, |
|
xmlBufContent(target))); |
|
xmlBufFree(target); |
|
} |
|
xmlXPathReleaseObject(ctxt->context, str); |
|
xmlXPathReleaseObject(ctxt->context, find); |
|
} |
|
|
|
/** |
|
* xmlXPathSubstringAfterFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the substring-after() XPath function |
|
* string substring-after(string, string) |
|
* The substring-after function returns the substring of the first |
|
* argument string that follows the first occurrence of the second |
|
* argument string in the first argument string, or the empty stringi |
|
* if the first argument string does not contain the second argument |
|
* string. For example, substring-after("1999/04/01","/") returns 04/01, |
|
* and substring-after("1999/04/01","19") returns 99/04/01. |
|
*/ |
|
void |
|
xmlXPathSubstringAfterFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr str; |
|
xmlXPathObjectPtr find; |
|
xmlBufPtr target; |
|
const xmlChar *point; |
|
int offset; |
|
|
|
CHECK_ARITY(2); |
|
CAST_TO_STRING; |
|
find = valuePop(ctxt); |
|
CAST_TO_STRING; |
|
str = valuePop(ctxt); |
|
|
|
target = xmlBufCreate(); |
|
if (target) { |
|
point = xmlStrstr(str->stringval, find->stringval); |
|
if (point) { |
|
offset = (int)(point - str->stringval) + xmlStrlen(find->stringval); |
|
xmlBufAdd(target, &str->stringval[offset], |
|
xmlStrlen(str->stringval) - offset); |
|
} |
|
valuePush(ctxt, xmlXPathCacheNewString(ctxt->context, |
|
xmlBufContent(target))); |
|
xmlBufFree(target); |
|
} |
|
xmlXPathReleaseObject(ctxt->context, str); |
|
xmlXPathReleaseObject(ctxt->context, find); |
|
} |
|
|
|
/** |
|
* xmlXPathNormalizeFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the normalize-space() XPath function |
|
* string normalize-space(string?) |
|
* The normalize-space function returns the argument string with white |
|
* space normalized by stripping leading and trailing whitespace |
|
* and replacing sequences of whitespace characters by a single |
|
* space. Whitespace characters are the same allowed by the S production |
|
* in XML. If the argument is omitted, it defaults to the context |
|
* node converted to a string, in other words the value of the context node. |
|
*/ |
|
void |
|
xmlXPathNormalizeFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr obj = NULL; |
|
xmlChar *source = NULL; |
|
xmlBufPtr target; |
|
xmlChar blank; |
|
|
|
if (ctxt == NULL) return; |
|
if (nargs == 0) { |
|
/* Use current context node */ |
|
valuePush(ctxt, |
|
xmlXPathCacheWrapString(ctxt->context, |
|
xmlXPathCastNodeToString(ctxt->context->node))); |
|
nargs = 1; |
|
} |
|
|
|
CHECK_ARITY(1); |
|
CAST_TO_STRING; |
|
CHECK_TYPE(XPATH_STRING); |
|
obj = valuePop(ctxt); |
|
source = obj->stringval; |
|
|
|
target = xmlBufCreate(); |
|
if (target && source) { |
|
|
|
/* Skip leading whitespaces */ |
|
while (IS_BLANK_CH(*source)) |
|
source++; |
|
|
|
/* Collapse intermediate whitespaces, and skip trailing whitespaces */ |
|
blank = 0; |
|
while (*source) { |
|
if (IS_BLANK_CH(*source)) { |
|
blank = 0x20; |
|
} else { |
|
if (blank) { |
|
xmlBufAdd(target, &blank, 1); |
|
blank = 0; |
|
} |
|
xmlBufAdd(target, source, 1); |
|
} |
|
source++; |
|
} |
|
valuePush(ctxt, xmlXPathCacheNewString(ctxt->context, |
|
xmlBufContent(target))); |
|
xmlBufFree(target); |
|
} |
|
xmlXPathReleaseObject(ctxt->context, obj); |
|
} |
|
|
|
/** |
|
* xmlXPathTranslateFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the translate() XPath function |
|
* string translate(string, string, string) |
|
* The translate function returns the first argument string with |
|
* occurrences of characters in the second argument string replaced |
|
* by the character at the corresponding position in the third argument |
|
* string. For example, translate("bar","abc","ABC") returns the string |
|
* BAr. If there is a character in the second argument string with no |
|
* character at a corresponding position in the third argument string |
|
* (because the second argument string is longer than the third argument |
|
* string), then occurrences of that character in the first argument |
|
* string are removed. For example, translate("--aaa--","abc-","ABC") |
|
* returns "AAA". If a character occurs more than once in second |
|
* argument string, then the first occurrence determines the replacement |
|
* character. If the third argument string is longer than the second |
|
* argument string, then excess characters are ignored. |
|
*/ |
|
void |
|
xmlXPathTranslateFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr str; |
|
xmlXPathObjectPtr from; |
|
xmlXPathObjectPtr to; |
|
xmlBufPtr target; |
|
int offset, max; |
|
xmlChar ch; |
|
const xmlChar *point; |
|
xmlChar *cptr; |
|
|
|
CHECK_ARITY(3); |
|
|
|
CAST_TO_STRING; |
|
to = valuePop(ctxt); |
|
CAST_TO_STRING; |
|
from = valuePop(ctxt); |
|
CAST_TO_STRING; |
|
str = valuePop(ctxt); |
|
|
|
target = xmlBufCreate(); |
|
if (target) { |
|
max = xmlUTF8Strlen(to->stringval); |
|
for (cptr = str->stringval; (ch=*cptr); ) { |
|
offset = xmlUTF8Strloc(from->stringval, cptr); |
|
if (offset >= 0) { |
|
if (offset < max) { |
|
point = xmlUTF8Strpos(to->stringval, offset); |
|
if (point) |
|
xmlBufAdd(target, point, xmlUTF8Strsize(point, 1)); |
|
} |
|
} else |
|
xmlBufAdd(target, cptr, xmlUTF8Strsize(cptr, 1)); |
|
|
|
/* Step to next character in input */ |
|
cptr++; |
|
if ( ch & 0x80 ) { |
|
/* if not simple ascii, verify proper format */ |
|
if ( (ch & 0xc0) != 0xc0 ) { |
|
xmlGenericError(xmlGenericErrorContext, |
|
"xmlXPathTranslateFunction: Invalid UTF8 string\n"); |
|
/* not asserting an XPath error is probably better */ |
|
break; |
|
} |
|
/* then skip over remaining bytes for this char */ |
|
while ( (ch <<= 1) & 0x80 ) |
|
if ( (*cptr++ & 0xc0) != 0x80 ) { |
|
xmlGenericError(xmlGenericErrorContext, |
|
"xmlXPathTranslateFunction: Invalid UTF8 string\n"); |
|
/* not asserting an XPath error is probably better */ |
|
break; |
|
} |
|
if (ch & 0x80) /* must have had error encountered */ |
|
break; |
|
} |
|
} |
|
} |
|
valuePush(ctxt, xmlXPathCacheNewString(ctxt->context, |
|
xmlBufContent(target))); |
|
xmlBufFree(target); |
|
xmlXPathReleaseObject(ctxt->context, str); |
|
xmlXPathReleaseObject(ctxt->context, from); |
|
xmlXPathReleaseObject(ctxt->context, to); |
|
} |
|
|
|
/** |
|
* xmlXPathBooleanFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the boolean() XPath function |
|
* boolean boolean(object) |
|
* The boolean function converts its argument to a boolean as follows: |
|
* - a number is true if and only if it is neither positive or |
|
* negative zero nor NaN |
|
* - a node-set is true if and only if it is non-empty |
|
* - a string is true if and only if its length is non-zero |
|
*/ |
|
void |
|
xmlXPathBooleanFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr cur; |
|
|
|
CHECK_ARITY(1); |
|
cur = valuePop(ctxt); |
|
if (cur == NULL) XP_ERROR(XPATH_INVALID_OPERAND); |
|
cur = xmlXPathCacheConvertBoolean(ctxt->context, cur); |
|
valuePush(ctxt, cur); |
|
} |
|
|
|
/** |
|
* xmlXPathNotFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the not() XPath function |
|
* boolean not(boolean) |
|
* The not function returns true if its argument is false, |
|
* and false otherwise. |
|
*/ |
|
void |
|
xmlXPathNotFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
CHECK_ARITY(1); |
|
CAST_TO_BOOLEAN; |
|
CHECK_TYPE(XPATH_BOOLEAN); |
|
ctxt->value->boolval = ! ctxt->value->boolval; |
|
} |
|
|
|
/** |
|
* xmlXPathTrueFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the true() XPath function |
|
* boolean true() |
|
*/ |
|
void |
|
xmlXPathTrueFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
CHECK_ARITY(0); |
|
valuePush(ctxt, xmlXPathCacheNewBoolean(ctxt->context, 1)); |
|
} |
|
|
|
/** |
|
* xmlXPathFalseFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the false() XPath function |
|
* boolean false() |
|
*/ |
|
void |
|
xmlXPathFalseFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
CHECK_ARITY(0); |
|
valuePush(ctxt, xmlXPathCacheNewBoolean(ctxt->context, 0)); |
|
} |
|
|
|
/** |
|
* xmlXPathLangFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the lang() XPath function |
|
* boolean lang(string) |
|
* The lang function returns true or false depending on whether the |
|
* language of the context node as specified by xml:lang attributes |
|
* is the same as or is a sublanguage of the language specified by |
|
* the argument string. The language of the context node is determined |
|
* by the value of the xml:lang attribute on the context node, or, if |
|
* the context node has no xml:lang attribute, by the value of the |
|
* xml:lang attribute on the nearest ancestor of the context node that |
|
* has an xml:lang attribute. If there is no such attribute, then lang |
|
* returns false. If there is such an attribute, then lang returns |
|
* true if the attribute value is equal to the argument ignoring case, |
|
* or if there is some suffix starting with - such that the attribute |
|
* value is equal to the argument ignoring that suffix of the attribute |
|
* value and ignoring case. |
|
*/ |
|
void |
|
xmlXPathLangFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr val = NULL; |
|
const xmlChar *theLang = NULL; |
|
const xmlChar *lang; |
|
int ret = 0; |
|
int i; |
|
|
|
CHECK_ARITY(1); |
|
CAST_TO_STRING; |
|
CHECK_TYPE(XPATH_STRING); |
|
val = valuePop(ctxt); |
|
lang = val->stringval; |
|
theLang = xmlNodeGetLang(ctxt->context->node); |
|
if ((theLang != NULL) && (lang != NULL)) { |
|
for (i = 0;lang[i] != 0;i++) |
|
if (toupper(lang[i]) != toupper(theLang[i])) |
|
goto not_equal; |
|
if ((theLang[i] == 0) || (theLang[i] == '-')) |
|
ret = 1; |
|
} |
|
not_equal: |
|
if (theLang != NULL) |
|
xmlFree((void *)theLang); |
|
|
|
xmlXPathReleaseObject(ctxt->context, val); |
|
valuePush(ctxt, xmlXPathCacheNewBoolean(ctxt->context, ret)); |
|
} |
|
|
|
/** |
|
* xmlXPathNumberFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the number() XPath function |
|
* number number(object?) |
|
*/ |
|
void |
|
xmlXPathNumberFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr cur; |
|
double res; |
|
|
|
if (ctxt == NULL) return; |
|
if (nargs == 0) { |
|
if (ctxt->context->node == NULL) { |
|
valuePush(ctxt, xmlXPathCacheNewFloat(ctxt->context, 0.0)); |
|
} else { |
|
xmlChar* content = xmlNodeGetContent(ctxt->context->node); |
|
|
|
res = xmlXPathStringEvalNumber(content); |
|
valuePush(ctxt, xmlXPathCacheNewFloat(ctxt->context, res)); |
|
xmlFree(content); |
|
} |
|
return; |
|
} |
|
|
|
CHECK_ARITY(1); |
|
cur = valuePop(ctxt); |
|
valuePush(ctxt, xmlXPathCacheConvertNumber(ctxt->context, cur)); |
|
} |
|
|
|
/** |
|
* xmlXPathSumFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the sum() XPath function |
|
* number sum(node-set) |
|
* The sum function returns the sum of the values of the nodes in |
|
* the argument node-set. |
|
*/ |
|
void |
|
xmlXPathSumFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr cur; |
|
int i; |
|
double res = 0.0; |
|
|
|
CHECK_ARITY(1); |
|
if ((ctxt->value == NULL) || |
|
((ctxt->value->type != XPATH_NODESET) && |
|
(ctxt->value->type != XPATH_XSLT_TREE))) |
|
XP_ERROR(XPATH_INVALID_TYPE); |
|
cur = valuePop(ctxt); |
|
|
|
if ((cur->nodesetval != NULL) && (cur->nodesetval->nodeNr != 0)) { |
|
for (i = 0; i < cur->nodesetval->nodeNr; i++) { |
|
res += xmlXPathCastNodeToNumber(cur->nodesetval->nodeTab[i]); |
|
} |
|
} |
|
valuePush(ctxt, xmlXPathCacheNewFloat(ctxt->context, res)); |
|
xmlXPathReleaseObject(ctxt->context, cur); |
|
} |
|
|
|
/** |
|
* xmlXPathFloorFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the floor() XPath function |
|
* number floor(number) |
|
* The floor function returns the largest (closest to positive infinity) |
|
* number that is not greater than the argument and that is an integer. |
|
*/ |
|
void |
|
xmlXPathFloorFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
CHECK_ARITY(1); |
|
CAST_TO_NUMBER; |
|
CHECK_TYPE(XPATH_NUMBER); |
|
|
|
ctxt->value->floatval = floor(ctxt->value->floatval); |
|
} |
|
|
|
/** |
|
* xmlXPathCeilingFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the ceiling() XPath function |
|
* number ceiling(number) |
|
* The ceiling function returns the smallest (closest to negative infinity) |
|
* number that is not less than the argument and that is an integer. |
|
*/ |
|
void |
|
xmlXPathCeilingFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
CHECK_ARITY(1); |
|
CAST_TO_NUMBER; |
|
CHECK_TYPE(XPATH_NUMBER); |
|
|
|
ctxt->value->floatval = ceil(ctxt->value->floatval); |
|
} |
|
|
|
/** |
|
* xmlXPathRoundFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the round() XPath function |
|
* number round(number) |
|
* The round function returns the number that is closest to the |
|
* argument and that is an integer. If there are two such numbers, |
|
* then the one that is closest to positive infinity is returned. |
|
*/ |
|
void |
|
xmlXPathRoundFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
double f; |
|
|
|
CHECK_ARITY(1); |
|
CAST_TO_NUMBER; |
|
CHECK_TYPE(XPATH_NUMBER); |
|
|
|
f = ctxt->value->floatval; |
|
|
|
if ((f >= -0.5) && (f < 0.5)) { |
|
/* Handles negative zero. */ |
|
ctxt->value->floatval *= 0.0; |
|
} |
|
else { |
|
double rounded = floor(f); |
|
if (f - rounded >= 0.5) |
|
rounded += 1.0; |
|
ctxt->value->floatval = rounded; |
|
} |
|
} |
|
|
|
/************************************************************************ |
|
* * |
|
* The Parser * |
|
* * |
|
************************************************************************/ |
|
|
|
/* |
|
* a few forward declarations since we use a recursive call based |
|
* implementation. |
|
*/ |
|
static void xmlXPathCompileExpr(xmlXPathParserContextPtr ctxt, int sort); |
|
static void xmlXPathCompPredicate(xmlXPathParserContextPtr ctxt, int filter); |
|
static void xmlXPathCompLocationPath(xmlXPathParserContextPtr ctxt); |
|
static void xmlXPathCompRelativeLocationPath(xmlXPathParserContextPtr ctxt); |
|
static xmlChar * xmlXPathParseNameComplex(xmlXPathParserContextPtr ctxt, |
|
int qualified); |
|
|
|
/** |
|
* xmlXPathCurrentChar: |
|
* @ctxt: the XPath parser context |
|
* @cur: pointer to the beginning of the char |
|
* @len: pointer to the length of the char read |
|
* |
|
* The current char value, if using UTF-8 this may actually span multiple |
|
* bytes in the input buffer. |
|
* |
|
* Returns the current char value and its length |
|
*/ |
|
|
|
static int |
|
xmlXPathCurrentChar(xmlXPathParserContextPtr ctxt, int *len) { |
|
unsigned char c; |
|
unsigned int val; |
|
const xmlChar *cur; |
|
|
|
if (ctxt == NULL) |
|
return(0); |
|
cur = ctxt->cur; |
|
|
|
/* |
|
* We are supposed to handle UTF8, check it's valid |
|
* From rfc2044: encoding of the Unicode values on UTF-8: |
|
* |
|
* UCS-4 range (hex.) UTF-8 octet sequence (binary) |
|
* 0000 0000-0000 007F 0xxxxxxx |
|
* 0000 0080-0000 07FF 110xxxxx 10xxxxxx |
|
* 0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx |
|
* |
|
* Check for the 0x110000 limit too |
|
*/ |
|
c = *cur; |
|
if (c & 0x80) { |
|
if ((cur[1] & 0xc0) != 0x80) |
|
goto encoding_error; |
|
if ((c & 0xe0) == 0xe0) { |
|
|
|
if ((cur[2] & 0xc0) != 0x80) |
|
goto encoding_error; |
|
if ((c & 0xf0) == 0xf0) { |
|
if (((c & 0xf8) != 0xf0) || |
|
((cur[3] & 0xc0) != 0x80)) |
|
goto encoding_error; |
|
/* 4-byte code */ |
|
*len = 4; |
|
val = (cur[0] & 0x7) << 18; |
|
val |= (cur[1] & 0x3f) << 12; |
|
val |= (cur[2] & 0x3f) << 6; |
|
val |= cur[3] & 0x3f; |
|
} else { |
|
/* 3-byte code */ |
|
*len = 3; |
|
val = (cur[0] & 0xf) << 12; |
|
val |= (cur[1] & 0x3f) << 6; |
|
val |= cur[2] & 0x3f; |
|
} |
|
} else { |
|
/* 2-byte code */ |
|
*len = 2; |
|
val = (cur[0] & 0x1f) << 6; |
|
val |= cur[1] & 0x3f; |
|
} |
|
if (!IS_CHAR(val)) { |
|
XP_ERROR0(XPATH_INVALID_CHAR_ERROR); |
|
} |
|
return(val); |
|
} else { |
|
/* 1-byte code */ |
|
*len = 1; |
|
return((int) *cur); |
|
} |
|
encoding_error: |
|
/* |
|
* If we detect an UTF8 error that probably means that the |
|
* input encoding didn't get properly advertised in the |
|
* declaration header. Report the error and switch the encoding |
|
* to ISO-Latin-1 (if you don't like this policy, just declare the |
|
* encoding !) |
|
*/ |
|
*len = 0; |
|
XP_ERROR0(XPATH_ENCODING_ERROR); |
|
} |
|
|
|
/** |
|
* xmlXPathParseNCName: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* parse an XML namespace non qualified name. |
|
* |
|
* [NS 3] NCName ::= (Letter | '_') (NCNameChar)* |
|
* |
|
* [NS 4] NCNameChar ::= Letter | Digit | '.' | '-' | '_' | |
|
* CombiningChar | Extender |
|
* |
|
* Returns the namespace name or NULL |
|
*/ |
|
|
|
xmlChar * |
|
xmlXPathParseNCName(xmlXPathParserContextPtr ctxt) { |
|
const xmlChar *in; |
|
xmlChar *ret; |
|
int count = 0; |
|
|
|
if ((ctxt == NULL) || (ctxt->cur == NULL)) return(NULL); |
|
/* |
|
* Accelerator for simple ASCII names |
|
*/ |
|
in = ctxt->cur; |
|
if (((*in >= 0x61) && (*in <= 0x7A)) || |
|
((*in >= 0x41) && (*in <= 0x5A)) || |
|
(*in == '_')) { |
|
in++; |
|
while (((*in >= 0x61) && (*in <= 0x7A)) || |
|
((*in >= 0x41) && (*in <= 0x5A)) || |
|
((*in >= 0x30) && (*in <= 0x39)) || |
|
(*in == '_') || (*in == '.') || |
|
(*in == '-')) |
|
in++; |
|
if ((*in == ' ') || (*in == '>') || (*in == '/') || |
|
(*in == '[') || (*in == ']') || (*in == ':') || |
|
(*in == '@') || (*in == '*')) { |
|
count = in - ctxt->cur; |
|
if (count == 0) |
|
return(NULL); |
|
ret = xmlStrndup(ctxt->cur, count); |
|
ctxt->cur = in; |
|
return(ret); |
|
} |
|
} |
|
return(xmlXPathParseNameComplex(ctxt, 0)); |
|
} |
|
|
|
|
|
/** |
|
* xmlXPathParseQName: |
|
* @ctxt: the XPath Parser context |
|
* @prefix: a xmlChar ** |
|
* |
|
* parse an XML qualified name |
|
* |
|
* [NS 5] QName ::= (Prefix ':')? LocalPart |
|
* |
|
* [NS 6] Prefix ::= NCName |
|
* |
|
* [NS 7] LocalPart ::= NCName |
|
* |
|
* Returns the function returns the local part, and prefix is updated |
|
* to get the Prefix if any. |
|
*/ |
|
|
|
static xmlChar * |
|
xmlXPathParseQName(xmlXPathParserContextPtr ctxt, xmlChar **prefix) { |
|
xmlChar *ret = NULL; |
|
|
|
*prefix = NULL; |
|
ret = xmlXPathParseNCName(ctxt); |
|
if (ret && CUR == ':') { |
|
*prefix = ret; |
|
NEXT; |
|
ret = xmlXPathParseNCName(ctxt); |
|
} |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathParseName: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* parse an XML name |
|
* |
|
* [4] NameChar ::= Letter | Digit | '.' | '-' | '_' | ':' | |
|
* CombiningChar | Extender |
|
* |
|
* [5] Name ::= (Letter | '_' | ':') (NameChar)* |
|
* |
|
* Returns the namespace name or NULL |
|
*/ |
|
|
|
xmlChar * |
|
xmlXPathParseName(xmlXPathParserContextPtr ctxt) { |
|
const xmlChar *in; |
|
xmlChar *ret; |
|
size_t count = 0; |
|
|
|
if ((ctxt == NULL) || (ctxt->cur == NULL)) return(NULL); |
|
/* |
|
* Accelerator for simple ASCII names |
|
*/ |
|
in = ctxt->cur; |
|
if (((*in >= 0x61) && (*in <= 0x7A)) || |
|
((*in >= 0x41) && (*in <= 0x5A)) || |
|
(*in == '_') || (*in == ':')) { |
|
in++; |
|
while (((*in >= 0x61) && (*in <= 0x7A)) || |
|
((*in >= 0x41) && (*in <= 0x5A)) || |
|
((*in >= 0x30) && (*in <= 0x39)) || |
|
(*in == '_') || (*in == '-') || |
|
(*in == ':') || (*in == '.')) |
|
in++; |
|
if ((*in > 0) && (*in < 0x80)) { |
|
count = in - ctxt->cur; |
|
if (count > XML_MAX_NAME_LENGTH) { |
|
ctxt->cur = in; |
|
XP_ERRORNULL(XPATH_EXPR_ERROR); |
|
} |
|
ret = xmlStrndup(ctxt->cur, count); |
|
ctxt->cur = in; |
|
return(ret); |
|
} |
|
} |
|
return(xmlXPathParseNameComplex(ctxt, 1)); |
|
} |
|
|
|
static xmlChar * |
|
xmlXPathParseNameComplex(xmlXPathParserContextPtr ctxt, int qualified) { |
|
xmlChar buf[XML_MAX_NAMELEN + 5]; |
|
int len = 0, l; |
|
int c; |
|
|
|
/* |
|
* Handler for more complex cases |
|
*/ |
|
c = CUR_CHAR(l); |
|
if ((c == ' ') || (c == '>') || (c == '/') || /* accelerators */ |
|
(c == '[') || (c == ']') || (c == '@') || /* accelerators */ |
|
(c == '*') || /* accelerators */ |
|
(!IS_LETTER(c) && (c != '_') && |
|
((!qualified) || (c != ':')))) { |
|
return(NULL); |
|
} |
|
|
|
while ((c != ' ') && (c != '>') && (c != '/') && /* test bigname.xml */ |
|
((IS_LETTER(c)) || (IS_DIGIT(c)) || |
|
(c == '.') || (c == '-') || |
|
(c == '_') || ((qualified) && (c == ':')) || |
|
(IS_COMBINING(c)) || |
|
(IS_EXTENDER(c)))) { |
|
COPY_BUF(l,buf,len,c); |
|
NEXTL(l); |
|
c = CUR_CHAR(l); |
|
if (len >= XML_MAX_NAMELEN) { |
|
/* |
|
* Okay someone managed to make a huge name, so he's ready to pay |
|
* for the processing speed. |
|
*/ |
|
xmlChar *buffer; |
|
int max = len * 2; |
|
|
|
if (len > XML_MAX_NAME_LENGTH) { |
|
XP_ERRORNULL(XPATH_EXPR_ERROR); |
|
} |
|
buffer = (xmlChar *) xmlMallocAtomic(max * sizeof(xmlChar)); |
|
if (buffer == NULL) { |
|
XP_ERRORNULL(XPATH_MEMORY_ERROR); |
|
} |
|
memcpy(buffer, buf, len); |
|
while ((IS_LETTER(c)) || (IS_DIGIT(c)) || /* test bigname.xml */ |
|
(c == '.') || (c == '-') || |
|
(c == '_') || ((qualified) && (c == ':')) || |
|
(IS_COMBINING(c)) || |
|
(IS_EXTENDER(c))) { |
|
if (len + 10 > max) { |
|
if (max > XML_MAX_NAME_LENGTH) { |
|
XP_ERRORNULL(XPATH_EXPR_ERROR); |
|
} |
|
max *= 2; |
|
buffer = (xmlChar *) xmlRealloc(buffer, |
|
max * sizeof(xmlChar)); |
|
if (buffer == NULL) { |
|
XP_ERRORNULL(XPATH_MEMORY_ERROR); |
|
} |
|
} |
|
COPY_BUF(l,buffer,len,c); |
|
NEXTL(l); |
|
c = CUR_CHAR(l); |
|
} |
|
buffer[len] = 0; |
|
return(buffer); |
|
} |
|
} |
|
if (len == 0) |
|
return(NULL); |
|
return(xmlStrndup(buf, len)); |
|
} |
|
|
|
#define MAX_FRAC 20 |
|
|
|
/** |
|
* xmlXPathStringEvalNumber: |
|
* @str: A string to scan |
|
* |
|
* [30a] Float ::= Number ('e' Digits?)? |
|
* |
|
* [30] Number ::= Digits ('.' Digits?)? |
|
* | '.' Digits |
|
* [31] Digits ::= [0-9]+ |
|
* |
|
* Compile a Number in the string |
|
* In complement of the Number expression, this function also handles |
|
* negative values : '-' Number. |
|
* |
|
* Returns the double value. |
|
*/ |
|
double |
|
xmlXPathStringEvalNumber(const xmlChar *str) { |
|
const xmlChar *cur = str; |
|
double ret; |
|
int ok = 0; |
|
int isneg = 0; |
|
int exponent = 0; |
|
int is_exponent_negative = 0; |
|
#ifdef __GNUC__ |
|
unsigned long tmp = 0; |
|
double temp; |
|
#endif |
|
if (cur == NULL) return(0); |
|
while (IS_BLANK_CH(*cur)) cur++; |
|
if ((*cur != '.') && ((*cur < '0') || (*cur > '9')) && (*cur != '-')) { |
|
return(NAN); |
|
} |
|
if (*cur == '-') { |
|
isneg = 1; |
|
cur++; |
|
} |
|
|
|
#ifdef __GNUC__ |
|
/* |
|
* tmp/temp is a workaround against a gcc compiler bug |
|
* http://veillard.com/gcc.bug |
|
*/ |
|
ret = 0; |
|
while ((*cur >= '0') && (*cur <= '9')) { |
|
ret = ret * 10; |
|
tmp = (*cur - '0'); |
|
ok = 1; |
|
cur++; |
|
temp = (double) tmp; |
|
ret = ret + temp; |
|
} |
|
#else |
|
ret = 0; |
|
while ((*cur >= '0') && (*cur <= '9')) { |
|
ret = ret * 10 + (*cur - '0'); |
|
ok = 1; |
|
cur++; |
|
} |
|
#endif |
|
|
|
if (*cur == '.') { |
|
int v, frac = 0, max; |
|
double fraction = 0; |
|
|
|
cur++; |
|
if (((*cur < '0') || (*cur > '9')) && (!ok)) { |
|
return(NAN); |
|
} |
|
while (*cur == '0') { |
|
frac = frac + 1; |
|
cur++; |
|
} |
|
max = frac + MAX_FRAC; |
|
while (((*cur >= '0') && (*cur <= '9')) && (frac < max)) { |
|
v = (*cur - '0'); |
|
fraction = fraction * 10 + v; |
|
frac = frac + 1; |
|
cur++; |
|
} |
|
fraction /= pow(10.0, frac); |
|
ret = ret + fraction; |
|
while ((*cur >= '0') && (*cur <= '9')) |
|
cur++; |
|
} |
|
if ((*cur == 'e') || (*cur == 'E')) { |
|
cur++; |
|
if (*cur == '-') { |
|
is_exponent_negative = 1; |
|
cur++; |
|
} else if (*cur == '+') { |
|
cur++; |
|
} |
|
while ((*cur >= '0') && (*cur <= '9')) { |
|
if (exponent < 1000000) |
|
exponent = exponent * 10 + (*cur - '0'); |
|
cur++; |
|
} |
|
} |
|
while (IS_BLANK_CH(*cur)) cur++; |
|
if (*cur != 0) return(NAN); |
|
if (isneg) ret = -ret; |
|
if (is_exponent_negative) exponent = -exponent; |
|
ret *= pow(10.0, (double)exponent); |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathCompNumber: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* [30] Number ::= Digits ('.' Digits?)? |
|
* | '.' Digits |
|
* [31] Digits ::= [0-9]+ |
|
* |
|
* Compile a Number, then push it on the stack |
|
* |
|
*/ |
|
static void |
|
xmlXPathCompNumber(xmlXPathParserContextPtr ctxt) |
|
{ |
|
double ret = 0.0; |
|
int ok = 0; |
|
int exponent = 0; |
|
int is_exponent_negative = 0; |
|
#ifdef __GNUC__ |
|
unsigned long tmp = 0; |
|
double temp; |
|
#endif |
|
|
|
CHECK_ERROR; |
|
if ((CUR != '.') && ((CUR < '0') || (CUR > '9'))) { |
|
XP_ERROR(XPATH_NUMBER_ERROR); |
|
} |
|
#ifdef __GNUC__ |
|
/* |
|
* tmp/temp is a workaround against a gcc compiler bug |
|
* http://veillard.com/gcc.bug |
|
*/ |
|
ret = 0; |
|
while ((CUR >= '0') && (CUR <= '9')) { |
|
ret = ret * 10; |
|
tmp = (CUR - '0'); |
|
ok = 1; |
|
NEXT; |
|
temp = (double) tmp; |
|
ret = ret + temp; |
|
} |
|
#else |
|
ret = 0; |
|
while ((CUR >= '0') && (CUR <= '9')) { |
|
ret = ret * 10 + (CUR - '0'); |
|
ok = 1; |
|
NEXT; |
|
} |
|
#endif |
|
if (CUR == '.') { |
|
int v, frac = 0, max; |
|
double fraction = 0; |
|
|
|
NEXT; |
|
if (((CUR < '0') || (CUR > '9')) && (!ok)) { |
|
XP_ERROR(XPATH_NUMBER_ERROR); |
|
} |
|
while (CUR == '0') { |
|
frac = frac + 1; |
|
NEXT; |
|
} |
|
max = frac + MAX_FRAC; |
|
while ((CUR >= '0') && (CUR <= '9') && (frac < max)) { |
|
v = (CUR - '0'); |
|
fraction = fraction * 10 + v; |
|
frac = frac + 1; |
|
NEXT; |
|
} |
|
fraction /= pow(10.0, frac); |
|
ret = ret + fraction; |
|
while ((CUR >= '0') && (CUR <= '9')) |
|
NEXT; |
|
} |
|
if ((CUR == 'e') || (CUR == 'E')) { |
|
NEXT; |
|
if (CUR == '-') { |
|
is_exponent_negative = 1; |
|
NEXT; |
|
} else if (CUR == '+') { |
|
NEXT; |
|
} |
|
while ((CUR >= '0') && (CUR <= '9')) { |
|
if (exponent < 1000000) |
|
exponent = exponent * 10 + (CUR - '0'); |
|
NEXT; |
|
} |
|
if (is_exponent_negative) |
|
exponent = -exponent; |
|
ret *= pow(10.0, (double) exponent); |
|
} |
|
PUSH_LONG_EXPR(XPATH_OP_VALUE, XPATH_NUMBER, 0, 0, |
|
xmlXPathCacheNewFloat(ctxt->context, ret), NULL); |
|
} |
|
|
|
/** |
|
* xmlXPathParseLiteral: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* Parse a Literal |
|
* |
|
* [29] Literal ::= '"' [^"]* '"' |
|
* | "'" [^']* "'" |
|
* |
|
* Returns the value found or NULL in case of error |
|
*/ |
|
static xmlChar * |
|
xmlXPathParseLiteral(xmlXPathParserContextPtr ctxt) { |
|
const xmlChar *q; |
|
xmlChar *ret = NULL; |
|
|
|
if (CUR == '"') { |
|
NEXT; |
|
q = CUR_PTR; |
|
while ((IS_CHAR_CH(CUR)) && (CUR != '"')) |
|
NEXT; |
|
if (!IS_CHAR_CH(CUR)) { |
|
XP_ERRORNULL(XPATH_UNFINISHED_LITERAL_ERROR); |
|
} else { |
|
ret = xmlStrndup(q, CUR_PTR - q); |
|
NEXT; |
|
} |
|
} else if (CUR == '\'') { |
|
NEXT; |
|
q = CUR_PTR; |
|
while ((IS_CHAR_CH(CUR)) && (CUR != '\'')) |
|
NEXT; |
|
if (!IS_CHAR_CH(CUR)) { |
|
XP_ERRORNULL(XPATH_UNFINISHED_LITERAL_ERROR); |
|
} else { |
|
ret = xmlStrndup(q, CUR_PTR - q); |
|
NEXT; |
|
} |
|
} else { |
|
XP_ERRORNULL(XPATH_START_LITERAL_ERROR); |
|
} |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathCompLiteral: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* Parse a Literal and push it on the stack. |
|
* |
|
* [29] Literal ::= '"' [^"]* '"' |
|
* | "'" [^']* "'" |
|
* |
|
* TODO: xmlXPathCompLiteral memory allocation could be improved. |
|
*/ |
|
static void |
|
xmlXPathCompLiteral(xmlXPathParserContextPtr ctxt) { |
|
const xmlChar *q; |
|
xmlChar *ret = NULL; |
|
|
|
if (CUR == '"') { |
|
NEXT; |
|
q = CUR_PTR; |
|
while ((IS_CHAR_CH(CUR)) && (CUR != '"')) |
|
NEXT; |
|
if (!IS_CHAR_CH(CUR)) { |
|
XP_ERROR(XPATH_UNFINISHED_LITERAL_ERROR); |
|
} else { |
|
ret = xmlStrndup(q, CUR_PTR - q); |
|
NEXT; |
|
} |
|
} else if (CUR == '\'') { |
|
NEXT; |
|
q = CUR_PTR; |
|
while ((IS_CHAR_CH(CUR)) && (CUR != '\'')) |
|
NEXT; |
|
if (!IS_CHAR_CH(CUR)) { |
|
XP_ERROR(XPATH_UNFINISHED_LITERAL_ERROR); |
|
} else { |
|
ret = xmlStrndup(q, CUR_PTR - q); |
|
NEXT; |
|
} |
|
} else { |
|
XP_ERROR(XPATH_START_LITERAL_ERROR); |
|
} |
|
if (ret == NULL) return; |
|
PUSH_LONG_EXPR(XPATH_OP_VALUE, XPATH_STRING, 0, 0, |
|
xmlXPathCacheNewString(ctxt->context, ret), NULL); |
|
xmlFree(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathCompVariableReference: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* Parse a VariableReference, evaluate it and push it on the stack. |
|
* |
|
* The variable bindings consist of a mapping from variable names |
|
* to variable values. The value of a variable is an object, which can be |
|
* of any of the types that are possible for the value of an expression, |
|
* and may also be of additional types not specified here. |
|
* |
|
* Early evaluation is possible since: |
|
* The variable bindings [...] used to evaluate a subexpression are |
|
* always the same as those used to evaluate the containing expression. |
|
* |
|
* [36] VariableReference ::= '$' QName |
|
*/ |
|
static void |
|
xmlXPathCompVariableReference(xmlXPathParserContextPtr ctxt) { |
|
xmlChar *name; |
|
xmlChar *prefix; |
|
|
|
SKIP_BLANKS; |
|
if (CUR != '$') { |
|
XP_ERROR(XPATH_VARIABLE_REF_ERROR); |
|
} |
|
NEXT; |
|
name = xmlXPathParseQName(ctxt, &prefix); |
|
if (name == NULL) { |
|
xmlFree(prefix); |
|
XP_ERROR(XPATH_VARIABLE_REF_ERROR); |
|
} |
|
ctxt->comp->last = -1; |
|
PUSH_LONG_EXPR(XPATH_OP_VARIABLE, 0, 0, 0, |
|
name, prefix); |
|
SKIP_BLANKS; |
|
if ((ctxt->context != NULL) && (ctxt->context->flags & XML_XPATH_NOVAR)) { |
|
XP_ERROR(XPATH_FORBID_VARIABLE_ERROR); |
|
} |
|
} |
|
|
|
/** |
|
* xmlXPathIsNodeType: |
|
* @name: a name string |
|
* |
|
* Is the name given a NodeType one. |
|
* |
|
* [38] NodeType ::= 'comment' |
|
* | 'text' |
|
* | 'processing-instruction' |
|
* | 'node' |
|
* |
|
* Returns 1 if true 0 otherwise |
|
*/ |
|
int |
|
xmlXPathIsNodeType(const xmlChar *name) { |
|
if (name == NULL) |
|
return(0); |
|
|
|
if (xmlStrEqual(name, BAD_CAST "node")) |
|
return(1); |
|
if (xmlStrEqual(name, BAD_CAST "text")) |
|
return(1); |
|
if (xmlStrEqual(name, BAD_CAST "comment")) |
|
return(1); |
|
if (xmlStrEqual(name, BAD_CAST "processing-instruction")) |
|
return(1); |
|
return(0); |
|
} |
|
|
|
/** |
|
* xmlXPathCompFunctionCall: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* [16] FunctionCall ::= FunctionName '(' ( Argument ( ',' Argument)*)? ')' |
|
* [17] Argument ::= Expr |
|
* |
|
* Compile a function call, the evaluation of all arguments are |
|
* pushed on the stack |
|
*/ |
|
static void |
|
xmlXPathCompFunctionCall(xmlXPathParserContextPtr ctxt) { |
|
xmlChar *name; |
|
xmlChar *prefix; |
|
int nbargs = 0; |
|
int sort = 1; |
|
|
|
name = xmlXPathParseQName(ctxt, &prefix); |
|
if (name == NULL) { |
|
xmlFree(prefix); |
|
XP_ERROR(XPATH_EXPR_ERROR); |
|
} |
|
SKIP_BLANKS; |
|
#ifdef DEBUG_EXPR |
|
if (prefix == NULL) |
|
xmlGenericError(xmlGenericErrorContext, "Calling function %s\n", |
|
name); |
|
else |
|
xmlGenericError(xmlGenericErrorContext, "Calling function %s:%s\n", |
|
prefix, name); |
|
#endif |
|
|
|
if (CUR != '(') { |
|
xmlFree(name); |
|
xmlFree(prefix); |
|
XP_ERROR(XPATH_EXPR_ERROR); |
|
} |
|
NEXT; |
|
SKIP_BLANKS; |
|
|
|
/* |
|
* Optimization for count(): we don't need the node-set to be sorted. |
|
*/ |
|
if ((prefix == NULL) && (name[0] == 'c') && |
|
xmlStrEqual(name, BAD_CAST "count")) |
|
{ |
|
sort = 0; |
|
} |
|
ctxt->comp->last = -1; |
|
if (CUR != ')') { |
|
while (CUR != 0) { |
|
int op1 = ctxt->comp->last; |
|
ctxt->comp->last = -1; |
|
xmlXPathCompileExpr(ctxt, sort); |
|
if (ctxt->error != XPATH_EXPRESSION_OK) { |
|
xmlFree(name); |
|
xmlFree(prefix); |
|
return; |
|
} |
|
PUSH_BINARY_EXPR(XPATH_OP_ARG, op1, ctxt->comp->last, 0, 0); |
|
nbargs++; |
|
if (CUR == ')') break; |
|
if (CUR != ',') { |
|
xmlFree(name); |
|
xmlFree(prefix); |
|
XP_ERROR(XPATH_EXPR_ERROR); |
|
} |
|
NEXT; |
|
SKIP_BLANKS; |
|
} |
|
} |
|
PUSH_LONG_EXPR(XPATH_OP_FUNCTION, nbargs, 0, 0, |
|
name, prefix); |
|
NEXT; |
|
SKIP_BLANKS; |
|
} |
|
|
|
/** |
|
* xmlXPathCompPrimaryExpr: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* [15] PrimaryExpr ::= VariableReference |
|
* | '(' Expr ')' |
|
* | Literal |
|
* | Number |
|
* | FunctionCall |
|
* |
|
* Compile a primary expression. |
|
*/ |
|
static void |
|
xmlXPathCompPrimaryExpr(xmlXPathParserContextPtr ctxt) { |
|
SKIP_BLANKS; |
|
if (CUR == '$') xmlXPathCompVariableReference(ctxt); |
|
else if (CUR == '(') { |
|
NEXT; |
|
SKIP_BLANKS; |
|
xmlXPathCompileExpr(ctxt, 1); |
|
CHECK_ERROR; |
|
if (CUR != ')') { |
|
XP_ERROR(XPATH_EXPR_ERROR); |
|
} |
|
NEXT; |
|
SKIP_BLANKS; |
|
} else if (IS_ASCII_DIGIT(CUR) || (CUR == '.' && IS_ASCII_DIGIT(NXT(1)))) { |
|
xmlXPathCompNumber(ctxt); |
|
} else if ((CUR == '\'') || (CUR == '"')) { |
|
xmlXPathCompLiteral(ctxt); |
|
} else { |
|
xmlXPathCompFunctionCall(ctxt); |
|
} |
|
SKIP_BLANKS; |
|
} |
|
|
|
/** |
|
* xmlXPathCompFilterExpr: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* [20] FilterExpr ::= PrimaryExpr |
|
* | FilterExpr Predicate |
|
* |
|
* Compile a filter expression. |
|
* Square brackets are used to filter expressions in the same way that |
|
* they are used in location paths. It is an error if the expression to |
|
* be filtered does not evaluate to a node-set. The context node list |
|
* used for evaluating the expression in square brackets is the node-set |
|
* to be filtered listed in document order. |
|
*/ |
|
|
|
static void |
|
xmlXPathCompFilterExpr(xmlXPathParserContextPtr ctxt) { |
|
xmlXPathCompPrimaryExpr(ctxt); |
|
CHECK_ERROR; |
|
SKIP_BLANKS; |
|
|
|
while (CUR == '[') { |
|
xmlXPathCompPredicate(ctxt, 1); |
|
SKIP_BLANKS; |
|
} |
|
|
|
|
|
} |
|
|
|
/** |
|
* xmlXPathScanName: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* Trickery: parse an XML name but without consuming the input flow |
|
* Needed to avoid insanity in the parser state. |
|
* |
|
* [4] NameChar ::= Letter | Digit | '.' | '-' | '_' | ':' | |
|
* CombiningChar | Extender |
|
* |
|
* [5] Name ::= (Letter | '_' | ':') (NameChar)* |
|
* |
|
* [6] Names ::= Name (S Name)* |
|
* |
|
* Returns the Name parsed or NULL |
|
*/ |
|
|
|
static xmlChar * |
|
xmlXPathScanName(xmlXPathParserContextPtr ctxt) { |
|
int len = 0, l; |
|
int c; |
|
const xmlChar *cur; |
|
xmlChar *ret; |
|
|
|
cur = ctxt->cur; |
|
|
|
c = CUR_CHAR(l); |
|
if ((c == ' ') || (c == '>') || (c == '/') || /* accelerators */ |
|
(!IS_LETTER(c) && (c != '_') && |
|
(c != ':'))) { |
|
return(NULL); |
|
} |
|
|
|
while ((c != ' ') && (c != '>') && (c != '/') && /* test bigname.xml */ |
|
((IS_LETTER(c)) || (IS_DIGIT(c)) || |
|
(c == '.') || (c == '-') || |
|
(c == '_') || (c == ':') || |
|
(IS_COMBINING(c)) || |
|
(IS_EXTENDER(c)))) { |
|
len += l; |
|
NEXTL(l); |
|
c = CUR_CHAR(l); |
|
} |
|
ret = xmlStrndup(cur, ctxt->cur - cur); |
|
ctxt->cur = cur; |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathCompPathExpr: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* [19] PathExpr ::= LocationPath |
|
* | FilterExpr |
|
* | FilterExpr '/' RelativeLocationPath |
|
* | FilterExpr '//' RelativeLocationPath |
|
* |
|
* Compile a path expression. |
|
* The / operator and // operators combine an arbitrary expression |
|
* and a relative location path. It is an error if the expression |
|
* does not evaluate to a node-set. |
|
* The / operator does composition in the same way as when / is |
|
* used in a location path. As in location paths, // is short for |
|
* /descendant-or-self::node()/. |
|
*/ |
|
|
|
static void |
|
xmlXPathCompPathExpr(xmlXPathParserContextPtr ctxt) { |
|
int lc = 1; /* Should we branch to LocationPath ? */ |
|
xmlChar *name = NULL; /* we may have to preparse a name to find out */ |
|
|
|
SKIP_BLANKS; |
|
if ((CUR == '$') || (CUR == '(') || |
|
(IS_ASCII_DIGIT(CUR)) || |
|
(CUR == '\'') || (CUR == '"') || |
|
(CUR == '.' && IS_ASCII_DIGIT(NXT(1)))) { |
|
lc = 0; |
|
} else if (CUR == '*') { |
|
/* relative or absolute location path */ |
|
lc = 1; |
|
} else if (CUR == '/') { |
|
/* relative or absolute location path */ |
|
lc = 1; |
|
} else if (CUR == '@') { |
|
/* relative abbreviated attribute location path */ |
|
lc = 1; |
|
} else if (CUR == '.') { |
|
/* relative abbreviated attribute location path */ |
|
lc = 1; |
|
} else { |
|
/* |
|
* Problem is finding if we have a name here whether it's: |
|
* - a nodetype |
|
* - a function call in which case it's followed by '(' |
|
* - an axis in which case it's followed by ':' |
|
* - a element name |
|
* We do an a priori analysis here rather than having to |
|
* maintain parsed token content through the recursive function |
|
* calls. This looks uglier but makes the code easier to |
|
* read/write/debug. |
|
*/ |
|
SKIP_BLANKS; |
|
name = xmlXPathScanName(ctxt); |
|
if ((name != NULL) && (xmlStrstr(name, (xmlChar *) "::") != NULL)) { |
|
#ifdef DEBUG_STEP |
|
xmlGenericError(xmlGenericErrorContext, |
|
"PathExpr: Axis\n"); |
|
#endif |
|
lc = 1; |
|
xmlFree(name); |
|
} else if (name != NULL) { |
|
int len =xmlStrlen(name); |
|
|
|
|
|
while (NXT(len) != 0) { |
|
if (NXT(len) == '/') { |
|
/* element name */ |
|
#ifdef DEBUG_STEP |
|
xmlGenericError(xmlGenericErrorContext, |
|
"PathExpr: AbbrRelLocation\n"); |
|
#endif |
|
lc = 1; |
|
break; |
|
} else if (IS_BLANK_CH(NXT(len))) { |
|
/* ignore blanks */ |
|
; |
|
} else if (NXT(len) == ':') { |
|
#ifdef DEBUG_STEP |
|
xmlGenericError(xmlGenericErrorContext, |
|
"PathExpr: AbbrRelLocation\n"); |
|
#endif |
|
lc = 1; |
|
break; |
|
} else if ((NXT(len) == '(')) { |
|
/* Node Type or Function */ |
|
if (xmlXPathIsNodeType(name)) { |
|
#ifdef DEBUG_STEP |
|
xmlGenericError(xmlGenericErrorContext, |
|
"PathExpr: Type search\n"); |
|
#endif |
|
lc = 1; |
|
#ifdef LIBXML_XPTR_ENABLED |
|
} else if (ctxt->xptr && |
|
xmlStrEqual(name, BAD_CAST "range-to")) { |
|
lc = 1; |
|
#endif |
|
} else { |
|
#ifdef DEBUG_STEP |
|
xmlGenericError(xmlGenericErrorContext, |
|
"PathExpr: function call\n"); |
|
#endif |
|
lc = 0; |
|
} |
|
break; |
|
} else if ((NXT(len) == '[')) { |
|
/* element name */ |
|
#ifdef DEBUG_STEP |
|
xmlGenericError(xmlGenericErrorContext, |
|
"PathExpr: AbbrRelLocation\n"); |
|
#endif |
|
lc = 1; |
|
break; |
|
} else if ((NXT(len) == '<') || (NXT(len) == '>') || |
|
(NXT(len) == '=')) { |
|
lc = 1; |
|
break; |
|
} else { |
|
lc = 1; |
|
break; |
|
} |
|
len++; |
|
} |
|
if (NXT(len) == 0) { |
|
#ifdef DEBUG_STEP |
|
xmlGenericError(xmlGenericErrorContext, |
|
"PathExpr: AbbrRelLocation\n"); |
|
#endif |
|
/* element name */ |
|
lc = 1; |
|
} |
|
xmlFree(name); |
|
} else { |
|
/* make sure all cases are covered explicitly */ |
|
XP_ERROR(XPATH_EXPR_ERROR); |
|
} |
|
} |
|
|
|
if (lc) { |
|
if (CUR == '/') { |
|
PUSH_LEAVE_EXPR(XPATH_OP_ROOT, 0, 0); |
|
} else { |
|
PUSH_LEAVE_EXPR(XPATH_OP_NODE, 0, 0); |
|
} |
|
xmlXPathCompLocationPath(ctxt); |
|
} else { |
|
xmlXPathCompFilterExpr(ctxt); |
|
CHECK_ERROR; |
|
if ((CUR == '/') && (NXT(1) == '/')) { |
|
SKIP(2); |
|
SKIP_BLANKS; |
|
|
|
PUSH_LONG_EXPR(XPATH_OP_COLLECT, AXIS_DESCENDANT_OR_SELF, |
|
NODE_TEST_TYPE, NODE_TYPE_NODE, NULL, NULL); |
|
PUSH_UNARY_EXPR(XPATH_OP_RESET, ctxt->comp->last, 1, 0); |
|
|
|
xmlXPathCompRelativeLocationPath(ctxt); |
|
} else if (CUR == '/') { |
|
xmlXPathCompRelativeLocationPath(ctxt); |
|
} |
|
} |
|
SKIP_BLANKS; |
|
} |
|
|
|
/** |
|
* xmlXPathCompUnionExpr: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* [18] UnionExpr ::= PathExpr |
|
* | UnionExpr '|' PathExpr |
|
* |
|
* Compile an union expression. |
|
*/ |
|
|
|
static void |
|
xmlXPathCompUnionExpr(xmlXPathParserContextPtr ctxt) { |
|
xmlXPathCompPathExpr(ctxt); |
|
CHECK_ERROR; |
|
SKIP_BLANKS; |
|
while (CUR == '|') { |
|
int op1 = ctxt->comp->last; |
|
PUSH_LEAVE_EXPR(XPATH_OP_NODE, 0, 0); |
|
|
|
NEXT; |
|
SKIP_BLANKS; |
|
xmlXPathCompPathExpr(ctxt); |
|
|
|
PUSH_BINARY_EXPR(XPATH_OP_UNION, op1, ctxt->comp->last, 0, 0); |
|
|
|
SKIP_BLANKS; |
|
} |
|
} |
|
|
|
/** |
|
* xmlXPathCompUnaryExpr: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* [27] UnaryExpr ::= UnionExpr |
|
* | '-' UnaryExpr |
|
* |
|
* Compile an unary expression. |
|
*/ |
|
|
|
static void |
|
xmlXPathCompUnaryExpr(xmlXPathParserContextPtr ctxt) { |
|
int minus = 0; |
|
int found = 0; |
|
|
|
SKIP_BLANKS; |
|
while (CUR == '-') { |
|
minus = 1 - minus; |
|
found = 1; |
|
NEXT; |
|
SKIP_BLANKS; |
|
} |
|
|
|
xmlXPathCompUnionExpr(ctxt); |
|
CHECK_ERROR; |
|
if (found) { |
|
if (minus) |
|
PUSH_UNARY_EXPR(XPATH_OP_PLUS, ctxt->comp->last, 2, 0); |
|
else |
|
PUSH_UNARY_EXPR(XPATH_OP_PLUS, ctxt->comp->last, 3, 0); |
|
} |
|
} |
|
|
|
/** |
|
* xmlXPathCompMultiplicativeExpr: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* [26] MultiplicativeExpr ::= UnaryExpr |
|
* | MultiplicativeExpr MultiplyOperator UnaryExpr |
|
* | MultiplicativeExpr 'div' UnaryExpr |
|
* | MultiplicativeExpr 'mod' UnaryExpr |
|
* [34] MultiplyOperator ::= '*' |
|
* |
|
* Compile an Additive expression. |
|
*/ |
|
|
|
static void |
|
xmlXPathCompMultiplicativeExpr(xmlXPathParserContextPtr ctxt) { |
|
xmlXPathCompUnaryExpr(ctxt); |
|
CHECK_ERROR; |
|
SKIP_BLANKS; |
|
while ((CUR == '*') || |
|
((CUR == 'd') && (NXT(1) == 'i') && (NXT(2) == 'v')) || |
|
((CUR == 'm') && (NXT(1) == 'o') && (NXT(2) == 'd'))) { |
|
int op = -1; |
|
int op1 = ctxt->comp->last; |
|
|
|
if (CUR == '*') { |
|
op = 0; |
|
NEXT; |
|
} else if (CUR == 'd') { |
|
op = 1; |
|
SKIP(3); |
|
} else if (CUR == 'm') { |
|
op = 2; |
|
SKIP(3); |
|
} |
|
SKIP_BLANKS; |
|
xmlXPathCompUnaryExpr(ctxt); |
|
CHECK_ERROR; |
|
PUSH_BINARY_EXPR(XPATH_OP_MULT, op1, ctxt->comp->last, op, 0); |
|
SKIP_BLANKS; |
|
} |
|
} |
|
|
|
/** |
|
* xmlXPathCompAdditiveExpr: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* [25] AdditiveExpr ::= MultiplicativeExpr |
|
* | AdditiveExpr '+' MultiplicativeExpr |
|
* | AdditiveExpr '-' MultiplicativeExpr |
|
* |
|
* Compile an Additive expression. |
|
*/ |
|
|
|
static void |
|
xmlXPathCompAdditiveExpr(xmlXPathParserContextPtr ctxt) { |
|
|
|
xmlXPathCompMultiplicativeExpr(ctxt); |
|
CHECK_ERROR; |
|
SKIP_BLANKS; |
|
while ((CUR == '+') || (CUR == '-')) { |
|
int plus; |
|
int op1 = ctxt->comp->last; |
|
|
|
if (CUR == '+') plus = 1; |
|
else plus = 0; |
|
NEXT; |
|
SKIP_BLANKS; |
|
xmlXPathCompMultiplicativeExpr(ctxt); |
|
CHECK_ERROR; |
|
PUSH_BINARY_EXPR(XPATH_OP_PLUS, op1, ctxt->comp->last, plus, 0); |
|
SKIP_BLANKS; |
|
} |
|
} |
|
|
|
/** |
|
* xmlXPathCompRelationalExpr: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* [24] RelationalExpr ::= AdditiveExpr |
|
* | RelationalExpr '<' AdditiveExpr |
|
* | RelationalExpr '>' AdditiveExpr |
|
* | RelationalExpr '<=' AdditiveExpr |
|
* | RelationalExpr '>=' AdditiveExpr |
|
* |
|
* A <= B > C is allowed ? Answer from James, yes with |
|
* (AdditiveExpr <= AdditiveExpr) > AdditiveExpr |
|
* which is basically what got implemented. |
|
* |
|
* Compile a Relational expression, then push the result |
|
* on the stack |
|
*/ |
|
|
|
static void |
|
xmlXPathCompRelationalExpr(xmlXPathParserContextPtr ctxt) { |
|
xmlXPathCompAdditiveExpr(ctxt); |
|
CHECK_ERROR; |
|
SKIP_BLANKS; |
|
while ((CUR == '<') || |
|
(CUR == '>') || |
|
((CUR == '<') && (NXT(1) == '=')) || |
|
((CUR == '>') && (NXT(1) == '='))) { |
|
int inf, strict; |
|
int op1 = ctxt->comp->last; |
|
|
|
if (CUR == '<') inf = 1; |
|
else inf = 0; |
|
if (NXT(1) == '=') strict = 0; |
|
else strict = 1; |
|
NEXT; |
|
if (!strict) NEXT; |
|
SKIP_BLANKS; |
|
xmlXPathCompAdditiveExpr(ctxt); |
|
CHECK_ERROR; |
|
PUSH_BINARY_EXPR(XPATH_OP_CMP, op1, ctxt->comp->last, inf, strict); |
|
SKIP_BLANKS; |
|
} |
|
} |
|
|
|
/** |
|
* xmlXPathCompEqualityExpr: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* [23] EqualityExpr ::= RelationalExpr |
|
* | EqualityExpr '=' RelationalExpr |
|
* | EqualityExpr '!=' RelationalExpr |
|
* |
|
* A != B != C is allowed ? Answer from James, yes with |
|
* (RelationalExpr = RelationalExpr) = RelationalExpr |
|
* (RelationalExpr != RelationalExpr) != RelationalExpr |
|
* which is basically what got implemented. |
|
* |
|
* Compile an Equality expression. |
|
* |
|
*/ |
|
static void |
|
xmlXPathCompEqualityExpr(xmlXPathParserContextPtr ctxt) { |
|
xmlXPathCompRelationalExpr(ctxt); |
|
CHECK_ERROR; |
|
SKIP_BLANKS; |
|
while ((CUR == '=') || ((CUR == '!') && (NXT(1) == '='))) { |
|
int eq; |
|
int op1 = ctxt->comp->last; |
|
|
|
if (CUR == '=') eq = 1; |
|
else eq = 0; |
|
NEXT; |
|
if (!eq) NEXT; |
|
SKIP_BLANKS; |
|
xmlXPathCompRelationalExpr(ctxt); |
|
CHECK_ERROR; |
|
PUSH_BINARY_EXPR(XPATH_OP_EQUAL, op1, ctxt->comp->last, eq, 0); |
|
SKIP_BLANKS; |
|
} |
|
} |
|
|
|
/** |
|
* xmlXPathCompAndExpr: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* [22] AndExpr ::= EqualityExpr |
|
* | AndExpr 'and' EqualityExpr |
|
* |
|
* Compile an AND expression. |
|
* |
|
*/ |
|
static void |
|
xmlXPathCompAndExpr(xmlXPathParserContextPtr ctxt) { |
|
xmlXPathCompEqualityExpr(ctxt); |
|
CHECK_ERROR; |
|
SKIP_BLANKS; |
|
while ((CUR == 'a') && (NXT(1) == 'n') && (NXT(2) == 'd')) { |
|
int op1 = ctxt->comp->last; |
|
SKIP(3); |
|
SKIP_BLANKS; |
|
xmlXPathCompEqualityExpr(ctxt); |
|
CHECK_ERROR; |
|
PUSH_BINARY_EXPR(XPATH_OP_AND, op1, ctxt->comp->last, 0, 0); |
|
SKIP_BLANKS; |
|
} |
|
} |
|
|
|
/** |
|
* xmlXPathCompileExpr: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* [14] Expr ::= OrExpr |
|
* [21] OrExpr ::= AndExpr |
|
* | OrExpr 'or' AndExpr |
|
* |
|
* Parse and compile an expression |
|
*/ |
|
static void |
|
xmlXPathCompileExpr(xmlXPathParserContextPtr ctxt, int sort) { |
|
xmlXPathCompAndExpr(ctxt); |
|
CHECK_ERROR; |
|
SKIP_BLANKS; |
|
while ((CUR == 'o') && (NXT(1) == 'r')) { |
|
int op1 = ctxt->comp->last; |
|
SKIP(2); |
|
SKIP_BLANKS; |
|
xmlXPathCompAndExpr(ctxt); |
|
CHECK_ERROR; |
|
PUSH_BINARY_EXPR(XPATH_OP_OR, op1, ctxt->comp->last, 0, 0); |
|
SKIP_BLANKS; |
|
} |
|
if ((sort) && (ctxt->comp->steps[ctxt->comp->last].op != XPATH_OP_VALUE)) { |
|
/* more ops could be optimized too */ |
|
/* |
|
* This is the main place to eliminate sorting for |
|
* operations which don't require a sorted node-set. |
|
* E.g. count(). |
|
*/ |
|
PUSH_UNARY_EXPR(XPATH_OP_SORT, ctxt->comp->last , 0, 0); |
|
} |
|
} |
|
|
|
/** |
|
* xmlXPathCompPredicate: |
|
* @ctxt: the XPath Parser context |
|
* @filter: act as a filter |
|
* |
|
* [8] Predicate ::= '[' PredicateExpr ']' |
|
* [9] PredicateExpr ::= Expr |
|
* |
|
* Compile a predicate expression |
|
*/ |
|
static void |
|
xmlXPathCompPredicate(xmlXPathParserContextPtr ctxt, int filter) { |
|
int op1 = ctxt->comp->last; |
|
|
|
SKIP_BLANKS; |
|
if (CUR != '[') { |
|
XP_ERROR(XPATH_INVALID_PREDICATE_ERROR); |
|
} |
|
NEXT; |
|
SKIP_BLANKS; |
|
|
|
ctxt->comp->last = -1; |
|
/* |
|
* This call to xmlXPathCompileExpr() will deactivate sorting |
|
* of the predicate result. |
|
* TODO: Sorting is still activated for filters, since I'm not |
|
* sure if needed. Normally sorting should not be needed, since |
|
* a filter can only diminish the number of items in a sequence, |
|
* but won't change its order; so if the initial sequence is sorted, |
|
* subsequent sorting is not needed. |
|
*/ |
|
if (! filter) |
|
xmlXPathCompileExpr(ctxt, 0); |
|
else |
|
xmlXPathCompileExpr(ctxt, 1); |
|
CHECK_ERROR; |
|
|
|
if (CUR != ']') { |
|
XP_ERROR(XPATH_INVALID_PREDICATE_ERROR); |
|
} |
|
|
|
if (filter) |
|
PUSH_BINARY_EXPR(XPATH_OP_FILTER, op1, ctxt->comp->last, 0, 0); |
|
else |
|
PUSH_BINARY_EXPR(XPATH_OP_PREDICATE, op1, ctxt->comp->last, 0, 0); |
|
|
|
NEXT; |
|
SKIP_BLANKS; |
|
} |
|
|
|
/** |
|
* xmlXPathCompNodeTest: |
|
* @ctxt: the XPath Parser context |
|
* @test: pointer to a xmlXPathTestVal |
|
* @type: pointer to a xmlXPathTypeVal |
|
* @prefix: placeholder for a possible name prefix |
|
* |
|
* [7] NodeTest ::= NameTest |
|
* | NodeType '(' ')' |
|
* | 'processing-instruction' '(' Literal ')' |
|
* |
|
* [37] NameTest ::= '*' |
|
* | NCName ':' '*' |
|
* | QName |
|
* [38] NodeType ::= 'comment' |
|
* | 'text' |
|
* | 'processing-instruction' |
|
* | 'node' |
|
* |
|
* Returns the name found and updates @test, @type and @prefix appropriately |
|
*/ |
|
static xmlChar * |
|
xmlXPathCompNodeTest(xmlXPathParserContextPtr ctxt, xmlXPathTestVal *test, |
|
xmlXPathTypeVal *type, const xmlChar **prefix, |
|
xmlChar *name) { |
|
int blanks; |
|
|
|
if ((test == NULL) || (type == NULL) || (prefix == NULL)) { |
|
STRANGE; |
|
return(NULL); |
|
} |
|
*type = (xmlXPathTypeVal) 0; |
|
*test = (xmlXPathTestVal) 0; |
|
*prefix = NULL; |
|
SKIP_BLANKS; |
|
|
|
if ((name == NULL) && (CUR == '*')) { |
|
/* |
|
* All elements |
|
*/ |
|
NEXT; |
|
*test = NODE_TEST_ALL; |
|
return(NULL); |
|
} |
|
|
|
if (name == NULL) |
|
name = xmlXPathParseNCName(ctxt); |
|
if (name == NULL) { |
|
XP_ERRORNULL(XPATH_EXPR_ERROR); |
|
} |
|
|
|
blanks = IS_BLANK_CH(CUR); |
|
SKIP_BLANKS; |
|
if (CUR == '(') { |
|
NEXT; |
|
/* |
|
* NodeType or PI search |
|
*/ |
|
if (xmlStrEqual(name, BAD_CAST "comment")) |
|
*type = NODE_TYPE_COMMENT; |
|
else if (xmlStrEqual(name, BAD_CAST "node")) |
|
*type = NODE_TYPE_NODE; |
|
else if (xmlStrEqual(name, BAD_CAST "processing-instruction")) |
|
*type = NODE_TYPE_PI; |
|
else if (xmlStrEqual(name, BAD_CAST "text")) |
|
*type = NODE_TYPE_TEXT; |
|
else { |
|
if (name != NULL) |
|
xmlFree(name); |
|
XP_ERRORNULL(XPATH_EXPR_ERROR); |
|
} |
|
|
|
*test = NODE_TEST_TYPE; |
|
|
|
SKIP_BLANKS; |
|
if (*type == NODE_TYPE_PI) { |
|
/* |
|
* Specific case: search a PI by name. |
|
*/ |
|
if (name != NULL) |
|
xmlFree(name); |
|
name = NULL; |
|
if (CUR != ')') { |
|
name = xmlXPathParseLiteral(ctxt); |
|
CHECK_ERROR NULL; |
|
*test = NODE_TEST_PI; |
|
SKIP_BLANKS; |
|
} |
|
} |
|
if (CUR != ')') { |
|
if (name != NULL) |
|
xmlFree(name); |
|
XP_ERRORNULL(XPATH_UNCLOSED_ERROR); |
|
} |
|
NEXT; |
|
return(name); |
|
} |
|
*test = NODE_TEST_NAME; |
|
if ((!blanks) && (CUR == ':')) { |
|
NEXT; |
|
|
|
/* |
|
* Since currently the parser context don't have a |
|
* namespace list associated: |
|
* The namespace name for this prefix can be computed |
|
* only at evaluation time. The compilation is done |
|
* outside of any context. |
|
*/ |
|
#if 0 |
|
*prefix = xmlXPathNsLookup(ctxt->context, name); |
|
if (name != NULL) |
|
xmlFree(name); |
|
if (*prefix == NULL) { |
|
XP_ERROR0(XPATH_UNDEF_PREFIX_ERROR); |
|
} |
|
#else |
|
*prefix = name; |
|
#endif |
|
|
|
if (CUR == '*') { |
|
/* |
|
* All elements |
|
*/ |
|
NEXT; |
|
*test = NODE_TEST_ALL; |
|
return(NULL); |
|
} |
|
|
|
name = xmlXPathParseNCName(ctxt); |
|
if (name == NULL) { |
|
XP_ERRORNULL(XPATH_EXPR_ERROR); |
|
} |
|
} |
|
return(name); |
|
} |
|
|
|
/** |
|
* xmlXPathIsAxisName: |
|
* @name: a preparsed name token |
|
* |
|
* [6] AxisName ::= 'ancestor' |
|
* | 'ancestor-or-self' |
|
* | 'attribute' |
|
* | 'child' |
|
* | 'descendant' |
|
* | 'descendant-or-self' |
|
* | 'following' |
|
* | 'following-sibling' |
|
* | 'namespace' |
|
* | 'parent' |
|
* | 'preceding' |
|
* | 'preceding-sibling' |
|
* | 'self' |
|
* |
|
* Returns the axis or 0 |
|
*/ |
|
static xmlXPathAxisVal |
|
xmlXPathIsAxisName(const xmlChar *name) { |
|
xmlXPathAxisVal ret = (xmlXPathAxisVal) 0; |
|
switch (name[0]) { |
|
case 'a': |
|
if (xmlStrEqual(name, BAD_CAST "ancestor")) |
|
ret = AXIS_ANCESTOR; |
|
if (xmlStrEqual(name, BAD_CAST "ancestor-or-self")) |
|
ret = AXIS_ANCESTOR_OR_SELF; |
|
if (xmlStrEqual(name, BAD_CAST "attribute")) |
|
ret = AXIS_ATTRIBUTE; |
|
break; |
|
case 'c': |
|
if (xmlStrEqual(name, BAD_CAST "child")) |
|
ret = AXIS_CHILD; |
|
break; |
|
case 'd': |
|
if (xmlStrEqual(name, BAD_CAST "descendant")) |
|
ret = AXIS_DESCENDANT; |
|
if (xmlStrEqual(name, BAD_CAST "descendant-or-self")) |
|
ret = AXIS_DESCENDANT_OR_SELF; |
|
break; |
|
case 'f': |
|
if (xmlStrEqual(name, BAD_CAST "following")) |
|
ret = AXIS_FOLLOWING; |
|
if (xmlStrEqual(name, BAD_CAST "following-sibling")) |
|
ret = AXIS_FOLLOWING_SIBLING; |
|
break; |
|
case 'n': |
|
if (xmlStrEqual(name, BAD_CAST "namespace")) |
|
ret = AXIS_NAMESPACE; |
|
break; |
|
case 'p': |
|
if (xmlStrEqual(name, BAD_CAST "parent")) |
|
ret = AXIS_PARENT; |
|
if (xmlStrEqual(name, BAD_CAST "preceding")) |
|
ret = AXIS_PRECEDING; |
|
if (xmlStrEqual(name, BAD_CAST "preceding-sibling")) |
|
ret = AXIS_PRECEDING_SIBLING; |
|
break; |
|
case 's': |
|
if (xmlStrEqual(name, BAD_CAST "self")) |
|
ret = AXIS_SELF; |
|
break; |
|
} |
|
return(ret); |
|
} |
|
|
|
/** |
|
* xmlXPathCompStep: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* [4] Step ::= AxisSpecifier NodeTest Predicate* |
|
* | AbbreviatedStep |
|
* |
|
* [12] AbbreviatedStep ::= '.' | '..' |
|
* |
|
* [5] AxisSpecifier ::= AxisName '::' |
|
* | AbbreviatedAxisSpecifier |
|
* |
|
* [13] AbbreviatedAxisSpecifier ::= '@'? |
|
* |
|
* Modified for XPtr range support as: |
|
* |
|
* [4xptr] Step ::= AxisSpecifier NodeTest Predicate* |
|
* | AbbreviatedStep |
|
* | 'range-to' '(' Expr ')' Predicate* |
|
* |
|
* Compile one step in a Location Path |
|
* A location step of . is short for self::node(). This is |
|
* particularly useful in conjunction with //. For example, the |
|
* location path .//para is short for |
|
* self::node()/descendant-or-self::node()/child::para |
|
* and so will select all para descendant elements of the context |
|
* node. |
|
* Similarly, a location step of .. is short for parent::node(). |
|
* For example, ../title is short for parent::node()/child::title |
|
* and so will select the title children of the parent of the context |
|
* node. |
|
*/ |
|
static void |
|
xmlXPathCompStep(xmlXPathParserContextPtr ctxt) { |
|
#ifdef LIBXML_XPTR_ENABLED |
|
int rangeto = 0; |
|
int op2 = -1; |
|
#endif |
|
|
|
SKIP_BLANKS; |
|
if ((CUR == '.') && (NXT(1) == '.')) { |
|
SKIP(2); |
|
SKIP_BLANKS; |
|
PUSH_LONG_EXPR(XPATH_OP_COLLECT, AXIS_PARENT, |
|
NODE_TEST_TYPE, NODE_TYPE_NODE, NULL, NULL); |
|
} else if (CUR == '.') { |
|
NEXT; |
|
SKIP_BLANKS; |
|
} else { |
|
xmlChar *name = NULL; |
|
const xmlChar *prefix = NULL; |
|
xmlXPathTestVal test = (xmlXPathTestVal) 0; |
|
xmlXPathAxisVal axis = (xmlXPathAxisVal) 0; |
|
xmlXPathTypeVal type = (xmlXPathTypeVal) 0; |
|
int op1; |
|
|
|
/* |
|
* The modification needed for XPointer change to the production |
|
*/ |
|
#ifdef LIBXML_XPTR_ENABLED |
|
if (ctxt->xptr) { |
|
name = xmlXPathParseNCName(ctxt); |
|
if ((name != NULL) && (xmlStrEqual(name, BAD_CAST "range-to"))) { |
|
op2 = ctxt->comp->last; |
|
xmlFree(name); |
|
SKIP_BLANKS; |
|
if (CUR != '(') { |
|
XP_ERROR(XPATH_EXPR_ERROR); |
|
} |
|
NEXT; |
|
SKIP_BLANKS; |
|
|
|
xmlXPathCompileExpr(ctxt, 1); |
|
/* PUSH_BINARY_EXPR(XPATH_OP_RANGETO, op2, ctxt->comp->last, 0, 0); */ |
|
CHECK_ERROR; |
|
|
|
SKIP_BLANKS; |
|
if (CUR != ')') { |
|
XP_ERROR(XPATH_EXPR_ERROR); |
|
} |
|
NEXT; |
|
rangeto = 1; |
|
goto eval_predicates; |
|
} |
|
} |
|
#endif |
|
if (CUR == '*') { |
|
axis = AXIS_CHILD; |
|
} else { |
|
if (name == NULL) |
|
name = xmlXPathParseNCName(ctxt); |
|
if (name != NULL) { |
|
axis = xmlXPathIsAxisName(name); |
|
if (axis != 0) { |
|
SKIP_BLANKS; |
|
if ((CUR == ':') && (NXT(1) == ':')) { |
|
SKIP(2); |
|
xmlFree(name); |
|
name = NULL; |
|
} else { |
|
/* an element name can conflict with an axis one :-\ */ |
|
axis = AXIS_CHILD; |
|
} |
|
} else { |
|
axis = AXIS_CHILD; |
|
} |
|
} else if (CUR == '@') { |
|
NEXT; |
|
axis = AXIS_ATTRIBUTE; |
|
} else { |
|
axis = AXIS_CHILD; |
|
} |
|
} |
|
|
|
if (ctxt->error != XPATH_EXPRESSION_OK) { |
|
xmlFree(name); |
|
return; |
|
} |
|
|
|
name = xmlXPathCompNodeTest(ctxt, &test, &type, &prefix, name); |
|
if (test == 0) |
|
return; |
|
|
|
if ((prefix != NULL) && (ctxt->context != NULL) && |
|
(ctxt->context->flags & XML_XPATH_CHECKNS)) { |
|
if (xmlXPathNsLookup(ctxt->context, prefix) == NULL) { |
|
xmlXPathErr(ctxt, XPATH_UNDEF_PREFIX_ERROR); |
|
} |
|
} |
|
#ifdef DEBUG_STEP |
|
xmlGenericError(xmlGenericErrorContext, |
|
"Basis : computing new set\n"); |
|
#endif |
|
|
|
#ifdef DEBUG_STEP |
|
xmlGenericError(xmlGenericErrorContext, "Basis : "); |
|
if (ctxt->value == NULL) |
|
xmlGenericError(xmlGenericErrorContext, "no value\n"); |
|
else if (ctxt->value->nodesetval == NULL) |
|
xmlGenericError(xmlGenericErrorContext, "Empty\n"); |
|
else |
|
xmlGenericErrorContextNodeSet(stdout, ctxt->value->nodesetval); |
|
#endif |
|
|
|
#ifdef LIBXML_XPTR_ENABLED |
|
eval_predicates: |
|
#endif |
|
op1 = ctxt->comp->last; |
|
ctxt->comp->last = -1; |
|
|
|
SKIP_BLANKS; |
|
while (CUR == '[') { |
|
xmlXPathCompPredicate(ctxt, 0); |
|
} |
|
|
|
#ifdef LIBXML_XPTR_ENABLED |
|
if (rangeto) { |
|
PUSH_BINARY_EXPR(XPATH_OP_RANGETO, op2, op1, 0, 0); |
|
} else |
|
#endif |
|
PUSH_FULL_EXPR(XPATH_OP_COLLECT, op1, ctxt->comp->last, axis, |
|
test, type, (void *)prefix, (void *)name); |
|
|
|
} |
|
#ifdef DEBUG_STEP |
|
xmlGenericError(xmlGenericErrorContext, "Step : "); |
|
if (ctxt->value == NULL) |
|
xmlGenericError(xmlGenericErrorContext, "no value\n"); |
|
else if (ctxt->value->nodesetval == NULL) |
|
xmlGenericError(xmlGenericErrorContext, "Empty\n"); |
|
else |
|
xmlGenericErrorContextNodeSet(xmlGenericErrorContext, |
|
ctxt->value->nodesetval); |
|
#endif |
|
} |
|
|
|
/** |
|
* xmlXPathCompRelativeLocationPath: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* [3] RelativeLocationPath ::= Step |
|
* | RelativeLocationPath '/' Step |
|
* | AbbreviatedRelativeLocationPath |
|
* [11] AbbreviatedRelativeLocationPath ::= RelativeLocationPath '//' Step |
|
* |
|
* Compile a relative location path. |
|
*/ |
|
static void |
|
xmlXPathCompRelativeLocationPath |
|
(xmlXPathParserContextPtr ctxt) { |
|
SKIP_BLANKS; |
|
if ((CUR == '/') && (NXT(1) == '/')) { |
|
SKIP(2); |
|
SKIP_BLANKS; |
|
PUSH_LONG_EXPR(XPATH_OP_COLLECT, AXIS_DESCENDANT_OR_SELF, |
|
NODE_TEST_TYPE, NODE_TYPE_NODE, NULL, NULL); |
|
} else if (CUR == '/') { |
|
NEXT; |
|
SKIP_BLANKS; |
|
} |
|
xmlXPathCompStep(ctxt); |
|
CHECK_ERROR; |
|
SKIP_BLANKS; |
|
while (CUR == '/') { |
|
if ((CUR == '/') && (NXT(1) == '/')) { |
|
SKIP(2); |
|
SKIP_BLANKS; |
|
PUSH_LONG_EXPR(XPATH_OP_COLLECT, AXIS_DESCENDANT_OR_SELF, |
|
NODE_TEST_TYPE, NODE_TYPE_NODE, NULL, NULL); |
|
xmlXPathCompStep(ctxt); |
|
} else if (CUR == '/') { |
|
NEXT; |
|
SKIP_BLANKS; |
|
xmlXPathCompStep(ctxt); |
|
} |
|
SKIP_BLANKS; |
|
} |
|
} |
|
|
|
/** |
|
* xmlXPathCompLocationPath: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* [1] LocationPath ::= RelativeLocationPath |
|
* | AbsoluteLocationPath |
|
* [2] AbsoluteLocationPath ::= '/' RelativeLocationPath? |
|
* | AbbreviatedAbsoluteLocationPath |
|
* [10] AbbreviatedAbsoluteLocationPath ::= |
|
* '//' RelativeLocationPath |
|
* |
|
* Compile a location path |
|
* |
|
* // is short for /descendant-or-self::node()/. For example, |
|
* //para is short for /descendant-or-self::node()/child::para and |
|
* so will select any para element in the document (even a para element |
|
* that is a document element will be selected by //para since the |
|
* document element node is a child of the root node); div//para is |
|
* short for div/descendant-or-self::node()/child::para and so will |
|
* select all para descendants of div children. |
|
*/ |
|
static void |
|
xmlXPathCompLocationPath(xmlXPathParserContextPtr ctxt) { |
|
SKIP_BLANKS; |
|
if (CUR != '/') { |
|
xmlXPathCompRelativeLocationPath(ctxt); |
|
} else { |
|
while (CUR == '/') { |
|
if ((CUR == '/') && (NXT(1) == '/')) { |
|
SKIP(2); |
|
SKIP_BLANKS; |
|
PUSH_LONG_EXPR(XPATH_OP_COLLECT, AXIS_DESCENDANT_OR_SELF, |
|
NODE_TEST_TYPE, NODE_TYPE_NODE, NULL, NULL); |
|
xmlXPathCompRelativeLocationPath(ctxt); |
|
} else if (CUR == '/') { |
|
NEXT; |
|
SKIP_BLANKS; |
|
if ((CUR != 0 ) && |
|
((IS_ASCII_LETTER(CUR)) || (CUR == '_') || (CUR == '.') || |
|
(CUR == '@') || (CUR == '*'))) |
|
xmlXPathCompRelativeLocationPath(ctxt); |
|
} |
|
CHECK_ERROR; |
|
} |
|
} |
|
} |
|
|
|
/************************************************************************ |
|
* * |
|
* XPath precompiled expression evaluation * |
|
* * |
|
************************************************************************/ |
|
|
|
static int |
|
xmlXPathCompOpEval(xmlXPathParserContextPtr ctxt, xmlXPathStepOpPtr op); |
|
|
|
#ifdef DEBUG_STEP |
|
static void |
|
xmlXPathDebugDumpStepAxis(xmlXPathStepOpPtr op, |
|
int nbNodes) |
|
{ |
|
xmlGenericError(xmlGenericErrorContext, "new step : "); |
|
switch (op->value) { |
|
case AXIS_ANCESTOR: |
|
xmlGenericError(xmlGenericErrorContext, "axis 'ancestors' "); |
|
break; |
|
case AXIS_ANCESTOR_OR_SELF: |
|
xmlGenericError(xmlGenericErrorContext, |
|
"axis 'ancestors-or-self' "); |
|
break; |
|
case AXIS_ATTRIBUTE: |
|
xmlGenericError(xmlGenericErrorContext, "axis 'attributes' "); |
|
break; |
|
case AXIS_CHILD: |
|
xmlGenericError(xmlGenericErrorContext, "axis 'child' "); |
|
break; |
|
case AXIS_DESCENDANT: |
|
xmlGenericError(xmlGenericErrorContext, "axis 'descendant' "); |
|
break; |
|
case AXIS_DESCENDANT_OR_SELF: |
|
xmlGenericError(xmlGenericErrorContext, |
|
"axis 'descendant-or-self' "); |
|
break; |
|
case AXIS_FOLLOWING: |
|
xmlGenericError(xmlGenericErrorContext, "axis 'following' "); |
|
break; |
|
case AXIS_FOLLOWING_SIBLING: |
|
xmlGenericError(xmlGenericErrorContext, |
|
"axis 'following-siblings' "); |
|
break; |
|
case AXIS_NAMESPACE: |
|
xmlGenericError(xmlGenericErrorContext, "axis 'namespace' "); |
|
break; |
|
case AXIS_PARENT: |
|
xmlGenericError(xmlGenericErrorContext, "axis 'parent' "); |
|
break; |
|
case AXIS_PRECEDING: |
|
xmlGenericError(xmlGenericErrorContext, "axis 'preceding' "); |
|
break; |
|
case AXIS_PRECEDING_SIBLING: |
|
xmlGenericError(xmlGenericErrorContext, |
|
"axis 'preceding-sibling' "); |
|
break; |
|
case AXIS_SELF: |
|
xmlGenericError(xmlGenericErrorContext, "axis 'self' "); |
|
break; |
|
} |
|
xmlGenericError(xmlGenericErrorContext, |
|
" context contains %d nodes\n", nbNodes); |
|
switch (op->value2) { |
|
case NODE_TEST_NONE: |
|
xmlGenericError(xmlGenericErrorContext, |
|
" searching for none !!!\n"); |
|
break; |
|
case NODE_TEST_TYPE: |
|
xmlGenericError(xmlGenericErrorContext, |
|
" searching for type %d\n", op->value3); |
|
break; |
|
case NODE_TEST_PI: |
|
xmlGenericError(xmlGenericErrorContext, |
|
" searching for PI !!!\n"); |
|
break; |
|
case NODE_TEST_ALL: |
|
xmlGenericError(xmlGenericErrorContext, |
|
" searching for *\n"); |
|
break; |
|
case NODE_TEST_NS: |
|
xmlGenericError(xmlGenericErrorContext, |
|
" searching for namespace %s\n", |
|
op->value5); |
|
break; |
|
case NODE_TEST_NAME: |
|
xmlGenericError(xmlGenericErrorContext, |
|
" searching for name %s\n", op->value5); |
|
if (op->value4) |
|
xmlGenericError(xmlGenericErrorContext, |
|
" with namespace %s\n", op->value4); |
|
break; |
|
} |
|
xmlGenericError(xmlGenericErrorContext, "Testing : "); |
|
} |
|
#endif /* DEBUG_STEP */ |
|
|
|
static int |
|
xmlXPathCompOpEvalPredicate(xmlXPathParserContextPtr ctxt, |
|
xmlXPathStepOpPtr op, |
|
xmlNodeSetPtr set, |
|
int contextSize, |
|
int hasNsNodes) |
|
{ |
|
if (op->ch1 != -1) { |
|
xmlXPathCompExprPtr comp = ctxt->comp; |
|
/* |
|
* Process inner predicates first. |
|
*/ |
|
if (comp->steps[op->ch1].op != XPATH_OP_PREDICATE) { |
|
/* |
|
* TODO: raise an internal error. |
|
*/ |
|
} |
|
contextSize = xmlXPathCompOpEvalPredicate(ctxt, |
|
&comp->steps[op->ch1], set, contextSize, hasNsNodes); |
|
CHECK_ERROR0; |
|
if (contextSize <= 0) |
|
return(0); |
|
} |
|
if (op->ch2 != -1) { |
|
xmlXPathContextPtr xpctxt = ctxt->context; |
|
xmlNodePtr contextNode, oldContextNode; |
|
xmlDocPtr oldContextDoc; |
|
int i, res, contextPos = 0, newContextSize; |
|
xmlXPathStepOpPtr exprOp; |
|
xmlXPathObjectPtr contextObj = NULL, exprRes = NULL; |
|
|
|
#ifdef LIBXML_XPTR_ENABLED |
|
/* |
|
* URGENT TODO: Check the following: |
|
* We don't expect location sets if evaluating prediates, right? |
|
* Only filters should expect location sets, right? |
|
*/ |
|
#endif |
|
/* |
|
* SPEC XPath 1.0: |
|
* "For each node in the node-set to be filtered, the |
|
* PredicateExpr is evaluated with that node as the |
|
* context node, with the number of nodes in the |
|
* node-set as the context size, and with the proximity |
|
* position of the node in the node-set with respect to |
|
* the axis as the context position;" |
|
* @oldset is the node-set" to be filtered. |
|
* |
|
* SPEC XPath 1.0: |
|
* "only predicates change the context position and |
|
* context size (see [2.4 Predicates])." |
|
* Example: |
|
* node-set context pos |
|
* nA 1 |
|
* nB 2 |
|
* nC 3 |
|
* After applying predicate [position() > 1] : |
|
* node-set context pos |
|
* nB 1 |
|
* nC 2 |
|
*/ |
|
oldContextNode = xpctxt->node; |
|
oldContextDoc = xpctxt->doc; |
|
/* |
|
* Get the expression of this predicate. |
|
*/ |
|
exprOp = &ctxt->comp->steps[op->ch2]; |
|
newContextSize = 0; |
|
for (i = 0; i < set->nodeNr; i++) { |
|
if (set->nodeTab[i] == NULL) |
|
continue; |
|
|
|
contextNode = set->nodeTab[i]; |
|
xpctxt->node = contextNode; |
|
xpctxt->contextSize = contextSize; |
|
xpctxt->proximityPosition = ++contextPos; |
|
|
|
/* |
|
* Also set the xpath document in case things like |
|
* key() are evaluated in the predicate. |
|
*/ |
|
if ((contextNode->type != XML_NAMESPACE_DECL) && |
|
(contextNode->doc != NULL)) |
|
xpctxt->doc = contextNode->doc; |
|
/* |
|
* Evaluate the predicate expression with 1 context node |
|
* at a time; this node is packaged into a node set; this |
|
* node set is handed over to the evaluation mechanism. |
|
*/ |
|
if (contextObj == NULL) |
|
contextObj = xmlXPathCacheNewNodeSet(xpctxt, contextNode); |
|
else { |
|
if (xmlXPathNodeSetAddUnique(contextObj->nodesetval, |
|
contextNode) < 0) { |
|
ctxt->error = XPATH_MEMORY_ERROR; |
|
goto evaluation_exit; |
|
} |
|
} |
|
|
|
valuePush(ctxt, contextObj); |
|
|
|
res = xmlXPathCompOpEvalToBoolean(ctxt, exprOp, 1); |
|
|
|
if ((ctxt->error != XPATH_EXPRESSION_OK) || (res == -1)) { |
|
xmlXPathNodeSetClear(set, hasNsNodes); |
|
newContextSize = 0; |
|
goto evaluation_exit; |
|
} |
|
|
|
if (res != 0) { |
|
newContextSize++; |
|
} else { |
|
/* |
|
* Remove the entry from the initial node set. |
|
*/ |
|
set->nodeTab[i] = NULL; |
|
if (contextNode->type == XML_NAMESPACE_DECL) |
|
xmlXPathNodeSetFreeNs((xmlNsPtr) contextNode); |
|
} |
|
if (ctxt->value == contextObj) { |
|
/* |
|
* Don't free the temporary XPath object holding the |
|
* context node, in order to avoid massive recreation |
|
* inside this loop. |
|
*/ |
|
valuePop(ctxt); |
|
xmlXPathNodeSetClear(contextObj->nodesetval, hasNsNodes); |
|
} else { |
|
/* |
|
* TODO: The object was lost in the evaluation machinery. |
|
* Can this happen? Maybe in internal-error cases. |
|
*/ |
|
contextObj = NULL; |
|
} |
|
} |
|
|
|
if (contextObj != NULL) { |
|
if (ctxt->value == contextObj) |
|
valuePop(ctxt); |
|
xmlXPathReleaseObject(xpctxt, contextObj); |
|
} |
|
evaluation_exit: |
|
if (exprRes != NULL) |
|
xmlXPathReleaseObject(ctxt->context, exprRes); |
|
/* |
|
* Reset/invalidate the context. |
|
*/ |
|
xpctxt->node = oldContextNode; |
|
xpctxt->doc = oldContextDoc; |
|
xpctxt->contextSize = -1; |
|
xpctxt->proximityPosition = -1; |
|
return(newContextSize); |
|
} |
|
return(contextSize); |
|
} |
|
|
|
static int |
|
xmlXPathCompOpEvalPositionalPredicate(xmlXPathParserContextPtr ctxt, |
|
xmlXPathStepOpPtr op, |
|
xmlNodeSetPtr set, |
|
int contextSize, |
|
int minPos, |
|
int maxPos, |
|
int hasNsNodes) |
|
{ |
|
if (op->ch1 != -1) { |
|
xmlXPathCompExprPtr comp = ctxt->comp; |
|
if (comp->steps[op->ch1].op != XPATH_OP_PREDICATE) { |
|
/* |
|
* TODO: raise an internal error. |
|
*/ |
|
} |
|
contextSize = xmlXPathCompOpEvalPredicate(ctxt, |
|
&comp->steps[op->ch1], set, contextSize, hasNsNodes); |
|
CHECK_ERROR0; |
|
if (contextSize <= 0) |
|
return(0); |
|
} |
|
/* |
|
* Check if the node set contains a sufficient number of nodes for |
|
* the requested range. |
|
*/ |
|
if (contextSize < minPos) { |
|
xmlXPathNodeSetClear(set, hasNsNodes); |
|
return(0); |
|
} |
|
if (op->ch2 == -1) { |
|
/* |
|
* TODO: Can this ever happen? |
|
*/ |
|
return (contextSize); |
|
} else { |
|
xmlDocPtr oldContextDoc; |
|
int i, pos = 0, newContextSize = 0, contextPos = 0, res; |
|
xmlXPathStepOpPtr exprOp; |
|
xmlXPathObjectPtr contextObj = NULL, exprRes = NULL; |
|
xmlNodePtr oldContextNode, contextNode = NULL; |
|
xmlXPathContextPtr xpctxt = ctxt->context; |
|
int frame; |
|
|
|
#ifdef LIBXML_XPTR_ENABLED |
|
/* |
|
* URGENT TODO: Check the following: |
|
* We don't expect location sets if evaluating prediates, right? |
|
* Only filters should expect location sets, right? |
|
*/ |
|
#endif /* LIBXML_XPTR_ENABLED */ |
|
|
|
/* |
|
* Save old context. |
|
*/ |
|
oldContextNode = xpctxt->node; |
|
oldContextDoc = xpctxt->doc; |
|
/* |
|
* Get the expression of this predicate. |
|
*/ |
|
exprOp = &ctxt->comp->steps[op->ch2]; |
|
for (i = 0; i < set->nodeNr; i++) { |
|
xmlXPathObjectPtr tmp; |
|
|
|
if (set->nodeTab[i] == NULL) |
|
continue; |
|
|
|
contextNode = set->nodeTab[i]; |
|
xpctxt->node = contextNode; |
|
xpctxt->contextSize = contextSize; |
|
xpctxt->proximityPosition = ++contextPos; |
|
|
|
/* |
|
* Initialize the new set. |
|
* Also set the xpath document in case things like |
|
* key() evaluation are attempted on the predicate |
|
*/ |
|
if ((contextNode->type != XML_NAMESPACE_DECL) && |
|
(contextNode->doc != NULL)) |
|
xpctxt->doc = contextNode->doc; |
|
/* |
|
* Evaluate the predicate expression with 1 context node |
|
* at a time; this node is packaged into a node set; this |
|
* node set is handed over to the evaluation mechanism. |
|
*/ |
|
if (contextObj == NULL) |
|
contextObj = xmlXPathCacheNewNodeSet(xpctxt, contextNode); |
|
else { |
|
if (xmlXPathNodeSetAddUnique(contextObj->nodesetval, |
|
contextNode) < 0) { |
|
ctxt->error = XPATH_MEMORY_ERROR; |
|
goto evaluation_exit; |
|
} |
|
} |
|
|
|
valuePush(ctxt, contextObj); |
|
frame = xmlXPathSetFrame(ctxt); |
|
res = xmlXPathCompOpEvalToBoolean(ctxt, exprOp, 1); |
|
xmlXPathPopFrame(ctxt, frame); |
|
tmp = valuePop(ctxt); |
|
|
|
if ((ctxt->error != XPATH_EXPRESSION_OK) || (res == -1)) { |
|
while (tmp != contextObj) { |
|
/* |
|
* Free up the result |
|
* then pop off contextObj, which will be freed later |
|
*/ |
|
xmlXPathReleaseObject(xpctxt, tmp); |
|
tmp = valuePop(ctxt); |
|
} |
|
goto evaluation_error; |
|
} |
|
/* push the result back onto the stack */ |
|
valuePush(ctxt, tmp); |
|
|
|
if (res) |
|
pos++; |
|
|
|
if (res && (pos >= minPos) && (pos <= maxPos)) { |
|
/* |
|
* Fits in the requested range. |
|
*/ |
|
newContextSize++; |
|
if (minPos == maxPos) { |
|
/* |
|
* Only 1 node was requested. |
|
*/ |
|
if (contextNode->type == XML_NAMESPACE_DECL) { |
|
/* |
|
* As always: take care of those nasty |
|
* namespace nodes. |
|
*/ |
|
set->nodeTab[i] = NULL; |
|
} |
|
xmlXPathNodeSetClear(set, hasNsNodes); |
|
set->nodeNr = 1; |
|
set->nodeTab[0] = contextNode; |
|
goto evaluation_exit; |
|
} |
|
if (pos == maxPos) { |
|
/* |
|
* We are done. |
|
*/ |
|
xmlXPathNodeSetClearFromPos(set, i +1, hasNsNodes); |
|
goto evaluation_exit; |
|
} |
|
} else { |
|
/* |
|
* Remove the entry from the initial node set. |
|
*/ |
|
set->nodeTab[i] = NULL; |
|
if (contextNode->type == XML_NAMESPACE_DECL) |
|
xmlXPathNodeSetFreeNs((xmlNsPtr) contextNode); |
|
} |
|
if (exprRes != NULL) { |
|
xmlXPathReleaseObject(ctxt->context, exprRes); |
|
exprRes = NULL; |
|
} |
|
if (ctxt->value == contextObj) { |
|
/* |
|
* Don't free the temporary XPath object holding the |
|
* context node, in order to avoid massive recreation |
|
* inside this loop. |
|
*/ |
|
valuePop(ctxt); |
|
xmlXPathNodeSetClear(contextObj->nodesetval, hasNsNodes); |
|
} else { |
|
/* |
|
* The object was lost in the evaluation machinery. |
|
* Can this happen? Maybe in case of internal-errors. |
|
*/ |
|
contextObj = NULL; |
|
} |
|
} |
|
goto evaluation_exit; |
|
|
|
evaluation_error: |
|
xmlXPathNodeSetClear(set, hasNsNodes); |
|
newContextSize = 0; |
|
|
|
evaluation_exit: |
|
if (contextObj != NULL) { |
|
if (ctxt->value == contextObj) |
|
valuePop(ctxt); |
|
xmlXPathReleaseObject(xpctxt, contextObj); |
|
} |
|
if (exprRes != NULL) |
|
xmlXPathReleaseObject(ctxt->context, exprRes); |
|
/* |
|
* Reset/invalidate the context. |
|
*/ |
|
xpctxt->node = oldContextNode; |
|
xpctxt->doc = oldContextDoc; |
|
xpctxt->contextSize = -1; |
|
xpctxt->proximityPosition = -1; |
|
return(newContextSize); |
|
} |
|
return(contextSize); |
|
} |
|
|
|
static int |
|
xmlXPathIsPositionalPredicate(xmlXPathParserContextPtr ctxt, |
|
xmlXPathStepOpPtr op, |
|
int *maxPos) |
|
{ |
|
|
|
xmlXPathStepOpPtr exprOp; |
|
|
|
/* |
|
* BIG NOTE: This is not intended for XPATH_OP_FILTER yet! |
|
*/ |
|
|
|
/* |
|
* If not -1, then ch1 will point to: |
|
* 1) For predicates (XPATH_OP_PREDICATE): |
|
* - an inner predicate operator |
|
* 2) For filters (XPATH_OP_FILTER): |
|
* - an inner filter operater OR |
|
* - an expression selecting the node set. |
|
* E.g. "key('a', 'b')" or "(//foo | //bar)". |
|
*/ |
|
if ((op->op != XPATH_OP_PREDICATE) && (op->op != XPATH_OP_FILTER)) |
|
return(0); |
|
|
|
if (op->ch2 != -1) { |
|
exprOp = &ctxt->comp->steps[op->ch2]; |
|
} else |
|
return(0); |
|
|
|
if ((exprOp != NULL) && |
|
(exprOp->op == XPATH_OP_VALUE) && |
|
(exprOp->value4 != NULL) && |
|
(((xmlXPathObjectPtr) exprOp->value4)->type == XPATH_NUMBER)) |
|
{ |
|
double floatval = ((xmlXPathObjectPtr) exprOp->value4)->floatval; |
|
|
|
/* |
|
* We have a "[n]" predicate here. |
|
* TODO: Unfortunately this simplistic test here is not |
|
* able to detect a position() predicate in compound |
|
* expressions like "[@attr = 'a" and position() = 1], |
|
* and even not the usage of position() in |
|
* "[position() = 1]"; thus - obviously - a position-range, |
|
* like it "[position() < 5]", is also not detected. |
|
* Maybe we could rewrite the AST to ease the optimization. |
|
*/ |
|
|
|
if ((floatval > INT_MIN) && (floatval < INT_MAX)) { |
|
*maxPos = (int) floatval; |
|
if (floatval == (double) *maxPos) |
|
return(1); |
|
} |
|
} |
|
return(0); |
|
} |
|
|
|
static int |
|
xmlXPathNodeCollectAndTest(xmlXPathParserContextPtr ctxt, |
|
xmlXPathStepOpPtr op, |
|
xmlNodePtr * first, xmlNodePtr * last, |
|
int toBool) |
|
{ |
|
|
|
#define XP_TEST_HIT \ |
|
if (hasAxisRange != 0) { \ |
|
if (++pos == maxPos) { \ |
|
if (addNode(seq, cur) < 0) \ |
|
ctxt->error = XPATH_MEMORY_ERROR; \ |
|
goto axis_range_end; } \ |
|
} else { \ |
|
if (addNode(seq, cur) < 0) \ |
|
ctxt->error = XPATH_MEMORY_ERROR; \ |
|
if (breakOnFirstHit) goto first_hit; } |
|
|
|
#define XP_TEST_HIT_NS \ |
|
if (hasAxisRange != 0) { \ |
|
if (++pos == maxPos) { \ |
|
hasNsNodes = 1; \ |
|
if (xmlXPathNodeSetAddNs(seq, xpctxt->node, (xmlNsPtr) cur) < 0) \ |
|
ctxt->error = XPATH_MEMORY_ERROR; \ |
|
goto axis_range_end; } \ |
|
} else { \ |
|
hasNsNodes = 1; \ |
|
if (xmlXPathNodeSetAddNs(seq, xpctxt->node, (xmlNsPtr) cur) < 0) \ |
|
ctxt->error = XPATH_MEMORY_ERROR; \ |
|
if (breakOnFirstHit) goto first_hit; } |
|
|
|
xmlXPathAxisVal axis = (xmlXPathAxisVal) op->value; |
|
xmlXPathTestVal test = (xmlXPathTestVal) op->value2; |
|
xmlXPathTypeVal type = (xmlXPathTypeVal) op->value3; |
|
const xmlChar *prefix = op->value4; |
|
const xmlChar *name = op->value5; |
|
const xmlChar *URI = NULL; |
|
|
|
#ifdef DEBUG_STEP |
|
int nbMatches = 0, prevMatches = 0; |
|
#endif |
|
int total = 0, hasNsNodes = 0; |
|
/* The popped object holding the context nodes */ |
|
xmlXPathObjectPtr obj; |
|
/* The set of context nodes for the node tests */ |
|
xmlNodeSetPtr contextSeq; |
|
int contextIdx; |
|
xmlNodePtr contextNode; |
|
/* The final resulting node set wrt to all context nodes */ |
|
xmlNodeSetPtr outSeq; |
|
/* |
|
* The temporary resulting node set wrt 1 context node. |
|
* Used to feed predicate evaluation. |
|
*/ |
|
xmlNodeSetPtr seq; |
|
xmlNodePtr cur; |
|
/* First predicate operator */ |
|
xmlXPathStepOpPtr predOp; |
|
int maxPos; /* The requested position() (when a "[n]" predicate) */ |
|
int hasPredicateRange, hasAxisRange, pos, size, newSize; |
|
int breakOnFirstHit; |
|
|
|
xmlXPathTraversalFunction next = NULL; |
|
int (*addNode) (xmlNodeSetPtr, xmlNodePtr); |
|
xmlXPathNodeSetMergeFunction mergeAndClear; |
|
xmlNodePtr oldContextNode; |
|
xmlXPathContextPtr xpctxt = ctxt->context; |
|
|
|
|
|
CHECK_TYPE0(XPATH_NODESET); |
|
obj = valuePop(ctxt); |
|
/* |
|
* Setup namespaces. |
|
*/ |
|
if (prefix != NULL) { |
|
URI = xmlXPathNsLookup(xpctxt, prefix); |
|
if (URI == NULL) { |
|
xmlXPathReleaseObject(xpctxt, obj); |
|
XP_ERROR0(XPATH_UNDEF_PREFIX_ERROR); |
|
} |
|
} |
|
/* |
|
* Setup axis. |
|
* |
|
* MAYBE FUTURE TODO: merging optimizations: |
|
* - If the nodes to be traversed wrt to the initial nodes and |
|
* the current axis cannot overlap, then we could avoid searching |
|
* for duplicates during the merge. |
|
* But the question is how/when to evaluate if they cannot overlap. |
|
* Example: if we know that for two initial nodes, the one is |
|
* not in the ancestor-or-self axis of the other, then we could safely |
|
* avoid a duplicate-aware merge, if the axis to be traversed is e.g. |
|
* the descendant-or-self axis. |
|
*/ |
|
mergeAndClear = xmlXPathNodeSetMergeAndClear; |
|
switch (axis) { |
|
case AXIS_ANCESTOR: |
|
first = NULL; |
|
next = xmlXPathNextAncestor; |
|
break; |
|
case AXIS_ANCESTOR_OR_SELF: |
|
first = NULL; |
|
next = xmlXPathNextAncestorOrSelf; |
|
break; |
|
case AXIS_ATTRIBUTE: |
|
first = NULL; |
|
last = NULL; |
|
next = xmlXPathNextAttribute; |
|
mergeAndClear = xmlXPathNodeSetMergeAndClearNoDupls; |
|
break; |
|
case AXIS_CHILD: |
|
last = NULL; |
|
if (((test == NODE_TEST_NAME) || (test == NODE_TEST_ALL)) && |
|
(type == NODE_TYPE_NODE)) |
|
{ |
|
/* |
|
* Optimization if an element node type is 'element'. |
|
*/ |
|
next = xmlXPathNextChildElement; |
|
} else |
|
next = xmlXPathNextChild; |
|
mergeAndClear = xmlXPathNodeSetMergeAndClearNoDupls; |
|
break; |
|
case AXIS_DESCENDANT: |
|
last = NULL; |
|
next = xmlXPathNextDescendant; |
|
break; |
|
case AXIS_DESCENDANT_OR_SELF: |
|
last = NULL; |
|
next = xmlXPathNextDescendantOrSelf; |
|
break; |
|
case AXIS_FOLLOWING: |
|
last = NULL; |
|
next = xmlXPathNextFollowing; |
|
break; |
|
case AXIS_FOLLOWING_SIBLING: |
|
last = NULL; |
|
next = xmlXPathNextFollowingSibling; |
|
break; |
|
case AXIS_NAMESPACE: |
|
first = NULL; |
|
last = NULL; |
|
next = (xmlXPathTraversalFunction) xmlXPathNextNamespace; |
|
mergeAndClear = xmlXPathNodeSetMergeAndClearNoDupls; |
|
break; |
|
case AXIS_PARENT: |
|
first = NULL; |
|
next = xmlXPathNextParent; |
|
break; |
|
case AXIS_PRECEDING: |
|
first = NULL; |
|
next = xmlXPathNextPrecedingInternal; |
|
break; |
|
case AXIS_PRECEDING_SIBLING: |
|
first = NULL; |
|
next = xmlXPathNextPrecedingSibling; |
|
break; |
|
case AXIS_SELF: |
|
first = NULL; |
|
last = NULL; |
|
next = xmlXPathNextSelf; |
|
mergeAndClear = xmlXPathNodeSetMergeAndClearNoDupls; |
|
break; |
|
} |
|
|
|
#ifdef DEBUG_STEP |
|
xmlXPathDebugDumpStepAxis(op, |
|
(obj->nodesetval != NULL) ? obj->nodesetval->nodeNr : 0); |
|
#endif |
|
|
|
if (next == NULL) { |
|
xmlXPathReleaseObject(xpctxt, obj); |
|
return(0); |
|
} |
|
contextSeq = obj->nodesetval; |
|
if ((contextSeq == NULL) || (contextSeq->nodeNr <= 0)) { |
|
xmlXPathReleaseObject(xpctxt, obj); |
|
valuePush(ctxt, xmlXPathCacheWrapNodeSet(xpctxt, NULL)); |
|
return(0); |
|
} |
|
/* |
|
* Predicate optimization --------------------------------------------- |
|
* If this step has a last predicate, which contains a position(), |
|
* then we'll optimize (although not exactly "position()", but only |
|
* the short-hand form, i.e., "[n]". |
|
* |
|
* Example - expression "/foo[parent::bar][1]": |
|
* |
|
* COLLECT 'child' 'name' 'node' foo -- op (we are here) |
|
* ROOT -- op->ch1 |
|
* PREDICATE -- op->ch2 (predOp) |
|
* PREDICATE -- predOp->ch1 = [parent::bar] |
|
* SORT |
|
* COLLECT 'parent' 'name' 'node' bar |
|
* NODE |
|
* ELEM Object is a number : 1 -- predOp->ch2 = [1] |
|
* |
|
*/ |
|
maxPos = 0; |
|
predOp = NULL; |
|
hasPredicateRange = 0; |
|
hasAxisRange = 0; |
|
if (op->ch2 != -1) { |
|
/* |
|
* There's at least one predicate. 16 == XPATH_OP_PREDICATE |
|
*/ |
|
predOp = &ctxt->comp->steps[op->ch2]; |
|
if (xmlXPathIsPositionalPredicate(ctxt, predOp, &maxPos)) { |
|
if (predOp->ch1 != -1) { |
|
/* |
|
* Use the next inner predicate operator. |
|
*/ |
|
predOp = &ctxt->comp->steps[predOp->ch1]; |
|
hasPredicateRange = 1; |
|
} else { |
|
/* |
|
* There's no other predicate than the [n] predicate. |
|
*/ |
|
predOp = NULL; |
|
hasAxisRange = 1; |
|
} |
|
} |
|
} |
|
breakOnFirstHit = ((toBool) && (predOp == NULL)) ? 1 : 0; |
|
/* |
|
* Axis traversal ----------------------------------------------------- |
|
*/ |
|
/* |
|
* 2.3 Node Tests |
|
* - For the attribute axis, the principal node type is attribute. |
|
* - For the namespace axis, the principal node type is namespace. |
|
* - For other axes, the principal node type is element. |
|
* |
|
* A node test * is true for any node of the |
|
* principal node type. For example, child::* will |
|
* select all element children of the context node |
|
*/ |
|
oldContextNode = xpctxt->node; |
|
addNode = xmlXPathNodeSetAddUnique; |
|
outSeq = NULL; |
|
seq = NULL; |
|
contextNode = NULL; |
|
contextIdx = 0; |
|
|
|
|
|
while (((contextIdx < contextSeq->nodeNr) || (contextNode != NULL)) && |
|
(ctxt->error == XPATH_EXPRESSION_OK)) { |
|
xpctxt->node = contextSeq->nodeTab[contextIdx++]; |
|
|
|
if (seq == NULL) { |
|
seq = xmlXPathNodeSetCreate(NULL); |
|
if (seq == NULL) { |
|
total = 0; |
|
goto error; |
|
} |
|
} |
|
/* |
|
* Traverse the axis and test the nodes. |
|
*/ |
|
pos = 0; |
|
cur = NULL; |
|
hasNsNodes = 0; |
|
do { |
|
cur = next(ctxt, cur); |
|
if (cur == NULL) |
|
break; |
|
|
|
/* |
|
* QUESTION TODO: What does the "first" and "last" stuff do? |
|
*/ |
|
if ((first != NULL) && (*first != NULL)) { |
|
if (*first == cur) |
|
break; |
|
if (((total % 256) == 0) && |
|
#ifdef XP_OPTIMIZED_NON_ELEM_COMPARISON |
|
(xmlXPathCmpNodesExt(*first, cur) >= 0)) |
|
#else |
|
(xmlXPathCmpNodes(*first, cur) >= 0)) |
|
#endif |
|
{ |
|
break; |
|
} |
|
} |
|
if ((last != NULL) && (*last != NULL)) { |
|
if (*last == cur) |
|
break; |
|
if (((total % 256) == 0) && |
|
#ifdef XP_OPTIMIZED_NON_ELEM_COMPARISON |
|
(xmlXPathCmpNodesExt(cur, *last) >= 0)) |
|
#else |
|
(xmlXPathCmpNodes(cur, *last) >= 0)) |
|
#endif |
|
{ |
|
break; |
|
} |
|
} |
|
|
|
total++; |
|
|
|
#ifdef DEBUG_STEP |
|
xmlGenericError(xmlGenericErrorContext, " %s", cur->name); |
|
#endif |
|
|
|
switch (test) { |
|
case NODE_TEST_NONE: |
|
total = 0; |
|
STRANGE |
|
goto error; |
|
case NODE_TEST_TYPE: |
|
if (type == NODE_TYPE_NODE) { |
|
switch (cur->type) { |
|
case XML_DOCUMENT_NODE: |
|
case XML_HTML_DOCUMENT_NODE: |
|
#ifdef LIBXML_DOCB_ENABLED |
|
case XML_DOCB_DOCUMENT_NODE: |
|
#endif |
|
case XML_ELEMENT_NODE: |
|
case XML_ATTRIBUTE_NODE: |
|
case XML_PI_NODE: |
|
case XML_COMMENT_NODE: |
|
case XML_CDATA_SECTION_NODE: |
|
case XML_TEXT_NODE: |
|
XP_TEST_HIT |
|
break; |
|
case XML_NAMESPACE_DECL: { |
|
if (axis == AXIS_NAMESPACE) { |
|
XP_TEST_HIT_NS |
|
} else { |
|
hasNsNodes = 1; |
|
XP_TEST_HIT |
|
} |
|
break; |
|
} |
|
default: |
|
break; |
|
} |
|
} else if (cur->type == (xmlElementType) type) { |
|
if (cur->type == XML_NAMESPACE_DECL) |
|
XP_TEST_HIT_NS |
|
else |
|
XP_TEST_HIT |
|
} else if ((type == NODE_TYPE_TEXT) && |
|
(cur->type == XML_CDATA_SECTION_NODE)) |
|
{ |
|
XP_TEST_HIT |
|
} |
|
break; |
|
case NODE_TEST_PI: |
|
if ((cur->type == XML_PI_NODE) && |
|
((name == NULL) || xmlStrEqual(name, cur->name))) |
|
{ |
|
XP_TEST_HIT |
|
} |
|
break; |
|
case NODE_TEST_ALL: |
|
if (axis == AXIS_ATTRIBUTE) { |
|
if (cur->type == XML_ATTRIBUTE_NODE) |
|
{ |
|
if (prefix == NULL) |
|
{ |
|
XP_TEST_HIT |
|
} else if ((cur->ns != NULL) && |
|
(xmlStrEqual(URI, cur->ns->href))) |
|
{ |
|
XP_TEST_HIT |
|
} |
|
} |
|
} else if (axis == AXIS_NAMESPACE) { |
|
if (cur->type == XML_NAMESPACE_DECL) |
|
{ |
|
XP_TEST_HIT_NS |
|
} |
|
} else { |
|
if (cur->type == XML_ELEMENT_NODE) { |
|
if (prefix == NULL) |
|
{ |
|
XP_TEST_HIT |
|
|
|
} else if ((cur->ns != NULL) && |
|
(xmlStrEqual(URI, cur->ns->href))) |
|
{ |
|
XP_TEST_HIT |
|
} |
|
} |
|
} |
|
break; |
|
case NODE_TEST_NS:{ |
|
TODO; |
|
break; |
|
} |
|
case NODE_TEST_NAME: |
|
if (axis == AXIS_ATTRIBUTE) { |
|
if (cur->type != XML_ATTRIBUTE_NODE) |
|
break; |
|
} else if (axis == AXIS_NAMESPACE) { |
|
if (cur->type != XML_NAMESPACE_DECL) |
|
break; |
|
} else { |
|
if (cur->type != XML_ELEMENT_NODE) |
|
break; |
|
} |
|
switch (cur->type) { |
|
case XML_ELEMENT_NODE: |
|
if (xmlStrEqual(name, cur->name)) { |
|
if (prefix == NULL) { |
|
if (cur->ns == NULL) |
|
{ |
|
XP_TEST_HIT |
|
} |
|
} else { |
|
if ((cur->ns != NULL) && |
|
(xmlStrEqual(URI, cur->ns->href))) |
|
{ |
|
XP_TEST_HIT |
|
} |
|
} |
|
} |
|
break; |
|
case XML_ATTRIBUTE_NODE:{ |
|
xmlAttrPtr attr = (xmlAttrPtr) cur; |
|
|
|
if (xmlStrEqual(name, attr->name)) { |
|
if (prefix == NULL) { |
|
if ((attr->ns == NULL) || |
|
(attr->ns->prefix == NULL)) |
|
{ |
|
XP_TEST_HIT |
|
} |
|
} else { |
|
if ((attr->ns != NULL) && |
|
(xmlStrEqual(URI, |
|
attr->ns->href))) |
|
{ |
|
XP_TEST_HIT |
|
} |
|
} |
|
} |
|
break; |
|
} |
|
case XML_NAMESPACE_DECL: |
|
if (cur->type == XML_NAMESPACE_DECL) { |
|
xmlNsPtr ns = (xmlNsPtr) cur; |
|
|
|
if ((ns->prefix != NULL) && (name != NULL) |
|
&& (xmlStrEqual(ns->prefix, name))) |
|
{ |
|
XP_TEST_HIT_NS |
|
} |
|
} |
|
break; |
|
default: |
|
break; |
|
} |
|
break; |
|
} /* switch(test) */ |
|
} while ((cur != NULL) && (ctxt->error == XPATH_EXPRESSION_OK)); |
|
|
|
goto apply_predicates; |
|
|
|
axis_range_end: /* ----------------------------------------------------- */ |
|
/* |
|
* We have a "/foo[n]", and position() = n was reached. |
|
* Note that we can have as well "/foo/::parent::foo[1]", so |
|
* a duplicate-aware merge is still needed. |
|
* Merge with the result. |
|
*/ |
|
if (outSeq == NULL) { |
|
outSeq = seq; |
|
seq = NULL; |
|
} else |
|
outSeq = mergeAndClear(outSeq, seq, 0); |
|
/* |
|
* Break if only a true/false result was requested. |
|
*/ |
|
if (toBool) |
|
break; |
|
continue; |
|
|
|
first_hit: /* ---------------------------------------------------------- */ |
|
/* |
|
* Break if only a true/false result was requested and |
|
* no predicates existed and a node test succeeded. |
|
*/ |
|
if (outSeq == NULL) { |
|
outSeq = seq; |
|
seq = NULL; |
|
} else |
|
outSeq = mergeAndClear(outSeq, seq, 0); |
|
break; |
|
|
|
#ifdef DEBUG_STEP |
|
if (seq != NULL) |
|
nbMatches += seq->nodeNr; |
|
#endif |
|
|
|
apply_predicates: /* --------------------------------------------------- */ |
|
if (ctxt->error != XPATH_EXPRESSION_OK) |
|
goto error; |
|
|
|
/* |
|
* Apply predicates. |
|
*/ |
|
if ((predOp != NULL) && (seq->nodeNr > 0)) { |
|
/* |
|
* E.g. when we have a "/foo[some expression][n]". |
|
*/ |
|
/* |
|
* QUESTION TODO: The old predicate evaluation took into |
|
* account location-sets. |
|
* (E.g. ctxt->value->type == XPATH_LOCATIONSET) |
|
* Do we expect such a set here? |
|
* All what I learned now from the evaluation semantics |
|
* does not indicate that a location-set will be processed |
|
* here, so this looks OK. |
|
*/ |
|
/* |
|
* Iterate over all predicates, starting with the outermost |
|
* predicate. |
|
* TODO: Problem: we cannot execute the inner predicates first |
|
* since we cannot go back *up* the operator tree! |
|
* Options we have: |
|
* 1) Use of recursive functions (like is it currently done |
|
* via xmlXPathCompOpEval()) |
|
* 2) Add a predicate evaluation information stack to the |
|
* context struct |
|
* 3) Change the way the operators are linked; we need a |
|
* "parent" field on xmlXPathStepOp |
|
* |
|
* For the moment, I'll try to solve this with a recursive |
|
* function: xmlXPathCompOpEvalPredicate(). |
|
*/ |
|
size = seq->nodeNr; |
|
if (hasPredicateRange != 0) |
|
newSize = xmlXPathCompOpEvalPositionalPredicate(ctxt, |
|
predOp, seq, size, maxPos, maxPos, hasNsNodes); |
|
else |
|
newSize = xmlXPathCompOpEvalPredicate(ctxt, |
|
predOp, seq, size, hasNsNodes); |
|
|
|
if (ctxt->error != XPATH_EXPRESSION_OK) { |
|
total = 0; |
|
goto error; |
|
} |
|
/* |
|
* Add the filtered set of nodes to the result node set. |
|
*/ |
|
if (newSize == 0) { |
|
/* |
|
* The predicates filtered all nodes out. |
|
*/ |
|
xmlXPathNodeSetClear(seq, hasNsNodes); |
|
} else if (seq->nodeNr > 0) { |
|
/* |
|
* Add to result set. |
|
*/ |
|
if (outSeq == NULL) { |
|
if (size != newSize) { |
|
/* |
|
* We need to merge and clear here, since |
|
* the sequence will contained NULLed entries. |
|
*/ |
|
outSeq = mergeAndClear(NULL, seq, 1); |
|
} else { |
|
outSeq = seq; |
|
seq = NULL; |
|
} |
|
} else |
|
outSeq = mergeAndClear(outSeq, seq, |
|
(size != newSize) ? 1: 0); |
|
/* |
|
* Break if only a true/false result was requested. |
|
*/ |
|
if (toBool) |
|
break; |
|
} |
|
} else if (seq->nodeNr > 0) { |
|
/* |
|
* Add to result set. |
|
*/ |
|
if (outSeq == NULL) { |
|
outSeq = seq; |
|
seq = NULL; |
|
} else { |
|
outSeq = mergeAndClear(outSeq, seq, 0); |
|
} |
|
} |
|
} |
|
|
|
error: |
|
if ((obj->boolval) && (obj->user != NULL)) { |
|
/* |
|
* QUESTION TODO: What does this do and why? |
|
* TODO: Do we have to do this also for the "error" |
|
* cleanup further down? |
|
*/ |
|
ctxt->value->boolval = 1; |
|
ctxt->value->user = obj->user; |
|
obj->user = NULL; |
|
obj->boolval = 0; |
|
} |
|
xmlXPathReleaseObject(xpctxt, obj); |
|
|
|
/* |
|
* Ensure we return at least an emtpy set. |
|
*/ |
|
if (outSeq == NULL) { |
|
if ((seq != NULL) && (seq->nodeNr == 0)) |
|
outSeq = seq; |
|
else |
|
outSeq = xmlXPathNodeSetCreate(NULL); |
|
/* XXX what if xmlXPathNodeSetCreate returned NULL here? */ |
|
} |
|
if ((seq != NULL) && (seq != outSeq)) { |
|
xmlXPathFreeNodeSet(seq); |
|
} |
|
/* |
|
* Hand over the result. Better to push the set also in |
|
* case of errors. |
|
*/ |
|
valuePush(ctxt, xmlXPathCacheWrapNodeSet(xpctxt, outSeq)); |
|
/* |
|
* Reset the context node. |
|
*/ |
|
xpctxt->node = oldContextNode; |
|
/* |
|
* When traversing the namespace axis in "toBool" mode, it's |
|
* possible that tmpNsList wasn't freed. |
|
*/ |
|
if (xpctxt->tmpNsList != NULL) { |
|
xmlFree(xpctxt->tmpNsList); |
|
xpctxt->tmpNsList = NULL; |
|
} |
|
|
|
#ifdef DEBUG_STEP |
|
xmlGenericError(xmlGenericErrorContext, |
|
"\nExamined %d nodes, found %d nodes at that step\n", |
|
total, nbMatches); |
|
#endif |
|
|
|
return(total); |
|
} |
|
|
|
static int |
|
xmlXPathCompOpEvalFilterFirst(xmlXPathParserContextPtr ctxt, |
|
xmlXPathStepOpPtr op, xmlNodePtr * first); |
|
|
|
/** |
|
* xmlXPathCompOpEvalFirst: |
|
* @ctxt: the XPath parser context with the compiled expression |
|
* @op: an XPath compiled operation |
|
* @first: the first elem found so far |
|
* |
|
* Evaluate the Precompiled XPath operation searching only the first |
|
* element in document order |
|
* |
|
* Returns the number of examined objects. |
|
*/ |
|
static int |
|
xmlXPathCompOpEvalFirst(xmlXPathParserContextPtr ctxt, |
|
xmlXPathStepOpPtr op, xmlNodePtr * first) |
|
{ |
|
int total = 0, cur; |
|
xmlXPathCompExprPtr comp; |
|
xmlXPathObjectPtr arg1, arg2; |
|
|
|
CHECK_ERROR0; |
|
comp = ctxt->comp; |
|
switch (op->op) { |
|
case XPATH_OP_END: |
|
return (0); |
|
case XPATH_OP_UNION: |
|
total = |
|
xmlXPathCompOpEvalFirst(ctxt, &comp->steps[op->ch1], |
|
first); |
|
CHECK_ERROR0; |
|
if ((ctxt->value != NULL) |
|
&& (ctxt->value->type == XPATH_NODESET) |
|
&& (ctxt->value->nodesetval != NULL) |
|
&& (ctxt->value->nodesetval->nodeNr >= 1)) { |
|
/* |
|
* limit tree traversing to first node in the result |
|
*/ |
|
/* |
|
* OPTIMIZE TODO: This implicitely sorts |
|
* the result, even if not needed. E.g. if the argument |
|
* of the count() function, no sorting is needed. |
|
* OPTIMIZE TODO: How do we know if the node-list wasn't |
|
* aready sorted? |
|
*/ |
|
if (ctxt->value->nodesetval->nodeNr > 1) |
|
xmlXPathNodeSetSort(ctxt->value->nodesetval); |
|
*first = ctxt->value->nodesetval->nodeTab[0]; |
|
} |
|
cur = |
|
xmlXPathCompOpEvalFirst(ctxt, &comp->steps[op->ch2], |
|
first); |
|
CHECK_ERROR0; |
|
|
|
arg2 = valuePop(ctxt); |
|
arg1 = valuePop(ctxt); |
|
if ((arg1 == NULL) || (arg1->type != XPATH_NODESET) || |
|
(arg2 == NULL) || (arg2->type != XPATH_NODESET)) { |
|
xmlXPathReleaseObject(ctxt->context, arg1); |
|
xmlXPathReleaseObject(ctxt->context, arg2); |
|
XP_ERROR0(XPATH_INVALID_TYPE); |
|
} |
|
|
|
arg1->nodesetval = xmlXPathNodeSetMerge(arg1->nodesetval, |
|
arg2->nodesetval); |
|
valuePush(ctxt, arg1); |
|
xmlXPathReleaseObject(ctxt->context, arg2); |
|
/* optimizer */ |
|
if (total > cur) |
|
xmlXPathCompSwap(op); |
|
return (total + cur); |
|
case XPATH_OP_ROOT: |
|
xmlXPathRoot(ctxt); |
|
return (0); |
|
case XPATH_OP_NODE: |
|
if (op->ch1 != -1) |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
if (op->ch2 != -1) |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch2]); |
|
CHECK_ERROR0; |
|
valuePush(ctxt, xmlXPathCacheNewNodeSet(ctxt->context, |
|
ctxt->context->node)); |
|
return (total); |
|
case XPATH_OP_RESET: |
|
if (op->ch1 != -1) |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
if (op->ch2 != -1) |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch2]); |
|
CHECK_ERROR0; |
|
ctxt->context->node = NULL; |
|
return (total); |
|
case XPATH_OP_COLLECT:{ |
|
if (op->ch1 == -1) |
|
return (total); |
|
|
|
total = xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
|
|
total += xmlXPathNodeCollectAndTest(ctxt, op, first, NULL, 0); |
|
return (total); |
|
} |
|
case XPATH_OP_VALUE: |
|
valuePush(ctxt, |
|
xmlXPathCacheObjectCopy(ctxt->context, |
|
(xmlXPathObjectPtr) op->value4)); |
|
return (0); |
|
case XPATH_OP_SORT: |
|
if (op->ch1 != -1) |
|
total += |
|
xmlXPathCompOpEvalFirst(ctxt, &comp->steps[op->ch1], |
|
first); |
|
CHECK_ERROR0; |
|
if ((ctxt->value != NULL) |
|
&& (ctxt->value->type == XPATH_NODESET) |
|
&& (ctxt->value->nodesetval != NULL) |
|
&& (ctxt->value->nodesetval->nodeNr > 1)) |
|
xmlXPathNodeSetSort(ctxt->value->nodesetval); |
|
return (total); |
|
#ifdef XP_OPTIMIZED_FILTER_FIRST |
|
case XPATH_OP_FILTER: |
|
total += xmlXPathCompOpEvalFilterFirst(ctxt, op, first); |
|
return (total); |
|
#endif |
|
default: |
|
return (xmlXPathCompOpEval(ctxt, op)); |
|
} |
|
} |
|
|
|
/** |
|
* xmlXPathCompOpEvalLast: |
|
* @ctxt: the XPath parser context with the compiled expression |
|
* @op: an XPath compiled operation |
|
* @last: the last elem found so far |
|
* |
|
* Evaluate the Precompiled XPath operation searching only the last |
|
* element in document order |
|
* |
|
* Returns the number of nodes traversed |
|
*/ |
|
static int |
|
xmlXPathCompOpEvalLast(xmlXPathParserContextPtr ctxt, xmlXPathStepOpPtr op, |
|
xmlNodePtr * last) |
|
{ |
|
int total = 0, cur; |
|
xmlXPathCompExprPtr comp; |
|
xmlXPathObjectPtr arg1, arg2; |
|
xmlNodePtr bak; |
|
xmlDocPtr bakd; |
|
int pp; |
|
int cs; |
|
|
|
CHECK_ERROR0; |
|
comp = ctxt->comp; |
|
switch (op->op) { |
|
case XPATH_OP_END: |
|
return (0); |
|
case XPATH_OP_UNION: |
|
bakd = ctxt->context->doc; |
|
bak = ctxt->context->node; |
|
pp = ctxt->context->proximityPosition; |
|
cs = ctxt->context->contextSize; |
|
total = |
|
xmlXPathCompOpEvalLast(ctxt, &comp->steps[op->ch1], last); |
|
CHECK_ERROR0; |
|
if ((ctxt->value != NULL) |
|
&& (ctxt->value->type == XPATH_NODESET) |
|
&& (ctxt->value->nodesetval != NULL) |
|
&& (ctxt->value->nodesetval->nodeNr >= 1)) { |
|
/* |
|
* limit tree traversing to first node in the result |
|
*/ |
|
if (ctxt->value->nodesetval->nodeNr > 1) |
|
xmlXPathNodeSetSort(ctxt->value->nodesetval); |
|
*last = |
|
ctxt->value->nodesetval->nodeTab[ctxt->value-> |
|
nodesetval->nodeNr - |
|
1]; |
|
} |
|
ctxt->context->doc = bakd; |
|
ctxt->context->node = bak; |
|
ctxt->context->proximityPosition = pp; |
|
ctxt->context->contextSize = cs; |
|
cur = |
|
xmlXPathCompOpEvalLast(ctxt, &comp->steps[op->ch2], last); |
|
CHECK_ERROR0; |
|
if ((ctxt->value != NULL) |
|
&& (ctxt->value->type == XPATH_NODESET) |
|
&& (ctxt->value->nodesetval != NULL) |
|
&& (ctxt->value->nodesetval->nodeNr >= 1)) { /* TODO: NOP ? */ |
|
} |
|
|
|
arg2 = valuePop(ctxt); |
|
arg1 = valuePop(ctxt); |
|
if ((arg1 == NULL) || (arg1->type != XPATH_NODESET) || |
|
(arg2 == NULL) || (arg2->type != XPATH_NODESET)) { |
|
xmlXPathReleaseObject(ctxt->context, arg1); |
|
xmlXPathReleaseObject(ctxt->context, arg2); |
|
XP_ERROR0(XPATH_INVALID_TYPE); |
|
} |
|
|
|
arg1->nodesetval = xmlXPathNodeSetMerge(arg1->nodesetval, |
|
arg2->nodesetval); |
|
valuePush(ctxt, arg1); |
|
xmlXPathReleaseObject(ctxt->context, arg2); |
|
/* optimizer */ |
|
if (total > cur) |
|
xmlXPathCompSwap(op); |
|
return (total + cur); |
|
case XPATH_OP_ROOT: |
|
xmlXPathRoot(ctxt); |
|
return (0); |
|
case XPATH_OP_NODE: |
|
if (op->ch1 != -1) |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
if (op->ch2 != -1) |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch2]); |
|
CHECK_ERROR0; |
|
valuePush(ctxt, xmlXPathCacheNewNodeSet(ctxt->context, |
|
ctxt->context->node)); |
|
return (total); |
|
case XPATH_OP_RESET: |
|
if (op->ch1 != -1) |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
if (op->ch2 != -1) |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch2]); |
|
CHECK_ERROR0; |
|
ctxt->context->node = NULL; |
|
return (total); |
|
case XPATH_OP_COLLECT:{ |
|
if (op->ch1 == -1) |
|
return (0); |
|
|
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
|
|
total += xmlXPathNodeCollectAndTest(ctxt, op, NULL, last, 0); |
|
return (total); |
|
} |
|
case XPATH_OP_VALUE: |
|
valuePush(ctxt, |
|
xmlXPathCacheObjectCopy(ctxt->context, |
|
(xmlXPathObjectPtr) op->value4)); |
|
return (0); |
|
case XPATH_OP_SORT: |
|
if (op->ch1 != -1) |
|
total += |
|
xmlXPathCompOpEvalLast(ctxt, &comp->steps[op->ch1], |
|
last); |
|
CHECK_ERROR0; |
|
if ((ctxt->value != NULL) |
|
&& (ctxt->value->type == XPATH_NODESET) |
|
&& (ctxt->value->nodesetval != NULL) |
|
&& (ctxt->value->nodesetval->nodeNr > 1)) |
|
xmlXPathNodeSetSort(ctxt->value->nodesetval); |
|
return (total); |
|
default: |
|
return (xmlXPathCompOpEval(ctxt, op)); |
|
} |
|
} |
|
|
|
#ifdef XP_OPTIMIZED_FILTER_FIRST |
|
static int |
|
xmlXPathCompOpEvalFilterFirst(xmlXPathParserContextPtr ctxt, |
|
xmlXPathStepOpPtr op, xmlNodePtr * first) |
|
{ |
|
int total = 0; |
|
xmlXPathCompExprPtr comp; |
|
xmlXPathObjectPtr res; |
|
xmlXPathObjectPtr obj; |
|
xmlNodeSetPtr oldset; |
|
xmlNodePtr oldnode; |
|
xmlDocPtr oldDoc; |
|
int i; |
|
|
|
CHECK_ERROR0; |
|
comp = ctxt->comp; |
|
/* |
|
* Optimization for ()[last()] selection i.e. the last elem |
|
*/ |
|
if ((op->ch1 != -1) && (op->ch2 != -1) && |
|
(comp->steps[op->ch1].op == XPATH_OP_SORT) && |
|
(comp->steps[op->ch2].op == XPATH_OP_SORT)) { |
|
int f = comp->steps[op->ch2].ch1; |
|
|
|
if ((f != -1) && |
|
(comp->steps[f].op == XPATH_OP_FUNCTION) && |
|
(comp->steps[f].value5 == NULL) && |
|
(comp->steps[f].value == 0) && |
|
(comp->steps[f].value4 != NULL) && |
|
(xmlStrEqual |
|
(comp->steps[f].value4, BAD_CAST "last"))) { |
|
xmlNodePtr last = NULL; |
|
|
|
total += |
|
xmlXPathCompOpEvalLast(ctxt, |
|
&comp->steps[op->ch1], |
|
&last); |
|
CHECK_ERROR0; |
|
/* |
|
* The nodeset should be in document order, |
|
* Keep only the last value |
|
*/ |
|
if ((ctxt->value != NULL) && |
|
(ctxt->value->type == XPATH_NODESET) && |
|
(ctxt->value->nodesetval != NULL) && |
|
(ctxt->value->nodesetval->nodeTab != NULL) && |
|
(ctxt->value->nodesetval->nodeNr > 1)) { |
|
xmlXPathNodeSetKeepLast(ctxt->value->nodesetval); |
|
*first = *(ctxt->value->nodesetval->nodeTab); |
|
} |
|
return (total); |
|
} |
|
} |
|
|
|
if (op->ch1 != -1) |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
if (op->ch2 == -1) |
|
return (total); |
|
if (ctxt->value == NULL) |
|
return (total); |
|
|
|
#ifdef LIBXML_XPTR_ENABLED |
|
oldnode = ctxt->context->node; |
|
/* |
|
* Hum are we filtering the result of an XPointer expression |
|
*/ |
|
if (ctxt->value->type == XPATH_LOCATIONSET) { |
|
xmlXPathObjectPtr tmp = NULL; |
|
xmlLocationSetPtr newlocset = NULL; |
|
xmlLocationSetPtr oldlocset; |
|
|
|
/* |
|
* Extract the old locset, and then evaluate the result of the |
|
* expression for all the element in the locset. use it to grow |
|
* up a new locset. |
|
*/ |
|
CHECK_TYPE0(XPATH_LOCATIONSET); |
|
obj = valuePop(ctxt); |
|
oldlocset = obj->user; |
|
ctxt->context->node = NULL; |
|
|
|
if ((oldlocset == NULL) || (oldlocset->locNr == 0)) { |
|
ctxt->context->contextSize = 0; |
|
ctxt->context->proximityPosition = 0; |
|
if (op->ch2 != -1) |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch2]); |
|
res = valuePop(ctxt); |
|
if (res != NULL) { |
|
xmlXPathReleaseObject(ctxt->context, res); |
|
} |
|
valuePush(ctxt, obj); |
|
CHECK_ERROR0; |
|
return (total); |
|
} |
|
newlocset = xmlXPtrLocationSetCreate(NULL); |
|
|
|
for (i = 0; i < oldlocset->locNr; i++) { |
|
/* |
|
* Run the evaluation with a node list made of a |
|
* single item in the nodelocset. |
|
*/ |
|
ctxt->context->node = oldlocset->locTab[i]->user; |
|
ctxt->context->contextSize = oldlocset->locNr; |
|
ctxt->context->proximityPosition = i + 1; |
|
if (tmp == NULL) { |
|
tmp = xmlXPathCacheNewNodeSet(ctxt->context, |
|
ctxt->context->node); |
|
} else { |
|
if (xmlXPathNodeSetAddUnique(tmp->nodesetval, |
|
ctxt->context->node) < 0) { |
|
ctxt->error = XPATH_MEMORY_ERROR; |
|
} |
|
} |
|
valuePush(ctxt, tmp); |
|
if (op->ch2 != -1) |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch2]); |
|
if (ctxt->error != XPATH_EXPRESSION_OK) { |
|
xmlXPathFreeObject(obj); |
|
return(0); |
|
} |
|
/* |
|
* The result of the evaluation need to be tested to |
|
* decided whether the filter succeeded or not |
|
*/ |
|
res = valuePop(ctxt); |
|
if (xmlXPathEvaluatePredicateResult(ctxt, res)) { |
|
xmlXPtrLocationSetAdd(newlocset, |
|
xmlXPathCacheObjectCopy(ctxt->context, |
|
oldlocset->locTab[i])); |
|
} |
|
/* |
|
* Cleanup |
|
*/ |
|
if (res != NULL) { |
|
xmlXPathReleaseObject(ctxt->context, res); |
|
} |
|
if (ctxt->value == tmp) { |
|
valuePop(ctxt); |
|
xmlXPathNodeSetClear(tmp->nodesetval, 1); |
|
/* |
|
* REVISIT TODO: Don't create a temporary nodeset |
|
* for everly iteration. |
|
*/ |
|
/* OLD: xmlXPathFreeObject(res); */ |
|
} else |
|
tmp = NULL; |
|
ctxt->context->node = NULL; |
|
/* |
|
* Only put the first node in the result, then leave. |
|
*/ |
|
if (newlocset->locNr > 0) { |
|
*first = (xmlNodePtr) oldlocset->locTab[i]->user; |
|
break; |
|
} |
|
} |
|
if (tmp != NULL) { |
|
xmlXPathReleaseObject(ctxt->context, tmp); |
|
} |
|
/* |
|
* The result is used as the new evaluation locset. |
|
*/ |
|
xmlXPathReleaseObject(ctxt->context, obj); |
|
ctxt->context->node = NULL; |
|
ctxt->context->contextSize = -1; |
|
ctxt->context->proximityPosition = -1; |
|
valuePush(ctxt, xmlXPtrWrapLocationSet(newlocset)); |
|
ctxt->context->node = oldnode; |
|
return (total); |
|
} |
|
#endif /* LIBXML_XPTR_ENABLED */ |
|
|
|
/* |
|
* Extract the old set, and then evaluate the result of the |
|
* expression for all the element in the set. use it to grow |
|
* up a new set. |
|
*/ |
|
CHECK_TYPE0(XPATH_NODESET); |
|
obj = valuePop(ctxt); |
|
oldset = obj->nodesetval; |
|
|
|
oldnode = ctxt->context->node; |
|
oldDoc = ctxt->context->doc; |
|
ctxt->context->node = NULL; |
|
|
|
if ((oldset == NULL) || (oldset->nodeNr == 0)) { |
|
ctxt->context->contextSize = 0; |
|
ctxt->context->proximityPosition = 0; |
|
/* QUESTION TODO: Why was this code commented out? |
|
if (op->ch2 != -1) |
|
total += |
|
xmlXPathCompOpEval(ctxt, |
|
&comp->steps[op->ch2]); |
|
CHECK_ERROR0; |
|
res = valuePop(ctxt); |
|
if (res != NULL) |
|
xmlXPathFreeObject(res); |
|
*/ |
|
valuePush(ctxt, obj); |
|
ctxt->context->node = oldnode; |
|
CHECK_ERROR0; |
|
} else { |
|
xmlNodeSetPtr newset; |
|
xmlXPathObjectPtr tmp = NULL; |
|
/* |
|
* Initialize the new set. |
|
* Also set the xpath document in case things like |
|
* key() evaluation are attempted on the predicate |
|
*/ |
|
newset = xmlXPathNodeSetCreate(NULL); |
|
/* XXX what if xmlXPathNodeSetCreate returned NULL? */ |
|
|
|
for (i = 0; i < oldset->nodeNr; i++) { |
|
/* |
|
* Run the evaluation with a node list made of |
|
* a single item in the nodeset. |
|
*/ |
|
ctxt->context->node = oldset->nodeTab[i]; |
|
if ((oldset->nodeTab[i]->type != XML_NAMESPACE_DECL) && |
|
(oldset->nodeTab[i]->doc != NULL)) |
|
ctxt->context->doc = oldset->nodeTab[i]->doc; |
|
if (tmp == NULL) { |
|
tmp = xmlXPathCacheNewNodeSet(ctxt->context, |
|
ctxt->context->node); |
|
} else { |
|
if (xmlXPathNodeSetAddUnique(tmp->nodesetval, |
|
ctxt->context->node) < 0) { |
|
ctxt->error = XPATH_MEMORY_ERROR; |
|
} |
|
} |
|
valuePush(ctxt, tmp); |
|
ctxt->context->contextSize = oldset->nodeNr; |
|
ctxt->context->proximityPosition = i + 1; |
|
if (op->ch2 != -1) |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch2]); |
|
if (ctxt->error != XPATH_EXPRESSION_OK) { |
|
xmlXPathFreeNodeSet(newset); |
|
xmlXPathFreeObject(obj); |
|
return(0); |
|
} |
|
/* |
|
* The result of the evaluation needs to be tested to |
|
* decide whether the filter succeeded or not |
|
*/ |
|
res = valuePop(ctxt); |
|
if (xmlXPathEvaluatePredicateResult(ctxt, res)) { |
|
if (xmlXPathNodeSetAdd(newset, oldset->nodeTab[i]) < 0) |
|
ctxt->error = XPATH_MEMORY_ERROR; |
|
} |
|
/* |
|
* Cleanup |
|
*/ |
|
if (res != NULL) { |
|
xmlXPathReleaseObject(ctxt->context, res); |
|
} |
|
if (ctxt->value == tmp) { |
|
valuePop(ctxt); |
|
/* |
|
* Don't free the temporary nodeset |
|
* in order to avoid massive recreation inside this |
|
* loop. |
|
*/ |
|
xmlXPathNodeSetClear(tmp->nodesetval, 1); |
|
} else |
|
tmp = NULL; |
|
ctxt->context->node = NULL; |
|
/* |
|
* Only put the first node in the result, then leave. |
|
*/ |
|
if (newset->nodeNr > 0) { |
|
*first = *(newset->nodeTab); |
|
break; |
|
} |
|
} |
|
if (tmp != NULL) { |
|
xmlXPathReleaseObject(ctxt->context, tmp); |
|
} |
|
/* |
|
* The result is used as the new evaluation set. |
|
*/ |
|
xmlXPathReleaseObject(ctxt->context, obj); |
|
ctxt->context->node = NULL; |
|
ctxt->context->contextSize = -1; |
|
ctxt->context->proximityPosition = -1; |
|
/* may want to move this past the '}' later */ |
|
ctxt->context->doc = oldDoc; |
|
valuePush(ctxt, xmlXPathCacheWrapNodeSet(ctxt->context, newset)); |
|
} |
|
ctxt->context->node = oldnode; |
|
return(total); |
|
} |
|
#endif /* XP_OPTIMIZED_FILTER_FIRST */ |
|
|
|
/** |
|
* xmlXPathCompOpEval: |
|
* @ctxt: the XPath parser context with the compiled expression |
|
* @op: an XPath compiled operation |
|
* |
|
* Evaluate the Precompiled XPath operation |
|
* Returns the number of nodes traversed |
|
*/ |
|
static int |
|
xmlXPathCompOpEval(xmlXPathParserContextPtr ctxt, xmlXPathStepOpPtr op) |
|
{ |
|
int total = 0; |
|
int equal, ret; |
|
xmlXPathCompExprPtr comp; |
|
xmlXPathObjectPtr arg1, arg2; |
|
xmlNodePtr bak; |
|
xmlDocPtr bakd; |
|
int pp; |
|
int cs; |
|
|
|
CHECK_ERROR0; |
|
comp = ctxt->comp; |
|
switch (op->op) { |
|
case XPATH_OP_END: |
|
return (0); |
|
case XPATH_OP_AND: |
|
bakd = ctxt->context->doc; |
|
bak = ctxt->context->node; |
|
pp = ctxt->context->proximityPosition; |
|
cs = ctxt->context->contextSize; |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
xmlXPathBooleanFunction(ctxt, 1); |
|
if ((ctxt->value == NULL) || (ctxt->value->boolval == 0)) |
|
return (total); |
|
arg2 = valuePop(ctxt); |
|
ctxt->context->doc = bakd; |
|
ctxt->context->node = bak; |
|
ctxt->context->proximityPosition = pp; |
|
ctxt->context->contextSize = cs; |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch2]); |
|
if (ctxt->error) { |
|
xmlXPathFreeObject(arg2); |
|
return(0); |
|
} |
|
xmlXPathBooleanFunction(ctxt, 1); |
|
arg1 = valuePop(ctxt); |
|
arg1->boolval &= arg2->boolval; |
|
valuePush(ctxt, arg1); |
|
xmlXPathReleaseObject(ctxt->context, arg2); |
|
return (total); |
|
case XPATH_OP_OR: |
|
bakd = ctxt->context->doc; |
|
bak = ctxt->context->node; |
|
pp = ctxt->context->proximityPosition; |
|
cs = ctxt->context->contextSize; |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
xmlXPathBooleanFunction(ctxt, 1); |
|
if ((ctxt->value == NULL) || (ctxt->value->boolval == 1)) |
|
return (total); |
|
arg2 = valuePop(ctxt); |
|
ctxt->context->doc = bakd; |
|
ctxt->context->node = bak; |
|
ctxt->context->proximityPosition = pp; |
|
ctxt->context->contextSize = cs; |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch2]); |
|
if (ctxt->error) { |
|
xmlXPathFreeObject(arg2); |
|
return(0); |
|
} |
|
xmlXPathBooleanFunction(ctxt, 1); |
|
arg1 = valuePop(ctxt); |
|
arg1->boolval |= arg2->boolval; |
|
valuePush(ctxt, arg1); |
|
xmlXPathReleaseObject(ctxt->context, arg2); |
|
return (total); |
|
case XPATH_OP_EQUAL: |
|
bakd = ctxt->context->doc; |
|
bak = ctxt->context->node; |
|
pp = ctxt->context->proximityPosition; |
|
cs = ctxt->context->contextSize; |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
ctxt->context->doc = bakd; |
|
ctxt->context->node = bak; |
|
ctxt->context->proximityPosition = pp; |
|
ctxt->context->contextSize = cs; |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch2]); |
|
CHECK_ERROR0; |
|
if (op->value) |
|
equal = xmlXPathEqualValues(ctxt); |
|
else |
|
equal = xmlXPathNotEqualValues(ctxt); |
|
valuePush(ctxt, xmlXPathCacheNewBoolean(ctxt->context, equal)); |
|
return (total); |
|
case XPATH_OP_CMP: |
|
bakd = ctxt->context->doc; |
|
bak = ctxt->context->node; |
|
pp = ctxt->context->proximityPosition; |
|
cs = ctxt->context->contextSize; |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
ctxt->context->doc = bakd; |
|
ctxt->context->node = bak; |
|
ctxt->context->proximityPosition = pp; |
|
ctxt->context->contextSize = cs; |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch2]); |
|
CHECK_ERROR0; |
|
ret = xmlXPathCompareValues(ctxt, op->value, op->value2); |
|
valuePush(ctxt, xmlXPathCacheNewBoolean(ctxt->context, ret)); |
|
return (total); |
|
case XPATH_OP_PLUS: |
|
bakd = ctxt->context->doc; |
|
bak = ctxt->context->node; |
|
pp = ctxt->context->proximityPosition; |
|
cs = ctxt->context->contextSize; |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
if (op->ch2 != -1) { |
|
ctxt->context->doc = bakd; |
|
ctxt->context->node = bak; |
|
ctxt->context->proximityPosition = pp; |
|
ctxt->context->contextSize = cs; |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch2]); |
|
} |
|
CHECK_ERROR0; |
|
if (op->value == 0) |
|
xmlXPathSubValues(ctxt); |
|
else if (op->value == 1) |
|
xmlXPathAddValues(ctxt); |
|
else if (op->value == 2) |
|
xmlXPathValueFlipSign(ctxt); |
|
else if (op->value == 3) { |
|
CAST_TO_NUMBER; |
|
CHECK_TYPE0(XPATH_NUMBER); |
|
} |
|
return (total); |
|
case XPATH_OP_MULT: |
|
bakd = ctxt->context->doc; |
|
bak = ctxt->context->node; |
|
pp = ctxt->context->proximityPosition; |
|
cs = ctxt->context->contextSize; |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
ctxt->context->doc = bakd; |
|
ctxt->context->node = bak; |
|
ctxt->context->proximityPosition = pp; |
|
ctxt->context->contextSize = cs; |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch2]); |
|
CHECK_ERROR0; |
|
if (op->value == 0) |
|
xmlXPathMultValues(ctxt); |
|
else if (op->value == 1) |
|
xmlXPathDivValues(ctxt); |
|
else if (op->value == 2) |
|
xmlXPathModValues(ctxt); |
|
return (total); |
|
case XPATH_OP_UNION: |
|
bakd = ctxt->context->doc; |
|
bak = ctxt->context->node; |
|
pp = ctxt->context->proximityPosition; |
|
cs = ctxt->context->contextSize; |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
ctxt->context->doc = bakd; |
|
ctxt->context->node = bak; |
|
ctxt->context->proximityPosition = pp; |
|
ctxt->context->contextSize = cs; |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch2]); |
|
CHECK_ERROR0; |
|
|
|
arg2 = valuePop(ctxt); |
|
arg1 = valuePop(ctxt); |
|
if ((arg1 == NULL) || (arg1->type != XPATH_NODESET) || |
|
(arg2 == NULL) || (arg2->type != XPATH_NODESET)) { |
|
xmlXPathReleaseObject(ctxt->context, arg1); |
|
xmlXPathReleaseObject(ctxt->context, arg2); |
|
XP_ERROR0(XPATH_INVALID_TYPE); |
|
} |
|
|
|
if ((arg1->nodesetval == NULL) || |
|
((arg2->nodesetval != NULL) && |
|
(arg2->nodesetval->nodeNr != 0))) |
|
{ |
|
arg1->nodesetval = xmlXPathNodeSetMerge(arg1->nodesetval, |
|
arg2->nodesetval); |
|
} |
|
|
|
valuePush(ctxt, arg1); |
|
xmlXPathReleaseObject(ctxt->context, arg2); |
|
return (total); |
|
case XPATH_OP_ROOT: |
|
xmlXPathRoot(ctxt); |
|
return (total); |
|
case XPATH_OP_NODE: |
|
if (op->ch1 != -1) |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
if (op->ch2 != -1) |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch2]); |
|
CHECK_ERROR0; |
|
valuePush(ctxt, xmlXPathCacheNewNodeSet(ctxt->context, |
|
ctxt->context->node)); |
|
return (total); |
|
case XPATH_OP_RESET: |
|
if (op->ch1 != -1) |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
if (op->ch2 != -1) |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch2]); |
|
CHECK_ERROR0; |
|
ctxt->context->node = NULL; |
|
return (total); |
|
case XPATH_OP_COLLECT:{ |
|
if (op->ch1 == -1) |
|
return (total); |
|
|
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
|
|
total += xmlXPathNodeCollectAndTest(ctxt, op, NULL, NULL, 0); |
|
return (total); |
|
} |
|
case XPATH_OP_VALUE: |
|
valuePush(ctxt, |
|
xmlXPathCacheObjectCopy(ctxt->context, |
|
(xmlXPathObjectPtr) op->value4)); |
|
return (total); |
|
case XPATH_OP_VARIABLE:{ |
|
xmlXPathObjectPtr val; |
|
|
|
if (op->ch1 != -1) |
|
total += |
|
xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
if (op->value5 == NULL) { |
|
val = xmlXPathVariableLookup(ctxt->context, op->value4); |
|
if (val == NULL) |
|
XP_ERROR0(XPATH_UNDEF_VARIABLE_ERROR); |
|
valuePush(ctxt, val); |
|
} else { |
|
const xmlChar *URI; |
|
|
|
URI = xmlXPathNsLookup(ctxt->context, op->value5); |
|
if (URI == NULL) { |
|
xmlGenericError(xmlGenericErrorContext, |
|
"xmlXPathCompOpEval: variable %s bound to undefined prefix %s\n", |
|
(char *) op->value4, (char *)op->value5); |
|
ctxt->error = XPATH_UNDEF_PREFIX_ERROR; |
|
return (total); |
|
} |
|
val = xmlXPathVariableLookupNS(ctxt->context, |
|
op->value4, URI); |
|
if (val == NULL) |
|
XP_ERROR0(XPATH_UNDEF_VARIABLE_ERROR); |
|
valuePush(ctxt, val); |
|
} |
|
return (total); |
|
} |
|
case XPATH_OP_FUNCTION:{ |
|
xmlXPathFunction func; |
|
const xmlChar *oldFunc, *oldFuncURI; |
|
int i; |
|
int frame; |
|
|
|
frame = xmlXPathSetFrame(ctxt); |
|
if (op->ch1 != -1) { |
|
total += |
|
xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
if (ctxt->error != XPATH_EXPRESSION_OK) { |
|
xmlXPathPopFrame(ctxt, frame); |
|
return (total); |
|
} |
|
} |
|
if (ctxt->valueNr < ctxt->valueFrame + op->value) { |
|
xmlGenericError(xmlGenericErrorContext, |
|
"xmlXPathCompOpEval: parameter error\n"); |
|
ctxt->error = XPATH_INVALID_OPERAND; |
|
xmlXPathPopFrame(ctxt, frame); |
|
return (total); |
|
} |
|
for (i = 0; i < op->value; i++) { |
|
if (ctxt->valueTab[(ctxt->valueNr - 1) - i] == NULL) { |
|
xmlGenericError(xmlGenericErrorContext, |
|
"xmlXPathCompOpEval: parameter error\n"); |
|
ctxt->error = XPATH_INVALID_OPERAND; |
|
xmlXPathPopFrame(ctxt, frame); |
|
return (total); |
|
} |
|
} |
|
if (op->cache != NULL) |
|
func = op->cache; |
|
else { |
|
const xmlChar *URI = NULL; |
|
|
|
if (op->value5 == NULL) |
|
func = |
|
xmlXPathFunctionLookup(ctxt->context, |
|
op->value4); |
|
else { |
|
URI = xmlXPathNsLookup(ctxt->context, op->value5); |
|
if (URI == NULL) { |
|
xmlGenericError(xmlGenericErrorContext, |
|
"xmlXPathCompOpEval: function %s bound to undefined prefix %s\n", |
|
(char *)op->value4, (char *)op->value5); |
|
xmlXPathPopFrame(ctxt, frame); |
|
ctxt->error = XPATH_UNDEF_PREFIX_ERROR; |
|
return (total); |
|
} |
|
func = xmlXPathFunctionLookupNS(ctxt->context, |
|
op->value4, URI); |
|
} |
|
if (func == NULL) { |
|
xmlGenericError(xmlGenericErrorContext, |
|
"xmlXPathCompOpEval: function %s not found\n", |
|
(char *)op->value4); |
|
XP_ERROR0(XPATH_UNKNOWN_FUNC_ERROR); |
|
} |
|
op->cache = func; |
|
op->cacheURI = (void *) URI; |
|
} |
|
oldFunc = ctxt->context->function; |
|
oldFuncURI = ctxt->context->functionURI; |
|
ctxt->context->function = op->value4; |
|
ctxt->context->functionURI = op->cacheURI; |
|
func(ctxt, op->value); |
|
ctxt->context->function = oldFunc; |
|
ctxt->context->functionURI = oldFuncURI; |
|
xmlXPathPopFrame(ctxt, frame); |
|
return (total); |
|
} |
|
case XPATH_OP_ARG: |
|
bakd = ctxt->context->doc; |
|
bak = ctxt->context->node; |
|
pp = ctxt->context->proximityPosition; |
|
cs = ctxt->context->contextSize; |
|
if (op->ch1 != -1) { |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
ctxt->context->contextSize = cs; |
|
ctxt->context->proximityPosition = pp; |
|
ctxt->context->node = bak; |
|
ctxt->context->doc = bakd; |
|
CHECK_ERROR0; |
|
} |
|
if (op->ch2 != -1) { |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch2]); |
|
ctxt->context->contextSize = cs; |
|
ctxt->context->proximityPosition = pp; |
|
ctxt->context->node = bak; |
|
ctxt->context->doc = bakd; |
|
CHECK_ERROR0; |
|
} |
|
return (total); |
|
case XPATH_OP_PREDICATE: |
|
case XPATH_OP_FILTER:{ |
|
xmlXPathObjectPtr res; |
|
xmlXPathObjectPtr obj, tmp; |
|
xmlNodeSetPtr newset = NULL; |
|
xmlNodeSetPtr oldset; |
|
xmlNodePtr oldnode; |
|
xmlDocPtr oldDoc; |
|
int i; |
|
|
|
/* |
|
* Optimization for ()[1] selection i.e. the first elem |
|
*/ |
|
if ((op->ch1 != -1) && (op->ch2 != -1) && |
|
#ifdef XP_OPTIMIZED_FILTER_FIRST |
|
/* |
|
* FILTER TODO: Can we assume that the inner processing |
|
* will result in an ordered list if we have an |
|
* XPATH_OP_FILTER? |
|
* What about an additional field or flag on |
|
* xmlXPathObject like @sorted ? This way we wouln'd need |
|
* to assume anything, so it would be more robust and |
|
* easier to optimize. |
|
*/ |
|
((comp->steps[op->ch1].op == XPATH_OP_SORT) || /* 18 */ |
|
(comp->steps[op->ch1].op == XPATH_OP_FILTER)) && /* 17 */ |
|
#else |
|
(comp->steps[op->ch1].op == XPATH_OP_SORT) && |
|
#endif |
|
(comp->steps[op->ch2].op == XPATH_OP_VALUE)) { /* 12 */ |
|
xmlXPathObjectPtr val; |
|
|
|
val = comp->steps[op->ch2].value4; |
|
if ((val != NULL) && (val->type == XPATH_NUMBER) && |
|
(val->floatval == 1.0)) { |
|
xmlNodePtr first = NULL; |
|
|
|
total += |
|
xmlXPathCompOpEvalFirst(ctxt, |
|
&comp->steps[op->ch1], |
|
&first); |
|
CHECK_ERROR0; |
|
/* |
|
* The nodeset should be in document order, |
|
* Keep only the first value |
|
*/ |
|
if ((ctxt->value != NULL) && |
|
(ctxt->value->type == XPATH_NODESET) && |
|
(ctxt->value->nodesetval != NULL) && |
|
(ctxt->value->nodesetval->nodeNr > 1)) |
|
xmlXPathNodeSetClearFromPos(ctxt->value->nodesetval, |
|
1, 1); |
|
return (total); |
|
} |
|
} |
|
/* |
|
* Optimization for ()[last()] selection i.e. the last elem |
|
*/ |
|
if ((op->ch1 != -1) && (op->ch2 != -1) && |
|
(comp->steps[op->ch1].op == XPATH_OP_SORT) && |
|
(comp->steps[op->ch2].op == XPATH_OP_SORT)) { |
|
int f = comp->steps[op->ch2].ch1; |
|
|
|
if ((f != -1) && |
|
(comp->steps[f].op == XPATH_OP_FUNCTION) && |
|
(comp->steps[f].value5 == NULL) && |
|
(comp->steps[f].value == 0) && |
|
(comp->steps[f].value4 != NULL) && |
|
(xmlStrEqual |
|
(comp->steps[f].value4, BAD_CAST "last"))) { |
|
xmlNodePtr last = NULL; |
|
|
|
total += |
|
xmlXPathCompOpEvalLast(ctxt, |
|
&comp->steps[op->ch1], |
|
&last); |
|
CHECK_ERROR0; |
|
/* |
|
* The nodeset should be in document order, |
|
* Keep only the last value |
|
*/ |
|
if ((ctxt->value != NULL) && |
|
(ctxt->value->type == XPATH_NODESET) && |
|
(ctxt->value->nodesetval != NULL) && |
|
(ctxt->value->nodesetval->nodeTab != NULL) && |
|
(ctxt->value->nodesetval->nodeNr > 1)) |
|
xmlXPathNodeSetKeepLast(ctxt->value->nodesetval); |
|
return (total); |
|
} |
|
} |
|
/* |
|
* Process inner predicates first. |
|
* Example "index[parent::book][1]": |
|
* ... |
|
* PREDICATE <-- we are here "[1]" |
|
* PREDICATE <-- process "[parent::book]" first |
|
* SORT |
|
* COLLECT 'parent' 'name' 'node' book |
|
* NODE |
|
* ELEM Object is a number : 1 |
|
*/ |
|
if (op->ch1 != -1) |
|
total += |
|
xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
if (op->ch2 == -1) |
|
return (total); |
|
if (ctxt->value == NULL) |
|
return (total); |
|
|
|
oldnode = ctxt->context->node; |
|
|
|
#ifdef LIBXML_XPTR_ENABLED |
|
/* |
|
* Hum are we filtering the result of an XPointer expression |
|
*/ |
|
if (ctxt->value->type == XPATH_LOCATIONSET) { |
|
xmlLocationSetPtr newlocset = NULL; |
|
xmlLocationSetPtr oldlocset; |
|
|
|
/* |
|
* Extract the old locset, and then evaluate the result of the |
|
* expression for all the element in the locset. use it to grow |
|
* up a new locset. |
|
*/ |
|
CHECK_TYPE0(XPATH_LOCATIONSET); |
|
obj = valuePop(ctxt); |
|
oldlocset = obj->user; |
|
ctxt->context->node = NULL; |
|
|
|
if ((oldlocset == NULL) || (oldlocset->locNr == 0)) { |
|
ctxt->context->contextSize = 0; |
|
ctxt->context->proximityPosition = 0; |
|
if (op->ch2 != -1) |
|
total += |
|
xmlXPathCompOpEval(ctxt, |
|
&comp->steps[op->ch2]); |
|
res = valuePop(ctxt); |
|
if (res != NULL) { |
|
xmlXPathReleaseObject(ctxt->context, res); |
|
} |
|
valuePush(ctxt, obj); |
|
CHECK_ERROR0; |
|
return (total); |
|
} |
|
newlocset = xmlXPtrLocationSetCreate(NULL); |
|
|
|
for (i = 0; i < oldlocset->locNr; i++) { |
|
/* |
|
* Run the evaluation with a node list made of a |
|
* single item in the nodelocset. |
|
*/ |
|
ctxt->context->node = oldlocset->locTab[i]->user; |
|
ctxt->context->contextSize = oldlocset->locNr; |
|
ctxt->context->proximityPosition = i + 1; |
|
tmp = xmlXPathCacheNewNodeSet(ctxt->context, |
|
ctxt->context->node); |
|
valuePush(ctxt, tmp); |
|
|
|
if (op->ch2 != -1) |
|
total += |
|
xmlXPathCompOpEval(ctxt, |
|
&comp->steps[op->ch2]); |
|
if (ctxt->error != XPATH_EXPRESSION_OK) { |
|
xmlXPathFreeObject(obj); |
|
return(0); |
|
} |
|
|
|
/* |
|
* The result of the evaluation need to be tested to |
|
* decided whether the filter succeeded or not |
|
*/ |
|
res = valuePop(ctxt); |
|
if (xmlXPathEvaluatePredicateResult(ctxt, res)) { |
|
xmlXPtrLocationSetAdd(newlocset, |
|
xmlXPathObjectCopy |
|
(oldlocset->locTab[i])); |
|
} |
|
|
|
/* |
|
* Cleanup |
|
*/ |
|
if (res != NULL) { |
|
xmlXPathReleaseObject(ctxt->context, res); |
|
} |
|
if (ctxt->value == tmp) { |
|
res = valuePop(ctxt); |
|
xmlXPathReleaseObject(ctxt->context, res); |
|
} |
|
|
|
ctxt->context->node = NULL; |
|
} |
|
|
|
/* |
|
* The result is used as the new evaluation locset. |
|
*/ |
|
xmlXPathReleaseObject(ctxt->context, obj); |
|
ctxt->context->node = NULL; |
|
ctxt->context->contextSize = -1; |
|
ctxt->context->proximityPosition = -1; |
|
valuePush(ctxt, xmlXPtrWrapLocationSet(newlocset)); |
|
ctxt->context->node = oldnode; |
|
return (total); |
|
} |
|
#endif /* LIBXML_XPTR_ENABLED */ |
|
|
|
/* |
|
* Extract the old set, and then evaluate the result of the |
|
* expression for all the element in the set. use it to grow |
|
* up a new set. |
|
*/ |
|
CHECK_TYPE0(XPATH_NODESET); |
|
obj = valuePop(ctxt); |
|
oldset = obj->nodesetval; |
|
|
|
oldnode = ctxt->context->node; |
|
oldDoc = ctxt->context->doc; |
|
ctxt->context->node = NULL; |
|
|
|
if ((oldset == NULL) || (oldset->nodeNr == 0)) { |
|
ctxt->context->contextSize = 0; |
|
ctxt->context->proximityPosition = 0; |
|
/* |
|
if (op->ch2 != -1) |
|
total += |
|
xmlXPathCompOpEval(ctxt, |
|
&comp->steps[op->ch2]); |
|
CHECK_ERROR0; |
|
res = valuePop(ctxt); |
|
if (res != NULL) |
|
xmlXPathFreeObject(res); |
|
*/ |
|
valuePush(ctxt, obj); |
|
ctxt->context->node = oldnode; |
|
CHECK_ERROR0; |
|
} else { |
|
tmp = NULL; |
|
/* |
|
* Initialize the new set. |
|
* Also set the xpath document in case things like |
|
* key() evaluation are attempted on the predicate |
|
*/ |
|
newset = xmlXPathNodeSetCreate(NULL); |
|
/* |
|
* SPEC XPath 1.0: |
|
* "For each node in the node-set to be filtered, the |
|
* PredicateExpr is evaluated with that node as the |
|
* context node, with the number of nodes in the |
|
* node-set as the context size, and with the proximity |
|
* position of the node in the node-set with respect to |
|
* the axis as the context position;" |
|
* @oldset is the node-set" to be filtered. |
|
* |
|
* SPEC XPath 1.0: |
|
* "only predicates change the context position and |
|
* context size (see [2.4 Predicates])." |
|
* Example: |
|
* node-set context pos |
|
* nA 1 |
|
* nB 2 |
|
* nC 3 |
|
* After applying predicate [position() > 1] : |
|
* node-set context pos |
|
* nB 1 |
|
* nC 2 |
|
* |
|
* removed the first node in the node-set, then |
|
* the context position of the |
|
*/ |
|
for (i = 0; i < oldset->nodeNr; i++) { |
|
/* |
|
* Run the evaluation with a node list made of |
|
* a single item in the nodeset. |
|
*/ |
|
ctxt->context->node = oldset->nodeTab[i]; |
|
if ((oldset->nodeTab[i]->type != XML_NAMESPACE_DECL) && |
|
(oldset->nodeTab[i]->doc != NULL)) |
|
ctxt->context->doc = oldset->nodeTab[i]->doc; |
|
if (tmp == NULL) { |
|
tmp = xmlXPathCacheNewNodeSet(ctxt->context, |
|
ctxt->context->node); |
|
} else { |
|
if (xmlXPathNodeSetAddUnique(tmp->nodesetval, |
|
ctxt->context->node) < 0) { |
|
ctxt->error = XPATH_MEMORY_ERROR; |
|
} |
|
} |
|
valuePush(ctxt, tmp); |
|
ctxt->context->contextSize = oldset->nodeNr; |
|
ctxt->context->proximityPosition = i + 1; |
|
/* |
|
* Evaluate the predicate against the context node. |
|
* Can/should we optimize position() predicates |
|
* here (e.g. "[1]")? |
|
*/ |
|
if (op->ch2 != -1) |
|
total += |
|
xmlXPathCompOpEval(ctxt, |
|
&comp->steps[op->ch2]); |
|
if (ctxt->error != XPATH_EXPRESSION_OK) { |
|
xmlXPathFreeNodeSet(newset); |
|
xmlXPathFreeObject(obj); |
|
return(0); |
|
} |
|
|
|
/* |
|
* The result of the evaluation needs to be tested to |
|
* decide whether the filter succeeded or not |
|
*/ |
|
/* |
|
* OPTIMIZE TODO: Can we use |
|
* xmlXPathNodeSetAdd*Unique()* instead? |
|
*/ |
|
res = valuePop(ctxt); |
|
if (xmlXPathEvaluatePredicateResult(ctxt, res)) { |
|
if (xmlXPathNodeSetAdd(newset, oldset->nodeTab[i]) |
|
< 0) |
|
ctxt->error = XPATH_MEMORY_ERROR; |
|
} |
|
|
|
/* |
|
* Cleanup |
|
*/ |
|
if (res != NULL) { |
|
xmlXPathReleaseObject(ctxt->context, res); |
|
} |
|
if (ctxt->value == tmp) { |
|
valuePop(ctxt); |
|
xmlXPathNodeSetClear(tmp->nodesetval, 1); |
|
/* |
|
* Don't free the temporary nodeset |
|
* in order to avoid massive recreation inside this |
|
* loop. |
|
*/ |
|
} else |
|
tmp = NULL; |
|
ctxt->context->node = NULL; |
|
} |
|
if (tmp != NULL) |
|
xmlXPathReleaseObject(ctxt->context, tmp); |
|
/* |
|
* The result is used as the new evaluation set. |
|
*/ |
|
xmlXPathReleaseObject(ctxt->context, obj); |
|
ctxt->context->node = NULL; |
|
ctxt->context->contextSize = -1; |
|
ctxt->context->proximityPosition = -1; |
|
/* may want to move this past the '}' later */ |
|
ctxt->context->doc = oldDoc; |
|
valuePush(ctxt, |
|
xmlXPathCacheWrapNodeSet(ctxt->context, newset)); |
|
} |
|
ctxt->context->node = oldnode; |
|
return (total); |
|
} |
|
case XPATH_OP_SORT: |
|
if (op->ch1 != -1) |
|
total += xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
if ((ctxt->value != NULL) && |
|
(ctxt->value->type == XPATH_NODESET) && |
|
(ctxt->value->nodesetval != NULL) && |
|
(ctxt->value->nodesetval->nodeNr > 1)) |
|
{ |
|
xmlXPathNodeSetSort(ctxt->value->nodesetval); |
|
} |
|
return (total); |
|
#ifdef LIBXML_XPTR_ENABLED |
|
case XPATH_OP_RANGETO:{ |
|
xmlXPathObjectPtr range; |
|
xmlXPathObjectPtr res, obj; |
|
xmlXPathObjectPtr tmp; |
|
xmlLocationSetPtr newlocset = NULL; |
|
xmlLocationSetPtr oldlocset; |
|
xmlNodeSetPtr oldset; |
|
int i, j; |
|
|
|
if (op->ch1 != -1) { |
|
total += |
|
xmlXPathCompOpEval(ctxt, &comp->steps[op->ch1]); |
|
CHECK_ERROR0; |
|
} |
|
if (ctxt->value == NULL) { |
|
XP_ERROR0(XPATH_INVALID_OPERAND); |
|
} |
|
if (op->ch2 == -1) |
|
return (total); |
|
|
|
if (ctxt->value->type == XPATH_LOCATIONSET) { |
|
/* |
|
* Extract the old locset, and then evaluate the result of the |
|
* expression for all the element in the locset. use it to grow |
|
* up a new locset. |
|
*/ |
|
CHECK_TYPE0(XPATH_LOCATIONSET); |
|
obj = valuePop(ctxt); |
|
oldlocset = obj->user; |
|
|
|
if ((oldlocset == NULL) || (oldlocset->locNr == 0)) { |
|
ctxt->context->node = NULL; |
|
ctxt->context->contextSize = 0; |
|
ctxt->context->proximityPosition = 0; |
|
total += xmlXPathCompOpEval(ctxt,&comp->steps[op->ch2]); |
|
res = valuePop(ctxt); |
|
if (res != NULL) { |
|
xmlXPathReleaseObject(ctxt->context, res); |
|
} |
|
valuePush(ctxt, obj); |
|
CHECK_ERROR0; |
|
return (total); |
|
} |
|
newlocset = xmlXPtrLocationSetCreate(NULL); |
|
|
|
for (i = 0; i < oldlocset->locNr; i++) { |
|
/* |
|
* Run the evaluation with a node list made of a |
|
* single item in the nodelocset. |
|
*/ |
|
ctxt->context->node = oldlocset->locTab[i]->user; |
|
ctxt->context->contextSize = oldlocset->locNr; |
|
ctxt->context->proximityPosition = i + 1; |
|
tmp = xmlXPathCacheNewNodeSet(ctxt->context, |
|
ctxt->context->node); |
|
valuePush(ctxt, tmp); |
|
|
|
if (op->ch2 != -1) |
|
total += |
|
xmlXPathCompOpEval(ctxt, |
|
&comp->steps[op->ch2]); |
|
if (ctxt->error != XPATH_EXPRESSION_OK) { |
|
xmlXPathFreeObject(obj); |
|
return(0); |
|
} |
|
|
|
res = valuePop(ctxt); |
|
if (res->type == XPATH_LOCATIONSET) { |
|
xmlLocationSetPtr rloc = |
|
(xmlLocationSetPtr)res->user; |
|
for (j=0; j<rloc->locNr; j++) { |
|
range = xmlXPtrNewRange( |
|
oldlocset->locTab[i]->user, |
|
oldlocset->locTab[i]->index, |
|
rloc->locTab[j]->user2, |
|
rloc->locTab[j]->index2); |
|
if (range != NULL) { |
|
xmlXPtrLocationSetAdd(newlocset, range); |
|
} |
|
} |
|
} else { |
|
range = xmlXPtrNewRangeNodeObject( |
|
(xmlNodePtr)oldlocset->locTab[i]->user, res); |
|
if (range != NULL) { |
|
xmlXPtrLocationSetAdd(newlocset,range); |
|
} |
|
} |
|
|
|
/* |
|
* Cleanup |
|
*/ |
|
if (res != NULL) { |
|
xmlXPathReleaseObject(ctxt->context, res); |
|
} |
|
if (ctxt->value == tmp) { |
|
res = valuePop(ctxt); |
|
xmlXPathReleaseObject(ctxt->context, res); |
|
} |
|
|
|
ctxt->context->node = NULL; |
|
} |
|
} else { /* Not a location set */ |
|
CHECK_TYPE0(XPATH_NODESET); |
|
obj = valuePop(ctxt); |
|
oldset = obj->nodesetval; |
|
ctxt->context->node = NULL; |
|
|
|
newlocset = xmlXPtrLocationSetCreate(NULL); |
|
|
|
if (oldset != NULL) { |
|
for (i = 0; i < oldset->nodeNr; i++) { |
|
/* |
|
* Run the evaluation with a node list made of a single item |
|
* in the nodeset. |
|
*/ |
|
ctxt->context->node = oldset->nodeTab[i]; |
|
/* |
|
* OPTIMIZE TODO: Avoid recreation for every iteration. |
|
*/ |
|
tmp = xmlXPathCacheNewNodeSet(ctxt->context, |
|
ctxt->context->node); |
|
valuePush(ctxt, tmp); |
|
|
|
if (op->ch2 != -1) |
|
total += |
|
xmlXPathCompOpEval(ctxt, |
|
&comp->steps[op->ch2]); |
|
if (ctxt->error != XPATH_EXPRESSION_OK) { |
|
xmlXPathFreeObject(obj); |
|
return(0); |
|
} |
|
|
|
res = valuePop(ctxt); |
|
range = |
|
xmlXPtrNewRangeNodeObject(oldset->nodeTab[i], |
|
res); |
|
if (range != NULL) { |
|
xmlXPtrLocationSetAdd(newlocset, range); |
|
} |
|
|
|
/* |
|
* Cleanup |
|
*/ |
|
if (res != NULL) { |
|
xmlXPathReleaseObject(ctxt->context, res); |
|
} |
|
if (ctxt->value == tmp) { |
|
res = valuePop(ctxt); |
|
xmlXPathReleaseObject(ctxt->context, res); |
|
} |
|
|
|
ctxt->context->node = NULL; |
|
} |
|
} |
|
} |
|
|
|
/* |
|
* The result is used as the new evaluation set. |
|
*/ |
|
xmlXPathReleaseObject(ctxt->context, obj); |
|
ctxt->context->node = NULL; |
|
ctxt->context->contextSize = -1; |
|
ctxt->context->proximityPosition = -1; |
|
valuePush(ctxt, xmlXPtrWrapLocationSet(newlocset)); |
|
return (total); |
|
} |
|
#endif /* LIBXML_XPTR_ENABLED */ |
|
} |
|
xmlGenericError(xmlGenericErrorContext, |
|
"XPath: unknown precompiled operation %d\n", op->op); |
|
ctxt->error = XPATH_INVALID_OPERAND; |
|
return (total); |
|
} |
|
|
|
/** |
|
* xmlXPathCompOpEvalToBoolean: |
|
* @ctxt: the XPath parser context |
|
* |
|
* Evaluates if the expression evaluates to true. |
|
* |
|
* Returns 1 if true, 0 if false and -1 on API or internal errors. |
|
*/ |
|
static int |
|
xmlXPathCompOpEvalToBoolean(xmlXPathParserContextPtr ctxt, |
|
xmlXPathStepOpPtr op, |
|
int isPredicate) |
|
{ |
|
xmlXPathObjectPtr resObj = NULL; |
|
|
|
start: |
|
/* comp = ctxt->comp; */ |
|
switch (op->op) { |
|
case XPATH_OP_END: |
|
return (0); |
|
case XPATH_OP_VALUE: |
|
resObj = (xmlXPathObjectPtr) op->value4; |
|
if (isPredicate) |
|
return(xmlXPathEvaluatePredicateResult(ctxt, resObj)); |
|
return(xmlXPathCastToBoolean(resObj)); |
|
case XPATH_OP_SORT: |
|
/* |
|
* We don't need sorting for boolean results. Skip this one. |
|
*/ |
|
if (op->ch1 != -1) { |
|
op = &ctxt->comp->steps[op->ch1]; |
|
goto start; |
|
} |
|
return(0); |
|
case XPATH_OP_COLLECT: |
|
if (op->ch1 == -1) |
|
return(0); |
|
|
|
xmlXPathCompOpEval(ctxt, &ctxt->comp->steps[op->ch1]); |
|
if (ctxt->error != XPATH_EXPRESSION_OK) |
|
return(-1); |
|
|
|
xmlXPathNodeCollectAndTest(ctxt, op, NULL, NULL, 1); |
|
if (ctxt->error != XPATH_EXPRESSION_OK) |
|
return(-1); |
|
|
|
resObj = valuePop(ctxt); |
|
if (resObj == NULL) |
|
return(-1); |
|
break; |
|
default: |
|
/* |
|
* Fallback to call xmlXPathCompOpEval(). |
|
*/ |
|
xmlXPathCompOpEval(ctxt, op); |
|
if (ctxt->error != XPATH_EXPRESSION_OK) |
|
return(-1); |
|
|
|
resObj = valuePop(ctxt); |
|
if (resObj == NULL) |
|
return(-1); |
|
break; |
|
} |
|
|
|
if (resObj) { |
|
int res; |
|
|
|
if (resObj->type == XPATH_BOOLEAN) { |
|
res = resObj->boolval; |
|
} else if (isPredicate) { |
|
/* |
|
* For predicates a result of type "number" is handled |
|
* differently: |
|
* SPEC XPath 1.0: |
|
* "If the result is a number, the result will be converted |
|
* to true if the number is equal to the context position |
|
* and will be converted to false otherwise;" |
|
*/ |
|
res = xmlXPathEvaluatePredicateResult(ctxt, resObj); |
|
} else { |
|
res = xmlXPathCastToBoolean(resObj); |
|
} |
|
xmlXPathReleaseObject(ctxt->context, resObj); |
|
return(res); |
|
} |
|
|
|
return(0); |
|
} |
|
|
|
#ifdef XPATH_STREAMING |
|
/** |
|
* xmlXPathRunStreamEval: |
|
* @ctxt: the XPath parser context with the compiled expression |
|
* |
|
* Evaluate the Precompiled Streamable XPath expression in the given context. |
|
*/ |
|
static int |
|
xmlXPathRunStreamEval(xmlXPathContextPtr ctxt, xmlPatternPtr comp, |
|
xmlXPathObjectPtr *resultSeq, int toBool) |
|
{ |
|
int max_depth, min_depth; |
|
int from_root; |
|
int ret, depth; |
|
int eval_all_nodes; |
|
xmlNodePtr cur = NULL, limit = NULL; |
|
xmlStreamCtxtPtr patstream = NULL; |
|
|
|
int nb_nodes = 0; |
|
|
|
if ((ctxt == NULL) || (comp == NULL)) |
|
return(-1); |
|
max_depth = xmlPatternMaxDepth(comp); |
|
if (max_depth == -1) |
|
return(-1); |
|
if (max_depth == -2) |
|
max_depth = 10000; |
|
min_depth = xmlPatternMinDepth(comp); |
|
if (min_depth == -1) |
|
return(-1); |
|
from_root = xmlPatternFromRoot(comp); |
|
if (from_root < 0) |
|
return(-1); |
|
#if 0 |
|
printf("stream eval: depth %d from root %d\n", max_depth, from_root); |
|
#endif |
|
|
|
if (! toBool) { |
|
if (resultSeq == NULL) |
|
return(-1); |
|
*resultSeq = xmlXPathCacheNewNodeSet(ctxt, NULL); |
|
if (*resultSeq == NULL) |
|
return(-1); |
|
} |
|
|
|
/* |
|
* handle the special cases of "/" amd "." being matched |
|
*/ |
|
if (min_depth == 0) { |
|
if (from_root) { |
|
/* Select "/" */ |
|
if (toBool) |
|
return(1); |
|
xmlXPathNodeSetAddUnique((*resultSeq)->nodesetval, |
|
(xmlNodePtr) ctxt->doc); |
|
} else { |
|
/* Select "self::node()" */ |
|
if (toBool) |
|
return(1); |
|
xmlXPathNodeSetAddUnique((*resultSeq)->nodesetval, ctxt->node); |
|
} |
|
} |
|
if (max_depth == 0) { |
|
return(0); |
|
} |
|
|
|
if (from_root) { |
|
cur = (xmlNodePtr)ctxt->doc; |
|
} else if (ctxt->node != NULL) { |
|
switch (ctxt->node->type) { |
|
case XML_ELEMENT_NODE: |
|
case XML_DOCUMENT_NODE: |
|
case XML_DOCUMENT_FRAG_NODE: |
|
case XML_HTML_DOCUMENT_NODE: |
|
#ifdef LIBXML_DOCB_ENABLED |
|
case XML_DOCB_DOCUMENT_NODE: |
|
#endif |
|
cur = ctxt->node; |
|
break; |
|
case XML_ATTRIBUTE_NODE: |
|
case XML_TEXT_NODE: |
|
case XML_CDATA_SECTION_NODE: |
|
case XML_ENTITY_REF_NODE: |
|
case XML_ENTITY_NODE: |
|
case XML_PI_NODE: |
|
case XML_COMMENT_NODE: |
|
case XML_NOTATION_NODE: |
|
case XML_DTD_NODE: |
|
case XML_DOCUMENT_TYPE_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; |
|
} |
|
limit = cur; |
|
} |
|
if (cur == NULL) { |
|
return(0); |
|
} |
|
|
|
patstream = xmlPatternGetStreamCtxt(comp); |
|
if (patstream == NULL) { |
|
/* |
|
* QUESTION TODO: Is this an error? |
|
*/ |
|
return(0); |
|
} |
|
|
|
eval_all_nodes = xmlStreamWantsAnyNode(patstream); |
|
|
|
if (from_root) { |
|
ret = xmlStreamPush(patstream, NULL, NULL); |
|
if (ret < 0) { |
|
} else if (ret == 1) { |
|
if (toBool) |
|
goto return_1; |
|
xmlXPathNodeSetAddUnique((*resultSeq)->nodesetval, cur); |
|
} |
|
} |
|
depth = 0; |
|
goto scan_children; |
|
next_node: |
|
do { |
|
nb_nodes++; |
|
|
|
switch (cur->type) { |
|
case XML_ELEMENT_NODE: |
|
case XML_TEXT_NODE: |
|
case XML_CDATA_SECTION_NODE: |
|
case XML_COMMENT_NODE: |
|
case XML_PI_NODE: |
|
if (cur->type == XML_ELEMENT_NODE) { |
|
ret = xmlStreamPush(patstream, cur->name, |
|
(cur->ns ? cur->ns->href : NULL)); |
|
} else if (eval_all_nodes) |
|
ret = xmlStreamPushNode(patstream, NULL, NULL, cur->type); |
|
else |
|
break; |
|
|
|
if (ret < 0) { |
|
/* NOP. */ |
|
} else if (ret == 1) { |
|
if (toBool) |
|
goto return_1; |
|
if (xmlXPathNodeSetAddUnique((*resultSeq)->nodesetval, cur) |
|
< 0) { |
|
ctxt->lastError.domain = XML_FROM_XPATH; |
|
ctxt->lastError.code = XML_ERR_NO_MEMORY; |
|
} |
|
} |
|
if ((cur->children == NULL) || (depth >= max_depth)) { |
|
ret = xmlStreamPop(patstream); |
|
while (cur->next != NULL) { |
|
cur = cur->next; |
|
if ((cur->type != XML_ENTITY_DECL) && |
|
(cur->type != XML_DTD_NODE)) |
|
goto next_node; |
|
} |
|
} |
|
default: |
|
break; |
|
} |
|
|
|
scan_children: |
|
if (cur->type == XML_NAMESPACE_DECL) break; |
|
if ((cur->children != NULL) && (depth < max_depth)) { |
|
/* |
|
* Do not descend on entities declarations |
|
*/ |
|
if (cur->children->type != XML_ENTITY_DECL) { |
|
cur = cur->children; |
|
depth++; |
|
/* |
|
* Skip DTDs |
|
*/ |
|
if (cur->type != XML_DTD_NODE) |
|
continue; |
|
} |
|
} |
|
|
|
if (cur == limit) |
|
break; |
|
|
|
while (cur->next != NULL) { |
|
cur = cur->next; |
|
if ((cur->type != XML_ENTITY_DECL) && |
|
(cur->type != XML_DTD_NODE)) |
|
goto next_node; |
|
} |
|
|
|
do { |
|
cur = cur->parent; |
|
depth--; |
|
if ((cur == NULL) || (cur == limit)) |
|
goto done; |
|
if (cur->type == XML_ELEMENT_NODE) { |
|
ret = xmlStreamPop(patstream); |
|
} else if ((eval_all_nodes) && |
|
((cur->type == XML_TEXT_NODE) || |
|
(cur->type == XML_CDATA_SECTION_NODE) || |
|
(cur->type == XML_COMMENT_NODE) || |
|
(cur->type == XML_PI_NODE))) |
|
{ |
|
ret = xmlStreamPop(patstream); |
|
} |
|
if (cur->next != NULL) { |
|
cur = cur->next; |
|
break; |
|
} |
|
} while (cur != NULL); |
|
|
|
} while ((cur != NULL) && (depth >= 0)); |
|
|
|
done: |
|
|
|
#if 0 |
|
printf("stream eval: checked %d nodes selected %d\n", |
|
nb_nodes, retObj->nodesetval->nodeNr); |
|
#endif |
|
|
|
if (patstream) |
|
xmlFreeStreamCtxt(patstream); |
|
return(0); |
|
|
|
return_1: |
|
if (patstream) |
|
xmlFreeStreamCtxt(patstream); |
|
return(1); |
|
} |
|
#endif /* XPATH_STREAMING */ |
|
|
|
/** |
|
* xmlXPathRunEval: |
|
* @ctxt: the XPath parser context with the compiled expression |
|
* @toBool: evaluate to a boolean result |
|
* |
|
* Evaluate the Precompiled XPath expression in the given context. |
|
*/ |
|
static int |
|
xmlXPathRunEval(xmlXPathParserContextPtr ctxt, int toBool) |
|
{ |
|
xmlXPathCompExprPtr comp; |
|
|
|
if ((ctxt == NULL) || (ctxt->comp == NULL)) |
|
return(-1); |
|
|
|
if (ctxt->valueTab == NULL) { |
|
/* Allocate the value stack */ |
|
ctxt->valueTab = (xmlXPathObjectPtr *) |
|
xmlMalloc(10 * sizeof(xmlXPathObjectPtr)); |
|
if (ctxt->valueTab == NULL) { |
|
xmlXPathPErrMemory(ctxt, "creating evaluation context\n"); |
|
xmlFree(ctxt); |
|
} |
|
ctxt->valueNr = 0; |
|
ctxt->valueMax = 10; |
|
ctxt->value = NULL; |
|
ctxt->valueFrame = 0; |
|
} |
|
#ifdef XPATH_STREAMING |
|
if (ctxt->comp->stream) { |
|
int res; |
|
|
|
if (toBool) { |
|
/* |
|
* Evaluation to boolean result. |
|
*/ |
|
res = xmlXPathRunStreamEval(ctxt->context, |
|
ctxt->comp->stream, NULL, 1); |
|
if (res != -1) |
|
return(res); |
|
} else { |
|
xmlXPathObjectPtr resObj = NULL; |
|
|
|
/* |
|
* Evaluation to a sequence. |
|
*/ |
|
res = xmlXPathRunStreamEval(ctxt->context, |
|
ctxt->comp->stream, &resObj, 0); |
|
|
|
if ((res != -1) && (resObj != NULL)) { |
|
valuePush(ctxt, resObj); |
|
return(0); |
|
} |
|
if (resObj != NULL) |
|
xmlXPathReleaseObject(ctxt->context, resObj); |
|
} |
|
/* |
|
* QUESTION TODO: This falls back to normal XPath evaluation |
|
* if res == -1. Is this intended? |
|
*/ |
|
} |
|
#endif |
|
comp = ctxt->comp; |
|
if (comp->last < 0) { |
|
xmlGenericError(xmlGenericErrorContext, |
|
"xmlXPathRunEval: last is less than zero\n"); |
|
return(-1); |
|
} |
|
if (toBool) |
|
return(xmlXPathCompOpEvalToBoolean(ctxt, |
|
&comp->steps[comp->last], 0)); |
|
else |
|
xmlXPathCompOpEval(ctxt, &comp->steps[comp->last]); |
|
|
|
return(0); |
|
} |
|
|
|
/************************************************************************ |
|
* * |
|
* Public interfaces * |
|
* * |
|
************************************************************************/ |
|
|
|
/** |
|
* xmlXPathEvalPredicate: |
|
* @ctxt: the XPath context |
|
* @res: the Predicate Expression evaluation result |
|
* |
|
* Evaluate a predicate result for the current node. |
|
* A PredicateExpr is evaluated by evaluating the Expr and converting |
|
* the result to a boolean. If the result is a number, the result will |
|
* be converted to true if the number is equal to the position of the |
|
* context node in the context node list (as returned by the position |
|
* function) and will be converted to false otherwise; if the result |
|
* is not a number, then the result will be converted as if by a call |
|
* to the boolean function. |
|
* |
|
* Returns 1 if predicate is true, 0 otherwise |
|
*/ |
|
int |
|
xmlXPathEvalPredicate(xmlXPathContextPtr ctxt, xmlXPathObjectPtr res) { |
|
if ((ctxt == NULL) || (res == NULL)) return(0); |
|
switch (res->type) { |
|
case XPATH_BOOLEAN: |
|
return(res->boolval); |
|
case XPATH_NUMBER: |
|
return(res->floatval == ctxt->proximityPosition); |
|
case XPATH_NODESET: |
|
case XPATH_XSLT_TREE: |
|
if (res->nodesetval == NULL) |
|
return(0); |
|
return(res->nodesetval->nodeNr != 0); |
|
case XPATH_STRING: |
|
return((res->stringval != NULL) && |
|
(xmlStrlen(res->stringval) != 0)); |
|
default: |
|
STRANGE |
|
} |
|
return(0); |
|
} |
|
|
|
/** |
|
* xmlXPathEvaluatePredicateResult: |
|
* @ctxt: the XPath Parser context |
|
* @res: the Predicate Expression evaluation result |
|
* |
|
* Evaluate a predicate result for the current node. |
|
* A PredicateExpr is evaluated by evaluating the Expr and converting |
|
* the result to a boolean. If the result is a number, the result will |
|
* be converted to true if the number is equal to the position of the |
|
* context node in the context node list (as returned by the position |
|
* function) and will be converted to false otherwise; if the result |
|
* is not a number, then the result will be converted as if by a call |
|
* to the boolean function. |
|
* |
|
* Returns 1 if predicate is true, 0 otherwise |
|
*/ |
|
int |
|
xmlXPathEvaluatePredicateResult(xmlXPathParserContextPtr ctxt, |
|
xmlXPathObjectPtr res) { |
|
if ((ctxt == NULL) || (res == NULL)) return(0); |
|
switch (res->type) { |
|
case XPATH_BOOLEAN: |
|
return(res->boolval); |
|
case XPATH_NUMBER: |
|
#if defined(__BORLANDC__) || (defined(_MSC_VER) && (_MSC_VER == 1200)) |
|
return((res->floatval == ctxt->context->proximityPosition) && |
|
(!xmlXPathIsNaN(res->floatval))); /* MSC pbm Mark Vakoc !*/ |
|
#else |
|
return(res->floatval == ctxt->context->proximityPosition); |
|
#endif |
|
case XPATH_NODESET: |
|
case XPATH_XSLT_TREE: |
|
if (res->nodesetval == NULL) |
|
return(0); |
|
return(res->nodesetval->nodeNr != 0); |
|
case XPATH_STRING: |
|
return((res->stringval != NULL) && (res->stringval[0] != 0)); |
|
#ifdef LIBXML_XPTR_ENABLED |
|
case XPATH_LOCATIONSET:{ |
|
xmlLocationSetPtr ptr = res->user; |
|
if (ptr == NULL) |
|
return(0); |
|
return (ptr->locNr != 0); |
|
} |
|
#endif |
|
default: |
|
STRANGE |
|
} |
|
return(0); |
|
} |
|
|
|
#ifdef XPATH_STREAMING |
|
/** |
|
* xmlXPathTryStreamCompile: |
|
* @ctxt: an XPath context |
|
* @str: the XPath expression |
|
* |
|
* Try to compile the XPath expression as a streamable subset. |
|
* |
|
* Returns the compiled expression or NULL if failed to compile. |
|
*/ |
|
static xmlXPathCompExprPtr |
|
xmlXPathTryStreamCompile(xmlXPathContextPtr ctxt, const xmlChar *str) { |
|
/* |
|
* Optimization: use streaming patterns when the XPath expression can |
|
* be compiled to a stream lookup |
|
*/ |
|
xmlPatternPtr stream; |
|
xmlXPathCompExprPtr comp; |
|
xmlDictPtr dict = NULL; |
|
const xmlChar **namespaces = NULL; |
|
xmlNsPtr ns; |
|
int i, j; |
|
|
|
if ((!xmlStrchr(str, '[')) && (!xmlStrchr(str, '(')) && |
|
(!xmlStrchr(str, '@'))) { |
|
const xmlChar *tmp; |
|
|
|
/* |
|
* We don't try to handle expressions using the verbose axis |
|
* specifiers ("::"), just the simplied form at this point. |
|
* Additionally, if there is no list of namespaces available and |
|
* there's a ":" in the expression, indicating a prefixed QName, |
|
* then we won't try to compile either. xmlPatterncompile() needs |
|
* to have a list of namespaces at compilation time in order to |
|
* compile prefixed name tests. |
|
*/ |
|
tmp = xmlStrchr(str, ':'); |
|
if ((tmp != NULL) && |
|
((ctxt == NULL) || (ctxt->nsNr == 0) || (tmp[1] == ':'))) |
|
return(NULL); |
|
|
|
if (ctxt != NULL) { |
|
dict = ctxt->dict; |
|
if (ctxt->nsNr > 0) { |
|
namespaces = xmlMalloc(2 * (ctxt->nsNr + 1) * sizeof(xmlChar*)); |
|
if (namespaces == NULL) { |
|
xmlXPathErrMemory(ctxt, "allocating namespaces array\n"); |
|
return(NULL); |
|
} |
|
for (i = 0, j = 0; (j < ctxt->nsNr); j++) { |
|
ns = ctxt->namespaces[j]; |
|
namespaces[i++] = ns->href; |
|
namespaces[i++] = ns->prefix; |
|
} |
|
namespaces[i++] = NULL; |
|
namespaces[i] = NULL; |
|
} |
|
} |
|
|
|
stream = xmlPatterncompile(str, dict, XML_PATTERN_XPATH, |
|
&namespaces[0]); |
|
if (namespaces != NULL) { |
|
xmlFree((xmlChar **)namespaces); |
|
} |
|
if ((stream != NULL) && (xmlPatternStreamable(stream) == 1)) { |
|
comp = xmlXPathNewCompExpr(); |
|
if (comp == NULL) { |
|
xmlXPathErrMemory(ctxt, "allocating streamable expression\n"); |
|
return(NULL); |
|
} |
|
comp->stream = stream; |
|
comp->dict = dict; |
|
if (comp->dict) |
|
xmlDictReference(comp->dict); |
|
return(comp); |
|
} |
|
xmlFreePattern(stream); |
|
} |
|
return(NULL); |
|
} |
|
#endif /* XPATH_STREAMING */ |
|
|
|
static void |
|
xmlXPathOptimizeExpression(xmlXPathCompExprPtr comp, xmlXPathStepOpPtr op) |
|
{ |
|
/* |
|
* Try to rewrite "descendant-or-self::node()/foo" to an optimized |
|
* internal representation. |
|
*/ |
|
|
|
if ((op->op == XPATH_OP_COLLECT /* 11 */) && |
|
(op->ch1 != -1) && |
|
(op->ch2 == -1 /* no predicate */)) |
|
{ |
|
xmlXPathStepOpPtr prevop = &comp->steps[op->ch1]; |
|
|
|
if ((prevop->op == XPATH_OP_COLLECT /* 11 */) && |
|
((xmlXPathAxisVal) prevop->value == |
|
AXIS_DESCENDANT_OR_SELF) && |
|
(prevop->ch2 == -1) && |
|
((xmlXPathTestVal) prevop->value2 == NODE_TEST_TYPE) && |
|
((xmlXPathTypeVal) prevop->value3 == NODE_TYPE_NODE)) |
|
{ |
|
/* |
|
* This is a "descendant-or-self::node()" without predicates. |
|
* Try to eliminate it. |
|
*/ |
|
|
|
switch ((xmlXPathAxisVal) op->value) { |
|
case AXIS_CHILD: |
|
case AXIS_DESCENDANT: |
|
/* |
|
* Convert "descendant-or-self::node()/child::" or |
|
* "descendant-or-self::node()/descendant::" to |
|
* "descendant::" |
|
*/ |
|
op->ch1 = prevop->ch1; |
|
op->value = AXIS_DESCENDANT; |
|
break; |
|
case AXIS_SELF: |
|
case AXIS_DESCENDANT_OR_SELF: |
|
/* |
|
* Convert "descendant-or-self::node()/self::" or |
|
* "descendant-or-self::node()/descendant-or-self::" to |
|
* to "descendant-or-self::" |
|
*/ |
|
op->ch1 = prevop->ch1; |
|
op->value = AXIS_DESCENDANT_OR_SELF; |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
} |
|
|
|
/* OP_VALUE has invalid ch1. */ |
|
if (op->op == XPATH_OP_VALUE) |
|
return; |
|
|
|
/* Recurse */ |
|
if (op->ch1 != -1) |
|
xmlXPathOptimizeExpression(comp, &comp->steps[op->ch1]); |
|
if (op->ch2 != -1) |
|
xmlXPathOptimizeExpression(comp, &comp->steps[op->ch2]); |
|
} |
|
|
|
/** |
|
* xmlXPathCtxtCompile: |
|
* @ctxt: an XPath context |
|
* @str: the XPath expression |
|
* |
|
* Compile an XPath expression |
|
* |
|
* Returns the xmlXPathCompExprPtr resulting from the compilation or NULL. |
|
* the caller has to free the object. |
|
*/ |
|
xmlXPathCompExprPtr |
|
xmlXPathCtxtCompile(xmlXPathContextPtr ctxt, const xmlChar *str) { |
|
xmlXPathParserContextPtr pctxt; |
|
xmlXPathCompExprPtr comp; |
|
|
|
#ifdef XPATH_STREAMING |
|
comp = xmlXPathTryStreamCompile(ctxt, str); |
|
if (comp != NULL) |
|
return(comp); |
|
#endif |
|
|
|
xmlXPathInit(); |
|
|
|
pctxt = xmlXPathNewParserContext(str, ctxt); |
|
if (pctxt == NULL) |
|
return NULL; |
|
xmlXPathCompileExpr(pctxt, 1); |
|
|
|
if( pctxt->error != XPATH_EXPRESSION_OK ) |
|
{ |
|
xmlXPathFreeParserContext(pctxt); |
|
return(NULL); |
|
} |
|
|
|
if (*pctxt->cur != 0) { |
|
/* |
|
* aleksey: in some cases this line prints *second* error message |
|
* (see bug #78858) and probably this should be fixed. |
|
* However, we are not sure that all error messages are printed |
|
* out in other places. It's not critical so we leave it as-is for now |
|
*/ |
|
xmlXPatherror(pctxt, __FILE__, __LINE__, XPATH_EXPR_ERROR); |
|
comp = NULL; |
|
} else { |
|
comp = pctxt->comp; |
|
pctxt->comp = NULL; |
|
} |
|
xmlXPathFreeParserContext(pctxt); |
|
|
|
if (comp != NULL) { |
|
comp->expr = xmlStrdup(str); |
|
#ifdef DEBUG_EVAL_COUNTS |
|
comp->string = xmlStrdup(str); |
|
comp->nb = 0; |
|
#endif |
|
if ((comp->nbStep > 1) && (comp->last >= 0)) { |
|
xmlXPathOptimizeExpression(comp, &comp->steps[comp->last]); |
|
} |
|
} |
|
return(comp); |
|
} |
|
|
|
/** |
|
* xmlXPathCompile: |
|
* @str: the XPath expression |
|
* |
|
* Compile an XPath expression |
|
* |
|
* Returns the xmlXPathCompExprPtr resulting from the compilation or NULL. |
|
* the caller has to free the object. |
|
*/ |
|
xmlXPathCompExprPtr |
|
xmlXPathCompile(const xmlChar *str) { |
|
return(xmlXPathCtxtCompile(NULL, str)); |
|
} |
|
|
|
/** |
|
* xmlXPathCompiledEvalInternal: |
|
* @comp: the compiled XPath expression |
|
* @ctxt: the XPath context |
|
* @resObj: the resulting XPath object or NULL |
|
* @toBool: 1 if only a boolean result is requested |
|
* |
|
* Evaluate the Precompiled XPath expression in the given context. |
|
* The caller has to free @resObj. |
|
* |
|
* Returns the xmlXPathObjectPtr resulting from the evaluation or NULL. |
|
* the caller has to free the object. |
|
*/ |
|
static int |
|
xmlXPathCompiledEvalInternal(xmlXPathCompExprPtr comp, |
|
xmlXPathContextPtr ctxt, |
|
xmlXPathObjectPtr *resObjPtr, |
|
int toBool) |
|
{ |
|
xmlXPathParserContextPtr pctxt; |
|
xmlXPathObjectPtr resObj; |
|
#ifndef LIBXML_THREAD_ENABLED |
|
static int reentance = 0; |
|
#endif |
|
int res; |
|
|
|
CHECK_CTXT_NEG(ctxt) |
|
|
|
if (comp == NULL) |
|
return(-1); |
|
xmlXPathInit(); |
|
|
|
#ifndef LIBXML_THREAD_ENABLED |
|
reentance++; |
|
if (reentance > 1) |
|
xmlXPathDisableOptimizer = 1; |
|
#endif |
|
|
|
#ifdef DEBUG_EVAL_COUNTS |
|
comp->nb++; |
|
if ((comp->string != NULL) && (comp->nb > 100)) { |
|
fprintf(stderr, "100 x %s\n", comp->string); |
|
comp->nb = 0; |
|
} |
|
#endif |
|
pctxt = xmlXPathCompParserContext(comp, ctxt); |
|
res = xmlXPathRunEval(pctxt, toBool); |
|
|
|
if (pctxt->error != XPATH_EXPRESSION_OK) { |
|
resObj = NULL; |
|
} else { |
|
resObj = valuePop(pctxt); |
|
if (resObj == NULL) { |
|
if (!toBool) |
|
xmlGenericError(xmlGenericErrorContext, |
|
"xmlXPathCompiledEval: No result on the stack.\n"); |
|
} else if (pctxt->valueNr > 0) { |
|
xmlGenericError(xmlGenericErrorContext, |
|
"xmlXPathCompiledEval: %d object(s) left on the stack.\n", |
|
pctxt->valueNr); |
|
} |
|
} |
|
|
|
if (resObjPtr) |
|
*resObjPtr = resObj; |
|
else |
|
xmlXPathReleaseObject(ctxt, resObj); |
|
|
|
pctxt->comp = NULL; |
|
xmlXPathFreeParserContext(pctxt); |
|
#ifndef LIBXML_THREAD_ENABLED |
|
reentance--; |
|
#endif |
|
|
|
return(res); |
|
} |
|
|
|
/** |
|
* xmlXPathCompiledEval: |
|
* @comp: the compiled XPath expression |
|
* @ctx: the XPath context |
|
* |
|
* Evaluate the Precompiled XPath expression in the given context. |
|
* |
|
* Returns the xmlXPathObjectPtr resulting from the evaluation or NULL. |
|
* the caller has to free the object. |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathCompiledEval(xmlXPathCompExprPtr comp, xmlXPathContextPtr ctx) |
|
{ |
|
xmlXPathObjectPtr res = NULL; |
|
|
|
xmlXPathCompiledEvalInternal(comp, ctx, &res, 0); |
|
return(res); |
|
} |
|
|
|
/** |
|
* xmlXPathCompiledEvalToBoolean: |
|
* @comp: the compiled XPath expression |
|
* @ctxt: the XPath context |
|
* |
|
* Applies the XPath boolean() function on the result of the given |
|
* compiled expression. |
|
* |
|
* Returns 1 if the expression evaluated to true, 0 if to false and |
|
* -1 in API and internal errors. |
|
*/ |
|
int |
|
xmlXPathCompiledEvalToBoolean(xmlXPathCompExprPtr comp, |
|
xmlXPathContextPtr ctxt) |
|
{ |
|
return(xmlXPathCompiledEvalInternal(comp, ctxt, NULL, 1)); |
|
} |
|
|
|
/** |
|
* xmlXPathEvalExpr: |
|
* @ctxt: the XPath Parser context |
|
* |
|
* Parse and evaluate an XPath expression in the given context, |
|
* then push the result on the context stack |
|
*/ |
|
void |
|
xmlXPathEvalExpr(xmlXPathParserContextPtr ctxt) { |
|
#ifdef XPATH_STREAMING |
|
xmlXPathCompExprPtr comp; |
|
#endif |
|
|
|
if (ctxt == NULL) return; |
|
|
|
#ifdef XPATH_STREAMING |
|
comp = xmlXPathTryStreamCompile(ctxt->context, ctxt->base); |
|
if (comp != NULL) { |
|
if (ctxt->comp != NULL) |
|
xmlXPathFreeCompExpr(ctxt->comp); |
|
ctxt->comp = comp; |
|
} else |
|
#endif |
|
{ |
|
xmlXPathCompileExpr(ctxt, 1); |
|
CHECK_ERROR; |
|
|
|
/* Check for trailing characters. */ |
|
if (*ctxt->cur != 0) |
|
XP_ERROR(XPATH_EXPR_ERROR); |
|
|
|
if ((ctxt->comp->nbStep > 1) && (ctxt->comp->last >= 0)) |
|
xmlXPathOptimizeExpression(ctxt->comp, |
|
&ctxt->comp->steps[ctxt->comp->last]); |
|
} |
|
|
|
xmlXPathRunEval(ctxt, 0); |
|
} |
|
|
|
/** |
|
* xmlXPathEval: |
|
* @str: the XPath expression |
|
* @ctx: the XPath context |
|
* |
|
* Evaluate the XPath Location Path in the given context. |
|
* |
|
* Returns the xmlXPathObjectPtr resulting from the evaluation or NULL. |
|
* the caller has to free the object. |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathEval(const xmlChar *str, xmlXPathContextPtr ctx) { |
|
xmlXPathParserContextPtr ctxt; |
|
xmlXPathObjectPtr res; |
|
|
|
CHECK_CTXT(ctx) |
|
|
|
xmlXPathInit(); |
|
|
|
ctxt = xmlXPathNewParserContext(str, ctx); |
|
if (ctxt == NULL) |
|
return NULL; |
|
xmlXPathEvalExpr(ctxt); |
|
|
|
if (ctxt->error != XPATH_EXPRESSION_OK) { |
|
res = NULL; |
|
} else { |
|
res = valuePop(ctxt); |
|
if (res == NULL) { |
|
xmlGenericError(xmlGenericErrorContext, |
|
"xmlXPathCompiledEval: No result on the stack.\n"); |
|
} else if (ctxt->valueNr > 0) { |
|
xmlGenericError(xmlGenericErrorContext, |
|
"xmlXPathCompiledEval: %d object(s) left on the stack.\n", |
|
ctxt->valueNr); |
|
} |
|
} |
|
|
|
xmlXPathFreeParserContext(ctxt); |
|
return(res); |
|
} |
|
|
|
/** |
|
* xmlXPathSetContextNode: |
|
* @node: the node to to use as the context node |
|
* @ctx: the XPath context |
|
* |
|
* Sets 'node' as the context node. The node must be in the same |
|
* document as that associated with the context. |
|
* |
|
* Returns -1 in case of error or 0 if successful |
|
*/ |
|
int |
|
xmlXPathSetContextNode(xmlNodePtr node, xmlXPathContextPtr ctx) { |
|
if ((node == NULL) || (ctx == NULL)) |
|
return(-1); |
|
|
|
if (node->doc == ctx->doc) { |
|
ctx->node = node; |
|
return(0); |
|
} |
|
return(-1); |
|
} |
|
|
|
/** |
|
* xmlXPathNodeEval: |
|
* @node: the node to to use as the context node |
|
* @str: the XPath expression |
|
* @ctx: the XPath context |
|
* |
|
* Evaluate the XPath Location Path in the given context. The node 'node' |
|
* is set as the context node. The context node is not restored. |
|
* |
|
* Returns the xmlXPathObjectPtr resulting from the evaluation or NULL. |
|
* the caller has to free the object. |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathNodeEval(xmlNodePtr node, const xmlChar *str, xmlXPathContextPtr ctx) { |
|
if (str == NULL) |
|
return(NULL); |
|
if (xmlXPathSetContextNode(node, ctx) < 0) |
|
return(NULL); |
|
return(xmlXPathEval(str, ctx)); |
|
} |
|
|
|
/** |
|
* xmlXPathEvalExpression: |
|
* @str: the XPath expression |
|
* @ctxt: the XPath context |
|
* |
|
* Alias for xmlXPathEval(). |
|
* |
|
* Returns the xmlXPathObjectPtr resulting from the evaluation or NULL. |
|
* the caller has to free the object. |
|
*/ |
|
xmlXPathObjectPtr |
|
xmlXPathEvalExpression(const xmlChar *str, xmlXPathContextPtr ctxt) { |
|
return(xmlXPathEval(str, ctxt)); |
|
} |
|
|
|
/************************************************************************ |
|
* * |
|
* Extra functions not pertaining to the XPath spec * |
|
* * |
|
************************************************************************/ |
|
/** |
|
* xmlXPathEscapeUriFunction: |
|
* @ctxt: the XPath Parser context |
|
* @nargs: the number of arguments |
|
* |
|
* Implement the escape-uri() XPath function |
|
* string escape-uri(string $str, bool $escape-reserved) |
|
* |
|
* This function applies the URI escaping rules defined in section 2 of [RFC |
|
* 2396] to the string supplied as $uri-part, which typically represents all |
|
* or part of a URI. The effect of the function is to replace any special |
|
* character in the string by an escape sequence of the form %xx%yy..., |
|
* where xxyy... is the hexadecimal representation of the octets used to |
|
* represent the character in UTF-8. |
|
* |
|
* The set of characters that are escaped depends on the setting of the |
|
* boolean argument $escape-reserved. |
|
* |
|
* If $escape-reserved is true, all characters are escaped other than lower |
|
* case letters a-z, upper case letters A-Z, digits 0-9, and the characters |
|
* referred to in [RFC 2396] as "marks": specifically, "-" | "_" | "." | "!" |
|
* | "~" | "*" | "'" | "(" | ")". The "%" character itself is escaped only |
|
* if it is not followed by two hexadecimal digits (that is, 0-9, a-f, and |
|
* A-F). |
|
* |
|
* If $escape-reserved is false, the behavior differs in that characters |
|
* referred to in [RFC 2396] as reserved characters are not escaped. These |
|
* characters are ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ",". |
|
* |
|
* [RFC 2396] does not define whether escaped URIs should use lower case or |
|
* upper case for hexadecimal digits. To ensure that escaped URIs can be |
|
* compared using string comparison functions, this function must always use |
|
* the upper-case letters A-F. |
|
* |
|
* Generally, $escape-reserved should be set to true when escaping a string |
|
* that is to form a single part of a URI, and to false when escaping an |
|
* entire URI or URI reference. |
|
* |
|
* In the case of non-ascii characters, the string is encoded according to |
|
* utf-8 and then converted according to RFC 2396. |
|
* |
|
* Examples |
|
* xf:escape-uri ("gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles#ocean"), true()) |
|
* returns "gopher%3A%2F%2Fspinaltap.micro.umn.edu%2F00%2FWeather%2FCalifornia%2FLos%20Angeles%23ocean" |
|
* xf:escape-uri ("gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles#ocean"), false()) |
|
* returns "gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles%23ocean" |
|
* |
|
*/ |
|
static void |
|
xmlXPathEscapeUriFunction(xmlXPathParserContextPtr ctxt, int nargs) { |
|
xmlXPathObjectPtr str; |
|
int escape_reserved; |
|
xmlBufPtr target; |
|
xmlChar *cptr; |
|
xmlChar escape[4]; |
|
|
|
CHECK_ARITY(2); |
|
|
|
escape_reserved = xmlXPathPopBoolean(ctxt); |
|
|
|
CAST_TO_STRING; |
|
str = valuePop(ctxt); |
|
|
|
target = xmlBufCreate(); |
|
|
|
escape[0] = '%'; |
|
escape[3] = 0; |
|
|
|
if (target) { |
|
for (cptr = str->stringval; *cptr; cptr++) { |
|
if ((*cptr >= 'A' && *cptr <= 'Z') || |
|
(*cptr >= 'a' && *cptr <= 'z') || |
|
(*cptr >= '0' && *cptr <= '9') || |
|
*cptr == '-' || *cptr == '_' || *cptr == '.' || |
|
*cptr == '!' || *cptr == '~' || *cptr == '*' || |
|
*cptr == '\''|| *cptr == '(' || *cptr == ')' || |
|
(*cptr == '%' && |
|
((cptr[1] >= 'A' && cptr[1] <= 'F') || |
|
(cptr[1] >= 'a' && cptr[1] <= 'f') || |
|
(cptr[1] >= '0' && cptr[1] <= '9')) && |
|
((cptr[2] >= 'A' && cptr[2] <= 'F') || |
|
(cptr[2] >= 'a' && cptr[2] <= 'f') || |
|
(cptr[2] >= '0' && cptr[2] <= '9'))) || |
|
(!escape_reserved && |
|
(*cptr == ';' || *cptr == '/' || *cptr == '?' || |
|
*cptr == ':' || *cptr == '@' || *cptr == '&' || |
|
*cptr == '=' || *cptr == '+' || *cptr == '$' || |
|
*cptr == ','))) { |
|
xmlBufAdd(target, cptr, 1); |
|
} else { |
|
if ((*cptr >> 4) < 10) |
|
escape[1] = '0' + (*cptr >> 4); |
|
else |
|
escape[1] = 'A' - 10 + (*cptr >> 4); |
|
if ((*cptr & 0xF) < 10) |
|
escape[2] = '0' + (*cptr & 0xF); |
|
else |
|
escape[2] = 'A' - 10 + (*cptr & 0xF); |
|
|
|
xmlBufAdd(target, &escape[0], 3); |
|
} |
|
} |
|
} |
|
valuePush(ctxt, xmlXPathCacheNewString(ctxt->context, |
|
xmlBufContent(target))); |
|
xmlBufFree(target); |
|
xmlXPathReleaseObject(ctxt->context, str); |
|
} |
|
|
|
/** |
|
* xmlXPathRegisterAllFunctions: |
|
* @ctxt: the XPath context |
|
* |
|
* Registers all default XPath functions in this context |
|
*/ |
|
void |
|
xmlXPathRegisterAllFunctions(xmlXPathContextPtr ctxt) |
|
{ |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"boolean", |
|
xmlXPathBooleanFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"ceiling", |
|
xmlXPathCeilingFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"count", |
|
xmlXPathCountFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"concat", |
|
xmlXPathConcatFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"contains", |
|
xmlXPathContainsFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"id", |
|
xmlXPathIdFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"false", |
|
xmlXPathFalseFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"floor", |
|
xmlXPathFloorFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"last", |
|
xmlXPathLastFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"lang", |
|
xmlXPathLangFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"local-name", |
|
xmlXPathLocalNameFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"not", |
|
xmlXPathNotFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"name", |
|
xmlXPathNameFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"namespace-uri", |
|
xmlXPathNamespaceURIFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"normalize-space", |
|
xmlXPathNormalizeFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"number", |
|
xmlXPathNumberFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"position", |
|
xmlXPathPositionFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"round", |
|
xmlXPathRoundFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"string", |
|
xmlXPathStringFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"string-length", |
|
xmlXPathStringLengthFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"starts-with", |
|
xmlXPathStartsWithFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"substring", |
|
xmlXPathSubstringFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"substring-before", |
|
xmlXPathSubstringBeforeFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"substring-after", |
|
xmlXPathSubstringAfterFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"sum", |
|
xmlXPathSumFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"true", |
|
xmlXPathTrueFunction); |
|
xmlXPathRegisterFunc(ctxt, (const xmlChar *)"translate", |
|
xmlXPathTranslateFunction); |
|
|
|
xmlXPathRegisterFuncNS(ctxt, (const xmlChar *)"escape-uri", |
|
(const xmlChar *)"http://www.w3.org/2002/08/xquery-functions", |
|
xmlXPathEscapeUriFunction); |
|
} |
|
|
|
#endif /* LIBXML_XPATH_ENABLED */ |
|
#define bottom_xpath |
|
#include "elfgcchack.h"
|
|
|