Vuo  2.0.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
44 }
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>";
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 << "<tr><td>" << nameSummary << "</td><td> → " << valueSummary << "</td></tr>";
844  free(nameSummary);
845  free(valueSummary);
846  }
847  if (attributeCount > maxAttributes)
848  summary << "<tr><td colspan=\"2\">…</td></tr>";
849  summary << "</table>";
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>";
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 << "<li>" << nameSummary << "</li>";
877  else
878  summary << "<li>&nbsp;</li>";
879  free(nameSummary);
880  }
881  if (childCount > maxChildren)
882  summary << "<li>…</li>";
883  summary << "</ul>";
884  }
885 
886  string summaryStr = summary.str();
887  if (summaryStr.empty())
888  summaryStr = "Empty tree";
889 
890  return strdup(summaryStr.c_str());
891 }
892 
893 
898 {
899  return (VuoTree){ NULL, NULL, NULL };
900 }
901 
906 {
907  xmlDoc *doc = xmlNewDoc((const xmlChar *)"1.0");
908  xmlNode *root = createXmlNode(name);
909  xmlDocSetRootElement(doc, root);
910 
911  xmlNodeSetContent(root, (const xmlChar *)content);
912 
913  unsigned long attributeCount = VuoListGetCount_VuoText(attributes.keys);
914  for (unsigned long i = 1; i <= attributeCount; ++i)
915  {
916  VuoText key = VuoListGetValue_VuoText(attributes.keys, i);
917  VuoText value = VuoListGetValue_VuoText(attributes.values, i);
918  xmlNewProp(root, (const xmlChar *)key, (const xmlChar *)value);
919  }
920 
921  VuoTree tree = VuoTree_makeFromXmlDoc(doc);
922 
923  tree.children = children;
924 
925  return tree;
926 }
927 
932 {
933  return VuoTree_parseJson(json);
934 }
935 
939 VuoTree VuoTree_makeFromXmlText(VuoText xml, bool includeWhitespace)
940 {
941  return VuoTree_parseXml(xml, includeWhitespace);
942 }
943 
948 {
949  xmlChar *xmlAsString = VuoTree_serializeXmlNodeAsXml(tree, indent, 0);
950  VuoText xml = VuoText_make((const char *)xmlAsString);
951  xmlFree(xmlAsString);
952  return xml;
953 }
954 
959 {
960  if (! tree.rootXmlNode && ! tree.rootJson)
961  return VuoText_make("");
962 
963  json_object *json = tree.rootJson;
964  if (! json)
965  json = VuoTree_serializeXmlNodeAsJson(tree, true);
966 
967  int flags = (indent ? JSON_C_TO_STRING_PRETTY : JSON_C_TO_STRING_PLAIN);
968  const char *jsonAsString = json_object_to_json_string_ext(json, flags);
969  VuoText jsonAsText = VuoText_make(jsonAsString);
970 
971  if (! tree.rootJson)
972  json_object_put(json);
973 
974  return jsonAsText;
975 }
976 
981 {
982  if (value.rootXmlNode)
983  VuoRetain(((xmlNode *)value.rootXmlNode)->doc);
984  VuoRetain(value.rootJson);
985  VuoRetain(value.children);
986 }
987 
992 {
993  if (value.rootXmlNode)
994  VuoRelease(((xmlNode *)value.rootXmlNode)->doc);
995  VuoRelease(value.rootJson);
996  VuoRelease(value.children);
997 }
998 
999 
1004 {
1005  if (! tree.rootXmlNode)
1006  return VuoText_make("");
1007 
1008  const xmlChar *name = ((xmlNode *)tree.rootXmlNode)->name;
1009  return VuoText_make((const char *)name);
1010 }
1011 
1016 {
1018 
1019  if (! tree.rootXmlNode)
1020  return attributes;
1021 
1022  for (xmlAttr *a = ((xmlNode *)tree.rootXmlNode)->properties; a; a = a->next)
1023  {
1024  const xmlChar *keyAsString = a->name;
1025  VuoText key = VuoText_make((const char *)keyAsString);
1026 
1027  xmlChar *valueAsString = xmlNodeListGetString(((xmlNode *)tree.rootXmlNode)->doc, a->children, 1);
1028  VuoText value = VuoText_make((const char *)valueAsString);
1029  xmlFree(valueAsString);
1030 
1031  VuoDictionarySetKeyValue_VuoText_VuoText(attributes, key, value);
1032  }
1033 
1034  return attributes;
1035 }
1036 
1042 {
1043  if (! tree.rootXmlNode)
1044  return VuoText_make("");
1045 
1046  xmlChar *value = xmlGetProp((xmlNode *)tree.rootXmlNode, (const xmlChar *)attribute);
1047  VuoText valueAsText = VuoText_make((const char *)value);
1048  xmlFree(value);
1049  return valueAsText;
1050 }
1051 
1055 static xmlChar * VuoTree_getContentOfXmlNode(xmlNode *node)
1056 {
1057  xmlChar *content = xmlStrdup((const xmlChar *)"");
1058  for (xmlNode *n = node->children; n; n = n->next)
1059  if (n->type == XML_TEXT_NODE || n->type == XML_CDATA_SECTION_NODE)
1060  content = xmlStrcat(content, n->content);
1061 
1062  return content;
1063 }
1064 
1068 VuoText VuoTree_getContent(VuoTree tree, bool includeDescendants)
1069 {
1070  if (! tree.rootXmlNode)
1071  return VuoText_make("");
1072 
1073  xmlChar *content = NULL;
1074 
1075  if (includeDescendants)
1076  {
1077  content = xmlNodeGetContent( (xmlNode *)tree.rootXmlNode );
1078 
1079  if (tree.children)
1080  {
1081  unsigned long childCount = VuoListGetCount_VuoTree(tree.children);
1082  for (unsigned long i = 1; i <= childCount; ++i)
1083  {
1084  VuoTree child = VuoListGetValue_VuoTree(tree.children, i);
1085  VuoText childContent = VuoTree_getContent(child, true);
1086  content = xmlStrcat(content, (const xmlChar *)childContent);
1087  }
1088  }
1089  }
1090  else
1091  {
1092  content = VuoTree_getContentOfXmlNode( (xmlNode *)tree.rootXmlNode );
1093  }
1094 
1095  VuoText contentAsText = VuoText_make((const char *)content);
1096 
1097  xmlFree(content);
1098 
1099  return contentAsText;
1100 }
1101 
1106 {
1107  if (tree.children)
1108  return VuoListCopy_VuoTree(tree.children);
1109 
1111 
1112  if (! tree.rootXmlNode)
1113  return children;
1114 
1115  for (xmlNode *n = ((xmlNode *)tree.rootXmlNode)->children; n; n = n->next)
1116  {
1117  if (n->type == XML_ELEMENT_NODE)
1118  {
1119  VuoTree child = VuoTree_makeFromXmlNode(n);
1120  VuoListAppendValue_VuoTree(children, child);
1121  }
1122  }
1123 
1124  return children;
1125 }
1126 
1131 {
1132  VuoText jsonText = VuoTree_serializeAsJson(tree, false);
1133  VuoLocal(jsonText);
1134 
1135  json_object *json = json_tokener_parse(jsonText);
1136 
1137  json_object *child = NULL;
1138  if (json)
1139  {
1140  json_object_object_foreach(json, key, val)
1141  {
1142  child = val;
1143  json_object_get(child);
1144  break;
1145  }
1146 
1147  json_object_put(json);
1148  }
1149 
1150  return child;
1151 }
1152 
1160 {
1161  VuoTree treeIncludingChildren;
1162  if (tree.children)
1163  {
1164  // The children may belong to different XML docs, so build a new XML doc that includes them.
1165  xmlChar *treeAsXml = VuoTree_serializeXmlNodeAsXml(tree, false, 0);
1166  treeIncludingChildren = VuoTree_parseXml((const char *)treeAsXml, false);
1167  VuoTree_retain(treeIncludingChildren);
1168  xmlFree(treeAsXml);
1169  }
1170  else
1171  {
1172  treeIncludingChildren = tree;
1173  }
1174  VuoDefer(^{ if (tree.children) VuoTree_release(treeIncludingChildren); });
1175 
1176  xmlNode *treeRoot = (xmlNode *)treeIncludingChildren.rootXmlNode;
1177 
1178  if (! treeRoot || VuoText_isEmpty(xpath))
1179  return VuoListCreate_VuoTree();
1180 
1181  xmlXPathContext *xpathContext = xmlXPathNewContext(treeRoot->doc);
1182  if (! xpathContext)
1183  {
1184  VUserLog("Error: Couldn't create xmlXPathContext");
1185  return VuoListCreate_VuoTree();
1186  }
1187  VuoDefer(^{ xmlXPathFreeContext(xpathContext); });
1188 
1189  // Replace smartquotes with plain quotes in the XPath.
1190  const int numSmartquotes = 4;
1191  const char *smartquotes[numSmartquotes] = { "“", "”", "‘", "’" };
1192  const char *plainquotes[numSmartquotes] = { "\"", "\"", "'", "'" };
1193  VuoText requotedXpaths[numSmartquotes];
1194  for (int i = 0; i < numSmartquotes; ++i)
1195  {
1196  requotedXpaths[i] = VuoText_replace(xpath, smartquotes[i], plainquotes[i]);
1197  xpath = requotedXpaths[i];
1198  }
1199  VuoText *requotedXPathsPtr = requotedXpaths;
1200  VuoDefer(^{
1201  for (int i = 0; i < numSmartquotes; ++i)
1202  {
1203  if (requotedXPathsPtr[i] != xpath)
1204  {
1205  VuoRetain(requotedXPathsPtr[i]);
1206  VuoRelease(requotedXPathsPtr[i]);
1207  }
1208  }
1209  });
1210 
1211  // Set the xmlNode that relative XPaths are relative to — the root of the tree.
1212  int ret = xmlXPathSetContextNode(treeRoot, xpathContext);
1213  if (ret < 0)
1214  {
1215  VUserLog("Error: Couldn't set context node to '%s'", treeRoot->name);
1216  return VuoListCreate_VuoTree();
1217  }
1218 
1219  // Is the tree a child sharing an xmlDoc with its parent?
1220  bool isSubtree = (treeRoot != xmlDocGetRootElement(treeRoot->doc));
1221 
1222  xmlChar *prefixedXpath;
1223  if (isSubtree && xpath[0] == '/')
1224  {
1225  // Child tree + absolute path: Construct the full absolute path for the xmlDoc.
1226  xmlChar *xpathPrefix = xmlStrdup((const xmlChar *)"");
1227  for (xmlNode *parent = treeRoot->parent; parent; parent = parent->parent)
1228  {
1229  if (parent->type == XML_ELEMENT_NODE)
1230  {
1231  xpathPrefix = xmlStrcat(xmlStrdup(parent->name), xpathPrefix);
1232  xpathPrefix = xmlStrcat(xmlStrdup((const xmlChar *)"/"), xpathPrefix);
1233  }
1234  }
1235  prefixedXpath = xmlStrcat(xpathPrefix, (const xmlChar *)xpath);
1236  }
1237  else
1238  {
1239  prefixedXpath = xmlStrdup((const xmlChar *)xpath);
1240  }
1241  VuoDefer(^{ xmlFree(prefixedXpath); });
1242 
1243  // Perform the query.
1244  xmlXPathObject *xpathObject = xmlXPathEvalExpression(prefixedXpath, xpathContext);
1245  if (! xpathObject)
1246  {
1247  VUserLog("Error: Couldn't evaluate XPath expression '%s'", prefixedXpath);
1248  return VuoListCreate_VuoTree();
1249  }
1250  VuoDefer(^{ xmlXPathFreeObject(xpathObject); });
1251 
1252  if (xmlXPathNodeSetIsEmpty(xpathObject->nodesetval))
1253  return VuoListCreate_VuoTree();
1254 
1255  // Turn the found nodes into a set of unique element nodes.
1256  vector<xmlNode *> foundNodes;
1257  map<xmlNode *, vector<xmlNode *> > attributesForFoundNode;
1258  for (int i = 0; i < xpathObject->nodesetval->nodeNr; ++i)
1259  {
1260  xmlNode *foundNode = xpathObject->nodesetval->nodeTab[i];
1261 
1262  if (foundNode->type == XML_ATTRIBUTE_NODE || foundNode->type == XML_TEXT_NODE || foundNode->type == XML_CDATA_SECTION_NODE)
1263  {
1264  if (find(foundNodes.begin(), foundNodes.end(), foundNode->parent) == foundNodes.end())
1265  foundNodes.push_back(foundNode->parent);
1266 
1267  if (foundNode->type == XML_ATTRIBUTE_NODE)
1268  attributesForFoundNode[foundNode->parent].push_back(foundNode);
1269  }
1270  else if (foundNode->type == XML_ELEMENT_NODE)
1271  {
1272  foundNodes.push_back(foundNode);
1273  }
1274  }
1275 
1276  // Turn the unique element nodes into VuoTrees.
1277  VuoList_VuoTree foundTrees = VuoListCreate_VuoTree();
1278  for (vector<xmlNode *>::iterator i = foundNodes.begin(); i != foundNodes.end(); ++i)
1279  {
1280  xmlNode *foundNode = *i;
1281 
1282  if (isSubtree)
1283  {
1284  // Child tree: Skip found xmlNodes that are within the xmlDoc but not this subtree.
1285  bool isNodeInTree = false;
1286  for (xmlNode *n = foundNode; n; n = n->parent)
1287  {
1288  if (n == treeRoot)
1289  {
1290  isNodeInTree = true;
1291  break;
1292  }
1293  }
1294  if (! isNodeInTree)
1295  continue;
1296  }
1297 
1298  VuoTree foundTree;
1299  map<xmlNode *, vector<xmlNode *> >::iterator attributesIter = attributesForFoundNode.find(foundNode);
1300  if (attributesIter == attributesForFoundNode.end())
1301  {
1302  // Searching for elements or text: The returned tree is a subtree of @a tree (including attributes and content).
1303  foundTree = VuoTree_makeFromXmlNode(foundNode);
1304  }
1305  else
1306  {
1307  // Searching for attributes: The returned tree contains only the parent element and found attributes.
1309  for (vector<xmlNode *>::iterator j = attributesIter->second.begin(); j != attributesIter->second.end(); ++j)
1310  {
1311  xmlNode *foundAttribute = *j;
1312  VuoText key = VuoText_make((const char *)foundAttribute->name);
1313  VuoText value = VuoText_make((const char *)xmlNodeGetContent(foundAttribute));
1314  VuoDictionarySetKeyValue_VuoText_VuoText(attributes, key, value);
1315  }
1316  foundTree = VuoTree_make((const char *)foundNode->name, attributes, "", NULL);
1317  }
1318 
1319  VuoListAppendValue_VuoTree(foundTrees, foundTree);
1320  }
1321 
1322  return foundTrees;
1323 }
1324 
1328 static bool compareName(xmlNode *node, VuoText name, VuoTextComparison comparison, VuoText unused)
1329 {
1330  return VuoText_compare((const char *)node->name, comparison, name);
1331 }
1332 
1336 static bool compareAttribute(xmlNode *node, VuoText value, VuoTextComparison comparison, VuoText attribute)
1337 {
1338  xmlChar *actualValue = xmlGetProp(node, (const xmlChar *)attribute);
1339  VuoDefer(^{ xmlFree(actualValue); });
1340  return VuoText_compare((const char *)actualValue, comparison, value);
1341 }
1342 
1347 static bool compareContent(xmlNode *node, VuoText content, VuoTextComparison comparison, VuoText unused)
1348 {
1349  xmlChar *actualContent = VuoTree_getContentOfXmlNode(node);
1350  VuoDefer(^{ xmlFree(actualContent); });
1351  return VuoText_compare((const char *)actualContent, comparison, content);
1352 }
1353 
1359  VuoText targetText, VuoTextComparison comparison, VuoText attribute,
1360  bool includeDescendants, bool atFindRoot)
1361 {
1362  xmlNode *treeRoot = (xmlNode *)tree.rootXmlNode;
1363 
1364  if (! treeRoot)
1365  return VuoListCreate_VuoTree();
1366 
1367  VuoList_VuoTree foundTrees = VuoListCreate_VuoTree();
1368  if (compare(treeRoot, targetText, comparison, attribute))
1369  VuoListAppendValue_VuoTree(foundTrees, tree);
1370 
1371  if (atFindRoot || includeDescendants)
1372  {
1373  if (tree.children)
1374  {
1375  unsigned long childCount = VuoListGetCount_VuoTree(tree.children);
1376  for (unsigned long i = 1; i <= childCount; ++i)
1377  {
1378  VuoTree child = VuoListGetValue_VuoTree(tree.children, i);
1379  VuoList_VuoTree childFoundTrees = VuoTree_findItems(child, compare, targetText, comparison, attribute, includeDescendants, false);
1380  unsigned long foundCount = VuoListGetCount_VuoTree(childFoundTrees);
1381  for (unsigned long j = 1; j <= foundCount; ++j)
1382  {
1383  VuoTree childFoundTree = VuoListGetValue_VuoTree(childFoundTrees, j);
1384  VuoListAppendValue_VuoTree(foundTrees, childFoundTree);
1385  }
1386  }
1387  }
1388  else
1389  {
1390  list<xmlNode *> nodesToVisit;
1391  for (xmlNode *n = treeRoot->children; n; n = n->next)
1392  if (n->type == XML_ELEMENT_NODE)
1393  nodesToVisit.push_back(n);
1394 
1395  while (! nodesToVisit.empty())
1396  {
1397  xmlNode *node = nodesToVisit.front();
1398  nodesToVisit.pop_front();
1399 
1400  if (compare(node, targetText, comparison, attribute))
1401  {
1402  VuoTree foundTree = VuoTree_makeFromXmlNode(node);
1403  VuoListAppendValue_VuoTree(foundTrees, foundTree);
1404  }
1405 
1406  if (includeDescendants)
1407  for (xmlNode *n = node->children; n; n = n->next)
1408  if (n->type == XML_ELEMENT_NODE)
1409  nodesToVisit.push_back(n);
1410  }
1411  }
1412  }
1413 
1414  return foundTrees;
1415 }
1416 
1423 VuoList_VuoTree VuoTree_findItemsWithName(VuoTree tree, VuoText name, VuoTextComparison comparison, bool includeDescendants)
1424 {
1425  return VuoTree_findItems(tree, compareName, name, comparison, NULL, includeDescendants, true);
1426 }
1427 
1435 VuoList_VuoTree VuoTree_findItemsWithAttribute(VuoTree tree, VuoText attribute, VuoText value, VuoTextComparison valueComparison, bool includeDescendants)
1436 {
1437  return VuoTree_findItems(tree, compareAttribute, value, valueComparison, attribute, includeDescendants, true);
1438 }
1439 
1449 VuoList_VuoTree VuoTree_findItemsWithContent(VuoTree tree, VuoText content, VuoTextComparison comparison, bool includeDescendants)
1450 {
1451  return VuoTree_findItems(tree, compareContent, content, comparison, NULL, includeDescendants, true);
1452 }