/mordor/tests/xml.cpp

http://github.com/mozy/mordor · C++ · 311 lines · 262 code · 39 blank · 10 comment · 14 complexity · c050bf4a8aa465c8895547cecd2ec73e MD5 · raw file

  1. // Copyright (c) 2010 - Mozy, Inc.
  2. #include <boost/bind.hpp>
  3. #include "mordor/xml/dom_parser.h"
  4. #include "mordor/test/test.h"
  5. #include "mordor/string.h"
  6. using namespace Mordor;
  7. static void callback(std::string &value, int &called,
  8. const std::string &string)
  9. {
  10. value.append(string);
  11. ++called;
  12. }
  13. static void reference(std::string &value, int &called,
  14. const std::string &string)
  15. {
  16. // When processing inner text a special callback is invoked for xml
  17. // references
  18. if (string == "&amp;")
  19. value.append("&");
  20. else if (string == "&gt;")
  21. value.append(">");
  22. else if (string == "&lt;")
  23. value.append("<");
  24. else if (string == "&quot;")
  25. value.append("\"");
  26. else if (string == "&apos;")
  27. value.append("\'");
  28. else
  29. // Real code should also look for character references like "&#38;"
  30. MORDOR_NOTREACHED();
  31. ++called;
  32. }
  33. static void handlereferencecallback(std::string &value, int &called,
  34. const std::string &string)
  35. {
  36. // A full Attribute value is passed, which may contain xml references
  37. std::string rawstring(string);
  38. replace(rawstring, "&amp;", "&");
  39. replace(rawstring, "&gt;", ">" );
  40. replace(rawstring, "&lt;", "<" );
  41. replace(rawstring, "&quot;", "\"");
  42. replace(rawstring, "&apos;", "\'");
  43. // Should also look for character references like "&#38;"
  44. value.append(rawstring);
  45. ++called;
  46. }
  47. static void emptyTag(int &called)
  48. {
  49. ++called;
  50. }
  51. MORDOR_UNITTEST(XMLParser, basic)
  52. {
  53. std::string start, end;
  54. int calledStart = 0, calledEnd = 0;
  55. CallbackXMLParserEventHandler handler(
  56. boost::bind(&callback, boost::ref(start), boost::ref(calledStart), _1),
  57. boost::bind(&callback, boost::ref(end), boost::ref(calledEnd), _1));
  58. XMLParser parser(handler);
  59. parser.run("<body></body>");
  60. MORDOR_ASSERT(parser.final());
  61. MORDOR_ASSERT(!parser.error());
  62. MORDOR_TEST_ASSERT_EQUAL(calledStart, 1);
  63. MORDOR_TEST_ASSERT_EQUAL(start, "body");
  64. MORDOR_TEST_ASSERT_EQUAL(calledEnd, 1);
  65. MORDOR_TEST_ASSERT_EQUAL(end, "body");
  66. }
  67. MORDOR_UNITTEST(XMLParser, emptyTag)
  68. {
  69. std::string tag;
  70. int calledStart = 0, calledEmpty = 0;
  71. CallbackXMLParserEventHandler handler(
  72. boost::bind(&callback, boost::ref(tag), boost::ref(calledStart), _1),
  73. NULL,
  74. boost::bind(&emptyTag, boost::ref(calledEmpty)));
  75. XMLParser parser(handler);
  76. parser.run("<empty />");
  77. MORDOR_ASSERT(parser.final());
  78. MORDOR_ASSERT(!parser.error());
  79. MORDOR_TEST_ASSERT_EQUAL(calledStart, 1);
  80. MORDOR_TEST_ASSERT_EQUAL(tag, "empty");
  81. MORDOR_TEST_ASSERT_EQUAL(calledEmpty, 1);
  82. }
  83. MORDOR_UNITTEST(XMLParser, references)
  84. {
  85. std::string text;
  86. int called = 0;
  87. CallbackXMLParserEventHandler handler(NULL,
  88. NULL,
  89. NULL,
  90. NULL,
  91. NULL,
  92. boost::bind(&callback, boost::ref(text), boost::ref(called), _1),
  93. boost::bind(&reference, boost::ref(text), boost::ref(called), _1));
  94. XMLParser parser(handler);
  95. parser.run("<root>sometext&amp;somemoretext</root>");
  96. MORDOR_ASSERT(parser.final());
  97. MORDOR_ASSERT(!parser.error());
  98. MORDOR_TEST_ASSERT_EQUAL(called, 3);
  99. MORDOR_TEST_ASSERT_EQUAL(text, "sometext&somemoretext");
  100. text.clear();
  101. called = 0;
  102. parser.run("<path>/Users/test1/Public/&lt;C:\\abc\\n&apos;&amp;.txt1</path>");
  103. MORDOR_ASSERT(parser.final());
  104. MORDOR_ASSERT(!parser.error());
  105. MORDOR_TEST_ASSERT_EQUAL(called, 6);
  106. MORDOR_TEST_ASSERT_EQUAL(text, "/Users/test1/Public/<C:\\abc\\n'&.txt1");
  107. text.clear();
  108. called = 0;
  109. parser.run("<p>&quot;&amp;</p>");
  110. MORDOR_ASSERT(parser.final());
  111. MORDOR_ASSERT(!parser.error());
  112. MORDOR_TEST_ASSERT_EQUAL(called, 2);
  113. MORDOR_TEST_ASSERT_EQUAL(text, "\"&");
  114. }
  115. MORDOR_UNITTEST(XMLParser, attribute)
  116. {
  117. std::string key, value;
  118. int calledKey = 0, calledVal = 0;
  119. CallbackXMLParserEventHandler handler(
  120. NULL,
  121. NULL,
  122. NULL,
  123. boost::bind(&callback, boost::ref(key), boost::ref(calledKey), _1),
  124. boost::bind(&handlereferencecallback, boost::ref(value), boost::ref(calledVal), _1));
  125. XMLParser parser(handler);
  126. parser.run("<mykey query=\"mymail\"/>");
  127. MORDOR_ASSERT(parser.final());
  128. MORDOR_ASSERT(!parser.error());
  129. MORDOR_TEST_ASSERT_EQUAL(calledKey, 1);
  130. MORDOR_TEST_ASSERT_EQUAL(key, "query");
  131. MORDOR_TEST_ASSERT_EQUAL(calledVal, 1);
  132. MORDOR_TEST_ASSERT_EQUAL(value, "mymail");
  133. key.clear(); value.clear();
  134. calledKey = 0; calledVal = 0;
  135. parser.run("<mykey qry=\"&quot;mymail&apos;\"/>");
  136. MORDOR_ASSERT(parser.final());
  137. MORDOR_ASSERT(!parser.error());
  138. MORDOR_TEST_ASSERT_EQUAL(calledKey, 1);
  139. MORDOR_TEST_ASSERT_EQUAL(key, "qry");
  140. MORDOR_TEST_ASSERT_EQUAL(calledVal, 1);
  141. MORDOR_TEST_ASSERT_EQUAL(value, "\"mymail\'");
  142. key.clear(); value.clear();
  143. calledKey = 0; calledVal = 0;
  144. parser.run("<mykey a=\'&quot;\"\' b=\"foo's\"/>");
  145. MORDOR_ASSERT(parser.final());
  146. MORDOR_ASSERT(!parser.error());
  147. MORDOR_TEST_ASSERT_EQUAL(calledKey, 2);
  148. MORDOR_TEST_ASSERT_EQUAL(key, "ab"); // The test callback concatenates them together
  149. MORDOR_TEST_ASSERT_EQUAL(calledVal, 2);
  150. MORDOR_TEST_ASSERT_EQUAL(value, "\"\"foo's");
  151. // A more complex real life XML
  152. key.clear(); value.clear();
  153. parser.run("<folder id=\"3\" i4ms=\"692002\" ms=\"456229\" name=\"Trash\" n=\"38\" l=\"1\"><search id=\"384010\" sortBy=\"dateDesc\" query=\"(to:(foo) tag:&quot;mymail&quot;\" name=\"inbox my mail\" l=\"3\" /></folder>");
  154. MORDOR_ASSERT(parser.final());
  155. MORDOR_ASSERT(!parser.error());
  156. MORDOR_ASSERT(!key.empty());
  157. MORDOR_ASSERT(!value.empty());
  158. }
  159. const char * xml_typical =
  160. "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\t"
  161. "<NamedObject type='file' deleted='false'>\n"
  162. " <id>/sync/1/path/x.y.txt</id>"
  163. " <versionId>1310496040</versionId>"
  164. "\t<Version deleted='false'>"
  165. "\t\t<versionId>1310496040</versionId>"
  166. "<size>8</size>"
  167. "<stime>Tue, 12 Jul 2011 18:40:40 GMT</stime>\r\n"
  168. "<fileAttributes archive='true' not_content_indexed='true' />"
  169. "</Version>"
  170. "</NamedObject>";
  171. MORDOR_UNITTEST(XMLParser, parsedocument)
  172. {
  173. // Confirm that a full xml with random white space and
  174. // typical elements can be parsed.
  175. XMLParserEventHandler defaulthandler;
  176. XMLParser parser(defaulthandler);
  177. parser.run(xml_typical);
  178. MORDOR_ASSERT(parser.final());
  179. MORDOR_ASSERT(!parser.error());
  180. }
  181. MORDOR_UNITTEST(XMLParser, domParser)
  182. {
  183. DOM::XMLParser parser;
  184. DOM::Document::ptr doc = parser.loadDocument(
  185. "<?xml version='1.0' encoding='UTF-8'?>\n"
  186. "<root><children>\n"
  187. "<child> this is child1\n</child>\n"
  188. "<child>this is child2</child>\n"
  189. "<empty id=\"1\" attr1=\"one\" attr2='two' />\n"
  190. "<tag>tag text<subtag> &quot;some &amp; some&quot; </subtag></tag>\n"
  191. "</children></root>");
  192. DOM::Element *root = (DOM::Element *)doc->getElementsByTagName("root")[0];
  193. MORDOR_TEST_ASSERT_EQUAL("root", root->nodeName());
  194. DOM::NodeList l = root->getElementsByTagName("child");
  195. MORDOR_TEST_ASSERT_EQUAL(2u, l.size());
  196. MORDOR_TEST_ASSERT_EQUAL("this is child1", l[0]->text());
  197. MORDOR_TEST_ASSERT_EQUAL("this is child1", l[0]->firstChild()->nodeValue());
  198. MORDOR_TEST_ASSERT_EQUAL("this is child2", l[1]->text());
  199. DOM::Node *children = root->firstChild();
  200. MORDOR_TEST_ASSERT_EQUAL(DOM::ELEMENT, children->nodeType());
  201. MORDOR_TEST_ASSERT_EQUAL("children", children->nodeName());
  202. MORDOR_TEST_ASSERT_EQUAL(4u, children->childNodes().size());
  203. DOM::Element *empty = (DOM::Element *)children->childNodes()[2];
  204. MORDOR_TEST_ASSERT_EQUAL("empty", empty->nodeName());
  205. MORDOR_TEST_ASSERT_EQUAL(DOM::ELEMENT, empty->nodeType());
  206. MORDOR_TEST_ASSERT_EQUAL("", empty->text());
  207. MORDOR_TEST_ASSERT_EQUAL("1", empty->id);
  208. MORDOR_TEST_ASSERT_EQUAL("one", empty->attribute("attr1"));
  209. MORDOR_TEST_ASSERT_EQUAL("two", empty->attribute("attr2"));
  210. MORDOR_TEST_ASSERT_EQUAL(true, empty->hasAttribute("attr2"));
  211. MORDOR_TEST_ASSERT_EQUAL(false, empty->hasAttribute("attr3"));
  212. DOM::Element *node = doc->getElementById("1");
  213. MORDOR_TEST_ASSERT_EQUAL(empty, node);
  214. MORDOR_TEST_ASSERT_EQUAL((DOM::Element *)NULL, doc->getElementById("haha"));
  215. DOM::Node *tag = children->childNodes()[3];
  216. MORDOR_TEST_ASSERT_EQUAL(DOM::ELEMENT, tag->nodeType());
  217. MORDOR_TEST_ASSERT_EQUAL("tag", tag->nodeName());
  218. MORDOR_TEST_ASSERT_EQUAL("tag text", tag->text());
  219. MORDOR_TEST_ASSERT_EQUAL("tag text", tag->firstChild()->nodeValue());
  220. DOM::NodeList subtags = ((DOM::Element *)tag)->getElementsByTagName("subtag");
  221. MORDOR_TEST_ASSERT_EQUAL(1u, subtags.size());
  222. MORDOR_TEST_ASSERT_EQUAL("\"some & some\"", subtags[0]->text());
  223. }
  224. MORDOR_UNITTEST(XMLParser, domParserInvalid)
  225. {
  226. DOM::XMLParser parser;
  227. MORDOR_TEST_ASSERT_EXCEPTION(parser.loadDocument(
  228. "<?xml version='1.0' encoding='UTF-8'?>\n"
  229. "<root<children>\"somesome\n"),
  230. std::invalid_argument);
  231. }
  232. MORDOR_UNITTEST(XMLParser, domParserUnmatchedEndTag)
  233. {
  234. DOM::XMLParser parser;
  235. MORDOR_TEST_ASSERT_EXCEPTION(parser.loadDocument(
  236. "<?xml version='1.0' encoding='UTF-8'?>\n"
  237. "<hello></goodbye>"),
  238. std::invalid_argument);
  239. MORDOR_TEST_ASSERT_EXCEPTION(parser.loadDocument(
  240. "<?xml version='1.0' encoding='UTF-8'?>\n"
  241. "<hello>\n"
  242. " <bonjour>\n"
  243. " </ciao>\n"
  244. "</goodbye>"),
  245. std::invalid_argument);
  246. }
  247. MORDOR_UNITTEST(XMLParser, domParserReferenceError)
  248. {
  249. const std::string xmlstr =
  250. "<Object>&#x394;&#x3BF;&#x3BA;&#x3B9;&#x3BC;&#x3AE;&#931;</Object>";
  251. DOM::XMLParser parser;
  252. DOM::Document::ptr doc = parser.loadDocument(xmlstr);
  253. DOM::Node *obj = doc->getElementsByTagName("Object")[0];
  254. MORDOR_TEST_ASSERT_EQUAL(obj->text(), "&#x394;&#x3BF;&#x3BA;&#x3B9;&#x3BC;&#x3AE;&#931;");
  255. }
  256. #if 0
  257. // Disabled until comment support fixed
  258. MORDOR_UNITTEST(XMLParser, parsecomment)
  259. {
  260. XMLParserEventHandler defaulthandler;
  261. XMLParser parser(defaulthandler);
  262. parser.run("<!-- a comment -->");
  263. MORDOR_ASSERT(parser.final());
  264. MORDOR_ASSERT(!parser.error());
  265. parser.run("foo <!-- bar --> baz <!-- qox --> blurb");
  266. MORDOR_ASSERT(parser.final());
  267. MORDOR_ASSERT(!parser.error());
  268. parser.run("<![CDATA[ <!-- OMGWTFBBQ ]]>Shoulda used a <!-- real parser -->");
  269. MORDOR_ASSERT(parser.final());
  270. MORDOR_ASSERT(!parser.error());
  271. }
  272. #endif