Vuo  2.3.2
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>
16 using namespace std;
17 
18 #include "type.h"
19 
20 extern "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 
48 static const char *encoding = "UTF-8";
49 
53 static void VuoTree_freeXmlDoc(void *value)
54 {
55  xmlFreeDoc( (xmlDocPtr)value );
56 }
57 
61 static void VuoTree_freeJsonObject(void *value)
62 {
63  json_object_put((json_object *)value);
64 }
65 
66 
73 static 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 
87 static VuoTree VuoTree_makeFromXmlDoc(xmlDoc *doc)
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 
104 static 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 
177 static 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 
223 static 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 
301  VuoTree child = VuoListGetValue_VuoTree(tree.children, i);
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 
318 static 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 
329 static 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 
444  VuoTree tree = VuoTree_makeFromXmlDoc(doc);
445 
446  tree.rootJson = json_tokener_parse(jsonString); // Use the original JSON, not the modified one from above.
448 
449  return tree;
450 }
451 
455 typedef struct
456 {
457  xmlNode *node;
458  const char *text;
459 } ChildInfo;
460 
464 static 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 
490 static 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  {
703  VuoTree child = VuoListGetValue_VuoTree(tree.children, i);
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  return tree;
764  }
765  else if (json_object_object_get_ex(js, "xml", &o))
766  {
767  const char *treeAsXml = json_object_get_string(o);
768  return VuoTree_parseXml(treeAsXml, true);
769  }
770  else if (json_object_object_get_ex(js, "json", &o))
771  {
772  const char *treeAsJson = json_object_get_string(o);
773  return VuoTree_parseJson(treeAsJson);
774  }
775 
776  return VuoTree_makeEmpty();
777 }
778 
782 struct json_object * VuoTree_getJson(const VuoTree value)
783 {
784  json_object *js = json_object_new_object();
785 
786  json_object_object_add(js, "pointer", json_object_new_int64((int64_t)value.rootXmlNode));
787  json_object_object_add(js, "jsonPointer", json_object_new_int64((int64_t)value.rootJson));
788  json_object_object_add(js, "childrenPointer", json_object_new_int64((int64_t)value.children));
789 
790  return js;
791 }
792 
797 {
798  json_object *js = json_object_new_object();
799 
800  if (value.rootJson)
801  {
802  const char *treeAsJson = json_object_to_json_string(value.rootJson);
803  json_object_object_add(js, "json", json_object_new_string(treeAsJson));
804  }
805  else
806  {
807  xmlChar *treeAsXml = VuoTree_serializeXmlNodeAsXml(value, false, 0);
808  json_object_object_add(js, "xml", json_object_new_string((const char *)treeAsXml));
809  xmlFree(treeAsXml);
810  }
811 
812  return js;
813 }
814 
818 char * VuoTree_getSummary(const VuoTree value)
819 {
820  ostringstream summary;
821 
822  VuoText name = VuoTree_getName(value);
823  VuoLocal(name);
824  if (! VuoText_isEmpty(name))
825  {
826  char *nameSummary = VuoText_getSummary(name);
827  summary << "<div>name: " << nameSummary << "</div>\n";
828  free(nameSummary);
829  }
830 
832  unsigned long attributeCount = VuoListGetCount_VuoText(attributes.keys);
833  if (attributeCount > 0)
834  {
835  summary << "attributes: <table>";
836  unsigned long maxAttributes = 5;
837  for (unsigned long i = 1; i <= attributeCount && i <= maxAttributes; ++i)
838  {
839  VuoText name = VuoListGetValue_VuoText(attributes.keys, i);
840  VuoText value = VuoListGetValue_VuoText(attributes.values, i);
841  char *nameSummary = VuoText_getSummary(name);
842  char *valueSummary = VuoText_getSummary(value);
843  summary << "\n<tr><td>" << nameSummary << "</td><td> → " << valueSummary << "</td></tr>";
844  free(nameSummary);
845  free(valueSummary);
846  }
847  if (attributeCount > maxAttributes)
848  summary << "\n<tr><td colspan=\"2\">…</td></tr>";
849  summary << "</table>\n";
850  }
853 
854  VuoText content = VuoTree_getContent(value, false);
855  VuoLocal(content);
856  if (! VuoText_isEmpty(content))
857  {
858  char *contentSummary = VuoText_getSummary(content);
859  summary << "<div>content: " << contentSummary << "</div>\n";
860  free(contentSummary);
861  }
862 
863  VuoList_VuoTree children = VuoTree_getChildren(value);
864  VuoLocal(children);
865  unsigned long childCount = VuoListGetCount_VuoTree(children);
866  if (childCount > 0)
867  {
868  summary << childCount << " " << (childCount == 1 ? "child" : "children") << "<ul>";
869  VuoTree *childrenArray = VuoListGetData_VuoTree(children);
870  unsigned long maxChildren = 15;
871  for (unsigned long i = 0; i < childCount && i < maxChildren; ++i)
872  {
873  VuoText name = VuoTree_getName(childrenArray[i]);
874  char *nameSummary = VuoText_getSummary(name);
875  if (strlen(nameSummary) > 0 && string(nameSummary).find_first_not_of(' ') != string::npos)
876  summary << "\n<li>" << nameSummary << "</li>";
877  else
878  summary << "\n<li>&nbsp;</li>";
879  free(nameSummary);
880  }
881  if (childCount > maxChildren)
882  summary << "\n<li>…</li>";
883  summary << "</ul>\n";
884  }
885 
886  string summaryStr = summary.str();
887  if (summaryStr.back() == '\n')
888  summaryStr = summaryStr.substr(0, summaryStr.length() - 1);
889  if (summaryStr.empty())
890  summaryStr = "Empty tree";
891 
892  return strdup(summaryStr.c_str());
893 }
894 
895 
900 {
901  return (VuoTree){ NULL, NULL, NULL };
902 }
903 
908 {
909  xmlDoc *doc = xmlNewDoc((const xmlChar *)"1.0");
910  xmlNode *root = createXmlNode(name);
911  xmlDocSetRootElement(doc, root);
912 
913  xmlNodeSetContent(root, (const xmlChar *)content);
914 
915  unsigned long attributeCount = VuoListGetCount_VuoText(attributes.keys);
916  for (unsigned long i = 1; i <= attributeCount; ++i)
917  {
918  VuoText key = VuoListGetValue_VuoText(attributes.keys, i);
919  VuoText value = VuoListGetValue_VuoText(attributes.values, i);
920  xmlNewProp(root, (const xmlChar *)key, (const xmlChar *)value);
921  }
922 
923  VuoTree tree = VuoTree_makeFromXmlDoc(doc);
924 
925  tree.children = children;
926 
927  return tree;
928 }
929 
934 {
935  return VuoTree_parseJson(json);
936 }
937 
941 VuoTree VuoTree_makeFromXmlText(VuoText xml, bool includeWhitespace)
942 {
943  return VuoTree_parseXml(xml, includeWhitespace);
944 }
945 
950 {
951  xmlChar *xmlAsString = VuoTree_serializeXmlNodeAsXml(tree, indent, 0);
952  VuoText xml = VuoText_make((const char *)xmlAsString);
953  xmlFree(xmlAsString);
954  return xml;
955 }
956 
961 {
962  if (! tree.rootXmlNode && ! tree.rootJson)
963  return VuoText_make("");
964 
965  json_object *json = tree.rootJson;
966  if (! json)
967  json = VuoTree_serializeXmlNodeAsJson(tree, true);
968 
969  int flags = (indent ? JSON_C_TO_STRING_PRETTY : JSON_C_TO_STRING_PLAIN);
970  const char *jsonAsString = json_object_to_json_string_ext(json, flags);
971  VuoText jsonAsText = VuoText_make(jsonAsString);
972 
973  if (! tree.rootJson)
974  json_object_put(json);
975 
976  return jsonAsText;
977 }
978 
983 {
984  if (value.rootXmlNode)
985  VuoRetain(((xmlNode *)value.rootXmlNode)->doc);
986  VuoRetain(value.rootJson);
987  VuoRetain(value.children);
988 }
989 
994 {
995  if (value.rootXmlNode)
996  VuoRelease(((xmlNode *)value.rootXmlNode)->doc);
997  VuoRelease(value.rootJson);
998  VuoRelease(value.children);
999 }
1000 
1001 
1006 {
1007  if (! tree.rootXmlNode)
1008  return VuoText_make("");
1009 
1010  const xmlChar *name = ((xmlNode *)tree.rootXmlNode)->name;
1011  return VuoText_make((const char *)name);
1012 }
1013 
1018 {
1020 
1021  if (! tree.rootXmlNode)
1022  return attributes;
1023 
1024  for (xmlAttr *a = ((xmlNode *)tree.rootXmlNode)->properties; a; a = a->next)
1025  {
1026  const xmlChar *keyAsString = a->name;
1027  VuoText key = VuoText_make((const char *)keyAsString);
1028 
1029  xmlChar *valueAsString = xmlNodeListGetString(((xmlNode *)tree.rootXmlNode)->doc, a->children, 1);
1030  VuoText value = VuoText_make((const char *)valueAsString);
1031  xmlFree(valueAsString);
1032 
1033  VuoDictionarySetKeyValue_VuoText_VuoText(attributes, key, value);
1034  }
1035 
1036  return attributes;
1037 }
1038 
1044 {
1045  if (! tree.rootXmlNode)
1046  return VuoText_make("");
1047 
1048  xmlChar *value = xmlGetProp((xmlNode *)tree.rootXmlNode, (const xmlChar *)attribute);
1049  VuoText valueAsText = VuoText_make((const char *)value);
1050  xmlFree(value);
1051  return valueAsText;
1052 }
1053 
1057 static xmlChar * VuoTree_getContentOfXmlNode(xmlNode *node)
1058 {
1059  xmlChar *content = xmlStrdup((const xmlChar *)"");
1060  for (xmlNode *n = node->children; n; n = n->next)
1061  if (n->type == XML_TEXT_NODE || n->type == XML_CDATA_SECTION_NODE)
1062  content = xmlStrcat(content, n->content);
1063 
1064  return content;
1065 }
1066 
1070 VuoText VuoTree_getContent(VuoTree tree, bool includeDescendants)
1071 {
1072  if (! tree.rootXmlNode)
1073  return VuoText_make("");
1074 
1075  xmlChar *content = NULL;
1076 
1077  if (includeDescendants)
1078  {
1079  content = xmlNodeGetContent( (xmlNode *)tree.rootXmlNode );
1080 
1081  if (tree.children)
1082  {
1083  unsigned long childCount = VuoListGetCount_VuoTree(tree.children);
1084  for (unsigned long i = 1; i <= childCount; ++i)
1085  {
1086  VuoTree child = VuoListGetValue_VuoTree(tree.children, i);
1087  VuoText childContent = VuoTree_getContent(child, true);
1088  content = xmlStrcat(content, (const xmlChar *)childContent);
1089  }
1090  }
1091  }
1092  else
1093  {
1094  content = VuoTree_getContentOfXmlNode( (xmlNode *)tree.rootXmlNode );
1095  }
1096 
1097  VuoText contentAsText = VuoText_make((const char *)content);
1098 
1099  xmlFree(content);
1100 
1101  return contentAsText;
1102 }
1103 
1108 {
1109  if (tree.children)
1110  return VuoListCopy_VuoTree(tree.children);
1111 
1113 
1114  if (! tree.rootXmlNode)
1115  return children;
1116 
1117  for (xmlNode *n = ((xmlNode *)tree.rootXmlNode)->children; n; n = n->next)
1118  {
1119  if (n->type == XML_ELEMENT_NODE)
1120  {
1121  VuoTree child = VuoTree_makeFromXmlNode(n);
1122  VuoListAppendValue_VuoTree(children, child);
1123  }
1124  }
1125 
1126  return children;
1127 }
1128 
1133 {
1134  VuoText jsonText = VuoTree_serializeAsJson(tree, false);
1135  VuoLocal(jsonText);
1136 
1137  json_object *json = json_tokener_parse(jsonText);
1138 
1139  json_object *child = NULL;
1140  if (json)
1141  {
1142  json_object_object_foreach(json, key, val)
1143  {
1144  child = val;
1145  json_object_get(child);
1146  break;
1147  }
1148 
1149  json_object_put(json);
1150  }
1151 
1152  return child;
1153 }
1154 
1162 {
1163  VuoTree treeIncludingChildren;
1164  if (tree.children)
1165  {
1166  // The children may belong to different XML docs, so build a new XML doc that includes them.
1167  xmlChar *treeAsXml = VuoTree_serializeXmlNodeAsXml(tree, false, 0);
1168  treeIncludingChildren = VuoTree_parseXml((const char *)treeAsXml, false);
1169  VuoTree_retain(treeIncludingChildren);
1170  xmlFree(treeAsXml);
1171  }
1172  else
1173  {
1174  treeIncludingChildren = tree;
1175  }
1176  VuoDefer(^{ if (tree.children) VuoTree_release(treeIncludingChildren); });
1177 
1178  xmlNode *treeRoot = (xmlNode *)treeIncludingChildren.rootXmlNode;
1179 
1180  if (! treeRoot || VuoText_isEmpty(xpath))
1181  return VuoListCreate_VuoTree();
1182 
1183  xmlXPathContext *xpathContext = xmlXPathNewContext(treeRoot->doc);
1184  if (! xpathContext)
1185  {
1186  VUserLog("Error: Couldn't create xmlXPathContext");
1187  return VuoListCreate_VuoTree();
1188  }
1189  VuoDefer(^{ xmlXPathFreeContext(xpathContext); });
1190 
1191  // Replace smartquotes with plain quotes in the XPath.
1192  const int numSmartquotes = 4;
1193  const char *smartquotes[numSmartquotes] = { "“", "”", "‘", "’" };
1194  const char *plainquotes[numSmartquotes] = { "\"", "\"", "'", "'" };
1195  VuoText requotedXpaths[numSmartquotes];
1196  for (int i = 0; i < numSmartquotes; ++i)
1197  {
1198  requotedXpaths[i] = VuoText_replace(xpath, smartquotes[i], plainquotes[i]);
1199  xpath = requotedXpaths[i];
1200  }
1201  VuoText *requotedXPathsPtr = requotedXpaths;
1202  VuoDefer(^{
1203  for (int i = 0; i < numSmartquotes; ++i)
1204  {
1205  if (requotedXPathsPtr[i] != xpath)
1206  {
1207  VuoRetain(requotedXPathsPtr[i]);
1208  VuoRelease(requotedXPathsPtr[i]);
1209  }
1210  }
1211  });
1212 
1213  // Set the xmlNode that relative XPaths are relative to — the root of the tree.
1214  int ret = xmlXPathSetContextNode(treeRoot, xpathContext);
1215  if (ret < 0)
1216  {
1217  VUserLog("Error: Couldn't set context node to '%s'", treeRoot->name);
1218  return VuoListCreate_VuoTree();
1219  }
1220 
1221  // Is the tree a child sharing an xmlDoc with its parent?
1222  bool isSubtree = (treeRoot != xmlDocGetRootElement(treeRoot->doc));
1223 
1224  xmlChar *prefixedXpath;
1225  if (isSubtree && xpath[0] == '/')
1226  {
1227  // Child tree + absolute path: Construct the full absolute path for the xmlDoc.
1228  xmlChar *xpathPrefix = xmlStrdup((const xmlChar *)"");
1229  for (xmlNode *parent = treeRoot->parent; parent; parent = parent->parent)
1230  {
1231  if (parent->type == XML_ELEMENT_NODE)
1232  {
1233  xpathPrefix = xmlStrcat(xmlStrdup(parent->name), xpathPrefix);
1234  xpathPrefix = xmlStrcat(xmlStrdup((const xmlChar *)"/"), xpathPrefix);
1235  }
1236  }
1237  prefixedXpath = xmlStrcat(xpathPrefix, (const xmlChar *)xpath);
1238  }
1239  else
1240  {
1241  prefixedXpath = xmlStrdup((const xmlChar *)xpath);
1242  }
1243  VuoDefer(^{ xmlFree(prefixedXpath); });
1244 
1245  // Perform the query.
1246  xmlXPathObject *xpathObject = xmlXPathEvalExpression(prefixedXpath, xpathContext);
1247  if (! xpathObject)
1248  {
1249  VUserLog("Error: Couldn't evaluate XPath expression '%s'", prefixedXpath);
1250  return VuoListCreate_VuoTree();
1251  }
1252  VuoDefer(^{ xmlXPathFreeObject(xpathObject); });
1253 
1254  if (xmlXPathNodeSetIsEmpty(xpathObject->nodesetval))
1255  return VuoListCreate_VuoTree();
1256 
1257  // Turn the found nodes into a set of unique element nodes.
1258  vector<xmlNode *> foundNodes;
1259  map<xmlNode *, vector<xmlNode *> > attributesForFoundNode;
1260  for (int i = 0; i < xpathObject->nodesetval->nodeNr; ++i)
1261  {
1262  xmlNode *foundNode = xpathObject->nodesetval->nodeTab[i];
1263 
1264  if (foundNode->type == XML_ATTRIBUTE_NODE || foundNode->type == XML_TEXT_NODE || foundNode->type == XML_CDATA_SECTION_NODE)
1265  {
1266  if (find(foundNodes.begin(), foundNodes.end(), foundNode->parent) == foundNodes.end())
1267  foundNodes.push_back(foundNode->parent);
1268 
1269  if (foundNode->type == XML_ATTRIBUTE_NODE)
1270  attributesForFoundNode[foundNode->parent].push_back(foundNode);
1271  }
1272  else if (foundNode->type == XML_ELEMENT_NODE)
1273  {
1274  foundNodes.push_back(foundNode);
1275  }
1276  }
1277 
1278  // Turn the unique element nodes into VuoTrees.
1279  VuoList_VuoTree foundTrees = VuoListCreate_VuoTree();
1280  for (vector<xmlNode *>::iterator i = foundNodes.begin(); i != foundNodes.end(); ++i)
1281  {
1282  xmlNode *foundNode = *i;
1283 
1284  if (isSubtree)
1285  {
1286  // Child tree: Skip found xmlNodes that are within the xmlDoc but not this subtree.
1287  bool isNodeInTree = false;
1288  for (xmlNode *n = foundNode; n; n = n->parent)
1289  {
1290  if (n == treeRoot)
1291  {
1292  isNodeInTree = true;
1293  break;
1294  }
1295  }
1296  if (! isNodeInTree)
1297  continue;
1298  }
1299 
1300  VuoTree foundTree;
1301  map<xmlNode *, vector<xmlNode *> >::iterator attributesIter = attributesForFoundNode.find(foundNode);
1302  if (attributesIter == attributesForFoundNode.end())
1303  {
1304  // Searching for elements or text: The returned tree is a subtree of @a tree (including attributes and content).
1305  foundTree = VuoTree_makeFromXmlNode(foundNode);
1306  }
1307  else
1308  {
1309  // Searching for attributes: The returned tree contains only the parent element and found attributes.
1311  for (vector<xmlNode *>::iterator j = attributesIter->second.begin(); j != attributesIter->second.end(); ++j)
1312  {
1313  xmlNode *foundAttribute = *j;
1314  VuoText key = VuoText_make((const char *)foundAttribute->name);
1315  VuoText value = VuoText_make((const char *)xmlNodeGetContent(foundAttribute));
1316  VuoDictionarySetKeyValue_VuoText_VuoText(attributes, key, value);
1317  }
1318  foundTree = VuoTree_make((const char *)foundNode->name, attributes, "", NULL);
1319  }
1320 
1321  VuoListAppendValue_VuoTree(foundTrees, foundTree);
1322  }
1323 
1324  return foundTrees;
1325 }
1326 
1330 static bool compareName(xmlNode *node, VuoText name, VuoTextComparison comparison, VuoText unused)
1331 {
1332  return VuoText_compare((const char *)node->name, comparison, name);
1333 }
1334 
1338 static bool compareAttribute(xmlNode *node, VuoText value, VuoTextComparison comparison, VuoText attribute)
1339 {
1340  xmlChar *actualValue = xmlGetProp(node, (const xmlChar *)attribute);
1341  VuoDefer(^{ xmlFree(actualValue); });
1342  return VuoText_compare((const char *)actualValue, comparison, value);
1343 }
1344 
1349 static bool compareContent(xmlNode *node, VuoText content, VuoTextComparison comparison, VuoText unused)
1350 {
1351  xmlChar *actualContent = VuoTree_getContentOfXmlNode(node);
1352  VuoDefer(^{ xmlFree(actualContent); });
1353  return VuoText_compare((const char *)actualContent, comparison, content);
1354 }
1355 
1361  VuoText targetText, VuoTextComparison comparison, VuoText attribute,
1362  bool includeDescendants, bool atFindRoot)
1363 {
1364  xmlNode *treeRoot = (xmlNode *)tree.rootXmlNode;
1365 
1366  if (! treeRoot)
1367  return VuoListCreate_VuoTree();
1368 
1369  VuoList_VuoTree foundTrees = VuoListCreate_VuoTree();
1370  if (compare(treeRoot, targetText, comparison, attribute))
1371  VuoListAppendValue_VuoTree(foundTrees, tree);
1372 
1373  if (atFindRoot || includeDescendants)
1374  {
1375  if (tree.children)
1376  {
1377  unsigned long childCount = VuoListGetCount_VuoTree(tree.children);
1378  for (unsigned long i = 1; i <= childCount; ++i)
1379  {
1380  VuoTree child = VuoListGetValue_VuoTree(tree.children, i);
1381  VuoList_VuoTree childFoundTrees = VuoTree_findItems(child, compare, targetText, comparison, attribute, includeDescendants, false);
1382  unsigned long foundCount = VuoListGetCount_VuoTree(childFoundTrees);
1383  for (unsigned long j = 1; j <= foundCount; ++j)
1384  {
1385  VuoTree childFoundTree = VuoListGetValue_VuoTree(childFoundTrees, j);
1386  VuoListAppendValue_VuoTree(foundTrees, childFoundTree);
1387  }
1388  }
1389  }
1390  else
1391  {
1392  list<xmlNode *> nodesToVisit;
1393  for (xmlNode *n = treeRoot->children; n; n = n->next)
1394  if (n->type == XML_ELEMENT_NODE)
1395  nodesToVisit.push_back(n);
1396 
1397  while (! nodesToVisit.empty())
1398  {
1399  xmlNode *node = nodesToVisit.front();
1400  nodesToVisit.pop_front();
1401 
1402  if (compare(node, targetText, comparison, attribute))
1403  {
1404  VuoTree foundTree = VuoTree_makeFromXmlNode(node);
1405  VuoListAppendValue_VuoTree(foundTrees, foundTree);
1406  }
1407 
1408  if (includeDescendants)
1409  for (xmlNode *n = node->children; n; n = n->next)
1410  if (n->type == XML_ELEMENT_NODE)
1411  nodesToVisit.push_back(n);
1412  }
1413  }
1414  }
1415 
1416  return foundTrees;
1417 }
1418 
1425 VuoList_VuoTree VuoTree_findItemsWithName(VuoTree tree, VuoText name, VuoTextComparison comparison, bool includeDescendants)
1426 {
1427  return VuoTree_findItems(tree, compareName, name, comparison, NULL, includeDescendants, true);
1428 }
1429 
1437 VuoList_VuoTree VuoTree_findItemsWithAttribute(VuoTree tree, VuoText attribute, VuoText value, VuoTextComparison valueComparison, bool includeDescendants)
1438 {
1439  return VuoTree_findItems(tree, compareAttribute, value, valueComparison, attribute, includeDescendants, true);
1440 }
1441 
1451 VuoList_VuoTree VuoTree_findItemsWithContent(VuoTree tree, VuoText content, VuoTextComparison comparison, bool includeDescendants)
1452 {
1453  return VuoTree_findItems(tree, compareContent, content, comparison, NULL, includeDescendants, true);
1454 }