/* * 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 #ifdef HAVE_STDLIB_H #include #endif #include #include #include #include #include #include #include #include /* #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 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 typedef 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; #ifdef LIBXML_DOCB_ENABLED case XML_DOCB_DOCUMENT_NODE: xmlGenericError(xmlGenericErrorContext, "?docb? "); break; #endif 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; #ifndef XML_XML_NAMESPACE /* xml: prefix is not really a namespace */ if ((name[0] == 'x') && (name[1] == 'm') && (name[2] == 'l') && (name[3] == ':')) return(NULL); #endif /* 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, "name); xmlBufferWriteChar(buf, " EMPTY>\n"); break; case XML_ELEMENT_TYPE_ANY: xmlBufferWriteChar(buf, "name); xmlBufferWriteChar(buf, " ANY>\n"); break; case XML_ELEMENT_TYPE_MIXED: xmlBufferWriteChar(buf, "name); xmlBufferWriteChar(buf, " "); xmlDumpElementContent(buf, elem->content, 1); xmlBufferWriteChar(buf, ">\n"); break; case XML_ELEMENT_TYPE_ELEMENT: xmlBufferWriteChar(buf, "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, "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, "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 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->parent == NULL) || (CONT->parent->type != XML_ELEMENT_CONTENT_OR)) && ((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 ""); 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); }