1 /** 2 * section: XPath 3 * synopsis: Load a document, locate subelements with XPath, modify 4 * said elements and save the resulting document. 5 * purpose: Shows how to make a full round-trip from a load/edit/save 6 * usage: xpath2 <xml-file> <xpath-expr> <new-value> 7 * test: xpath2 test3.xml '//discarded' discarded > xpath2.tmp && diff xpath2.tmp $(srcdir)/xpath2.res 8 * author: Aleksey Sanin and Daniel Veillard 9 * copy: see Copyright for the status of this software. 10 */ 11 #include <stdlib.h> 12 #include <stdio.h> 13 #include <string.h> 14 #include <assert.h> 15 16 #include <libxml/tree.h> 17 #include <libxml/parser.h> 18 #include <libxml/xpath.h> 19 #include <libxml/xpathInternals.h> 20 21 #if defined(LIBXML_XPATH_ENABLED) && defined(LIBXML_SAX1_ENABLED) && \ 22 defined(LIBXML_OUTPUT_ENABLED) 23 24 25 static void usage(const char *name); 26 static int example4(const char *filename, const xmlChar * xpathExpr, 27 const xmlChar * value); 28 static void update_xpath_nodes(xmlNodeSetPtr nodes, const xmlChar * value); 29 30 31 int 32 main(int argc, char **argv) { 33 /* Parse command line and process file */ 34 if (argc != 4) { 35 fprintf(stderr, "Error: wrong number of arguments.\n"); 36 usage(argv[0]); 37 return(-1); 38 } 39 40 /* Init libxml */ 41 xmlInitParser(); 42 LIBXML_TEST_VERSION 43 44 /* Do the main job */ 45 if (example4(argv[1], BAD_CAST argv[2], BAD_CAST argv[3])) { 46 usage(argv[0]); 47 return(-1); 48 } 49 50 /* Shutdown libxml */ 51 xmlCleanupParser(); 52 53 /* 54 * this is to debug memory for regression tests 55 */ 56 xmlMemoryDump(); 57 return 0; 58 } 59 60 /** 61 * usage: 62 * @name: the program name. 63 * 64 * Prints usage information. 65 */ 66 static void 67 usage(const char *name) { 68 assert(name); 69 70 fprintf(stderr, "Usage: %s <xml-file> <xpath-expr> <value>\n", name); 71 } 72 73 /** 74 * example4: 75 * @filename: the input XML filename. 76 * @xpathExpr: the xpath expression for evaluation. 77 * @value: the new node content. 78 * 79 * Parses input XML file, evaluates XPath expression and update the nodes 80 * then print the result. 81 * 82 * Returns 0 on success and a negative value otherwise. 83 */ 84 static int 85 example4(const char* filename, const xmlChar* xpathExpr, const xmlChar* value) { 86 xmlDocPtr doc; 87 xmlXPathContextPtr xpathCtx; 88 xmlXPathObjectPtr xpathObj; 89 90 assert(filename); 91 assert(xpathExpr); 92 assert(value); 93 94 /* Load XML document */ 95 doc = xmlParseFile(filename); 96 if (doc == NULL) { 97 fprintf(stderr, "Error: unable to parse file \"%s\"\n", filename); 98 return(-1); 99 } 100 101 /* Create xpath evaluation context */ 102 xpathCtx = xmlXPathNewContext(doc); 103 if(xpathCtx == NULL) { 104 fprintf(stderr,"Error: unable to create new XPath context\n"); 105 xmlFreeDoc(doc); 106 return(-1); 107 } 108 109 /* Evaluate xpath expression */ 110 xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx); 111 if(xpathObj == NULL) { 112 fprintf(stderr,"Error: unable to evaluate xpath expression \"%s\"\n", xpathExpr); 113 xmlXPathFreeContext(xpathCtx); 114 xmlFreeDoc(doc); 115 return(-1); 116 } 117 118 /* update selected nodes */ 119 update_xpath_nodes(xpathObj->nodesetval, value); 120 121 122 /* Cleanup of XPath data */ 123 xmlXPathFreeObject(xpathObj); 124 xmlXPathFreeContext(xpathCtx); 125 126 /* dump the resulting document */ 127 xmlDocDump(stdout, doc); 128 129 130 /* free the document */ 131 xmlFreeDoc(doc); 132 133 return(0); 134 } 135 136 /** 137 * update_xpath_nodes: 138 * @nodes: the nodes set. 139 * @value: the new value for the node(s) 140 * 141 * Prints the @nodes content to @output. 142 */ 143 static void 144 update_xpath_nodes(xmlNodeSetPtr nodes, const xmlChar* value) { 145 int size; 146 int i; 147 148 assert(value); 149 size = (nodes) ? nodes->nodeNr : 0; 150 151 /* 152 * NOTE: the nodes are processed in reverse order, i.e. reverse document 153 * order because xmlNodeSetContent can actually free up descendant 154 * of the node and such nodes may have been selected too ! Handling 155 * in reverse order ensure that descendant are accessed first, before 156 * they get removed. Mixing XPath and modifications on a tree must be 157 * done carefully ! 158 */ 159 for(i = size - 1; i >= 0; i--) { 160 assert(nodes->nodeTab[i]); 161 162 xmlNodeSetContent(nodes->nodeTab[i], value); 163 /* 164 * All the elements returned by an XPath query are pointers to 165 * elements from the tree *except* namespace nodes where the XPath 166 * semantic is different from the implementation in libxml2 tree. 167 * As a result when a returned node set is freed when 168 * xmlXPathFreeObject() is called, that routine must check the 169 * element type. But node from the returned set may have been removed 170 * by xmlNodeSetContent() resulting in access to freed data. 171 * This can be exercised by running 172 * valgrind xpath2 test3.xml '//discarded' discarded 173 * There is 2 ways around it: 174 * - make a copy of the pointers to the nodes from the result set 175 * then call xmlXPathFreeObject() and then modify the nodes 176 * or 177 * - remove the reference to the modified nodes from the node set 178 * as they are processed, if they are not namespace nodes. 179 */ 180 if (nodes->nodeTab[i]->type != XML_NAMESPACE_DECL) 181 nodes->nodeTab[i] = NULL; 182 } 183 } 184 185 #else 186 int main(void) { 187 fprintf(stderr, "XPath support not compiled in\n"); 188 exit(1); 189 } 190 #endif 191