Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
5 : * This Source Code Form is subject to the terms of the Mozilla Public
6 : * License, v. 2.0. If a copy of the MPL was not distributed with this
7 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 : */
9 :
10 : #define USE_CPPUNIT 1
11 :
12 : #include "test/xmldiff.hxx"
13 :
14 : #include <libxml/xpath.h>
15 : #include <libxml/parser.h>
16 : #include <libxml/tree.h>
17 : #include <libxml/xmlmemory.h>
18 :
19 : #include <set>
20 : #include <cstring>
21 : #include <sstream>
22 : #include <cmath>
23 : #include <cassert>
24 :
25 : #if USE_CPPUNIT
26 : #include <cppunit/extensions/HelperMacros.h>
27 : #endif
28 :
29 : #include <rtl/math.hxx>
30 :
31 :
32 : struct tolerance
33 : {
34 0 : ~tolerance()
35 : {
36 0 : xmlFree(elementName);
37 0 : xmlFree(attribName);
38 0 : }
39 :
40 0 : tolerance()
41 : : elementName(NULL)
42 : , attribName(NULL)
43 : , relative(false)
44 0 : , value(0.0)
45 : {
46 0 : }
47 :
48 0 : tolerance(const tolerance& tol)
49 : {
50 0 : elementName = xmlStrdup(tol.elementName);
51 0 : attribName = xmlStrdup(tol.attribName);
52 0 : relative = tol.relative;
53 0 : value = tol.value;
54 0 : }
55 :
56 : xmlChar* elementName;
57 : xmlChar* attribName;
58 : bool relative;
59 : double value;
60 0 : bool operator<(const tolerance& rTol) const
61 : {
62 0 : int cmp = xmlStrcmp(elementName, rTol.elementName);
63 0 : if(cmp == 0)
64 : {
65 0 : cmp = xmlStrcmp(attribName, rTol.attribName);
66 : }
67 :
68 0 : if(cmp>=0)
69 0 : return false;
70 : else
71 0 : return true;
72 : }
73 : };
74 :
75 : class XMLDiff
76 : {
77 : public:
78 : XMLDiff(const char* pFileName, const char* pContent, int size, const char* pToleranceFileName);
79 : ~XMLDiff();
80 :
81 : bool compare();
82 : private:
83 : typedef std::set<tolerance> ToleranceContainer;
84 :
85 : void loadToleranceFile(xmlDocPtr xmlTolerance);
86 : bool compareAttributes(xmlNodePtr node1, xmlNodePtr node2);
87 : bool compareElements(xmlNodePtr node1, xmlNodePtr node2);
88 :
89 : /// Error message for cppunit that prints out when expected and found are not equal.
90 : void cppunitAssertEqual(const xmlChar *expected, const xmlChar *found);
91 :
92 : /// Error message for cppunit that prints out when expected and found are not equal - for doubles.
93 : void cppunitAssertEqualDouble(const xmlChar *node, double expected, double found, double delta);
94 :
95 : ToleranceContainer toleranceContainer;
96 : xmlDocPtr xmlFile1;
97 : xmlDocPtr xmlFile2;
98 : std::string fileName;
99 : };
100 :
101 :
102 0 : XMLDiff::XMLDiff( const char* pFileName, const char* pContent, int size, const char* pToleranceFile)
103 0 : : fileName(pFileName)
104 : {
105 0 : xmlFile1 = xmlParseFile(pFileName);
106 0 : xmlFile2 = xmlParseMemory(pContent, size);
107 :
108 0 : if(pToleranceFile)
109 : {
110 0 : xmlDocPtr xmlToleranceFile = xmlParseFile(pToleranceFile);
111 0 : loadToleranceFile(xmlToleranceFile);
112 0 : xmlFreeDoc(xmlToleranceFile);
113 : }
114 0 : }
115 :
116 0 : XMLDiff::~XMLDiff()
117 : {
118 0 : xmlFreeDoc(xmlFile1);
119 0 : xmlFreeDoc(xmlFile2);
120 0 : }
121 :
122 : namespace {
123 :
124 0 : void readAttributesForTolerance(xmlNodePtr node, tolerance& tol)
125 : {
126 0 : xmlChar* elementName = xmlGetProp(node, BAD_CAST("elementName"));
127 0 : tol.elementName = elementName;
128 :
129 0 : xmlChar* attribName = xmlGetProp(node, BAD_CAST("attribName"));
130 0 : tol.attribName = attribName;
131 :
132 0 : xmlChar* value = xmlGetProp(node, BAD_CAST("value"));
133 0 : double val = xmlXPathCastStringToNumber(value);
134 0 : xmlFree(value);
135 0 : tol.value = val;
136 :
137 0 : xmlChar* relative = xmlGetProp(node, BAD_CAST("relative"));
138 0 : bool rel = false;
139 0 : if(xmlStrEqual(relative, BAD_CAST("true")))
140 0 : rel = true;
141 0 : xmlFree(relative);
142 0 : tol.relative = rel;
143 0 : }
144 :
145 : }
146 :
147 0 : void XMLDiff::loadToleranceFile(xmlDocPtr xmlToleranceFile)
148 : {
149 0 : xmlNodePtr root = xmlDocGetRootElement(xmlToleranceFile);
150 : #if USE_CPPUNIT
151 0 : CPPUNIT_ASSERT_MESSAGE("did not find correct tolerance file", xmlStrEqual( root->name, BAD_CAST("tolerances") ));
152 : #else
153 : if(!xmlStrEqual( root->name, BAD_CAST("tolerances") ))
154 : {
155 : assert(false);
156 : return;
157 : }
158 : #endif
159 0 : xmlNodePtr child = NULL;
160 0 : for (child = root->children; child != NULL; child = child->next)
161 : {
162 : // assume a valid xml file
163 0 : if(child->type != XML_ELEMENT_NODE)
164 0 : continue;
165 :
166 : assert(xmlStrEqual(child->name, BAD_CAST("tolerance")));
167 :
168 0 : tolerance tol;
169 0 : readAttributesForTolerance(child, tol);
170 0 : toleranceContainer.insert(tol);
171 0 : }
172 0 : }
173 :
174 0 : bool XMLDiff::compare()
175 : {
176 0 : xmlNode* root1 = xmlDocGetRootElement(xmlFile1);
177 0 : xmlNode* root2 = xmlDocGetRootElement(xmlFile2);
178 :
179 : #if USE_CPPUNIT
180 0 : CPPUNIT_ASSERT(root1);
181 0 : CPPUNIT_ASSERT(root2);
182 0 : cppunitAssertEqual(root1->name, root2->name);
183 : #else
184 : if (!root1 || !root2)
185 : return false;
186 : if(!xmlStrEqual(root1->name, root2->name))
187 : return false;
188 : #endif
189 0 : return compareElements(root1, root2);
190 : }
191 :
192 : namespace {
193 :
194 0 : bool checkForEmptyChildren(xmlNodePtr node)
195 : {
196 0 : if(!node)
197 0 : return true;
198 :
199 0 : for(; node != NULL; node = node->next)
200 : {
201 0 : if (node->type == XML_ELEMENT_NODE)
202 0 : return false;
203 : }
204 0 : return true;
205 : }
206 :
207 : }
208 :
209 0 : bool XMLDiff::compareElements(xmlNode* node1, xmlNode* node2)
210 : {
211 : #if USE_CPPUNIT
212 0 : cppunitAssertEqual(node1->name, node2->name);
213 : #else
214 : if (!xmlStrEqual( node1->name, node2->name ))
215 : return false;
216 : #endif
217 :
218 : //compare attributes
219 0 : bool sameAttribs = compareAttributes(node1, node2);
220 : #if USE_CPPUNIT
221 0 : CPPUNIT_ASSERT(sameAttribs);
222 : #else
223 : if (!sameAttribs)
224 : return false;
225 : #endif
226 :
227 : // compare children
228 0 : xmlNode* child2 = NULL;
229 0 : xmlNode* child1 = NULL;
230 0 : for(child1 = node1->children, child2 = node2->children; child1 != NULL && child2 != NULL; child1 = child1->next, child2 = child2->next)
231 : {
232 0 : if (child1->type == XML_ELEMENT_NODE)
233 : {
234 0 : bool bCompare = compareElements(child1, child2);
235 0 : if(!bCompare)
236 : {
237 0 : return false;
238 : }
239 : }
240 : }
241 :
242 : #if USE_CPPUNIT
243 0 : CPPUNIT_ASSERT(checkForEmptyChildren(child1));
244 0 : CPPUNIT_ASSERT(checkForEmptyChildren(child2));
245 : #else
246 : if(!checkForEmptyChildren(child1) || !checkForEmptyChildren(child2))
247 : return false;
248 : #endif
249 :
250 0 : return true;
251 : }
252 :
253 0 : void XMLDiff::cppunitAssertEqual(const xmlChar *expected, const xmlChar *found)
254 : {
255 : #if USE_CPPUNIT
256 0 : std::stringstream stringStream;
257 0 : stringStream << "Reference: " << fileName << "\n- Expected: " << (const char*) expected << "\n- Found: " << (const char*) found;
258 :
259 0 : CPPUNIT_ASSERT_MESSAGE(stringStream.str(), xmlStrEqual(expected, found));
260 : #endif
261 0 : }
262 :
263 0 : void XMLDiff::cppunitAssertEqualDouble(const xmlChar *node, double expected, double found, double delta)
264 : {
265 : #if USE_CPPUNIT
266 0 : std::stringstream stringStream;
267 0 : stringStream << "Reference: " << fileName << "\n- Node: " << (const char*) node;
268 :
269 0 : CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(stringStream.str(), expected, found, delta);
270 : #endif
271 0 : }
272 :
273 : namespace {
274 :
275 0 : bool compareValuesWithTolerance(double val1, double val2, double tolerance, bool relative)
276 : {
277 0 : if(relative)
278 : {
279 0 : return (val1/tolerance) <= val2 && val2 <= (val1*tolerance);
280 : }
281 : else
282 : {
283 0 : return (val1 - tolerance) <= val2 && val2 <= (val1 + tolerance);
284 : }
285 : }
286 :
287 : }
288 :
289 0 : bool XMLDiff::compareAttributes(xmlNodePtr node1, xmlNodePtr node2)
290 : {
291 0 : xmlAttrPtr attr1 = NULL;
292 0 : xmlAttrPtr attr2 = NULL;
293 0 : for(attr1 = node1->properties, attr2 = node2->properties; attr1 != NULL && attr2 != NULL; attr1 = attr1->next, attr2 = attr2->next)
294 : {
295 : #if USE_CPPUNIT
296 0 : cppunitAssertEqual(attr1->name, attr2->name);
297 : #else
298 : if (!xmlStrEqual( attr1->name, attr2->name ))
299 : return false;
300 : #endif
301 :
302 0 : xmlChar* val1 = xmlGetProp(node1, attr1->name);
303 0 : xmlChar* val2 = xmlGetProp(node2, attr2->name);
304 :
305 0 : double dVal1 = xmlXPathCastStringToNumber(val1);
306 0 : double dVal2 = xmlXPathCastStringToNumber(val2);
307 :
308 0 : if(!rtl::math::isNan(dVal1) || !rtl::math::isNan(dVal2))
309 : {
310 : //compare by value and respect tolerance
311 0 : tolerance tol;
312 0 : tol.elementName = xmlStrdup(node1->name);
313 0 : tol.attribName = xmlStrdup(attr1->name);
314 0 : ToleranceContainer::iterator itr = toleranceContainer.find( tol );
315 0 : bool useTolerance = false;
316 0 : if (itr != toleranceContainer.end())
317 : {
318 0 : useTolerance = true;
319 : }
320 :
321 0 : if (useTolerance)
322 : {
323 0 : bool valInTolerance = compareValuesWithTolerance(dVal1, dVal2, itr->value, itr->relative);
324 : #if USE_CPPUNIT
325 0 : std::stringstream stringStream("Expected Value: ");
326 0 : stringStream << dVal1 << "; Found Value: " << dVal2 << "; Tolerance: " << itr->value;
327 0 : stringStream << "; Relative: " << itr->relative;
328 0 : CPPUNIT_ASSERT_MESSAGE(stringStream.str(), valInTolerance);
329 : #else
330 : if (!valInTolerance)
331 : return false;
332 : #endif
333 : }
334 : else
335 : {
336 : #if USE_CPPUNIT
337 0 : cppunitAssertEqualDouble(attr1->name, dVal1, dVal2, 1e-08);
338 : #else
339 : if (dVal1 != dVal2)
340 : return false;
341 : #endif
342 0 : }
343 : }
344 : else
345 : {
346 :
347 : #if USE_CPPUNIT
348 0 : cppunitAssertEqual(val1, val2);
349 : #else
350 : if(!xmlStrEqual( val1, val2 ))
351 : return false;
352 : #endif
353 : }
354 :
355 0 : xmlFree(val1);
356 0 : xmlFree(val2);
357 : }
358 :
359 : // unequal number of attributes
360 : #ifdef CPPUNIT_ASSERT
361 0 : CPPUNIT_ASSERT(!attr1);
362 0 : CPPUNIT_ASSERT(!attr2);
363 : #else
364 : if (attr1 || attr2)
365 : return false;
366 : #endif
367 :
368 0 : return true;
369 : }
370 :
371 :
372 : bool
373 0 : doXMLDiff(char const*const pFileName, char const*const pContent, int const size,
374 : char const*const pToleranceFileName)
375 : {
376 0 : XMLDiff aDiff(pFileName, pContent, size, pToleranceFileName);
377 0 : return aDiff.compare();
378 0 : }
379 :
380 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|