/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#define USE_CPPUNIT 1
#include <test/xmldiff.hxx>
#include <libxml/xpath.h>
#include <libxml/parser.h>
#include <set>
#include <sstream>
#include <cassert>
#include <cmath>
#include <vector>
#if USE_CPPUNIT
#include <cppunit/TestAssert.h>
#endif
namespace {
struct tolerance
{
~tolerance()
{
xmlFree(elementName);
xmlFree(attribName);
}
tolerance()
: elementName(nullptr)
, attribName(nullptr)
, relative(false )
, value(0.0)
{
}
tolerance(const tolerance& tol)
{
elementName = xmlStrdup(tol.elementName);
attribName = xmlStrdup(tol.attribName);
relative = tol.relative;
value = tol.value;
}
xmlChar* elementName;
xmlChar* attribName;
bool relative;
double value;
bool operator <(const tolerance& rTol) const
{
int cmp = xmlStrcmp(elementName, rTol.elementName);
if (cmp == 0)
{
cmp = xmlStrcmp(attribName, rTol.attribName);
}
return cmp < 0;
}
};
class XMLDiff
{
public :
XMLDiff(const char * pFileName, const char * pContent, int size, const char * pToleranceFileName);
~XMLDiff();
bool compare();
private :
typedef std::set<tolerance> ToleranceContainer;
void loadToleranceFile(xmlDocPtr xmlTolerance);
bool compareAttributes(xmlNodePtr node1, xmlNodePtr node2);
bool compareElements(xmlNode* node1, xmlNode* node2);
/// Error message for cppunit that prints out when expected and found are not equal.
void cppunitAssertEqual(const xmlChar *expected, const xmlChar *found);
/// Error message for cppunit that prints out when expected and found are not equal - for doubles.
void cppunitAssertEqualDouble(const xmlNodePtr node, const xmlAttrPtr attr, double expected, double found, double delta);
ToleranceContainer toleranceContainer;
xmlDocPtr xmlFile1;
xmlDocPtr xmlFile2;
std::string fileName;
};
}
XMLDiff::XMLDiff( const char * pFileName, const char * pContent, int size, const char * pToleranceFile)
: xmlFile1(xmlParseFile(pFileName))
, xmlFile2(xmlParseMemory(pContent, size))
, fileName(pFileName)
{
if (pToleranceFile)
{
xmlDocPtr xmlToleranceFile = xmlParseFile(pToleranceFile);
loadToleranceFile(xmlToleranceFile);
xmlFreeDoc(xmlToleranceFile);
}
}
XMLDiff::~XMLDiff()
{
xmlFreeDoc(xmlFile1);
xmlFreeDoc(xmlFile2);
}
namespace {
void readAttributesForTolerance(xmlNodePtr node, tolerance& tol)
{
xmlChar* elementName = xmlGetProp(node, BAD_CAST("elementName" ));
tol.elementName = elementName;
xmlChar* attribName = xmlGetProp(node, BAD_CAST("attribName" ));
tol.attribName = attribName;
xmlChar* value = xmlGetProp(node, BAD_CAST("value" ));
double val = xmlXPathCastStringToNumber(value);
xmlFree(value);
tol.value = val;
xmlChar* relative = xmlGetProp(node, BAD_CAST("relative" ));
bool rel = false ;
if (xmlStrEqual(relative, BAD_CAST("true" )))
rel = true ;
xmlFree(relative);
tol.relative = rel;
}
}
void XMLDiff::loadToleranceFile(xmlDocPtr xmlToleranceFile)
{
xmlNodePtr root = xmlDocGetRootElement(xmlToleranceFile);
#if USE_CPPUNIT
CPPUNIT_ASSERT_MESSAGE("did not find correct tolerance file" , xmlStrEqual( root->name, BAD_CAST("tolerances" ) ));
#else
if (!xmlStrEqual( root->name, BAD_CAST("tolerances" ) ))
{
assert(false );
return ;
}
#endif
xmlNodePtr child = nullptr;
for (child = root->children; child != nullptr; child = child->next)
{
// assume a valid xml file
if (child->type != XML_ELEMENT_NODE)
continue ;
assert(xmlStrEqual(child->name, BAD_CAST("tolerance" )));
tolerance tol;
readAttributesForTolerance(child, tol);
toleranceContainer.insert(tol);
}
}
bool XMLDiff::compare()
{
xmlNode* root1 = xmlDocGetRootElement(xmlFile1);
xmlNode* root2 = xmlDocGetRootElement(xmlFile2);
#if USE_CPPUNIT
CPPUNIT_ASSERT(root1);
CPPUNIT_ASSERT(root2);
cppunitAssertEqual(root1->name, root2->name);
#else
if (!root1 || !root2)
return false ;
if (!xmlStrEqual(root1->name, root2->name))
return false ;
#endif
return compareElements(root1, root2);
}
namespace {
bool checkForEmptyChildren(xmlNodePtr node)
{
if (!node)
return true ;
for (; node != nullptr; node = node->next)
{
if (node->type == XML_ELEMENT_NODE)
return false ;
}
return true ;
}
}
bool XMLDiff::compareElements(xmlNode* node1, xmlNode* node2)
{
#if USE_CPPUNIT
cppunitAssertEqual(node1->name, node2->name);
#else
if (!xmlStrEqual( node1->name, node2->name ))
return false ;
#endif
//compare attributes
bool sameAttribs = compareAttributes(node1, node2);
#if USE_CPPUNIT
CPPUNIT_ASSERT(sameAttribs);
#else
if (!sameAttribs)
return false ;
#endif
// compare children
xmlNode* child2 = nullptr;
xmlNode* child1 = nullptr;
for (child1 = node1->children, child2 = node2->children; child1 != nullptr && child2 != nullptr; child1 = child1->next, child2 = child2->next)
{
if (child1->type == XML_ELEMENT_NODE)
{
bool bCompare = compareElements(child1, child2);
if (!bCompare)
{
return false ;
}
}
}
#if USE_CPPUNIT
CPPUNIT_ASSERT(checkForEmptyChildren(child1));
CPPUNIT_ASSERT(checkForEmptyChildren(child2));
#else
if (!checkForEmptyChildren(child1) || !checkForEmptyChildren(child2))
return false ;
#endif
return true ;
}
void XMLDiff::cppunitAssertEqual(const xmlChar *expected, const xmlChar *found)
{
#if USE_CPPUNIT
std::stringstream stringStream;
stringStream << "Reference: " << fileName << "\n- Expected: " << reinterpret_cast <const char *>(expected) << "\n- Found: " << reinterpret_cast <const char *>(found);
CPPUNIT_ASSERT_MESSAGE(stringStream.str(), xmlStrEqual(expected, found));
#endif
}
void XMLDiff::cppunitAssertEqualDouble(const xmlNodePtr node, const xmlAttrPtr attr, double expected, double found, double delta)
{
#if USE_CPPUNIT
xmlChar * path = xmlGetNodePath(node);
std::stringstream stringStream;
stringStream << "Reference: " << fileName << "\n- Node: " << reinterpret_cast <const char *>(path) << "\n- Attr: " << reinterpret_cast <const char *>(attr->name);
xmlFree(path);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(stringStream.str(), expected, found, delta);
#endif
}
namespace {
bool compareValuesWithTolerance(double val1, double val2, double tolerance, bool relative)
{
if (relative)
{
return (val1/tolerance) <= val2 && val2 <= (val1*tolerance);
}
else
{
return (val1 - tolerance) <= val2 && val2 <= (val1 + tolerance);
}
}
}
bool XMLDiff::compareAttributes(xmlNodePtr node1, xmlNodePtr node2)
{
CPPUNIT_ASSERT(node1);
CPPUNIT_ASSERT(node2);
xmlAttrPtr attr1 = nullptr;
xmlAttrPtr attr2 = nullptr;
for (attr1 = node1->properties, attr2 = node2->properties; attr1 != nullptr && attr2 != nullptr; attr1 = attr1->next, attr2 = attr2->next)
{
#if USE_CPPUNIT
cppunitAssertEqual(attr1->name, attr2->name);
#else
if (!xmlStrEqual( attr1->name, attr2->name ))
return false ;
#endif
xmlChar* val1 = xmlGetProp(node1, attr1->name);
xmlChar* val2 = xmlGetProp(node2, attr2->name);
double dVal1 = xmlXPathCastStringToNumber(val1);
double dVal2 = xmlXPathCastStringToNumber(val2);
if (!std::isnan(dVal1) || !std::isnan(dVal2))
{
//compare by value and respect tolerance
tolerance tol;
tol.elementName = xmlStrdup(node1->name);
tol.attribName = xmlStrdup(attr1->name);
ToleranceContainer::iterator itr = toleranceContainer.find( tol );
bool useTolerance = false ;
if (itr != toleranceContainer.end())
{
useTolerance = true ;
}
if (useTolerance)
{
bool valInTolerance = compareValuesWithTolerance(dVal1, dVal2, itr->value, itr->relative);
#if USE_CPPUNIT
std::stringstream stringStream("Expected Value: " );
stringStream << dVal1 << "; Found Value: " << dVal2 << "; Tolerance: " << itr->value;
stringStream << "; Relative: " << itr->relative;
CPPUNIT_ASSERT_MESSAGE(stringStream.str(), valInTolerance);
#else
if (!valInTolerance)
return false ;
#endif
}
else
{
#if USE_CPPUNIT
cppunitAssertEqualDouble(node1, attr1, dVal1, dVal2, 1e-08);
#else
if (dVal1 != dVal2)
return false ;
#endif
}
}
else
{
#if USE_CPPUNIT
cppunitAssertEqual(val1, val2);
#else
if (!xmlStrEqual( val1, val2 ))
return false ;
#endif
}
xmlFree(val1);
xmlFree(val2);
}
// unequal number of attributes
#ifdef CPPUNIT_ASSERT
if (attr1 || attr2)
{
std::stringstream failStream;
failStream << "Unequal number of attributes in " ;
// print chain from document root
std::vector<std::string> parents;
auto n = node1;
while (n)
{
if (n->name)
parents.push_back(std::string(reinterpret_cast <const char *>(n->name)));
n = n->parent;
}
bool first = true ;
for (auto it = parents.rbegin(); it != parents.rend(); ++it)
{
if (!first)
failStream << "->" ;
first = false ;
failStream << *it;
}
failStream << " Attr1: " ;
attr1 = node1->properties;
while (attr1 != nullptr)
{
xmlChar* val1 = xmlGetProp(node1, attr1->name);
failStream << BAD_CAST(attr1->name) << "=" << BAD_CAST(val1) << ", " ;
xmlFree(val1);
attr1 = attr1->next;
}
failStream << " Attr2: " ;
attr2 = node2->properties;
while (attr2 != nullptr)
{
xmlChar* val2 = xmlGetProp(node2, attr2->name);
failStream << BAD_CAST(attr2->name) << "=" << BAD_CAST(val2) << ", " ;
xmlFree(val2);
attr2 = attr2->next;
}
CPPUNIT_ASSERT_MESSAGE(failStream.str(), false );
}
#else
if (attr1 || attr2)
return false ;
#endif
return true ;
}
bool
doXMLDiff(char const *const pFileName, char const *const pContent, int const size,
char const *const pToleranceFileName)
{
XMLDiff aDiff(pFileName, pContent, size, pToleranceFileName);
return aDiff.compare();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
quality 96%
¤ Dauer der Verarbeitung: 0.6 Sekunden
¤
*© Formatika GbR, Deutschland