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 <stdlib.h> /* atof */
10
11 #include "lldb/Host/Config.h"
12 #include "lldb/Host/StringConvert.h"
13 #include "lldb/Host/XML.h"
14
15 using namespace lldb;
16 using namespace lldb_private;
17
18 #pragma mark-- XMLDocument
19
XMLDocument()20 XMLDocument::XMLDocument() : m_document(nullptr) {}
21
~XMLDocument()22 XMLDocument::~XMLDocument() { Clear(); }
23
Clear()24 void 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
IsValid() const34 bool XMLDocument::IsValid() const { return m_document != nullptr; }
35
ErrorCallback(void * ctx,const char * format,...)36 void 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
ParseFile(const char * path)45 bool 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
ParseMemory(const char * xml,size_t xml_length,const char * url)55 bool 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
GetRootElement(const char * required_name)66 XMLNode 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
GetErrors() const82 llvm::StringRef XMLDocument::GetErrors() const { return m_errors.GetString(); }
83
XMLEnabled()84 bool XMLDocument::XMLEnabled() {
85 #if LLDB_ENABLE_LIBXML2
86 return true;
87 #else
88 return false;
89 #endif
90 }
91
92 #pragma mark-- XMLNode
93
XMLNode()94 XMLNode::XMLNode() : m_node(nullptr) {}
95
XMLNode(XMLNodeImpl node)96 XMLNode::XMLNode(XMLNodeImpl node) : m_node(node) {}
97
~XMLNode()98 XMLNode::~XMLNode() {}
99
Clear()100 void XMLNode::Clear() { m_node = nullptr; }
101
GetParent() const102 XMLNode 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
GetSibling() const113 XMLNode 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
GetChild() const124 XMLNode 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
GetAttributeValue(const char * name,const char * fail_value) const136 llvm::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
GetAttributeValueAsUnsigned(const char * name,uint64_t & value,uint64_t fail_value,int base) const154 bool 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
ForEachChildNode(NodeCallback const & callback) const166 void XMLNode::ForEachChildNode(NodeCallback const &callback) const {
167 #if LLDB_ENABLE_LIBXML2
168 if (IsValid())
169 GetChild().ForEachSiblingNode(callback);
170 #endif
171 }
172
ForEachChildElement(NodeCallback const & callback) const173 void 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
ForEachChildElementWithName(const char * name,NodeCallback const & callback) const181 void 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
ForEachAttribute(AttributeCallback const & callback) const190 void 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
ForEachSiblingNode(NodeCallback const & callback) const213 void 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
ForEachSiblingElement(NodeCallback const & callback) const226 void 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
ForEachSiblingElementWithName(const char * name,NodeCallback const & callback) const243 void 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
GetName() const272 llvm::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
GetElementText(std::string & text) const282 bool 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
GetElementTextAsUnsigned(uint64_t & value,uint64_t fail_value,int base) const303 bool 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
GetElementTextAsFloat(double & value,double fail_value) const318 bool 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
NameIs(const char * name) const334 bool 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
FindFirstChildElementWithName(const char * name) const348 XMLNode 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
IsValid() const363 bool XMLNode::IsValid() const { return m_node != nullptr; }
364
IsElement() const365 bool 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
GetElementForPath(const NamePath & path)373 XMLNode 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
ApplePropertyList()394 ApplePropertyList::ApplePropertyList() : m_xml_doc(), m_dict_node() {}
395
ApplePropertyList(const char * path)396 ApplePropertyList::ApplePropertyList(const char *path)
397 : m_xml_doc(), m_dict_node() {
398 ParseFile(path);
399 }
400
~ApplePropertyList()401 ApplePropertyList::~ApplePropertyList() {}
402
GetErrors() const403 llvm::StringRef ApplePropertyList::GetErrors() const {
404 return m_xml_doc.GetErrors();
405 }
406
ParseFile(const char * path)407 bool 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
IsValid() const422 bool ApplePropertyList::IsValid() const { return (bool)m_dict_node; }
423
GetValueAsString(const char * key,std::string & value) const424 bool 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
GetValueNode(const char * key) const432 XMLNode 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
ExtractStringFromValueNode(const XMLNode & node,std::string & value)455 bool 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
476 namespace {
477
CreatePlistValue(XMLNode node)478 StructuredData::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
GetStructuredData()534 StructuredData::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