| /* |
| * valid.c : part of the code use to do the DTD handling and the validity |
| * checking |
| * |
| * See Copyright for the status of this software. |
| * |
| * daniel@veillard.com |
| */ |
| |
| #include "libxml.h" |
| |
| #include <string.h> |
| |
| #ifdef HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| |
| #include <libxml/xmlmemory.h> |
| #include <libxml/hash.h> |
| #include <libxml/valid.h> |
| #include <libxml/parser.h> |
| #include <libxml/parserInternals.h> |
| #include <libxml/xmlerror.h> |
| #include <libxml/list.h> |
| |
| /* #define DEBUG_VALID_ALGO */ |
| |
| /* |
| * Generic function for accessing stacks in the Validity Context |
| */ |
| |
| #define PUSH_AND_POP(scope, type, name) \ |
| scope int name##VPush(xmlValidCtxtPtr ctxt, type value) { \ |
| if (ctxt->name##Max <= 0) { \ |
| ctxt->name##Max = 4; \ |
| ctxt->name##Tab = (type *) xmlMalloc( \ |
| ctxt->name##Max * sizeof(ctxt->name##Tab[0])); \ |
| if (ctxt->name##Tab == NULL) { \ |
| xmlGenericError(xmlGenericErrorContext, \ |
| "malloc failed !\n"); \ |
| ctxt->name##Max = 0; \ |
| return(0); \ |
| } \ |
| } \ |
| if (ctxt->name##Nr >= ctxt->name##Max) { \ |
| ctxt->name##Max *= 2; \ |
| ctxt->name##Tab = (type *) xmlRealloc(ctxt->name##Tab, \ |
| ctxt->name##Max * sizeof(ctxt->name##Tab[0])); \ |
| if (ctxt->name##Tab == NULL) { \ |
| xmlGenericError(xmlGenericErrorContext, \ |
| "realloc failed !\n"); \ |
| return(0); \ |
| } \ |
| } \ |
| ctxt->name##Tab[ctxt->name##Nr] = value; \ |
| ctxt->name = value; \ |
| return(ctxt->name##Nr++); \ |
| } \ |
| scope type name##VPop(xmlValidCtxtPtr ctxt) { \ |
| type ret; \ |
| if (ctxt->name##Nr <= 0) return(0); \ |
| ctxt->name##Nr--; \ |
| if (ctxt->name##Nr > 0) \ |
| ctxt->name = ctxt->name##Tab[ctxt->name##Nr - 1]; \ |
| else \ |
| ctxt->name = NULL; \ |
| ret = ctxt->name##Tab[ctxt->name##Nr]; \ |
| ctxt->name##Tab[ctxt->name##Nr] = 0; \ |
| return(ret); \ |
| } \ |
| |
| /* |
| * I will use a home made algorithm less complex and easier to |
| * debug/maintin than a generic NFA -> DFA state based algo. The |
| * only restriction is on the deepness of the tree limited by the |
| * size of the occurs bitfield |
| * |
| * this is the content of a saved state for rollbacks |
| */ |
| |
| #define ROLLBACK_OR 0 |
| #define ROLLBACK_PARENT 1 |
| |
| struct _xmlValidState { |
| xmlElementContentPtr cont; /* pointer to the content model subtree */ |
| xmlNodePtr node; /* pointer to the current node in the list */ |
| long occurs;/* bitfield for multiple occurences */ |
| unsigned char depth; /* current depth in the overall tree */ |
| unsigned char state; /* ROLLBACK_XXX */ |
| } _xmlValidState; |
| |
| #define MAX_DEPTH ((sizeof(_xmlValidState.occurs)) * 8) |
| #define CONT ctxt->vstate->cont |
| #define NODE ctxt->vstate->node |
| #define DEPTH ctxt->vstate->depth |
| #define OCCURS ctxt->vstate->occurs |
| #define STATE ctxt->vstate->state |
| |
| #define OCCURENCE (ctxt->vstate->occurs & (1 << DEPTH)) |
| #define PARENT_OCCURENCE (ctxt->vstate->occurs & ((1 << DEPTH) - 1)) |
| |
| #define SET_OCCURENCE ctxt->vstate->occurs |= (1 << DEPTH) |
| #define RESET_OCCURENCE ctxt->vstate->occurs &= ((1 << DEPTH) - 1) |
| |
| static int |
| vstateVPush(xmlValidCtxtPtr ctxt, xmlElementContentPtr cont, |
| xmlNodePtr node, unsigned char depth, long occurs, |
| unsigned char state) { |
| int i = ctxt->vstateNr - 1; |
| |
| if (ctxt->vstateNr >= ctxt->vstateMax) { |
| ctxt->vstateMax *= 2; |
| ctxt->vstateTab = (xmlValidState *) xmlRealloc(ctxt->vstateTab, |
| ctxt->vstateMax * sizeof(ctxt->vstateTab[0])); |
| if (ctxt->vstateTab == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "realloc failed !n"); |
| return(0); |
| } |
| ctxt->vstate = &ctxt->vstateTab[0]; |
| } |
| /* |
| * Don't push on the stack a state already here |
| */ |
| if ((i >= 0) && (ctxt->vstateTab[i].cont == cont) && |
| (ctxt->vstateTab[i].node == node) && |
| (ctxt->vstateTab[i].depth == depth) && |
| (ctxt->vstateTab[i].occurs == occurs) && |
| (ctxt->vstateTab[i].state == state)) |
| return(ctxt->vstateNr); |
| ctxt->vstateTab[ctxt->vstateNr].cont = cont; |
| ctxt->vstateTab[ctxt->vstateNr].node = node; |
| ctxt->vstateTab[ctxt->vstateNr].depth = depth; |
| ctxt->vstateTab[ctxt->vstateNr].occurs = occurs; |
| ctxt->vstateTab[ctxt->vstateNr].state = state; |
| return(ctxt->vstateNr++); |
| } |
| |
| static int |
| vstateVPop(xmlValidCtxtPtr ctxt) { |
| if (ctxt->vstateNr <= 1) return(-1); |
| ctxt->vstateNr--; |
| ctxt->vstate = &ctxt->vstateTab[0]; |
| ctxt->vstate->cont = ctxt->vstateTab[ctxt->vstateNr].cont; |
| ctxt->vstate->node = ctxt->vstateTab[ctxt->vstateNr].node; |
| ctxt->vstate->depth = ctxt->vstateTab[ctxt->vstateNr].depth; |
| ctxt->vstate->occurs = ctxt->vstateTab[ctxt->vstateNr].occurs; |
| ctxt->vstate->state = ctxt->vstateTab[ctxt->vstateNr].state; |
| return(ctxt->vstateNr); |
| } |
| |
| PUSH_AND_POP(static, xmlNodePtr, node) |
| |
| #ifdef DEBUG_VALID_ALGO |
| static void |
| xmlValidPrintNode(xmlNodePtr cur) { |
| if (cur == NULL) { |
| xmlGenericError(xmlGenericErrorContext, "null"); |
| return; |
| } |
| switch (cur->type) { |
| case XML_ELEMENT_NODE: |
| xmlGenericError(xmlGenericErrorContext, "%s ", cur->name); |
| break; |
| case XML_TEXT_NODE: |
| xmlGenericError(xmlGenericErrorContext, "text "); |
| break; |
| case XML_CDATA_SECTION_NODE: |
| xmlGenericError(xmlGenericErrorContext, "cdata "); |
| break; |
| case XML_ENTITY_REF_NODE: |
| xmlGenericError(xmlGenericErrorContext, "&%s; ", cur->name); |
| break; |
| case XML_PI_NODE: |
| xmlGenericError(xmlGenericErrorContext, "pi(%s) ", cur->name); |
| break; |
| case XML_COMMENT_NODE: |
| xmlGenericError(xmlGenericErrorContext, "comment "); |
| break; |
| case XML_ATTRIBUTE_NODE: |
| xmlGenericError(xmlGenericErrorContext, "?attr? "); |
| break; |
| case XML_ENTITY_NODE: |
| xmlGenericError(xmlGenericErrorContext, "?ent? "); |
| break; |
| case XML_DOCUMENT_NODE: |
| xmlGenericError(xmlGenericErrorContext, "?doc? "); |
| break; |
| case XML_DOCUMENT_TYPE_NODE: |
| xmlGenericError(xmlGenericErrorContext, "?doctype? "); |
| break; |
| case XML_DOCUMENT_FRAG_NODE: |
| xmlGenericError(xmlGenericErrorContext, "?frag? "); |
| break; |
| case XML_NOTATION_NODE: |
| xmlGenericError(xmlGenericErrorContext, "?nota? "); |
| break; |
| case XML_HTML_DOCUMENT_NODE: |
| xmlGenericError(xmlGenericErrorContext, "?html? "); |
| break; |
| case XML_DTD_NODE: |
| xmlGenericError(xmlGenericErrorContext, "?dtd? "); |
| break; |
| case XML_ELEMENT_DECL: |
| xmlGenericError(xmlGenericErrorContext, "?edecl? "); |
| break; |
| case XML_ATTRIBUTE_DECL: |
| xmlGenericError(xmlGenericErrorContext, "?adecl? "); |
| break; |
| case XML_ENTITY_DECL: |
| xmlGenericError(xmlGenericErrorContext, "?entdecl? "); |
| break; |
| case XML_NAMESPACE_DECL: |
| xmlGenericError(xmlGenericErrorContext, "?nsdecl? "); |
| break; |
| case XML_XINCLUDE_START: |
| xmlGenericError(xmlGenericErrorContext, "incstart "); |
| break; |
| case XML_XINCLUDE_END: |
| xmlGenericError(xmlGenericErrorContext, "incend "); |
| break; |
| } |
| } |
| |
| static void |
| xmlValidPrintNodeList(xmlNodePtr cur) { |
| if (cur == NULL) |
| xmlGenericError(xmlGenericErrorContext, "null "); |
| while (cur != NULL) { |
| xmlValidPrintNode(cur); |
| cur = cur->next; |
| } |
| } |
| |
| static void |
| xmlValidDebug(xmlNodePtr cur, xmlElementContentPtr cont) { |
| char expr[1000]; |
| |
| expr[0] = 0; |
| xmlGenericError(xmlGenericErrorContext, "valid: "); |
| xmlValidPrintNodeList(cur); |
| xmlGenericError(xmlGenericErrorContext, "against "); |
| xmlSnprintfElementContent(expr, 5000, cont, 1); |
| xmlGenericError(xmlGenericErrorContext, "%s\n", expr); |
| } |
| |
| static void |
| xmlValidDebugState(xmlValidStatePtr state) { |
| xmlGenericError(xmlGenericErrorContext, "("); |
| if (state->cont == NULL) |
| xmlGenericError(xmlGenericErrorContext, "null,"); |
| else |
| switch (state->cont->type) { |
| case XML_ELEMENT_CONTENT_PCDATA: |
| xmlGenericError(xmlGenericErrorContext, "pcdata,"); |
| break; |
| case XML_ELEMENT_CONTENT_ELEMENT: |
| xmlGenericError(xmlGenericErrorContext, "%s,", |
| state->cont->name); |
| break; |
| case XML_ELEMENT_CONTENT_SEQ: |
| xmlGenericError(xmlGenericErrorContext, "seq,"); |
| break; |
| case XML_ELEMENT_CONTENT_OR: |
| xmlGenericError(xmlGenericErrorContext, "or,"); |
| break; |
| } |
| xmlValidPrintNode(state->node); |
| xmlGenericError(xmlGenericErrorContext, ",%d,%X,%d)", |
| state->depth, state->occurs, state->state); |
| } |
| |
| static void |
| xmlValidStateDebug(xmlValidCtxtPtr ctxt) { |
| int i, j; |
| |
| xmlGenericError(xmlGenericErrorContext, "state: "); |
| xmlValidDebugState(ctxt->vstate); |
| xmlGenericError(xmlGenericErrorContext, " stack: %d ", |
| ctxt->vstateNr - 1); |
| for (i = 0, j = ctxt->vstateNr - 1;(i < 3) && (j > 0);i++,j--) |
| xmlValidDebugState(&ctxt->vstateTab[j]); |
| xmlGenericError(xmlGenericErrorContext, "\n"); |
| } |
| |
| /***** |
| #define DEBUG_VALID_STATE(n,c) xmlValidDebug(n,c); |
| *****/ |
| |
| #define DEBUG_VALID_STATE(n,c) xmlValidStateDebug(ctxt); |
| #define DEBUG_VALID_MSG(m) \ |
| xmlGenericError(xmlGenericErrorContext, "%s\n", m); |
| |
| #else |
| #define DEBUG_VALID_STATE(n,c) |
| #define DEBUG_VALID_MSG(m) |
| #endif |
| |
| /* TODO: use hash table for accesses to elem and attribute dedinitions */ |
| |
| #define VERROR \ |
| if ((ctxt != NULL) && (ctxt->error != NULL)) ctxt->error |
| |
| #define VWARNING \ |
| if ((ctxt != NULL) && (ctxt->warning != NULL)) ctxt->warning |
| |
| #define CHECK_DTD \ |
| if (doc == NULL) return(0); \ |
| else if ((doc->intSubset == NULL) && \ |
| (doc->extSubset == NULL)) return(0) |
| |
| xmlElementPtr xmlGetDtdElementDesc(xmlDtdPtr dtd, const xmlChar *name); |
| static xmlElementPtr xmlGetDtdElementDesc2(xmlDtdPtr dtd, const xmlChar *name, |
| int create); |
| xmlAttributePtr xmlScanAttributeDecl(xmlDtdPtr dtd, const xmlChar *elem); |
| |
| /************************************************************************ |
| * * |
| * QName handling helper * |
| * * |
| ************************************************************************/ |
| |
| /** |
| * xmlSplitQName2: |
| * @name: an XML parser context |
| * @prefix: a xmlChar ** |
| * |
| * parse an XML qualified name string |
| * |
| * [NS 5] QName ::= (Prefix ':')? LocalPart |
| * |
| * [NS 6] Prefix ::= NCName |
| * |
| * [NS 7] LocalPart ::= NCName |
| * |
| * Returns NULL if not a QName, otherwise the local part, and prefix |
| * is updated to get the Prefix if any. |
| */ |
| |
| xmlChar * |
| xmlSplitQName2(const xmlChar *name, xmlChar **prefix) { |
| int len = 0; |
| xmlChar *ret = NULL; |
| |
| *prefix = NULL; |
| |
| /* xml: prefix is not really a namespace */ |
| if ((name[0] == 'x') && (name[1] == 'm') && |
| (name[2] == 'l') && (name[3] == ':')) |
| return(NULL); |
| |
| /* nasty but valid */ |
| if (name[0] == ':') |
| return(NULL); |
| |
| /* |
| * we are not trying to validate but just to cut, and yes it will |
| * work even if this is as set of UTF-8 encoded chars |
| */ |
| while ((name[len] != 0) && (name[len] != ':')) |
| len++; |
| |
| if (name[len] == 0) |
| return(NULL); |
| |
| *prefix = xmlStrndup(name, len); |
| ret = xmlStrdup(&name[len + 1]); |
| |
| return(ret); |
| } |
| |
| /**************************************************************** |
| * * |
| * Util functions for data allocation/deallocation * |
| * * |
| ****************************************************************/ |
| |
| /** |
| * xmlNewElementContent: |
| * @name: the subelement name or NULL |
| * @type: the type of element content decl |
| * |
| * Allocate an element content structure. |
| * |
| * Returns NULL if not, othervise the new element content structure |
| */ |
| xmlElementContentPtr |
| xmlNewElementContent(xmlChar *name, xmlElementContentType type) { |
| xmlElementContentPtr ret; |
| |
| switch(type) { |
| case XML_ELEMENT_CONTENT_ELEMENT: |
| if (name == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlNewElementContent : name == NULL !\n"); |
| } |
| break; |
| case XML_ELEMENT_CONTENT_PCDATA: |
| case XML_ELEMENT_CONTENT_SEQ: |
| case XML_ELEMENT_CONTENT_OR: |
| if (name != NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlNewElementContent : name != NULL !\n"); |
| } |
| break; |
| default: |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlNewElementContent: unknown type %d\n", type); |
| return(NULL); |
| } |
| ret = (xmlElementContentPtr) xmlMalloc(sizeof(xmlElementContent)); |
| if (ret == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlNewElementContent : out of memory!\n"); |
| return(NULL); |
| } |
| ret->type = type; |
| ret->ocur = XML_ELEMENT_CONTENT_ONCE; |
| if (name != NULL) |
| ret->name = xmlStrdup(name); |
| else |
| ret->name = NULL; |
| ret->c1 = ret->c2 = ret->parent = NULL; |
| return(ret); |
| } |
| |
| /** |
| * xmlCopyElementContent: |
| * @content: An element content pointer. |
| * |
| * Build a copy of an element content description. |
| * |
| * Returns the new xmlElementContentPtr or NULL in case of error. |
| */ |
| xmlElementContentPtr |
| xmlCopyElementContent(xmlElementContentPtr cur) { |
| xmlElementContentPtr ret; |
| |
| if (cur == NULL) return(NULL); |
| ret = xmlNewElementContent((xmlChar *) cur->name, cur->type); |
| if (ret == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlCopyElementContent : out of memory\n"); |
| return(NULL); |
| } |
| ret->ocur = cur->ocur; |
| if (cur->c1 != NULL) ret->c1 = xmlCopyElementContent(cur->c1); |
| if (ret->c1 != NULL) |
| ret->c1->parent = ret; |
| if (cur->c2 != NULL) ret->c2 = xmlCopyElementContent(cur->c2); |
| if (ret->c2 != NULL) |
| ret->c2->parent = ret; |
| return(ret); |
| } |
| |
| /** |
| * xmlFreeElementContent: |
| * @cur: the element content tree to free |
| * |
| * Free an element content structure. This is a recursive call ! |
| */ |
| void |
| xmlFreeElementContent(xmlElementContentPtr cur) { |
| if (cur == NULL) return; |
| switch (cur->type) { |
| case XML_ELEMENT_CONTENT_PCDATA: |
| case XML_ELEMENT_CONTENT_ELEMENT: |
| case XML_ELEMENT_CONTENT_SEQ: |
| case XML_ELEMENT_CONTENT_OR: |
| break; |
| default: |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlFreeElementContent : type %d\n", cur->type); |
| return; |
| } |
| if (cur->c1 != NULL) xmlFreeElementContent(cur->c1); |
| if (cur->c2 != NULL) xmlFreeElementContent(cur->c2); |
| if (cur->name != NULL) xmlFree((xmlChar *) cur->name); |
| xmlFree(cur); |
| } |
| |
| /** |
| * xmlDumpElementContent: |
| * @buf: An XML buffer |
| * @content: An element table |
| * @glob: 1 if one must print the englobing parenthesis, 0 otherwise |
| * |
| * This will dump the content of the element table as an XML DTD definition |
| */ |
| static void |
| xmlDumpElementContent(xmlBufferPtr buf, xmlElementContentPtr content, int glob) { |
| if (content == NULL) return; |
| |
| if (glob) xmlBufferWriteChar(buf, "("); |
| switch (content->type) { |
| case XML_ELEMENT_CONTENT_PCDATA: |
| xmlBufferWriteChar(buf, "#PCDATA"); |
| break; |
| case XML_ELEMENT_CONTENT_ELEMENT: |
| xmlBufferWriteCHAR(buf, content->name); |
| break; |
| case XML_ELEMENT_CONTENT_SEQ: |
| if ((content->c1->type == XML_ELEMENT_CONTENT_OR) || |
| (content->c1->type == XML_ELEMENT_CONTENT_SEQ)) |
| xmlDumpElementContent(buf, content->c1, 1); |
| else |
| xmlDumpElementContent(buf, content->c1, 0); |
| xmlBufferWriteChar(buf, " , "); |
| if (content->c2->type == XML_ELEMENT_CONTENT_OR) |
| xmlDumpElementContent(buf, content->c2, 1); |
| else |
| xmlDumpElementContent(buf, content->c2, 0); |
| break; |
| case XML_ELEMENT_CONTENT_OR: |
| if ((content->c1->type == XML_ELEMENT_CONTENT_OR) || |
| (content->c1->type == XML_ELEMENT_CONTENT_SEQ)) |
| xmlDumpElementContent(buf, content->c1, 1); |
| else |
| xmlDumpElementContent(buf, content->c1, 0); |
| xmlBufferWriteChar(buf, " | "); |
| if (content->c2->type == XML_ELEMENT_CONTENT_SEQ) |
| xmlDumpElementContent(buf, content->c2, 1); |
| else |
| xmlDumpElementContent(buf, content->c2, 0); |
| break; |
| default: |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlDumpElementContent: unknown type %d\n", |
| content->type); |
| } |
| if (glob) |
| xmlBufferWriteChar(buf, ")"); |
| switch (content->ocur) { |
| case XML_ELEMENT_CONTENT_ONCE: |
| break; |
| case XML_ELEMENT_CONTENT_OPT: |
| xmlBufferWriteChar(buf, "?"); |
| break; |
| case XML_ELEMENT_CONTENT_MULT: |
| xmlBufferWriteChar(buf, "*"); |
| break; |
| case XML_ELEMENT_CONTENT_PLUS: |
| xmlBufferWriteChar(buf, "+"); |
| break; |
| } |
| } |
| |
| /** |
| * xmlSprintfElementContent: |
| * @buf: an output buffer |
| * @content: An element table |
| * @glob: 1 if one must print the englobing parenthesis, 0 otherwise |
| * |
| * Deprecated, unsafe, use xmlSnprintfElementContent |
| */ |
| void |
| xmlSprintfElementContent(char *buf ATTRIBUTE_UNUSED, |
| xmlElementContentPtr content ATTRIBUTE_UNUSED, |
| int glob ATTRIBUTE_UNUSED) { |
| } |
| |
| /** |
| * xmlSnprintfElementContent: |
| * @buf: an output buffer |
| * @size: the buffer size |
| * @content: An element table |
| * @glob: 1 if one must print the englobing parenthesis, 0 otherwise |
| * |
| * This will dump the content of the element content definition |
| * Intended just for the debug routine |
| */ |
| void |
| xmlSnprintfElementContent(char *buf, int size, xmlElementContentPtr content, int glob) { |
| int len; |
| |
| if (content == NULL) return; |
| len = strlen(buf); |
| if (size - len < 50) { |
| if ((size - len > 4) && (buf[len - 1] != '.')) |
| strcat(buf, " ..."); |
| return; |
| } |
| if (glob) strcat(buf, "("); |
| switch (content->type) { |
| case XML_ELEMENT_CONTENT_PCDATA: |
| strcat(buf, "#PCDATA"); |
| break; |
| case XML_ELEMENT_CONTENT_ELEMENT: |
| if (size - len < xmlStrlen(content->name + 10)) { |
| strcat(buf, " ..."); |
| return; |
| } |
| strcat(buf, (char *) content->name); |
| break; |
| case XML_ELEMENT_CONTENT_SEQ: |
| if ((content->c1->type == XML_ELEMENT_CONTENT_OR) || |
| (content->c1->type == XML_ELEMENT_CONTENT_SEQ)) |
| xmlSnprintfElementContent(buf, size, content->c1, 1); |
| else |
| xmlSnprintfElementContent(buf, size, content->c1, 0); |
| len = strlen(buf); |
| if (size - len < 50) { |
| if ((size - len > 4) && (buf[len - 1] != '.')) |
| strcat(buf, " ..."); |
| return; |
| } |
| strcat(buf, " , "); |
| if (content->c2->type == XML_ELEMENT_CONTENT_OR) |
| xmlSnprintfElementContent(buf, size, content->c2, 1); |
| else |
| xmlSnprintfElementContent(buf, size, content->c2, 0); |
| break; |
| case XML_ELEMENT_CONTENT_OR: |
| if ((content->c1->type == XML_ELEMENT_CONTENT_OR) || |
| (content->c1->type == XML_ELEMENT_CONTENT_SEQ)) |
| xmlSnprintfElementContent(buf, size, content->c1, 1); |
| else |
| xmlSnprintfElementContent(buf, size, content->c1, 0); |
| len = strlen(buf); |
| if (size - len < 50) { |
| if ((size - len > 4) && (buf[len - 1] != '.')) |
| strcat(buf, " ..."); |
| return; |
| } |
| strcat(buf, " | "); |
| if (content->c2->type == XML_ELEMENT_CONTENT_SEQ) |
| xmlSnprintfElementContent(buf, size, content->c2, 1); |
| else |
| xmlSnprintfElementContent(buf, size, content->c2, 0); |
| break; |
| } |
| if (glob) |
| strcat(buf, ")"); |
| switch (content->ocur) { |
| case XML_ELEMENT_CONTENT_ONCE: |
| break; |
| case XML_ELEMENT_CONTENT_OPT: |
| strcat(buf, "?"); |
| break; |
| case XML_ELEMENT_CONTENT_MULT: |
| strcat(buf, "*"); |
| break; |
| case XML_ELEMENT_CONTENT_PLUS: |
| strcat(buf, "+"); |
| break; |
| } |
| } |
| |
| /**************************************************************** |
| * * |
| * Registration of DTD declarations * |
| * * |
| ****************************************************************/ |
| |
| /** |
| * xmlCreateElementTable: |
| * |
| * create and initialize an empty element hash table. |
| * |
| * Returns the xmlElementTablePtr just created or NULL in case of error. |
| */ |
| static xmlElementTablePtr |
| xmlCreateElementTable(void) { |
| return(xmlHashCreate(0)); |
| } |
| |
| /** |
| * xmlFreeElement: |
| * @elem: An element |
| * |
| * Deallocate the memory used by an element definition |
| */ |
| static void |
| xmlFreeElement(xmlElementPtr elem) { |
| if (elem == NULL) return; |
| xmlUnlinkNode((xmlNodePtr) elem); |
| xmlFreeElementContent(elem->content); |
| if (elem->name != NULL) |
| xmlFree((xmlChar *) elem->name); |
| if (elem->prefix != NULL) |
| xmlFree((xmlChar *) elem->prefix); |
| xmlFree(elem); |
| } |
| |
| |
| /** |
| * xmlAddElementDecl: |
| * @ctxt: the validation context |
| * @dtd: pointer to the DTD |
| * @name: the entity name |
| * @type: the element type |
| * @content: the element content tree or NULL |
| * |
| * Register a new element declaration |
| * |
| * Returns NULL if not, othervise the entity |
| */ |
| xmlElementPtr |
| xmlAddElementDecl(xmlValidCtxtPtr ctxt, xmlDtdPtr dtd, const xmlChar *name, |
| xmlElementTypeVal type, |
| xmlElementContentPtr content) { |
| xmlElementPtr ret; |
| xmlElementTablePtr table; |
| xmlAttributePtr oldAttributes = NULL; |
| xmlChar *ns, *uqname; |
| |
| if (dtd == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddElementDecl: dtd == NULL\n"); |
| return(NULL); |
| } |
| if (name == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddElementDecl: name == NULL\n"); |
| return(NULL); |
| } |
| switch (type) { |
| case XML_ELEMENT_TYPE_EMPTY: |
| if (content != NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddElementDecl: content != NULL for EMPTY\n"); |
| return(NULL); |
| } |
| break; |
| case XML_ELEMENT_TYPE_ANY: |
| if (content != NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddElementDecl: content != NULL for ANY\n"); |
| return(NULL); |
| } |
| break; |
| case XML_ELEMENT_TYPE_MIXED: |
| if (content == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddElementDecl: content == NULL for MIXED\n"); |
| return(NULL); |
| } |
| break; |
| case XML_ELEMENT_TYPE_ELEMENT: |
| if (content == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddElementDecl: content == NULL for ELEMENT\n"); |
| return(NULL); |
| } |
| break; |
| default: |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddElementDecl: unknown type %d\n", type); |
| return(NULL); |
| } |
| |
| /* |
| * check if name is a QName |
| */ |
| uqname = xmlSplitQName2(name, &ns); |
| if (uqname != NULL) |
| name = uqname; |
| |
| /* |
| * Create the Element table if needed. |
| */ |
| table = (xmlElementTablePtr) dtd->elements; |
| if (table == NULL) { |
| table = xmlCreateElementTable(); |
| dtd->elements = (void *) table; |
| } |
| if (table == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddElementDecl: Table creation failed!\n"); |
| return(NULL); |
| } |
| |
| /* |
| * lookup old attributes inserted on an undefined element in the |
| * internal subset. |
| */ |
| if ((dtd->doc != NULL) && (dtd->doc->intSubset != NULL)) { |
| ret = xmlHashLookup2(dtd->doc->intSubset->elements, name, ns); |
| if ((ret != NULL) && (ret->etype == XML_ELEMENT_TYPE_UNDEFINED)) { |
| oldAttributes = ret->attributes; |
| ret->attributes = NULL; |
| xmlHashRemoveEntry2(dtd->doc->intSubset->elements, name, ns, NULL); |
| xmlFreeElement(ret); |
| } |
| } |
| |
| /* |
| * The element may already be present if one of its attribute |
| * was registered first |
| */ |
| ret = xmlHashLookup2(table, name, ns); |
| if (ret != NULL) { |
| if (ret->etype != XML_ELEMENT_TYPE_UNDEFINED) { |
| /* |
| * The element is already defined in this Dtd. |
| */ |
| VERROR(ctxt->userData, "Redefinition of element %s\n", name); |
| if (uqname != NULL) |
| xmlFree(uqname); |
| return(NULL); |
| } |
| } else { |
| ret = (xmlElementPtr) xmlMalloc(sizeof(xmlElement)); |
| if (ret == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddElementDecl: out of memory\n"); |
| return(NULL); |
| } |
| memset(ret, 0, sizeof(xmlElement)); |
| ret->type = XML_ELEMENT_DECL; |
| |
| /* |
| * fill the structure. |
| */ |
| ret->name = xmlStrdup(name); |
| ret->prefix = ns; |
| |
| /* |
| * Validity Check: |
| * Insertion must not fail |
| */ |
| if (xmlHashAddEntry2(table, name, ns, ret)) { |
| /* |
| * The element is already defined in this Dtd. |
| */ |
| VERROR(ctxt->userData, "Redefinition of element %s\n", name); |
| xmlFreeElement(ret); |
| if (uqname != NULL) |
| xmlFree(uqname); |
| return(NULL); |
| } |
| } |
| |
| /* |
| * Finish to fill the structure. |
| */ |
| ret->etype = type; |
| ret->content = xmlCopyElementContent(content); |
| ret->attributes = oldAttributes; |
| |
| /* |
| * Link it to the Dtd |
| */ |
| ret->parent = dtd; |
| ret->doc = dtd->doc; |
| if (dtd->last == NULL) { |
| dtd->children = dtd->last = (xmlNodePtr) ret; |
| } else { |
| dtd->last->next = (xmlNodePtr) ret; |
| ret->prev = dtd->last; |
| dtd->last = (xmlNodePtr) ret; |
| } |
| if (uqname != NULL) |
| xmlFree(uqname); |
| return(ret); |
| } |
| |
| /** |
| * xmlFreeElementTable: |
| * @table: An element table |
| * |
| * Deallocate the memory used by an element hash table. |
| */ |
| void |
| xmlFreeElementTable(xmlElementTablePtr table) { |
| xmlHashFree(table, (xmlHashDeallocator) xmlFreeElement); |
| } |
| |
| /** |
| * xmlCopyElement: |
| * @elem: An element |
| * |
| * Build a copy of an element. |
| * |
| * Returns the new xmlElementPtr or NULL in case of error. |
| */ |
| static xmlElementPtr |
| xmlCopyElement(xmlElementPtr elem) { |
| xmlElementPtr cur; |
| |
| cur = (xmlElementPtr) xmlMalloc(sizeof(xmlElement)); |
| if (cur == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlCopyElement: out of memory !\n"); |
| return(NULL); |
| } |
| memset(cur, 0, sizeof(xmlElement)); |
| cur->type = XML_ELEMENT_DECL; |
| cur->etype = elem->etype; |
| if (elem->name != NULL) |
| cur->name = xmlStrdup(elem->name); |
| else |
| cur->name = NULL; |
| if (elem->prefix != NULL) |
| cur->prefix = xmlStrdup(elem->prefix); |
| else |
| cur->prefix = NULL; |
| cur->content = xmlCopyElementContent(elem->content); |
| /* TODO : rebuild the attribute list on the copy */ |
| cur->attributes = NULL; |
| return(cur); |
| } |
| |
| /** |
| * xmlCopyElementTable: |
| * @table: An element table |
| * |
| * Build a copy of an element table. |
| * |
| * Returns the new xmlElementTablePtr or NULL in case of error. |
| */ |
| xmlElementTablePtr |
| xmlCopyElementTable(xmlElementTablePtr table) { |
| return((xmlElementTablePtr) xmlHashCopy(table, |
| (xmlHashCopier) xmlCopyElement)); |
| } |
| |
| /** |
| * xmlDumpElementDecl: |
| * @buf: the XML buffer output |
| * @elem: An element table |
| * |
| * This will dump the content of the element declaration as an XML |
| * DTD definition |
| */ |
| void |
| xmlDumpElementDecl(xmlBufferPtr buf, xmlElementPtr elem) { |
| switch (elem->etype) { |
| case XML_ELEMENT_TYPE_EMPTY: |
| xmlBufferWriteChar(buf, "<!ELEMENT "); |
| xmlBufferWriteCHAR(buf, elem->name); |
| xmlBufferWriteChar(buf, " EMPTY>\n"); |
| break; |
| case XML_ELEMENT_TYPE_ANY: |
| xmlBufferWriteChar(buf, "<!ELEMENT "); |
| xmlBufferWriteCHAR(buf, elem->name); |
| xmlBufferWriteChar(buf, " ANY>\n"); |
| break; |
| case XML_ELEMENT_TYPE_MIXED: |
| xmlBufferWriteChar(buf, "<!ELEMENT "); |
| xmlBufferWriteCHAR(buf, elem->name); |
| xmlBufferWriteChar(buf, " "); |
| xmlDumpElementContent(buf, elem->content, 1); |
| xmlBufferWriteChar(buf, ">\n"); |
| break; |
| case XML_ELEMENT_TYPE_ELEMENT: |
| xmlBufferWriteChar(buf, "<!ELEMENT "); |
| xmlBufferWriteCHAR(buf, elem->name); |
| xmlBufferWriteChar(buf, " "); |
| xmlDumpElementContent(buf, elem->content, 1); |
| xmlBufferWriteChar(buf, ">\n"); |
| break; |
| default: |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlDumpElementDecl: internal: unknown type %d\n", |
| elem->etype); |
| } |
| } |
| |
| /** |
| * xmlDumpElementTable: |
| * @buf: the XML buffer output |
| * @table: An element table |
| * |
| * This will dump the content of the element table as an XML DTD definition |
| */ |
| void |
| xmlDumpElementTable(xmlBufferPtr buf, xmlElementTablePtr table) { |
| xmlHashScan(table, (xmlHashScanner) xmlDumpElementDecl, buf); |
| } |
| |
| /** |
| * xmlCreateEnumeration: |
| * @name: the enumeration name or NULL |
| * |
| * create and initialize an enumeration attribute node. |
| * |
| * Returns the xmlEnumerationPtr just created or NULL in case |
| * of error. |
| */ |
| xmlEnumerationPtr |
| xmlCreateEnumeration(xmlChar *name) { |
| xmlEnumerationPtr ret; |
| |
| ret = (xmlEnumerationPtr) xmlMalloc(sizeof(xmlEnumeration)); |
| if (ret == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlCreateEnumeration : xmlMalloc(%ld) failed\n", |
| (long)sizeof(xmlEnumeration)); |
| return(NULL); |
| } |
| memset(ret, 0, sizeof(xmlEnumeration)); |
| |
| if (name != NULL) |
| ret->name = xmlStrdup(name); |
| return(ret); |
| } |
| |
| /** |
| * xmlFreeEnumeration: |
| * @cur: the tree to free. |
| * |
| * free an enumeration attribute node (recursive). |
| */ |
| void |
| xmlFreeEnumeration(xmlEnumerationPtr cur) { |
| if (cur == NULL) return; |
| |
| if (cur->next != NULL) xmlFreeEnumeration(cur->next); |
| |
| if (cur->name != NULL) xmlFree((xmlChar *) cur->name); |
| xmlFree(cur); |
| } |
| |
| /** |
| * xmlCopyEnumeration: |
| * @cur: the tree to copy. |
| * |
| * Copy an enumeration attribute node (recursive). |
| * |
| * Returns the xmlEnumerationPtr just created or NULL in case |
| * of error. |
| */ |
| xmlEnumerationPtr |
| xmlCopyEnumeration(xmlEnumerationPtr cur) { |
| xmlEnumerationPtr ret; |
| |
| if (cur == NULL) return(NULL); |
| ret = xmlCreateEnumeration((xmlChar *) cur->name); |
| |
| if (cur->next != NULL) ret->next = xmlCopyEnumeration(cur->next); |
| else ret->next = NULL; |
| |
| return(ret); |
| } |
| |
| /** |
| * xmlDumpEnumeration: |
| * @buf: the XML buffer output |
| * @enum: An enumeration |
| * |
| * This will dump the content of the enumeration |
| */ |
| static void |
| xmlDumpEnumeration(xmlBufferPtr buf, xmlEnumerationPtr cur) { |
| if (cur == NULL) return; |
| |
| xmlBufferWriteCHAR(buf, cur->name); |
| if (cur->next == NULL) |
| xmlBufferWriteChar(buf, ")"); |
| else { |
| xmlBufferWriteChar(buf, " | "); |
| xmlDumpEnumeration(buf, cur->next); |
| } |
| } |
| |
| /** |
| * xmlCreateAttributeTable: |
| * |
| * create and initialize an empty attribute hash table. |
| * |
| * Returns the xmlAttributeTablePtr just created or NULL in case |
| * of error. |
| */ |
| static xmlAttributeTablePtr |
| xmlCreateAttributeTable(void) { |
| return(xmlHashCreate(0)); |
| } |
| |
| /** |
| * xmlScanAttributeDeclCallback: |
| * @attr: the attribute decl |
| * @list: the list to update |
| * |
| * Callback called by xmlScanAttributeDecl when a new attribute |
| * has to be entered in the list. |
| */ |
| static void |
| xmlScanAttributeDeclCallback(xmlAttributePtr attr, xmlAttributePtr *list, |
| const xmlChar* name ATTRIBUTE_UNUSED) { |
| attr->nexth = *list; |
| *list = attr; |
| } |
| |
| /** |
| * xmlScanAttributeDecl: |
| * @dtd: pointer to the DTD |
| * @elem: the element name |
| * |
| * When inserting a new element scan the DtD for existing attributes |
| * for taht element and initialize the Attribute chain |
| * |
| * Returns the pointer to the first attribute decl in the chain, |
| * possibly NULL. |
| */ |
| xmlAttributePtr |
| xmlScanAttributeDecl(xmlDtdPtr dtd, const xmlChar *elem) { |
| xmlAttributePtr ret = NULL; |
| xmlAttributeTablePtr table; |
| |
| if (dtd == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlScanAttributeDecl: dtd == NULL\n"); |
| return(NULL); |
| } |
| if (elem == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlScanAttributeDecl: elem == NULL\n"); |
| return(NULL); |
| } |
| table = (xmlAttributeTablePtr) dtd->attributes; |
| if (table == NULL) |
| return(NULL); |
| |
| /* WRONG !!! */ |
| xmlHashScan3(table, NULL, NULL, elem, |
| (xmlHashScanner) xmlScanAttributeDeclCallback, &ret); |
| return(ret); |
| } |
| |
| /** |
| * xmlScanIDAttributeDecl: |
| * @ctxt: the validation context |
| * @elem: the element name |
| * |
| * Verify that the element don't have too many ID attributes |
| * declared. |
| * |
| * Returns the number of ID attributes found. |
| */ |
| static int |
| xmlScanIDAttributeDecl(xmlValidCtxtPtr ctxt, xmlElementPtr elem) { |
| xmlAttributePtr cur; |
| int ret = 0; |
| |
| if (elem == NULL) return(0); |
| cur = elem->attributes; |
| while (cur != NULL) { |
| if (cur->atype == XML_ATTRIBUTE_ID) { |
| ret ++; |
| if (ret > 1) |
| VERROR(ctxt->userData, |
| "Element %s has too many ID attributes defined : %s\n", |
| elem->name, cur->name); |
| } |
| cur = cur->nexth; |
| } |
| return(ret); |
| } |
| |
| /** |
| * xmlFreeAttribute: |
| * @elem: An attribute |
| * |
| * Deallocate the memory used by an attribute definition |
| */ |
| static void |
| xmlFreeAttribute(xmlAttributePtr attr) { |
| if (attr == NULL) return; |
| xmlUnlinkNode((xmlNodePtr) attr); |
| if (attr->tree != NULL) |
| xmlFreeEnumeration(attr->tree); |
| if (attr->elem != NULL) |
| xmlFree((xmlChar *) attr->elem); |
| if (attr->name != NULL) |
| xmlFree((xmlChar *) attr->name); |
| if (attr->defaultValue != NULL) |
| xmlFree((xmlChar *) attr->defaultValue); |
| if (attr->prefix != NULL) |
| xmlFree((xmlChar *) attr->prefix); |
| xmlFree(attr); |
| } |
| |
| |
| /** |
| * xmlAddAttributeDecl: |
| * @ctxt: the validation context |
| * @dtd: pointer to the DTD |
| * @elem: the element name |
| * @name: the attribute name |
| * @ns: the attribute namespace prefix |
| * @type: the attribute type |
| * @def: the attribute default type |
| * @defaultValue: the attribute default value |
| * @tree: if it's an enumeration, the associated list |
| * |
| * Register a new attribute declaration |
| * Note that @tree becomes the ownership of the DTD |
| * |
| * Returns NULL if not new, othervise the attribute decl |
| */ |
| xmlAttributePtr |
| xmlAddAttributeDecl(xmlValidCtxtPtr ctxt, xmlDtdPtr dtd, const xmlChar *elem, |
| const xmlChar *name, const xmlChar *ns, |
| xmlAttributeType type, xmlAttributeDefault def, |
| const xmlChar *defaultValue, xmlEnumerationPtr tree) { |
| xmlAttributePtr ret; |
| xmlAttributeTablePtr table; |
| xmlElementPtr elemDef; |
| |
| if (dtd == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddAttributeDecl: dtd == NULL\n"); |
| xmlFreeEnumeration(tree); |
| return(NULL); |
| } |
| if (name == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddAttributeDecl: name == NULL\n"); |
| xmlFreeEnumeration(tree); |
| return(NULL); |
| } |
| if (elem == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddAttributeDecl: elem == NULL\n"); |
| xmlFreeEnumeration(tree); |
| return(NULL); |
| } |
| /* |
| * Check the type and possibly the default value. |
| */ |
| switch (type) { |
| case XML_ATTRIBUTE_CDATA: |
| break; |
| case XML_ATTRIBUTE_ID: |
| break; |
| case XML_ATTRIBUTE_IDREF: |
| break; |
| case XML_ATTRIBUTE_IDREFS: |
| break; |
| case XML_ATTRIBUTE_ENTITY: |
| break; |
| case XML_ATTRIBUTE_ENTITIES: |
| break; |
| case XML_ATTRIBUTE_NMTOKEN: |
| break; |
| case XML_ATTRIBUTE_NMTOKENS: |
| break; |
| case XML_ATTRIBUTE_ENUMERATION: |
| break; |
| case XML_ATTRIBUTE_NOTATION: |
| break; |
| default: |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddAttributeDecl: unknown type %d\n", type); |
| xmlFreeEnumeration(tree); |
| return(NULL); |
| } |
| if ((defaultValue != NULL) && |
| (!xmlValidateAttributeValue(type, defaultValue))) { |
| VERROR(ctxt->userData, "Attribute %s on %s: invalid default value\n", |
| elem, name, defaultValue); |
| defaultValue = NULL; |
| } |
| |
| /* |
| * Create the Attribute table if needed. |
| */ |
| table = (xmlAttributeTablePtr) dtd->attributes; |
| if (table == NULL) { |
| table = xmlCreateAttributeTable(); |
| dtd->attributes = (void *) table; |
| } |
| if (table == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddAttributeDecl: Table creation failed!\n"); |
| return(NULL); |
| } |
| |
| |
| ret = (xmlAttributePtr) xmlMalloc(sizeof(xmlAttribute)); |
| if (ret == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddAttributeDecl: out of memory\n"); |
| return(NULL); |
| } |
| memset(ret, 0, sizeof(xmlAttribute)); |
| ret->type = XML_ATTRIBUTE_DECL; |
| |
| /* |
| * fill the structure. |
| */ |
| ret->atype = type; |
| ret->name = xmlStrdup(name); |
| ret->prefix = xmlStrdup(ns); |
| ret->elem = xmlStrdup(elem); |
| ret->def = def; |
| ret->tree = tree; |
| if (defaultValue != NULL) |
| ret->defaultValue = xmlStrdup(defaultValue); |
| |
| /* |
| * Validity Check: |
| * Search the DTD for previous declarations of the ATTLIST |
| */ |
| if (xmlHashAddEntry3(table, name, ns, elem, ret) < 0) { |
| /* |
| * The attribute is already defined in this Dtd. |
| */ |
| VWARNING(ctxt->userData, |
| "Attribute %s on %s: already defined\n", |
| name, elem); |
| xmlFreeAttribute(ret); |
| return(NULL); |
| } |
| |
| /* |
| * Validity Check: |
| * Multiple ID per element |
| */ |
| elemDef = xmlGetDtdElementDesc2(dtd, elem, 1); |
| if (elemDef != NULL) { |
| |
| if ((type == XML_ATTRIBUTE_ID) && |
| (xmlScanIDAttributeDecl(NULL, elemDef) != 0)) |
| VERROR(ctxt->userData, |
| "Element %s has too may ID attributes defined : %s\n", |
| elem, name); |
| /* |
| * Insert namespace default def first they need to be |
| * processed firt. |
| */ |
| if ((xmlStrEqual(ret->name, BAD_CAST "xmlns")) || |
| ((ret->prefix != NULL && |
| (xmlStrEqual(ret->prefix, BAD_CAST "xmlns"))))) { |
| ret->nexth = elemDef->attributes; |
| elemDef->attributes = ret; |
| } else { |
| xmlAttributePtr tmp = elemDef->attributes; |
| |
| while ((tmp != NULL) && |
| ((xmlStrEqual(tmp->name, BAD_CAST "xmlns")) || |
| ((ret->prefix != NULL && |
| (xmlStrEqual(ret->prefix, BAD_CAST "xmlns")))))) { |
| if (tmp->nexth == NULL) |
| break; |
| tmp = tmp->nexth; |
| } |
| if (tmp != NULL) { |
| ret->nexth = tmp->nexth; |
| tmp->nexth = ret; |
| } else { |
| ret->nexth = elemDef->attributes; |
| elemDef->attributes = ret; |
| } |
| } |
| } |
| |
| /* |
| * Link it to the Dtd |
| */ |
| ret->parent = dtd; |
| ret->doc = dtd->doc; |
| if (dtd->last == NULL) { |
| dtd->children = dtd->last = (xmlNodePtr) ret; |
| } else { |
| dtd->last->next = (xmlNodePtr) ret; |
| ret->prev = dtd->last; |
| dtd->last = (xmlNodePtr) ret; |
| } |
| return(ret); |
| } |
| |
| /** |
| * xmlFreeAttributeTable: |
| * @table: An attribute table |
| * |
| * Deallocate the memory used by an entities hash table. |
| */ |
| void |
| xmlFreeAttributeTable(xmlAttributeTablePtr table) { |
| xmlHashFree(table, (xmlHashDeallocator) xmlFreeAttribute); |
| } |
| |
| /** |
| * xmlCopyAttribute: |
| * @attr: An attribute |
| * |
| * Build a copy of an attribute. |
| * |
| * Returns the new xmlAttributePtr or NULL in case of error. |
| */ |
| static xmlAttributePtr |
| xmlCopyAttribute(xmlAttributePtr attr) { |
| xmlAttributePtr cur; |
| |
| cur = (xmlAttributePtr) xmlMalloc(sizeof(xmlAttribute)); |
| if (cur == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlCopyAttribute: out of memory !\n"); |
| return(NULL); |
| } |
| memset(cur, 0, sizeof(xmlAttribute)); |
| cur->atype = attr->atype; |
| cur->def = attr->def; |
| cur->tree = xmlCopyEnumeration(attr->tree); |
| if (attr->elem != NULL) |
| cur->elem = xmlStrdup(attr->elem); |
| if (attr->name != NULL) |
| cur->name = xmlStrdup(attr->name); |
| if (attr->defaultValue != NULL) |
| cur->defaultValue = xmlStrdup(attr->defaultValue); |
| return(cur); |
| } |
| |
| /** |
| * xmlCopyAttributeTable: |
| * @table: An attribute table |
| * |
| * Build a copy of an attribute table. |
| * |
| * Returns the new xmlAttributeTablePtr or NULL in case of error. |
| */ |
| xmlAttributeTablePtr |
| xmlCopyAttributeTable(xmlAttributeTablePtr table) { |
| return((xmlAttributeTablePtr) xmlHashCopy(table, |
| (xmlHashCopier) xmlCopyAttribute)); |
| } |
| |
| /** |
| * xmlDumpAttributeDecl: |
| * @buf: the XML buffer output |
| * @attr: An attribute declaration |
| * |
| * This will dump the content of the attribute declaration as an XML |
| * DTD definition |
| */ |
| void |
| xmlDumpAttributeDecl(xmlBufferPtr buf, xmlAttributePtr attr) { |
| xmlBufferWriteChar(buf, "<!ATTLIST "); |
| xmlBufferWriteCHAR(buf, attr->elem); |
| xmlBufferWriteChar(buf, " "); |
| if (attr->prefix != NULL) { |
| xmlBufferWriteCHAR(buf, attr->prefix); |
| xmlBufferWriteChar(buf, ":"); |
| } |
| xmlBufferWriteCHAR(buf, attr->name); |
| switch (attr->atype) { |
| case XML_ATTRIBUTE_CDATA: |
| xmlBufferWriteChar(buf, " CDATA"); |
| break; |
| case XML_ATTRIBUTE_ID: |
| xmlBufferWriteChar(buf, " ID"); |
| break; |
| case XML_ATTRIBUTE_IDREF: |
| xmlBufferWriteChar(buf, " IDREF"); |
| break; |
| case XML_ATTRIBUTE_IDREFS: |
| xmlBufferWriteChar(buf, " IDREFS"); |
| break; |
| case XML_ATTRIBUTE_ENTITY: |
| xmlBufferWriteChar(buf, " ENTITY"); |
| break; |
| case XML_ATTRIBUTE_ENTITIES: |
| xmlBufferWriteChar(buf, " ENTITIES"); |
| break; |
| case XML_ATTRIBUTE_NMTOKEN: |
| xmlBufferWriteChar(buf, " NMTOKEN"); |
| break; |
| case XML_ATTRIBUTE_NMTOKENS: |
| xmlBufferWriteChar(buf, " NMTOKENS"); |
| break; |
| case XML_ATTRIBUTE_ENUMERATION: |
| xmlBufferWriteChar(buf, " ("); |
| xmlDumpEnumeration(buf, attr->tree); |
| break; |
| case XML_ATTRIBUTE_NOTATION: |
| xmlBufferWriteChar(buf, " NOTATION ("); |
| xmlDumpEnumeration(buf, attr->tree); |
| break; |
| default: |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlDumpAttributeTable: internal: unknown type %d\n", |
| attr->atype); |
| } |
| switch (attr->def) { |
| case XML_ATTRIBUTE_NONE: |
| break; |
| case XML_ATTRIBUTE_REQUIRED: |
| xmlBufferWriteChar(buf, " #REQUIRED"); |
| break; |
| case XML_ATTRIBUTE_IMPLIED: |
| xmlBufferWriteChar(buf, " #IMPLIED"); |
| break; |
| case XML_ATTRIBUTE_FIXED: |
| xmlBufferWriteChar(buf, " #FIXED"); |
| break; |
| default: |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlDumpAttributeTable: internal: unknown default %d\n", |
| attr->def); |
| } |
| if (attr->defaultValue != NULL) { |
| xmlBufferWriteChar(buf, " "); |
| xmlBufferWriteQuotedString(buf, attr->defaultValue); |
| } |
| xmlBufferWriteChar(buf, ">\n"); |
| } |
| |
| /** |
| * xmlDumpAttributeTable: |
| * @buf: the XML buffer output |
| * @table: An attribute table |
| * |
| * This will dump the content of the attribute table as an XML DTD definition |
| */ |
| void |
| xmlDumpAttributeTable(xmlBufferPtr buf, xmlAttributeTablePtr table) { |
| xmlHashScan(table, (xmlHashScanner) xmlDumpAttributeDecl, buf); |
| } |
| |
| /************************************************************************ |
| * * |
| * NOTATIONs * |
| * * |
| ************************************************************************/ |
| /** |
| * xmlCreateNotationTable: |
| * |
| * create and initialize an empty notation hash table. |
| * |
| * Returns the xmlNotationTablePtr just created or NULL in case |
| * of error. |
| */ |
| static xmlNotationTablePtr |
| xmlCreateNotationTable(void) { |
| return(xmlHashCreate(0)); |
| } |
| |
| /** |
| * xmlFreeNotation: |
| * @not: A notation |
| * |
| * Deallocate the memory used by an notation definition |
| */ |
| static void |
| xmlFreeNotation(xmlNotationPtr nota) { |
| if (nota == NULL) return; |
| if (nota->name != NULL) |
| xmlFree((xmlChar *) nota->name); |
| if (nota->PublicID != NULL) |
| xmlFree((xmlChar *) nota->PublicID); |
| if (nota->SystemID != NULL) |
| xmlFree((xmlChar *) nota->SystemID); |
| xmlFree(nota); |
| } |
| |
| |
| /** |
| * xmlAddNotationDecl: |
| * @dtd: pointer to the DTD |
| * @ctxt: the validation context |
| * @name: the entity name |
| * @PublicID: the public identifier or NULL |
| * @SystemID: the system identifier or NULL |
| * |
| * Register a new notation declaration |
| * |
| * Returns NULL if not, othervise the entity |
| */ |
| xmlNotationPtr |
| xmlAddNotationDecl(xmlValidCtxtPtr ctxt ATTRIBUTE_UNUSED, xmlDtdPtr dtd, |
| const xmlChar *name, |
| const xmlChar *PublicID, const xmlChar *SystemID) { |
| xmlNotationPtr ret; |
| xmlNotationTablePtr table; |
| |
| if (dtd == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddNotationDecl: dtd == NULL\n"); |
| return(NULL); |
| } |
| if (name == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddNotationDecl: name == NULL\n"); |
| return(NULL); |
| } |
| if ((PublicID == NULL) && (SystemID == NULL)) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddNotationDecl: no PUBLIC ID nor SYSTEM ID\n"); |
| } |
| |
| /* |
| * Create the Notation table if needed. |
| */ |
| table = (xmlNotationTablePtr) dtd->notations; |
| if (table == NULL) |
| dtd->notations = table = xmlCreateNotationTable(); |
| if (table == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddNotationDecl: Table creation failed!\n"); |
| return(NULL); |
| } |
| |
| ret = (xmlNotationPtr) xmlMalloc(sizeof(xmlNotation)); |
| if (ret == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddNotationDecl: out of memory\n"); |
| return(NULL); |
| } |
| memset(ret, 0, sizeof(xmlNotation)); |
| |
| /* |
| * fill the structure. |
| */ |
| ret->name = xmlStrdup(name); |
| if (SystemID != NULL) |
| ret->SystemID = xmlStrdup(SystemID); |
| if (PublicID != NULL) |
| ret->PublicID = xmlStrdup(PublicID); |
| |
| /* |
| * Validity Check: |
| * Check the DTD for previous declarations of the ATTLIST |
| */ |
| if (xmlHashAddEntry(table, name, ret)) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddNotationDecl: %s already defined\n", name); |
| xmlFreeNotation(ret); |
| return(NULL); |
| } |
| return(ret); |
| } |
| |
| /** |
| * xmlFreeNotationTable: |
| * @table: An notation table |
| * |
| * Deallocate the memory used by an entities hash table. |
| */ |
| void |
| xmlFreeNotationTable(xmlNotationTablePtr table) { |
| xmlHashFree(table, (xmlHashDeallocator) xmlFreeNotation); |
| } |
| |
| /** |
| * xmlCopyNotation: |
| * @nota: A notation |
| * |
| * Build a copy of a notation. |
| * |
| * Returns the new xmlNotationPtr or NULL in case of error. |
| */ |
| static xmlNotationPtr |
| xmlCopyNotation(xmlNotationPtr nota) { |
| xmlNotationPtr cur; |
| |
| cur = (xmlNotationPtr) xmlMalloc(sizeof(xmlNotation)); |
| if (cur == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlCopyNotation: out of memory !\n"); |
| return(NULL); |
| } |
| if (nota->name != NULL) |
| cur->name = xmlStrdup(nota->name); |
| else |
| cur->name = NULL; |
| if (nota->PublicID != NULL) |
| cur->PublicID = xmlStrdup(nota->PublicID); |
| else |
| cur->PublicID = NULL; |
| if (nota->SystemID != NULL) |
| cur->SystemID = xmlStrdup(nota->SystemID); |
| else |
| cur->SystemID = NULL; |
| return(cur); |
| } |
| |
| /** |
| * xmlCopyNotationTable: |
| * @table: A notation table |
| * |
| * Build a copy of a notation table. |
| * |
| * Returns the new xmlNotationTablePtr or NULL in case of error. |
| */ |
| xmlNotationTablePtr |
| xmlCopyNotationTable(xmlNotationTablePtr table) { |
| return((xmlNotationTablePtr) xmlHashCopy(table, |
| (xmlHashCopier) xmlCopyNotation)); |
| } |
| |
| /** |
| * xmlDumpNotationDecl: |
| * @buf: the XML buffer output |
| * @nota: A notation declaration |
| * |
| * This will dump the content the notation declaration as an XML DTD definition |
| */ |
| void |
| xmlDumpNotationDecl(xmlBufferPtr buf, xmlNotationPtr nota) { |
| xmlBufferWriteChar(buf, "<!NOTATION "); |
| xmlBufferWriteCHAR(buf, nota->name); |
| if (nota->PublicID != NULL) { |
| xmlBufferWriteChar(buf, " PUBLIC "); |
| xmlBufferWriteQuotedString(buf, nota->PublicID); |
| if (nota->SystemID != NULL) { |
| xmlBufferWriteChar(buf, " "); |
| xmlBufferWriteCHAR(buf, nota->SystemID); |
| } |
| } else { |
| xmlBufferWriteChar(buf, " SYSTEM "); |
| xmlBufferWriteCHAR(buf, nota->SystemID); |
| } |
| xmlBufferWriteChar(buf, " >\n"); |
| } |
| |
| /** |
| * xmlDumpNotationTable: |
| * @buf: the XML buffer output |
| * @table: A notation table |
| * |
| * This will dump the content of the notation table as an XML DTD definition |
| */ |
| void |
| xmlDumpNotationTable(xmlBufferPtr buf, xmlNotationTablePtr table) { |
| xmlHashScan(table, (xmlHashScanner) xmlDumpNotationDecl, buf); |
| } |
| |
| /************************************************************************ |
| * * |
| * IDs * |
| * * |
| ************************************************************************/ |
| /** |
| * xmlCreateIDTable: |
| * |
| * create and initialize an empty id hash table. |
| * |
| * Returns the xmlIDTablePtr just created or NULL in case |
| * of error. |
| */ |
| static xmlIDTablePtr |
| xmlCreateIDTable(void) { |
| return(xmlHashCreate(0)); |
| } |
| |
| /** |
| * xmlFreeID: |
| * @not: A id |
| * |
| * Deallocate the memory used by an id definition |
| */ |
| static void |
| xmlFreeID(xmlIDPtr id) { |
| if (id == NULL) return; |
| if (id->value != NULL) |
| xmlFree((xmlChar *) id->value); |
| xmlFree(id); |
| } |
| |
| /** |
| * xmlAddID: |
| * @ctxt: the validation context |
| * @doc: pointer to the document |
| * @value: the value name |
| * @attr: the attribute holding the ID |
| * |
| * Register a new id declaration |
| * |
| * Returns NULL if not, othervise the new xmlIDPtr |
| */ |
| xmlIDPtr |
| xmlAddID(xmlValidCtxtPtr ctxt, xmlDocPtr doc, const xmlChar *value, |
| xmlAttrPtr attr) { |
| xmlIDPtr ret; |
| xmlIDTablePtr table; |
| |
| if (doc == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddIDDecl: doc == NULL\n"); |
| return(NULL); |
| } |
| if (value == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddIDDecl: value == NULL\n"); |
| return(NULL); |
| } |
| if (attr == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddIDDecl: attr == NULL\n"); |
| return(NULL); |
| } |
| |
| /* |
| * Create the ID table if needed. |
| */ |
| table = (xmlIDTablePtr) doc->ids; |
| if (table == NULL) |
| doc->ids = table = xmlCreateIDTable(); |
| if (table == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddID: Table creation failed!\n"); |
| return(NULL); |
| } |
| |
| ret = (xmlIDPtr) xmlMalloc(sizeof(xmlID)); |
| if (ret == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddID: out of memory\n"); |
| return(NULL); |
| } |
| |
| /* |
| * fill the structure. |
| */ |
| ret->value = xmlStrdup(value); |
| ret->attr = attr; |
| |
| if (xmlHashAddEntry(table, value, ret) < 0) { |
| /* |
| * The id is already defined in this Dtd. |
| */ |
| VERROR(ctxt->userData, "ID %s already defined\n", value); |
| xmlFreeID(ret); |
| return(NULL); |
| } |
| return(ret); |
| } |
| |
| /** |
| * xmlFreeIDTable: |
| * @table: An id table |
| * |
| * Deallocate the memory used by an ID hash table. |
| */ |
| void |
| xmlFreeIDTable(xmlIDTablePtr table) { |
| xmlHashFree(table, (xmlHashDeallocator) xmlFreeID); |
| } |
| |
| /** |
| * xmlIsID: |
| * @doc: the document |
| * @elem: the element carrying the attribute |
| * @attr: the attribute |
| * |
| * Determine whether an attribute is of type ID. In case we have Dtd(s) |
| * then this is simple, otherwise we use an heuristic: name ID (upper |
| * or lowercase). |
| * |
| * Returns 0 or 1 depending on the lookup result |
| */ |
| int |
| xmlIsID(xmlDocPtr doc, xmlNodePtr elem, xmlAttrPtr attr) { |
| if (doc == NULL) return(0); |
| if (attr == NULL) return(0); |
| if ((doc->intSubset == NULL) && (doc->extSubset == NULL)) { |
| return(0); |
| } else if (doc->type == XML_HTML_DOCUMENT_NODE) { |
| if ((xmlStrEqual(BAD_CAST "id", attr->name)) || |
| (xmlStrEqual(BAD_CAST "name", attr->name))) |
| return(1); |
| return(0); |
| } else { |
| xmlAttributePtr attrDecl; |
| |
| if (elem == NULL) return(0); |
| attrDecl = xmlGetDtdAttrDesc(doc->intSubset, elem->name, attr->name); |
| if ((attrDecl == NULL) && (doc->extSubset != NULL)) |
| attrDecl = xmlGetDtdAttrDesc(doc->extSubset, elem->name, |
| attr->name); |
| |
| if ((attrDecl != NULL) && (attrDecl->atype == XML_ATTRIBUTE_ID)) |
| return(1); |
| } |
| return(0); |
| } |
| |
| /** |
| * xmlRemoveID |
| * @doc: the document |
| * @attr: the attribute |
| * |
| * Remove the given attribute from the ID table maintained internally. |
| * |
| * Returns -1 if the lookup failed and 0 otherwise |
| */ |
| int |
| xmlRemoveID(xmlDocPtr doc, xmlAttrPtr attr) { |
| xmlAttrPtr cur; |
| xmlIDTablePtr table; |
| xmlChar *ID; |
| |
| if (doc == NULL) return(-1); |
| if (attr == NULL) return(-1); |
| table = (xmlIDTablePtr) doc->ids; |
| if (table == NULL) |
| return(-1); |
| |
| if (attr == NULL) |
| return(-1); |
| ID = xmlNodeListGetString(doc, attr->children, 1); |
| if (ID == NULL) |
| return(-1); |
| cur = xmlHashLookup(table, ID); |
| if (cur != attr) { |
| xmlFree(ID); |
| return(-1); |
| } |
| xmlHashUpdateEntry(table, ID, NULL, (xmlHashDeallocator) xmlFreeID); |
| xmlFree(ID); |
| return(0); |
| } |
| |
| /** |
| * xmlGetID: |
| * @doc: pointer to the document |
| * @ID: the ID value |
| * |
| * Search the attribute declaring the given ID |
| * |
| * Returns NULL if not found, otherwise the xmlAttrPtr defining the ID |
| */ |
| xmlAttrPtr |
| xmlGetID(xmlDocPtr doc, const xmlChar *ID) { |
| xmlIDTablePtr table; |
| xmlIDPtr id; |
| |
| if (doc == NULL) { |
| xmlGenericError(xmlGenericErrorContext, "xmlGetID: doc == NULL\n"); |
| return(NULL); |
| } |
| |
| if (ID == NULL) { |
| xmlGenericError(xmlGenericErrorContext, "xmlGetID: ID == NULL\n"); |
| return(NULL); |
| } |
| |
| table = (xmlIDTablePtr) doc->ids; |
| if (table == NULL) |
| return(NULL); |
| |
| id = xmlHashLookup(table, ID); |
| if (id == NULL) |
| return(NULL); |
| return(id->attr); |
| } |
| |
| /************************************************************************ |
| * * |
| * Refs * |
| * * |
| ************************************************************************/ |
| typedef struct xmlRemoveMemo_t |
| { |
| xmlListPtr l; |
| xmlAttrPtr ap; |
| } xmlRemoveMemo; |
| |
| typedef xmlRemoveMemo *xmlRemoveMemoPtr; |
| |
| typedef struct xmlValidateMemo_t |
| { |
| xmlValidCtxtPtr ctxt; |
| const xmlChar *name; |
| } xmlValidateMemo; |
| |
| typedef xmlValidateMemo *xmlValidateMemoPtr; |
| |
| /** |
| * xmlCreateRefTable: |
| * |
| * create and initialize an empty ref hash table. |
| * |
| * Returns the xmlRefTablePtr just created or NULL in case |
| * of error. |
| */ |
| static xmlRefTablePtr |
| xmlCreateRefTable(void) { |
| return(xmlHashCreate(0)); |
| } |
| |
| /** |
| * xmlFreeRef: |
| * @lk: A list link |
| * |
| * Deallocate the memory used by a ref definition |
| */ |
| static void |
| xmlFreeRef(xmlLinkPtr lk) { |
| xmlRefPtr ref = (xmlRefPtr)xmlLinkGetData(lk); |
| if (ref == NULL) return; |
| if (ref->value != NULL) |
| xmlFree((xmlChar *)ref->value); |
| xmlFree(ref); |
| } |
| |
| /** |
| * xmlFreeRefList: |
| * @list_ref: A list of references. |
| * |
| * Deallocate the memory used by a list of references |
| */ |
| static void |
| xmlFreeRefList(xmlListPtr list_ref) { |
| if (list_ref == NULL) return; |
| xmlListDelete(list_ref); |
| } |
| |
| /** |
| * xmlWalkRemoveRef: |
| * @data: Contents of current link |
| * @user: Value supplied by the user |
| * |
| * Return 0 to abort the walk or 1 to continue |
| */ |
| static int |
| xmlWalkRemoveRef(const void *data, const void *user) |
| { |
| xmlAttrPtr attr0 = ((xmlRefPtr)data)->attr; |
| xmlAttrPtr attr1 = ((xmlRemoveMemoPtr)user)->ap; |
| xmlListPtr ref_list = ((xmlRemoveMemoPtr)user)->l; |
| |
| if (attr0 == attr1) { /* Matched: remove and terminate walk */ |
| xmlListRemoveFirst(ref_list, (void *)data); |
| return 0; |
| } |
| return 1; |
| } |
| |
| /** |
| * xmlAddRef: |
| * @ctxt: the validation context |
| * @doc: pointer to the document |
| * @value: the value name |
| * @attr: the attribute holding the Ref |
| * |
| * Register a new ref declaration |
| * |
| * Returns NULL if not, othervise the new xmlRefPtr |
| */ |
| xmlRefPtr |
| xmlAddRef(xmlValidCtxtPtr ctxt ATTRIBUTE_UNUSED, xmlDocPtr doc, const xmlChar *value, |
| xmlAttrPtr attr) { |
| xmlRefPtr ret; |
| xmlRefTablePtr table; |
| xmlListPtr ref_list; |
| |
| if (doc == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddRefDecl: doc == NULL\n"); |
| return(NULL); |
| } |
| if (value == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddRefDecl: value == NULL\n"); |
| return(NULL); |
| } |
| if (attr == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddRefDecl: attr == NULL\n"); |
| return(NULL); |
| } |
| |
| /* |
| * Create the Ref table if needed. |
| */ |
| table = (xmlRefTablePtr) doc->refs; |
| if (table == NULL) |
| doc->refs = table = xmlCreateRefTable(); |
| if (table == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddRef: Table creation failed!\n"); |
| return(NULL); |
| } |
| |
| ret = (xmlRefPtr) xmlMalloc(sizeof(xmlRef)); |
| if (ret == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddRef: out of memory\n"); |
| return(NULL); |
| } |
| |
| /* |
| * fill the structure. |
| */ |
| ret->value = xmlStrdup(value); |
| ret->attr = attr; |
| |
| /* To add a reference :- |
| * References are maintained as a list of references, |
| * Lookup the entry, if no entry create new nodelist |
| * Add the owning node to the NodeList |
| * Return the ref |
| */ |
| |
| if (NULL == (ref_list = xmlHashLookup(table, value))) { |
| if (NULL == (ref_list = xmlListCreate(xmlFreeRef, NULL))) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddRef: Reference list creation failed!\n"); |
| return(NULL); |
| } |
| if (xmlHashAddEntry(table, value, ref_list) < 0) { |
| xmlListDelete(ref_list); |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlAddRef: Reference list insertion failed!\n"); |
| return(NULL); |
| } |
| } |
| xmlListInsert(ref_list, ret); |
| return(ret); |
| } |
| |
| /** |
| * xmlFreeRefTable: |
| * @table: An ref table |
| * |
| * Deallocate the memory used by an Ref hash table. |
| */ |
| void |
| xmlFreeRefTable(xmlRefTablePtr table) { |
| xmlHashFree(table, (xmlHashDeallocator) xmlFreeRefList); |
| } |
| |
| /** |
| * xmlIsRef: |
| * @doc: the document |
| * @elem: the element carrying the attribute |
| * @attr: the attribute |
| * |
| * Determine whether an attribute is of type Ref. In case we have Dtd(s) |
| * then this is simple, otherwise we use an heuristic: name Ref (upper |
| * or lowercase). |
| * |
| * Returns 0 or 1 depending on the lookup result |
| */ |
| int |
| xmlIsRef(xmlDocPtr doc, xmlNodePtr elem, xmlAttrPtr attr) { |
| if ((doc->intSubset == NULL) && (doc->extSubset == NULL)) { |
| return(0); |
| } else if (doc->type == XML_HTML_DOCUMENT_NODE) { |
| /* TODO @@@ */ |
| return(0); |
| } else { |
| xmlAttributePtr attrDecl; |
| |
| attrDecl = xmlGetDtdAttrDesc(doc->intSubset, elem->name, attr->name); |
| if ((attrDecl == NULL) && (doc->extSubset != NULL)) |
| attrDecl = xmlGetDtdAttrDesc(doc->extSubset, |
| elem->name, attr->name); |
| |
| if ((attrDecl != NULL) && |
| (attrDecl->atype == XML_ATTRIBUTE_IDREF || |
| attrDecl->atype == XML_ATTRIBUTE_IDREFS)) |
| return(1); |
| } |
| return(0); |
| } |
| |
| /** |
| * xmlRemoveRef |
| * @doc: the document |
| * @attr: the attribute |
| * |
| * Remove the given attribute from the Ref table maintained internally. |
| * |
| * Returns -1 if the lookup failed and 0 otherwise |
| */ |
| int |
| xmlRemoveRef(xmlDocPtr doc, xmlAttrPtr attr) { |
| xmlListPtr ref_list; |
| xmlRefTablePtr table; |
| xmlChar *ID; |
| xmlRemoveMemo target; |
| |
| if (doc == NULL) return(-1); |
| if (attr == NULL) return(-1); |
| table = (xmlRefTablePtr) doc->refs; |
| if (table == NULL) |
| return(-1); |
| |
| if (attr == NULL) |
| return(-1); |
| ID = xmlNodeListGetString(doc, attr->children, 1); |
| if (ID == NULL) |
| return(-1); |
| ref_list = xmlHashLookup(table, ID); |
| |
| if(ref_list == NULL) { |
| xmlFree(ID); |
| return (-1); |
| } |
| /* At this point, ref_list refers to a list of references which |
| * have the same key as the supplied attr. Our list of references |
| * is ordered by reference address and we don't have that information |
| * here to use when removing. We'll have to walk the list and |
| * check for a matching attribute, when we find one stop the walk |
| * and remove the entry. |
| * The list is ordered by reference, so that means we don't have the |
| * key. Passing the list and the reference to the walker means we |
| * will have enough data to be able to remove the entry. |
| */ |
| target.l = ref_list; |
| target.ap = attr; |
| |
| /* Remove the supplied attr from our list */ |
| xmlListWalk(ref_list, xmlWalkRemoveRef, &target); |
| |
| /*If the list is empty then remove the list entry in the hash */ |
| if (xmlListEmpty(ref_list)) |
| xmlHashUpdateEntry(table, ID, NULL, (xmlHashDeallocator) |
| xmlFreeRefList); |
| xmlFree(ID); |
| return(0); |
| } |
| |
| /** |
| * xmlGetRefs: |
| * @doc: pointer to the document |
| * @ID: the ID value |
| * |
| * Find the set of references for the supplied ID. |
| * |
| * Returns NULL if not found, otherwise node set for the ID. |
| */ |
| xmlListPtr |
| xmlGetRefs(xmlDocPtr doc, const xmlChar *ID) { |
| xmlRefTablePtr table; |
| |
| if (doc == NULL) { |
| xmlGenericError(xmlGenericErrorContext, "xmlGetRef: doc == NULL\n"); |
| return(NULL); |
| } |
| |
| if (ID == NULL) { |
| xmlGenericError(xmlGenericErrorContext, "xmlGetRef: ID == NULL\n"); |
| return(NULL); |
| } |
| |
| table = (xmlRefTablePtr) doc->refs; |
| if (table == NULL) |
| return(NULL); |
| |
| return (xmlHashLookup(table, ID)); |
| } |
| |
| /************************************************************************ |
| * * |
| * Routines for validity checking * |
| * * |
| ************************************************************************/ |
| |
| /** |
| * xmlGetDtdElementDesc: |
| * @dtd: a pointer to the DtD to search |
| * @name: the element name |
| * |
| * Search the Dtd for the description of this element |
| * |
| * returns the xmlElementPtr if found or NULL |
| */ |
| |
| xmlElementPtr |
| xmlGetDtdElementDesc(xmlDtdPtr dtd, const xmlChar *name) { |
| xmlElementTablePtr table; |
| xmlElementPtr cur; |
| xmlChar *uqname = NULL, *prefix = NULL; |
| |
| if (dtd == NULL) return(NULL); |
| if (dtd->elements == NULL) |
| return(NULL); |
| table = (xmlElementTablePtr) dtd->elements; |
| |
| uqname = xmlSplitQName2(name, &prefix); |
| if (uqname != NULL) |
| name = uqname; |
| cur = xmlHashLookup2(table, name, prefix); |
| if (prefix != NULL) xmlFree(prefix); |
| if (uqname != NULL) xmlFree(uqname); |
| return(cur); |
| } |
| /** |
| * xmlGetDtdElementDesc2: |
| * @dtd: a pointer to the DtD to search |
| * @name: the element name |
| * @create: create an empty description if not found |
| * |
| * Search the Dtd for the description of this element |
| * |
| * returns the xmlElementPtr if found or NULL |
| */ |
| |
| xmlElementPtr |
| xmlGetDtdElementDesc2(xmlDtdPtr dtd, const xmlChar *name, int create) { |
| xmlElementTablePtr table; |
| xmlElementPtr cur; |
| xmlChar *uqname = NULL, *prefix = NULL; |
| |
| if (dtd == NULL) return(NULL); |
| if (dtd->elements == NULL) { |
| if (!create) |
| return(NULL); |
| /* |
| * Create the Element table if needed. |
| */ |
| table = (xmlElementTablePtr) dtd->elements; |
| if (table == NULL) { |
| table = xmlCreateElementTable(); |
| dtd->elements = (void *) table; |
| } |
| if (table == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlGetDtdElementDesc: Table creation failed!\n"); |
| return(NULL); |
| } |
| } |
| table = (xmlElementTablePtr) dtd->elements; |
| |
| uqname = xmlSplitQName2(name, &prefix); |
| if (uqname != NULL) |
| name = uqname; |
| cur = xmlHashLookup2(table, name, prefix); |
| if ((cur == NULL) && (create)) { |
| cur = (xmlElementPtr) xmlMalloc(sizeof(xmlElement)); |
| if (cur == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlGetDtdElementDesc: out of memory\n"); |
| return(NULL); |
| } |
| memset(cur, 0, sizeof(xmlElement)); |
| cur->type = XML_ELEMENT_DECL; |
| |
| /* |
| * fill the structure. |
| */ |
| cur->name = xmlStrdup(name); |
| cur->prefix = xmlStrdup(prefix); |
| cur->etype = XML_ELEMENT_TYPE_UNDEFINED; |
| |
| xmlHashAddEntry2(table, name, prefix, cur); |
| } |
| if (prefix != NULL) xmlFree(prefix); |
| if (uqname != NULL) xmlFree(uqname); |
| return(cur); |
| } |
| |
| /** |
| * xmlGetDtdQElementDesc: |
| * @dtd: a pointer to the DtD to search |
| * @name: the element name |
| * @prefix: the element namespace prefix |
| * |
| * Search the Dtd for the description of this element |
| * |
| * returns the xmlElementPtr if found or NULL |
| */ |
| |
| xmlElementPtr |
| xmlGetDtdQElementDesc(xmlDtdPtr dtd, const xmlChar *name, |
| const xmlChar *prefix) { |
| xmlElementTablePtr table; |
| |
| if (dtd == NULL) return(NULL); |
| if (dtd->elements == NULL) return(NULL); |
| table = (xmlElementTablePtr) dtd->elements; |
| |
| return(xmlHashLookup2(table, name, prefix)); |
| } |
| |
| /** |
| * xmlGetDtdAttrDesc: |
| * @dtd: a pointer to the DtD to search |
| * @elem: the element name |
| * @name: the attribute name |
| * |
| * Search the Dtd for the description of this attribute on |
| * this element. |
| * |
| * returns the xmlAttributePtr if found or NULL |
| */ |
| |
| xmlAttributePtr |
| xmlGetDtdAttrDesc(xmlDtdPtr dtd, const xmlChar *elem, const xmlChar *name) { |
| xmlAttributeTablePtr table; |
| xmlAttributePtr cur; |
| xmlChar *uqname = NULL, *prefix = NULL; |
| |
| if (dtd == NULL) return(NULL); |
| if (dtd->attributes == NULL) return(NULL); |
| |
| table = (xmlAttributeTablePtr) dtd->attributes; |
| if (table == NULL) |
| return(NULL); |
| |
| uqname = xmlSplitQName2(name, &prefix); |
| |
| if (uqname != NULL) { |
| cur = xmlHashLookup3(table, uqname, prefix, elem); |
| if (prefix != NULL) xmlFree(prefix); |
| if (uqname != NULL) xmlFree(uqname); |
| } else |
| cur = xmlHashLookup3(table, name, NULL, elem); |
| return(cur); |
| } |
| |
| /** |
| * xmlGetDtdQAttrDesc: |
| * @dtd: a pointer to the DtD to search |
| * @elem: the element name |
| * @name: the attribute name |
| * @prefix: the attribute namespace prefix |
| * |
| * Search the Dtd for the description of this qualified attribute on |
| * this element. |
| * |
| * returns the xmlAttributePtr if found or NULL |
| */ |
| |
| xmlAttributePtr |
| xmlGetDtdQAttrDesc(xmlDtdPtr dtd, const xmlChar *elem, const xmlChar *name, |
| const xmlChar *prefix) { |
| xmlAttributeTablePtr table; |
| |
| if (dtd == NULL) return(NULL); |
| if (dtd->attributes == NULL) return(NULL); |
| table = (xmlAttributeTablePtr) dtd->attributes; |
| |
| return(xmlHashLookup3(table, name, prefix, elem)); |
| } |
| |
| /** |
| * xmlGetDtdNotationDesc: |
| * @dtd: a pointer to the DtD to search |
| * @name: the notation name |
| * |
| * Search the Dtd for the description of this notation |
| * |
| * returns the xmlNotationPtr if found or NULL |
| */ |
| |
| xmlNotationPtr |
| xmlGetDtdNotationDesc(xmlDtdPtr dtd, const xmlChar *name) { |
| xmlNotationTablePtr table; |
| |
| if (dtd == NULL) return(NULL); |
| if (dtd->notations == NULL) return(NULL); |
| table = (xmlNotationTablePtr) dtd->notations; |
| |
| return(xmlHashLookup(table, name)); |
| } |
| |
| /** |
| * xmlValidateNotationUse: |
| * @ctxt: the validation context |
| * @doc: the document |
| * @notationName: the notation name to check |
| * |
| * Validate that the given mame match a notation declaration. |
| * - [ VC: Notation Declared ] |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| int |
| xmlValidateNotationUse(xmlValidCtxtPtr ctxt, xmlDocPtr doc, |
| const xmlChar *notationName) { |
| xmlNotationPtr notaDecl; |
| if ((doc == NULL) || (doc->intSubset == NULL)) return(-1); |
| |
| notaDecl = xmlGetDtdNotationDesc(doc->intSubset, notationName); |
| if ((notaDecl == NULL) && (doc->extSubset != NULL)) |
| notaDecl = xmlGetDtdNotationDesc(doc->extSubset, notationName); |
| |
| if (notaDecl == NULL) { |
| VERROR(ctxt->userData, "NOTATION %s is not declared\n", |
| notationName); |
| return(0); |
| } |
| return(1); |
| } |
| |
| /** |
| * xmlIsMixedElement |
| * @doc: the document |
| * @name: the element name |
| * |
| * Search in the DtDs whether an element accept Mixed content (or ANY) |
| * basically if it is supposed to accept text childs |
| * |
| * returns 0 if no, 1 if yes, and -1 if no element description is available |
| */ |
| |
| int |
| xmlIsMixedElement(xmlDocPtr doc, const xmlChar *name) { |
| xmlElementPtr elemDecl; |
| |
| if ((doc == NULL) || (doc->intSubset == NULL)) return(-1); |
| |
| elemDecl = xmlGetDtdElementDesc(doc->intSubset, name); |
| if ((elemDecl == NULL) && (doc->extSubset != NULL)) |
| elemDecl = xmlGetDtdElementDesc(doc->extSubset, name); |
| if (elemDecl == NULL) return(-1); |
| switch (elemDecl->etype) { |
| case XML_ELEMENT_TYPE_UNDEFINED: |
| return(-1); |
| case XML_ELEMENT_TYPE_ELEMENT: |
| return(0); |
| case XML_ELEMENT_TYPE_EMPTY: |
| /* |
| * return 1 for EMPTY since we want VC error to pop up |
| * on <empty> </empty> for example |
| */ |
| case XML_ELEMENT_TYPE_ANY: |
| case XML_ELEMENT_TYPE_MIXED: |
| return(1); |
| } |
| return(1); |
| } |
| |
| /** |
| * xmlValidateNameValue: |
| * @value: an Name value |
| * |
| * Validate that the given value match Name production |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| static int |
| xmlValidateNameValue(const xmlChar *value) { |
| const xmlChar *cur; |
| |
| if (value == NULL) return(0); |
| cur = value; |
| |
| if (!IS_LETTER(*cur) && (*cur != '_') && |
| (*cur != ':')) { |
| return(0); |
| } |
| |
| while ((IS_LETTER(*cur)) || (IS_DIGIT(*cur)) || |
| (*cur == '.') || (*cur == '-') || |
| (*cur == '_') || (*cur == ':') || |
| (IS_COMBINING(*cur)) || |
| (IS_EXTENDER(*cur))) |
| cur++; |
| |
| if (*cur != 0) return(0); |
| |
| return(1); |
| } |
| |
| /** |
| * xmlValidateNamesValue: |
| * @value: an Names value |
| * |
| * Validate that the given value match Names production |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| static int |
| xmlValidateNamesValue(const xmlChar *value) { |
| const xmlChar *cur; |
| |
| if (value == NULL) return(0); |
| cur = value; |
| |
| if (!IS_LETTER(*cur) && (*cur != '_') && |
| (*cur != ':')) { |
| return(0); |
| } |
| |
| while ((IS_LETTER(*cur)) || (IS_DIGIT(*cur)) || |
| (*cur == '.') || (*cur == '-') || |
| (*cur == '_') || (*cur == ':') || |
| (IS_COMBINING(*cur)) || |
| (IS_EXTENDER(*cur))) |
| cur++; |
| |
| while (IS_BLANK(*cur)) { |
| while (IS_BLANK(*cur)) cur++; |
| |
| if (!IS_LETTER(*cur) && (*cur != '_') && |
| (*cur != ':')) { |
| return(0); |
| } |
| |
| while ((IS_LETTER(*cur)) || (IS_DIGIT(*cur)) || |
| (*cur == '.') || (*cur == '-') || |
| (*cur == '_') || (*cur == ':') || |
| (IS_COMBINING(*cur)) || |
| (IS_EXTENDER(*cur))) |
| cur++; |
| } |
| |
| if (*cur != 0) return(0); |
| |
| return(1); |
| } |
| |
| /** |
| * xmlValidateNmtokenValue: |
| * @value: an Mntoken value |
| * |
| * Validate that the given value match Nmtoken production |
| * |
| * [ VC: Name Token ] |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| static int |
| xmlValidateNmtokenValue(const xmlChar *value) { |
| const xmlChar *cur; |
| |
| if (value == NULL) return(0); |
| cur = value; |
| |
| if (!IS_LETTER(*cur) && !IS_DIGIT(*cur) && |
| (*cur != '.') && (*cur != '-') && |
| (*cur != '_') && (*cur != ':') && |
| (!IS_COMBINING(*cur)) && |
| (!IS_EXTENDER(*cur))) |
| return(0); |
| |
| while ((IS_LETTER(*cur)) || (IS_DIGIT(*cur)) || |
| (*cur == '.') || (*cur == '-') || |
| (*cur == '_') || (*cur == ':') || |
| (IS_COMBINING(*cur)) || |
| (IS_EXTENDER(*cur))) |
| cur++; |
| |
| if (*cur != 0) return(0); |
| |
| return(1); |
| } |
| |
| /** |
| * xmlValidateNmtokensValue: |
| * @value: an Mntokens value |
| * |
| * Validate that the given value match Nmtokens production |
| * |
| * [ VC: Name Token ] |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| static int |
| xmlValidateNmtokensValue(const xmlChar *value) { |
| const xmlChar *cur; |
| |
| if (value == NULL) return(0); |
| cur = value; |
| |
| while (IS_BLANK(*cur)) cur++; |
| if (!IS_LETTER(*cur) && !IS_DIGIT(*cur) && |
| (*cur != '.') && (*cur != '-') && |
| (*cur != '_') && (*cur != ':') && |
| (!IS_COMBINING(*cur)) && |
| (!IS_EXTENDER(*cur))) |
| return(0); |
| |
| while ((IS_LETTER(*cur)) || (IS_DIGIT(*cur)) || |
| (*cur == '.') || (*cur == '-') || |
| (*cur == '_') || (*cur == ':') || |
| (IS_COMBINING(*cur)) || |
| (IS_EXTENDER(*cur))) |
| cur++; |
| |
| while (IS_BLANK(*cur)) { |
| while (IS_BLANK(*cur)) cur++; |
| if (*cur == 0) return(1); |
| |
| if (!IS_LETTER(*cur) && !IS_DIGIT(*cur) && |
| (*cur != '.') && (*cur != '-') && |
| (*cur != '_') && (*cur != ':') && |
| (!IS_COMBINING(*cur)) && |
| (!IS_EXTENDER(*cur))) |
| return(0); |
| |
| while ((IS_LETTER(*cur)) || (IS_DIGIT(*cur)) || |
| (*cur == '.') || (*cur == '-') || |
| (*cur == '_') || (*cur == ':') || |
| (IS_COMBINING(*cur)) || |
| (IS_EXTENDER(*cur))) |
| cur++; |
| } |
| |
| if (*cur != 0) return(0); |
| |
| return(1); |
| } |
| |
| /** |
| * xmlValidateNotationDecl: |
| * @ctxt: the validation context |
| * @doc: a document instance |
| * @nota: a notation definition |
| * |
| * Try to validate a single notation definition |
| * basically it does the following checks as described by the |
| * XML-1.0 recommendation: |
| * - it seems that no validity constraing exist on notation declarations |
| * But this function get called anyway ... |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| int |
| xmlValidateNotationDecl(xmlValidCtxtPtr ctxt ATTRIBUTE_UNUSED, xmlDocPtr doc ATTRIBUTE_UNUSED, |
| xmlNotationPtr nota ATTRIBUTE_UNUSED) { |
| int ret = 1; |
| |
| return(ret); |
| } |
| |
| /** |
| * xmlValidateAttributeValue: |
| * @type: an attribute type |
| * @value: an attribute value |
| * |
| * Validate that the given attribute value match the proper production |
| * |
| * [ VC: ID ] |
| * Values of type ID must match the Name production.... |
| * |
| * [ VC: IDREF ] |
| * Values of type IDREF must match the Name production, and values |
| * of type IDREFS must match Names ... |
| * |
| * [ VC: Entity Name ] |
| * Values of type ENTITY must match the Name production, values |
| * of type ENTITIES must match Names ... |
| * |
| * [ VC: Name Token ] |
| * Values of type NMTOKEN must match the Nmtoken production; values |
| * of type NMTOKENS must match Nmtokens. |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| int |
| xmlValidateAttributeValue(xmlAttributeType type, const xmlChar *value) { |
| switch (type) { |
| case XML_ATTRIBUTE_ENTITIES: |
| case XML_ATTRIBUTE_IDREFS: |
| return(xmlValidateNamesValue(value)); |
| case XML_ATTRIBUTE_ENTITY: |
| case XML_ATTRIBUTE_IDREF: |
| case XML_ATTRIBUTE_ID: |
| case XML_ATTRIBUTE_NOTATION: |
| return(xmlValidateNameValue(value)); |
| case XML_ATTRIBUTE_NMTOKENS: |
| case XML_ATTRIBUTE_ENUMERATION: |
| return(xmlValidateNmtokensValue(value)); |
| case XML_ATTRIBUTE_NMTOKEN: |
| return(xmlValidateNmtokenValue(value)); |
| case XML_ATTRIBUTE_CDATA: |
| break; |
| } |
| return(1); |
| } |
| |
| /** |
| * xmlValidateAttributeValue2: |
| * @ctxt: the validation context |
| * @doc: the document |
| * @name: the attribute name (used for error reporting only) |
| * @type: the attribute type |
| * @value: the attribute value |
| * |
| * Validate that the given attribute value match a given type. |
| * This typically cannot be done before having finished parsing |
| * the subsets. |
| * |
| * [ VC: IDREF ] |
| * Values of type IDREF must match one of the declared IDs |
| * Values of type IDREFS must match a sequence of the declared IDs |
| * each Name must match the value of an ID attribute on some element |
| * in the XML document; i.e. IDREF values must match the value of |
| * some ID attribute |
| * |
| * [ VC: Entity Name ] |
| * Values of type ENTITY must match one declared entity |
| * Values of type ENTITIES must match a sequence of declared entities |
| * |
| * [ VC: Notation Attributes ] |
| * all notation names in the declaration must be declared. |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| static int |
| xmlValidateAttributeValue2(xmlValidCtxtPtr ctxt, xmlDocPtr doc, |
| const xmlChar *name, xmlAttributeType type, const xmlChar *value) { |
| int ret = 1; |
| switch (type) { |
| case XML_ATTRIBUTE_IDREFS: |
| case XML_ATTRIBUTE_IDREF: |
| case XML_ATTRIBUTE_ID: |
| case XML_ATTRIBUTE_NMTOKENS: |
| case XML_ATTRIBUTE_ENUMERATION: |
| case XML_ATTRIBUTE_NMTOKEN: |
| case XML_ATTRIBUTE_CDATA: |
| break; |
| case XML_ATTRIBUTE_ENTITY: { |
| xmlEntityPtr ent; |
| |
| ent = xmlGetDocEntity(doc, value); |
| if (ent == NULL) { |
| VERROR(ctxt->userData, |
| "ENTITY attribute %s reference an unknown entity \"%s\"\n", |
| name, value); |
| ret = 0; |
| } else if (ent->etype != XML_EXTERNAL_GENERAL_UNPARSED_ENTITY) { |
| VERROR(ctxt->userData, |
| "ENTITY attribute %s reference an entity \"%s\" of wrong type\n", |
| name, value); |
| ret = 0; |
| } |
| break; |
| } |
| case XML_ATTRIBUTE_ENTITIES: { |
| xmlChar *dup, *nam = NULL, *cur, save; |
| xmlEntityPtr ent; |
| |
| dup = xmlStrdup(value); |
| if (dup == NULL) |
| return(0); |
| cur = dup; |
| while (*cur != 0) { |
| nam = cur; |
| while ((*cur != 0) && (!IS_BLANK(*cur))) cur++; |
| save = *cur; |
| *cur = 0; |
| ent = xmlGetDocEntity(doc, nam); |
| if (ent == NULL) { |
| VERROR(ctxt->userData, |
| "ENTITIES attribute %s reference an unknown entity \"%s\"\n", |
| name, nam); |
| ret = 0; |
| } else if (ent->etype != XML_EXTERNAL_GENERAL_UNPARSED_ENTITY) { |
| VERROR(ctxt->userData, |
| "ENTITIES attribute %s reference an entity \"%s\" of wrong type\n", |
| name, nam); |
| ret = 0; |
| } |
| if (save == 0) |
| break; |
| *cur = save; |
| while (IS_BLANK(*cur)) cur++; |
| } |
| xmlFree(dup); |
| break; |
| } |
| case XML_ATTRIBUTE_NOTATION: { |
| xmlNotationPtr nota; |
| |
| nota = xmlGetDtdNotationDesc(doc->intSubset, value); |
| if ((nota == NULL) && (doc->extSubset != NULL)) |
| nota = xmlGetDtdNotationDesc(doc->extSubset, value); |
| |
| if (nota == NULL) { |
| VERROR(ctxt->userData, |
| "NOTATION attribute %s reference an unknown notation \"%s\"\n", |
| name, value); |
| ret = 0; |
| } |
| break; |
| } |
| } |
| return(ret); |
| } |
| |
| /** |
| * xmlValidNormalizeAttributeValue: |
| * @doc: the document |
| * @elem: the parent |
| * @name: the attribute name |
| * @value: the attribute value |
| * |
| * Does the validation related extra step of the normalization of attribute |
| * values: |
| * |
| * If the declared value is not CDATA, then the XML processor must further |
| * process the normalized attribute value by discarding any leading and |
| * trailing space (#x20) characters, and by replacing sequences of space |
| * (#x20) characters by single space (#x20) character. |
| * |
| * returns a new normalized string if normalization is needed, NULL otherwise |
| * the caller must free the returned value. |
| */ |
| |
| xmlChar * |
| xmlValidNormalizeAttributeValue(xmlDocPtr doc, xmlNodePtr elem, |
| const xmlChar *name, const xmlChar *value) { |
| xmlChar *ret, *dst; |
| const xmlChar *src; |
| xmlAttributePtr attrDecl = NULL; |
| |
| if (doc == NULL) return(NULL); |
| if (elem == NULL) return(NULL); |
| if (name == NULL) return(NULL); |
| if (value == NULL) return(NULL); |
| |
| if ((elem->ns != NULL) && (elem->ns->prefix != NULL)) { |
| xmlChar qname[500]; |
| snprintf((char *) qname, sizeof(qname), "%s:%s", |
| elem->ns->prefix, elem->name); |
| qname[sizeof(qname) - 1] = 0; |
| attrDecl = xmlGetDtdAttrDesc(doc->intSubset, qname, name); |
| if ((attrDecl == NULL) && (doc->extSubset != NULL)) |
| attrDecl = xmlGetDtdAttrDesc(doc->extSubset, qname, name); |
| } |
| attrDecl = xmlGetDtdAttrDesc(doc->intSubset, elem->name, name); |
| if ((attrDecl == NULL) && (doc->extSubset != NULL)) |
| attrDecl = xmlGetDtdAttrDesc(doc->extSubset, elem->name, name); |
| |
| if (attrDecl == NULL) |
| return(NULL); |
| if (attrDecl->atype == XML_ATTRIBUTE_CDATA) |
| return(NULL); |
| |
| ret = xmlStrdup(value); |
| if (ret == NULL) |
| return(NULL); |
| src = value; |
| dst = ret; |
| while (*src == 0x20) src++; |
| while (*src != 0) { |
| if (*src == 0x20) { |
| while (*src == 0x20) src++; |
| if (*src != 0) |
| *dst++ = 0x20; |
| } else { |
| *dst++ = *src++; |
| } |
| } |
| *dst = 0; |
| return(ret); |
| } |
| |
| static void |
| xmlValidateAttributeIdCallback(xmlAttributePtr attr, int *count, |
| const xmlChar* name ATTRIBUTE_UNUSED) { |
| if (attr->atype == XML_ATTRIBUTE_ID) (*count)++; |
| } |
| |
| /** |
| * xmlValidateAttributeDecl: |
| * @ctxt: the validation context |
| * @doc: a document instance |
| * @attr: an attribute definition |
| * |
| * Try to validate a single attribute definition |
| * basically it does the following checks as described by the |
| * XML-1.0 recommendation: |
| * - [ VC: Attribute Default Legal ] |
| * - [ VC: Enumeration ] |
| * - [ VC: ID Attribute Default ] |
| * |
| * The ID/IDREF uniqueness and matching are done separately |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| int |
| xmlValidateAttributeDecl(xmlValidCtxtPtr ctxt, xmlDocPtr doc, |
| xmlAttributePtr attr) { |
| int ret = 1; |
| int val; |
| CHECK_DTD; |
| if(attr == NULL) return(1); |
| |
| /* Attribute Default Legal */ |
| /* Enumeration */ |
| if (attr->defaultValue != NULL) { |
| val = xmlValidateAttributeValue(attr->atype, attr->defaultValue); |
| if (val == 0) { |
| VERROR(ctxt->userData, |
| "Syntax of default value for attribute %s on %s is not valid\n", |
| attr->name, attr->elem); |
| } |
| ret &= val; |
| } |
| |
| /* ID Attribute Default */ |
| if ((attr->atype == XML_ATTRIBUTE_ID)&& |
| (attr->def != XML_ATTRIBUTE_IMPLIED) && |
| (attr->def != XML_ATTRIBUTE_REQUIRED)) { |
| VERROR(ctxt->userData, |
| "ID attribute %s on %s is not valid must be #IMPLIED or #REQUIRED\n", |
| attr->name, attr->elem); |
| ret = 0; |
| } |
| |
| /* One ID per Element Type */ |
| if (attr->atype == XML_ATTRIBUTE_ID) { |
| int nbId; |
| |
| /* the trick is taht we parse DtD as their own internal subset */ |
| xmlElementPtr elem = xmlGetDtdElementDesc(doc->intSubset, |
| attr->elem); |
| if (elem != NULL) { |
| nbId = xmlScanIDAttributeDecl(NULL, elem); |
| } else { |
| xmlAttributeTablePtr table; |
| |
| /* |
| * The attribute may be declared in the internal subset and the |
| * element in the external subset. |
| */ |
| nbId = 0; |
| table = (xmlAttributeTablePtr) doc->intSubset->attributes; |
| xmlHashScan3(table, NULL, NULL, attr->elem, (xmlHashScanner) |
| xmlValidateAttributeIdCallback, &nbId); |
| } |
| if (nbId > 1) { |
| VERROR(ctxt->userData, |
| "Element %s has %d ID attribute defined in the internal subset : %s\n", |
| attr->elem, nbId, attr->name); |
| } else if (doc->extSubset != NULL) { |
| int extId = 0; |
| elem = xmlGetDtdElementDesc(doc->extSubset, attr->elem); |
| if (elem != NULL) { |
| extId = xmlScanIDAttributeDecl(NULL, elem); |
| } |
| if (extId > 1) { |
| VERROR(ctxt->userData, |
| "Element %s has %d ID attribute defined in the external subset : %s\n", |
| attr->elem, extId, attr->name); |
| } else if (extId + nbId > 1) { |
| VERROR(ctxt->userData, |
| "Element %s has ID attributes defined in the internal and external subset : %s\n", |
| attr->elem, attr->name); |
| } |
| } |
| } |
| |
| /* Validity Constraint: Enumeration */ |
| if ((attr->defaultValue != NULL) && (attr->tree != NULL)) { |
| xmlEnumerationPtr tree = attr->tree; |
| while (tree != NULL) { |
| if (xmlStrEqual(tree->name, attr->defaultValue)) break; |
| tree = tree->next; |
| } |
| if (tree == NULL) { |
| VERROR(ctxt->userData, |
| "Default value \"%s\" for attribute %s on %s is not among the enumerated set\n", |
| attr->defaultValue, attr->name, attr->elem); |
| ret = 0; |
| } |
| } |
| |
| return(ret); |
| } |
| |
| /** |
| * xmlValidateElementDecl: |
| * @ctxt: the validation context |
| * @doc: a document instance |
| * @elem: an element definition |
| * |
| * Try to validate a single element definition |
| * basically it does the following checks as described by the |
| * XML-1.0 recommendation: |
| * - [ VC: One ID per Element Type ] |
| * - [ VC: No Duplicate Types ] |
| * - [ VC: Unique Element Type Declaration ] |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| int |
| xmlValidateElementDecl(xmlValidCtxtPtr ctxt, xmlDocPtr doc, |
| xmlElementPtr elem) { |
| int ret = 1; |
| xmlElementPtr tst; |
| |
| CHECK_DTD; |
| |
| if (elem == NULL) return(1); |
| |
| /* No Duplicate Types */ |
| if (elem->etype == XML_ELEMENT_TYPE_MIXED) { |
| xmlElementContentPtr cur, next; |
| const xmlChar *name; |
| |
| cur = elem->content; |
| while (cur != NULL) { |
| if (cur->type != XML_ELEMENT_CONTENT_OR) break; |
| if (cur->c1 == NULL) break; |
| if (cur->c1->type == XML_ELEMENT_CONTENT_ELEMENT) { |
| name = cur->c1->name; |
| next = cur->c2; |
| while (next != NULL) { |
| if (next->type == XML_ELEMENT_CONTENT_ELEMENT) { |
| if (xmlStrEqual(next->name, name)) { |
| VERROR(ctxt->userData, |
| "Definition of %s has duplicate references of %s\n", |
| elem->name, name); |
| ret = 0; |
| } |
| break; |
| } |
| if (next->c1 == NULL) break; |
| if (next->c1->type != XML_ELEMENT_CONTENT_ELEMENT) break; |
| if (xmlStrEqual(next->c1->name, name)) { |
| VERROR(ctxt->userData, |
| "Definition of %s has duplicate references of %s\n", |
| elem->name, name); |
| ret = 0; |
| } |
| next = next->c2; |
| } |
| } |
| cur = cur->c2; |
| } |
| } |
| |
| /* VC: Unique Element Type Declaration */ |
| tst = xmlGetDtdElementDesc(doc->intSubset, elem->name); |
| if ((tst != NULL ) && (tst != elem) && |
| (tst->etype != XML_ELEMENT_TYPE_UNDEFINED)) { |
| VERROR(ctxt->userData, "Redefinition of element %s\n", |
| elem->name); |
| ret = 0; |
| } |
| tst = xmlGetDtdElementDesc(doc->extSubset, elem->name); |
| if ((tst != NULL ) && (tst != elem) && |
| (tst->etype != XML_ELEMENT_TYPE_UNDEFINED)) { |
| VERROR(ctxt->userData, "Redefinition of element %s\n", |
| elem->name); |
| ret = 0; |
| } |
| |
| /* One ID per Element Type |
| * already done when registering the attribute |
| if (xmlScanIDAttributeDecl(ctxt, elem) > 1) { |
| ret = 0; |
| } */ |
| return(ret); |
| } |
| |
| /** |
| * xmlValidateOneAttribute: |
| * @ctxt: the validation context |
| * @doc: a document instance |
| * @elem: an element instance |
| * @attr: an attribute instance |
| * @value: the attribute value (without entities processing) |
| * |
| * Try to validate a single attribute for an element |
| * basically it does the following checks as described by the |
| * XML-1.0 recommendation: |
| * - [ VC: Attribute Value Type ] |
| * - [ VC: Fixed Attribute Default ] |
| * - [ VC: Entity Name ] |
| * - [ VC: Name Token ] |
| * - [ VC: ID ] |
| * - [ VC: IDREF ] |
| * - [ VC: Entity Name ] |
| * - [ VC: Notation Attributes ] |
| * |
| * The ID/IDREF uniqueness and matching are done separately |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| int |
| xmlValidateOneAttribute(xmlValidCtxtPtr ctxt, xmlDocPtr doc, |
| xmlNodePtr elem, xmlAttrPtr attr, const xmlChar *value) { |
| /* xmlElementPtr elemDecl; */ |
| xmlAttributePtr attrDecl = NULL; |
| int val; |
| int ret = 1; |
| |
| CHECK_DTD; |
| if ((elem == NULL) || (elem->name == NULL)) return(0); |
| if ((attr == NULL) || (attr->name == NULL)) return(0); |
| |
| if ((elem->ns != NULL) && (elem->ns->prefix != NULL)) { |
| xmlChar qname[500]; |
| snprintf((char *) qname, sizeof(qname), "%s:%s", |
| elem->ns->prefix, elem->name); |
| qname[sizeof(qname) - 1] = 0; |
| if (attr->ns != NULL) { |
| attrDecl = xmlGetDtdQAttrDesc(doc->intSubset, qname, |
| attr->name, attr->ns->prefix); |
| if ((attrDecl == NULL) && (doc->extSubset != NULL)) |
| attrDecl = xmlGetDtdQAttrDesc(doc->extSubset, qname, |
| attr->name, attr->ns->prefix); |
| } else { |
| attrDecl = xmlGetDtdAttrDesc(doc->intSubset, qname, attr->name); |
| if ((attrDecl == NULL) && (doc->extSubset != NULL)) |
| attrDecl = xmlGetDtdAttrDesc(doc->extSubset, |
| qname, attr->name); |
| } |
| } |
| if (attrDecl == NULL) { |
| if (attr->ns != NULL) { |
| attrDecl = xmlGetDtdQAttrDesc(doc->intSubset, elem->name, |
| attr->name, attr->ns->prefix); |
| if ((attrDecl == NULL) && (doc->extSubset != NULL)) |
| attrDecl = xmlGetDtdQAttrDesc(doc->extSubset, elem->name, |
| attr->name, attr->ns->prefix); |
| } else { |
| attrDecl = xmlGetDtdAttrDesc(doc->intSubset, |
| elem->name, attr->name); |
| if ((attrDecl == NULL) && (doc->extSubset != NULL)) |
| attrDecl = xmlGetDtdAttrDesc(doc->extSubset, |
| elem->name, attr->name); |
| } |
| } |
| |
| |
| /* Validity Constraint: Attribute Value Type */ |
| if (attrDecl == NULL) { |
| VERROR(ctxt->userData, |
| "No declaration for attribute %s on element %s\n", |
| attr->name, elem->name); |
| return(0); |
| } |
| attr->atype = attrDecl->atype; |
| |
| val = xmlValidateAttributeValue(attrDecl->atype, value); |
| if (val == 0) { |
| VERROR(ctxt->userData, |
| "Syntax of value for attribute %s on %s is not valid\n", |
| attr->name, elem->name); |
| ret = 0; |
| } |
| |
| /* Validity constraint: Fixed Attribute Default */ |
| if (attrDecl->def == XML_ATTRIBUTE_FIXED) { |
| if (!xmlStrEqual(value, attrDecl->defaultValue)) { |
| VERROR(ctxt->userData, |
| "Value for attribute %s on %s is differnt from default \"%s\"\n", |
| attr->name, elem->name, attrDecl->defaultValue); |
| ret = 0; |
| } |
| } |
| |
| /* Validity Constraint: ID uniqueness */ |
| if (attrDecl->atype == XML_ATTRIBUTE_ID) { |
| if (xmlAddID(ctxt, doc, value, attr) == NULL) |
| ret = 0; |
| } |
| |
| if ((attrDecl->atype == XML_ATTRIBUTE_IDREF) || |
| (attrDecl->atype == XML_ATTRIBUTE_IDREFS)) { |
| if (xmlAddRef(ctxt, doc, value, attr) == NULL) |
| ret = 0; |
| } |
| |
| /* Validity Constraint: Notation Attributes */ |
| if (attrDecl->atype == XML_ATTRIBUTE_NOTATION) { |
| xmlEnumerationPtr tree = attrDecl->tree; |
| xmlNotationPtr nota; |
| |
| /* First check that the given NOTATION was declared */ |
| nota = xmlGetDtdNotationDesc(doc->intSubset, value); |
| if (nota == NULL) |
| nota = xmlGetDtdNotationDesc(doc->extSubset, value); |
| |
| if (nota == NULL) { |
| VERROR(ctxt->userData, |
| "Value \"%s\" for attribute %s on %s is not a declared Notation\n", |
| value, attr->name, elem->name); |
| ret = 0; |
| } |
| |
| /* Second, verify that it's among the list */ |
| while (tree != NULL) { |
| if (xmlStrEqual(tree->name, value)) break; |
| tree = tree->next; |
| } |
| if (tree == NULL) { |
| VERROR(ctxt->userData, |
| "Value \"%s\" for attribute %s on %s is not among the enumerated notations\n", |
| value, attr->name, elem->name); |
| ret = 0; |
| } |
| } |
| |
| /* Validity Constraint: Enumeration */ |
| if (attrDecl->atype == XML_ATTRIBUTE_ENUMERATION) { |
| xmlEnumerationPtr tree = attrDecl->tree; |
| while (tree != NULL) { |
| if (xmlStrEqual(tree->name, value)) break; |
| tree = tree->next; |
| } |
| if (tree == NULL) { |
| VERROR(ctxt->userData, |
| "Value \"%s\" for attribute %s on %s is not among the enumerated set\n", |
| value, attr->name, elem->name); |
| ret = 0; |
| } |
| } |
| |
| /* Fixed Attribute Default */ |
| if ((attrDecl->def == XML_ATTRIBUTE_FIXED) && |
| (!xmlStrEqual(attrDecl->defaultValue, value))) { |
| VERROR(ctxt->userData, |
| "Value for attribute %s on %s must be \"%s\"\n", |
| attr->name, elem->name, attrDecl->defaultValue); |
| ret = 0; |
| } |
| |
| /* Extra check for the attribute value */ |
| ret &= xmlValidateAttributeValue2(ctxt, doc, attr->name, |
| attrDecl->atype, value); |
| |
| return(ret); |
| } |
| |
| /** |
| * xmlValidateSkipIgnorable: |
| * @ctxt: the validation context |
| * @child: the child list |
| * |
| * Skip ignorable elements w.r.t. the validation process |
| * |
| * returns the first element to consider for validation of the content model |
| */ |
| |
| static xmlNodePtr |
| xmlValidateSkipIgnorable(xmlNodePtr child) { |
| while (child != NULL) { |
| switch (child->type) { |
| /* These things are ignored (skipped) during validation. */ |
| case XML_PI_NODE: |
| case XML_COMMENT_NODE: |
| case XML_XINCLUDE_START: |
| case XML_XINCLUDE_END: |
| child = child->next; |
| break; |
| case XML_TEXT_NODE: |
| if (xmlIsBlankNode(child)) |
| child = child->next; |
| else |
| return(child); |
| break; |
| /* keep current node */ |
| default: |
| return(child); |
| } |
| } |
| return(child); |
| } |
| |
| /** |
| * xmlValidateElementType: |
| * @ctxt: the validation context |
| * |
| * Try to validate the content model of an element internal function |
| * |
| * returns 1 if valid or 0 ,-1 in case of error, -2 if an entity |
| * reference is found and -3 if the validation succeeded but |
| * the content model is not determinist. |
| */ |
| |
| static int |
| xmlValidateElementType(xmlValidCtxtPtr ctxt) { |
| int ret = -1; |
| int determinist = 1; |
| |
| NODE = xmlValidateSkipIgnorable(NODE); |
| if ((NODE == NULL) && (CONT == NULL)) |
| return(1); |
| if ((NODE == NULL) && |
| ((CONT->ocur == XML_ELEMENT_CONTENT_MULT) || |
| (CONT->ocur == XML_ELEMENT_CONTENT_OPT))) { |
| return(1); |
| } |
| if (CONT == NULL) return(-1); |
| if ((NODE != NULL) && (NODE->type == XML_ENTITY_REF_NODE)) |
| return(-2); |
| |
| /* |
| * We arrive here when more states need to be examined |
| */ |
| cont: |
| |
| /* |
| * We just recovered from a rollback generated by a possible |
| * epsilon transition, go directly to the analysis phase |
| */ |
| if (STATE == ROLLBACK_PARENT) { |
| DEBUG_VALID_MSG("restaured parent branch"); |
| DEBUG_VALID_STATE(NODE, CONT) |
| ret = 1; |
| goto analyze; |
| } |
| |
| DEBUG_VALID_STATE(NODE, CONT) |
| /* |
| * we may have to save a backup state here. This is the equivalent |
| * of handling epsilon transition in NFAs. |
| */ |
| if ((CONT != NULL) && |
| ((CONT->ocur == XML_ELEMENT_CONTENT_MULT) || |
| (CONT->ocur == XML_ELEMENT_CONTENT_OPT) || |
| ((CONT->ocur == XML_ELEMENT_CONTENT_PLUS) && (OCCURENCE)))) { |
| DEBUG_VALID_MSG("saving parent branch"); |
| vstateVPush(ctxt, CONT, NODE, DEPTH, OCCURS, ROLLBACK_PARENT); |
| } |
| |
| |
| /* |
| * Check first if the content matches |
| */ |
| switch (CONT->type) { |
| case XML_ELEMENT_CONTENT_PCDATA: |
| if (NODE == NULL) { |
| DEBUG_VALID_MSG("pcdata failed no node"); |
| ret = 0; |
| break; |
| } |
| if (NODE->type == XML_TEXT_NODE) { |
| DEBUG_VALID_MSG("pcdata found, skip to next"); |
| /* |
| * go to next element in the content model |
| * skipping ignorable elems |
| */ |
| do { |
| NODE = NODE->next; |
| NODE = xmlValidateSkipIgnorable(NODE); |
| if ((NODE != NULL) && |
| (NODE->type == XML_ENTITY_REF_NODE)) |
| return(-2); |
| } while ((NODE != NULL) && |
| ((NODE->type != XML_ELEMENT_NODE) && |
| (NODE->type != XML_TEXT_NODE))); |
| ret = 1; |
| break; |
| } else { |
| DEBUG_VALID_MSG("pcdata failed"); |
| ret = 0; |
| break; |
| } |
| break; |
| case XML_ELEMENT_CONTENT_ELEMENT: |
| if (NODE == NULL) { |
| DEBUG_VALID_MSG("element failed no node"); |
| ret = 0; |
| break; |
| } |
| ret = ((NODE->type == XML_ELEMENT_NODE) && |
| (xmlStrEqual(NODE->name, CONT->name))); |
| if (ret == 1) { |
| DEBUG_VALID_MSG("element found, skip to next"); |
| /* |
| * go to next element in the content model |
| * skipping ignorable elems |
| */ |
| do { |
| NODE = NODE->next; |
| NODE = xmlValidateSkipIgnorable(NODE); |
| if ((NODE != NULL) && |
| (NODE->type == XML_ENTITY_REF_NODE)) |
| return(-2); |
| } while ((NODE != NULL) && |
| ((NODE->type != XML_ELEMENT_NODE) && |
| (NODE->type != XML_TEXT_NODE))); |
| } else { |
| DEBUG_VALID_MSG("element failed"); |
| ret = 0; |
| break; |
| } |
| break; |
| case XML_ELEMENT_CONTENT_OR: |
| /* |
| * Small optimization. |
| */ |
| if (CONT->c1->type == XML_ELEMENT_CONTENT_ELEMENT) { |
| if ((NODE == NULL) || |
| (!xmlStrEqual(NODE->name, CONT->c1->name))) { |
| DEPTH++; |
| CONT = CONT->c2; |
| goto cont; |
| } |
| } |
| |
| /* |
| * save the second branch 'or' branch |
| */ |
| DEBUG_VALID_MSG("saving 'or' branch"); |
| vstateVPush(ctxt, CONT->c2, NODE, (unsigned char)(DEPTH + 1), |
| OCCURS, ROLLBACK_OR); |
| |
| DEPTH++; |
| CONT = CONT->c1; |
| goto cont; |
| case XML_ELEMENT_CONTENT_SEQ: |
| /* |
| * Small optimization. |
| */ |
| if ((CONT->c1->type == XML_ELEMENT_CONTENT_ELEMENT) && |
| ((CONT->c1->ocur == XML_ELEMENT_CONTENT_OPT) || |
| (CONT->c1->ocur == XML_ELEMENT_CONTENT_MULT))) { |
| if ((NODE == NULL) || |
| (!xmlStrEqual(NODE->name, CONT->c1->name))) { |
| DEPTH++; |
| CONT = CONT->c2; |
| goto cont; |
| } |
| } |
| DEPTH++; |
| CONT = CONT->c1; |
| goto cont; |
| } |
| |
| /* |
| * At this point handle going up in the tree |
| */ |
| if (ret == -1) { |
| DEBUG_VALID_MSG("error found returning"); |
| return(ret); |
| } |
| analyze: |
| while (CONT != NULL) { |
| /* |
| * First do the analysis depending on the occurence model at |
| * this level. |
| */ |
| if (ret == 0) { |
| switch (CONT->ocur) { |
| xmlNodePtr cur; |
| |
| case XML_ELEMENT_CONTENT_ONCE: |
| cur = ctxt->vstate->node; |
| DEBUG_VALID_MSG("Once branch failed, rollback"); |
| if (vstateVPop(ctxt) < 0 ) { |
| DEBUG_VALID_MSG("exhaustion, failed"); |
| return(0); |
| } |
| if (cur != ctxt->vstate->node) |
| determinist = -3; |
| goto cont; |
| case XML_ELEMENT_CONTENT_PLUS: |
| if (OCCURENCE == 0) { |
| cur = ctxt->vstate->node; |
| DEBUG_VALID_MSG("Plus branch failed, rollback"); |
| if (vstateVPop(ctxt) < 0 ) { |
| DEBUG_VALID_MSG("exhaustion, failed"); |
| return(0); |
| } |
| if (cur != ctxt->vstate->node) |
| determinist = -3; |
| goto cont; |
| } |
| DEBUG_VALID_MSG("Plus branch found"); |
| ret = 1; |
| break; |
| case XML_ELEMENT_CONTENT_MULT: |
| #ifdef DEBUG_VALID_ALGO |
| if (OCCURENCE == 0) { |
| DEBUG_VALID_MSG("Mult branch failed"); |
| } else { |
| DEBUG_VALID_MSG("Mult branch found"); |
| } |
| #endif |
| ret = 1; |
| break; |
| case XML_ELEMENT_CONTENT_OPT: |
| DEBUG_VALID_MSG("Option branch failed"); |
| ret = 1; |
| break; |
| } |
| } else { |
| switch (CONT->ocur) { |
| case XML_ELEMENT_CONTENT_OPT: |
| DEBUG_VALID_MSG("Option branch succeeded"); |
| ret = 1; |
| break; |
| case XML_ELEMENT_CONTENT_ONCE: |
| DEBUG_VALID_MSG("Once branch succeeded"); |
| ret = 1; |
| break; |
| case XML_ELEMENT_CONTENT_PLUS: |
| if (STATE == ROLLBACK_PARENT) { |
| DEBUG_VALID_MSG("Plus branch rollback"); |
| ret = 1; |
| break; |
| } |
| if (NODE == NULL) { |
| DEBUG_VALID_MSG("Plus branch exhausted"); |
| ret = 1; |
| break; |
| } |
| DEBUG_VALID_MSG("Plus branch succeeded, continuing"); |
| SET_OCCURENCE; |
| goto cont; |
| case XML_ELEMENT_CONTENT_MULT: |
| if (STATE == ROLLBACK_PARENT) { |
| DEBUG_VALID_MSG("Mult branch rollback"); |
| ret = 1; |
| break; |
| } |
| if (NODE == NULL) { |
| DEBUG_VALID_MSG("Mult branch exhausted"); |
| ret = 1; |
| break; |
| } |
| DEBUG_VALID_MSG("Mult branch succeeded, continuing"); |
| SET_OCCURENCE; |
| goto cont; |
| } |
| } |
| STATE = 0; |
| |
| /* |
| * Then act accordingly at the parent level |
| */ |
| RESET_OCCURENCE; |
| if (CONT->parent == NULL) |
| break; |
| |
| switch (CONT->parent->type) { |
| case XML_ELEMENT_CONTENT_PCDATA: |
| DEBUG_VALID_MSG("Error: parent pcdata"); |
| return(-1); |
| case XML_ELEMENT_CONTENT_ELEMENT: |
| DEBUG_VALID_MSG("Error: parent element"); |
| return(-1); |
| case XML_ELEMENT_CONTENT_OR: |
| if (ret == 1) { |
| DEBUG_VALID_MSG("Or succeeded"); |
| CONT = CONT->parent; |
| DEPTH--; |
| } else { |
| DEBUG_VALID_MSG("Or failed"); |
| CONT = CONT->parent; |
| DEPTH--; |
| } |
| break; |
| case XML_ELEMENT_CONTENT_SEQ: |
| if (ret == 0) { |
| DEBUG_VALID_MSG("Sequence failed"); |
| CONT = CONT->parent; |
| DEPTH--; |
| } else if (CONT == CONT->parent->c1) { |
| DEBUG_VALID_MSG("Sequence testing 2nd branch"); |
| CONT = CONT->parent->c2; |
| goto cont; |
| } else { |
| DEBUG_VALID_MSG("Sequence succeeded"); |
| CONT = CONT->parent; |
| DEPTH--; |
| } |
| } |
| } |
| if (NODE != NULL) { |
| xmlNodePtr cur; |
| |
| cur = ctxt->vstate->node; |
| DEBUG_VALID_MSG("Failed, remaining input, rollback"); |
| if (vstateVPop(ctxt) < 0 ) { |
| DEBUG_VALID_MSG("exhaustion, failed"); |
| return(0); |
| } |
| if (cur != ctxt->vstate->node) |
| determinist = -3; |
| goto cont; |
| } |
| if (ret == 0) { |
| xmlNodePtr cur; |
| |
| cur = ctxt->vstate->node; |
| DEBUG_VALID_MSG("Failure, rollback"); |
| if (vstateVPop(ctxt) < 0 ) { |
| DEBUG_VALID_MSG("exhaustion, failed"); |
| return(0); |
| } |
| if (cur != ctxt->vstate->node) |
| determinist = -3; |
| goto cont; |
| } |
| return(determinist); |
| } |
| |
| /** |
| * xmlSnprintfElements: |
| * @buf: an output buffer |
| * @size: the size of the buffer |
| * @content: An element |
| * @glob: 1 if one must print the englobing parenthesis, 0 otherwise |
| * |
| * This will dump the list of elements to the buffer |
| * Intended just for the debug routine |
| */ |
| static void |
| xmlSnprintfElements(char *buf, int size, xmlNodePtr node, int glob) { |
| xmlNodePtr cur; |
| int len; |
| |
| if (node == NULL) return; |
| if (glob) strcat(buf, "("); |
| cur = node; |
| while (cur != NULL) { |
| len = strlen(buf); |
| if (size - len < 50) { |
| if ((size - len > 4) && (buf[len - 1] != '.')) |
| strcat(buf, " ..."); |
| return; |
| } |
| switch (cur->type) { |
| case XML_ELEMENT_NODE: |
| if (size - len < xmlStrlen(cur->name + 10)) { |
| if ((size - len > 4) && (buf[len - 1] != '.')) |
| strcat(buf, " ..."); |
| return; |
| } |
| strcat(buf, (char *) cur->name); |
| if (cur->next != NULL) |
| strcat(buf, " "); |
| break; |
| case XML_TEXT_NODE: |
| if (xmlIsBlankNode(cur)) |
| break; |
| case XML_CDATA_SECTION_NODE: |
| case XML_ENTITY_REF_NODE: |
| strcat(buf, "CDATA"); |
| if (cur->next != NULL) |
| strcat(buf, " "); |
| break; |
| case XML_ATTRIBUTE_NODE: |
| case XML_DOCUMENT_NODE: |
| #ifdef LIBXML_DOCB_ENABLED |
| case XML_DOCB_DOCUMENT_NODE: |
| #endif |
| case XML_HTML_DOCUMENT_NODE: |
| case XML_DOCUMENT_TYPE_NODE: |
| case XML_DOCUMENT_FRAG_NODE: |
| case XML_NOTATION_NODE: |
| case XML_NAMESPACE_DECL: |
| strcat(buf, "???"); |
| if (cur->next != NULL) |
| strcat(buf, " "); |
| break; |
| case XML_ENTITY_NODE: |
| case XML_PI_NODE: |
| case XML_DTD_NODE: |
| case XML_COMMENT_NODE: |
| case XML_ELEMENT_DECL: |
| case XML_ATTRIBUTE_DECL: |
| case XML_ENTITY_DECL: |
| case XML_XINCLUDE_START: |
| case XML_XINCLUDE_END: |
| break; |
| } |
| cur = cur->next; |
| } |
| if (glob) strcat(buf, ")"); |
| } |
| |
| /** |
| * xmlValidateElementContent: |
| * @ctxt: the validation context |
| * @child: the child list |
| * @cont: pointer to the content declaration |
| * @warn: emit the error message |
| * |
| * Try to validate the content model of an element |
| * |
| * returns 1 if valid or 0 if not and -1 in case of error |
| */ |
| |
| static int |
| xmlValidateElementContent(xmlValidCtxtPtr ctxt, xmlNodePtr child, |
| xmlElementContentPtr cont, int warn, const xmlChar *name) { |
| int ret; |
| xmlNodePtr repl = NULL, last = NULL, cur, tmp; |
| |
| /* |
| * Allocate the stack |
| */ |
| ctxt->vstateMax = 8; |
| ctxt->vstateTab = (xmlValidState *) xmlMalloc( |
| ctxt->vstateMax * sizeof(ctxt->vstateTab[0])); |
| if (ctxt->vstateTab == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "malloc failed !n"); |
| return(-1); |
| } |
| /* |
| * The first entry in the stack is reserved to the current state |
| */ |
| ctxt->nodeMax = 0; |
| ctxt->nodeNr = 0; |
| ctxt->nodeTab = NULL; |
| ctxt->vstate = &ctxt->vstateTab[0]; |
| ctxt->vstateNr = 1; |
| CONT = cont; |
| NODE = child; |
| DEPTH = 0; |
| OCCURS = 0; |
| STATE = 0; |
| ret = xmlValidateElementType(ctxt); |
| if ((ret == -3) && (warn)) { |
| VWARNING(ctxt->userData, |
| "Element %s content model is ambiguous\n", name); |
| } else if (ret == -2) { |
| /* |
| * An entities reference appeared at this level. |
| * Buid a minimal representation of this node content |
| * sufficient to run the validation process on it |
| */ |
| DEBUG_VALID_MSG("Found an entity reference, linearizing"); |
| cur = child; |
| while (cur != NULL) { |
| switch (cur->type) { |
| case XML_ENTITY_REF_NODE: |
| /* |
| * Push the current node to be able to roll back |
| * and process within the entity |
| */ |
| if ((cur->children != NULL) && |
| (cur->children->children != NULL)) { |
| nodeVPush(ctxt, cur); |
| cur = cur->children->children; |
| continue; |
| } |
| break; |
| case XML_TEXT_NODE: |
| case XML_CDATA_SECTION_NODE: |
| if (xmlIsBlankNode(cur)) |
| break; |
| /* no break on purpose */ |
| case XML_ELEMENT_NODE: |
| /* |
| * Allocate a new node and minimally fills in |
| * what's required |
| */ |
| tmp = (xmlNodePtr) xmlMalloc(sizeof(xmlNode)); |
| if (tmp == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlValidateElementContent : malloc failed\n"); |
| xmlFreeNodeList(repl); |
| ret = -1; |
| goto done; |
| } |
| tmp->type = cur->type; |
| tmp->name = cur->name; |
| tmp->ns = cur->ns; |
| tmp->next = NULL; |
| if (repl == NULL) |
| repl = last = tmp; |
| else { |
| last->next = tmp; |
| last = tmp; |
| } |
| break; |
| default: |
| break; |
| } |
| /* |
| * Switch to next element |
| */ |
| cur = cur->next; |
| while (cur == NULL) { |
| cur = nodeVPop(ctxt); |
| if (cur == NULL) |
| break; |
| cur = cur->next; |
| } |
| } |
| |
| /* |
| * Relaunch the validation |
| */ |
| ctxt->vstate = &ctxt->vstateTab[0]; |
| ctxt->vstateNr = 1; |
| CONT = cont; |
| NODE = repl; |
| DEPTH = 0; |
| OCCURS = 0; |
| STATE = 0; |
| ret = xmlValidateElementType(ctxt); |
| } |
| if ((warn) && ((ret != 1) && (ret != -3))) { |
| char expr[5000]; |
| char list[5000]; |
| |
| expr[0] = 0; |
| xmlSnprintfElementContent(expr, 5000, cont, 1); |
| list[0] = 0; |
| if (repl != NULL) |
| xmlSnprintfElements(list, 5000, repl, 1); |
| else |
| xmlSnprintfElements(list, 5000, child, 1); |
| |
| if (name != NULL) { |
| VERROR(ctxt->userData, |
| "Element %s content doesn't follow the Dtd\nExpecting %s, got %s\n", |
| name, expr, list); |
| } else { |
| VERROR(ctxt->userData, |
| "Element content doesn't follow the Dtd\nExpecting %s, got %s\n", |
| expr, list); |
| } |
| ret = 0; |
| } |
| if (ret == -3) |
| ret = 1; |
| |
| |
| done: |
| /* |
| * Deallocate the copy if done, and free up the validation stack |
| */ |
| while (repl != NULL) { |
| tmp = repl->next; |
| xmlFree(repl); |
| repl = tmp; |
| } |
| ctxt->vstateMax = 0; |
| if (ctxt->vstateTab != NULL) { |
| xmlFree(ctxt->vstateTab); |
| ctxt->vstateTab = NULL; |
| } |
| ctxt->nodeMax = 0; |
| ctxt->nodeNr = 0; |
| if (ctxt->nodeTab != NULL) { |
| xmlFree(ctxt->nodeTab); |
| ctxt->nodeTab = NULL; |
| } |
| return(ret); |
| |
| } |
| |
| /** |
| * xmlValidateCdataElement: |
| * @ctxt: the validation context |
| * @doc: a document instance |
| * @elem: an element instance |
| * |
| * Check that an element follows #CDATA |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| static int |
| xmlValidateOneCdataElement(xmlValidCtxtPtr ctxt, xmlDocPtr doc, |
| xmlNodePtr elem) { |
| int ret = 1; |
| xmlNodePtr cur, child; |
| |
| if ((ctxt == NULL) || (doc == NULL) | (elem == NULL)) |
| return(0); |
| |
| child = elem->children; |
| |
| cur = child; |
| while (cur != NULL) { |
| switch (cur->type) { |
| case XML_ENTITY_REF_NODE: |
| /* |
| * Push the current node to be able to roll back |
| * and process within the entity |
| */ |
| if ((cur->children != NULL) && |
| (cur->children->children != NULL)) { |
| nodeVPush(ctxt, cur); |
| cur = cur->children->children; |
| continue; |
| } |
| break; |
| case XML_COMMENT_NODE: |
| case XML_PI_NODE: |
| case XML_TEXT_NODE: |
| case XML_CDATA_SECTION_NODE: |
| break; |
| default: |
| ret = 0; |
| goto done; |
| } |
| /* |
| * Switch to next element |
| */ |
| cur = cur->next; |
| while (cur == NULL) { |
| cur = nodeVPop(ctxt); |
| if (cur == NULL) |
| break; |
| cur = cur->next; |
| } |
| } |
| done: |
| ctxt->nodeMax = 0; |
| ctxt->nodeNr = 0; |
| if (ctxt->nodeTab != NULL) { |
| xmlFree(ctxt->nodeTab); |
| ctxt->nodeTab = NULL; |
| } |
| return(ret); |
| } |
| |
| /** |
| * xmlValidateOneElement: |
| * @ctxt: the validation context |
| * @doc: a document instance |
| * @elem: an element instance |
| * |
| * Try to validate a single element and it's attributes, |
| * basically it does the following checks as described by the |
| * XML-1.0 recommendation: |
| * - [ VC: Element Valid ] |
| * - [ VC: Required Attribute ] |
| * Then call xmlValidateOneAttribute() for each attribute present. |
| * |
| * The ID/IDREF checkings are done separately |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| int |
| xmlValidateOneElement(xmlValidCtxtPtr ctxt, xmlDocPtr doc, |
| xmlNodePtr elem) { |
| xmlElementPtr elemDecl = NULL; |
| xmlElementContentPtr cont; |
| xmlAttributePtr attr; |
| xmlNodePtr child; |
| int ret = 1; |
| const xmlChar *name; |
| |
| CHECK_DTD; |
| |
| if (elem == NULL) return(0); |
| switch (elem->type) { |
| case XML_ATTRIBUTE_NODE: |
| VERROR(ctxt->userData, |
| "Attribute element not expected here\n"); |
| return(0); |
| case XML_TEXT_NODE: |
| if (elem->children != NULL) { |
| VERROR(ctxt->userData, "Text element has childs !\n"); |
| return(0); |
| } |
| if (elem->properties != NULL) { |
| VERROR(ctxt->userData, "Text element has attributes !\n"); |
| return(0); |
| } |
| if (elem->ns != NULL) { |
| VERROR(ctxt->userData, "Text element has namespace !\n"); |
| return(0); |
| } |
| if (elem->nsDef != NULL) { |
| VERROR(ctxt->userData, |
| "Text element carries namespace definitions !\n"); |
| return(0); |
| } |
| if (elem->content == NULL) { |
| VERROR(ctxt->userData, |
| "Text element has no content !\n"); |
| return(0); |
| } |
| return(1); |
| case XML_XINCLUDE_START: |
| case XML_XINCLUDE_END: |
| return(1); |
| case XML_CDATA_SECTION_NODE: |
| case XML_ENTITY_REF_NODE: |
| case XML_PI_NODE: |
| case XML_COMMENT_NODE: |
| return(1); |
| case XML_ENTITY_NODE: |
| VERROR(ctxt->userData, |
| "Entity element not expected here\n"); |
| return(0); |
| case XML_NOTATION_NODE: |
| VERROR(ctxt->userData, |
| "Notation element not expected here\n"); |
| return(0); |
| case XML_DOCUMENT_NODE: |
| case XML_DOCUMENT_TYPE_NODE: |
| case XML_DOCUMENT_FRAG_NODE: |
| VERROR(ctxt->userData, |
| "Document element not expected here\n"); |
| return(0); |
| case XML_HTML_DOCUMENT_NODE: |
| VERROR(ctxt->userData, |
| "\n"); |
| return(0); |
| case XML_ELEMENT_NODE: |
| break; |
| default: |
| VERROR(ctxt->userData, |
| "unknown element type %d\n", elem->type); |
| return(0); |
| } |
| if (elem->name == NULL) return(0); |
| |
| /* |
| * Fetch the declaration for the qualified name |
| */ |
| if ((elem->ns != NULL) && (elem->ns->prefix != NULL)) { |
| elemDecl = xmlGetDtdQElementDesc(doc->intSubset, |
| elem->name, elem->ns->prefix); |
| if ((elemDecl == NULL) && (doc->extSubset != NULL)) |
| elemDecl = xmlGetDtdQElementDesc(doc->extSubset, |
| elem->name, elem->ns->prefix); |
| } |
| |
| /* |
| * Fetch the declaration for the non qualified name |
| */ |
| if (elemDecl == NULL) { |
| elemDecl = xmlGetDtdElementDesc(doc->intSubset, elem->name); |
| if ((elemDecl == NULL) && (doc->extSubset != NULL)) |
| elemDecl = xmlGetDtdElementDesc(doc->extSubset, elem->name); |
| } |
| if (elemDecl == NULL) { |
| VERROR(ctxt->userData, "No declaration for element %s\n", |
| elem->name); |
| return(0); |
| } |
| |
| /* Check taht the element content matches the definition */ |
| switch (elemDecl->etype) { |
| case XML_ELEMENT_TYPE_UNDEFINED: |
| VERROR(ctxt->userData, "No declaration for element %s\n", |
| elem->name); |
| return(0); |
| case XML_ELEMENT_TYPE_EMPTY: |
| if (elem->children != NULL) { |
| VERROR(ctxt->userData, |
| "Element %s was declared EMPTY this one has content\n", |
| elem->name); |
| ret = 0; |
| } |
| break; |
| case XML_ELEMENT_TYPE_ANY: |
| /* I don't think anything is required then */ |
| break; |
| case XML_ELEMENT_TYPE_MIXED: |
| /* simple case of declared as #PCDATA */ |
| if ((elemDecl->content != NULL) && |
| (elemDecl->content->type == XML_ELEMENT_CONTENT_PCDATA)) { |
| ret = xmlValidateOneCdataElement(ctxt, doc, elem); |
| if (!ret) { |
| VERROR(ctxt->userData, |
| "Element %s was declared #PCDATA but contains non text nodes\n", |
| elem->name); |
| } |
| break; |
| } |
| child = elem->children; |
| /* Hum, this start to get messy */ |
| while (child != NULL) { |
| if (child->type == XML_ELEMENT_NODE) { |
| name = child->name; |
| if ((child->ns != NULL) && (child->ns->prefix != NULL)) { |
| xmlChar qname[500]; |
| snprintf((char *) qname, sizeof(qname), "%s:%s", |
| child->ns->prefix, child->name); |
| qname[sizeof(qname) - 1] = 0; |
| cont = elemDecl->content; |
| while (cont != NULL) { |
| if (cont->type == XML_ELEMENT_CONTENT_ELEMENT) { |
| if (xmlStrEqual(cont->name, qname)) break; |
| } else if ((cont->type == XML_ELEMENT_CONTENT_OR) && |
| (cont->c1 != NULL) && |
| (cont->c1->type == XML_ELEMENT_CONTENT_ELEMENT)){ |
| if (xmlStrEqual(cont->c1->name, qname)) break; |
| } else if ((cont->type != XML_ELEMENT_CONTENT_OR) || |
| (cont->c1 == NULL) || |
| (cont->c1->type != XML_ELEMENT_CONTENT_PCDATA)){ |
| /* Internal error !!! */ |
| xmlGenericError(xmlGenericErrorContext, |
| "Internal: MIXED struct bad\n"); |
| break; |
| } |
| cont = cont->c2; |
| } |
| if (cont != NULL) |
| goto child_ok; |
| } |
| cont = elemDecl->content; |
| while (cont != NULL) { |
| if (cont->type == XML_ELEMENT_CONTENT_ELEMENT) { |
| if (xmlStrEqual(cont->name, name)) break; |
| } else if ((cont->type == XML_ELEMENT_CONTENT_OR) && |
| (cont->c1 != NULL) && |
| (cont->c1->type == XML_ELEMENT_CONTENT_ELEMENT)) { |
| if (xmlStrEqual(cont->c1->name, name)) break; |
| } else if ((cont->type != XML_ELEMENT_CONTENT_OR) || |
| (cont->c1 == NULL) || |
| (cont->c1->type != XML_ELEMENT_CONTENT_PCDATA)) { |
| /* Internal error !!! */ |
| xmlGenericError(xmlGenericErrorContext, |
| "Internal: MIXED struct bad\n"); |
| break; |
| } |
| cont = cont->c2; |
| } |
| if (cont == NULL) { |
| VERROR(ctxt->userData, |
| "Element %s is not declared in %s list of possible childs\n", |
| name, elem->name); |
| ret = 0; |
| } |
| } |
| child_ok: |
| child = child->next; |
| } |
| break; |
| case XML_ELEMENT_TYPE_ELEMENT: |
| child = elem->children; |
| cont = elemDecl->content; |
| ret = xmlValidateElementContent(ctxt, child, cont, 1, elem->name); |
| break; |
| } |
| |
| /* [ VC: Required Attribute ] */ |
| attr = elemDecl->attributes; |
| while (attr != NULL) { |
| if (attr->def == XML_ATTRIBUTE_REQUIRED) { |
| xmlAttrPtr attrib; |
| int qualified = -1; |
| |
| attrib = elem->properties; |
| while (attrib != NULL) { |
| if (xmlStrEqual(attrib->name, attr->name)) { |
| if (attr->prefix != NULL) { |
| xmlNsPtr nameSpace = attrib->ns; |
| |
| if (nameSpace == NULL) |
| nameSpace = elem->ns; |
| /* |
| * qualified names handling is problematic, having a |
| * different prefix should be possible but DTDs don't |
| * allow to define the URI instead of the prefix :-( |
| */ |
| if (nameSpace == NULL) { |
| if (qualified < 0) |
| qualified = 0; |
| } else if (!xmlStrEqual(nameSpace->prefix, |
| attr->prefix)) { |
| if (qualified < 1) |
| qualified = 1; |
| } else |
| goto found; |
| } else { |
| /* |
| * We should allow applications to define namespaces |
| * for their application even if the DTD doesn't |
| * carry one, otherwise, basically we would always |
| * break. |
| */ |
| goto found; |
| } |
| } |
| attrib = attrib->next; |
| } |
| if (qualified == -1) { |
| if (attr->prefix == NULL) { |
| VERROR(ctxt->userData, |
| "Element %s doesn't carry attribute %s\n", |
| elem->name, attr->name); |
| ret = 0; |
| } else { |
| VERROR(ctxt->userData, |
| "Element %s doesn't carry attribute %s:%s\n", |
| elem->name, attr->prefix,attr->name); |
| ret = 0; |
| } |
| } else if (qualified == 0) { |
| VWARNING(ctxt->userData, |
| "Element %s required attribute %s:%s has no prefix\n", |
| elem->name, attr->prefix,attr->name); |
| } else if (qualified == 1) { |
| VWARNING(ctxt->userData, |
| "Element %s required attribute %s:%s has different prefix\n", |
| elem->name, attr->prefix,attr->name); |
| } |
| } |
| found: |
| attr = attr->nexth; |
| } |
| return(ret); |
| } |
| |
| /** |
| * xmlValidateRoot: |
| * @ctxt: the validation context |
| * @doc: a document instance |
| * |
| * Try to validate a the root element |
| * basically it does the following check as described by the |
| * XML-1.0 recommendation: |
| * - [ VC: Root Element Type ] |
| * it doesn't try to recurse or apply other check to the element |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| int |
| xmlValidateRoot(xmlValidCtxtPtr ctxt, xmlDocPtr doc) { |
| xmlNodePtr root; |
| if (doc == NULL) return(0); |
| |
| root = xmlDocGetRootElement(doc); |
| if ((root == NULL) || (root->name == NULL)) { |
| VERROR(ctxt->userData, "Not valid: no root element\n"); |
| return(0); |
| } |
| |
| /* |
| * When doing post validation against a separate DTD, those may |
| * no internal subset has been generated |
| */ |
| if ((doc->intSubset != NULL) && |
| (doc->intSubset->name != NULL)) { |
| /* |
| * Check first the document root against the NQName |
| */ |
| if (!xmlStrEqual(doc->intSubset->name, root->name)) { |
| if ((root->ns != NULL) && (root->ns->prefix != NULL)) { |
| xmlChar qname[500]; |
| snprintf((char *) qname, sizeof(qname), "%s:%s", |
| root->ns->prefix, root->name); |
| qname[sizeof(qname) - 1] = 0; |
| if (xmlStrEqual(doc->intSubset->name, qname)) |
| goto name_ok; |
| } |
| if ((xmlStrEqual(doc->intSubset->name, BAD_CAST "HTML")) && |
| (xmlStrEqual(root->name, BAD_CAST "html"))) |
| goto name_ok; |
| VERROR(ctxt->userData, |
| "Not valid: root and DtD name do not match '%s' and '%s'\n", |
| root->name, doc->intSubset->name); |
| return(0); |
| |
| } |
| } |
| name_ok: |
| return(1); |
| } |
| |
| |
| /** |
| * xmlValidateElement: |
| * @ctxt: the validation context |
| * @doc: a document instance |
| * @elem: an element instance |
| * |
| * Try to validate the subtree under an element |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| int |
| xmlValidateElement(xmlValidCtxtPtr ctxt, xmlDocPtr doc, xmlNodePtr elem) { |
| xmlNodePtr child; |
| xmlAttrPtr attr; |
| xmlChar *value; |
| int ret = 1; |
| |
| if (elem == NULL) return(0); |
| |
| /* |
| * XInclude elements were added after parsing in the infoset, |
| * they don't really mean anything validation wise. |
| */ |
| if ((elem->type == XML_XINCLUDE_START) || |
| (elem->type == XML_XINCLUDE_END)) |
| return(1); |
| |
| CHECK_DTD; |
| |
| /* |
| * Entities references have to be handled separately |
| */ |
| if (elem->type == XML_ENTITY_REF_NODE) { |
| return(1); |
| } |
| |
| ret &= xmlValidateOneElement(ctxt, doc, elem); |
| attr = elem->properties; |
| while(attr != NULL) { |
| value = xmlNodeListGetString(doc, attr->children, 0); |
| ret &= xmlValidateOneAttribute(ctxt, doc, elem, attr, value); |
| if (value != NULL) |
| xmlFree(value); |
| attr= attr->next; |
| } |
| child = elem->children; |
| while (child != NULL) { |
| ret &= xmlValidateElement(ctxt, doc, child); |
| child = child->next; |
| } |
| |
| return(ret); |
| } |
| |
| /** |
| * xmlValidateRef: |
| * @ref: A reference to be validated |
| * @ctxt: Validation context |
| * @name: Name of ID we are searching for |
| * |
| */ |
| static void |
| xmlValidateRef(xmlRefPtr ref, xmlValidCtxtPtr ctxt, |
| const xmlChar *name) { |
| xmlAttrPtr id; |
| xmlAttrPtr attr; |
| |
| if (ref == NULL) |
| return; |
| attr = ref->attr; |
| if (attr == NULL) |
| return; |
| if (attr->atype == XML_ATTRIBUTE_IDREF) { |
| id = xmlGetID(ctxt->doc, name); |
| if (id == NULL) { |
| VERROR(ctxt->userData, |
| "IDREF attribute %s reference an unknown ID \"%s\"\n", |
| attr->name, name); |
| ctxt->valid = 0; |
| } |
| } else if (attr->atype == XML_ATTRIBUTE_IDREFS) { |
| xmlChar *dup, *str = NULL, *cur, save; |
| |
| dup = xmlStrdup(name); |
| if (dup == NULL) { |
| ctxt->valid = 0; |
| return; |
| } |
| cur = dup; |
| while (*cur != 0) { |
| str = cur; |
| while ((*cur != 0) && (!IS_BLANK(*cur))) cur++; |
| save = *cur; |
| *cur = 0; |
| id = xmlGetID(ctxt->doc, str); |
| if (id == NULL) { |
| VERROR(ctxt->userData, |
| "IDREFS attribute %s reference an unknown ID \"%s\"\n", |
| attr->name, str); |
| ctxt->valid = 0; |
| } |
| if (save == 0) |
| break; |
| *cur = save; |
| while (IS_BLANK(*cur)) cur++; |
| } |
| xmlFree(dup); |
| } |
| } |
| |
| /** |
| * xmlWalkValidateList: |
| * @data: Contents of current link |
| * @user: Value supplied by the user |
| * |
| * Return 0 to abort the walk or 1 to continue |
| */ |
| static int |
| xmlWalkValidateList(const void *data, const void *user) |
| { |
| xmlValidateMemoPtr memo = (xmlValidateMemoPtr)user; |
| xmlValidateRef((xmlRefPtr)data, memo->ctxt, memo->name); |
| return 1; |
| } |
| |
| /** |
| * xmlValidateCheckRefCallback: |
| * @ref_list: List of references |
| * @ctxt: Validation context |
| * @name: Name of ID we are searching for |
| * |
| */ |
| static void |
| xmlValidateCheckRefCallback(xmlListPtr ref_list, xmlValidCtxtPtr ctxt, |
| const xmlChar *name) { |
| xmlValidateMemo memo; |
| |
| if (ref_list == NULL) |
| return; |
| memo.ctxt = ctxt; |
| memo.name = name; |
| |
| xmlListWalk(ref_list, xmlWalkValidateList, &memo); |
| |
| } |
| |
| /** |
| * xmlValidateDocumentFinal: |
| * @ctxt: the validation context |
| * @doc: a document instance |
| * |
| * Does the final step for the document validation once all the |
| * incremental validation steps have been completed |
| * |
| * basically it does the following checks described by the XML Rec |
| * |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| int |
| xmlValidateDocumentFinal(xmlValidCtxtPtr ctxt, xmlDocPtr doc) { |
| xmlRefTablePtr table; |
| |
| if (doc == NULL) { |
| xmlGenericError(xmlGenericErrorContext, |
| "xmlValidateDocumentFinal: doc == NULL\n"); |
| return(0); |
| } |
| |
| /* |
| * Check all the NOTATION/NOTATIONS attributes |
| */ |
| /* |
| * Check all the ENTITY/ENTITIES attributes definition for validity |
| */ |
| /* |
| * Check all the IDREF/IDREFS attributes definition for validity |
| */ |
| table = (xmlRefTablePtr) doc->refs; |
| ctxt->doc = doc; |
| ctxt->valid = 1; |
| xmlHashScan(table, (xmlHashScanner) xmlValidateCheckRefCallback, ctxt); |
| return(ctxt->valid); |
| } |
| |
| /** |
| * xmlValidateDtd: |
| * @ctxt: the validation context |
| * @doc: a document instance |
| * @dtd: a dtd instance |
| * |
| * Try to validate the document against the dtd instance |
| * |
| * basically it does check all the definitions in the DtD. |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| int |
| xmlValidateDtd(xmlValidCtxtPtr ctxt, xmlDocPtr doc, xmlDtdPtr dtd) { |
| int ret; |
| xmlDtdPtr oldExt; |
| xmlNodePtr root; |
| |
| if (dtd == NULL) return(0); |
| if (doc == NULL) return(0); |
| oldExt = doc->extSubset; |
| doc->extSubset = dtd; |
| ret = xmlValidateRoot(ctxt, doc); |
| if (ret == 0) { |
| doc->extSubset = oldExt; |
| return(ret); |
| } |
| if (doc->ids != NULL) { |
| xmlFreeIDTable(doc->ids); |
| doc->ids = NULL; |
| } |
| if (doc->refs != NULL) { |
| xmlFreeRefTable(doc->refs); |
| doc->refs = NULL; |
| } |
| root = xmlDocGetRootElement(doc); |
| ret = xmlValidateElement(ctxt, doc, root); |
| ret &= xmlValidateDocumentFinal(ctxt, doc); |
| doc->extSubset = oldExt; |
| return(ret); |
| } |
| |
| static void |
| xmlValidateAttributeCallback(xmlAttributePtr cur, xmlValidCtxtPtr ctxt, |
| const xmlChar *name ATTRIBUTE_UNUSED) { |
| if (cur == NULL) |
| return; |
| switch (cur->atype) { |
| case XML_ATTRIBUTE_CDATA: |
| case XML_ATTRIBUTE_ID: |
| case XML_ATTRIBUTE_IDREF : |
| case XML_ATTRIBUTE_IDREFS: |
| case XML_ATTRIBUTE_NMTOKEN: |
| case XML_ATTRIBUTE_NMTOKENS: |
| case XML_ATTRIBUTE_ENUMERATION: |
| break; |
| case XML_ATTRIBUTE_ENTITY: |
| case XML_ATTRIBUTE_ENTITIES: |
| case XML_ATTRIBUTE_NOTATION: |
| if (cur->defaultValue != NULL) { |
| ctxt->valid &= xmlValidateAttributeValue2(ctxt, ctxt->doc, |
| cur->name, cur->atype, cur->defaultValue); |
| } |
| if (cur->tree != NULL) { |
| xmlEnumerationPtr tree = cur->tree; |
| while (tree != NULL) { |
| ctxt->valid &= xmlValidateAttributeValue2(ctxt, ctxt->doc, |
| cur->name, cur->atype, tree->name); |
| tree = tree->next; |
| } |
| } |
| } |
| } |
| |
| /** |
| * xmlValidateDtdFinal: |
| * @ctxt: the validation context |
| * @doc: a document instance |
| * |
| * Does the final step for the dtds validation once all the |
| * subsets have been parsed |
| * |
| * basically it does the following checks described by the XML Rec |
| * - check that ENTITY and ENTITIES type attributes default or |
| * possible values matches one of the defined entities. |
| * - check that NOTATION type attributes default or |
| * possible values matches one of the defined notations. |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| int |
| xmlValidateDtdFinal(xmlValidCtxtPtr ctxt, xmlDocPtr doc) { |
| int ret = 1; |
| xmlDtdPtr dtd; |
| xmlAttributeTablePtr table; |
| |
| if (doc == NULL) return(0); |
| if ((doc->intSubset == NULL) && (doc->extSubset == NULL)) |
| return(0); |
| ctxt->doc = doc; |
| ctxt->valid = ret; |
| dtd = doc->intSubset; |
| if ((dtd != NULL) && (dtd->attributes != NULL)) { |
| table = (xmlAttributeTablePtr) dtd->attributes; |
| xmlHashScan(table, (xmlHashScanner) xmlValidateAttributeCallback, ctxt); |
| } |
| dtd = doc->extSubset; |
| if ((dtd != NULL) && (dtd->attributes != NULL)) { |
| table = (xmlAttributeTablePtr) dtd->attributes; |
| xmlHashScan(table, (xmlHashScanner) xmlValidateAttributeCallback, ctxt); |
| } |
| return(ctxt->valid); |
| } |
| |
| /** |
| * xmlValidateDocument: |
| * @ctxt: the validation context |
| * @doc: a document instance |
| * |
| * Try to validate the document instance |
| * |
| * basically it does the all the checks described by the XML Rec |
| * i.e. validates the internal and external subset (if present) |
| * and validate the document tree. |
| * |
| * returns 1 if valid or 0 otherwise |
| */ |
| |
| int |
| xmlValidateDocument(xmlValidCtxtPtr ctxt, xmlDocPtr doc) { |
| int ret; |
| xmlNodePtr root; |
| |
| if ((doc->intSubset == NULL) && (doc->extSubset == NULL)) |
| return(0); |
| if ((doc->intSubset != NULL) && ((doc->intSubset->SystemID != NULL) || |
| (doc->intSubset->ExternalID != NULL)) && (doc->extSubset == NULL)) { |
| doc->extSubset = xmlParseDTD(doc->intSubset->ExternalID, |
| doc->intSubset->SystemID); |
| if (doc->extSubset == NULL) { |
| if (doc->intSubset->SystemID != NULL) { |
| VERROR(ctxt->userData, |
| "Could not load the external subset \"%s\"\n", |
| doc->intSubset->SystemID); |
| } else { |
| VERROR(ctxt->userData, |
| "Could not load the external subset \"%s\"\n", |
| doc->intSubset->ExternalID); |
| } |
| return(0); |
| } |
| } |
| |
| if (doc->ids != NULL) { |
| xmlFreeIDTable(doc->ids); |
| doc->ids = NULL; |
| } |
| if (doc->refs != NULL) { |
| xmlFreeRefTable(doc->refs); |
| doc->refs = NULL; |
| } |
| ret = xmlValidateDtdFinal(ctxt, doc); |
| if (!xmlValidateRoot(ctxt, doc)) return(0); |
| |
| root = xmlDocGetRootElement(doc); |
| ret &= xmlValidateElement(ctxt, doc, root); |
| ret &= xmlValidateDocumentFinal(ctxt, doc); |
| return(ret); |
| } |
| |
| |
| /************************************************************************ |
| * * |
| * Routines for dynamic validation editing * |
| * * |
| ************************************************************************/ |
| |
| /** |
| * xmlValidGetPotentialChildren: |
| * @ctree: an element content tree |
| * @list: an array to store the list of child names |
| * @len: a pointer to the number of element in the list |
| * @max: the size of the array |
| * |
| * Build/extend a list of potential children allowed by the content tree |
| * |
| * returns the number of element in the list, or -1 in case of error. |
| */ |
| |
| int |
| xmlValidGetPotentialChildren(xmlElementContent *ctree, const xmlChar **list, |
| int *len, int max) { |
| int i; |
| |
| if ((ctree == NULL) || (list == NULL) || (len == NULL)) |
| return(-1); |
| if (*len >= max) return(*len); |
| |
| switch (ctree->type) { |
| case XML_ELEMENT_CONTENT_PCDATA: |
| for (i = 0; i < *len;i++) |
| if (xmlStrEqual(BAD_CAST "#PCDATA", list[i])) return(*len); |
| list[(*len)++] = BAD_CAST "#PCDATA"; |
| break; |
| case XML_ELEMENT_CONTENT_ELEMENT: |
| for (i = 0; i < *len;i++) |
| if (xmlStrEqual(ctree->name, list[i])) return(*len); |
| list[(*len)++] = ctree->name; |
| break; |
| case XML_ELEMENT_CONTENT_SEQ: |
| xmlValidGetPotentialChildren(ctree->c1, list, len, max); |
| xmlValidGetPotentialChildren(ctree->c2, list, len, max); |
| break; |
| case XML_ELEMENT_CONTENT_OR: |
| xmlValidGetPotentialChildren(ctree->c1, list, len, max); |
| xmlValidGetPotentialChildren(ctree->c2, list, len, max); |
| break; |
| } |
| |
| return(*len); |
| } |
| |
| /** |
| * xmlValidGetValidElements: |
| * @prev: an element to insert after |
| * @next: an element to insert next |
| * @list: an array to store the list of child names |
| * @max: the size of the array |
| * |
| * This function returns the list of authorized children to insert |
| * within an existing tree while respecting the validity constraints |
| * forced by the Dtd. The insertion point is defined using @prev and |
| * @next in the following ways: |
| * to insert before 'node': xmlValidGetValidElements(node->prev, node, ... |
| * to insert next 'node': xmlValidGetValidElements(node, node->next, ... |
| * to replace 'node': xmlValidGetValidElements(node->prev, node->next, ... |
| * to prepend a child to 'node': xmlValidGetValidElements(NULL, node->childs, |
| * to append a child to 'node': xmlValidGetValidElements(node->last, NULL, ... |
| * |
| * pointers to the element names are inserted at the beginning of the array |
| * and do not need to be freed. |
| * |
| * returns the number of element in the list, or -1 in case of error. If |
| * the function returns the value @max the caller is invited to grow the |
| * receiving array and retry. |
| */ |
| |
| int |
| xmlValidGetValidElements(xmlNode *prev, xmlNode *next, const xmlChar **list, |
| int max) { |
| xmlValidCtxt vctxt; |
| int nb_valid_elements = 0; |
| const xmlChar *elements[256]; |
| int nb_elements = 0, i; |
| |
| xmlNode *ref_node; |
| xmlNode *parent; |
| xmlNode *test_node; |
| |
| xmlNode *prev_next; |
| xmlNode *next_prev; |
| xmlNode *parent_childs; |
| xmlNode *parent_last; |
| |
| xmlElement *element_desc; |
| |
| vctxt.userData = NULL; |
| vctxt.error = NULL; |
| vctxt.warning = NULL; |
| |
| if (prev == NULL && next == NULL) |
| return(-1); |
| |
| if (list == NULL) return(-1); |
| if (max <= 0) return(-1); |
| |
| nb_valid_elements = 0; |
| ref_node = prev ? prev : next; |
| parent = ref_node->parent; |
| |
| /* |
| * Retrieves the parent element declaration |
| */ |
| element_desc = xmlGetDtdElementDesc(parent->doc->intSubset, |
| parent->name); |
| if ((element_desc == NULL) && (parent->doc->extSubset != NULL)) |
| element_desc = xmlGetDtdElementDesc(parent->doc->extSubset, |
| parent->name); |
| if (element_desc == NULL) return(-1); |
| |
| /* |
| * Do a backup of the current tree structure |
| */ |
| prev_next = prev ? prev->next : NULL; |
| next_prev = next ? next->prev : NULL; |
| parent_childs = parent->children; |
| parent_last = parent->last; |
| |
| /* |
| * Creates a dummy node and insert it into the tree |
| */ |
| test_node = xmlNewNode (NULL, BAD_CAST "<!dummy?>"); |
| test_node->doc = ref_node->doc; |
| test_node->parent = parent; |
| test_node->prev = prev; |
| test_node->next = next; |
| |
| if (prev) prev->next = test_node; |
| else parent->children = test_node; |
| |
| if (next) next->prev = test_node; |
| else parent->last = test_node; |
| |
| /* |
| * Insert each potential child node and check if the parent is |
| * still valid |
| */ |
| nb_elements = xmlValidGetPotentialChildren(element_desc->content, |
| elements, &nb_elements, 256); |
| |
| for (i = 0;i < nb_elements;i++) { |
| test_node->name = elements[i]; |
| if (xmlValidateOneElement(&vctxt, parent->doc, parent)) { |
| int j; |
| |
| for (j = 0; j < nb_valid_elements;j++) |
| if (xmlStrEqual(elements[i], list[j])) break; |
| list[nb_valid_elements++] = elements[i]; |
| if (nb_valid_elements >= max) break; |
| } |
| } |
| |
| /* |
| * Restore the tree structure |
| */ |
| if (prev) prev->next = prev_next; |
| if (next) next->prev = next_prev; |
| parent->children = parent_childs; |
| parent->last = parent_last; |
| |
| return(nb_valid_elements); |
| } |