xpath2.pas 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  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. program xpath2;
  12. {$mode objfpc}
  13. uses
  14. ctypes,
  15. xml2,
  16. exutils,
  17. SysUtils;
  18. (**
  19. * usage:
  20. * @name: the program name.
  21. *
  22. * Prints usage information.
  23. *)
  24. procedure usage(const name: String);
  25. begin
  26. //assert(name);
  27. printfn('Usage: %s <xml-file> <xpath-expr> <value>', [name]);
  28. end;
  29. (**
  30. * update_xpath_nodes:
  31. * @nodes: the nodes set.
  32. * @value: the new value for the node(s)
  33. *
  34. * Prints the @nodes content to @output.
  35. *)
  36. procedure update_xpath_nodes(nodes: xmlNodeSetPtr; const value: xmlCharPtr);
  37. var
  38. size: cint;
  39. i: cint;
  40. begin
  41. assert(value <> Nil);
  42. if nodes <> nil then
  43. size := nodes^.nodeNr
  44. else
  45. size := 0;
  46. (*
  47. * NOTE: the nodes are processed in reverse order, i.e. reverse document
  48. * order because xmlNodeSetContent can actually free up descendant
  49. * of the node and such nodes may have been selected too ! Handling
  50. * in reverse order ensure that descendant are accessed first, before
  51. * they get removed. Mixing XPath and modifications on a tree must be
  52. * done carefully !
  53. *)
  54. for i := size - 1 downto 0 do
  55. begin
  56. assert(nodes^.nodeTab[i] <> Nil);
  57. xmlNodeSetContent(nodes^.nodeTab[i], value);
  58. (*
  59. * All the elements returned by an XPath query are pointers to
  60. * elements from the tree *except* namespace nodes where the XPath
  61. * semantic is different from the implementation in libxml2 tree.
  62. * As a result when a returned node set is freed when
  63. * xmlXPathFreeObject() is called, that routine must check the
  64. * element type. But node from the returned set may have been removed
  65. * by xmlNodeSetContent() resulting in access to freed data.
  66. * This can be exercised by running
  67. * valgrind xpath2 test3.xml '//discarded' discarded
  68. * There is 2 ways around it:
  69. * - make a copy of the pointers to the nodes from the result set
  70. * then call xmlXPathFreeObject() and then modify the nodes
  71. * or
  72. * - remove the reference to the modified nodes from the node set
  73. * as they are processed, if they are not namespace nodes.
  74. *)
  75. if nodes^.nodeTab[i]^._type <> XML_NAMESPACE_DECL then
  76. nodes^.nodeTab[i] := Nil;
  77. end;
  78. end;
  79. (**
  80. * example4:
  81. * @filename: the input XML filename.
  82. * @xpathExpr: the xpath expression for evaluation.
  83. * @value: the new node content.
  84. *
  85. * Parses input XML file, evaluates XPath expression and update the nodes
  86. * then print the result.
  87. *
  88. * Returns 0 on success and a negative value otherwise.
  89. *)
  90. function example4(const filename: PAnsiChar; const xpathExpr, value: xmlCharPtr): cint;
  91. var
  92. doc: xmlDocPtr;
  93. xpathCtx: xmlXPathContextPtr;
  94. xpathObj: xmlXPathObjectPtr;
  95. mem: xmlCharPtr;
  96. memLen: Integer;
  97. begin
  98. assert(filename <> Nil);
  99. assert(xpathExpr <> Nil);
  100. assert(value <> Nil);
  101. (* Load XML document *)
  102. doc := xmlParseFile(filename);
  103. if doc = Nil then
  104. begin
  105. printfn('Error: unable to parse file "%s"', [filename]);
  106. Exit(-1);
  107. end;
  108. (* Create xpath evaluation context *)
  109. xpathCtx := xmlXPathNewContext(doc);
  110. if xpathCtx = Nil then
  111. begin
  112. printfn('Error: unable to create new XPath context');
  113. xmlFreeDoc(doc);
  114. Exit(-1);
  115. end;
  116. (* Evaluate xpath expression *)
  117. xpathObj := xmlXPathEvalExpression(xpathExpr, xpathCtx);
  118. if xpathObj = Nil then
  119. begin
  120. printfn('Error: unable to evaluate xpath expression "%s"', [xpathExpr]);
  121. xmlXPathFreeContext(xpathCtx);
  122. xmlFreeDoc(doc);
  123. Exit(-1);
  124. end;
  125. (* update selected nodes *)
  126. update_xpath_nodes(xpathObj^.nodesetval, value);
  127. (* Cleanup of XPath data *)
  128. xmlXPathFreeObject(xpathObj);
  129. xmlXPathFreeContext(xpathCtx);
  130. (* dump the resulting document *)
  131. xmlDocDumpMemory(doc, mem, memLen);
  132. WriteLn(mem);
  133. xmlFree(mem);
  134. (* free the document *)
  135. xmlFreeDoc(doc);
  136. Result := 0;
  137. end;
  138. begin
  139. (* Parse command line and process file *)
  140. if ParamCount <> 3 then
  141. begin
  142. printfn('Error: wrong number of arguments.');
  143. usage(ParamStr(0));
  144. Halt(-1);
  145. end;
  146. (* Init libxml *)
  147. xmlInitParser();
  148. LIBXML_TEST_VERSION;
  149. (* Do the main job *)
  150. if example4(PAnsiChar(ParamStr(1)), PAnsiChar(ParamStr(2)), PAnsiChar(ParamStr(3))) <> 0 then
  151. begin
  152. usage(ParamStr(0));
  153. Halt(-1);
  154. end;
  155. (* Shutdown libxml *)
  156. xmlCleanupParser();
  157. (*
  158. * this is to debug memory for regression tests
  159. *)
  160. xmlMemoryDump();
  161. end.