204 lines
7.1 KiB
C++
204 lines
7.1 KiB
C++
#include "packager/mpd/test/xml_compare.h"
|
|
|
|
#include <libxml/parser.h>
|
|
#include <libxml/tree.h>
|
|
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#include "packager/base/logging.h"
|
|
#include "packager/base/strings/string_util.h"
|
|
|
|
namespace shaka {
|
|
|
|
namespace {
|
|
xml::scoped_xml_ptr<xmlDoc> GetDocFromString(const std::string& xml_str) {
|
|
return xml::scoped_xml_ptr<xmlDoc>(
|
|
xmlReadMemory(xml_str.data(), xml_str.size(), NULL, NULL, 0));
|
|
}
|
|
|
|
// Make a map from attributes of the node.
|
|
std::map<std::string, std::string> GetMapOfAttributes(xmlNodePtr node) {
|
|
DVLOG(2) << "Getting attributes for node "
|
|
<< reinterpret_cast<const char*>(node->name);
|
|
std::map<std::string, std::string> attribute_map;
|
|
for (xmlAttr* attribute = node->properties;
|
|
attribute && attribute->name && attribute->children;
|
|
attribute = attribute->next) {
|
|
const char* name = reinterpret_cast<const char*>(attribute->name);
|
|
xml::scoped_xml_ptr<xmlChar> value(
|
|
xmlNodeListGetString(node->doc, attribute->children, 1));
|
|
|
|
attribute_map[name] = reinterpret_cast<const char*>(value.get());
|
|
DVLOG(3) << "In node " << reinterpret_cast<const char*>(node->name)
|
|
<< " found attribute " << name << " with value "
|
|
<< reinterpret_cast<const char*>(value.get());
|
|
}
|
|
|
|
return attribute_map;
|
|
}
|
|
|
|
bool MapCompareFunc(std::pair<std::string, std::string> a,
|
|
std::pair<std::string, std::string> 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<std::string, std::string>& map1,
|
|
const std::map<std::string, std::string>& 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<const char*>(node1->name)
|
|
<< " and " << reinterpret_cast<const char*>(node2->name);
|
|
return xmlStrcmp(node1->name, node2->name) == 0;
|
|
}
|
|
|
|
bool CompareContents(xmlNodePtr node1, xmlNodePtr node2) {
|
|
xml::scoped_xml_ptr<xmlChar> node1_content_ptr(xmlNodeGetContent(node1));
|
|
xml::scoped_xml_ptr<xmlChar> node2_content_ptr(xmlNodeGetContent(node2));
|
|
std::string node1_content =
|
|
reinterpret_cast<const char*>(node1_content_ptr.get());
|
|
std::string node2_content =
|
|
reinterpret_cast<const char*>(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<const char*>(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<const char*>(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<const char*>(node1->name) << " "
|
|
<< reinterpret_cast<const char*>(node2->name);
|
|
return false;
|
|
}
|
|
|
|
if (!CompareAttributes(node1, node2)) {
|
|
LOG(ERROR) << "Attributes of element "
|
|
<< reinterpret_cast<const char*>(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<xmlDoc> xml1_doc(GetDocFromString(xml1));
|
|
xml::scoped_xml_ptr<xmlDoc> xml2_doc(GetDocFromString(xml2));
|
|
return XmlEqual(xml1_doc.get(), xml2_doc.get());
|
|
}
|
|
|
|
bool XmlEqual(const std::string& xml1, xmlDocPtr xml2) {
|
|
xml::scoped_xml_ptr<xmlDoc> 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<xmlDoc> 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<xmlDoc> 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:
|
|
// <?xml version="" encoding="UTF-8"?>
|
|
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
|