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