1//===-- XML.cpp -----------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "lldb/Host/Config.h"
10#include "lldb/Host/XML.h"
11
12#include "llvm/ADT/StringExtras.h"
13
14using namespace lldb;
15using namespace lldb_private;
16
17#pragma mark-- XMLDocument
18
19XMLDocument::XMLDocument() = default;
20
21XMLDocument::~XMLDocument() { Clear(); }
22
23void XMLDocument::Clear() {
24#if LLDB_ENABLE_LIBXML2
25  if (m_document) {
26    xmlDocPtr doc = m_document;
27    m_document = nullptr;
28    xmlFreeDoc(doc);
29  }
30#endif
31}
32
33bool XMLDocument::IsValid() const { return m_document != nullptr; }
34
35void XMLDocument::ErrorCallback(void *ctx, const char *format, ...) {
36  XMLDocument *document = (XMLDocument *)ctx;
37  va_list args;
38  va_start(args, format);
39  document->m_errors.PrintfVarArg(format, args);
40  document->m_errors.EOL();
41  va_end(args);
42}
43
44bool XMLDocument::ParseFile(const char *path) {
45#if LLDB_ENABLE_LIBXML2
46  Clear();
47  xmlSetGenericErrorFunc((void *)this, XMLDocument::ErrorCallback);
48  m_document = xmlParseFile(path);
49  xmlSetGenericErrorFunc(nullptr, nullptr);
50#endif
51  return IsValid();
52}
53
54bool XMLDocument::ParseMemory(const char *xml, size_t xml_length,
55                              const char *url) {
56#if LLDB_ENABLE_LIBXML2
57  Clear();
58  xmlSetGenericErrorFunc((void *)this, XMLDocument::ErrorCallback);
59  m_document = xmlReadMemory(xml, (int)xml_length, url, nullptr, 0);
60  xmlSetGenericErrorFunc(nullptr, nullptr);
61#endif
62  return IsValid();
63}
64
65XMLNode XMLDocument::GetRootElement(const char *required_name) {
66#if LLDB_ENABLE_LIBXML2
67  if (IsValid()) {
68    XMLNode root_node(xmlDocGetRootElement(m_document));
69    if (required_name) {
70      llvm::StringRef actual_name = root_node.GetName();
71      if (actual_name == required_name)
72        return root_node;
73    } else {
74      return root_node;
75    }
76  }
77#endif
78  return XMLNode();
79}
80
81llvm::StringRef XMLDocument::GetErrors() const { return m_errors.GetString(); }
82
83bool XMLDocument::XMLEnabled() {
84#if LLDB_ENABLE_LIBXML2
85  return true;
86#else
87  return false;
88#endif
89}
90
91#pragma mark-- XMLNode
92
93XMLNode::XMLNode() = default;
94
95XMLNode::XMLNode(XMLNodeImpl node) : m_node(node) {}
96
97XMLNode::~XMLNode() = default;
98
99void XMLNode::Clear() { m_node = nullptr; }
100
101XMLNode XMLNode::GetParent() const {
102#if LLDB_ENABLE_LIBXML2
103  if (IsValid())
104    return XMLNode(m_node->parent);
105  else
106    return XMLNode();
107#else
108  return XMLNode();
109#endif
110}
111
112XMLNode XMLNode::GetSibling() const {
113#if LLDB_ENABLE_LIBXML2
114  if (IsValid())
115    return XMLNode(m_node->next);
116  else
117    return XMLNode();
118#else
119  return XMLNode();
120#endif
121}
122
123XMLNode XMLNode::GetChild() const {
124#if LLDB_ENABLE_LIBXML2
125
126  if (IsValid())
127    return XMLNode(m_node->children);
128  else
129    return XMLNode();
130#else
131  return XMLNode();
132#endif
133}
134
135std::string XMLNode::GetAttributeValue(const char *name,
136                                       const char *fail_value) const {
137  std::string attr_value;
138#if LLDB_ENABLE_LIBXML2
139  if (IsValid()) {
140    xmlChar *value = xmlGetProp(m_node, (const xmlChar *)name);
141    if (value) {
142      attr_value = (const char *)value;
143      xmlFree(value);
144    }
145  } else {
146    if (fail_value)
147      attr_value = fail_value;
148  }
149#else
150  if (fail_value)
151    attr_value = fail_value;
152#endif
153  return attr_value;
154}
155
156bool XMLNode::GetAttributeValueAsUnsigned(const char *name, uint64_t &value,
157                                          uint64_t fail_value, int base) const {
158  value = fail_value;
159  return llvm::to_integer(GetAttributeValue(name, ""), value, base);
160}
161
162void XMLNode::ForEachChildNode(NodeCallback const &callback) const {
163#if LLDB_ENABLE_LIBXML2
164  if (IsValid())
165    GetChild().ForEachSiblingNode(callback);
166#endif
167}
168
169void XMLNode::ForEachChildElement(NodeCallback const &callback) const {
170#if LLDB_ENABLE_LIBXML2
171  XMLNode child = GetChild();
172  if (child)
173    child.ForEachSiblingElement(callback);
174#endif
175}
176
177void XMLNode::ForEachChildElementWithName(const char *name,
178                                          NodeCallback const &callback) const {
179#if LLDB_ENABLE_LIBXML2
180  XMLNode child = GetChild();
181  if (child)
182    child.ForEachSiblingElementWithName(name, callback);
183#endif
184}
185
186void XMLNode::ForEachAttribute(AttributeCallback const &callback) const {
187#if LLDB_ENABLE_LIBXML2
188
189  if (IsValid()) {
190    for (xmlAttrPtr attr = m_node->properties; attr != nullptr;
191         attr = attr->next) {
192      // check if name matches
193      if (attr->name) {
194        // check child is a text node
195        xmlNodePtr child = attr->children;
196        if (child->type == XML_TEXT_NODE) {
197          llvm::StringRef attr_value;
198          if (child->content)
199            attr_value = llvm::StringRef((const char *)child->content);
200          if (!callback(llvm::StringRef((const char *)attr->name), attr_value))
201            return;
202        }
203      }
204    }
205  }
206#endif
207}
208
209void XMLNode::ForEachSiblingNode(NodeCallback const &callback) const {
210#if LLDB_ENABLE_LIBXML2
211
212  if (IsValid()) {
213    // iterate through all siblings
214    for (xmlNodePtr node = m_node; node; node = node->next) {
215      if (!callback(XMLNode(node)))
216        return;
217    }
218  }
219#endif
220}
221
222void XMLNode::ForEachSiblingElement(NodeCallback const &callback) const {
223#if LLDB_ENABLE_LIBXML2
224
225  if (IsValid()) {
226    // iterate through all siblings
227    for (xmlNodePtr node = m_node; node; node = node->next) {
228      // we are looking for element nodes only
229      if (node->type != XML_ELEMENT_NODE)
230        continue;
231
232      if (!callback(XMLNode(node)))
233        return;
234    }
235  }
236#endif
237}
238
239void XMLNode::ForEachSiblingElementWithName(
240    const char *name, NodeCallback const &callback) const {
241#if LLDB_ENABLE_LIBXML2
242
243  if (IsValid()) {
244    // iterate through all siblings
245    for (xmlNodePtr node = m_node; node; node = node->next) {
246      // we are looking for element nodes only
247      if (node->type != XML_ELEMENT_NODE)
248        continue;
249
250      // If name is nullptr, we take all nodes of type "t", else just the ones
251      // whose name matches
252      if (name) {
253        if (strcmp((const char *)node->name, name) != 0)
254          continue; // Name mismatch, ignore this one
255      } else {
256        if (node->name)
257          continue; // nullptr name specified and this element has a name,
258                    // ignore this one
259      }
260
261      if (!callback(XMLNode(node)))
262        return;
263    }
264  }
265#endif
266}
267
268llvm::StringRef XMLNode::GetName() const {
269#if LLDB_ENABLE_LIBXML2
270  if (IsValid()) {
271    if (m_node->name)
272      return llvm::StringRef((const char *)m_node->name);
273  }
274#endif
275  return llvm::StringRef();
276}
277
278bool XMLNode::GetElementText(std::string &text) const {
279  text.clear();
280#if LLDB_ENABLE_LIBXML2
281  if (IsValid()) {
282    bool success = false;
283    if (m_node->type == XML_ELEMENT_NODE) {
284      // check child is a text node
285      for (xmlNodePtr node = m_node->children; node != nullptr;
286           node = node->next) {
287        if (node->type == XML_TEXT_NODE) {
288          text.append((const char *)node->content);
289          success = true;
290        }
291      }
292    }
293    return success;
294  }
295#endif
296  return false;
297}
298
299bool XMLNode::GetElementTextAsUnsigned(uint64_t &value, uint64_t fail_value,
300                                       int base) const {
301  std::string text;
302
303  value = fail_value;
304  return GetElementText(text) && llvm::to_integer(text, value, base);
305}
306
307bool XMLNode::GetElementTextAsFloat(double &value, double fail_value) const {
308  std::string text;
309
310  value = fail_value;
311  return GetElementText(text) && llvm::to_float(text, value);
312}
313
314bool XMLNode::NameIs(const char *name) const {
315#if LLDB_ENABLE_LIBXML2
316
317  if (IsValid()) {
318    // In case we are looking for a nullptr name or an exact pointer match
319    if (m_node->name == (const xmlChar *)name)
320      return true;
321    if (m_node->name)
322      return strcmp((const char *)m_node->name, name) == 0;
323  }
324#endif
325  return false;
326}
327
328XMLNode XMLNode::FindFirstChildElementWithName(const char *name) const {
329  XMLNode result_node;
330
331#if LLDB_ENABLE_LIBXML2
332  ForEachChildElementWithName(
333      name, [&result_node](const XMLNode &node) -> bool {
334        result_node = node;
335        // Stop iterating, we found the node we wanted
336        return false;
337      });
338#endif
339
340  return result_node;
341}
342
343bool XMLNode::IsValid() const { return m_node != nullptr; }
344
345bool XMLNode::IsElement() const {
346#if LLDB_ENABLE_LIBXML2
347  if (IsValid())
348    return m_node->type == XML_ELEMENT_NODE;
349#endif
350  return false;
351}
352
353XMLNode XMLNode::GetElementForPath(const NamePath &path) {
354#if LLDB_ENABLE_LIBXML2
355
356  if (IsValid()) {
357    if (path.empty())
358      return *this;
359    else {
360      XMLNode node = FindFirstChildElementWithName(path[0].c_str());
361      const size_t n = path.size();
362      for (size_t i = 1; node && i < n; ++i)
363        node = node.FindFirstChildElementWithName(path[i].c_str());
364      return node;
365    }
366  }
367#endif
368
369  return XMLNode();
370}
371
372#pragma mark-- ApplePropertyList
373
374ApplePropertyList::ApplePropertyList() : m_xml_doc(), m_dict_node() {}
375
376ApplePropertyList::ApplePropertyList(const char *path)
377    : m_xml_doc(), m_dict_node() {
378  ParseFile(path);
379}
380
381ApplePropertyList::~ApplePropertyList() = default;
382
383llvm::StringRef ApplePropertyList::GetErrors() const {
384  return m_xml_doc.GetErrors();
385}
386
387bool ApplePropertyList::ParseFile(const char *path) {
388  if (m_xml_doc.ParseFile(path)) {
389    XMLNode plist = m_xml_doc.GetRootElement("plist");
390    if (plist) {
391      plist.ForEachChildElementWithName("dict",
392                                        [this](const XMLNode &dict) -> bool {
393                                          this->m_dict_node = dict;
394                                          return false; // Stop iterating
395                                        });
396      return (bool)m_dict_node;
397    }
398  }
399  return false;
400}
401
402bool ApplePropertyList::IsValid() const { return (bool)m_dict_node; }
403
404bool ApplePropertyList::GetValueAsString(const char *key,
405                                         std::string &value) const {
406  XMLNode value_node = GetValueNode(key);
407  if (value_node)
408    return ApplePropertyList::ExtractStringFromValueNode(value_node, value);
409  return false;
410}
411
412XMLNode ApplePropertyList::GetValueNode(const char *key) const {
413  XMLNode value_node;
414#if LLDB_ENABLE_LIBXML2
415
416  if (IsValid()) {
417    m_dict_node.ForEachChildElementWithName(
418        "key", [key, &value_node](const XMLNode &key_node) -> bool {
419          std::string key_name;
420          if (key_node.GetElementText(key_name)) {
421            if (key_name == key) {
422              value_node = key_node.GetSibling();
423              while (value_node && !value_node.IsElement())
424                value_node = value_node.GetSibling();
425              return false; // Stop iterating
426            }
427          }
428          return true; // Keep iterating
429        });
430  }
431#endif
432  return value_node;
433}
434
435bool ApplePropertyList::ExtractStringFromValueNode(const XMLNode &node,
436                                                   std::string &value) {
437  value.clear();
438#if LLDB_ENABLE_LIBXML2
439  if (node.IsValid()) {
440    llvm::StringRef element_name = node.GetName();
441    if (element_name == "true" || element_name == "false") {
442      // The text value _is_ the element name itself...
443      value = element_name.str();
444      return true;
445    } else if (element_name == "dict" || element_name == "array")
446      return false; // dictionaries and arrays have no text value, so we fail
447    else
448      return node.GetElementText(value);
449  }
450#endif
451  return false;
452}
453
454#if LLDB_ENABLE_LIBXML2
455
456static StructuredData::ObjectSP CreatePlistValue(XMLNode node) {
457  llvm::StringRef element_name = node.GetName();
458  if (element_name == "array") {
459    std::shared_ptr<StructuredData::Array> array_sp(
460        new StructuredData::Array());
461    node.ForEachChildElement([&array_sp](const XMLNode &node) -> bool {
462      array_sp->AddItem(CreatePlistValue(node));
463      return true; // Keep iterating through all child elements of the array
464    });
465    return array_sp;
466  } else if (element_name == "dict") {
467    XMLNode key_node;
468    std::shared_ptr<StructuredData::Dictionary> dict_sp(
469        new StructuredData::Dictionary());
470    node.ForEachChildElement(
471        [&key_node, &dict_sp](const XMLNode &node) -> bool {
472          if (node.NameIs("key")) {
473            // This is a "key" element node
474            key_node = node;
475          } else {
476            // This is a value node
477            if (key_node) {
478              std::string key_name;
479              key_node.GetElementText(key_name);
480              dict_sp->AddItem(key_name, CreatePlistValue(node));
481              key_node.Clear();
482            }
483          }
484          return true; // Keep iterating through all child elements of the
485                       // dictionary
486        });
487    return dict_sp;
488  } else if (element_name == "real") {
489    double value = 0.0;
490    node.GetElementTextAsFloat(value);
491    return StructuredData::ObjectSP(new StructuredData::Float(value));
492  } else if (element_name == "integer") {
493    uint64_t value = 0;
494    node.GetElementTextAsUnsigned(value, 0, 0);
495    return StructuredData::ObjectSP(new StructuredData::UnsignedInteger(value));
496  } else if ((element_name == "string") || (element_name == "data") ||
497             (element_name == "date")) {
498    std::string text;
499    node.GetElementText(text);
500    return StructuredData::ObjectSP(
501        new StructuredData::String(std::move(text)));
502  } else if (element_name == "true") {
503    return StructuredData::ObjectSP(new StructuredData::Boolean(true));
504  } else if (element_name == "false") {
505    return StructuredData::ObjectSP(new StructuredData::Boolean(false));
506  }
507  return StructuredData::ObjectSP(new StructuredData::Null());
508}
509#endif
510
511StructuredData::ObjectSP ApplePropertyList::GetStructuredData() {
512  StructuredData::ObjectSP root_sp;
513#if LLDB_ENABLE_LIBXML2
514  if (IsValid()) {
515    return CreatePlistValue(m_dict_node);
516  }
517#endif
518  return root_sp;
519}
520