blob: c67b0f0fa7fff6ac578ec2570ab13f46f5aefa7d [file] [log] [blame]
// 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 "DFChanges.h"
#include "DFHashTable.h"
#include "DFString.h"
#include "DFNameMap.h"
#include "DFCommon.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
static char *fullNameForTag(DFDocument *doc, Tag tag)
{
const TagDecl *tagDecl = DFNameMapNameForTag(doc->map,tag);
const NamespaceDecl *nsDecl = DFNameMapNamespaceForID(doc->map,tagDecl->namespaceID);
if ((nsDecl->prefix == NULL) || (tagDecl->namespaceID == NAMESPACE_HTML))
return xstrdup(tagDecl->localName);
else
return DFFormatString("%s:%s",nsDecl->prefix,tagDecl->localName);
}
static void nodeDetailToBuffer(DFNode *node, DFBuffer *output)
{
if (node == NULL) {
DFBufferAppendString(output,"null");
return;
}
switch (node->tag) {
case DOM_DOCUMENT:
DFBufferAppendString(output,"#document");
break;
case DOM_TEXT: {
char *quotedValue = DFQuote(node->value);
DFBufferAppendString(output,quotedValue);
free(quotedValue);
break;
}
case DOM_COMMENT:
DFBufferAppendString(output,"#comment");
break;
case DOM_CDATA:
DFBufferAppendString(output,"#cdata-section");
break;
case DOM_PROCESSING_INSTRUCTION:
DFBufferAppendString(output,node->target);
break;
default: {
char *elementName = fullNameForTag(node->doc,node->tag);
DFBufferFormat(output,"<%s",elementName);
for (unsigned int i = 0; i < node->attrsCount; i++) {
char *attrName = fullNameForTag(node->doc,node->attrs[i].tag);
const char *attrValue = node->attrs[i].value;
char *quotedValue = DFQuote(attrValue);
DFBufferFormat(output," %s=%s",attrName,quotedValue);
free(quotedValue);
free(attrName);
}
DFBufferFormat(output,">");
switch (node->tag) {
case WORD_T:
case WORD_DELTEXT: {
char *content = DFNodeTextToString(node);
char *quoted = DFQuote(content);
char *sub;
if (strlen(quoted) >= 2)
sub = DFSubstring(quoted,1,strlen(quoted)-2);
else
sub = xstrdup(quoted);
DFBufferFormat(output,"%s</%s>",quoted,elementName);
free(sub);
free(quoted);
free(content);
}
}
free(elementName);
break;
}
}
}
static void DFElementsByAttr(DFNode *node, Tag attr, DFHashTable *result)
{
if (node->tag >= MIN_ELEMENT_TAG) {
const char *value = DFGetAttribute(node,attr);
if ((value != NULL) && (DFHashTableLookup(result,value) == NULL))
DFHashTableAdd(result,value,node);
}
for (DFNode *child = node->first; child != NULL; child = child->next)
DFElementsByAttr(child,attr,result);
}
static void DFRecordChanges(DFNode *parent1, Tag idAttr, DFHashTable *map)
{
assert(parent1->tag >= MIN_ELEMENT_TAG);
// Check for changes in children
for (DFNode *child1 = parent1->first; child1 != NULL; child1 = child1->next) {
if (child1->tag >= MIN_ELEMENT_TAG)
DFRecordChanges(child1,idAttr,map);
}
// Check the parent itself - starting with finding out, first of all, whether there
// is actually a corresponding element in the new document
const char *idValue = DFGetAttribute(parent1,idAttr);
DFNode *parent2 = (idValue != NULL) ? DFHashTableLookup(map,idValue) : NULL;
if (parent2 == NULL) {
parent1->changed = 1;
return;
}
if (parent1->tag != parent2->tag)
parent1->changed = 1; // keep going - still want to check the children
if (!identicalAttributesExcept(parent1,parent2,0))
parent1->changed = 1;; // keep going - still want to check the children
DFNode *child1 = parent1->first;
DFNode *child2 = parent2->first;
while ((child1 != NULL) && (child2 != NULL)) {
// Non-element nodes can't be correlated based on their id attribute. So we judge whether
// they are the same by inspecting their content, assuming the parent has the same set
// of children.
if (child1->tag != child2->tag) {
parent1->changed = 1;
}
else {
switch (child1->tag) {
case DOM_TEXT:
case DOM_COMMENT:
case DOM_CDATA: {
assert(child1->value != NULL);
assert(child2->value != NULL);
if (strcmp(child1->value,child2->value))
child1->changed = 1;
break;
}
case DOM_PROCESSING_INSTRUCTION: {
if (strcmp(child1->target,child2->target) ||
strcmp(child1->value,child2->value)) {
child1->changed = 1;
}
break;
}
}
}
child1 = child1->next;
child2 = child2->next;
}
if ((child1 != NULL) && (child2 == NULL))
parent1->changed = 1;
if ((child1 == NULL) && (child2 != NULL))
parent1->changed = 1;
}
static void DFPropagateChanges(DFNode *node)
{
for (DFNode *child = node->first; child != NULL; child = child->next) {
DFPropagateChanges(child);
if (child->changed || child->childrenChanged)
node->childrenChanged = 1;
}
}
static void DFChangesToStringRecursive(DFNode *node, int indent, DFBuffer *output)
{
if (node->childrenChanged)
DFBufferAppendString(output,"*");
else
DFBufferAppendString(output," ");
if (node->changed)
DFBufferAppendString(output,"C");
else
DFBufferAppendString(output," ");
DFBufferAppendString(output," ");
for (int i = 0; i < indent; i++)
DFBufferAppendString(output," ");
nodeDetailToBuffer(node,output);
DFBufferAppendString(output,"\n");
for (DFNode *child = node->first; child != NULL; child = child->next)
DFChangesToStringRecursive(child,indent+1,output);
}
void DFComputeChanges(DFNode *root1, DFNode *root2, Tag idAttr)
{
DFHashTable *map = DFHashTableNew(NULL,NULL);
DFElementsByAttr(root2,idAttr,map);
DFRecordChanges(root1,HTML_ID,map);
DFPropagateChanges(root1);
DFHashTableRelease(map);
}
char *DFChangesToString(DFNode *root)
{
DFBuffer *output = DFBufferNew();
DFChangesToStringRecursive(root,0,output);
char *result = xstrdup(output->data);
DFBufferRelease(output);
return result;
}