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