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
25static void usage(const char *name);
26static int example4(const char *filename, const xmlChar * xpathExpr,
27                    const xmlChar * value);
28static void update_xpath_nodes(xmlNodeSetPtr nodes, const xmlChar * value);
29
30
31int
32main(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 */
66static void
67usage(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 */
84static int
85example4(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 */
143static void
144update_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
186int main(void) {
187    fprintf(stderr, "XPath support not compiled in\n");
188    exit(1);
189}
190#endif
191