| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| #include "DFPlatform.h" |
| #include "DFDOM.h" |
| #include "DFXMLNames.h" |
| #include "DFHashTable.h" |
| #include "DFNameMap.h" |
| #include "DFAllocator.h" |
| #include "DFString.h" |
| #include "DFCommon.h" |
| #include <assert.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| // // |
| // DFDocument // |
| // // |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| static void DFAssignSeqNo(DFDocument *doc, DFNode *node) |
| { |
| node->seqNo = doc->nextSeqNo++; |
| node->doc = doc; |
| if (node != doc->docNode) { |
| unsigned int hash = node->seqNo % SEQNO_HASH_SIZE; |
| assert(node->seqNoHashNext == NULL); |
| node->seqNoHashNext = doc->seqNoHashBins[hash]; |
| doc->seqNoHashBins[hash] = node; |
| } |
| } |
| |
| static DFNode *DocumentCreateNode(DFDocument *doc, Tag tag) |
| { |
| if (doc->nodesCount == doc->nodesAlloc) { |
| doc->nodesAlloc *= 2; |
| doc->nodes = (DFNode **)xrealloc(doc->nodes,doc->nodesAlloc*sizeof(DFNode *)); |
| } |
| |
| // Node *node = NodeNew(tag); |
| DFNode *node = DFAllocatorAlloc(doc->allocator,sizeof(DFNode)); |
| bzero(node,sizeof(DFNode)); |
| node->tag = tag; |
| doc->nodes[doc->nodesCount++] = node; |
| DFAssignSeqNo(doc,node); |
| return node; |
| } |
| |
| DFDocument *DFDocumentNew(void) |
| { |
| DFDocument *doc = (DFDocument *)xcalloc(1,sizeof(DFDocument)); |
| doc->retainCount = 1; |
| doc->allocator = DFAllocatorNew(); |
| doc->map = DFNameMapNew(); |
| doc->nodesByIdAttr = DFHashTableNew2(NULL,NULL,997); |
| |
| doc->nodesCount = 0; |
| doc->nodesAlloc = 1; |
| doc->nodes = (DFNode **)xmalloc(doc->nodesAlloc*sizeof(DFNode *)); |
| doc->docNode = DocumentCreateNode(doc,DOM_DOCUMENT); |
| |
| return doc; |
| } |
| |
| DFDocument *DFDocumentNewWithRoot(Tag rootTag) |
| { |
| DFDocument *doc = DFDocumentNew(); |
| doc->root = DFCreateElement(doc,rootTag); |
| DFAppendChild(doc->docNode,doc->root); |
| return doc; |
| } |
| |
| static void DFClearSeqNoHash(DFDocument *doc) |
| { |
| for (unsigned int hash = 0; hash < SEQNO_HASH_SIZE; hash++) { |
| DFNode *node = doc->seqNoHashBins[hash]; |
| while (node != NULL) { |
| DFNode *nextNode = node->seqNoHashNext; |
| node->seqNoHashNext = NULL; |
| node = nextNode; |
| } |
| doc->seqNoHashBins[hash] = NULL; |
| } |
| } |
| |
| DFDocument *DFDocumentRetain(DFDocument *doc) |
| { |
| if (doc == NULL) |
| return NULL; |
| doc->retainCount++; |
| return doc; |
| } |
| |
| void DFDocumentRelease(DFDocument *doc) |
| { |
| if (doc == NULL) |
| return; |
| assert(doc->retainCount > 0); |
| doc->retainCount--; |
| if (doc->retainCount == 0) { |
| DFHashTableRelease(doc->nodesByIdAttr); |
| DFClearSeqNoHash(doc); |
| for (size_t i = 0; i < doc->nodesCount; i++) |
| free(doc->nodes[i]->attrs); |
| free(doc->nodes); |
| DFNameMapFree(doc->map); |
| DFAllocatorFree(doc->allocator); |
| free(doc); |
| } |
| } |
| |
| DFNode *DFNodeForSeqNo(DFDocument *doc, unsigned int seqNo) |
| { |
| unsigned int hash = seqNo % SEQNO_HASH_SIZE; |
| for (DFNode *node = doc->seqNoHashBins[hash]; node != NULL; node = node->seqNoHashNext) { |
| if (node->seqNo == seqNo) |
| return node; |
| } |
| return NULL; |
| } |
| |
| DFNode *DFElementForIdAttr(DFDocument *doc, const char *idAttr) |
| { |
| return (DFNode *)DFHashTableLookup(doc->nodesByIdAttr,idAttr); |
| } |
| |
| void DFDocumentReassignSeqNosRecursive(DFDocument *doc, DFNode *node) |
| { |
| DFAssignSeqNo(doc,node); |
| for (DFNode *child = node->first; child != NULL; child = child->next) |
| DFDocumentReassignSeqNosRecursive(doc,child); |
| } |
| |
| void DFDocumentReassignSeqNos(DFDocument *doc) |
| { |
| DFClearSeqNoHash(doc); |
| doc->nextSeqNo = 0; |
| DFDocumentReassignSeqNosRecursive(doc,doc->docNode); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| // // |
| // DOM // |
| // // |
| //////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| char *DFCopyStringLen(DFDocument *doc, const char *str, size_t len) |
| { |
| char *copy = (char *)DFAllocatorAlloc(doc->allocator,len+1); |
| memcpy(copy,str,len); |
| copy[len] = '\0'; |
| return copy; |
| } |
| |
| char *DFCopyString(DFDocument *doc, const char *str) |
| { |
| return DFCopyStringLen(doc,str,strlen(str)); |
| } |
| |
| // Document methods |
| |
| DFNode *DFCreateElement(DFDocument *doc, Tag tag) |
| { |
| DFNode *node = DocumentCreateNode(doc,tag); |
| return node; |
| } |
| |
| DFNode *DFCreateTextNode(DFDocument *doc, const char *data) |
| { |
| DFNode *node = DocumentCreateNode(doc,DOM_TEXT); |
| node->value = DFCopyString(doc,data); |
| return node; |
| } |
| |
| DFNode *DFCreateComment(DFDocument *doc, const char *data) |
| { |
| DFNode *node = DocumentCreateNode(doc,DOM_COMMENT); |
| node->value = DFCopyString(doc,data); |
| return node; |
| } |
| |
| DFNode *DFCreateCDATASection(DFDocument *doc, char *data) |
| { |
| DFNode *node = DocumentCreateNode(doc,DOM_CDATA); |
| node->value = DFCopyString(doc,data); |
| return node; |
| } |
| |
| DFNode *DFCreateProcessingInstruction(DFDocument *doc, const char *target, const char *content) |
| { |
| DFNode *node = DocumentCreateNode(doc,DOM_PROCESSING_INSTRUCTION); |
| node->target = DFCopyString(doc,target); |
| node->value = DFCopyString(doc,content); |
| return node; |
| } |
| |
| // Node methods |
| |
| void DFInsertBefore(DFNode *parent, DFNode *newChild, DFNode *refChild) |
| { |
| assert(newChild->doc == parent->doc); |
| assert((refChild == NULL) || (refChild->doc == parent->doc)); |
| |
| if (newChild == refChild) |
| return; |
| |
| if (newChild->parent != NULL) |
| DFRemoveNode(newChild); |
| |
| assert(newChild->prev == NULL); |
| assert(newChild->next == NULL); |
| assert((refChild == NULL) || (refChild->parent == parent)); |
| assert(((parent->first == NULL) && (parent->last == NULL)) || |
| ((parent->first != NULL) && (parent->last != NULL))); |
| assert((parent->first != NULL) || (refChild == NULL)); |
| |
| newChild->prev = (refChild != NULL) ? refChild->prev : parent->last; |
| newChild->next = refChild; |
| |
| if (newChild->prev != NULL) |
| newChild->prev->next = newChild; |
| if (newChild->next != NULL) |
| newChild->next->prev = newChild; |
| |
| if (refChild == parent->first) |
| parent->first = newChild; |
| if (refChild == NULL) |
| parent->last = newChild; |
| |
| newChild->parent = parent; |
| } |
| |
| void DFAppendChild(DFNode *parent, DFNode *newChild) |
| { |
| DFInsertBefore(parent,newChild,NULL); |
| } |
| |
| DFNode *DFCreateChildElement(DFNode *parent, Tag tag) |
| { |
| DFNode *child = DFCreateElement(parent->doc,tag); |
| DFAppendChild(parent,child); |
| return child; |
| } |
| |
| DFNode *DFCreateChildTextNode(DFNode *parent, const char *data) |
| { |
| DFNode *text = DFCreateTextNode(parent->doc,data); |
| DFAppendChild(parent,text); |
| return text; |
| } |
| |
| void DFRemoveNode(DFNode *node) |
| { |
| DFNode *parent = node->parent; |
| if (parent == NULL) |
| return; |
| if (parent->first == node) |
| parent->first = node->next; |
| if (parent->last == node) |
| parent->last = node->prev; |
| if (node->prev != NULL) |
| node->prev->next = node->next; |
| if (node->next != NULL) |
| node->next->prev = node->prev; |
| node->next = NULL; |
| node->prev = NULL; |
| node->parent = NULL; |
| } |
| |
| void DFRemoveNodeButKeepChildren(DFNode *node) |
| { |
| DFNode *parent = node->parent; |
| assert(parent != NULL); |
| while (node->first != NULL) |
| DFInsertBefore(parent,node->first,node); |
| DFRemoveNode(node); |
| } |
| |
| void DFSetNodeValue(DFNode *node, const char *value) |
| { |
| node->value = DFCopyString(node->doc,value); |
| } |
| |
| // Element methods |
| |
| const char *DFGetAttribute(DFNode *node, Tag tag) |
| { |
| if ((node == NULL) || (node->tag < MIN_ELEMENT_TAG)) |
| return NULL; |
| |
| for (unsigned int i = 0; i < node->attrsCount; i++) { |
| if (node->attrs[i].tag == tag) |
| return node->attrs[i].value; |
| } |
| |
| return NULL; |
| } |
| |
| const char *DFGetChildAttribute(DFNode *parent, Tag childTag, Tag attrTag) |
| { |
| DFNode *child = DFChildWithTag(parent,childTag); |
| return DFGetAttribute(child,attrTag); |
| } |
| |
| void DFSetAttribute(DFNode *element, Tag tag, const char *value) |
| { |
| if (value == NULL) { |
| DFRemoveAttribute(element,tag); |
| return; |
| } |
| |
| if (tag == HTML_ID) { |
| const char *oldIdAttr = DFGetAttribute(element,HTML_ID); |
| if (oldIdAttr != NULL) |
| DFHashTableRemove(element->doc->nodesByIdAttr,oldIdAttr); |
| DFHashTableAdd(element->doc->nodesByIdAttr,value,element); |
| } |
| |
| // Is there an existing attribute with this tag? If so, replace it |
| for (unsigned int i = 0; i < element->attrsCount; i++) { |
| if (element->attrs[i].tag == tag) { |
| element->attrs[i].value = DFCopyString(element->doc,value); |
| return; |
| } |
| } |
| |
| // No existing attribute with this tag - add it |
| if (element->attrsCount == element->attrsAlloc) { |
| element->attrsAlloc = (element->attrsAlloc == 0) ? 8 : (2*element->attrsAlloc); |
| element->attrs = (DFAttribute *)xrealloc(element->attrs,element->attrsAlloc*sizeof(DFAttribute)); |
| } |
| |
| element->attrs[element->attrsCount].tag = tag; |
| element->attrs[element->attrsCount].value = DFCopyString(element->doc,value); |
| element->attrsCount++; |
| } |
| |
| void DFVFormatAttribute(DFNode *element, Tag tag, const char *format, va_list ap) |
| { |
| char *value = DFVFormatString(format,ap); |
| DFSetAttribute(element,tag,value); |
| free(value); |
| } |
| |
| void DFFormatAttribute(DFNode *element, Tag tag, const char *format, ...) |
| { |
| va_list ap; |
| va_start(ap,format); |
| DFVFormatAttribute(element,tag,format,ap); |
| va_end(ap); |
| } |
| |
| void DFRemoveAttribute(DFNode *element, Tag tag) |
| { |
| if (tag == HTML_ID) { |
| const char *oldIdAttr = DFGetAttribute(element,HTML_ID); |
| if (oldIdAttr != NULL) |
| DFHashTableRemove(element->doc->nodesByIdAttr,oldIdAttr); |
| } |
| |
| for (unsigned int i = 0; i < element->attrsCount; i++) { |
| if (element->attrs[i].tag == tag) { |
| if (i+1 < element->attrsCount) { |
| // Move the last attribute into this slot |
| element->attrs[i].tag = element->attrs[element->attrsCount-1].tag; |
| element->attrs[i].value = element->attrs[element->attrsCount-1].value; |
| } |
| element->attrsCount--; |
| return; |
| } |
| } |
| } |
| |
| void DFRemoveAllAttributes(DFNode *element) |
| { |
| element->attrsCount = 0; |
| } |
| |
| DFNode *DFPrevNode(DFNode *node) |
| { |
| if (node->prev != NULL) { |
| node = node->prev; |
| while (node->last != NULL) |
| node = node->last; |
| return node; |
| } |
| else { |
| return node->parent; |
| } |
| } |
| |
| DFNode *DFNextNodeAfter(DFNode *node) |
| { |
| while (node != NULL) { |
| if (node->next != NULL) { |
| node = node->next; |
| break; |
| } |
| |
| node = node->parent; |
| } |
| return node; |
| } |
| |
| DFNode *DFNextNode(DFNode *node) |
| { |
| if (node->first) { |
| node = node->first; |
| return node; |
| } |
| else { |
| return DFNextNodeAfter(node); |
| } |
| } |
| |
| Tag DFLookupTag(DFDocument *doc, const char *URI, const char *name) |
| { |
| return DFNameMapTagForName(doc->map,URI,name); |
| } |
| |
| const char *DFTagName(DFDocument *doc, Tag tag) |
| { |
| const TagDecl *tagDecl = DFNameMapNameForTag(doc->map,tag); |
| return (tagDecl != NULL) ? tagDecl->localName : NULL; |
| } |
| |
| const char *DFTagURI(DFDocument *doc, Tag tag) |
| { |
| const TagDecl *tagDecl = DFNameMapNameForTag(doc->map,tag); |
| const NamespaceDecl *nsDecl = (tagDecl == NULL) ? NULL : DFNameMapNamespaceForID(doc->map,tagDecl->namespaceID); |
| return (nsDecl != NULL) ? nsDecl->namespaceURI : NULL; |
| } |
| |
| const char *DFNodeName(DFNode *node) |
| { |
| if (node == NULL) |
| return NULL; |
| switch (node->tag) { |
| case DOM_DOCUMENT: |
| return "#document"; |
| case DOM_TEXT: |
| return "#text"; |
| case DOM_COMMENT: |
| return "#comment"; |
| case DOM_CDATA: |
| return "#cdata-section"; |
| case DOM_PROCESSING_INSTRUCTION: |
| return node->target; |
| default: |
| return DFTagName(node->doc,node->tag); |
| } |
| } |
| |
| const char *DFNodeURI(DFNode *node) |
| { |
| return (node->tag < MIN_ELEMENT_TAG) ? NULL : DFTagURI(node->doc,node->tag); |
| } |
| |
| void DFNodeTextToBuffer(DFNode *node, DFBuffer *buf) |
| { |
| switch (node->tag) { |
| case DOM_TEXT: |
| case DOM_CDATA: { |
| DFBufferAppendString(buf,node->value); |
| break; |
| } |
| } |
| for (DFNode *child = node->first; child != NULL; child = child->next) |
| DFNodeTextToBuffer(child,buf); |
| } |
| |
| char *DFNodeTextToString(DFNode *node) |
| { |
| DFBuffer *buf = DFBufferNew(); |
| DFNodeTextToBuffer(node,buf); |
| char *result = xstrdup(buf->data); |
| DFBufferRelease(buf); |
| return result; |
| } |
| |
| void DFStripIds(DFNode *node) |
| { |
| if (node->tag >= MIN_ELEMENT_TAG) |
| DFRemoveAttribute(node,HTML_ID); |
| for (DFNode *child = node->first; child != NULL; child = child->next) |
| DFStripIds(child); |
| } |
| |
| DFNode *DFChildWithTag(DFNode *parent, Tag tag) |
| { |
| if ((parent == NULL) || (tag < MIN_ELEMENT_TAG)) |
| return NULL; |
| for (DFNode *child = parent->first; child != NULL; child = child->next) { |
| if (child->tag == tag) |
| return child; |
| } |
| return NULL; |
| } |
| |
| void DFRemoveWhitespaceNodes(DFNode *node) |
| { |
| DFNode *next; |
| for (DFNode *child = node->first; child != NULL; child = next) { |
| next = child->next; |
| switch (child->tag) { |
| case DOM_TEXT: |
| case DOM_CDATA: { |
| if (DFStringIsWhitespace(child->value)) |
| DFRemoveNode(child); |
| break; |
| } |
| case DOM_COMMENT: |
| DFRemoveNode(child); |
| break; |
| default: |
| DFRemoveWhitespaceNodes(child); |
| break; |
| } |
| } |
| } |
| |
| int DFIsWhitespaceNode(DFNode *node) |
| { |
| return ((node->tag == DOM_TEXT) && DFStringIsWhitespace(node->value)); |
| } |
| |
| int identicalAttributesExcept(DFNode *first, DFNode *second, Tag except) |
| { |
| // FIXME: except paremeter is ignored |
| |
| // This is O(n^2), but it's not really a problem as we generally only have a very small |
| // number of attributes |
| |
| for (unsigned int i = 0; i < first->attrsCount; i++) { |
| Tag tag = first->attrs[i].tag; |
| if (tag == HTML_ID) |
| continue; |
| const char *firstValue = first->attrs[i].value; |
| const char *secondValue = DFGetAttribute(second,tag); |
| if (!DFStringEquals(firstValue,secondValue)) |
| return 0; |
| } |
| |
| for (unsigned int i = 0; i < second->attrsCount; i++) { |
| Tag tag = second->attrs[i].tag; |
| if (tag == HTML_ID) |
| continue; |
| const char *secondValue = second->attrs[i].value; |
| const char *firstValue = DFGetAttribute(first,tag); |
| if (!DFStringEquals(secondValue,firstValue)) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| void DFStripWhitespace(DFNode *node) |
| { |
| if (node->tag == DOM_TEXT) { |
| char *trimmed = DFStringTrimWhitespace(node->value); |
| if ((strlen(trimmed) == 0) && (node->parent != NULL)) |
| DFRemoveNode(node); |
| else |
| DFSetNodeValue(node,trimmed); |
| free(trimmed); |
| } |
| else { |
| if (node->tag >= MIN_ELEMENT_TAG) { |
| const char *space = DFGetAttribute(node,XML_SPACE); |
| if ((space != NULL) && !strcmp(space,"preserve")) |
| return; |
| } |
| DFNode *next; |
| for (DFNode *child = node->first; child != NULL; child = next) { |
| next = child->next; |
| DFStripWhitespace(child); |
| } |
| } |
| } |