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 54502 : ~tolerance()
35 : {
36 54502 : xmlFree(elementName);
37 54502 : xmlFree(attribName);
38 54502 : }
39 :
40 54502 : tolerance()
41 : : elementName(NULL)
42 : , attribName(NULL)
43 : , relative(false)
44 54502 : , value(0.0)
45 : {
46 54502 : }
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 xmlNodePtr node, const xmlAttrPtr attr, double expected, double found, double delta);
94 :
95 : ToleranceContainer toleranceContainer;
96 : xmlDocPtr xmlFile1;
97 : xmlDocPtr xmlFile2;
98 : std::string fileName;
99 : };
100 :
101 :
102 36 : XMLDiff::XMLDiff( const char* pFileName, const char* pContent, int size, const char* pToleranceFile)
103 36 : : fileName(pFileName)
104 : {
105 36 : xmlFile1 = xmlParseFile(pFileName);
106 36 : xmlFile2 = xmlParseMemory(pContent, size);
107 :
108 36 : if(pToleranceFile)
109 : {
110 36 : xmlDocPtr xmlToleranceFile = xmlParseFile(pToleranceFile);
111 36 : loadToleranceFile(xmlToleranceFile);
112 36 : xmlFreeDoc(xmlToleranceFile);
113 : }
114 36 : }
115 :
116 72 : XMLDiff::~XMLDiff()
117 : {
118 36 : xmlFreeDoc(xmlFile1);
119 36 : xmlFreeDoc(xmlFile2);
120 36 : }
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 36 : void XMLDiff::loadToleranceFile(xmlDocPtr xmlToleranceFile)
148 : {
149 36 : xmlNodePtr root = xmlDocGetRootElement(xmlToleranceFile);
150 : #if USE_CPPUNIT
151 36 : 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 36 : xmlNodePtr child = NULL;
160 72 : for (child = root->children; child != NULL; child = child->next)
161 : {
162 : // assume a valid xml file
163 36 : if(child->type != XML_ELEMENT_NODE)
164 36 : 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 36 : }
173 :
174 36 : bool XMLDiff::compare()
175 : {
176 36 : xmlNode* root1 = xmlDocGetRootElement(xmlFile1);
177 36 : xmlNode* root2 = xmlDocGetRootElement(xmlFile2);
178 :
179 : #if USE_CPPUNIT
180 36 : CPPUNIT_ASSERT(root1);
181 36 : CPPUNIT_ASSERT(root2);
182 36 : 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 36 : return compareElements(root1, root2);
190 : }
191 :
192 : namespace {
193 :
194 62484 : bool checkForEmptyChildren(xmlNodePtr node)
195 : {
196 62484 : if(!node)
197 62484 : 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 31242 : bool XMLDiff::compareElements(xmlNode* node1, xmlNode* node2)
210 : {
211 : #if USE_CPPUNIT
212 31242 : cppunitAssertEqual(node1->name, node2->name);
213 : #else
214 : if (!xmlStrEqual( node1->name, node2->name ))
215 : return false;
216 : #endif
217 :
218 : //compare attributes
219 31242 : bool sameAttribs = compareAttributes(node1, node2);
220 : #if USE_CPPUNIT
221 31242 : CPPUNIT_ASSERT(sameAttribs);
222 : #else
223 : if (!sameAttribs)
224 : return false;
225 : #endif
226 :
227 : // compare children
228 31242 : xmlNode* child2 = NULL;
229 31242 : xmlNode* child1 = NULL;
230 103768 : for(child1 = node1->children, child2 = node2->children; child1 != NULL && child2 != NULL; child1 = child1->next, child2 = child2->next)
231 : {
232 72526 : if (child1->type == XML_ELEMENT_NODE)
233 : {
234 31206 : bool bCompare = compareElements(child1, child2);
235 31206 : if(!bCompare)
236 : {
237 0 : return false;
238 : }
239 : }
240 : }
241 :
242 : #if USE_CPPUNIT
243 31242 : CPPUNIT_ASSERT(checkForEmptyChildren(child1));
244 31242 : CPPUNIT_ASSERT(checkForEmptyChildren(child2));
245 : #else
246 : if(!checkForEmptyChildren(child1) || !checkForEmptyChildren(child2))
247 : return false;
248 : #endif
249 :
250 31242 : return true;
251 : }
252 :
253 131464 : void XMLDiff::cppunitAssertEqual(const xmlChar *expected, const xmlChar *found)
254 : {
255 : #if USE_CPPUNIT
256 131464 : std::stringstream stringStream;
257 131464 : stringStream << "Reference: " << fileName << "\n- Expected: " << (const char*) expected << "\n- Found: " << (const char*) found;
258 :
259 131464 : CPPUNIT_ASSERT_MESSAGE(stringStream.str(), xmlStrEqual(expected, found));
260 : #endif
261 131464 : }
262 :
263 54502 : void XMLDiff::cppunitAssertEqualDouble(const xmlNodePtr node, const xmlAttrPtr attr, double expected, double found, double delta)
264 : {
265 : #if USE_CPPUNIT
266 54502 : xmlChar * path = xmlGetNodePath(node);
267 54502 : std::stringstream stringStream;
268 54502 : stringStream << "Reference: " << fileName << "\n- Node: " << (const char*) path << "\n- Attr: " << (const char*) attr->name;
269 54502 : xmlFree(path);
270 :
271 54502 : CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(stringStream.str(), expected, found, delta);
272 : #endif
273 54502 : }
274 :
275 : namespace {
276 :
277 0 : bool compareValuesWithTolerance(double val1, double val2, double tolerance, bool relative)
278 : {
279 0 : if(relative)
280 : {
281 0 : return (val1/tolerance) <= val2 && val2 <= (val1*tolerance);
282 : }
283 : else
284 : {
285 0 : return (val1 - tolerance) <= val2 && val2 <= (val1 + tolerance);
286 : }
287 : }
288 :
289 : }
290 :
291 31242 : bool XMLDiff::compareAttributes(xmlNodePtr node1, xmlNodePtr node2)
292 : {
293 31242 : xmlAttrPtr attr1 = NULL;
294 31242 : xmlAttrPtr attr2 = NULL;
295 108586 : for(attr1 = node1->properties, attr2 = node2->properties; attr1 != NULL && attr2 != NULL; attr1 = attr1->next, attr2 = attr2->next)
296 : {
297 : #if USE_CPPUNIT
298 77344 : cppunitAssertEqual(attr1->name, attr2->name);
299 : #else
300 : if (!xmlStrEqual( attr1->name, attr2->name ))
301 : return false;
302 : #endif
303 :
304 77344 : xmlChar* val1 = xmlGetProp(node1, attr1->name);
305 77344 : xmlChar* val2 = xmlGetProp(node2, attr2->name);
306 :
307 77344 : double dVal1 = xmlXPathCastStringToNumber(val1);
308 77344 : double dVal2 = xmlXPathCastStringToNumber(val2);
309 :
310 77344 : if(!rtl::math::isNan(dVal1) || !rtl::math::isNan(dVal2))
311 : {
312 : //compare by value and respect tolerance
313 54502 : tolerance tol;
314 54502 : tol.elementName = xmlStrdup(node1->name);
315 54502 : tol.attribName = xmlStrdup(attr1->name);
316 54502 : ToleranceContainer::iterator itr = toleranceContainer.find( tol );
317 54502 : bool useTolerance = false;
318 54502 : if (itr != toleranceContainer.end())
319 : {
320 0 : useTolerance = true;
321 : }
322 :
323 54502 : if (useTolerance)
324 : {
325 0 : bool valInTolerance = compareValuesWithTolerance(dVal1, dVal2, itr->value, itr->relative);
326 : #if USE_CPPUNIT
327 0 : std::stringstream stringStream("Expected Value: ");
328 0 : stringStream << dVal1 << "; Found Value: " << dVal2 << "; Tolerance: " << itr->value;
329 0 : stringStream << "; Relative: " << itr->relative;
330 0 : CPPUNIT_ASSERT_MESSAGE(stringStream.str(), valInTolerance);
331 : #else
332 : if (!valInTolerance)
333 : return false;
334 : #endif
335 : }
336 : else
337 : {
338 : #if USE_CPPUNIT
339 54502 : cppunitAssertEqualDouble(node1, attr1, dVal1, dVal2, 1e-08);
340 : #else
341 : if (dVal1 != dVal2)
342 : return false;
343 : #endif
344 54502 : }
345 : }
346 : else
347 : {
348 :
349 : #if USE_CPPUNIT
350 22842 : cppunitAssertEqual(val1, val2);
351 : #else
352 : if(!xmlStrEqual( val1, val2 ))
353 : return false;
354 : #endif
355 : }
356 :
357 77344 : xmlFree(val1);
358 77344 : xmlFree(val2);
359 : }
360 :
361 : // unequal number of attributes
362 : #ifdef CPPUNIT_ASSERT
363 31242 : CPPUNIT_ASSERT(!attr1);
364 31242 : CPPUNIT_ASSERT(!attr2);
365 : #else
366 : if (attr1 || attr2)
367 : return false;
368 : #endif
369 :
370 31242 : return true;
371 : }
372 :
373 :
374 : bool
375 36 : doXMLDiff(char const*const pFileName, char const*const pContent, int const size,
376 : char const*const pToleranceFileName)
377 : {
378 36 : XMLDiff aDiff(pFileName, pContent, size, pToleranceFileName);
379 36 : return aDiff.compare();
380 642 : }
381 :
382 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|