//===-- XML.cpp -----------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "lldb/Host/Config.h" #include "lldb/Host/XML.h" #include "llvm/ADT/StringExtras.h" using namespace lldb; using namespace lldb_private; #pragma mark-- XMLDocument XMLDocument::XMLDocument() = default; XMLDocument::~XMLDocument() { Clear(); } void XMLDocument::Clear() { #if LLDB_ENABLE_LIBXML2 if (m_document) { xmlDocPtr doc = m_document; m_document = nullptr; xmlFreeDoc(doc); } #endif } bool XMLDocument::IsValid() const { return m_document != nullptr; } void XMLDocument::ErrorCallback(void *ctx, const char *format, ...) { XMLDocument *document = (XMLDocument *)ctx; va_list args; va_start(args, format); document->m_errors.PrintfVarArg(format, args); document->m_errors.EOL(); va_end(args); } bool XMLDocument::ParseFile(const char *path) { #if LLDB_ENABLE_LIBXML2 Clear(); xmlSetGenericErrorFunc((void *)this, XMLDocument::ErrorCallback); m_document = xmlParseFile(path); xmlSetGenericErrorFunc(nullptr, nullptr); #endif return IsValid(); } bool XMLDocument::ParseMemory(const char *xml, size_t xml_length, const char *url) { #if LLDB_ENABLE_LIBXML2 Clear(); xmlSetGenericErrorFunc((void *)this, XMLDocument::ErrorCallback); m_document = xmlReadMemory(xml, (int)xml_length, url, nullptr, 0); xmlSetGenericErrorFunc(nullptr, nullptr); #endif return IsValid(); } XMLNode XMLDocument::GetRootElement(const char *required_name) { #if LLDB_ENABLE_LIBXML2 if (IsValid()) { XMLNode root_node(xmlDocGetRootElement(m_document)); if (required_name) { llvm::StringRef actual_name = root_node.GetName(); if (actual_name == required_name) return root_node; } else { return root_node; } } #endif return XMLNode(); } llvm::StringRef XMLDocument::GetErrors() const { return m_errors.GetString(); } bool XMLDocument::XMLEnabled() { #if LLDB_ENABLE_LIBXML2 return true; #else return false; #endif } #pragma mark-- XMLNode XMLNode::XMLNode() = default; XMLNode::XMLNode(XMLNodeImpl node) : m_node(node) {} XMLNode::~XMLNode() = default; void XMLNode::Clear() { m_node = nullptr; } XMLNode XMLNode::GetParent() const { #if LLDB_ENABLE_LIBXML2 if (IsValid()) return XMLNode(m_node->parent); else return XMLNode(); #else return XMLNode(); #endif } XMLNode XMLNode::GetSibling() const { #if LLDB_ENABLE_LIBXML2 if (IsValid()) return XMLNode(m_node->next); else return XMLNode(); #else return XMLNode(); #endif } XMLNode XMLNode::GetChild() const { #if LLDB_ENABLE_LIBXML2 if (IsValid()) return XMLNode(m_node->children); else return XMLNode(); #else return XMLNode(); #endif } std::string XMLNode::GetAttributeValue(const char *name, const char *fail_value) const { std::string attr_value; #if LLDB_ENABLE_LIBXML2 if (IsValid()) { xmlChar *value = xmlGetProp(m_node, (const xmlChar *)name); if (value) { attr_value = (const char *)value; xmlFree(value); } } else { if (fail_value) attr_value = fail_value; } #else if (fail_value) attr_value = fail_value; #endif return attr_value; } bool XMLNode::GetAttributeValueAsUnsigned(const char *name, uint64_t &value, uint64_t fail_value, int base) const { value = fail_value; return llvm::to_integer(GetAttributeValue(name, ""), value, base); } void XMLNode::ForEachChildNode(NodeCallback const &callback) const { #if LLDB_ENABLE_LIBXML2 if (IsValid()) GetChild().ForEachSiblingNode(callback); #endif } void XMLNode::ForEachChildElement(NodeCallback const &callback) const { #if LLDB_ENABLE_LIBXML2 XMLNode child = GetChild(); if (child) child.ForEachSiblingElement(callback); #endif } void XMLNode::ForEachChildElementWithName(const char *name, NodeCallback const &callback) const { #if LLDB_ENABLE_LIBXML2 XMLNode child = GetChild(); if (child) child.ForEachSiblingElementWithName(name, callback); #endif } void XMLNode::ForEachAttribute(AttributeCallback const &callback) const { #if LLDB_ENABLE_LIBXML2 if (IsValid()) { for (xmlAttrPtr attr = m_node->properties; attr != nullptr; attr = attr->next) { // check if name matches if (attr->name) { // check child is a text node xmlNodePtr child = attr->children; if (child->type == XML_TEXT_NODE) { llvm::StringRef attr_value; if (child->content) attr_value = llvm::StringRef((const char *)child->content); if (!callback(llvm::StringRef((const char *)attr->name), attr_value)) return; } } } } #endif } void XMLNode::ForEachSiblingNode(NodeCallback const &callback) const { #if LLDB_ENABLE_LIBXML2 if (IsValid()) { // iterate through all siblings for (xmlNodePtr node = m_node; node; node = node->next) { if (!callback(XMLNode(node))) return; } } #endif } void XMLNode::ForEachSiblingElement(NodeCallback const &callback) const { #if LLDB_ENABLE_LIBXML2 if (IsValid()) { // iterate through all siblings for (xmlNodePtr node = m_node; node; node = node->next) { // we are looking for element nodes only if (node->type != XML_ELEMENT_NODE) continue; if (!callback(XMLNode(node))) return; } } #endif } void XMLNode::ForEachSiblingElementWithName( const char *name, NodeCallback const &callback) const { #if LLDB_ENABLE_LIBXML2 if (IsValid()) { // iterate through all siblings for (xmlNodePtr node = m_node; node; node = node->next) { // we are looking for element nodes only if (node->type != XML_ELEMENT_NODE) continue; // If name is nullptr, we take all nodes of type "t", else just the ones // whose name matches if (name) { if (strcmp((const char *)node->name, name) != 0) continue; // Name mismatch, ignore this one } else { if (node->name) continue; // nullptr name specified and this element has a name, // ignore this one } if (!callback(XMLNode(node))) return; } } #endif } llvm::StringRef XMLNode::GetName() const { #if LLDB_ENABLE_LIBXML2 if (IsValid()) { if (m_node->name) return llvm::StringRef((const char *)m_node->name); } #endif return llvm::StringRef(); } bool XMLNode::GetElementText(std::string &text) const { text.clear(); #if LLDB_ENABLE_LIBXML2 if (IsValid()) { bool success = false; if (m_node->type == XML_ELEMENT_NODE) { // check child is a text node for (xmlNodePtr node = m_node->children; node != nullptr; node = node->next) { if (node->type == XML_TEXT_NODE) { text.append((const char *)node->content); success = true; } } } return success; } #endif return false; } bool XMLNode::GetElementTextAsUnsigned(uint64_t &value, uint64_t fail_value, int base) const { std::string text; value = fail_value; return GetElementText(text) && llvm::to_integer(text, value, base); } bool XMLNode::GetElementTextAsFloat(double &value, double fail_value) const { std::string text; value = fail_value; return GetElementText(text) && llvm::to_float(text, value); } bool XMLNode::NameIs(const char *name) const { #if LLDB_ENABLE_LIBXML2 if (IsValid()) { // In case we are looking for a nullptr name or an exact pointer match if (m_node->name == (const xmlChar *)name) return true; if (m_node->name) return strcmp((const char *)m_node->name, name) == 0; } #endif return false; } XMLNode XMLNode::FindFirstChildElementWithName(const char *name) const { XMLNode result_node; #if LLDB_ENABLE_LIBXML2 ForEachChildElementWithName( name, [&result_node](const XMLNode &node) -> bool { result_node = node; // Stop iterating, we found the node we wanted return false; }); #endif return result_node; } bool XMLNode::IsValid() const { return m_node != nullptr; } bool XMLNode::IsElement() const { #if LLDB_ENABLE_LIBXML2 if (IsValid()) return m_node->type == XML_ELEMENT_NODE; #endif return false; } XMLNode XMLNode::GetElementForPath(const NamePath &path) { #if LLDB_ENABLE_LIBXML2 if (IsValid()) { if (path.empty()) return *this; else { XMLNode node = FindFirstChildElementWithName(path[0].c_str()); const size_t n = path.size(); for (size_t i = 1; node && i < n; ++i) node = node.FindFirstChildElementWithName(path[i].c_str()); return node; } } #endif return XMLNode(); } #pragma mark-- ApplePropertyList ApplePropertyList::ApplePropertyList() : m_xml_doc(), m_dict_node() {} ApplePropertyList::ApplePropertyList(const char *path) : m_xml_doc(), m_dict_node() { ParseFile(path); } ApplePropertyList::~ApplePropertyList() = default; llvm::StringRef ApplePropertyList::GetErrors() const { return m_xml_doc.GetErrors(); } bool ApplePropertyList::ParseFile(const char *path) { if (m_xml_doc.ParseFile(path)) { XMLNode plist = m_xml_doc.GetRootElement("plist"); if (plist) { plist.ForEachChildElementWithName("dict", [this](const XMLNode &dict) -> bool { this->m_dict_node = dict; return false; // Stop iterating }); return (bool)m_dict_node; } } return false; } bool ApplePropertyList::IsValid() const { return (bool)m_dict_node; } bool ApplePropertyList::GetValueAsString(const char *key, std::string &value) const { XMLNode value_node = GetValueNode(key); if (value_node) return ApplePropertyList::ExtractStringFromValueNode(value_node, value); return false; } XMLNode ApplePropertyList::GetValueNode(const char *key) const { XMLNode value_node; #if LLDB_ENABLE_LIBXML2 if (IsValid()) { m_dict_node.ForEachChildElementWithName( "key", [key, &value_node](const XMLNode &key_node) -> bool { std::string key_name; if (key_node.GetElementText(key_name)) { if (key_name == key) { value_node = key_node.GetSibling(); while (value_node && !value_node.IsElement()) value_node = value_node.GetSibling(); return false; // Stop iterating } } return true; // Keep iterating }); } #endif return value_node; } bool ApplePropertyList::ExtractStringFromValueNode(const XMLNode &node, std::string &value) { value.clear(); #if LLDB_ENABLE_LIBXML2 if (node.IsValid()) { llvm::StringRef element_name = node.GetName(); if (element_name == "true" || element_name == "false") { // The text value _is_ the element name itself... value = element_name.str(); return true; } else if (element_name == "dict" || element_name == "array") return false; // dictionaries and arrays have no text value, so we fail else return node.GetElementText(value); } #endif return false; } #if LLDB_ENABLE_LIBXML2 static StructuredData::ObjectSP CreatePlistValue(XMLNode node) { llvm::StringRef element_name = node.GetName(); if (element_name == "array") { std::shared_ptr array_sp( new StructuredData::Array()); node.ForEachChildElement([&array_sp](const XMLNode &node) -> bool { array_sp->AddItem(CreatePlistValue(node)); return true; // Keep iterating through all child elements of the array }); return array_sp; } else if (element_name == "dict") { XMLNode key_node; std::shared_ptr dict_sp( new StructuredData::Dictionary()); node.ForEachChildElement( [&key_node, &dict_sp](const XMLNode &node) -> bool { if (node.NameIs("key")) { // This is a "key" element node key_node = node; } else { // This is a value node if (key_node) { std::string key_name; key_node.GetElementText(key_name); dict_sp->AddItem(key_name, CreatePlistValue(node)); key_node.Clear(); } } return true; // Keep iterating through all child elements of the // dictionary }); return dict_sp; } else if (element_name == "real") { double value = 0.0; node.GetElementTextAsFloat(value); return StructuredData::ObjectSP(new StructuredData::Float(value)); } else if (element_name == "integer") { uint64_t value = 0; node.GetElementTextAsUnsigned(value, 0, 0); return StructuredData::ObjectSP(new StructuredData::UnsignedInteger(value)); } else if ((element_name == "string") || (element_name == "data") || (element_name == "date")) { std::string text; node.GetElementText(text); return StructuredData::ObjectSP( new StructuredData::String(std::move(text))); } else if (element_name == "true") { return StructuredData::ObjectSP(new StructuredData::Boolean(true)); } else if (element_name == "false") { return StructuredData::ObjectSP(new StructuredData::Boolean(false)); } return StructuredData::ObjectSP(new StructuredData::Null()); } #endif StructuredData::ObjectSP ApplePropertyList::GetStructuredData() { StructuredData::ObjectSP root_sp; #if LLDB_ENABLE_LIBXML2 if (IsValid()) { return CreatePlistValue(m_dict_node); } #endif return root_sp; }