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
main(int argc,char ** argv)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 return 0;
51 }
52
53 /**
54 * usage:
55 * @name: the program name.
56 *
57 * Prints usage information.
58 */
59 static void
usage(const char * name)60 usage(const char *name) {
61 assert(name);
62
63 fprintf(stderr, "Usage: %s <xml-file> <xpath-expr> <value>\n", name);
64 }
65
66 /**
67 * example4:
68 * @filename: the input XML filename.
69 * @xpathExpr: the xpath expression for evaluation.
70 * @value: the new node content.
71 *
72 * Parses input XML file, evaluates XPath expression and update the nodes
73 * then print the result.
74 *
75 * Returns 0 on success and a negative value otherwise.
76 */
77 static int
example4(const char * filename,const xmlChar * xpathExpr,const xmlChar * value)78 example4(const char* filename, const xmlChar* xpathExpr, const xmlChar* value) {
79 xmlDocPtr doc;
80 xmlXPathContextPtr xpathCtx;
81 xmlXPathObjectPtr xpathObj;
82
83 assert(filename);
84 assert(xpathExpr);
85 assert(value);
86
87 /* Load XML document */
88 doc = xmlParseFile(filename);
89 if (doc == NULL) {
90 fprintf(stderr, "Error: unable to parse file \"%s\"\n", filename);
91 return(-1);
92 }
93
94 /* Create xpath evaluation context */
95 xpathCtx = xmlXPathNewContext(doc);
96 if(xpathCtx == NULL) {
97 fprintf(stderr,"Error: unable to create new XPath context\n");
98 xmlFreeDoc(doc);
99 return(-1);
100 }
101
102 /* Evaluate xpath expression */
103 xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx);
104 if(xpathObj == NULL) {
105 fprintf(stderr,"Error: unable to evaluate xpath expression \"%s\"\n", xpathExpr);
106 xmlXPathFreeContext(xpathCtx);
107 xmlFreeDoc(doc);
108 return(-1);
109 }
110
111 /* update selected nodes */
112 update_xpath_nodes(xpathObj->nodesetval, value);
113
114
115 /* Cleanup of XPath data */
116 xmlXPathFreeObject(xpathObj);
117 xmlXPathFreeContext(xpathCtx);
118
119 /* dump the resulting document */
120 xmlDocDump(stdout, doc);
121
122
123 /* free the document */
124 xmlFreeDoc(doc);
125
126 return(0);
127 }
128
129 /**
130 * update_xpath_nodes:
131 * @nodes: the nodes set.
132 * @value: the new value for the node(s)
133 *
134 * Prints the @nodes content to @output.
135 */
136 static void
update_xpath_nodes(xmlNodeSetPtr nodes,const xmlChar * value)137 update_xpath_nodes(xmlNodeSetPtr nodes, const xmlChar* value) {
138 int size;
139 int i;
140
141 assert(value);
142 size = (nodes) ? nodes->nodeNr : 0;
143
144 /*
145 * NOTE: the nodes are processed in reverse order, i.e. reverse document
146 * order because xmlNodeSetContent can actually free up descendant
147 * of the node and such nodes may have been selected too ! Handling
148 * in reverse order ensure that descendant are accessed first, before
149 * they get removed. Mixing XPath and modifications on a tree must be
150 * done carefully !
151 */
152 for(i = size - 1; i >= 0; i--) {
153 assert(nodes->nodeTab[i]);
154
155 xmlNodeSetContent(nodes->nodeTab[i], value);
156 /*
157 * All the elements returned by an XPath query are pointers to
158 * elements from the tree *except* namespace nodes where the XPath
159 * semantic is different from the implementation in libxml2 tree.
160 * As a result when a returned node set is freed when
161 * xmlXPathFreeObject() is called, that routine must check the
162 * element type. But node from the returned set may have been removed
163 * by xmlNodeSetContent() resulting in access to freed data.
164 * This can be exercised by running
165 * valgrind xpath2 test3.xml '//discarded' discarded
166 * There is 2 ways around it:
167 * - make a copy of the pointers to the nodes from the result set
168 * then call xmlXPathFreeObject() and then modify the nodes
169 * or
170 * - remove the reference to the modified nodes from the node set
171 * as they are processed, if they are not namespace nodes.
172 */
173 if (nodes->nodeTab[i]->type != XML_NAMESPACE_DECL)
174 nodes->nodeTab[i] = NULL;
175 }
176 }
177
178 #else
main(void)179 int main(void) {
180 fprintf(stderr, "XPath support not compiled in\n");
181 return 0;
182 }
183 #endif
184