Quelle diff.cxx
Sprache: C
/* -*- 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 * pToleranceFileN
ame);
~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, 1 e-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: */
Messung V0.5 in Prozent C=96 H=96 G=95
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-06-06)
¤
*© Formatika GbR, Deutschland
2026-08-25
Neuigkeiten
Aktuelles
Motto des Tages
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=277311
#Domains=752002