/* 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. */ /* dom.c - document object model interface */ #include #include #include #include "defines.h" #include #include "mbs.h" #include "domc.h" #include "dom.h" #if HAVE_STRDUP < 1 static char * strdup(const char *s) { return s ? strcpy(malloc(strlen(s) + 1), s) : NULL; } #endif /* Forward references for node.c */ DOM_Node *Document_createNode(DOM_Document *doc, unsigned short nodeType); NodeEntry *NodeList_remove(DOM_NodeList *nl, DOM_Node *oldChild); NodeEntry *NodeList_append(DOM_NodeList *nl, DOM_Node *newChild); DOM_NodeList *Document_createNodeList(DOM_Document *doc); DOM_NamedNodeMap *Document_createNamedNodeMap(DOM_Document *doc); /* Forward references for events.c */ void updateCommonParent(DOM_Node *node); /* DOM_Exception */ struct msgno_entry dom_codes[] = { { 1, "The index specified was out of range" }, { 0, "The text size is out of range" }, { 0, "The request violated tree hierarchy constraints" }, { 0, "The document context is invalid" }, { 0, "An inappropriate character was encountered" }, { 0, "The node does not support the addition of data" }, { 0, "No modification allowed" }, { 0, "The specified node was not found" }, { 0, "The requested operation is not supported" }, { 0, "The attribute is being used elsewhere" }, { 0, "An XML parser error occured" }, { 0, "Failed to create DOM object" }, { 0, "Character encoding error" }, { 0, "The event type was not specified by initialization" }, { 0, "A filtered list cannot be modified" }, { 0, NULL } }; int _exception = 0; int * _DOM_Exception(void) { return &_exception; } unsigned short child_matrix[] = { 0x00dd, /* DOM_ELEMENT_NODE 1 */ 0x0014, /* DOM_ATTRIBUTE_NODE 2 */ 0x0000, /* DOM_TEXT_NODE 3 */ 0x0000, /* DOM_CDATA_SECTION_NODE 4 */ 0x00dd, /* DOM_ENTITY_REFERENCE_NODE 5 */ 0x00dd, /* DOM_ENTITY_NODE 6 */ 0x0000, /* DOM_PROCESSING_INSTRUCTION_NODE 7 */ 0x0000, /* DOM_COMMENT_NODE 8 */ 0x02c1, /* DOM_DOCUMENT_NODE 9 */ 0x0820, /* DOM_DOCUMENT_TYPE_NODE 10 */ 0x00dd, /* DOM_DOCUMENT_FRAGMENT_NODE 11 */ 0x0000 /* DOM_NOTATION_NODE 12 */ }; const char *node_names[] = { "No such node type", "DOM_ELEMENT_NODE", "DOM_ATTRIBUTE_NODE", "DOM_TEXT_NODE", "DOM_CDATA_SECTION_NODE", "DOM_ENTITY_REFERENCE_NODE", "DOM_ENTITY_NODE", "DOM_PROCESSING_INSTRUCTION_NODE", "DOM_COMMENT_NODE", "DOM_DOCUMENT_NODE", "DOM_DOCUMENT_TYPE_NODE", "DOM_DOCUMENT_FRAGMENT_NODE", "DOM_NOTATION_NODE" }; /* DOM_Implementation and DOM_ImplementationLS */ int DOM_Implementation_hasFeature(DOM_String *feature, DOM_String *version) { feature = NULL; version = NULL; return 0; } DOM_DocumentType * DOM_Implementation_createDocumentType(DOM_String *qualifiedName, DOM_String *publicId, DOM_String *systemId) { DOM_DocumentType *doctype; DOM_NamedNodeMap *entities; DOM_NamedNodeMap *notations; if ((doctype = Document_createNode(NULL, DOM_DOCUMENT_TYPE_NODE)) == NULL) { AMSG(""); return NULL; } if ((doctype->nodeName = doctype->u.DocumentType.name = strdup(qualifiedName)) == NULL || (publicId && ((doctype->u.DocumentType.publicId = strdup(publicId))) == NULL) || (systemId && ((doctype->u.DocumentType.systemId = strdup(systemId))) == NULL)) { DOM_Exception = errno; PMNO(DOM_Exception); DOM_Document_destroyNode(NULL, doctype); return NULL; } if ((entities = Document_createNamedNodeMap(NULL)) == NULL || (notations = Document_createNamedNodeMap(NULL)) == NULL) { AMNO(DOM_CREATE_FAILED); DOM_Document_destroyNode(NULL, doctype); return NULL; } entities->filter = DOM_ENTITY_NODE; notations->filter = DOM_NOTATION_NODE; entities->list = notations->list = doctype->childNodes; doctype->u.DocumentType.entities = entities; doctype->u.DocumentType.notations = notations; return doctype; } DOM_Document * DOM_Implementation_createDocument(DOM_String *namespaceURI, DOM_String *qualifiedName, DOM_DocumentType *doctype) { DOM_Document *doc; DOM_Element *el; namespaceURI = NULL; msgno_add_codes(dom_codes); if ((doc = Document_createNode(NULL, DOM_DOCUMENT_NODE)) == NULL) { AMSG(""); return NULL; } doc->nodeName = "#document"; if (doctype) { DOM_Node_appendChild(doc, doctype); } if (qualifiedName && *qualifiedName) { if ((el = DOM_Document_createElement(doc, qualifiedName)) == NULL) { AMSG(""); DOM_Document_destroyNode(doc, doc); return NULL; } DOM_Node_appendChild(doc, el); } return doc; } DOM_String * DOM_Element_getAttribute(const DOM_Element *element, const DOM_String *name) { DOM_Node *node; DOM_String *r = NULL; if (element && name && element->attributes) { if ((node = DOM_NamedNodeMap_getNamedItem(element->attributes, name))) { if ((r = strdup(node->nodeValue)) == NULL) { AMSG(""); return NULL; } } else if ((r = strdup("")) == NULL) { PMNO(errno); return NULL; } } return r; } void DOM_Element_setAttribute(DOM_Element *element, const DOM_String *name, const DOM_String *value) { DOM_Attr *attr; DOM_String *prevValue; unsigned short attrChange; DOM_MutationEvent evt; if (element == NULL || name == NULL || value == NULL || element->attributes == NULL) { return; } attr = DOM_NamedNodeMap_getNamedItem(element->attributes, name); if (attr) { prevValue = attr->nodeValue; attrChange = DOM_MUTATION_EVENT_MODIFICATION; if ((attr->nodeValue = attr->u.Attr.value = strdup(value)) == NULL) { DOM_Exception = errno; PMNO(DOM_Exception); DOM_Document_destroyNode(attr->ownerDocument, attr); return; } } else { prevValue = NULL; attrChange = DOM_MUTATION_EVENT_ADDITION; if ((attr = DOM_Document_createAttribute(element->ownerDocument, name)) == NULL) { AMNO(DOM_CREATE_FAILED); return; } free(attr->nodeValue); if ((attr->nodeValue = attr->u.Attr.value = strdup(value)) == NULL) { DOM_Exception = errno; DOM_Document_destroyNode(attr->ownerDocument, attr); return; } DOM_NamedNodeMap_setNamedItem(element->attributes, attr); } DOM_MutationEvent_initMutationEvent(&evt, "DOMAttrModified", 1, 0, attr, prevValue, attr->nodeValue, attr->nodeName, attrChange); /*MSG("attr=%p,prevValue=[%s],newValue=[%s],attrName=%s", attr, prevValue, attr->nodeValue, attr->nodeName); */ DOM_EventTarget_dispatchEvent(element, &evt); updateCommonParent(element->parentNode); free(prevValue); } void DOM_Element_removeAttribute(DOM_Element *element, const DOM_String *name) { DOM_Attr *attr; DOM_MutationEvent evt; if (element == NULL || name == NULL) { return; } attr = DOM_NamedNodeMap_removeNamedItem(element->attributes, name); /* removeAttribute doesn't throw exceptions on NOT_FOUND_ERR */ if (DOM_Exception == DOM_NOT_FOUND_ERR) DOM_Exception = 0; if (attr) { DOM_MutationEvent_initMutationEvent(&evt, "DOMAttrModified", 1, 0, attr, attr->nodeValue, NULL, attr->nodeName, DOM_MUTATION_EVENT_REMOVAL); DOM_EventTarget_dispatchEvent(element, &evt); updateCommonParent(element->parentNode); DOM_Document_destroyNode(attr->ownerDocument, attr); } } DOM_Attr * DOM_Element_getAttributeNode(const DOM_Element *element, const DOM_String *name) { if (element && name) { return DOM_NamedNodeMap_getNamedItem(element->attributes, name); } return NULL; } DOM_Attr * DOM_Element_setAttributeNode(DOM_Element *element, DOM_Attr *newAttr) { DOM_Node *attr; DOM_MutationEvent evt; if (element == NULL || newAttr == NULL) { return NULL; } if (element->ownerDocument != newAttr->ownerDocument) { DOM_Exception = DOM_WRONG_DOCUMENT_ERR; PMNO(DOM_Exception); return NULL; } attr = DOM_NamedNodeMap_setNamedItem(element->attributes, newAttr); if (attr) { DOM_MutationEvent_initMutationEvent(&evt, "DOMAttrModified", 1, 0, attr, attr->nodeValue, NULL, attr->nodeName, DOM_MUTATION_EVENT_REMOVAL); DOM_EventTarget_dispatchEvent(element, &evt); } DOM_MutationEvent_initMutationEvent(&evt, "DOMAttrModified", 1, 0, newAttr, NULL, newAttr->nodeValue, newAttr->nodeName, DOM_MUTATION_EVENT_ADDITION); DOM_EventTarget_dispatchEvent(element, &evt); updateCommonParent(element->parentNode); return attr; } DOM_Attr * DOM_Element_removeAttributeNode(DOM_Element *element, DOM_Attr *oldAttr) { DOM_MutationEvent evt; if (element == NULL || oldAttr == NULL || NodeList_remove(element->attributes, oldAttr) == NULL) { DOM_Exception = DOM_NOT_FOUND_ERR; PMNO(DOM_Exception); return NULL; } DOM_MutationEvent_initMutationEvent(&evt, "DOMAttrModified", 1, 0, oldAttr, oldAttr->nodeValue, NULL, oldAttr->nodeName, DOM_MUTATION_EVENT_REMOVAL); DOM_EventTarget_dispatchEvent(element, &evt); updateCommonParent(element->parentNode); return oldAttr; } static void getElementsPreorder(DOM_NodeList *list, DOM_Node *node, const DOM_String *tagname) { DOM_Node *n; if (list && node && node->nodeType == DOM_ELEMENT_NODE && tagname) { if ((tagname[0] == '*' && tagname[1] == '\0') || strcoll(tagname, node->nodeName) == 0) { NodeList_append(list, node); } for (n = node->firstChild; n != NULL; n = n->nextSibling) { getElementsPreorder(list, n, tagname); } } } DOM_NodeList * DOM_Element_getElementsByTagName(DOM_Element *element, const DOM_String *name) { DOM_NodeList *list; DOM_Node *n; if (element && element->nodeType == DOM_ELEMENT_NODE && name && (list = Document_createNodeList(element->ownerDocument))) { for (n = element->firstChild; n != NULL; n = n->nextSibling) { getElementsPreorder(list, n, name); } return list; } return NULL; } void DOM_Element_normalize(DOM_Element *element) { DOM_Node *node; DOM_Text *last = NULL; if (element) { for (node = element->firstChild; node != NULL; node = node->nextSibling) { if (node->nodeType == DOM_TEXT_NODE) { if (last) { DOM_CharacterData_insertData(node, 0, last->nodeValue); DOM_Node_removeChild(element, last); DOM_Document_destroyNode(last->ownerDocument, last); } last = node; } else { last = NULL; DOM_Element_normalize(node); } if (DOM_Exception) { AMSG(""); return; } } } } DOM_String * DOM_CharacterData_substringData(DOM_CharacterData *data, int offset, int count) { DOM_String *sub; int dlen; if (data == NULL) { DOM_Exception = NULL_POINTER_ERR; PMNO(DOM_Exception); return NULL; } if (offset < 0 || offset > (dlen = data->u.CharacterData.length) || count < 0) { DOM_Exception = DOM_INDEX_SIZE_ERR; PMNO(DOM_Exception); return NULL; } if (count > (dlen - offset)) { count = dlen - offset; } if ((sub = mbsoff(data->nodeValue, offset)) == NULL || (sub = mbsndup(sub, -1, count)) == NULL) { DOM_Exception = errno; PMNO(DOM_Exception); return NULL; } return sub; } void DOM_CharacterData_appendData(DOM_CharacterData *data, const DOM_String *arg) { DOM_String *str, *prevValue; size_t dsize, asize; DOM_MutationEvent evt; if (data == NULL) { DOM_Exception = NULL_POINTER_ERR; PMNO(DOM_Exception); return; } if (arg) { dsize = mbssize(data->nodeValue); asize = mbssize(arg); if ((str = malloc(dsize + asize + 1)) == NULL) { DOM_Exception = errno; PMNO(DOM_Exception); return; } memcpy(str, data->nodeValue, dsize); memcpy(str + dsize, arg, asize); str[dsize + asize] = '\0'; prevValue = data->nodeValue; data->nodeValue = data->u.CharacterData.data = str; data->u.CharacterData.length += mbslen(arg); DOM_MutationEvent_initMutationEvent(&evt, "DOMCharacterDataModified", 1, 0, NULL, prevValue, data->nodeValue, NULL, 0); DOM_EventTarget_dispatchEvent(data, &evt); updateCommonParent(data->parentNode); free(prevValue); } } void DOM_CharacterData_insertData(DOM_CharacterData *data, int offset, const DOM_String *arg) { DOM_String *str, *prevValue; size_t dsize, asize, o; DOM_MutationEvent evt; if (data == NULL) { DOM_Exception = NULL_POINTER_ERR; PMNO(DOM_Exception); return; } if (offset < 0 || offset > data->u.CharacterData.length) { DOM_Exception = DOM_INDEX_SIZE_ERR; PMNO(DOM_Exception); return; } if (arg) { dsize = mbssize(data->nodeValue); asize = mbssize(arg); if ((str = malloc(dsize + asize + 1)) == NULL) { DOM_Exception = errno; PMNO(DOM_Exception); return; } o = mbsoff(data->nodeValue, offset) - data->nodeValue; memcpy(str, data->nodeValue, o); memcpy(str + o, arg, asize); memcpy(str + o + asize, data->nodeValue + o, dsize - o); str[dsize + asize] = '\0'; prevValue = data->nodeValue; data->nodeValue = data->u.CharacterData.data = str; data->u.CharacterData.length += mbslen(arg); DOM_MutationEvent_initMutationEvent(&evt, "DOMCharacterDataModified", 1, 0, NULL, prevValue, data->nodeValue, NULL, 0); DOM_EventTarget_dispatchEvent(data, &evt); updateCommonParent(data->parentNode); free(prevValue); } } void DOM_CharacterData_deleteData(DOM_CharacterData *data, int offset, int count) { DOM_String *p1, *p2, *str, *prevValue; int p1size, p2size, dlen; DOM_MutationEvent evt; if (data == NULL) { DOM_Exception = NULL_POINTER_ERR; PMNO(DOM_Exception); return; } if (offset < 0 || offset > (dlen = data->u.CharacterData.length)) { DOM_Exception = DOM_INDEX_SIZE_ERR; PMNO(DOM_Exception); return; } if (count < 0 || (offset + count) > dlen) { count = dlen - offset; } p1 = mbsoff(data->nodeValue, offset); p1size = p1 - data->nodeValue; p2 = mbsoff(p1, count); p2size = strlen(p2); if ((str = malloc(p1size + p2size + 1)) == NULL) { DOM_Exception = errno; PMNO(DOM_Exception); return; } memcpy(str, data->nodeValue, p1size); memcpy(str + p1size, p2, p2size); str[p1size + p2size] = '\0'; prevValue = data->nodeValue; data->nodeValue = data->u.CharacterData.data = str; data->u.CharacterData.length = dlen - count; DOM_MutationEvent_initMutationEvent(&evt, "DOMCharacterDataModified", 1, 0, NULL, prevValue, data->nodeValue, NULL, 0); DOM_EventTarget_dispatchEvent(data, &evt); updateCommonParent(data->parentNode); free(prevValue); } void DOM_CharacterData_replaceData(DOM_CharacterData *data, int offset, int count, const DOM_String *arg) { DOM_CharacterData_deleteData(data, offset, count); DOM_CharacterData_insertData(data, offset, arg); } int DOM_CharacterData_getLength(DOM_CharacterData *data) { return data ? data->u.CharacterData.length : 0; } DOM_Text * DOM_Text_splitText(DOM_Text *text, int offset) { DOM_Text *node; if (text == NULL || text->parentNode == NULL) { DOM_Exception = NULL_POINTER_ERR; PMNO(DOM_Exception); return NULL; } if (offset < 0 || offset > text->u.CharacterData.length) { DOM_Exception = DOM_INDEX_SIZE_ERR; PMNO(DOM_Exception); return NULL; } node = DOM_Document_createTextNode(text->ownerDocument, mbsoff(text->nodeValue, offset)); if (node == NULL) { AMNO(DOM_CREATE_FAILED); return NULL; } DOM_CharacterData_deleteData(text, offset, -1); DOM_Node_insertBefore(text->parentNode, node, text->nextSibling); return node; } DOM_Element * DOM_Document_createElement(DOM_Document *doc, const DOM_String *tagName) { DOM_Element *element; element = Document_createNode(doc, DOM_ELEMENT_NODE); if (element) { if ((element->nodeName = element->u.Element.tagName = strdup(tagName)) == NULL) { DOM_Exception = errno; PMNO(DOM_Exception); DOM_Document_destroyNode(doc, element); return NULL; } if ((element->attributes = Document_createNamedNodeMap(doc)) == NULL) { AMNO(DOM_CREATE_FAILED); DOM_Document_destroyNode(doc, element); return NULL; } element->attributes->_ownerElement = element; /* for Attr.ownerElement */ } return element; } DOM_DocumentFragment * DOM_Document_createDocumentFragment(DOM_Document *doc) { DOM_DocumentFragment *frag; frag = Document_createNode(doc, DOM_DOCUMENT_FRAGMENT_NODE); if (frag) { frag->nodeName = "#document-fragment"; } return frag; } DOM_Text * DOM_Document_createTextNode(DOM_Document *doc, const DOM_String *data) { DOM_Text *text; text = Document_createNode(doc, DOM_TEXT_NODE); if (text) { text->nodeName = "#text"; text->nodeValue = text->u.CharacterData.data = strdup(data); if (text->nodeValue == NULL) { DOM_Exception = errno; PMNO(DOM_Exception); DOM_Document_destroyNode(doc, text); return NULL; } text->u.CharacterData.length = mbslen(data); } return text; } DOM_Comment * DOM_Document_createComment(DOM_Document *doc, const DOM_String *data) { DOM_Comment *comment; comment = Document_createNode(doc, DOM_COMMENT_NODE); if (comment) { comment->nodeName = "#comment"; comment->nodeValue = comment->u.CharacterData.data = strdup(data); if (comment->nodeValue == NULL) { DOM_Exception = errno; PMNO(DOM_Exception); DOM_Document_destroyNode(doc, comment); return NULL; } comment->u.CharacterData.length = mbslen(data); } return comment; } DOM_CDATASection * DOM_Document_createCDATASection(DOM_Document *doc, const DOM_String *data) { DOM_CDATASection *cdata; cdata = Document_createNode(doc, DOM_CDATA_SECTION_NODE); if (cdata) { cdata->nodeName = "#cdata-section"; cdata->nodeValue = cdata->u.CharacterData.data = strdup(data); if (cdata->u.CharacterData.data == NULL) { DOM_Exception = errno; PMNO(DOM_Exception); DOM_Document_destroyNode(doc, cdata); return NULL; } cdata->u.CharacterData.length = mbslen(data); } return cdata; } DOM_ProcessingInstruction * DOM_Document_createProcessingInstruction(DOM_Document *doc, const DOM_String *target, const DOM_String *data) { DOM_ProcessingInstruction *pi; pi = Document_createNode(doc, DOM_PROCESSING_INSTRUCTION_NODE); if (pi) { pi->nodeName = pi->u.ProcessingInstruction.target = strdup(target); pi->nodeValue = pi->u.ProcessingInstruction.data = strdup(data); if (pi->nodeName == NULL || pi->nodeValue == NULL) { DOM_Exception = errno; PMNO(DOM_Exception); DOM_Document_destroyNode(doc, pi); return NULL; } } return pi; } DOM_Attr * DOM_Document_createAttribute(DOM_Document *doc, const DOM_String *name) { DOM_Attr *attr; attr = Document_createNode(doc, DOM_ATTRIBUTE_NODE); if (attr) { attr->nodeName = attr->u.Attr.name = strdup(name); attr->nodeValue = attr->u.Attr.value = strdup(""); attr->u.Attr.specified = 1; if (attr->nodeName == NULL || attr->nodeValue == NULL) { DOM_Exception = errno; PMNO(DOM_Exception); DOM_Document_destroyNode(doc, attr); return NULL; } } return attr; } DOM_EntityReference * DOM_Document_createEntityReference(DOM_Document *doc, const DOM_String *name) { DOM_EntityReference *eref; eref = Document_createNode(doc, DOM_ENTITY_REFERENCE_NODE); if (eref && (eref->nodeName = strdup(name)) == NULL) { DOM_Document_destroyNode(doc, eref); return NULL; } return eref; } DOM_NodeList * DOM_Document_getElementsByTagName(DOM_Document *doc, const DOM_String *tagname) { DOM_NodeList *list; if (doc && doc->nodeType == DOM_DOCUMENT_NODE && tagname && (list = Document_createNodeList(doc))) { getElementsPreorder(list, doc->u.Document.documentElement, tagname); return list; } return NULL; } DOM_DocumentType * DOM_Document_getDoctype(DOM_Document *doc) { return doc ? doc->u.Document.doctype : NULL; } DOM_Element * DOM_Document_getDocumentElement(DOM_Document *doc) { return doc ? doc->u.Document.documentElement : NULL; } /* Temporary functions */ void printNode(DOM_Node *node, int indent) { DOM_Node *n; int i; if (node == NULL) { printf("node was null\n"); return; } for (i = 0; i < indent; i++) { printf(" "); } printf("%s: %s=%s\n", node_names[node->nodeType], node->nodeName, node->nodeValue); if (node->nodeType == DOM_ELEMENT_NODE && node->attributes->length) { printf(" "); n = DOM_NamedNodeMap_item(node->attributes, 0); printf("%s=%s", n->nodeName, n->nodeValue); for (i = 1; i < node->attributes->length; i++) { n = DOM_NamedNodeMap_item(node->attributes, i); printf(",%s=%s", n->nodeName, n->nodeValue); } printf("\n"); for (i = 0; i < indent; i++) { printf(" "); } } for (n = node->firstChild; n != NULL; n = n->nextSibling) { printNode(n, indent + 1); } } void DOM_Node_printNode2(DOM_Node *node) { printf("\n"); printNode(node, 0); } void DOM_Node_printNode(DOM_Node *node) { if (node == NULL) { printf("node was null\n"); return; } printf("\nnodeName=%s,nodeValue=%s,", node->nodeName, node->nodeValue); printf("\n\ttype=%u", node->nodeType); printf(",parentNode->nodeName=%s,firstChild->nodeName=%s", (node->parentNode == NULL ? "(null)" : node->parentNode->nodeName), (node->firstChild == NULL ? "(null)" : node->firstChild->nodeName)); printf(",lastChild->nodeName=%s,\n\tchildNodes->length=%u", (node->lastChild == NULL ? "(null)" : node->lastChild->nodeName), (node->childNodes == NULL ? 0 : node->childNodes->length)); printf(",previousSibling->nodeName=%s,nextSibling->nodeName=%s,attributes->length=%u\n", (node->previousSibling == NULL ? "(null)" : node->previousSibling->nodeName), (node->nextSibling == NULL ? "(null)": node->nextSibling->nodeName), (node->attributes == NULL ? 0 : node->attributes->length)); fflush(stdout); }