判断两个XML文件结构与内容是否相同
1. 引入
目前公司的这款软件导入导出数据库信息的方法是:组织数据的内容和结构 利用MS com的sax解析 储存数据为XML格式
优点是可以选择部分导出
缺点是速度慢文件导出的文件庞大,若客户出现导入导出错误要重现或者调试困难
由于软件持续更新所以不同版本的,XML有差异
现客户需求将高版本的软件导出成 低版本XML,保证从任何高版本导出的文件 与 低版本 原先的格式与内容一致
我们实现了功能之后为了测试功能,要写单元测试
2. 分析
要测试高版本导出的文件的 内容和格式能完全导出成低版本
首先要通过低版本 生成一个数据库,将内容全部导出成A
再将A导入到高版本确保A的信息完全转化成高版本的结构
将高版本导出成低版本文件B
比较AB的内容和结构
B除某些少部分信息与结构,其他必须完全与低版本一致
3. 实现
将A与B通过 dom 解析,取出各自的节点进行一一比较直到所有在A中的节点的位置和信息都与B中一致后,才可判定2者一致,由于导出时数据库的结构不同,XML的文件中节点的顺序可能不同在我们的软件中这种情况是可以认为是一致的。顺序不用考虑,因此要求UT忽略顺序
比较主要分为3步
1)在A和B中取出某个节点
2)比较两个节点的信息是否一致
3)比较这两个节点的子节点信息是否一致
XML格式为树状结构,除根节点和叶子节点外每个节点均有父节点和子节点,另外本软件导出的文件只会有一个根节点
因此可以进行递归查找进一步把流程改为:
1)从A和B中取出根节点,对根节点进行compare(若一致则可以确定两文件内容、结构一致)
2)compare流程
(1)比较当前节点信息若不一致则返回失败
(2)若一致,则取出AB所有的子节点,遍历A的子节点 去 B的子节点集中查找 (递归调用Compare进行判断)若所有A的子节点都能在B中找到,则判断AB节点相同(前提AB中子节点数量一致)
#include "ModelParser.h"
#include "IExporterDocumentTranslator.h"
.....将低版本文件导入到高版本
.....导出成低版本
ModelParser version4Parser(fileOfversion4);//ModelParser类用来解析XML文件
ModelParser exportedFileParser(exportedFile);
ASSERT(version4Parser.IsValid());
ASSERT(exportedFileParser.IsValid());
//Compare it with the 4.0 file, their datas should be consistent
ASSERT(version4Parser.Compare(exportedFileParser));
ModelParser类及实现
ModelParser.h
#pragma once
#include"XMLDOMNodeParser.h"
class ModelParser
{
public:
ModelParser(const String& modelFilePath);
/////////////////////////////////////////////////////////////////////////
// Compare the content with another file, if the files are the same return true (exportas test use only)
bool Compare(const ModelParser& otherModel) const;
bool IsValid() const;
private:
shared_ptr<XMLDOMNodeParser> rootInterchangeFile;
IXMLDOMNodePtr GetInterchangeFile(IXMLDOMNodeListPtr listPtr);
};
ModelParser.cpp
#include "StdAfx.h"
#include "ModelParser.h"
ModelParser::ModelParser(const String& modelFilePath) :
rootInterchangeFile(NULL)
{
IXMLDOMDocumentPtr docPtr;
docPtr.CreateInstance(__uuidof(DOMDocument30));
// Load a document:
_variant_t tempPath = static_cast<LPCTSTR>(modelFilePath);
VARIANT path = tempPath;
VARIANT_BOOL result = VARIANT_FALSE;
docPtr->load(path, &result);
IXMLDOMNodeListPtr rootNodes = NULL;
const BSTR allTags = L" ";
docPtr->getElementsByTagName(allTags, &rootNodes);
IXMLDOMNodePtr root = GetInterchangeFile(rootNodes);
rootInterchangeFile = shared_ptr<XMLDOMNodeParser>(new XMLDOMNodeParser(root));
}
bool ModelParser::IsValid() const
{
return rootInterchangeFile->IsValid(*rootInterchangeFile);
}
IXMLDOMNodePtr ModelParser::GetInterchangeFile(IXMLDOMNodeListPtr listPtr)
{
IXMLDOMNodePtr nodePtr = NULL;
long num = 0;
do
{
listPtr->get_item(num++, &nodePtr);
if (nodePtr)
{
BSTR nodeType, nodeName;
nodePtr->get_nodeTypeString(&nodeType);
nodePtr->get_nodeName(&nodeName);
if (0 == (lstrcmp((LPCTSTR)nodeType, (LPCTSTR)L"element")) && 0 == (lstrcmp((LPCTSTR)nodeName, (LPCTSTR)L"InterchangeFile")))
{
break;
}
}
} while (nodePtr);
return nodePtr;
}
bool ModelParser::Compare(const ModelParser& otherModel) const
{
bool isSame = false;
try
{
isSame = rootInterchangeFile->Compare(*otherModel.rootInterchangeFile);
}
catch (...)
{
isSame = false;
}
return isSame;
}
#pragma once
#include "Strings.h"
#include <msxml2.h>
using namespace std;
struct BSTRSorter
{
bool operator()(BSTR left, BSTR right) const
{
return lstrcmp((LPCTSTR)left, (LPCTSTR)right) < 0;
}
};
class XMLDOMNodeParser
{
public:
XMLDOMNodeParser(IXMLDOMNodePtr nodePtr);
bool IsValid(const XMLDOMNodeParser& node) const;
/////////////////////////////////////////////////////////////////////////
// Compare the structure, content with another node, if the nodes are the same return true (exportas test use only)
bool Compare(XMLDOMNodeParser& otherNode);
private:
bool CompareChildren(XMLDOMNodeParser& otherNode);
bool CompareAttributes(const XMLDOMNodeParser& otherNode);
BSTR GetAttributeValueByName(const IXMLDOMNamedNodeMapPtr& attributes, const BSTR attributeName) const;
bool PutNodesAttributesIntoMap(map<BSTR, BSTR, BSTRSorter>& attributesMap, const IXMLDOMNamedNodeMapPtr& nodesPtr);
bool ShouldIgnore() const;
bool IsBuiltInGuid(const BSTR value) const;
bool IsBuiltInPresentation() const;
/////////////////////////////////////////////////////////////////////////
// Ignore some attributes' diffrence
static void InitialAttributesExcludedList();
/////////////////////////////////////////////////////////////////////////
// If a presentation's type is build-in, ignore the presentation's diffrence
static void InitialPresentationList();
IXMLDOMNodePtr node;
BSTR nodeName;
BSTR nodeType;
map<BSTR, BSTR, BSTRSorter> attributesMap;
long attributeLenth, childListLenth;
IXMLDOMNamedNodeMapPtr attributesPtr;
shared_ptr<XMLDOMNodeParser> childNodeTemp;
IXMLDOMNodeListPtr childList;
static set<BSTR, BSTRSorter> attributesExcludedList;
static set<BSTR, BSTRSorter> presentationBuildinList;
};
#include "StdAfx.h"
#include "XMLDOMNodeParser.h"
set<BSTR, BSTRSorter> XMLDOMNodeParser::attributesExcludedList;
set<BSTR, BSTRSorter> XMLDOMNodeParser::presentationBuildinList;
XMLDOMNodeParser::XMLDOMNodeParser(IXMLDOMNodePtr nodePtr) :
attributeLenth(0),
childListLenth(0),
attributesPtr(NULL),
node(NULL),
childList(NULL)
{
InitialAttributesExcludedList();
InitialPresentationList();
node = nodePtr;
if (NULL != node)
{
node->get_nodeName(&nodeName);
node->get_attributes(&attributesPtr);
if (NULL != attributesPtr)
{
attributesPtr->get_length(&attributeLenth);
if (!PutNodesAttributesIntoMap(attributesMap, attributesPtr))
node = NULL;
}
node->get_childNodes(&childList);
if(NULL != childList)
childList->get_length(&childListLenth);
node->get_nodeTypeString(&nodeType);
}
}
bool XMLDOMNodeParser::IsValid(const XMLDOMNodeParser& node) const
{
return node.node != NULL;
}
bool XMLDOMNodeParser::CompareAttributes(const XMLDOMNodeParser& otherNode)
{
if (NULL == attributesPtr || NULL == otherNode.attributesPtr || attributeLenth != otherNode.attributeLenth)
return false;
for (map<BSTR, BSTR, BSTRSorter>::iterator iter = attributesMap.begin(); iter != attributesMap.end(); ++iter)
{
BSTR attrName = iter->first;
BSTR attrText = iter->second;
if (0 == wcslen(attrName))
{
return false;
}
if (attributesExcludedList.find(attrName) != attributesExcludedList.end())
{
continue;
}
map<BSTR, BSTR, BSTRSorter>::iterator otherIter = const_cast<XMLDOMNodeParser&>(otherNode).attributesMap.find(iter->first);
if (otherIter != otherNode.attributesMap.end())
{
if (0 != wcscmp(otherIter->second, attrText))
return false;
}
else
return false;
}
return true;
}
bool XMLDOMNodeParser::ShouldIgnore() const
{
return NULL == node || 0 != lstrcmp((LPCTSTR)nodeType, (LPCTSTR)L"element");
}
bool XMLDOMNodeParser::IsBuiltInPresentation() const
{
if (0 == wcscmp(nodeName, L"PRESENTATION"))
{
BSTR owner = GetAttributeValueByName(this->attributesPtr, L"Owner");
if (IsBuiltInGuid(owner))
return true;
}
return false;
}
BSTR XMLDOMNodeParser::GetAttributeValueByName(const IXMLDOMNamedNodeMapPtr& attributes, const BSTR attributeName) const
{
IXMLDOMNodePtr pIAttrNode = NULL;
BSTR tmpName, attributeValue;
long length = 0;
attributes->get_length(&length);
for (long num = 0; num < length; ++num)
{
attributes->get_item(num, &pIAttrNode);
pIAttrNode->get_nodeName(&tmpName);
if (0 == wcscmp(tmpName, attributeName))
{
pIAttrNode->get_text(&attributeValue);
}
}
return attributeValue;
}
bool XMLDOMNodeParser::PutNodesAttributesIntoMap(map<BSTR, BSTR, BSTRSorter>&attributesMap, const IXMLDOMNamedNodeMapPtr&nodesPtr)
{
long length = 0;
nodesPtr->get_length(&length);
for (long num = 0; num < length; ++num)
{
BSTR attrName, attrText;
IXMLDOMNodePtr pIAttrNode = NULL;
nodesPtr->get_item(num, &pIAttrNode);
pIAttrNode->get_nodeName(&attrName);
pIAttrNode->get_text(&attrText);
if (0 != wcslen(attrName))
{
if (attributesExcludedList.find(attrName) == attributesExcludedList.end())
attributesMap[attrName] = attrText;
}
else
{
return false;
}
}
return true;
}
bool XMLDOMNodeParser::Compare(XMLDOMNodeParser& otherNode)
{
if (0 != wcscmp(nodeName, otherNode.nodeName))
return false;
if (this->IsBuiltInPresentation() && otherNode.IsBuiltInPresentation())
{
return true;
}
if (!CompareAttributes(otherNode))
{
return false;
}
return CompareChildren(otherNode);
}
bool XMLDOMNodeParser::CompareChildren(XMLDOMNodeParser& otherNode)
{
if (NULL == childList && NULL == otherNode.childList)//No Child
return true;
if (childListLenth != otherNode.childListLenth)
return false;
set<long> checked;
IXMLDOMNodePtr childNodeTempPtr = NULL;
for (long childnumA = 0; childnumA < childListLenth; ++childnumA)
{
childList->get_item(childnumA, &childNodeTempPtr);
childNodeTemp = shared_ptr<XMLDOMNodeParser> (new XMLDOMNodeParser(childNodeTempPtr));
if (childNodeTemp->ShouldIgnore())
continue;
bool found = false;
for (long childnumB = 0; childnumB < childListLenth; ++childnumB)
{
if (checked.find(childnumB) == checked.end())
{
otherNode.childList->get_item(childnumB, &childNodeTempPtr);
otherNode.childNodeTemp = shared_ptr<XMLDOMNodeParser> (new XMLDOMNodeParser(childNodeTempPtr));
if (otherNode.childNodeTemp->ShouldIgnore())
continue;
if (childNodeTemp->Compare(*otherNode.childNodeTemp))
{
found = true;
checked.insert(childnumB);
break;
}
}
}
if (!found)
return false;
}
return true;
}
bool XMLDOMNodeParser::IsBuiltInGuid(const BSTR value) const
{
return presentationBuildinList.find(value) != presentationBuildinList.end();
}
/////////////////////////////////////////////////////////////////////////
// Ignore some attributes' diffrence
void XMLDOMNodeParser::InitialAttributesExcludedList()
{
if (attributesExcludedList.empty())
{
attributesExcludedList.insert(L"CreationTime");
attributesExcludedList.insert(L"ProductLevel");
attributesExcludedList.insert(L"Creator");
}
}
/////////////////////////////////////////////////////////////////////////
// If a presentation's type is build-in, ignore the presentation's diffrence
void XMLDOMNodeParser::InitialPresentationList()
{
if (presentationBuildinList.empty())
{
presentationBuildinList.insert(L"00006596-0000-0000-0000-000000000000");
presentationBuildinList.insert(L"00006590-0000-0000-0000-000000000000");
presentationBuildinList.insert(L"00006591-0000-0000-0000-000000000000");
presentationBuildinList.insert(L"00006593-0000-0000-0000-000000000000");
presentationBuildinList.insert(L"00006594-0000-0000-0000-000000000000");
presentationBuildinList.insert(L"00006595-0000-0000-0000-000000000000");
presentationBuildinList.insert(L"000059D9-0000-0000-0000-000000000000");
}
}