00001 //////////////////////////////////////////////////////////////////////////////// 00002 /// 00003 /// \file 00004 /// \author Gerald Fahrnholz 00005 /// 00006 /// \defgroup GrpXmlCheck Using XmlCheck 00007 /// @{ 00008 /// \ingroup GrpTestUtils 00009 /// \brief 00010 /// Class XmlCheck allows to check the contents of an XML tree/subtree. 00011 /// 00012 /// <b>\n Supported features</b> 00013 /// 00014 /// - check of XML trees and subtrees 00015 /// 00016 /// - support for XPath navigation 00017 /// 00018 /// - skipping specific attributes/nodes 00019 /// 00020 /// - treating float attributes/nodes with configurable precision 00021 /// 00022 /// <b>Depends on</b> 00023 /// 00024 /// - TinyXpath (includes TinyXml) 00025 /// 00026 /// <b>HowTo access XML nodes with TinyXml</b> 00027 /// \code 00028 /// 00029 /// // Read XML string into XML document structure 00030 /// TiXmlDocument doc; 00031 /// doc.Parse(DEMO_XML_STRING); 00032 /// 00033 /// // Save XML document to file 00034 /// std::string filePath = MY_TEST_DIR + "/GeneratedXmlFile.xml"; 00035 /// doc.SaveFile(filePath.c_str()); 00036 /// 00037 /// // Read XML file into XML document structure 00038 /// TiXmlDocument docFromFile; 00039 /// docFromFile.LoadFile(filePath.c_str()); 00040 /// \endcode 00041 /// 00042 /// 00043 /// <b>HowTo address subnodes with TinyXpath</b> 00044 /// <table> 00045 /// <tr> 00046 /// <td>/MyContainer/Group_B</td> 00047 /// <td>subnode Group_B</td> 00048 /// </tr> 00049 /// <tr> 00050 /// <td>/MyContainer/Group_B/x</td> 00051 /// <td>first x element within Group_B</td> 00052 /// </tr> 00053 /// <tr> 00054 /// <td>/MyContainer/Group_B/x[3]</td> 00055 /// <td>third x element within Group_B</td> 00056 /// </tr> 00057 /// <tr> 00058 /// <td>/MyContainer/Group_B/x[last()]</td> 00059 /// <td>last x element of Group_B</td> 00060 /// </tr> 00061 /// <tr> 00062 /// <td>/MyContainer/Country[\@name='Germany']</td> 00063 /// <td>first country with specific attribute 'name'</td> 00064 /// </tr> 00065 /// <tr> 00066 /// <td>/MyContainer/Country[attribute::name='USA']</td> 00067 /// <td>first country with specific attribute 'name' (explicit syntax)</td> 00068 /// </tr> 00069 /// </table> 00070 /// 00071 /// 00072 /// <b>Typical code snippets when used with Boost.Test</b><br><br> 00073 /// see also \ref GrpTestXmlCheckWithBoost 00074 /// 00075 /// - comparing complete XML trees 00076 /// <pre> 00077 /// 00078 /// BOOST_MESSAGE("\nComparing two XML trees"); 00079 /// BOOST_CHECK(TTB::TheXmlCheck()-><b>Compare</b>(&docA,&docB)); 00080 /// BOOST_MESSAGE(TTB::TheXmlCheck()->GetCompareResult()); 00081 /// </pre> 00082 /// 00083 /// - comparing XML subtrees and ignoring attributes/nodes 00084 /// <pre> 00085 /// 00086 /// BOOST_MESSAGE("\nComparing two XML subtrees using XPath, ignoring time"); 00087 /// TTB::TheXmlCheck()-><b>IgnoreAttribute</b>("time"); 00088 /// BOOST_CHECK(TTB::TheXmlCheck()->Compare( 00089 /// TTB::XmlCheck::<b>SubNodeFromXPath</b>(docA.RootElement(), "/MyContainer/Group_B"), 00090 /// TTB::XmlCheck::<b>SubNodeFromXPath</b>(docB.RootElement(), "/MyContainer/Group_B"))); 00091 /// BOOST_MESSAGE(TTB::TheXmlCheck()->GetCompareResult()); 00092 /// </pre> 00093 /// 00094 /// 00095 /// <b>Typical code snippets when used with Boost.Test and TestToolBox::TestEvents</b><p> 00096 /// see also \ref GrpTestXmlCheckWithBoostExtensions 00097 /// 00098 /// - checking XML contents, ignoring some attributes/nodes 00099 /// <pre> 00100 /// 00101 /// TTB::TheXmlCheck()->IgnoreAttribute("date"); 00102 /// TTB::TheXmlCheck()->IgnoreAttribute("time"); 00103 /// TTB::TheXmlCheck()->IgnoreNode("date"); 00104 /// TTB::TheXmlCheck()->UseAsDoubleAttribute("tolerance", true, 3); 00105 /// TTB::TheXmlCheck()->UseAsDoubleNode("x", true, 4); 00106 /// TTB::TheXmlCheck()-><strong>CheckNode(&doc)</strong>; 00107 /// 00108 /// TTB_EXP("MyContainer"); 00109 /// TTB_EXP(" Group_A"); 00110 /// TTB_EXP(" Vector"); 00111 /// TTB_EXP(" id: 1"); 00112 /// TTB_EXP(" tolerance: 0.026"); 00113 /// TTB_EXP(" This is the text of the first vector element"); 00114 /// TTB_EXP(" x"); 00115 /// TTB_EXP(" 3.1424"); 00116 /// TTB_EXP(" Vector"); 00117 /// TTB_EXP(" id: 2"); 00118 /// TTB_EXP(" tolerance: 0.026"); 00119 /// TTB_EXP(" This is the text of the second vector element"); 00120 /// TTB_EXP(" x"); 00121 /// TTB_EXP(" 1.3264"); 00122 /// </pre> 00123 /// 00124 /// - checking XML contents, accessing subtree using XPath 00125 /// <pre> 00126 /// 00127 /// TTB::TheXmlCheck()-><b>CheckSubNode(doc.RootElement(),""/MyContainer/Group_A/Vector[last()]")</b>; 00128 /// 00129 /// TTB_EXP("Vector"); 00130 /// TTB_EXP(" id: 3"); 00131 /// TTB_EXP(" tolerance: 1.357"); 00132 /// TTB_EXP(" x"); 00133 /// TTB_EXP(" 6.28532"); 00134 /// 00135 /// or as alternative syntax: 00136 /// 00137 /// TTB::TheXmlCheck()-><b>CheckSubNode</b>( 00138 /// TTB::XmlCheck::<b>SubNodeFromXPath</b>(doc.RootElement(),<b>"/MyContainer/Group_A"</b>), 00139 /// <b>"Vector[last()]"</b>); 00140 /// 00141 /// or identifying XML contents via <b>multiple attributes / multiple XPath expressions</b>: 00142 /// 00143 /// XPath: Direct acess to third node hierarchy (street within town within country) 00144 /// TTB::TheXmlCheck()->CheckSubNode(doc.RootElement(), 00145 /// "Country[@name='Germany']", 00146 /// "Town[@name='Regensburg']", 00147 /// "Street[@name='LappersdorferStrasse']"); 00148 /// 00149 /// TTB_EXP("Street"); 00150 /// TTB_EXP(" name: LappersdorferStrasse"); 00151 /// TTB_EXP(" Contents for LappersdorferStrasse in Regensburg, Germany"); 00152 /// </pre> 00153 /// 00154 /// 00155 /// - comparing two XML trees 00156 /// <pre> 00157 /// 00158 /// TTB::TheXmlCheck()->IgnoreAttribute("time"); 00159 /// TTB::TheXmlCheck()-><b>Compare(&docA,&docB)</b>; 00160 /// 00161 /// TTB_EXP("Difference detected"); 00162 /// TTB_EXP("A: tolerance: 0.0258 (line: 7 col: 54, path: //MyContainer/Group_A/Vector)"); 00163 /// TTB_EXP("B: tolerance: 0.8258 (line: 9 col: 54, path: //MyContainer/Group_A/Vector)"); 00164 /// 00165 /// 00166 /// TTB::TheXmlCheck()->Compare( 00167 /// TTB::XmlCheck::<b>SubNodeFromXPath</b>(docA.RootElement(), "/MyContainer/Group_B"), 00168 /// TTB::XmlCheck::<b>SubNodeFromXPath</b>(docB.RootElement(), "/MyContainer/Group_B")); 00169 /// 00170 /// TTB_EXP("Difference detected"); 00171 /// TTB_EXP("A: 3.132437 (line: 18 col: 8, path: //MyContainer/Group_B/x/3.132437/)"); 00172 /// TTB_EXP("B: 12.132437 (line: 20 col: 8, path: //MyContainer/Group_B/x/12.132437/)"); 00173 /// </pre> 00174 /// 00175 /// 00176 /// <b>Code usage</b> 00177 /// 00178 /// There are two main possibilities to use XmlCheck within your test applications: 00179 /// 00180 /// - use as "inline code" 00181 /// - from all source files use <code>#include "TestToolBox\XmlCheckImpl.h"</code><br> 00182 /// - example see \ref TestXmlCheckWithBoost.cpp<br> 00183 /// - Disadvantage: each cpp file contains the same implementation code 00184 /// 00185 /// - use as "library" code:<br> 00186 /// - from all source files use <code>#include "TestToolBox\XmlCheck.h"</code><br> 00187 /// - add implementation code to a single source file e.g. stdafx.cpp 00188 /// \code 00189 /// // Compile as real functions which are available to other 00190 /// // source files which need only include TestToolBox\XmlCheck.h 00191 /// #define TTB_NOT_INLINE 00192 /// #include "TestToolBox\XmlCheckImpl.h" 00193 /// \endcode 00194 /// - example see \ref TestXmlCheckWithBoostExtensions.cpp<br> 00195 /// 00196 /// \sa XmlCheck.h (File documentation) 00197 /// \sa TestToolBox::XmlCheck (class documentation) 00198 /// \sa GrpTestXmlCheckBasic (basic tests) 00199 /// \sa GrpTestXmlCheckWithBoost (tests with Boost) 00200 /// \sa GrpTestXmlCheckWithBoostExtensions (tests with Boost and TestToolBox::TestEvents) 00201 /// 00202 //////////////////////////////////////////////////////////////////////////////// 00203 00204 #pragma once 00205 00206 #include <set> 00207 #include <map> 00208 #include <vector> 00209 00210 class TiXmlNode; 00211 class TiXmlElement; 00212 00213 00214 //************************************************************************ 00215 00216 namespace TestToolBox 00217 { 00218 00219 struct IEventReceiver; 00220 enum EventContext; 00221 00222 //************************************************************************ 00223 //************************************************************************ 00224 00225 00226 /// \brief Check an XML tree/subtree or compare two trees/subtrees. 00227 /// For more info about usage see \ref GrpXmlCheck. 00228 /// 00229 /// \nosubgrouping 00230 /// \sa XmlCheck.h (File documentation) 00231 /// \sa GrpTestXmlCheckBasic (basic tests) 00232 /// \sa GrpTestXmlCheckWithBoost (tests with Boost) 00233 /// \sa GrpTestXmlCheckWithBoostExtensions (tests with Boost and TestToolBox::TestEvents) 00234 class XmlCheck 00235 { 00236 public: 00237 00238 ///\cond do not document 00239 00240 // Constructor 00241 XmlCheck(); 00242 00243 // Destructor 00244 virtual ~XmlCheck(); 00245 00246 // Get the pointer to the only instance of this class 00247 static XmlCheck* Get (void); 00248 00249 static XmlCheck* s_pXmlCheck; 00250 00251 static void Cleanup (void); 00252 00253 ///\endcond 00254 00255 //--------------------------------------------------------------- 00256 //! \name Configuration 00257 //! \{ 00258 00259 /// \brief Define to which destination test events (e.g. failures) are reported 00260 /// \return previous EventReceiver 00261 IEventReceiver* SetEventReceiver ( 00262 IEventReceiver* in_pEventReceiver); 00263 00264 /// Set default values for the following settings 00265 void SetDefaults (void); 00266 00267 /// \brief Set output mode for writing XML contents 00268 /// 00269 /// Allowed values: CTX_PROT, CTX_INFO, CTX_TEST_EVENT 00270 void SetOutputModeForWritingXmlContents ( 00271 EventContext in_outputMode); 00272 00273 /// Set amount of data written to test output 00274 void SetDetailedOutput (bool in_detailedOutput); 00275 00276 /// Define whether the given attribute shall be ignored when checking XML data 00277 void IgnoreAttribute ( 00278 std::string const & in_attribute, 00279 bool in_ignore = true); 00280 00281 /// Return whether the given attribute shall be ignored when checking XML data 00282 bool IsAttributeToIgnore ( 00283 std::string const & in_attribute) const; 00284 00285 /// Define whether the given node shall be ignored when checking XML data 00286 void IgnoreNode ( 00287 std::string const & in_nodeName, 00288 bool in_ignore = true); 00289 00290 /// Return whether the given node shall be ignored when checking XML data 00291 bool IsNodeToIgnore ( 00292 std::string const & in_nodeName) const; 00293 00294 /// \brief Define whether the given attribute is of type double and 00295 /// in case of type double specify the precision to be used for 00296 /// checking the value. 00297 void UseAsDoubleAttribute ( 00298 std::string const & in_attribute, 00299 bool in_useAsDouble = true, 00300 int in_precision = 4); 00301 00302 /// Return whether the given attribute is of type double 00303 bool IsDoubleAttribute ( 00304 std::string const & in_attribute) const; 00305 00306 /// Return the precision used for checking of the given double attribute 00307 int GetPrecisionOfDoubleAttribute ( 00308 std::string const & in_attribute) const; 00309 00310 /// \brief Define whether the given node is of type double and 00311 /// in case of type double specify the precision to be used for 00312 /// checking the value. 00313 void UseAsDoubleNode ( 00314 std::string const & in_nodeName, 00315 bool in_useAsDouble = true, 00316 int in_precision = 4); 00317 00318 /// Return whether the given node is of type double 00319 bool IsDoubleNode ( 00320 std::string const & in_nodeName) const; 00321 00322 /// Return the precision used for checking of the given double node 00323 int GetPrecisionOfDoubleNode ( 00324 std::string const & in_nodeName) const; 00325 00326 /// \} 00327 00328 //--------------------------------------------------------------- 00329 /// \name Check XML contents 00330 /// \{ 00331 00332 00333 /// \brief Writes contents of given XML node to EventReceiver. 00334 /// Each attribute and text is reported with a separate call 00335 /// to EventReceiver. 00336 void CheckNode ( 00337 TiXmlNode* in_pNode, ///< XML (sub)tree to check 00338 char const * in_file = 0, ///< optional file name used in case of error 00339 long in_lineNum = 0 ///< optional line number used in case of error 00340 ); 00341 00342 /// \brief Writes contents of given XML node to EventReceiver. 00343 /// Each attribute and text is reported with a separate call 00344 /// to EventReceiver. 00345 void CheckSubNode ( 00346 TiXmlNode* in_pNode, ///< XML (sub)tree to check 00347 std::string const & in_xPathExpression_1, ///< XPath expression defining a subnode 00348 std::string const & in_xPathExpression_2 = "", ///< XPath expression defining a subnode of level 2 00349 std::string const & in_xPathExpression_3 = "", ///< XPath expression defining a subnode of level 3 00350 std::string const & in_xPathExpression_4 = "", ///< XPath expression defining a subnode of level 4 00351 std::string const & in_xPathExpression_5 = "", ///< XPath expression defining a subnode of level 5 00352 char const * in_file = 0, ///< optional file name used in case of error 00353 long in_lineNum = 0 ///< optional line number used in case of error 00354 ); 00355 00356 /// \brief Same as CheckSubNode() but writes contents only for the first in_maxChilds 00357 /// direct child elements of the node identified by the XPath expression(s) 00358 void CheckSubNodeWithMaxChilds ( 00359 int in_maxChilds, ///< max number of childs to check 00360 TiXmlNode* in_pNode, ///< XML (sub)tree to check 00361 std::string const & in_xPathExpression_1, ///< XPath expression defining a subnode 00362 std::string const & in_xPathExpression_2 = "", ///< XPath expression defining a subnode of level 2 00363 std::string const & in_xPathExpression_3 = "", ///< XPath expression defining a subnode of level 3 00364 std::string const & in_xPathExpression_4 = "", ///< XPath expression defining a subnode of level 4 00365 std::string const & in_xPathExpression_5 = "", ///< XPath expression defining a subnode of level 5 00366 char const * in_file = 0, ///< optional file name used in case of error 00367 long in_lineNum = 0 ///< optional line number used in case of error 00368 ); 00369 00370 /// \brief Compares two XML (sub) trees. The result depends on the settings 00371 /// for attributes/nodes to ignore and for the used precision of double values. 00372 /// \return true if the XML trees are identical 00373 bool Compare ( 00374 TiXmlNode* in_pNodeA, ///< XML (sub)tree A to check 00375 TiXmlNode* in_pNodeB, ///< XML (sub)tree B to check 00376 char const * in_file = 0, ///< optional file name used in case of error 00377 long in_lineNum = 0 ///< optional line number used in case of error 00378 ); 00379 00380 /// Get textual description of the last call to Compare 00381 std::string GetCompareResult (void) const; 00382 00383 /// \} 00384 00385 //--------------------------------------------------------------- 00386 //! \name Helper functions 00387 //! \{ 00388 00389 /// Get the path for the given node 00390 static std::string GetFullPath ( 00391 TiXmlNode* in_pNode, 00392 std::string const & in_rPathToConcat = ""); 00393 00394 /// Get the node from the given parent node and the given path description 00395 static TiXmlNode* SubNodeFromXPath ( 00396 TiXmlNode* in_pParentNode, 00397 std::string const & in_xPathExpression_1, 00398 std::string const & in_xPathExpression_2 = "", 00399 std::string const & in_xPathExpression_3 = "", 00400 std::string const & in_xPathExpression_4 = "", 00401 std::string const & in_xPathExpression_5 = ""); 00402 00403 ///\cond do not document 00404 00405 struct StringPosition 00406 { 00407 explicit StringPosition ( 00408 std::string::size_type in_pos = 0) 00409 : m_curPos (in_pos) 00410 , m_lastPos (in_pos) 00411 , m_lineStr () 00412 {} 00413 00414 std::string::size_type m_curPos; 00415 std::string::size_type m_lastPos; 00416 std::string m_lineStr; 00417 }; 00418 00419 // Extract the next substring delimited by new line 00420 static bool GetNextLineStr ( 00421 std::string const & in_stringWithNewLines, 00422 StringPosition & io_current); 00423 00424 ///\endcond 00425 00426 /// \} 00427 00428 protected: 00429 00430 /// Interface for receiving all kinds of test events 00431 IEventReceiver* m_pEventReceiver; 00432 00433 private: 00434 00435 /// Indenting to be used when writing an XML tree to test output 00436 static const unsigned int NUM_INDENTS_PER_SPACE=2; 00437 00438 /// Output mode for writing XML contents 00439 EventContext m_outputMode; 00440 00441 /// Flag indicating whether the output contains more details 00442 bool m_detailedOutput; 00443 00444 /// Set of attribute names to ignore when checking an XML tree 00445 std::set<std::string> m_attributesToIgnore; 00446 00447 /// Set of node names to ignore when checking an XML tree 00448 std::set<std::string> m_nodesToIgnore; 00449 00450 /// Set of attribute names to be treated as doubles with specific precision 00451 std::map<std::string,int> m_doubleAttributes; 00452 00453 /// Set of node names to be treated as doubles with specific precision 00454 std::map<std::string,int> m_doubleNodes; 00455 00456 /// Result of last call to Compare() 00457 std::string m_compareResult; 00458 00459 ///\cond do not document 00460 00461 struct TextWithPosInfo 00462 { 00463 TextWithPosInfo () 00464 : m_row (0) 00465 , m_col (0) 00466 , m_fullPath () 00467 , m_text () 00468 {} 00469 00470 TextWithPosInfo ( 00471 int in_row, 00472 int in_col, 00473 std::string const & in_fullPath, 00474 std::string const & in_text) 00475 : m_row (in_row) 00476 , m_col (in_col) 00477 , m_fullPath (in_fullPath) 00478 , m_text (in_text) 00479 {} 00480 00481 int m_row; 00482 int m_col; 00483 std::string m_fullPath; 00484 std::string m_text; 00485 }; 00486 00487 // Each line contains an XML entry plus additional infos for 00488 // better error reporting 00489 typedef std::vector<TextWithPosInfo> FormattedContents; 00490 00491 /// \endcond 00492 00493 00494 private: 00495 /// \brief Write given XML node to EventReceiver or given optional container 00496 void WriteNode( 00497 TiXmlNode* in_pNode, ///< XML (sub)tree to check 00498 unsigned int in_indent, ///< indentation for pretty format 00499 int in_maxChildNodes = 0, ///<maximum number of direct childs to check, 0 : all childs 00500 char const * in_file = 0, ///< optional file name used in case of error 00501 long in_lineNum = 0, ///< optional line number used in case of error 00502 FormattedContents* in_pContents = 0 ///< optional container to write output to instead 00503 ///< of using configured EventReceiver 00504 ); 00505 00506 /// \brief Writes attributes of given XML node to EventReceiver. 00507 /// 00508 /// Each attribute and text is reported with a separate call 00509 /// to EventReceiver. 00510 void WriteAttributes( 00511 TiXmlElement* in_pElement, ///< XML (sub)tree to check 00512 unsigned int in_indent, ///< indentation for pretty format 00513 char const * in_file = 0, ///< optional file name used in case of error 00514 long in_lineNum = 0, ///< optional line number used in case of error 00515 FormattedContents* in_pContents = 0 ///< optional container to write output to 00516 ); 00517 00518 /// Helper function to produce more detailed output 00519 std::string IfDetailed (std::string const & in_str); 00520 00521 ///\cond do not document 00522 const char * IndentStr( unsigned int in_numIndents ) 00523 { 00524 // static const char* INDENT=" + "; 00525 static const char* INDENT=" "; 00526 static const unsigned int LENGTH=static_cast<int>(strlen(INDENT)); 00527 unsigned int n=in_numIndents*NUM_INDENTS_PER_SPACE; 00528 if ( n > LENGTH ) n = LENGTH; 00529 00530 return &INDENT[ LENGTH-n ]; 00531 } 00532 00533 // same as getIndent but no "+" at the end 00534 const char * IndentEmptyStr( unsigned int in_numIndents ) 00535 { 00536 static const char* INDENT=" "; 00537 static const unsigned int LENGTH=static_cast<int>(strlen(INDENT)); 00538 unsigned int n=in_numIndents*NUM_INDENTS_PER_SPACE; 00539 if ( n > LENGTH ) n = LENGTH; 00540 00541 return &INDENT[ LENGTH-n ]; 00542 } 00543 ///\endcond 00544 00545 }; // class XmlCheck 00546 00547 ///\cond do not document 00548 __declspec(selectany) XmlCheck* XmlCheck::s_pXmlCheck = 0; 00549 __declspec(selectany) const unsigned int XmlCheck::NUM_INDENTS_PER_SPACE; 00550 ///\endcond 00551 00552 /// Access/create the singleton instance of PollingCheck 00553 XmlCheck* TheXmlCheck (void); 00554 00555 00556 //************************************************************************ 00557 //************************************************************************ 00558 00559 } // namespace TestToolBox 00560 00561 ///@} // end group GrpXmlCheck 00562