Vuo  2.4.1
VuoTree.cc
Go to the documentation of this file.
1
10#include <algorithm>
11#include <list>
12#include <map>
13#include <sstream>
14#include <string>
15#include <vector>
16using namespace std;
17
18#include "type.h"
19
20extern "C"
21{
22#include "VuoTree.h"
23#include "VuoList_VuoTree.h"
24
25#include <ctype.h> // isspace()
26#include <libxml/parser.h>
27#include <libxml/tree.h>
28#include <libxml/xpath.h>
29
31#ifdef VUO_COMPILER
33 "title" : "Tree",
34 "description" : "Hierarchically structured information.",
35 "keywords" : [ ],
36 "version" : "1.0.0",
37 "dependencies" : [
38 "VuoList_VuoTree",
39 "VuoDictionary_VuoText_VuoText",
40 "VuoTextHtml"
41 ]
42 });
43#endif
45}
46
48static const char *encoding = "UTF-8";
49
53static void VuoTree_freeXmlDoc(void *value)
54{
55 xmlFreeDoc( (xmlDocPtr)value );
56}
57
61static void VuoTree_freeJsonObject(void *value)
62{
63 json_object_put((json_object *)value);
64}
65
66
73static VuoTree VuoTree_makeFromXmlNode(xmlNode *node)
74{
75 // Don't register rootXmlNode, to avoid multiply registering it if it's the root of more than one tree.
76 return (VuoTree){ node, NULL, NULL };
77}
78
88{
89 xmlNode *node = xmlDocGetRootElement(doc);
90 if (! node)
91 {
92 xmlFreeDoc(doc);
93 return VuoTree_makeEmpty();
94 }
95
97 return VuoTree_makeFromXmlNode(node);
98}
99
104static xmlNodePtr VuoTree_boilDownPropertyList(xmlNodePtr plist)
105{
106 if (strcmp((const char *)plist->name, "dict") == 0)
107 {
108 xmlNodePtr n = xmlNewNode(NULL, (const xmlChar *)"");
109 for (xmlNodePtr cur = plist->children; cur; cur = cur->next)
110 {
111 if (cur->type != XML_ELEMENT_NODE)
112 continue;
113
114 // A 'key' element followed by a value element.
115 if (strcmp((const char *)cur->name, "key") != 0)
116 {
117 VUserLog("Error: Malformed property list XML: Expected a <key> element, but found <%s>.", cur->name);
118 return NULL;
119 }
120
121 // Extract the key from the first text content. (Faster than xmlNodeGetContent.)
122 xmlChar *key = NULL;
123 for (xmlNode *nn = cur->children; nn; nn = nn->next)
124 if (nn->type == XML_TEXT_NODE)
125 {
126 key = nn->content;
127 break;
128 }
129
130
131 cur = cur->next;
132 if (!cur)
133 {
134 VUserLog("Error: Malformed property list XML: Expected a value element to follow the <key> element, but instead reached the end.");
135 return NULL;
136 }
137
138 xmlNodePtr subNode = VuoTree_boilDownPropertyList(cur);
139 subNode->name = xmlStrdup(key);
140 xmlAddChild(n, subNode);
141 }
142 return n;
143 }
144 else if (strcmp((const char *)plist->name, "array") == 0)
145 {
146 xmlNodePtr n = xmlNewNode(NULL, (const xmlChar *)"");
147 for (xmlNodePtr cur = plist->children; cur; cur = cur->next)
148 xmlAddChild(n, VuoTree_boilDownPropertyList(cur));
149 return n;
150 }
151 else if (strcmp((const char *)plist->name, "false") == 0)
152 {
153 xmlNodePtr n = xmlNewNode(NULL, (const xmlChar *)"");
154 xmlAddChild(n, xmlNewText(xmlCharStrdup("false")));
155 return n;
156 }
157 else if (strcmp((const char *)plist->name, "true") == 0)
158 {
159 xmlNodePtr n = xmlNewNode(NULL, (const xmlChar *)"");
160 xmlAddChild(n, xmlNewText(xmlCharStrdup("true")));
161 return n;
162 }
163 else
164 {
165 // Leave other types as-is (string, data, date, integer, real).
166 // (Don't attempt to decode <data>'s base64, since raw binary data isn't valid XML or VuoText.
167 // The composition author should use a separate node to decode it into VuoData.)
168 xmlNodePtr n = xmlCopyNode(plist, true);
169 n->name = xmlCharStrdup("item");
170 return n;
171 }
172}
173
177static VuoTree VuoTree_parseXml(const char *xmlString, bool includeWhitespace)
178{
179 if (! xmlString)
180 return VuoTree_makeEmpty();
181
182 size_t length = strlen(xmlString);
183 if (length == 0)
184 return VuoTree_makeEmpty();
185
186 int flags = 0;
187 if (! includeWhitespace)
188 flags |= XML_PARSE_NOBLANKS;
189
190 xmlDoc *doc = xmlReadMemory(xmlString, length, "VuoTree.xml", encoding, flags);
191 if (! doc)
192 {
193 VUserLog("Error: Couldn't parse tree as XML");
194 return VuoTree_makeEmpty();
195 }
196
197
198 // If the XML file is an Apple Property List, decode it.
199 xmlNodePtr root = xmlDocGetRootElement(doc);
200 if (doc->intSubset && doc->intSubset->SystemID
201 && (strcmp((const char *)doc->intSubset->SystemID, "http://www.apple.com/DTDs/PropertyList-1.0.dtd") == 0
202 || strcmp((const char *)doc->intSubset->SystemID, "https://www.apple.com/DTDs/PropertyList-1.0.dtd") == 0))
203 {
204 xmlNodePtr transformedRoot = VuoTree_boilDownPropertyList(root->children);
205 if (!transformedRoot)
206 return VuoTree_makeEmpty();
207
208 transformedRoot->name = xmlCharStrdup("document");
209
210 xmlFreeDoc(doc);
211
212 doc = xmlNewDoc((const xmlChar *)"1.0");
213 xmlDocSetRootElement(doc, transformedRoot);
214 }
215
216
217 return VuoTree_makeFromXmlDoc(doc);
218}
219
223static xmlChar * VuoTree_serializeXmlNodeAsXml(VuoTree tree, bool indent, int level)
224{
225 if (! tree.rootXmlNode)
226 return xmlStrdup((const xmlChar *)"");
227
228 xmlNode *treeRoot = xmlCopyNode((xmlNode *)tree.rootXmlNode, 1);
229 VuoDefer(^{ xmlUnlinkNode(treeRoot); xmlFreeNode(treeRoot); });
230
231 list<xmlNode *> nodesToVisit;
232 nodesToVisit.push_back(treeRoot);
233 while (! nodesToVisit.empty())
234 {
235 xmlNode *currentNode = nodesToVisit.front();
236 nodesToVisit.pop_front();
237
238 if (xmlStrlen(currentNode->name) == 0)
239 {
240 const char *name = (currentNode == treeRoot && level == 0 ? "document" : "item");
241 xmlNodeSetName(currentNode, (const xmlChar *)name);
242 }
243 else
244 {
245 int flags = XML_PARSE_NOERROR | XML_PARSE_NOWARNING;
246 string testElement = "<" + string((const char *)currentNode->name) + "/>";
247 xmlDoc *testDoc = xmlReadMemory(testElement.c_str(), testElement.length(), "VuoTree.xml", encoding, flags);
248 if (!testDoc)
249 {
250 xmlNewProp(currentNode, (const xmlChar *)"name", (const xmlChar *)currentNode->name);
251 xmlNodeSetName(currentNode, (const xmlChar *)"item");
252 }
253 }
254
255 for (xmlNode *n = currentNode->children; n; n = n->next)
256 if (n->type == XML_ELEMENT_NODE)
257 nodesToVisit.push_back(n);
258 }
259
260 xmlBuffer *buffer = xmlBufferCreate();
261 VuoDefer(^{ xmlBufferFree(buffer); });
262
263 int ret = xmlNodeDump(buffer, treeRoot->doc, treeRoot, level, indent);
264
265 if (ret < 0)
266 {
267 VUserLog("Error: Couldn't serialize tree named '%s' as XML.", treeRoot->name);
268 return xmlStrdup((const xmlChar *)"");
269 }
270
271 xmlChar *xml = xmlBufferDetach(buffer);
272
273 if (tree.children)
274 {
275 xmlChar *closingTag;
276 if (xml[xmlStrlen(xml)-2] == '/')
277 {
278 // Split <name ... /> into <name ...> and </name>
279 closingTag = xmlStrdup((const xmlChar *)"</");
280 closingTag = xmlStrcat(closingTag, treeRoot->name);
281 closingTag = xmlStrcat(closingTag, (const xmlChar *)">");
282 xml[xmlStrlen(xml)-2] = '>';
283 xml[xmlStrlen(xml)-1] = 0;
284 }
285 else
286 {
287 // Split <name ...></name> into <name ...> and </name>
288 int closingTagLength = xmlStrlen(treeRoot->name) + 3;
289 int splitIndex = xmlStrlen(xml) - closingTagLength;
290 closingTag = xmlStrsub(xml, splitIndex, closingTagLength);
291 xml[splitIndex] = 0;
292 }
293
294 string whitespaceBeforeChild = (indent ? "\n" + string(2 * (level+1), ' ') : "");
295
296 unsigned long childCount = VuoListGetCount_VuoTree(tree.children);
297 for (unsigned long i = 1; i <= childCount; ++i)
298 {
299 xml = xmlStrcat(xml, (const xmlChar *)whitespaceBeforeChild.c_str());
300
302 xmlChar *childXml = VuoTree_serializeXmlNodeAsXml(child, indent, level+1);
303 xml = xmlStrcat(xml, childXml);
304 }
305
306 string whitespaceBeforeClosingTag = (indent ? "\n" + string(2 * level, ' ') : "");
307 xml = xmlStrcat(xml, (const xmlChar *)whitespaceBeforeClosingTag.c_str());
308
309 xml = xmlStrcat(xml, closingTag);
310 }
311
312 return xml;
313}
314
318static xmlNode * createXmlNode(const char *name)
319{
320 if (! name || strlen(name) == 0)
321 return xmlNewNode(NULL, (const xmlChar *)"");
322
323 return xmlNewNode(NULL, (const xmlChar *)name);
324}
325
329static VuoTree VuoTree_parseJson(const char *jsonString)
330{
331 if (! jsonString || strlen(jsonString) == 0)
332 return VuoTree_makeEmpty();
333
334 enum json_tokener_error error;
335 json_object *json = json_tokener_parse_verbose(jsonString, &error);
336 if (! json)
337 {
338 VUserLog("Error: Couldn't parse tree as JSON: %s", json_tokener_error_desc(error));
339 return VuoTree_makeEmpty();
340 }
341
342 bool hasRoot;
343 if (json_object_is_type(json, json_type_object) && json_object_object_length(json) == 1)
344 {
345 hasRoot = true;
346 json_object_object_foreach(json, key, value)
347 {
348 if (json_object_is_type(value, json_type_array))
349 {
350 hasRoot = false;
351 break;
352 }
353 }
354 }
355 else
356 hasRoot = false;
357
358 json_object *renamedRootObject = NULL;
359 json_object *renamedArrayObject = NULL;
360 if (! hasRoot)
361 {
362 // The JSON doesn't have a root, so add one. The names are placeholders and will be changed to empty strings in the XML.
363 // Change ... to {"json":...}
364 // Change [...] to {"json":{"array":[...]}} — special case for arrays
365
366 if (json_object_is_type(json, json_type_array))
367 {
368 json_object *arrayParent = json_object_new_object();
369 json_object_object_add(arrayParent, "array", json);
370 json = arrayParent;
371 renamedArrayObject = arrayParent;
372 }
373
374 json_object *parent = json_object_new_object();
375 json_object_object_add(parent, "json", json);
376 json = parent;
377 renamedRootObject = parent;
378 }
379
380 xmlDoc *doc = xmlNewDoc((const xmlChar *)"1.0");
381 xmlNode *root = NULL;
382 map<json_object *, xmlNode *> parents;
383
384 list<json_object *> jsonsToVisit;
385 jsonsToVisit.push_back(json);
386 while (! jsonsToVisit.empty())
387 {
388 json_object *currentJson = jsonsToVisit.front();
389 jsonsToVisit.pop_front();
390
391 xmlNode *parentNode = parents[currentJson];
392
393 // Translate {"key1":value1,"key2":value2,...} to <key1>value1</key1><key2>value2</key2>...
394 // Translate {"key1":[value1,value2,...],...} to <key1>value1</key1><key1>value2</key1>... — special case for arrays
395
396 json_type type = json_object_get_type(currentJson);
397 if (type == json_type_object)
398 {
399 json_object_object_foreach(currentJson, key, value)
400 {
401 const char *nodeName = (currentJson == renamedRootObject || currentJson == renamedArrayObject ? "" : key);
402
403 if (json_object_is_type(value, json_type_array))
404 {
405 int length = json_object_array_length(value);
406 for (int i = 0; i < length; ++i)
407 {
408 json_object *arrayValue = json_object_array_get_idx(value, i);
409
410 xmlNode *currentNode = createXmlNode(nodeName);
411 xmlAddChild(parentNode, currentNode);
412
413 parents[arrayValue] = currentNode;
414 jsonsToVisit.push_back(arrayValue);
415 }
416 }
417 else
418 {
419 xmlNode *currentNode = createXmlNode(nodeName);
420
421 if (parentNode)
422 xmlAddChild(parentNode, currentNode);
423 else
424 root = currentNode;
425
426 parents[value] = currentNode;
427 jsonsToVisit.push_back(value);
428 }
429 }
430 }
431 else
432 {
433 const char *content = json_object_get_string(currentJson);
434 xmlChar *encoded = xmlEncodeSpecialChars(doc, (const xmlChar *)content);
435 xmlNodeSetContent(parentNode, encoded);
436 free(encoded);
437 }
438 }
439
440 xmlDocSetRootElement(doc, root);
441
442 json_object_put(json);
443
445
446 tree.rootJson = json_tokener_parse(jsonString); // Use the original JSON, not the modified one from above.
448
449 return tree;
450}
451
455typedef struct
456{
457 xmlNode *node;
458 const char *text;
459} ChildInfo;
460
464static json_object * createJsonString(const char *s)
465{
466 const int charCount = 5;
467 const char *controlChars[charCount] = { "\b", "\f", "\n", "\r", "\t" };
468 VuoText t[charCount+1];
469 t[0] = s;
470 for (int i = 0; i < charCount; ++i)
471 t[i+1] = VuoText_replace(t[i], controlChars[i], "");
472
473 json_object *ret = json_object_new_string(t[charCount]);
474
475 for (int i = charCount; i > 0; --i)
476 {
477 if (t[i] != t[i-1])
478 {
479 VuoRetain(t[i]);
480 VuoRelease(t[i]);
481 }
482 }
483
484 return ret;
485}
486
490static void addToContainer(json_object *container, const char *key, json_object *value)
491{
492 if (json_object_is_type(container, json_type_array))
493 json_object_array_add(container, value);
494 else
495 json_object_object_add(container, key, value);
496}
497
504{
505 xmlNode *rootNode = (xmlNode *)tree.rootXmlNode;
506
507 json_object *topLevelJson = json_object_new_object();
508 const char *topLevelName = (xmlStrlen(rootNode->name) > 0 ? (const char *)rootNode->name : (atRoot ? "document" : "item"));
509
510 map<xmlNode *, json_object *> containers;
511 containers[rootNode] = topLevelJson;
512
513 map<xmlNode *, const char *> names;
514 names[rootNode] = topLevelName;
515
516 list<xmlNode *> nodesToVisit;
517 nodesToVisit.push_back( (xmlNode *)tree.rootXmlNode );
518 while (! nodesToVisit.empty())
519 {
520 xmlNode *currentNode = nodesToVisit.front();
521 nodesToVisit.pop_front();
522// VLog("visit %s", names[currentNode]);
523// VLog(" %s", json_object_to_json_string(containers[currentNode]));
524
525 // Make a list of currentNode's attributes and children, indexed by name.
526
527 vector< pair<const char *, ChildInfo> > childInfos;
528 int elementCount = 0;
529 int textCount = 0;
530
531 for (xmlAttr *attribute = currentNode->properties; attribute; attribute = attribute->next)
532 {
533 xmlChar *attributeValue = xmlNodeListGetString(rootNode->doc, attribute->children, 1);
534 ChildInfo info = { NULL, (const char *)attributeValue };
535 childInfos.push_back(make_pair( (const char *)attribute->name, info ));
536 }
537
538 for (xmlNode *childNode = currentNode->children; childNode; childNode = childNode->next)
539 {
540 if (childNode->type == XML_ELEMENT_NODE)
541 {
542 const char *childName = (xmlStrlen(childNode->name) > 0 ? (const char *)childNode->name : "item");
543 names[childNode] = childName;
544
545 ChildInfo info = { childNode, NULL };
546 childInfos.push_back(make_pair( childName, info ));
547
548 ++elementCount;
549 }
550 else if (childNode->type == XML_TEXT_NODE || childNode->type == XML_CDATA_SECTION_NODE)
551 {
552 ChildInfo info = { childNode, (char *)childNode->content };
553 childInfos.push_back(make_pair( (const char *)NULL, info ));
554
555 ++textCount;
556 }
557 }
558
559 json_object *currentContainer = containers[currentNode];
560 const char *currentName = names[currentNode];
561
562 if (childInfos.empty())
563 {
564 // No attributes or children — {"currentName":null}
565
566 addToContainer(currentContainer, currentName, NULL);
567// VLog(" %s", json_object_to_json_string(currentContainer));
568 }
569 else if (childInfos.size() == 1 && childInfos[0].first == NULL)
570 {
571 // Single text child — {"currentName":"text"}
572
573 json_object *text = createJsonString(childInfos[0].second.text);
574 addToContainer(currentContainer, currentName, text);
575// VLog(" %s", json_object_to_json_string(currentContainer));
576 }
577 else
578 {
579 // Consolidate the list of attributes and children, grouping by name.
580
581 vector< pair<const char *, vector<ChildInfo> > > mergedChildInfo;
582 map<size_t, bool> childInfosVisited;
583
584 for (size_t i = 0; i < childInfos.size(); ++i)
585 {
586 if (childInfosVisited[i])
587 continue;
588
589 vector<ChildInfo> sameNameInfo;
590 sameNameInfo.push_back(childInfos[i].second);
591 childInfosVisited[i] = true;
592
593 if (childInfos[i].first)
594 {
595 for (size_t j = i+1; j < childInfos.size(); ++j)
596 {
597 if (! childInfos[j].first)
598 break;
599
600 if (! strcmp(childInfos[i].first, childInfos[j].first))
601 {
602 sameNameInfo.push_back(childInfos[j].second);
603 childInfosVisited[j] = true;
604 }
605 }
606 }
607
608 mergedChildInfo.push_back(make_pair( childInfos[i].first, sameNameInfo ));
609// VLog(" %s : %lu", childInfos[i].first, sameNameInfo.size());
610 }
611
612 // Multiple children — {"currentName":{...}} or {"currentName":[...]}
613
614 bool isMixedContent = elementCount > 0 && textCount > 0;
615 json_object *childContainer = (isMixedContent ? json_object_new_array() : json_object_new_object());
616 addToContainer(currentContainer, currentName, childContainer);
617// VLog(" %s", json_object_to_json_string(currentContainer));
618
619 for (vector< pair<const char *, vector<ChildInfo> > >::iterator i = mergedChildInfo.begin(); i != mergedChildInfo.end(); ++i)
620 {
621 if ((*i).second.size() == 1)
622 {
623 // Child with unique name — {"childName":"text"} or {"childName":{...}}
624
625 if ((*i).second[0].text)
626 {
627 json_object *text = createJsonString((*i).second[0].text);
628 const char *childName = ((*i).first ? (*i).first : "content");
629 addToContainer(childContainer, childName, text);
630 }
631 else
632 {
633 json_object *singleChildContainer;
634 if (isMixedContent)
635 {
636 singleChildContainer = json_object_new_object();
637 json_object_array_add(childContainer, singleChildContainer);
638 }
639 else
640 singleChildContainer = childContainer;
641
642 containers[(*i).second[0].node] = singleChildContainer;
643 }
644 }
645 else
646 {
647 // Children with same name — {"childName":[...]}
648
649 json_object *sameNameArray = json_object_new_array();
650 for (vector<ChildInfo>::iterator j = (*i).second.begin(); j != (*i).second.end(); ++j)
651 {
652 if ((*j).text)
653 {
654 json_object *text = createJsonString((*j).text);
655 json_object_array_add(sameNameArray, text);
656 }
657 else
658 containers[(*j).node] = sameNameArray;
659 }
660
661 json_object *sameNameArrayContainer;
662 if (isMixedContent)
663 {
664 sameNameArrayContainer = json_object_new_object();
665 json_object_array_add(childContainer, sameNameArrayContainer);
666 }
667 else
668 sameNameArrayContainer = childContainer;
669
670 const char *childName = ((*i).first ? (*i).first : "content");
671 addToContainer(sameNameArrayContainer, childName, sameNameArray);
672// VLog(" %s", json_object_to_json_string(currentContainer));
673 }
674 }
675 }
676
677 for (xmlNode *childNode = currentNode->children; childNode; childNode = childNode->next)
678 if (childNode->type == XML_ELEMENT_NODE)
679 nodesToVisit.push_back(childNode);
680 }
681
682 if (tree.children)
683 {
684 const char *rootKey = NULL;
685 json_object *rootValue = NULL;
686 json_object_object_foreach(topLevelJson, key, value)
687 {
688 rootKey = key;
689 rootValue = value;
690 break;
691 }
692
693 if (! rootValue)
694 {
695 // Change {"rootKey":NULL} to {"rootKey":{}}
696 rootValue = json_object_new_object();
697 json_object_object_add(topLevelJson, rootKey, rootValue);
698 }
699
700 unsigned long childCount = VuoListGetCount_VuoTree(tree.children);
701 for (unsigned long i = 1; i <= childCount; ++i)
702 {
704 if (! child.rootXmlNode)
705 continue;
706
707 json_object *childJson = VuoTree_serializeXmlNodeAsJson(child, false);
708
709 const char *childKey = NULL;
710 json_object *childValue = NULL;
711 json_object_object_foreach(childJson, key, value)
712 {
713 childKey = key;
714 childValue = value;
715 break;
716 }
717
718 json_object *siblingValue = NULL;
719 if (json_object_object_get_ex(rootValue, childKey, &siblingValue))
720 {
721 if (! json_object_is_type(siblingValue, json_type_array))
722 {
723 // Change {"rootKey":{"childKey":...}} to {"rootKey":{"childKey":[...]}
724 json_object *siblingsArray = json_object_new_array();
725 json_object_get(siblingValue);
726 json_object_array_add(siblingsArray, siblingValue);
727 json_object_object_add(rootValue, childKey, siblingsArray);
728 siblingValue = siblingsArray;
729 }
730
731 // Change {"rootKey":{"childKey":[...]} to {"rootKey":{"childKey":[...,childValue]}
732 json_object_array_add(siblingValue, childValue);
733 }
734 else
735 {
736 // Change {"rootKey":{...}} to {"rootKey":{...,"childKey":childValue}}
737 json_object_object_add(rootValue, childKey, childValue);
738 }
739 }
740 }
741
742 return topLevelJson;
743}
744
745
750{
751 json_object *o;
752 if (json_object_object_get_ex(js, "pointer", &o))
753 {
754 xmlNode *node = (xmlNode *)json_object_get_int64(o);
755 VuoTree tree = VuoTree_makeFromXmlNode(node);
756
757 if (json_object_object_get_ex(js, "jsonPointer", &o))
758 tree.rootJson = (json_object *)json_object_get_int64(o);
759
760 if (json_object_object_get_ex(js, "childrenPointer", &o))
761 tree.children = (VuoList_VuoTree)json_object_get_int64(o);
762
763 VuoTree *treePtr = new VuoTree(tree);
764 auto releaseCallback = [](json_object *js, void *userData)
765 {
766 VuoTree *treePtr = static_cast<VuoTree *>(userData);
767 VuoTree_release(*treePtr);
768 delete treePtr;
769 };
770 json_object_set_userdata(js, treePtr, releaseCallback);
771
772 return tree;
773 }
774 else if (json_object_object_get_ex(js, "xml", &o))
775 {
776 const char *treeAsXml = json_object_get_string(o);
777 return VuoTree_parseXml(treeAsXml, true);
778 }
779 else if (json_object_object_get_ex(js, "json", &o))
780 {
781 const char *treeAsJson = json_object_get_string(o);
782 return VuoTree_parseJson(treeAsJson);
783 }
784
785 return VuoTree_makeEmpty();
786}
787
792{
793 json_object *js = json_object_new_object();
794
795 VuoTree_retain(value);
796
797 json_object_object_add(js, "pointer", json_object_new_int64((int64_t)value.rootXmlNode));
798 json_object_object_add(js, "jsonPointer", json_object_new_int64((int64_t)value.rootJson));
799 json_object_object_add(js, "childrenPointer", json_object_new_int64((int64_t)value.children));
800
801 return js;
802}
803
808{
809 json_object *js = json_object_new_object();
810
811 if (value.rootJson)
812 {
813 const char *treeAsJson = json_object_to_json_string(value.rootJson);
814 json_object_object_add(js, "json", json_object_new_string(treeAsJson));
815 }
816 else
817 {
818 xmlChar *treeAsXml = VuoTree_serializeXmlNodeAsXml(value, false, 0);
819 json_object_object_add(js, "xml", json_object_new_string((const char *)treeAsXml));
820 xmlFree(treeAsXml);
821 }
822
823 return js;
824}
825
829char * VuoTree_getSummary(const VuoTree value)
830{
831 ostringstream summary;
832
833 VuoText name = VuoTree_getName(value);
834 VuoLocal(name);
835 if (! VuoText_isEmpty(name))
836 {
837 char *nameSummary = VuoText_getSummary(name);
838 summary << "<div>name: " << nameSummary << "</div>\n";
839 free(nameSummary);
840 }
841
843 unsigned long attributeCount = VuoListGetCount_VuoText(attributes.keys);
844 if (attributeCount > 0)
845 {
846 summary << "attributes: <table>";
847 unsigned long maxAttributes = 5;
848 for (unsigned long i = 1; i <= attributeCount && i <= maxAttributes; ++i)
849 {
850 VuoText name = VuoListGetValue_VuoText(attributes.keys, i);
851 VuoText value = VuoListGetValue_VuoText(attributes.values, i);
852 char *nameSummary = VuoText_getSummary(name);
853 char *valueSummary = VuoText_getSummary(value);
854 summary << "\n<tr><td>" << nameSummary << "</td><td> → " << valueSummary << "</td></tr>";
855 free(nameSummary);
856 free(valueSummary);
857 }
858 if (attributeCount > maxAttributes)
859 summary << "\n<tr><td colspan=\"2\">…</td></tr>";
860 summary << "</table>\n";
861 }
864
865 VuoText content = VuoTree_getContent(value, false);
866 VuoLocal(content);
867 if (! VuoText_isEmpty(content))
868 {
869 char *contentSummary = VuoText_getSummary(content);
870 summary << "<div>content: " << contentSummary << "</div>\n";
871 free(contentSummary);
872 }
873
874 VuoList_VuoTree children = VuoTree_getChildren(value);
875 VuoLocal(children);
876 unsigned long childCount = VuoListGetCount_VuoTree(children);
877 if (childCount > 0)
878 {
879 summary << childCount << " " << (childCount == 1 ? "child" : "children") << "<ul>";
880 VuoTree *childrenArray = VuoListGetData_VuoTree(children);
881 unsigned long maxChildren = 15;
882 for (unsigned long i = 0; i < childCount && i < maxChildren; ++i)
883 {
884 VuoText name = VuoTree_getName(childrenArray[i]);
885 char *nameSummary = VuoText_getSummary(name);
886 if (strlen(nameSummary) > 0 && string(nameSummary).find_first_not_of(' ') != string::npos)
887 summary << "\n<li>" << nameSummary << "</li>";
888 else
889 summary << "\n<li>&nbsp;</li>";
890 free(nameSummary);
891 }
892 if (childCount > maxChildren)
893 summary << "\n<li>…</li>";
894 summary << "</ul>\n";
895 }
896
897 string summaryStr = summary.str();
898 if (summaryStr.back() == '\n')
899 summaryStr = summaryStr.substr(0, summaryStr.length() - 1);
900 if (summaryStr.empty())
901 summaryStr = "Empty tree";
902
903 return strdup(summaryStr.c_str());
904}
905
906
911{
912 return (VuoTree){ NULL, NULL, NULL };
913}
914
919{
920 xmlDoc *doc = xmlNewDoc((const xmlChar *)"1.0");
921 xmlNode *root = createXmlNode(name);
922 xmlDocSetRootElement(doc, root);
923
924 xmlNodeSetContent(root, (const xmlChar *)content);
925
926 unsigned long attributeCount = VuoListGetCount_VuoText(attributes.keys);
927 for (unsigned long i = 1; i <= attributeCount; ++i)
928 {
929 VuoText key = VuoListGetValue_VuoText(attributes.keys, i);
930 VuoText value = VuoListGetValue_VuoText(attributes.values, i);
931 xmlNewProp(root, (const xmlChar *)key, (const xmlChar *)value);
932 }
933
935
936 tree.children = children;
937
938 return tree;
939}
940
945{
946 return VuoTree_parseJson(json);
947}
948
952VuoTree VuoTree_makeFromXmlText(VuoText xml, bool includeWhitespace)
953{
954 return VuoTree_parseXml(xml, includeWhitespace);
955}
956
961{
962 xmlChar *xmlAsString = VuoTree_serializeXmlNodeAsXml(tree, indent, 0);
963 VuoText xml = VuoText_make((const char *)xmlAsString);
964 xmlFree(xmlAsString);
965 return xml;
966}
967
972{
973 if (! tree.rootXmlNode && ! tree.rootJson)
974 return VuoText_make("");
975
976 json_object *json = tree.rootJson;
977 if (! json)
978 json = VuoTree_serializeXmlNodeAsJson(tree, true);
979
980 int flags = (indent ? JSON_C_TO_STRING_PRETTY : JSON_C_TO_STRING_PLAIN);
981 const char *jsonAsString = json_object_to_json_string_ext(json, flags);
982 VuoText jsonAsText = VuoText_make(jsonAsString);
983
984 if (! tree.rootJson)
985 json_object_put(json);
986
987 return jsonAsText;
988}
989
994{
995 if (value.rootXmlNode)
996 VuoRetain(((xmlNode *)value.rootXmlNode)->doc);
997 VuoRetain(value.rootJson);
998 VuoRetain(value.children);
999}
1000
1005{
1006 if (value.rootXmlNode)
1007 VuoRelease(((xmlNode *)value.rootXmlNode)->doc);
1008 VuoRelease(value.rootJson);
1009 VuoRelease(value.children);
1010}
1011
1012
1017{
1018 if (! tree.rootXmlNode)
1019 return VuoText_make("");
1020
1021 const xmlChar *name = ((xmlNode *)tree.rootXmlNode)->name;
1022 return VuoText_make((const char *)name);
1023}
1024
1029{
1031
1032 if (! tree.rootXmlNode)
1033 return attributes;
1034
1035 for (xmlAttr *a = ((xmlNode *)tree.rootXmlNode)->properties; a; a = a->next)
1036 {
1037 const xmlChar *keyAsString = a->name;
1038 VuoText key = VuoText_make((const char *)keyAsString);
1039
1040 xmlChar *valueAsString = xmlNodeListGetString(((xmlNode *)tree.rootXmlNode)->doc, a->children, 1);
1041 VuoText value = VuoText_make((const char *)valueAsString);
1042 xmlFree(valueAsString);
1043
1044 VuoDictionarySetKeyValue_VuoText_VuoText(attributes, key, value);
1045 }
1046
1047 return attributes;
1048}
1049
1055{
1056 if (! tree.rootXmlNode)
1057 return VuoText_make("");
1058
1059 xmlChar *value = xmlGetProp((xmlNode *)tree.rootXmlNode, (const xmlChar *)attribute);
1060 VuoText valueAsText = VuoText_make((const char *)value);
1061 xmlFree(value);
1062 return valueAsText;
1063}
1064
1068static xmlChar * VuoTree_getContentOfXmlNode(xmlNode *node)
1069{
1070 xmlChar *content = xmlStrdup((const xmlChar *)"");
1071 for (xmlNode *n = node->children; n; n = n->next)
1072 if (n->type == XML_TEXT_NODE || n->type == XML_CDATA_SECTION_NODE)
1073 content = xmlStrcat(content, n->content);
1074
1075 return content;
1076}
1077
1081VuoText VuoTree_getContent(VuoTree tree, bool includeDescendants)
1082{
1083 if (! tree.rootXmlNode)
1084 return VuoText_make("");
1085
1086 xmlChar *content = NULL;
1087
1088 if (includeDescendants)
1089 {
1090 content = xmlNodeGetContent( (xmlNode *)tree.rootXmlNode );
1091
1092 if (tree.children)
1093 {
1094 unsigned long childCount = VuoListGetCount_VuoTree(tree.children);
1095 for (unsigned long i = 1; i <= childCount; ++i)
1096 {
1097 VuoTree child = VuoListGetValue_VuoTree(tree.children, i);
1098 VuoText childContent = VuoTree_getContent(child, true);
1099 content = xmlStrcat(content, (const xmlChar *)childContent);
1100 }
1101 }
1102 }
1103 else
1104 {
1105 content = VuoTree_getContentOfXmlNode( (xmlNode *)tree.rootXmlNode );
1106 }
1107
1108 VuoText contentAsText = VuoText_make((const char *)content);
1109
1110 xmlFree(content);
1111
1112 return contentAsText;
1113}
1114
1119{
1120 if (tree.children)
1121 return VuoListCopy_VuoTree(tree.children);
1122
1124
1125 if (! tree.rootXmlNode)
1126 return children;
1127
1128 for (xmlNode *n = ((xmlNode *)tree.rootXmlNode)->children; n; n = n->next)
1129 {
1130 if (n->type == XML_ELEMENT_NODE)
1131 {
1133 VuoListAppendValue_VuoTree(children, child);
1134 }
1135 }
1136
1137 return children;
1138}
1139
1144{
1145 VuoText jsonText = VuoTree_serializeAsJson(tree, false);
1146 VuoLocal(jsonText);
1147
1148 json_object *json = json_tokener_parse(jsonText);
1149
1150 json_object *child = NULL;
1151 if (json)
1152 {
1153 json_object_object_foreach(json, key, val)
1154 {
1155 child = val;
1156 json_object_get(child);
1157 break;
1158 }
1159
1160 json_object_put(json);
1161 }
1162
1163 return child;
1164}
1165
1173{
1174 VuoTree treeIncludingChildren;
1175 if (tree.children)
1176 {
1177 // The children may belong to different XML docs, so build a new XML doc that includes them.
1178 xmlChar *treeAsXml = VuoTree_serializeXmlNodeAsXml(tree, false, 0);
1179 treeIncludingChildren = VuoTree_parseXml((const char *)treeAsXml, false);
1180 VuoTree_retain(treeIncludingChildren);
1181 xmlFree(treeAsXml);
1182 }
1183 else
1184 {
1185 treeIncludingChildren = tree;
1186 }
1187 VuoDefer(^{ if (tree.children) VuoTree_release(treeIncludingChildren); });
1188
1189 xmlNode *treeRoot = (xmlNode *)treeIncludingChildren.rootXmlNode;
1190
1191 if (! treeRoot || VuoText_isEmpty(xpath))
1192 return VuoListCreate_VuoTree();
1193
1194 xmlXPathContext *xpathContext = xmlXPathNewContext(treeRoot->doc);
1195 if (! xpathContext)
1196 {
1197 VUserLog("Error: Couldn't create xmlXPathContext");
1198 return VuoListCreate_VuoTree();
1199 }
1200 VuoDefer(^{ xmlXPathFreeContext(xpathContext); });
1201
1202 // Replace smartquotes with plain quotes in the XPath.
1203 const int numSmartquotes = 4;
1204 const char *smartquotes[numSmartquotes] = { "“", "”", "‘", "’" };
1205 const char *plainquotes[numSmartquotes] = { "\"", "\"", "'", "'" };
1206 VuoText requotedXpaths[numSmartquotes];
1207 for (int i = 0; i < numSmartquotes; ++i)
1208 {
1209 requotedXpaths[i] = VuoText_replace(xpath, smartquotes[i], plainquotes[i]);
1210 xpath = requotedXpaths[i];
1211 }
1212 VuoText *requotedXPathsPtr = requotedXpaths;
1213 VuoDefer(^{
1214 for (int i = 0; i < numSmartquotes; ++i)
1215 {
1216 if (requotedXPathsPtr[i] != xpath)
1217 {
1218 VuoRetain(requotedXPathsPtr[i]);
1219 VuoRelease(requotedXPathsPtr[i]);
1220 }
1221 }
1222 });
1223
1224 // Set the xmlNode that relative XPaths are relative to — the root of the tree.
1225 int ret = xmlXPathSetContextNode(treeRoot, xpathContext);
1226 if (ret < 0)
1227 {
1228 VUserLog("Error: Couldn't set context node to '%s'", treeRoot->name);
1229 return VuoListCreate_VuoTree();
1230 }
1231
1232 // Is the tree a child sharing an xmlDoc with its parent?
1233 bool isSubtree = (treeRoot != xmlDocGetRootElement(treeRoot->doc));
1234
1235 xmlChar *prefixedXpath;
1236 if (isSubtree && xpath[0] == '/')
1237 {
1238 // Child tree + absolute path: Construct the full absolute path for the xmlDoc.
1239 xmlChar *xpathPrefix = xmlStrdup((const xmlChar *)"");
1240 for (xmlNode *parent = treeRoot->parent; parent; parent = parent->parent)
1241 {
1242 if (parent->type == XML_ELEMENT_NODE)
1243 {
1244 xpathPrefix = xmlStrcat(xmlStrdup(parent->name), xpathPrefix);
1245 xpathPrefix = xmlStrcat(xmlStrdup((const xmlChar *)"/"), xpathPrefix);
1246 }
1247 }
1248 prefixedXpath = xmlStrcat(xpathPrefix, (const xmlChar *)xpath);
1249 }
1250 else
1251 {
1252 prefixedXpath = xmlStrdup((const xmlChar *)xpath);
1253 }
1254 VuoDefer(^{ xmlFree(prefixedXpath); });
1255
1256 // Perform the query.
1257 xmlXPathObject *xpathObject = xmlXPathEvalExpression(prefixedXpath, xpathContext);
1258 if (! xpathObject)
1259 {
1260 VUserLog("Error: Couldn't evaluate XPath expression '%s'", prefixedXpath);
1261 return VuoListCreate_VuoTree();
1262 }
1263 VuoDefer(^{ xmlXPathFreeObject(xpathObject); });
1264
1265 if (xmlXPathNodeSetIsEmpty(xpathObject->nodesetval))
1266 return VuoListCreate_VuoTree();
1267
1268 // Turn the found nodes into a set of unique element nodes.
1269 vector<xmlNode *> foundNodes;
1270 map<xmlNode *, vector<xmlNode *> > attributesForFoundNode;
1271 for (int i = 0; i < xpathObject->nodesetval->nodeNr; ++i)
1272 {
1273 xmlNode *foundNode = xpathObject->nodesetval->nodeTab[i];
1274
1275 if (foundNode->type == XML_ATTRIBUTE_NODE || foundNode->type == XML_TEXT_NODE || foundNode->type == XML_CDATA_SECTION_NODE)
1276 {
1277 if (find(foundNodes.begin(), foundNodes.end(), foundNode->parent) == foundNodes.end())
1278 foundNodes.push_back(foundNode->parent);
1279
1280 if (foundNode->type == XML_ATTRIBUTE_NODE)
1281 attributesForFoundNode[foundNode->parent].push_back(foundNode);
1282 }
1283 else if (foundNode->type == XML_ELEMENT_NODE)
1284 {
1285 foundNodes.push_back(foundNode);
1286 }
1287 }
1288
1289 // Turn the unique element nodes into VuoTrees.
1291 for (vector<xmlNode *>::iterator i = foundNodes.begin(); i != foundNodes.end(); ++i)
1292 {
1293 xmlNode *foundNode = *i;
1294
1295 if (isSubtree)
1296 {
1297 // Child tree: Skip found xmlNodes that are within the xmlDoc but not this subtree.
1298 bool isNodeInTree = false;
1299 for (xmlNode *n = foundNode; n; n = n->parent)
1300 {
1301 if (n == treeRoot)
1302 {
1303 isNodeInTree = true;
1304 break;
1305 }
1306 }
1307 if (! isNodeInTree)
1308 continue;
1309 }
1310
1311 VuoTree foundTree;
1312 map<xmlNode *, vector<xmlNode *> >::iterator attributesIter = attributesForFoundNode.find(foundNode);
1313 if (attributesIter == attributesForFoundNode.end())
1314 {
1315 // Searching for elements or text: The returned tree is a subtree of @a tree (including attributes and content).
1316 foundTree = VuoTree_makeFromXmlNode(foundNode);
1317 }
1318 else
1319 {
1320 // Searching for attributes: The returned tree contains only the parent element and found attributes.
1322 for (vector<xmlNode *>::iterator j = attributesIter->second.begin(); j != attributesIter->second.end(); ++j)
1323 {
1324 xmlNode *foundAttribute = *j;
1325 VuoText key = VuoText_make((const char *)foundAttribute->name);
1326 VuoText value = VuoText_make((const char *)xmlNodeGetContent(foundAttribute));
1327 VuoDictionarySetKeyValue_VuoText_VuoText(attributes, key, value);
1328 }
1329 foundTree = VuoTree_make((const char *)foundNode->name, attributes, "", NULL);
1330 }
1331
1332 VuoListAppendValue_VuoTree(foundTrees, foundTree);
1333 }
1334
1335 return foundTrees;
1336}
1337
1341static bool compareName(xmlNode *node, VuoText name, VuoTextComparison comparison, VuoText unused)
1342{
1343 return VuoText_compare((const char *)node->name, comparison, name);
1344}
1345
1349static bool compareAttribute(xmlNode *node, VuoText value, VuoTextComparison comparison, VuoText attribute)
1350{
1351 xmlChar *actualValue = xmlGetProp(node, (const xmlChar *)attribute);
1352 VuoDefer(^{ xmlFree(actualValue); });
1353 return VuoText_compare((const char *)actualValue, comparison, value);
1354}
1355
1360static bool compareContent(xmlNode *node, VuoText content, VuoTextComparison comparison, VuoText unused)
1361{
1362 xmlChar *actualContent = VuoTree_getContentOfXmlNode(node);
1363 VuoDefer(^{ xmlFree(actualContent); });
1364 return VuoText_compare((const char *)actualContent, comparison, content);
1365}
1366
1372 VuoText targetText, VuoTextComparison comparison, VuoText attribute,
1373 bool includeDescendants, bool atFindRoot)
1374{
1375 xmlNode *treeRoot = (xmlNode *)tree.rootXmlNode;
1376
1377 if (! treeRoot)
1378 return VuoListCreate_VuoTree();
1379
1381 if (compare(treeRoot, targetText, comparison, attribute))
1382 VuoListAppendValue_VuoTree(foundTrees, tree);
1383
1384 if (atFindRoot || includeDescendants)
1385 {
1386 if (tree.children)
1387 {
1388 unsigned long childCount = VuoListGetCount_VuoTree(tree.children);
1389 for (unsigned long i = 1; i <= childCount; ++i)
1390 {
1391 VuoTree child = VuoListGetValue_VuoTree(tree.children, i);
1392 VuoList_VuoTree childFoundTrees = VuoTree_findItems(child, compare, targetText, comparison, attribute, includeDescendants, false);
1393 unsigned long foundCount = VuoListGetCount_VuoTree(childFoundTrees);
1394 for (unsigned long j = 1; j <= foundCount; ++j)
1395 {
1396 VuoTree childFoundTree = VuoListGetValue_VuoTree(childFoundTrees, j);
1397 VuoListAppendValue_VuoTree(foundTrees, childFoundTree);
1398 }
1399 }
1400 }
1401 else
1402 {
1403 list<xmlNode *> nodesToVisit;
1404 for (xmlNode *n = treeRoot->children; n; n = n->next)
1405 if (n->type == XML_ELEMENT_NODE)
1406 nodesToVisit.push_back(n);
1407
1408 while (! nodesToVisit.empty())
1409 {
1410 xmlNode *node = nodesToVisit.front();
1411 nodesToVisit.pop_front();
1412
1413 if (compare(node, targetText, comparison, attribute))
1414 {
1415 VuoTree foundTree = VuoTree_makeFromXmlNode(node);
1416 VuoListAppendValue_VuoTree(foundTrees, foundTree);
1417 }
1418
1419 if (includeDescendants)
1420 for (xmlNode *n = node->children; n; n = n->next)
1421 if (n->type == XML_ELEMENT_NODE)
1422 nodesToVisit.push_back(n);
1423 }
1424 }
1425 }
1426
1427 return foundTrees;
1428}
1429
1436VuoList_VuoTree VuoTree_findItemsWithName(VuoTree tree, VuoText name, VuoTextComparison comparison, bool includeDescendants)
1437{
1438 return VuoTree_findItems(tree, compareName, name, comparison, NULL, includeDescendants, true);
1439}
1440
1448VuoList_VuoTree VuoTree_findItemsWithAttribute(VuoTree tree, VuoText attribute, VuoText value, VuoTextComparison valueComparison, bool includeDescendants)
1449{
1450 return VuoTree_findItems(tree, compareAttribute, value, valueComparison, attribute, includeDescendants, true);
1451}
1452
1462VuoList_VuoTree VuoTree_findItemsWithContent(VuoTree tree, VuoText content, VuoTextComparison comparison, bool includeDescendants)
1463{
1464 return VuoTree_findItems(tree, compareContent, content, comparison, NULL, includeDescendants, true);
1465}