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