2023-12-01 17:32:19 +00:00
|
|
|
// Copyright 2023 Google LLC. All rights reserved.
|
|
|
|
//
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file or at
|
|
|
|
// https://developers.google.com/open-source/licenses/bsd
|
2014-04-12 01:23:20 +00:00
|
|
|
|
2023-12-01 17:32:19 +00:00
|
|
|
#include <packager/mpd/test/xml_compare.h>
|
2014-08-28 18:35:15 +00:00
|
|
|
|
2014-04-12 01:23:20 +00:00
|
|
|
#include <algorithm>
|
|
|
|
#include <map>
|
|
|
|
#include <string>
|
2014-06-26 01:40:40 +00:00
|
|
|
#include <utility>
|
2014-04-12 01:23:20 +00:00
|
|
|
|
2023-12-01 17:32:19 +00:00
|
|
|
#include <absl/log/check.h>
|
|
|
|
#include <absl/log/log.h>
|
|
|
|
#include <absl/strings/strip.h>
|
|
|
|
#include <libxml/parser.h>
|
|
|
|
#include <libxml/tree.h>
|
|
|
|
|
|
|
|
#include <packager/macros/logging.h>
|
2014-04-12 01:23:20 +00:00
|
|
|
|
2016-05-20 21:19:33 +00:00
|
|
|
namespace shaka {
|
2014-04-12 01:23:20 +00:00
|
|
|
|
|
|
|
namespace {
|
2015-11-17 00:06:17 +00:00
|
|
|
xml::scoped_xml_ptr<xmlDoc> GetDocFromString(const std::string& xml_str) {
|
2016-08-17 17:41:40 +00:00
|
|
|
return xml::scoped_xml_ptr<xmlDoc>(
|
|
|
|
xmlReadMemory(xml_str.data(), xml_str.size(), NULL, NULL, 0));
|
2014-04-12 01:23:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Make a map from attributes of the node.
|
|
|
|
std::map<std::string, std::string> GetMapOfAttributes(xmlNodePtr node) {
|
2014-06-26 01:40:40 +00:00
|
|
|
DVLOG(2) << "Getting attributes for node "
|
|
|
|
<< reinterpret_cast<const char*>(node->name);
|
2014-04-12 01:23:20 +00:00
|
|
|
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);
|
2015-11-17 00:06:17 +00:00
|
|
|
xml::scoped_xml_ptr<xmlChar> value(
|
2014-04-12 01:23:20 +00:00
|
|
|
xmlNodeListGetString(node->doc, attribute->children, 1));
|
|
|
|
|
|
|
|
attribute_map[name] = reinterpret_cast<const char*>(value.get());
|
2014-06-26 01:40:40 +00:00
|
|
|
DVLOG(3) << "In node " << reinterpret_cast<const char*>(node->name)
|
|
|
|
<< " found attribute " << name << " with value "
|
|
|
|
<< reinterpret_cast<const char*>(value.get());
|
2014-04-12 01:23:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return attribute_map;
|
|
|
|
}
|
|
|
|
|
2014-06-26 01:40:40 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-04-12 01:23:20 +00:00
|
|
|
bool MapsEqual(const std::map<std::string, std::string>& map1,
|
|
|
|
const std::map<std::string, std::string>& map2) {
|
|
|
|
return map1.size() == map2.size() &&
|
2014-06-26 01:40:40 +00:00
|
|
|
std::equal(map1.begin(), map1.end(), map2.begin(), MapCompareFunc);
|
2014-04-12 01:23:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2014-06-26 01:40:40 +00:00
|
|
|
DVLOG(2) << "Comparing " << reinterpret_cast<const char*>(node1->name)
|
|
|
|
<< " and " << reinterpret_cast<const char*>(node2->name);
|
2014-04-12 01:23:20 +00:00
|
|
|
return xmlStrcmp(node1->name, node2->name) == 0;
|
|
|
|
}
|
|
|
|
|
2015-07-15 21:57:47 +00:00
|
|
|
bool CompareContents(xmlNodePtr node1, xmlNodePtr node2) {
|
2015-11-17 00:06:17 +00:00
|
|
|
xml::scoped_xml_ptr<xmlChar> node1_content_ptr(xmlNodeGetContent(node1));
|
|
|
|
xml::scoped_xml_ptr<xmlChar> node2_content_ptr(xmlNodeGetContent(node2));
|
2015-07-15 21:57:47 +00:00
|
|
|
std::string node1_content =
|
2015-08-20 17:57:51 +00:00
|
|
|
reinterpret_cast<const char*>(node1_content_ptr.get());
|
2015-07-15 21:57:47 +00:00
|
|
|
std::string node2_content =
|
2015-08-20 17:57:51 +00:00
|
|
|
reinterpret_cast<const char*>(node2_content_ptr.get());
|
2023-12-01 17:32:19 +00:00
|
|
|
|
|
|
|
node1_content.erase(
|
|
|
|
std::remove(node1_content.begin(), node1_content.end(), '\n'),
|
|
|
|
node1_content.end());
|
|
|
|
node2_content.erase(
|
|
|
|
std::remove(node2_content.begin(), node2_content.end(), '\n'),
|
|
|
|
node2_content.end());
|
|
|
|
|
|
|
|
node1_content = absl::StripAsciiWhitespace(node1_content);
|
|
|
|
node2_content = absl::StripAsciiWhitespace(node2_content);
|
|
|
|
|
2015-07-15 21:57:47 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-04-12 01:23:20 +00:00
|
|
|
// 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);
|
2015-07-15 21:57:47 +00:00
|
|
|
if (!node1_child && !node2_child) {
|
|
|
|
// Note that xmlFirstElementChild() returns NULL if there are only
|
|
|
|
// text type children.
|
|
|
|
return CompareContents(node1, node2);
|
|
|
|
}
|
|
|
|
|
2014-04-12 01:23:20 +00:00
|
|
|
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) {
|
2015-11-17 00:06:17 +00:00
|
|
|
xml::scoped_xml_ptr<xmlDoc> xml1_doc(GetDocFromString(xml1));
|
|
|
|
xml::scoped_xml_ptr<xmlDoc> xml2_doc(GetDocFromString(xml2));
|
2020-11-10 00:32:58 +00:00
|
|
|
if (!xml1_doc || !xml2_doc) {
|
|
|
|
LOG(ERROR) << "xml1/xml2 is not valid XML.";
|
2014-04-12 01:23:20 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
xmlNodePtr xml1_root_element = xmlDocGetRootElement(xml1_doc.get());
|
|
|
|
xmlNodePtr xml2_root_element = xmlDocGetRootElement(xml2_doc.get());
|
2014-04-12 01:23:20 +00:00
|
|
|
if (!xml1_root_element || !xml2_root_element)
|
2020-11-10 00:32:58 +00:00
|
|
|
return false;
|
2014-04-12 01:23:20 +00:00
|
|
|
return CompareNodes(xml1_root_element, xml2_root_element);
|
|
|
|
}
|
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
bool XmlEqual(const std::string& xml1,
|
2023-12-01 17:32:19 +00:00
|
|
|
const std::optional<xml::XmlNode>& xml2) {
|
2020-11-10 00:32:58 +00:00
|
|
|
return xml2 && XmlEqual(xml1, *xml2);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool XmlEqual(const std::string& xml1, const xml::XmlNode& xml2) {
|
2017-12-14 06:33:47 +00:00
|
|
|
xml::scoped_xml_ptr<xmlDoc> xml1_doc(GetDocFromString(xml1));
|
|
|
|
if (!xml1_doc) {
|
2020-11-10 00:32:58 +00:00
|
|
|
LOG(ERROR) << "xml1 is not valid XML.";
|
2017-12-14 06:33:47 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
xmlNodePtr xml1_root_element = xmlDocGetRootElement(xml1_doc.get());
|
|
|
|
if (!xml1_root_element)
|
|
|
|
return false;
|
2020-11-10 00:32:58 +00:00
|
|
|
return CompareNodes(xml1_root_element, xml2.GetRawPtr());
|
|
|
|
}
|
|
|
|
|
2023-12-01 17:32:19 +00:00
|
|
|
std::string XmlNodeToString(const std::optional<xml::XmlNode>& xml_node) {
|
2020-11-10 00:32:58 +00:00
|
|
|
return xml_node ? XmlNodeToString(*xml_node) : "$ERROR$";
|
2017-12-14 06:33:47 +00:00
|
|
|
}
|
|
|
|
|
2020-11-10 00:32:58 +00:00
|
|
|
std::string XmlNodeToString(const xml::XmlNode& xml_node) {
|
|
|
|
std::string output = xml_node.ToString(/* comment= */ "");
|
2017-12-14 06:33:47 +00:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2016-05-20 21:19:33 +00:00
|
|
|
} // namespace shaka
|