26 #include <libxml/parser.h>
27 #include <libxml/tree.h>
28 #include <libxml/xpath.h>
34 "description" :
"Hierarchically structured information.",
39 "VuoDictionary_VuoText_VuoText",
55 xmlFreeDoc( (xmlDocPtr)value );
76 return (
VuoTree){ node, NULL, NULL };
89 xmlNode *node = xmlDocGetRootElement(doc);
106 if (strcmp((
const char *)plist->name,
"dict") == 0)
108 xmlNodePtr n = xmlNewNode(NULL, (
const xmlChar *)
"");
109 for (xmlNodePtr cur = plist->children; cur; cur = cur->next)
111 if (cur->type != XML_ELEMENT_NODE)
115 if (strcmp((
const char *)cur->name,
"key") != 0)
117 VUserLog(
"Error: Malformed property list XML: Expected a <key> element, but found <%s>.", cur->name);
123 for (xmlNode *nn = cur->children; nn; nn = nn->next)
124 if (nn->type == XML_TEXT_NODE)
134 VUserLog(
"Error: Malformed property list XML: Expected a value element to follow the <key> element, but instead reached the end.");
139 subNode->name = xmlStrdup(key);
140 xmlAddChild(n, subNode);
144 else if (strcmp((
const char *)plist->name,
"array") == 0)
146 xmlNodePtr n = xmlNewNode(NULL, (
const xmlChar *)
"");
147 for (xmlNodePtr cur = plist->children; cur; cur = cur->next)
151 else if (strcmp((
const char *)plist->name,
"false") == 0)
153 xmlNodePtr n = xmlNewNode(NULL, (
const xmlChar *)
"");
154 xmlAddChild(n, xmlNewText(xmlCharStrdup(
"false")));
157 else if (strcmp((
const char *)plist->name,
"true") == 0)
159 xmlNodePtr n = xmlNewNode(NULL, (
const xmlChar *)
"");
160 xmlAddChild(n, xmlNewText(xmlCharStrdup(
"true")));
168 xmlNodePtr n = xmlCopyNode(plist,
true);
169 n->name = xmlCharStrdup(
"item");
182 size_t length = strlen(xmlString);
187 if (! includeWhitespace)
188 flags |= XML_PARSE_NOBLANKS;
190 xmlDoc *doc = xmlReadMemory(xmlString, length,
"VuoTree.xml",
encoding, flags);
193 VUserLog(
"Error: Couldn't parse tree as XML");
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))
205 if (!transformedRoot)
208 transformedRoot->name = xmlCharStrdup(
"document");
212 doc = xmlNewDoc((
const xmlChar *)
"1.0");
213 xmlDocSetRootElement(doc, transformedRoot);
226 return xmlStrdup((
const xmlChar *)
"");
228 xmlNode *treeRoot = xmlCopyNode((xmlNode *)tree.
rootXmlNode, 1);
229 VuoDefer(^{ xmlUnlinkNode(treeRoot); xmlFreeNode(treeRoot); });
231 list<xmlNode *> nodesToVisit;
232 nodesToVisit.push_back(treeRoot);
233 while (! nodesToVisit.empty())
235 xmlNode *currentNode = nodesToVisit.front();
236 nodesToVisit.pop_front();
238 if (xmlStrlen(currentNode->name) == 0)
240 const char *name = (currentNode == treeRoot && level == 0 ?
"document" :
"item");
241 xmlNodeSetName(currentNode, (
const xmlChar *)name);
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);
250 xmlNewProp(currentNode, (
const xmlChar *)
"name", (
const xmlChar *)currentNode->name);
251 xmlNodeSetName(currentNode, (
const xmlChar *)
"item");
255 for (xmlNode *n = currentNode->children; n; n = n->next)
256 if (n->type == XML_ELEMENT_NODE)
257 nodesToVisit.push_back(n);
260 xmlBuffer *buffer = xmlBufferCreate();
261 VuoDefer(^{ xmlBufferFree(buffer); });
263 int ret = xmlNodeDump(buffer, treeRoot->doc, treeRoot, level, indent);
267 VUserLog(
"Error: Couldn't serialize tree named '%s' as XML.", treeRoot->name);
268 return xmlStrdup((
const xmlChar *)
"");
271 xmlChar *xml = xmlBufferDetach(buffer);
276 if (xml[xmlStrlen(xml)-2] ==
'/')
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;
288 int closingTagLength = xmlStrlen(treeRoot->name) + 3;
289 int splitIndex = xmlStrlen(xml) - closingTagLength;
290 closingTag = xmlStrsub(xml, splitIndex, closingTagLength);
294 string whitespaceBeforeChild = (indent ?
"\n" + string(2 * (level+1),
' ') :
"");
297 for (
unsigned long i = 1; i <= childCount; ++i)
299 xml = xmlStrcat(xml, (
const xmlChar *)whitespaceBeforeChild.c_str());
303 xml = xmlStrcat(xml, childXml);
306 string whitespaceBeforeClosingTag = (indent ?
"\n" + string(2 * level,
' ') :
"");
307 xml = xmlStrcat(xml, (
const xmlChar *)whitespaceBeforeClosingTag.c_str());
309 xml = xmlStrcat(xml, closingTag);
320 if (! name || strlen(name) == 0)
321 return xmlNewNode(NULL, (
const xmlChar *)
"");
323 return xmlNewNode(NULL, (
const xmlChar *)name);
331 if (! jsonString || strlen(jsonString) == 0)
334 enum json_tokener_error error;
335 json_object *json = json_tokener_parse_verbose(jsonString, &error);
338 VUserLog(
"Error: Couldn't parse tree as JSON: %s", json_tokener_error_desc(error));
343 if (json_object_is_type(json, json_type_object) && json_object_object_length(json) == 1)
346 json_object_object_foreach(json, key, value)
348 if (json_object_is_type(value, json_type_array))
366 if (json_object_is_type(json, json_type_array))
368 json_object *arrayParent = json_object_new_object();
369 json_object_object_add(arrayParent,
"array", json);
371 renamedArrayObject = arrayParent;
375 json_object_object_add(parent,
"json", json);
377 renamedRootObject = parent;
380 xmlDoc *doc = xmlNewDoc((
const xmlChar *)
"1.0");
381 xmlNode *root = NULL;
382 map<json_object *, xmlNode *> parents;
384 list<json_object *> jsonsToVisit;
385 jsonsToVisit.push_back(json);
386 while (! jsonsToVisit.empty())
389 jsonsToVisit.pop_front();
391 xmlNode *parentNode = parents[currentJson];
396 json_type type = json_object_get_type(currentJson);
397 if (type == json_type_object)
399 json_object_object_foreach(currentJson, key, value)
401 const char *nodeName = (currentJson == renamedRootObject || currentJson == renamedArrayObject ?
"" : key);
403 if (json_object_is_type(value, json_type_array))
405 int length = json_object_array_length(value);
406 for (
int i = 0; i < length; ++i)
408 json_object *arrayValue = json_object_array_get_idx(value, i);
411 xmlAddChild(parentNode, currentNode);
413 parents[arrayValue] = currentNode;
414 jsonsToVisit.push_back(arrayValue);
422 xmlAddChild(parentNode, currentNode);
426 parents[value] = currentNode;
427 jsonsToVisit.push_back(value);
433 const char *content = json_object_get_string(currentJson);
434 xmlChar *encoded = xmlEncodeSpecialChars(doc, (
const xmlChar *)content);
435 xmlNodeSetContent(parentNode, encoded);
440 xmlDocSetRootElement(doc, root);
442 json_object_put(json);
446 tree.
rootJson = json_tokener_parse(jsonString);
466 const int charCount = 5;
467 const char *controlChars[charCount] = {
"\b",
"\f",
"\n",
"\r",
"\t" };
470 for (
int i = 0; i < charCount; ++i)
473 json_object *ret = json_object_new_string(t[charCount]);
475 for (
int i = charCount; i > 0; --i)
492 if (json_object_is_type(container, json_type_array))
493 json_object_array_add(container, value);
495 json_object_object_add(container, key, value);
507 json_object *topLevelJson = json_object_new_object();
508 const char *topLevelName = (xmlStrlen(rootNode->name) > 0 ? (
const char *)rootNode->name : (atRoot ?
"document" :
"item"));
510 map<xmlNode *, json_object *> containers;
511 containers[rootNode] = topLevelJson;
513 map<xmlNode *, const char *> names;
514 names[rootNode] = topLevelName;
516 list<xmlNode *> nodesToVisit;
517 nodesToVisit.push_back( (xmlNode *)tree.
rootXmlNode );
518 while (! nodesToVisit.empty())
520 xmlNode *currentNode = nodesToVisit.front();
521 nodesToVisit.pop_front();
527 vector< pair<const char *, ChildInfo> > childInfos;
528 int elementCount = 0;
531 for (xmlAttr *attribute = currentNode->properties; attribute; attribute = attribute->next)
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 ));
538 for (xmlNode *childNode = currentNode->children; childNode; childNode = childNode->next)
540 if (childNode->type == XML_ELEMENT_NODE)
542 const char *childName = (xmlStrlen(childNode->name) > 0 ? (
const char *)childNode->name :
"item");
543 names[childNode] = childName;
546 childInfos.push_back(make_pair( childName, info ));
550 else if (childNode->type == XML_TEXT_NODE || childNode->type == XML_CDATA_SECTION_NODE)
552 ChildInfo info = { childNode, (
char *)childNode->content };
553 childInfos.push_back(make_pair( (
const char *)NULL, info ));
559 json_object *currentContainer = containers[currentNode];
560 const char *currentName = names[currentNode];
562 if (childInfos.empty())
569 else if (childInfos.size() == 1 && childInfos[0].first == NULL)
581 vector< pair<const char *, vector<ChildInfo> > > mergedChildInfo;
582 map<size_t, bool> childInfosVisited;
584 for (
size_t i = 0; i < childInfos.size(); ++i)
586 if (childInfosVisited[i])
589 vector<ChildInfo> sameNameInfo;
590 sameNameInfo.push_back(childInfos[i].second);
591 childInfosVisited[i] =
true;
593 if (childInfos[i].first)
595 for (
size_t j = i+1; j < childInfos.size(); ++j)
597 if (! childInfos[j].first)
600 if (! strcmp(childInfos[i].first, childInfos[j].first))
602 sameNameInfo.push_back(childInfos[j].second);
603 childInfosVisited[j] =
true;
608 mergedChildInfo.push_back(make_pair( childInfos[i].first, sameNameInfo ));
614 bool isMixedContent = elementCount > 0 && textCount > 0;
615 json_object *childContainer = (isMixedContent ? json_object_new_array() : json_object_new_object());
619 for (vector< pair<
const char *, vector<ChildInfo> > >::iterator i = mergedChildInfo.begin(); i != mergedChildInfo.end(); ++i)
621 if ((*i).second.size() == 1)
625 if ((*i).second[0].text)
628 const char *childName = ((*i).first ? (*i).first :
"content");
636 singleChildContainer = json_object_new_object();
637 json_object_array_add(childContainer, singleChildContainer);
640 singleChildContainer = childContainer;
642 containers[(*i).second[0].node] = singleChildContainer;
649 json_object *sameNameArray = json_object_new_array();
650 for (vector<ChildInfo>::iterator j = (*i).second.begin(); j != (*i).second.end(); ++j)
655 json_object_array_add(sameNameArray, text);
658 containers[(*j).node] = sameNameArray;
664 sameNameArrayContainer = json_object_new_object();
665 json_object_array_add(childContainer, sameNameArrayContainer);
668 sameNameArrayContainer = childContainer;
670 const char *childName = ((*i).first ? (*i).first :
"content");
677 for (xmlNode *childNode = currentNode->children; childNode; childNode = childNode->next)
678 if (childNode->type == XML_ELEMENT_NODE)
679 nodesToVisit.push_back(childNode);
684 const char *rootKey = NULL;
686 json_object_object_foreach(topLevelJson, key, value)
696 rootValue = json_object_new_object();
697 json_object_object_add(topLevelJson, rootKey, rootValue);
701 for (
unsigned long i = 1; i <= childCount; ++i)
709 const char *childKey = NULL;
711 json_object_object_foreach(childJson, key, value)
719 if (json_object_object_get_ex(rootValue, childKey, &siblingValue))
721 if (! json_object_is_type(siblingValue, json_type_array))
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;
732 json_object_array_add(siblingValue, childValue);
737 json_object_object_add(rootValue, childKey, childValue);
752 if (json_object_object_get_ex(js,
"pointer", &o))
754 xmlNode *node = (xmlNode *)json_object_get_int64(o);
757 if (json_object_object_get_ex(js,
"jsonPointer", &o))
760 if (json_object_object_get_ex(js,
"childrenPointer", &o))
765 else if (json_object_object_get_ex(js,
"xml", &o))
767 const char *treeAsXml = json_object_get_string(o);
770 else if (json_object_object_get_ex(js,
"json", &o))
772 const char *treeAsJson = json_object_get_string(o);
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));
802 const char *treeAsJson = json_object_to_json_string(value.
rootJson);
803 json_object_object_add(js,
"json", json_object_new_string(treeAsJson));
808 json_object_object_add(js,
"xml", json_object_new_string((
const char *)treeAsXml));
820 ostringstream summary;
827 summary <<
"<div>name: " << nameSummary <<
"</div>\n";
833 if (attributeCount > 0)
835 summary <<
"attributes: <table>";
836 unsigned long maxAttributes = 5;
837 for (
unsigned long i = 1; i <= attributeCount && i <= maxAttributes; ++i)
843 summary <<
"\n<tr><td>" << nameSummary <<
"</td><td> → " << valueSummary <<
"</td></tr>";
847 if (attributeCount > maxAttributes)
848 summary <<
"\n<tr><td colspan=\"2\">…</td></tr>";
849 summary <<
"</table>\n";
859 summary <<
"<div>content: " << contentSummary <<
"</div>\n";
860 free(contentSummary);
868 summary << childCount <<
" " << (childCount == 1 ?
"child" :
"children") <<
"<ul>";
870 unsigned long maxChildren = 15;
871 for (
unsigned long i = 0; i < childCount && i < maxChildren; ++i)
875 if (strlen(nameSummary) > 0 &&
string(nameSummary).find_first_not_of(
' ') != string::npos)
876 summary <<
"\n<li>" << nameSummary <<
"</li>";
878 summary <<
"\n<li> </li>";
881 if (childCount > maxChildren)
882 summary <<
"\n<li>…</li>";
883 summary <<
"</ul>\n";
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";
892 return strdup(summaryStr.c_str());
901 return (
VuoTree){ NULL, NULL, NULL };
909 xmlDoc *doc = xmlNewDoc((
const xmlChar *)
"1.0");
911 xmlDocSetRootElement(doc, root);
913 xmlNodeSetContent(root, (
const xmlChar *)content);
916 for (
unsigned long i = 1; i <= attributeCount; ++i)
920 xmlNewProp(root, (
const xmlChar *)key, (
const xmlChar *)value);
953 xmlFree(xmlAsString);
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);
974 json_object_put(json);
1010 const xmlChar *name = ((xmlNode *)tree.
rootXmlNode)->name;
1024 for (xmlAttr *a = ((xmlNode *)tree.
rootXmlNode)->properties; a; a = a->next)
1026 const xmlChar *keyAsString = a->name;
1029 xmlChar *valueAsString = xmlNodeListGetString(((xmlNode *)tree.
rootXmlNode)->doc, a->children, 1);
1031 xmlFree(valueAsString);
1048 xmlChar *value = xmlGetProp((xmlNode *)tree.
rootXmlNode, (
const xmlChar *)attribute);
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);
1075 xmlChar *content = NULL;
1077 if (includeDescendants)
1079 content = xmlNodeGetContent( (xmlNode *)tree.
rootXmlNode );
1084 for (
unsigned long i = 1; i <= childCount; ++i)
1088 content = xmlStrcat(content, (
const xmlChar *)childContent);
1101 return contentAsText;
1117 for (xmlNode *n = ((xmlNode *)tree.
rootXmlNode)->children; n; n = n->next)
1119 if (n->type == XML_ELEMENT_NODE)
1142 json_object_object_foreach(json, key, val)
1145 json_object_get(child);
1149 json_object_put(json);
1163 VuoTree treeIncludingChildren;
1174 treeIncludingChildren = tree;
1178 xmlNode *treeRoot = (xmlNode *)treeIncludingChildren.
rootXmlNode;
1183 xmlXPathContext *xpathContext = xmlXPathNewContext(treeRoot->doc);
1186 VUserLog(
"Error: Couldn't create xmlXPathContext");
1189 VuoDefer(^{ xmlXPathFreeContext(xpathContext); });
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)
1198 requotedXpaths[i] =
VuoText_replace(xpath, smartquotes[i], plainquotes[i]);
1199 xpath = requotedXpaths[i];
1201 VuoText *requotedXPathsPtr = requotedXpaths;
1203 for (
int i = 0; i < numSmartquotes; ++i)
1205 if (requotedXPathsPtr[i] != xpath)
1214 int ret = xmlXPathSetContextNode(treeRoot, xpathContext);
1217 VUserLog(
"Error: Couldn't set context node to '%s'", treeRoot->name);
1222 bool isSubtree = (treeRoot != xmlDocGetRootElement(treeRoot->doc));
1224 xmlChar *prefixedXpath;
1225 if (isSubtree && xpath[0] ==
'/')
1228 xmlChar *xpathPrefix = xmlStrdup((
const xmlChar *)
"");
1229 for (xmlNode *parent = treeRoot->parent; parent; parent = parent->parent)
1231 if (parent->type == XML_ELEMENT_NODE)
1233 xpathPrefix = xmlStrcat(xmlStrdup(parent->name), xpathPrefix);
1234 xpathPrefix = xmlStrcat(xmlStrdup((
const xmlChar *)
"/"), xpathPrefix);
1237 prefixedXpath = xmlStrcat(xpathPrefix, (
const xmlChar *)xpath);
1241 prefixedXpath = xmlStrdup((
const xmlChar *)xpath);
1243 VuoDefer(^{ xmlFree(prefixedXpath); });
1246 xmlXPathObject *xpathObject = xmlXPathEvalExpression(prefixedXpath, xpathContext);
1249 VUserLog(
"Error: Couldn't evaluate XPath expression '%s'", prefixedXpath);
1252 VuoDefer(^{ xmlXPathFreeObject(xpathObject); });
1254 if (xmlXPathNodeSetIsEmpty(xpathObject->nodesetval))
1258 vector<xmlNode *> foundNodes;
1259 map<xmlNode *, vector<xmlNode *> > attributesForFoundNode;
1260 for (
int i = 0; i < xpathObject->nodesetval->nodeNr; ++i)
1262 xmlNode *foundNode = xpathObject->nodesetval->nodeTab[i];
1264 if (foundNode->type == XML_ATTRIBUTE_NODE || foundNode->type == XML_TEXT_NODE || foundNode->type == XML_CDATA_SECTION_NODE)
1266 if (find(foundNodes.begin(), foundNodes.end(), foundNode->parent) == foundNodes.end())
1267 foundNodes.push_back(foundNode->parent);
1269 if (foundNode->type == XML_ATTRIBUTE_NODE)
1270 attributesForFoundNode[foundNode->parent].push_back(foundNode);
1272 else if (foundNode->type == XML_ELEMENT_NODE)
1274 foundNodes.push_back(foundNode);
1280 for (vector<xmlNode *>::iterator i = foundNodes.begin(); i != foundNodes.end(); ++i)
1282 xmlNode *foundNode = *i;
1287 bool isNodeInTree =
false;
1288 for (xmlNode *n = foundNode; n; n = n->parent)
1292 isNodeInTree =
true;
1301 map<xmlNode *, vector<xmlNode *> >::iterator attributesIter = attributesForFoundNode.find(foundNode);
1302 if (attributesIter == attributesForFoundNode.end())
1311 for (vector<xmlNode *>::iterator j = attributesIter->second.begin(); j != attributesIter->second.end(); ++j)
1313 xmlNode *foundAttribute = *j;
1318 foundTree =
VuoTree_make((
const char *)foundNode->name, attributes,
"", NULL);
1340 xmlChar *actualValue = xmlGetProp(node, (
const xmlChar *)attribute);
1341 VuoDefer(^{ xmlFree(actualValue); });
1352 VuoDefer(^{ xmlFree(actualContent); });
1353 return VuoText_compare((
const char *)actualContent, comparison, content);
1362 bool includeDescendants,
bool atFindRoot)
1370 if (
compare(treeRoot, targetText, comparison, attribute))
1373 if (atFindRoot || includeDescendants)
1378 for (
unsigned long i = 1; i <= childCount; ++i)
1383 for (
unsigned long j = 1; j <= foundCount; ++j)
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);
1397 while (! nodesToVisit.empty())
1399 xmlNode *node = nodesToVisit.front();
1400 nodesToVisit.pop_front();
1402 if (
compare(node, targetText, comparison, attribute))
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);