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