/* DOMC Document Object Model library in C * Copyright (c) 2001 Michael B. Allen * * The MIT License * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ /* node.c - DOM_NodeList, DOM_NamedNodeMap, and DOM_Node */ #include #include #include "defines.h" #include #include "mbs.h" #include "domc.h" #include "dom.h" /* Forward references */ void DOM_Document_destroyNamedNodeMap(DOM_Document *doc, DOM_NamedNodeMap *nnm, int free_nodes); void updateCommonParent(DOM_Node *node); /* Node */ void DOM_Document_destroyNode(DOM_Document *doc, DOM_Node *node) { if (node == NULL) { return; } if (node->childNodes) { DOM_Document_destroyNodeList(doc, node->childNodes, 1); } if (node->listeners) { unsigned int i; for (i = 0; i < node->listeners_len; i++) { if (node->listeners[i]) { free(node->listeners[i]->type); free(node->listeners[i]); } } free(node->listeners); } switch(node->nodeType) { case DOM_ELEMENT_NODE: DOM_Document_destroyNamedNodeMap(doc, node->attributes, 1); free(node->nodeName); break; case DOM_TEXT_NODE: case DOM_COMMENT_NODE: case DOM_CDATA_SECTION_NODE: free(node->nodeValue); break; case DOM_ATTRIBUTE_NODE: free(node->nodeName); free(node->nodeValue); break; case DOM_ENTITY_REFERENCE_NODE: case DOM_ENTITY_NODE: free(node->nodeName); free(node->nodeValue); free(node->u.Entity.publicId); free(node->u.Entity.systemId); free(node->u.Entity.notationName); break; case DOM_PROCESSING_INSTRUCTION_NODE: free(node->nodeName); free(node->nodeValue); break; case DOM_DOCUMENT_NODE: free(node->u.Document.version); free(node->u.Document.encoding); break; case DOM_DOCUMENT_TYPE_NODE: DOM_Document_destroyNamedNodeMap(doc, node->u.DocumentType.entities, 0); DOM_Document_destroyNamedNodeMap(doc, node->u.DocumentType.notations, 0); free(node->u.DocumentType.publicId); free(node->u.DocumentType.systemId); free(node->nodeName); break; case DOM_NOTATION_NODE: free(node->nodeName); free(node->u.Notation.publicId); free(node->u.Notation.systemId); break; } free(node); } DOM_Node * Document_createNode(DOM_Document *doc, unsigned short nodeType) { DOM_Node *node; msgno_add_codes(dom_codes); if (doc == NULL && nodeType != DOM_DOCUMENT_NODE && nodeType != DOM_DOCUMENT_TYPE_NODE) { DOM_Exception = NULL_POINTER_ERR; PMNF(DOM_Exception, ": doc=NULL,nodeType=%u", nodeType); return NULL; } node = calloc(sizeof *node, 1); if (node == NULL) { DOM_Exception = errno; PMNO(DOM_Exception); return NULL; } node->nodeType = nodeType; node->ownerDocument = doc; switch (nodeType) { case DOM_DOCUMENT_NODE: case DOM_DOCUMENT_TYPE_NODE: /* DocumentType doesn't really have children but we need to store DTD * entries other than entities and notations */ case DOM_ELEMENT_NODE: case DOM_ATTRIBUTE_NODE: case DOM_ENTITY_REFERENCE_NODE: case DOM_ENTITY_NODE: case DOM_DOCUMENT_FRAGMENT_NODE: node->childNodes = Document_createNodeList(doc); if (node->childNodes == NULL) { AMNO(DOM_CREATE_FAILED); DOM_Document_destroyNode(doc, node); return NULL; } } return node; } static int _isAncestor(DOM_Node *node, DOM_Node *parent) { DOM_Node *p; for (p = parent; p; p = p->parentNode) { if (p == node) { return 1; } } return 0; } static void dispatchEventPreorder(DOM_Node *node, DOM_MutationEvent *evt) { DOM_Node *n; DOM_EventTarget_dispatchEvent(node, evt); for (n = node->firstChild; n != NULL; n = n->nextSibling) { dispatchEventPreorder(n, evt); } } static void dispatchEventPostorder(DOM_Node *node, DOM_MutationEvent *evt) { DOM_Node *n; for (n = node->firstChild; n != NULL; n = n->nextSibling) { dispatchEventPostorder(n, evt); } DOM_EventTarget_dispatchEvent(node, evt); } static DOM_Node * _removeChild(DOM_Node *node, DOM_Node *oldChild) { NodeEntry *e; DOM_MutationEvent evt; if (NodeList_exists(node->childNodes, oldChild) == 0) { return NULL; } DOM_MutationEvent_initMutationEvent(&evt, "DOMNodeRemoved", 1, 0, node, NULL, NULL, NULL, 0); DOM_EventTarget_dispatchEvent(oldChild, &evt); DOM_MutationEvent_initMutationEvent(&evt, "DOMNodeRemovedFromDocument", 0, 0, NULL, NULL, NULL, NULL, 0); dispatchEventPostorder(oldChild, &evt); e = NodeList_remove(node->childNodes, oldChild); free(e); if (node->firstChild == node->lastChild) { node->firstChild = node->lastChild = NULL; } else if (oldChild == node->firstChild) { node->firstChild = oldChild->nextSibling; node->firstChild->previousSibling = NULL; } else if (oldChild == node->lastChild) { node->lastChild = oldChild->previousSibling; node->lastChild->nextSibling = NULL; } else { oldChild->previousSibling->nextSibling = oldChild->nextSibling; oldChild->nextSibling->previousSibling = oldChild->previousSibling; } oldChild->previousSibling = NULL; oldChild->nextSibling = NULL; oldChild->parentNode = NULL; if (MODIFYING_DOC_ELEM(node, oldChild)) { node->u.Document.documentElement = NULL; } else if (MODIFYING_DOCTYPE_ELEM(node, oldChild)) { node->u.Document.doctype = NULL; oldChild->ownerDocument = NULL; } else { updateCommonParent(node); } return oldChild; } DOM_Node * DOM_Node_insertBefore(DOM_Node *node, DOM_Node *newChild, DOM_Node *refChild) { NodeEntry *e; DOM_MutationEvent evt; if (node == NULL || newChild == NULL) { DOM_Exception = NULL_POINTER_ERR; PMNO(DOM_Exception); return NULL; } if (newChild->ownerDocument != node->ownerDocument && newChild->ownerDocument != node) { DOM_Exception = DOM_WRONG_DOCUMENT_ERR; PMNO(DOM_Exception); return NULL; } if (refChild != NULL && refChild->parentNode != node) { DOM_Exception = DOM_HIERARCHY_REQUEST_ERR; PMNO(DOM_Exception); return NULL; } if (newChild->nodeType == DOM_DOCUMENT_FRAGMENT_NODE) { DOM_Node *n, *nxt; for (n = newChild->firstChild; n != NULL; n = n->nextSibling) { if (INVALID_HIER_REQ(node, n) || _isAncestor(n, node)) { DOM_Exception = DOM_HIERARCHY_REQUEST_ERR; PMNO(DOM_Exception); return NULL; } } for (n = newChild->firstChild; n != NULL; n = nxt) { nxt = n->nextSibling; if (_removeChild(newChild, n) == NULL) { return NULL; } if (DOM_Node_insertBefore(node, n, refChild) == NULL) { DOM_Document_destroyNode(n->ownerDocument, n); return NULL; } } return newChild; } if (INVALID_HIER_REQ(node, newChild) || _isAncestor(newChild, node)) { DOM_Exception = DOM_HIERARCHY_REQUEST_ERR; PMNO(DOM_Exception); return NULL; } _removeChild(node, newChild); if ((e = NodeList_insert(node->childNodes, newChild, refChild)) == NULL) { return NULL; } if (node->firstChild == NULL) { node->firstChild = node->lastChild = newChild; newChild->previousSibling = NULL; newChild->nextSibling = NULL; } else if (refChild == NULL) { newChild->previousSibling = node->lastChild; node->lastChild->nextSibling = newChild; node->lastChild = newChild; newChild->nextSibling = NULL; } else { newChild->previousSibling = refChild->previousSibling; newChild->nextSibling = refChild; if (refChild == node->firstChild) { node->firstChild = newChild; newChild->previousSibling = NULL; } else { refChild->previousSibling->nextSibling = newChild; } refChild->previousSibling = newChild; } newChild->parentNode = node; if (MODIFYING_DOC_ELEM(node, newChild)) { node->u.Document.documentElement = newChild; } else if (MODIFYING_DOCTYPE_ELEM(node, newChild)) { node->u.Document.doctype = newChild; newChild->ownerDocument = node; } DOM_MutationEvent_initMutationEvent(&evt, "DOMNodeInserted", 1, 0, node, NULL, NULL, NULL, 0); DOM_EventTarget_dispatchEvent(newChild, &evt); DOM_MutationEvent_initMutationEvent(&evt, "DOMNodeInsertedIntoDocument", 0, 0, NULL, NULL, NULL, NULL, 0); dispatchEventPreorder(newChild, &evt); updateCommonParent(node); return newChild; } DOM_Node * DOM_Node_replaceChild(DOM_Node *node, DOM_Node *newChild, DOM_Node *oldChild) { DOM_MutationEvent evt; if (node == NULL || newChild == NULL || oldChild == NULL) { DOM_Exception = NULL_POINTER_ERR; PMNO(DOM_Exception); return NULL; } if (newChild->ownerDocument != node->ownerDocument && newChild->ownerDocument != node) { DOM_Exception = DOM_WRONG_DOCUMENT_ERR; PMNO(DOM_Exception); return NULL; } if (!NodeList_exists(node->childNodes, oldChild)) { DOM_Exception = DOM_NOT_FOUND_ERR; PMNO(DOM_Exception); return NULL; } if (newChild->nodeType == DOM_DOCUMENT_FRAGMENT_NODE) { DOM_Node *n, *nxt; for (n = newChild->firstChild; n != NULL; n = n->nextSibling) { if (INVALID_HIER_REQ(node, n) || _isAncestor(n, node)) { DOM_Exception = DOM_HIERARCHY_REQUEST_ERR; PMNO(DOM_Exception); return NULL; } } for (n = newChild->firstChild; n != NULL; n = nxt) { nxt = n->nextSibling; if (_removeChild(newChild, n) == NULL) { return NULL; } if (DOM_Node_insertBefore(node, n, oldChild) == NULL) { DOM_Document_destroyNode(n->ownerDocument, n); return NULL; } } if (_removeChild(node, oldChild) == NULL) { return NULL; } return oldChild; } if (INVALID_HIER_REQ(node, newChild) || _isAncestor(newChild, node)) { DOM_Exception = DOM_HIERARCHY_REQUEST_ERR; PMNO(DOM_Exception); return NULL; } _removeChild(node, newChild); if (NodeList_exists(node->childNodes, oldChild) == 0) { return NULL; } DOM_MutationEvent_initMutationEvent(&evt, "DOMNodeRemoved", 1, 0, node, NULL, NULL, NULL, 0); DOM_EventTarget_dispatchEvent(oldChild, &evt); DOM_MutationEvent_initMutationEvent(&evt, "DOMNodeRemovedFromDocument", 0, 0, NULL, NULL, NULL, NULL, 0); dispatchEventPostorder(oldChild, &evt); NodeList_replace(node->childNodes, newChild, oldChild); node->firstChild = node->childNodes->first->node; node->lastChild = node->childNodes->last->node; if ((newChild->previousSibling = oldChild->previousSibling)) { newChild->previousSibling->nextSibling = newChild; } if ((newChild->nextSibling = oldChild->nextSibling)) { newChild->nextSibling->previousSibling = newChild; } newChild->parentNode = node; oldChild->parentNode = NULL; oldChild->previousSibling = NULL; oldChild->nextSibling = NULL; if (MODIFYING_DOC_ELEM(node, newChild)) { node->u.Document.documentElement = newChild; } else if (MODIFYING_DOCTYPE_ELEM(node, newChild)) { node->u.Document.doctype = newChild; newChild->ownerDocument = node; } DOM_MutationEvent_initMutationEvent(&evt, "DOMNodeInserted", 1, 0, node, NULL, NULL, NULL, 0); DOM_EventTarget_dispatchEvent(newChild, &evt); DOM_MutationEvent_initMutationEvent(&evt, "DOMNodeInsertedIntoDocument", 0, 0, NULL, NULL, NULL, NULL, 0); dispatchEventPreorder(newChild, &evt); updateCommonParent(node); return oldChild; } DOM_Node * DOM_Node_removeChild(DOM_Node *node, DOM_Node *oldChild) { if (node == NULL || oldChild == NULL) { DOM_Exception = NULL_POINTER_ERR; PMNO(DOM_Exception); return NULL; } if (oldChild->ownerDocument != node->ownerDocument && oldChild->ownerDocument != node) { DOM_Exception = DOM_WRONG_DOCUMENT_ERR; PMNO(DOM_Exception); return NULL; } if ((oldChild = _removeChild(node, oldChild)) == NULL) { DOM_Exception = DOM_NOT_FOUND_ERR; PMNO(DOM_Exception); } return oldChild; } DOM_Node * DOM_Node_appendChild(DOM_Node *node, DOM_Node *newChild) { DOM_MutationEvent evt; if (node == NULL || newChild == NULL) { DOM_Exception = NULL_POINTER_ERR; PMNO(DOM_Exception); return NULL; } if (newChild->ownerDocument != node->ownerDocument && node->nodeType != DOM_DOCUMENT_NODE && newChild->nodeType != DOM_DOCUMENT_TYPE_NODE) { DOM_Exception = DOM_WRONG_DOCUMENT_ERR; PMNO(DOM_Exception); return NULL; } if (newChild->nodeType == DOM_DOCUMENT_FRAGMENT_NODE) { DOM_Node *n, *nxt; for (n = newChild->firstChild; n != NULL; n = n->nextSibling) { if (INVALID_HIER_REQ(node, n) || _isAncestor(n, node)) { DOM_Exception = DOM_HIERARCHY_REQUEST_ERR; PMNO(DOM_Exception); return NULL; } } for (n = newChild->firstChild; n != NULL; n = nxt) { nxt = n->nextSibling; if (_removeChild(newChild, n) == NULL) { return NULL; } if (DOM_Node_appendChild(node, n) == NULL) { DOM_Document_destroyNode(n->ownerDocument, n); return NULL; } } return newChild; } if (INVALID_HIER_REQ(node, newChild) || _isAncestor(newChild, node)) { DOM_Exception = DOM_HIERARCHY_REQUEST_ERR; PMNO(DOM_Exception); return NULL; } _removeChild(node, newChild); if (NodeList_append(node->childNodes, newChild) == NULL) { return NULL; } if (node->firstChild == NULL) { node->firstChild = node->lastChild = newChild; newChild->previousSibling = NULL; newChild->nextSibling = NULL; } else { node->lastChild->nextSibling = newChild; newChild->previousSibling = node->lastChild; node->lastChild = newChild; } newChild->nextSibling = NULL; newChild->parentNode = node; if (MODIFYING_DOC_ELEM(node, newChild)) { node->u.Document.documentElement = newChild; } else if (MODIFYING_DOCTYPE_ELEM(node, newChild)) { node->u.Document.doctype = newChild; newChild->ownerDocument = node; } DOM_MutationEvent_initMutationEvent(&evt, "DOMNodeInserted", 1, 0, node, NULL, NULL, NULL, 0); DOM_EventTarget_dispatchEvent(newChild, &evt); DOM_MutationEvent_initMutationEvent(&evt, "DOMNodeInsertedIntoDocument", 0, 0, NULL, NULL, NULL, NULL, 0); dispatchEventPreorder(newChild, &evt); updateCommonParent(node); return newChild; } int DOM_Node_hasChildNodes(const DOM_Node *node) { return node != NULL && node->firstChild; } extern const char *node_names[]; static DOM_Node * Node_cloneNode(DOM_Document *ownerDocument, DOM_Node *node, int deep) { DOM_Node *clone = NULL; DOM_Node *ntmp, *ctmp; NodeEntry *e; DOM_String *tmp; switch(node->nodeType) { case DOM_ELEMENT_NODE: clone = DOM_Document_createElement(ownerDocument, node->nodeName); if (clone) { for (e = node->attributes->first; e != NULL; e = e->next) { if ((ctmp = Node_cloneNode(ownerDocument, e->node, deep)) == NULL || NodeList_append(clone->attributes, ctmp) == NULL) { DOM_Document_destroyNode(clone->ownerDocument, ctmp); DOM_Document_destroyNode(clone->ownerDocument, clone); return NULL; } } } break; case DOM_ATTRIBUTE_NODE: if ((clone = DOM_Document_createAttribute(ownerDocument, node->nodeName))) { clone->u.Attr.specified = node->u.Attr.specified; free(clone->nodeValue); clone->u.Attr.value = clone->nodeValue = mbsdup(node->nodeValue); if (clone->u.Attr.value == NULL) { DOM_Exception = errno; PMNO(DOM_Exception); DOM_Document_destroyNode(clone->ownerDocument, clone); return NULL; } } break; case DOM_COMMENT_NODE: clone = DOM_Document_createComment(ownerDocument, node->nodeValue); break; case DOM_TEXT_NODE: clone = DOM_Document_createTextNode(ownerDocument, node->nodeValue); break; case DOM_CDATA_SECTION_NODE: clone = DOM_Document_createCDATASection(ownerDocument, node->nodeValue); break; case DOM_DOCUMENT_FRAGMENT_NODE: clone = DOM_Document_createDocumentFragment(ownerDocument); break; case DOM_DOCUMENT_NODE: clone = ownerDocument; break; case DOM_PROCESSING_INSTRUCTION_NODE: clone = DOM_Document_createProcessingInstruction(ownerDocument, node->u.ProcessingInstruction.target, node->u.ProcessingInstruction.data); break; case DOM_ENTITY_NODE: if ((clone = Document_createNode(ownerDocument, DOM_ENTITY_NODE))) { tmp = node->nodeValue; if ((clone->nodeName = mbsdup(node->nodeName)) == NULL || /* This will eventually go away as Entities should not have nodeValues */ (clone->nodeValue = mbsdup(node->nodeValue)) == NULL || (node->u.Entity.publicId && (clone->u.Entity.publicId = mbsdup(node->u.Entity.publicId)) == NULL) || (node->u.Entity.systemId && (clone->u.Entity.systemId = mbsdup(node->u.Entity.systemId)) == NULL) || (node->u.Entity.notationName && (clone->u.Entity.notationName = mbsdup(node->u.Entity.notationName)) == NULL)) { DOM_Exception = errno; PMNO(DOM_Exception); DOM_Document_destroyNode(clone->ownerDocument, clone); return NULL; } free(tmp); } break; case DOM_NOTATION_NODE: if ((clone = Document_createNode(ownerDocument, DOM_NOTATION_NODE))) { if ((clone->nodeName = mbsdup(node->nodeName)) == NULL || (node->u.Notation.publicId && (clone->u.Notation.publicId = mbsdup(node->u.Notation.publicId)) == NULL) || (node->u.Notation.systemId && (clone->u.Notation.systemId = mbsdup(node->u.Notation.systemId)) == NULL)) { DOM_Exception = errno; PMNO(DOM_Exception); DOM_Document_destroyNode(clone->ownerDocument, clone); return NULL; } } break; case DOM_DOCUMENT_TYPE_NODE: if ((clone = DOM_Implementation_createDocumentType(node->nodeName, NULL, NULL))) { if ((node->u.DocumentType.publicId && (clone->u.DocumentType.publicId = mbsdup(node->u.DocumentType.publicId)) == NULL) || (node->u.DocumentType.systemId && (clone->u.DocumentType.systemId = mbsdup(node->u.DocumentType.systemId)) == NULL)) { DOM_Exception = errno; PMNO(DOM_Exception); DOM_Document_destroyNode(clone->ownerDocument, clone); return NULL; } } ownerDocument->u.Document.doctype = clone; clone->ownerDocument = ownerDocument; break; case DOM_ENTITY_REFERENCE_NODE: DOM_Exception = DOM_NOT_SUPPORTED_ERR; PMNO(DOM_Exception); return NULL; } if (deep && clone && node->childNodes) { for (ntmp = node->firstChild; ntmp != NULL; ntmp = ntmp->nextSibling) { ctmp = Node_cloneNode(ownerDocument, ntmp, 1); if (ctmp == NULL || DOM_Node_appendChild(clone, ctmp) == NULL) { DOM_Document_destroyNode(clone->ownerDocument, ctmp); DOM_Document_destroyNode(clone->ownerDocument, clone); return NULL; } } } return clone; } DOM_Node * DOM_Node_cloneNode(DOM_Node *node, int deep) { if (node == NULL) { DOM_Exception = NULL_POINTER_ERR; PMNO(DOM_Exception); return NULL; } if (node->nodeType == DOM_DOCUMENT_NODE) { DOM_Document *doc; if ((doc = DOM_Implementation_createDocument(NULL, NULL, NULL)) == NULL) { AMSG(""); return NULL; } return Node_cloneNode(doc, node, deep); } return Node_cloneNode(node->ownerDocument, node, deep); } DOM_String * DOM_Node_getNodeValue(DOM_Node *node) { if (node == NULL) { DOM_Exception = NULL_POINTER_ERR; PMNO(DOM_Exception); return NULL; } return node->nodeValue; } void DOM_Node_setNodeValue(DOM_Node *node, DOM_String *value) { DOM_String *str = NULL; if (node == NULL) { DOM_Exception = NULL_POINTER_ERR; PMNO(DOM_Exception); return; } switch(node->nodeType) { case DOM_ATTRIBUTE_NODE: case DOM_COMMENT_NODE: case DOM_TEXT_NODE: case DOM_CDATA_SECTION_NODE: case DOM_PROCESSING_INSTRUCTION_NODE: if ((str = mbsdup(value)) == NULL) { DOM_Exception = errno; AMSG(""); return; } break; } switch(node->nodeType) { case DOM_ATTRIBUTE_NODE: free(node->nodeValue); node->nodeValue = node->u.Attr.value = str; break; case DOM_COMMENT_NODE: case DOM_TEXT_NODE: case DOM_CDATA_SECTION_NODE: free(node->nodeValue); node->nodeValue = node->u.CharacterData.data = str; break; case DOM_PROCESSING_INSTRUCTION_NODE: free(node->nodeValue); node->nodeValue = node->u.ProcessingInstruction.data = str; break; default: return; /* No effect */ } }