#include "packager/mpd/test/xml_compare.h" #include #include #include #include #include #include #include "packager/base/logging.h" #include "packager/base/strings/string_util.h" namespace shaka { namespace { xml::scoped_xml_ptr GetDocFromString(const std::string& xml_str) { return xml::scoped_xml_ptr( xmlReadMemory(xml_str.data(), xml_str.size(), NULL, NULL, 0)); } // Make a map from attributes of the node. std::map GetMapOfAttributes(xmlNodePtr node) { DVLOG(2) << "Getting attributes for node " << reinterpret_cast(node->name); std::map attribute_map; for (xmlAttr* attribute = node->properties; attribute && attribute->name && attribute->children; attribute = attribute->next) { const char* name = reinterpret_cast(attribute->name); xml::scoped_xml_ptr value( xmlNodeListGetString(node->doc, attribute->children, 1)); attribute_map[name] = reinterpret_cast(value.get()); DVLOG(3) << "In node " << reinterpret_cast(node->name) << " found attribute " << name << " with value " << reinterpret_cast(value.get()); } return attribute_map; } bool MapCompareFunc(std::pair a, std::pair b) { if (a.first != b.first) { DLOG(INFO) << "Attribute mismatch " << a.first << " and " << b.first; return false; } if (a.second != b.second) { DLOG(INFO) << "Value mismatch for " << a.first << std::endl << "Values are " << a.second << " and " << b.second; return false; } return true; } bool MapsEqual(const std::map& map1, const std::map& map2) { return map1.size() == map2.size() && std::equal(map1.begin(), map1.end(), map2.begin(), MapCompareFunc); } // Return true if the nodes have the same attributes. bool CompareAttributes(xmlNodePtr node1, xmlNodePtr node2) { return MapsEqual(GetMapOfAttributes(node1), GetMapOfAttributes(node2)); } // Return true if the name of the nodes match. bool CompareNames(xmlNodePtr node1, xmlNodePtr node2) { DVLOG(2) << "Comparing " << reinterpret_cast(node1->name) << " and " << reinterpret_cast(node2->name); return xmlStrcmp(node1->name, node2->name) == 0; } bool CompareContents(xmlNodePtr node1, xmlNodePtr node2) { xml::scoped_xml_ptr node1_content_ptr(xmlNodeGetContent(node1)); xml::scoped_xml_ptr node2_content_ptr(xmlNodeGetContent(node2)); std::string node1_content = reinterpret_cast(node1_content_ptr.get()); std::string node2_content = reinterpret_cast(node2_content_ptr.get()); base::ReplaceChars(node1_content, "\n", "", &node1_content); base::TrimString(node1_content, " ", &node1_content); base::ReplaceChars(node2_content, "\n", "", &node2_content); base::TrimString(node2_content, " ", &node2_content); DVLOG(2) << "Comparing contents of " << reinterpret_cast(node1->name) << "\n" << "First node's content:\n" << node1_content << "\n" << "Second node's content:\n" << node2_content; const bool same_content = node1_content == node2_content; LOG_IF(ERROR, !same_content) << "Contents of " << reinterpret_cast(node1->name) << " do not match.\n" << "First node's content:\n" << node1_content << "\n" << "Second node's content:\n" << node2_content; return same_content; } // Recursively check the elements. // Note that the terminating condition of the recursion is when the children do // not match (inside the loop). bool CompareNodes(xmlNodePtr node1, xmlNodePtr node2) { DCHECK(node1 && node2); if (!CompareNames(node1, node2)) { LOG(ERROR) << "Names of the nodes do not match: " << reinterpret_cast(node1->name) << " " << reinterpret_cast(node2->name); return false; } if (!CompareAttributes(node1, node2)) { LOG(ERROR) << "Attributes of element " << reinterpret_cast(node1->name) << " do not match."; return false; } xmlNodePtr node1_child = xmlFirstElementChild(node1); xmlNodePtr node2_child = xmlFirstElementChild(node2); if (!node1_child && !node2_child) { // Note that xmlFirstElementChild() returns NULL if there are only // text type children. return CompareContents(node1, node2); } do { if (!node1_child || !node2_child) return node1_child == node2_child; DCHECK(node1_child && node2_child); if (!CompareNodes(node1_child, node2_child)) return false; node1_child = xmlNextElementSibling(node1_child); node2_child = xmlNextElementSibling(node2_child); } while (node1_child || node2_child); // Both nodes have equivalent descendents. return true; } } // namespace bool XmlEqual(const std::string& xml1, const std::string& xml2) { xml::scoped_xml_ptr xml1_doc(GetDocFromString(xml1)); xml::scoped_xml_ptr xml2_doc(GetDocFromString(xml2)); return XmlEqual(xml1_doc.get(), xml2_doc.get()); } bool XmlEqual(const std::string& xml1, xmlDocPtr xml2) { xml::scoped_xml_ptr xml1_doc(GetDocFromString(xml1)); return XmlEqual(xml1_doc.get(), xml2); } bool XmlEqual(xmlDocPtr xml1, xmlDocPtr xml2) { if (!xml1 || !xml2) { LOG(ERROR) << "xml1 and/or xml2 are not valid XML."; return false; } xmlNodePtr xml1_root_element = xmlDocGetRootElement(xml1); xmlNodePtr xml2_root_element = xmlDocGetRootElement(xml2); if (!xml1_root_element || !xml2_root_element) return xml1_root_element == xml2_root_element; return CompareNodes(xml1_root_element, xml2_root_element); } bool XmlEqual(const std::string& xml1, xmlNodePtr xml2) { xml::scoped_xml_ptr xml1_doc(GetDocFromString(xml1)); if (!xml1_doc) { LOG(ERROR) << "xml1 are not valid XML."; return false; } xmlNodePtr xml1_root_element = xmlDocGetRootElement(xml1_doc.get()); if (!xml1_root_element) return false; return CompareNodes(xml1_root_element, xml2); } std::string XmlNodeToString(xmlNodePtr xml_node) { // Create an xmlDoc from xmlNodePtr. The node is copied so ownership does not // transfer. xml::scoped_xml_ptr doc(xmlNewDoc(BAD_CAST "")); xmlDocSetRootElement(doc.get(), xmlCopyNode(xml_node, true)); // Format the xmlDoc to string. static const int kNiceFormat = 1; int doc_str_size = 0; xmlChar* doc_str = nullptr; xmlDocDumpFormatMemoryEnc(doc.get(), &doc_str, &doc_str_size, "UTF-8", kNiceFormat); std::string output(doc_str, doc_str + doc_str_size); xmlFree(doc_str); // Remove the first line from the formatted string: // const size_t first_newline_char_pos = output.find('\n'); DCHECK_NE(first_newline_char_pos, std::string::npos); return output.substr(first_newline_char_pos + 1); } } // namespace shaka