implemented streaming of RelaxNG (when possible) on top of the xmlReader
* relaxng.c xmlreader.c xmllint.c include/libxml/relaxng.h
include/libxml/xmlreader.h: implemented streaming of
RelaxNG (when possible) on top of the xmlReader interface,
provided it as xmllint --stream --relaxng .rng .xml
This seems to mostly work.
* Makefile.am: updated to test RelaxNG streaming
Daniel
diff --git a/ChangeLog b/ChangeLog
index c355fc9..c82ea6f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+Wed Apr 16 01:28:15 CEST 2003 Daniel Veillard <daniel@veillard.com>
+
+ * relaxng.c xmlreader.c xmllint.c include/libxml/relaxng.h
+ include/libxml/xmlreader.h: implemented streaming of
+ RelaxNG (when possible) on top of the xmlReader interface,
+ provided it as xmllint --stream --relaxng .rng .xml
+ This seems to mostly work.
+ * Makefile.am: updated to test RelaxNG streaming
+
Mon Apr 14 18:08:33 CEST 2003 Daniel Veillard <daniel@veillard.com>
* relaxng.c include/libxml/relaxng.h: integrated the regexp
diff --git a/Makefile.am b/Makefile.am
index cb482f6..216af1b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -733,6 +733,33 @@
rm res.$$name err.$$name ; \
fi ; fi ; \
done; done)
+ @echo "##"
+ @echo "## Relax-NG streaming regression tests"
+ @echo "##"
+ -@(for i in $(srcdir)/test/relaxng/*.rng ; do \
+ name=`basename $$i | sed 's+\.rng++'`; \
+ for j in $(srcdir)/test/relaxng/"$$name"_*.xml ; do \
+ if [ -f $$j ] ; then \
+ xno=`basename $$j | sed 's+.*_\(.*\).xml+\1+'`; \
+ if [ ! -f $(srcdir)/result/relaxng/"$$name"_"$$xno" ]; \
+ then \
+ echo New test file "$$name"_"$$xno" ; \
+ $(CHECKER) $(top_builddir)/xmllint$(EXEEXT) --noout --relaxng $$i $$j \
+ > $(srcdir)/result/relaxng/"$$name"_"$$xno" \
+ 2> $(srcdir)/result/relaxng/"$$name"_"$$xno".err; \
+ else \
+ echo Testing "$$name"_"$$xno" ; \
+ $(CHECKER) $(top_builddir)/xmllint$(EXEEXT) --noout --stream --relaxng $$i $$j \
+ > res.$$name 2> err.$$name;\
+ grep "MORY ALLO" .memdump | grep -v "MEMORY ALLOCATED : 0";\
+ diff $(srcdir)/result/relaxng/"$$name"_"$$xno" \
+ res.$$name;\
+ diff $(srcdir)/result/relaxng/"$$name"_"$$xno".err \
+ err.$$name | grep -v "error detected at";\
+ grep Unimplemented err.$$name; \
+ rm res.$$name err.$$name ; \
+ fi ; fi ; \
+ done; done)
dist-hook: libxml2.spec
-cp libxml2.spec $(distdir)
diff --git a/include/libxml/relaxng.h b/include/libxml/relaxng.h
index 3e8de7ab..a21f92c 100644
--- a/include/libxml/relaxng.h
+++ b/include/libxml/relaxng.h
@@ -104,4 +104,19 @@
int xmlRelaxNGValidateDoc (xmlRelaxNGValidCtxtPtr ctxt,
xmlDocPtr doc);
void xmlRelaxNGCleanupTypes (void);
+/*
+ * Interfaces for progressive validation when possible
+ */
+int xmlRelaxNGValidatePushElement (xmlRelaxNGValidCtxtPtr ctxt,
+ xmlDocPtr doc,
+ xmlNodePtr elem);
+int xmlRelaxNGValidatePushCData (xmlRelaxNGValidCtxtPtr ctxt,
+ const xmlChar *data,
+ int len);
+int xmlRelaxNGValidatePopElement (xmlRelaxNGValidCtxtPtr ctxt,
+ xmlDocPtr doc,
+ xmlNodePtr elem);
+int xmlRelaxNGValidateFullElement (xmlRelaxNGValidCtxtPtr ctxt,
+ xmlDocPtr doc,
+ xmlNodePtr elem);
#endif /* __XML_RELAX_NG__ */
diff --git a/include/libxml/xmlreader.h b/include/libxml/xmlreader.h
index de98061..1ced8b8 100644
--- a/include/libxml/xmlreader.h
+++ b/include/libxml/xmlreader.h
@@ -109,6 +109,10 @@
xmlNodePtr xmlTextReaderExpand (xmlTextReaderPtr reader);
int xmlTextReaderNext (xmlTextReaderPtr reader);
int xmlTextReaderIsValid (xmlTextReaderPtr reader);
+#ifdef LIBXML_SCHEMAS_ENABLED
+int xmlTextReaderRelaxNGValidate (xmlTextReaderPtr reader,
+ const char *rng);
+#endif
/*
* Error handling extensions
diff --git a/relaxng.c b/relaxng.c
index 4a00024..cb3c130 100644
--- a/relaxng.c
+++ b/relaxng.c
@@ -57,8 +57,8 @@
/* #define DEBUG_INCLUDE */
/* #define DEBUG_ERROR 1 */
/* #define DEBUG_COMPILE 1 */
+/* #define DEBUG_PROGRESSIVE 1 */
-#define UNBOUNDED (1 << 30)
#define MAX_ERROR 5
#define TODO \
@@ -356,6 +356,17 @@
int freeStatesNr;
int freeStatesMax;
xmlRelaxNGStatesPtr *freeStates; /* the pool of free state groups */
+
+ /*
+ * This is used for "progressive" validation
+ */
+ xmlRegExecCtxtPtr elem; /* the current element regexp */
+ int elemNr; /* the number of element validated */
+ int elemMax; /* the max depth of elements */
+ xmlRegExecCtxtPtr *elemTab; /* the stack of regexp runtime */
+ int pstate; /* progressive state */
+ xmlNodePtr pnode; /* the current node */
+ xmlRelaxNGDefinePtr pdef; /* the non-streamable definition */
};
/**
@@ -7095,8 +7106,19 @@
/*
* try to compile (parts of) the schemas
*/
- if (ctxt->grammar != NULL)
+ if ((ctxt->grammar != NULL) && (ctxt->grammar->start != NULL)) {
+ if (ctxt->grammar->start->type != XML_RELAXNG_START) {
+ xmlRelaxNGDefinePtr def;
+
+ def = xmlRelaxNGNewDefine(ctxt, NULL);
+ if (def != NULL) {
+ def->type = XML_RELAXNG_START;
+ def->content = ctxt->grammar->start;
+ ctxt->grammar->start = def;
+ }
+ }
xmlRelaxNGTryCompile(ctxt, ctxt->grammar->start);
+ }
/*
* Transfer the pointer for cleanup at the schema level.
@@ -7485,7 +7507,7 @@
ctxt->state->seq = NULL;
} else if (ret == 0) {
/*
- * TODO: get soem of the names needed to exit the current state of exec
+ * TODO: get some of the names needed to exit the current state of exec
*/
VALID_ERR2(XML_RELAXNG_ERR_NOELEM, BAD_CAST "");
ret = -1;
@@ -7503,6 +7525,381 @@
* Progressive validation of when possible *
* *
************************************************************************/
+static int xmlRelaxNGValidateAttributeList(xmlRelaxNGValidCtxtPtr ctxt,
+ xmlRelaxNGDefinePtr defines);
+static int xmlRelaxNGValidateElementEnd(xmlRelaxNGValidCtxtPtr ctxt);
+
+/**
+ * xmlRelaxNGElemPush:
+ * @ctxt: the validation context
+ * @exec: the regexp runtime for the new content model
+ *
+ * Push a new regexp for the current node content model on the stack
+ *
+ * Returns 0 in case of success and -1 in case of error.
+ */
+static int
+xmlRelaxNGElemPush(xmlRelaxNGValidCtxtPtr ctxt, xmlRegExecCtxtPtr exec) {
+ if (ctxt->elemTab == NULL) {
+ ctxt->elemMax = 10;
+ ctxt->elemTab = (xmlRegExecCtxtPtr *) xmlMalloc(ctxt->elemMax *
+ sizeof(xmlRegExecCtxtPtr));
+ if (ctxt->elemTab == NULL) {
+ VALID_ERR(XML_RELAXNG_ERR_MEMORY);
+ return(-1);
+ }
+ }
+ if (ctxt->elemNr >= ctxt->elemMax) {
+ ctxt->elemMax *= 2;
+ ctxt->elemTab = (xmlRegExecCtxtPtr *) xmlRealloc(ctxt->elemTab,
+ ctxt->elemMax * sizeof(xmlRegExecCtxtPtr));
+ if (ctxt->elemTab == NULL) {
+ VALID_ERR(XML_RELAXNG_ERR_MEMORY);
+ return(-1);
+ }
+ }
+ ctxt->elemTab[ctxt->elemNr++] = exec;
+ ctxt->elem = exec;
+ return(0);
+}
+
+/**
+ * xmlRelaxNGElemPop:
+ * @ctxt: the validation context
+ *
+ * Pop the regexp of the current node content model from the stack
+ *
+ * Returns the exec or NULL if empty
+ */
+static xmlRegExecCtxtPtr
+xmlRelaxNGElemPop(xmlRelaxNGValidCtxtPtr ctxt) {
+ xmlRegExecCtxtPtr ret;
+
+ if (ctxt->elemNr <= 0) return(NULL);
+ ctxt->elemNr--;
+ ret = ctxt->elemTab[ctxt->elemNr];
+ ctxt->elemTab[ctxt->elemNr] = NULL;
+ if (ctxt->elemNr > 0)
+ ctxt->elem = ctxt->elemTab[ctxt->elemNr - 1];
+ else
+ ctxt->elem = NULL;
+ return(ret);
+}
+
+/**
+ * xmlRelaxNGValidateProgressiveCallback:
+ * @exec: the regular expression instance
+ * @token: the token which matched
+ * @transdata: callback data, the define for the subelement if available
+ @ @inputdata: callback data, the Relax NG validation context
+ *
+ * Handle the callback and if needed validate the element children.
+ * some of the in/out informations are passed via the context in @inputdata.
+ */
+static void
+xmlRelaxNGValidateProgressiveCallback(xmlRegExecCtxtPtr exec ATTRIBUTE_UNUSED,
+ const xmlChar *token,
+ void *transdata,
+ void *inputdata) {
+ xmlRelaxNGValidCtxtPtr ctxt = (xmlRelaxNGValidCtxtPtr) inputdata;
+ xmlRelaxNGDefinePtr define = (xmlRelaxNGDefinePtr) transdata;
+ xmlRelaxNGValidStatePtr state;
+ xmlNodePtr node = ctxt->pnode;
+ int ret;
+
+#ifdef DEBUG_PROGRESSIVE
+ xmlGenericError(xmlGenericErrorContext,
+ "Progressive callback for: '%s'\n", token);
+#endif
+ if (ctxt == NULL) {
+ fprintf(stderr, "callback on %s missing context\n", token);
+ return;
+ }
+ ctxt->pstate = 1;
+ if (define == NULL) {
+ if (token[0] == '#')
+ return;
+ fprintf(stderr, "callback on %s missing define\n", token);
+ if ((ctxt != NULL) && (ctxt->errNo == XML_RELAXNG_OK))
+ ctxt->errNo = XML_RELAXNG_ERR_INTERNAL;
+ ctxt->pstate = -1;
+ return;
+ }
+ if ((ctxt == NULL) || (define == NULL)) {
+ fprintf(stderr, "callback on %s missing info\n", token);
+ if ((ctxt != NULL) && (ctxt->errNo == XML_RELAXNG_OK))
+ ctxt->errNo = XML_RELAXNG_ERR_INTERNAL;
+ ctxt->pstate = -1;
+ return;
+ } else if (define->type != XML_RELAXNG_ELEMENT) {
+ fprintf(stderr, "callback on %s define is not element\n", token);
+ if (ctxt->errNo == XML_RELAXNG_OK)
+ ctxt->errNo = XML_RELAXNG_ERR_INTERNAL;
+ ctxt->pstate = -1;
+ return;
+ }
+ if (node->type != XML_ELEMENT_NODE) {
+ VALID_ERR(XML_RELAXNG_ERR_NOTELEM);
+ if ((ctxt->flags & FLAGS_IGNORABLE) == 0)
+ xmlRelaxNGDumpValidError(ctxt);
+ ctxt->pstate = -1;
+ return;
+ }
+ if (define->contModel == NULL) {
+ /*
+ * this node cannot be validated in a streamable fashion
+ */
+#ifdef DEBUG_PROGRESSIVE
+ xmlGenericError(xmlGenericErrorContext,
+ "Element '%s' validation is not streamable\n", token);
+#endif
+ ctxt->pstate = 0;
+ ctxt->pdef = define;
+ return;
+ }
+ exec = xmlRegNewExecCtxt(define->contModel,
+ xmlRelaxNGValidateProgressiveCallback,
+ ctxt);
+ if (exec == NULL) {
+ ctxt->pstate = -1;
+ return;
+ }
+ xmlRelaxNGElemPush(ctxt, exec);
+
+ /*
+ * Validate the attributes part of the content.
+ */
+ state = xmlRelaxNGNewValidState(ctxt, node);
+ if (state == NULL) {
+ ctxt->pstate = -1;
+ return;
+ }
+ ctxt->state = state;
+ if (define->attrs != NULL) {
+ ret = xmlRelaxNGValidateAttributeList(ctxt, define->attrs);
+ if (ret != 0) {
+ ctxt->pstate = -1;
+ VALID_ERR2(XML_RELAXNG_ERR_ATTRVALID, node->name);
+ }
+ }
+ ctxt->state->seq = NULL;
+ ret = xmlRelaxNGValidateElementEnd(ctxt);
+ if (ret != 0) {
+ ctxt->pstate = -1;
+ }
+ xmlRelaxNGFreeValidState(ctxt, state);
+ ctxt->state = NULL;
+}
+
+/**
+ * xmlRelaxNGValidatePushElement:
+ * @ctxt: the validation context
+ * @doc: a document instance
+ * @elem: an element instance
+ *
+ * Push a new element start on the RelaxNG validation stack.
+ *
+ * returns 1 if no validation problem was found or 0 if validating the
+ * element requires a full node, and -1 in case of error.
+ */
+int
+xmlRelaxNGValidatePushElement(xmlRelaxNGValidCtxtPtr ctxt, xmlDocPtr doc,
+ xmlNodePtr elem)
+{
+ int ret = 1;
+
+ if ((ctxt == NULL) || (elem == NULL))
+ return (-1);
+
+#ifdef DEBUG_PROGRESSIVE
+ xmlGenericError(xmlGenericErrorContext, "PushElem %s\n", elem->name);
+#endif
+ if (ctxt->elem == 0) {
+ xmlRelaxNGPtr schema;
+ xmlRelaxNGGrammarPtr grammar;
+ xmlRegExecCtxtPtr exec;
+ xmlRelaxNGDefinePtr define;
+
+ schema = ctxt->schema;
+ if (schema == NULL) {
+ VALID_ERR(XML_RELAXNG_ERR_NOGRAMMAR);
+ return (-1);
+ }
+ grammar = schema->topgrammar;
+ if ((grammar == NULL) || (grammar->start == NULL)) {
+ VALID_ERR(XML_RELAXNG_ERR_NOGRAMMAR);
+ return (-1);
+ }
+ define = grammar->start;
+ if (define->contModel == NULL) {
+ ctxt->pdef = define;
+ return (0);
+ }
+ exec = xmlRegNewExecCtxt(define->contModel,
+ xmlRelaxNGValidateProgressiveCallback,
+ ctxt);
+ if (exec == NULL) {
+ return (-1);
+ }
+ xmlRelaxNGElemPush(ctxt, exec);
+ }
+ ctxt->pnode = elem;
+ ctxt->pstate = 0;
+ if (elem->ns != NULL) {
+ ret =
+ xmlRegExecPushString2(ctxt->elem, elem->name, elem->ns->href,
+ ctxt);
+ } else {
+ ret = xmlRegExecPushString(ctxt->elem, elem->name, ctxt);
+ }
+ if (ret < 0) {
+ VALID_ERR2(XML_RELAXNG_ERR_ELEMWRONG, elem->name);
+ } else {
+ if (ctxt->pstate == 0)
+ ret = 0;
+ else if (ctxt->pstate < 0)
+ ret = -1;
+ else
+ ret = 1;
+ }
+#ifdef DEBUG_PROGRESSIVE
+ if (ret < 0)
+ xmlGenericError(xmlGenericErrorContext, "PushElem %s failed\n",
+ elem->name);
+#endif
+ return (ret);
+}
+
+/**
+ * xmlRelaxNGValidatePushCData:
+ * @ctxt: the RelaxNG validation context
+ * @data: some character data read
+ * @len: the lenght of the data
+ *
+ * check the CData parsed for validation in the current stack
+ *
+ * returns 1 if no validation problem was found or -1 otherwise
+ */
+int
+xmlRelaxNGValidatePushCData(xmlRelaxNGValidCtxtPtr ctxt,
+ const xmlChar * data, int len)
+{
+ int ret = 1;
+
+ if ((ctxt == NULL) || (ctxt->elem == NULL) || (data == NULL))
+ return (-1);
+
+#ifdef DEBUG_PROGRESSIVE
+ xmlGenericError(xmlGenericErrorContext, "CDATA %s %d\n", data, len);
+#endif
+
+ while (*data != 0) {
+ if (!IS_BLANK(*data))
+ break;
+ data++;
+ }
+ if (*data == 0)
+ return(1);
+
+ ret = xmlRegExecPushString(ctxt->elem, BAD_CAST "#text", ctxt);
+ if (ret < 0) {
+ VALID_ERR2(XML_RELAXNG_ERR_TEXTWRONG, " TODO ");
+#ifdef DEBUG_PROGRESSIVE
+ xmlGenericError(xmlGenericErrorContext, "CDATA failed\n");
+#endif
+
+ return(-1);
+ }
+ return(1);
+}
+
+/**
+ * xmlRelaxNGValidatePopElement:
+ * @ctxt: the RelaxNG validation context
+ * @doc: a document instance
+ * @elem: an element instance
+ *
+ * Pop the element end from the RelaxNG validation stack.
+ *
+ * returns 1 if no validation problem was found or 0 otherwise
+ */
+int
+xmlRelaxNGValidatePopElement(xmlRelaxNGValidCtxtPtr ctxt,
+ xmlDocPtr doc ATTRIBUTE_UNUSED,
+ xmlNodePtr elem) {
+ int ret;
+ xmlRegExecCtxtPtr exec;
+
+ if ((ctxt == NULL) || (ctxt->elem == NULL) || (elem == NULL)) return(-1);
+#ifdef DEBUG_PROGRESSIVE
+ xmlGenericError(xmlGenericErrorContext, "PopElem %s\n", elem->name);
+#endif
+ /*
+ * verify that we reached a terminal state of the content model.
+ */
+ exec = xmlRelaxNGElemPop(ctxt);
+ ret = xmlRegExecPushString(exec, NULL, NULL);
+ if (ret == 0) {
+ /*
+ * TODO: get some of the names needed to exit the current state of exec
+ */
+ VALID_ERR2(XML_RELAXNG_ERR_NOELEM, BAD_CAST "");
+ ret = -1;
+ } else if (ret < 0) {
+ ret = -1;
+ } else {
+ ret = 1;
+ }
+ xmlRegFreeExecCtxt(exec);
+#ifdef DEBUG_PROGRESSIVE
+ if (ret < 0)
+ xmlGenericError(xmlGenericErrorContext, "PopElem %s failed\n",
+ elem->name);
+#endif
+ return(ret);
+}
+
+/**
+ * xmlRelaxNGValidateFullElement:
+ * @ctxt: the validation context
+ * @doc: a document instance
+ * @elem: an element instance
+ *
+ * Validate a full subtree when xmlRelaxNGValidatePushElement() returned
+ * 0 and the content of the node has been expanded.
+ *
+ * returns 1 if no validation problem was found or -1 in case of error.
+ */
+int
+xmlRelaxNGValidateFullElement(xmlRelaxNGValidCtxtPtr ctxt, xmlDocPtr doc,
+ xmlNodePtr elem) {
+ int ret;
+ xmlRelaxNGValidStatePtr state;
+
+ if ((ctxt == NULL) || (ctxt->pdef == NULL) || (elem == NULL)) return(-1);
+#ifdef DEBUG_PROGRESSIVE
+ xmlGenericError(xmlGenericErrorContext, "FullElem %s\n", elem->name);
+#endif
+ state = xmlRelaxNGNewValidState(ctxt, elem->parent);
+ if (state == NULL) {
+ return(-1);
+ }
+ state->seq = elem;
+ ctxt->state = state;
+ ctxt->errNo = XML_RELAXNG_OK;
+ ret = xmlRelaxNGValidateDefinition(ctxt, ctxt->pdef);
+ if ((ret != 0) || (ctxt->errNo != XML_RELAXNG_OK)) ret = -1;
+ else ret = 1;
+ xmlRelaxNGFreeValidState(ctxt, state);
+ ctxt->state = NULL;
+#ifdef DEBUG_PROGRESSIVE
+ if (ret < 0)
+ xmlGenericError(xmlGenericErrorContext, "FullElem %s failed\n",
+ elem->name);
+#endif
+ return(ret);
+}
+
/************************************************************************
* *
* Generic interpreted validation implementation *
@@ -9158,11 +9555,10 @@
case XML_RELAXNG_ATTRIBUTE:
ret = xmlRelaxNGValidateAttribute(ctxt, define);
break;
+ case XML_RELAXNG_START:
case XML_RELAXNG_NOOP:
case XML_RELAXNG_REF:
case XML_RELAXNG_EXTERNALREF:
- ret = xmlRelaxNGValidateDefinition(ctxt, define->content);
- break;
case XML_RELAXNG_PARENTREF:
ret = xmlRelaxNGValidateDefinition(ctxt, define->content);
break;
@@ -9308,7 +9704,6 @@
xmlFree(content);
break;
}
- case XML_RELAXNG_START:
case XML_RELAXNG_EXCEPT:
case XML_RELAXNG_PARAM:
TODO ret = -1;
@@ -9617,6 +10012,16 @@
}
if (ctxt->errTab != NULL)
xmlFree(ctxt->errTab);
+ if (ctxt->elemTab != NULL) {
+ xmlRegExecCtxtPtr exec;
+
+ exec = xmlRelaxNGElemPop(ctxt);
+ while (exec != NULL) {
+ xmlRegFreeExecCtxt(exec);
+ exec = xmlRelaxNGElemPop(ctxt);
+ }
+ xmlFree(ctxt->elemTab);
+ }
xmlFree(ctxt);
}
diff --git a/valid.c b/valid.c
index 4dbe117..2b5e051 100644
--- a/valid.c
+++ b/valid.c
@@ -250,39 +250,6 @@
return (ret);
}
-#if 0
-/**
- * xmlFreeValidCtxt:
- * @ctxt: a validation context
- *
- * Free the memory allocated for a validation context
- */
-void
-xmlFreeValidCtxt(xmlValidCtxtPtr ctxt) {
- if (ctxt == NULL)
- return;
-#ifdef LIBXML_REGEXP_ENABLED
- while (ctxt->vstateNr >= 0)
- vstateVPop(ctxt);
- if (ctxt->vstateNr <= 1) return(-1);
- ctxt->vstateNr--;
- elemDecl = ctxt->vstateTab[ctxt->vstateNr].elemDecl;
- ctxt->vstateTab[ctxt->vstateNr].elemDecl = NULL;
- ctxt->vstateTab[ctxt->vstateNr].node = NULL;
- if ((elemDecl != NULL) && (elemDecl->etype == XML_ELEMENT_TYPE_ELEMENT)) {
- xmlRegFreeExecCtxt(ctxt->vstateTab[ctxt->vstateNr].exec);
- }
- ctxt->vstateTab[ctxt->vstateNr].exec = NULL;
- if (ctxt->vstateNr >= 1)
- ctxt->vstate = &ctxt->vstateTab[ctxt->vstateNr - 1];
- else
- ctxt->vstate = NULL;
- return(ctxt->vstateNr);
-#else /* ! LIBXML_REGEXP_ENABLED */
-#endif /* LIBXML_REGEXP_ENABLED */
-}
-#endif
-
#ifdef DEBUG_VALID_ALGO
static void
xmlValidPrintNode(xmlNodePtr cur) {
diff --git a/xmllint.c b/xmllint.c
index fb9141b..2aac293 100644
--- a/xmllint.c
+++ b/xmllint.c
@@ -624,6 +624,8 @@
if (reader != NULL) {
if (valid)
xmlTextReaderSetParserProp(reader, XML_PARSER_VALIDATE, 1);
+ if (relaxng != NULL)
+ xmlTextReaderRelaxNGValidate(reader, relaxng);
/*
* Process all nodes in sequence
@@ -642,6 +644,14 @@
progresult = 3;
}
}
+ if (relaxng != NULL) {
+ if (xmlTextReaderIsValid(reader) != 1) {
+ printf("%s fails to validate\n", filename);
+ progresult = 3;
+ } else {
+ printf("%s validates\n", filename);
+ }
+ }
/*
* Done, cleanup and status
*/
diff --git a/xmlreader.c b/xmlreader.c
index 4821b2a..54a3b94 100644
--- a/xmlreader.c
+++ b/xmlreader.c
@@ -12,7 +12,6 @@
/*
* TODOs:
- * - provide an API to expand part of the tree
* - provide an API to preserve part of the tree
* - Streaming XInclude support
* - validation against a provided DTD
@@ -36,6 +35,7 @@
#include <libxml/xmlmemory.h>
#include <libxml/xmlIO.h>
#include <libxml/xmlreader.h>
+#include <libxml/relaxng.h>
/* #define DEBUG_CALLBACKS */
/* #define DEBUG_READER */
@@ -85,8 +85,15 @@
XML_TEXTREADER_DONE= 5
} xmlTextReaderState;
+typedef enum {
+ XML_TEXTREADER_NOT_VALIDATE = 0,
+ XML_TEXTREADER_VALIDATE_DTD = 1,
+ XML_TEXTREADER_VALIDATE_RNG = 2
+} xmlTextReaderValidate;
+
struct _xmlTextReader {
int mode; /* the parsing mode */
+ xmlTextReaderValidate validate;/* is there any validation */
int allocs; /* what structure were deallocated */
xmlTextReaderState state;
xmlParserCtxtPtr ctxt; /* the parser context */
@@ -112,6 +119,14 @@
/* error handling */
xmlTextReaderErrorFunc errorFunc; /* callback function */
void *errorFuncArg; /* callback function user argument */
+
+#ifdef LIBXML_SCHEMAS_ENABLED
+ /* Handling of RelaxNG validation */
+ xmlRelaxNGPtr rngSchemas; /* The Relax NG schemas */
+ xmlRelaxNGValidCtxtPtr rngValidCtxt; /* The Relax NG validation context */
+ int rngValidErrors; /* The number of errors detected */
+ xmlNodePtr rngFullNode; /* the node if RNG not progressive */
+#endif
};
static const char *xmlTextReaderIsEmpty = "This element is empty";
@@ -425,22 +440,84 @@
#ifdef LIBXML_REGEXP_ENABLED
xmlNodePtr node = reader->node;
- if ((node->ns == NULL) || (node->ns->prefix == NULL)) {
- reader->ctxt->valid &= xmlValidatePushElement(&reader->ctxt->vctxt,
- reader->ctxt->myDoc, node, node->name);
- } else {
- xmlChar *qname;
+ if ((reader->validate == XML_TEXTREADER_VALIDATE_DTD) &&
+ (reader->ctxt != NULL) && (reader->ctxt->validate == 1)) {
+ if ((node->ns == NULL) || (node->ns->prefix == NULL)) {
+ reader->ctxt->valid &= xmlValidatePushElement(&reader->ctxt->vctxt,
+ reader->ctxt->myDoc, node, node->name);
+ } else {
+ /* TODO use the BuildQName interface */
+ xmlChar *qname;
- qname = xmlStrdup(node->ns->prefix);
- qname = xmlStrcat(qname, BAD_CAST ":");
- qname = xmlStrcat(qname, node->name);
- reader->ctxt->valid &= xmlValidatePushElement(&reader->ctxt->vctxt,
- reader->ctxt->myDoc, node, qname);
- if (qname != NULL)
- xmlFree(qname);
+ qname = xmlStrdup(node->ns->prefix);
+ qname = xmlStrcat(qname, BAD_CAST ":");
+ qname = xmlStrcat(qname, node->name);
+ reader->ctxt->valid &= xmlValidatePushElement(&reader->ctxt->vctxt,
+ reader->ctxt->myDoc, node, qname);
+ if (qname != NULL)
+ xmlFree(qname);
+ }
+#ifdef LIBXML_SCHEMAS_ENABLED
+ } else if ((reader->validate == XML_TEXTREADER_VALIDATE_RNG) &&
+ (reader->rngValidCtxt != NULL)) {
+ int ret;
+
+ if (reader->rngFullNode != NULL) return;
+ ret = xmlRelaxNGValidatePushElement(reader->rngValidCtxt,
+ reader->ctxt->myDoc,
+ node);
+ if (ret == 0) {
+ /*
+ * this element requires a full tree
+ */
+ node = xmlTextReaderExpand(reader);
+ if (node == NULL) {
+printf("Expand failed !\n");
+ ret = -1;
+ } else {
+ ret = xmlRelaxNGValidateFullElement(reader->rngValidCtxt,
+ reader->ctxt->myDoc,
+ node);
+ reader->rngFullNode = node;
+ }
+ }
+ if (ret != 1)
+ reader->rngValidErrors++;
+#endif
}
#endif /* LIBXML_REGEXP_ENABLED */
}
+
+/**
+ * xmlTextReaderValidateCData:
+ * @reader: the xmlTextReaderPtr used
+ * @data: pointer to the CData
+ * @len: lenght of the CData block in bytes.
+ *
+ * Push some CData for validation
+ */
+static void
+xmlTextReaderValidateCData(xmlTextReaderPtr reader,
+ const xmlChar *data, int len) {
+#ifdef LIBXML_REGEXP_ENABLED
+ if ((reader->validate == XML_TEXTREADER_VALIDATE_DTD) &&
+ (reader->ctxt != NULL) && (reader->ctxt->validate == 1)) {
+ reader->ctxt->valid &= xmlValidatePushCData(&reader->ctxt->vctxt,
+ data, len);
+#ifdef LIBXML_SCHEMAS_ENABLED
+ } else if ((reader->validate == XML_TEXTREADER_VALIDATE_RNG) &&
+ (reader->rngValidCtxt != NULL)) {
+ int ret;
+
+ if (reader->rngFullNode != NULL) return;
+ ret = xmlRelaxNGValidatePushCData(reader->rngValidCtxt, data, len);
+ if (ret != 1)
+ reader->rngValidErrors++;
+#endif
+ }
+#endif /* LIBXML_REGEXP_ENABLED */
+}
+
/**
* xmlTextReaderValidatePop:
* @reader: the xmlTextReaderPtr used
@@ -452,19 +529,39 @@
#ifdef LIBXML_REGEXP_ENABLED
xmlNodePtr node = reader->node;
- if ((node->ns == NULL) || (node->ns->prefix == NULL)) {
- reader->ctxt->valid &= xmlValidatePopElement(&reader->ctxt->vctxt,
- reader->ctxt->myDoc, node, node->name);
- } else {
- xmlChar *qname;
+ if ((reader->validate == XML_TEXTREADER_VALIDATE_DTD) &&
+ (reader->ctxt != NULL) && (reader->ctxt->validate == 1)) {
+ if ((node->ns == NULL) || (node->ns->prefix == NULL)) {
+ reader->ctxt->valid &= xmlValidatePopElement(&reader->ctxt->vctxt,
+ reader->ctxt->myDoc, node, node->name);
+ } else {
+ /* TODO use the BuildQName interface */
+ xmlChar *qname;
- qname = xmlStrdup(node->ns->prefix);
- qname = xmlStrcat(qname, BAD_CAST ":");
- qname = xmlStrcat(qname, node->name);
- reader->ctxt->valid &= xmlValidatePopElement(&reader->ctxt->vctxt,
- reader->ctxt->myDoc, node, qname);
- if (qname != NULL)
- xmlFree(qname);
+ qname = xmlStrdup(node->ns->prefix);
+ qname = xmlStrcat(qname, BAD_CAST ":");
+ qname = xmlStrcat(qname, node->name);
+ reader->ctxt->valid &= xmlValidatePopElement(&reader->ctxt->vctxt,
+ reader->ctxt->myDoc, node, qname);
+ if (qname != NULL)
+ xmlFree(qname);
+ }
+#ifdef LIBXML_SCHEMAS_ENABLED
+ } else if ((reader->validate == XML_TEXTREADER_VALIDATE_RNG) &&
+ (reader->rngValidCtxt != NULL)) {
+ int ret;
+
+ if (reader->rngFullNode != NULL) {
+ if (node == reader->rngFullNode)
+ reader->rngFullNode = NULL;
+ return;
+ }
+ ret = xmlRelaxNGValidatePopElement(reader->rngValidCtxt,
+ reader->ctxt->myDoc,
+ node);
+ if (ret != 1)
+ reader->rngValidErrors++;
+#endif
}
#endif /* LIBXML_REGEXP_ENABLED */
}
@@ -514,8 +611,8 @@
xmlTextReaderValidatePush(reader);
} else if ((node->type == XML_TEXT_NODE) ||
(node->type == XML_CDATA_SECTION_NODE)) {
- ctxt->valid &= xmlValidatePushCData(&ctxt->vctxt,
- node->content, xmlStrlen(node->content));
+ xmlTextReaderValidateCData(reader, node->content,
+ xmlStrlen(node->content));
}
/*
@@ -745,7 +842,7 @@
reader->state = XML_TEXTREADER_END;
goto node_found;
}
- if ((reader->ctxt->validate) &&
+ if ((reader->validate) &&
(reader->node->type == XML_ELEMENT_NODE))
xmlTextReaderValidatePop(reader);
reader->node = reader->node->next;
@@ -770,7 +867,7 @@
reader->state = XML_TEXTREADER_END;
goto node_found;
}
- if ((reader->ctxt->validate) && (reader->node->type == XML_ELEMENT_NODE))
+ if ((reader->validate) && (reader->node->type == XML_ELEMENT_NODE))
xmlTextReaderValidatePop(reader);
reader->node = reader->node->parent;
if ((reader->node == NULL) ||
@@ -826,7 +923,7 @@
}
} else if ((reader->node != NULL) &&
(reader->node->type == XML_ENTITY_REF_NODE) &&
- (reader->ctxt != NULL) && (reader->ctxt->validate == 1)) {
+ (reader->ctxt != NULL) && (reader->validate)) {
xmlTextReaderValidateEntity(reader);
}
if ((reader->node != NULL) &&
@@ -837,9 +934,8 @@
goto get_next_node;
}
#ifdef LIBXML_REGEXP_ENABLED
- if ((reader->ctxt->validate) && (reader->node != NULL)) {
+ if ((reader->validate) && (reader->node != NULL)) {
xmlNodePtr node = reader->node;
- xmlParserCtxtPtr ctxt = reader->ctxt;
if ((node->type == XML_ELEMENT_NODE) &&
((reader->state != XML_TEXTREADER_END) &&
@@ -847,8 +943,8 @@
xmlTextReaderValidatePush(reader);
} else if ((node->type == XML_TEXT_NODE) ||
(node->type == XML_CDATA_SECTION_NODE)) {
- ctxt->valid &= xmlValidatePushCData(&ctxt->vctxt,
- node->content, xmlStrlen(node->content));
+ xmlTextReaderValidateCData(reader, node->content,
+ xmlStrlen(node->content));
}
}
#endif /* LIBXML_REGEXP_ENABLED */
@@ -1140,6 +1236,14 @@
xmlFreeTextReader(xmlTextReaderPtr reader) {
if (reader == NULL)
return;
+ if (reader->rngSchemas != NULL) {
+ xmlRelaxNGFree(reader->rngSchemas);
+ reader->rngSchemas = NULL;
+ }
+ if (reader->rngValidCtxt != NULL) {
+ xmlRelaxNGFreeValidCtxt(reader->rngValidCtxt);
+ reader->rngValidCtxt = NULL;
+ }
if (reader->ctxt != NULL) {
if (reader->ctxt->myDoc != NULL) {
xmlFreeDoc(reader->ctxt->myDoc);
@@ -2308,6 +2412,7 @@
case XML_PARSER_VALIDATE:
if (value != 0) {
ctxt->validate = 1;
+ reader->validate = XML_TEXTREADER_VALIDATE_DTD;
} else {
ctxt->validate = 0;
}
@@ -2351,7 +2456,7 @@
return(1);
return(0);
case XML_PARSER_VALIDATE:
- return(ctxt->validate);
+ return(reader->validate);
case XML_PARSER_SUBST_ENTITIES:
return(ctxt->replaceEntities);
}
@@ -2396,6 +2501,64 @@
return(reader->ctxt->myDoc);
}
+/**
+ * xmlTextReaderRelaxNGValidate:
+ * @reader: the xmlTextReaderPtr used
+ * @rng: the path to a RelaxNG schema or NULL
+ *
+ * Use RelaxNG to validate the document as it is processed.
+ * Activation is only possible before the first Read().
+ * if @rng is NULL, then RelaxNG validation is desactivated.
+ *
+ * Returns 0 in case the RelaxNG validation could be (des)activated and
+ * -1 in case of error.
+ */
+int
+xmlTextReaderRelaxNGValidate(xmlTextReaderPtr reader, const char *rng) {
+ xmlRelaxNGParserCtxtPtr ctxt;
+
+ if (reader == NULL)
+ return(-1);
+
+ if (rng == NULL) {
+ if (reader->rngSchemas != NULL) {
+ xmlRelaxNGFree(reader->rngSchemas);
+ reader->rngSchemas = NULL;
+ }
+ if (reader->rngValidCtxt != NULL) {
+ xmlRelaxNGFreeValidCtxt(reader->rngValidCtxt);
+ reader->rngValidCtxt = NULL;
+ }
+ return(0);
+ }
+ if (reader->mode != XML_TEXTREADER_MODE_INITIAL)
+ return(-1);
+ ctxt = xmlRelaxNGNewParserCtxt(rng);
+ if (reader->errorFunc != NULL) {
+ xmlRelaxNGSetParserErrors(ctxt,
+ (xmlRelaxNGValidityErrorFunc) reader->errorFunc,
+ (xmlRelaxNGValidityWarningFunc) reader->errorFunc,
+ reader->errorFuncArg);
+ }
+ reader->rngSchemas = xmlRelaxNGParse(ctxt);
+ xmlRelaxNGFreeParserCtxt(ctxt);
+ if (reader->rngSchemas == NULL)
+ return(-1);
+ reader->rngValidCtxt = xmlRelaxNGNewValidCtxt(reader->rngSchemas);
+ if (reader->rngValidCtxt == NULL)
+ return(-1);
+ if (reader->errorFunc != NULL) {
+ xmlRelaxNGSetValidErrors(reader->rngValidCtxt,
+ (xmlRelaxNGValidityErrorFunc)reader->errorFunc,
+ (xmlRelaxNGValidityWarningFunc) reader->errorFunc,
+ reader->errorFuncArg);
+ }
+ reader->rngValidErrors = 0;
+ reader->rngFullNode = NULL;
+ reader->validate = XML_TEXTREADER_VALIDATE_RNG;
+ return(0);
+}
+
/************************************************************************
* *
* Error Handling Extensions *
@@ -2621,8 +2784,15 @@
*/
int
xmlTextReaderIsValid(xmlTextReaderPtr reader) {
- if ((reader == NULL) || (reader->ctxt == NULL)) return(-1);
- return(reader->ctxt->valid);
+ if (reader == NULL) return(-1);
+#ifdef LIBXML_SCHEMAS_ENABLED
+ if (reader->validate == XML_TEXTREADER_VALIDATE_RNG)
+ return(reader->rngValidErrors == 0);
+#endif
+ if ((reader->validate == XML_TEXTREADER_VALIDATE_DTD) &&
+ (reader->ctxt != NULL))
+ return(reader->ctxt->valid);
+ return(0);
}
/**