MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 1 | <?xml version="1.0"?> |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 2 | <!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" |
| 3 | "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [ |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 4 | <!ENTITY KEYWORD SYSTEM "includekeyword.c"> |
MDT 2003 John Fleck | 63f3a47 | 2003-07-24 21:48:30 +0000 | [diff] [blame] | 5 | <!ENTITY XPATH SYSTEM "includexpath.c"> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 6 | <!ENTITY STORY SYSTEM "includestory.xml"> |
| 7 | <!ENTITY ADDKEYWORD SYSTEM "includeaddkeyword.c"> |
| 8 | <!ENTITY ADDATTRIBUTE SYSTEM "includeaddattribute.c"> |
MDT 2002 John Fleck | 5452083 | 2002-06-13 03:30:26 +0000 | [diff] [blame] | 9 | <!ENTITY GETATTRIBUTE SYSTEM "includegetattribute.c"> |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 10 | <!ENTITY CONVERT SYSTEM "includeconvert.c"> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 11 | ]> |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 12 | <article lang="en"> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 13 | <articleinfo> |
| 14 | <title>Libxml Tutorial</title> |
| 15 | <author> |
| 16 | <firstname>John</firstname> |
| 17 | <surname>Fleck</surname> |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 18 | <email>jfleck@inkstain.net</email> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 19 | </author> |
| 20 | <copyright> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 21 | <year>2002, 2003</year> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 22 | <holder>John Fleck</holder> |
| 23 | </copyright> |
| 24 | <revhistory> |
| 25 | <revision> |
| 26 | <revnumber>1</revnumber> |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 27 | <date>June 4, 2002</date> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 28 | <revremark>Initial draft</revremark> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 29 | </revision> |
MDT 2002 John Fleck | 5452083 | 2002-06-13 03:30:26 +0000 | [diff] [blame] | 30 | <revision> |
| 31 | <revnumber>2</revnumber> |
| 32 | <date>June 12, 2002</date> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 33 | <revremark>retrieving attribute value added</revremark> |
MDT 2002 John Fleck | 5452083 | 2002-06-13 03:30:26 +0000 | [diff] [blame] | 34 | </revision> |
MDT 2002 John Fleck | 77e4d35 | 2002-09-01 01:37:11 +0000 | [diff] [blame] | 35 | <revision> |
| 36 | <revnumber>3</revnumber> |
| 37 | <date>Aug. 31, 2002</date> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 38 | <revremark>freeing memory fix</revremark> |
MDT 2002 John Fleck | 77e4d35 | 2002-09-01 01:37:11 +0000 | [diff] [blame] | 39 | </revision> |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 40 | <revision> |
| 41 | <revnumber>4</revnumber> |
| 42 | <date>Nov. 10, 2002</date> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 43 | <revremark>encoding discussion added</revremark> |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 44 | </revision> |
MST 2002 John Fleck | 44aacb3 | 2002-12-16 04:34:57 +0000 | [diff] [blame] | 45 | <revision> |
| 46 | <revnumber>5</revnumber> |
| 47 | <date>Dec. 15, 2002</date> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 48 | <revremark>more memory freeing changes</revremark> |
| 49 | </revision> |
| 50 | <revision> |
| 51 | <revnumber>6</revnumber> |
| 52 | <date>Jan. 26. 2003</date> |
| 53 | <revremark>add index</revremark> |
MST 2002 John Fleck | 44aacb3 | 2002-12-16 04:34:57 +0000 | [diff] [blame] | 54 | </revision> |
MDT 2003 John Fleck | 8aff3b7 | 2003-04-26 03:54:07 +0000 | [diff] [blame] | 55 | <revision> |
| 56 | <revnumber>7</revnumber> |
| 57 | <date>April 25, 2003</date> |
| 58 | <revremark>add compilation appendix</revremark> |
| 59 | </revision> |
MDT 2003 John Fleck | 63f3a47 | 2003-07-24 21:48:30 +0000 | [diff] [blame] | 60 | <revision> |
| 61 | <revnumber>8</revnumber> |
| 62 | <date>July 24, 2003</date> |
| 63 | <revremark>add XPath example</revremark> |
| 64 | </revision> |
MST 2004 John Fleck | d14bccc | 2004-02-15 01:57:42 +0000 | [diff] [blame] | 65 | <revision> |
| 66 | <revnumber>9</revnumber> |
| 67 | <date>Feb. 14, 2004</date> |
| 68 | <revremark>Fix bug in XPath example</revremark> |
| 69 | </revision> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 70 | </revhistory> |
| 71 | </articleinfo> |
| 72 | <abstract> |
| 73 | <para>Libxml is a freely licensed C language library for handling |
| 74 | <acronym>XML</acronym>, portable across a large number of platforms. This |
| 75 | tutorial provides examples of its basic functions.</para> |
| 76 | </abstract> |
| 77 | <sect1 id="introduction"> |
| 78 | <title>Introduction</title> |
| 79 | <para>Libxml is a C language library implementing functions for reading, |
| 80 | creating and manipulating <acronym>XML</acronym> data. This tutorial |
| 81 | provides example code and explanations of its basic functionality.</para> |
| 82 | <para>Libxml and more details about its use are available on <ulink |
| 83 | url="http://www.xmlsoft.org/">the project home page</ulink>. Included there is complete <ulink url="http://xmlsoft.org/html/libxml-lib.html"> |
| 84 | <acronym>API</acronym> documentation</ulink>. This tutorial is not meant |
| 85 | to substitute for that complete documentation, but to illustrate the |
| 86 | functions needed to use the library to perform basic operations. |
| 87 | <!-- |
| 88 | Links to |
| 89 | other resources can be found in <xref linkend="furtherresources" />. |
| 90 | --> |
| 91 | </para> |
| 92 | <para>The tutorial is based on a simple <acronym>XML</acronym> application I |
| 93 | use for articles I write. The format includes metadata and the body |
| 94 | of the article.</para> |
| 95 | <para>The example code in this tutorial demonstrates how to: |
| 96 | <itemizedlist> |
| 97 | <listitem> |
| 98 | <para>Parse the document.</para> |
| 99 | </listitem> |
| 100 | <listitem> |
| 101 | <para>Extract the text within a specified element.</para> |
| 102 | </listitem> |
| 103 | <listitem> |
| 104 | <para>Add an element and its content.</para> |
| 105 | </listitem> |
| 106 | <listitem> |
MDT 2002 John Fleck | 5452083 | 2002-06-13 03:30:26 +0000 | [diff] [blame] | 107 | <para>Add an attribute.</para> |
| 108 | </listitem> |
| 109 | <listitem> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 110 | <para>Extract the value of an attribute.</para> |
| 111 | </listitem> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 112 | </itemizedlist> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 113 | </para> |
| 114 | <para>Full code for the examples is included in the appendices.</para> |
| 115 | |
| 116 | </sect1> |
| 117 | |
| 118 | <sect1 id="xmltutorialdatatypes"> |
| 119 | <title>Data Types</title> |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 120 | <para><application>Libxml</application> declares a number of data types we |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 121 | will encounter repeatedly, hiding the messy stuff so you do not have to deal |
| 122 | with it unless you have some specific need.</para> |
| 123 | <para> |
| 124 | <variablelist> |
| 125 | <varlistentry> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 126 | <term><indexterm> |
| 127 | <primary>xmlChar</primary> |
| 128 | </indexterm> |
| 129 | <ulink |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 130 | url="http://xmlsoft.org/html/libxml-tree.html#XMLCHAR">xmlChar</ulink></term> |
| 131 | <listitem> |
| 132 | <para>A basic replacement for char, a byte in a UTF-8 encoded |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 133 | string. If your data uses another encoding, it must be converted to |
| 134 | UTF-8 for use with <application>libxml's</application> |
| 135 | functions. More information on encoding is available on the <ulink |
| 136 | url="http://www.xmlsoft.org/encoding.html"><application>libxml</application> encoding support web page</ulink>.</para> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 137 | </listitem> |
| 138 | </varlistentry> |
| 139 | <varlistentry> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 140 | <term><indexterm> |
| 141 | <primary>xmlDoc</primary> |
| 142 | </indexterm> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 143 | <ulink url="http://xmlsoft.org/html/libxml-tree.html#XMLDOC">xmlDoc</ulink></term> |
| 144 | <listitem> |
| 145 | <para>A structure containing the tree created by a parsed doc. <ulink |
| 146 | url="http://xmlsoft.org/html/libxml-tree.html#XMLDOCPTR">xmlDocPtr</ulink> |
| 147 | is a pointer to the structure.</para> |
| 148 | </listitem> |
| 149 | </varlistentry> |
| 150 | <varlistentry> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 151 | <term><indexterm> |
| 152 | <primary>xmlNodePtr</primary> |
| 153 | </indexterm> |
| 154 | <ulink |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 155 | url="http://xmlsoft.org/html/libxml-tree.html#XMLNODEPTR">xmlNodePtr</ulink> |
| 156 | and <ulink url="http://xmlsoft.org/html/libxml-tree.html#XMLNODE">xmlNode</ulink></term> |
| 157 | <listitem> |
| 158 | <para>A structure containing a single node. <ulink |
| 159 | url="http://xmlsoft.org/html/libxml-tree.html#XMLNODEPTR">xmlNodePtr</ulink> |
| 160 | is a pointer to the structure, and is used in traversing the document tree.</para> |
| 161 | </listitem> |
| 162 | </varlistentry> |
| 163 | </variablelist> |
| 164 | </para> |
| 165 | |
| 166 | </sect1> |
| 167 | |
| 168 | <sect1 id="xmltutorialparsing"> |
| 169 | <title>Parsing the file</title> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 170 | <para><indexterm id="fileparsing" class="startofrange"> |
MDT 2003 John Fleck | 8aff3b7 | 2003-04-26 03:54:07 +0000 | [diff] [blame] | 171 | <primary>file</primary> |
| 172 | <secondary>parsing</secondary> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 173 | </indexterm> |
| 174 | Parsing the file requires only the name of the file and a single |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 175 | function call, plus error checking. Full code: <xref |
| 176 | linkend="keywordappendix" /></para> |
| 177 | <para> |
| 178 | <programlisting> |
| 179 | <co id="declaredoc" /> xmlDocPtr doc; |
| 180 | <co id="declarenode" /> xmlNodePtr cur; |
| 181 | |
| 182 | <co id="parsefile" /> doc = xmlParseFile(docname); |
| 183 | |
| 184 | <co id="checkparseerror" /> if (doc == NULL ) { |
| 185 | fprintf(stderr,"Document not parsed successfully. \n"); |
| 186 | return; |
| 187 | } |
| 188 | |
| 189 | <co id="getrootelement" /> cur = xmlDocGetRootElement(doc); |
| 190 | |
| 191 | <co id="checkemptyerror" /> if (cur == NULL) { |
| 192 | fprintf(stderr,"empty document\n"); |
| 193 | xmlFreeDoc(doc); |
| 194 | return; |
| 195 | } |
| 196 | |
| 197 | <co id="checkroottype" /> if (xmlStrcmp(cur->name, (const xmlChar *) "story")) { |
| 198 | fprintf(stderr,"document of the wrong type, root node != story"); |
| 199 | xmlFreeDoc(doc); |
| 200 | return; |
| 201 | } |
| 202 | |
| 203 | </programlisting> |
| 204 | <calloutlist> |
| 205 | <callout arearefs="declaredoc"> |
| 206 | <para>Declare the pointer that will point to your parsed document.</para> |
| 207 | </callout> |
| 208 | <callout arearefs="declarenode"> |
| 209 | <para>Declare a node pointer (you'll need this in order to |
| 210 | interact with individual nodes).</para> |
| 211 | </callout> |
| 212 | <callout arearefs="checkparseerror"> |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 213 | <para>Check to see that the document was successfully parsed. If it |
| 214 | was not, <application>libxml</application> will at this point |
| 215 | register an error and stop. |
| 216 | <note> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 217 | <para><indexterm> |
| 218 | <primary>encoding</primary> |
| 219 | </indexterm> |
| 220 | One common example of an error at this point is improper |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 221 | handling of encoding. The <acronym>XML</acronym> standard requires |
| 222 | documents stored with an encoding other than UTF-8 or UTF-16 to |
| 223 | contain an explicit declaration of their encoding. If the |
| 224 | declaration is there, <application>libxml</application> will |
| 225 | automatically perform the necessary conversion to UTF-8 for |
| 226 | you. More information on <acronym>XML's</acronym> encoding |
| 227 | requirements is contained in the <ulink |
| 228 | url="http://www.w3.org/TR/REC-xml#charencoding">standard</ulink>.</para> |
| 229 | </note> |
| 230 | </para> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 231 | </callout> |
| 232 | <callout arearefs="getrootelement"> |
| 233 | <para>Retrieve the document's root element.</para> |
| 234 | </callout> |
| 235 | <callout arearefs="checkemptyerror"> |
| 236 | <para>Check to make sure the document actually contains something.</para> |
| 237 | </callout> |
| 238 | <callout arearefs="checkroottype"> |
| 239 | <para>In our case, we need to make sure the document is the right |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 240 | type. "story" is the root type of the documents used in this |
| 241 | tutorial.</para> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 242 | </callout> |
| 243 | </calloutlist> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 244 | <indexterm startref="fileparsing" class="endofrange" /> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 245 | </para> |
| 246 | </sect1> |
| 247 | |
| 248 | <sect1 id="xmltutorialgettext"> |
| 249 | <title>Retrieving Element Content</title> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 250 | <para><indexterm> |
| 251 | <primary>element</primary> |
| 252 | <secondary>retrieving content</secondary> |
| 253 | </indexterm> |
| 254 | Retrieving the content of an element involves traversing the document |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 255 | tree until you find what you are looking for. In this case, we are looking |
| 256 | for an element called "keyword" contained within element called "story". The |
| 257 | process to find the node we are interested in involves tediously walking the |
| 258 | tree. We assume you already have an xmlDocPtr called <varname>doc</varname> |
| 259 | and an xmlNodPtr called <varname>cur</varname>.</para> |
| 260 | |
| 261 | <para> |
| 262 | <programlisting> |
MST 2002 John Fleck | 7c67a83 | 2002-12-16 13:38:06 +0000 | [diff] [blame] | 263 | <co id="getchildnode" />cur = cur->xmlChildrenNode; |
| 264 | <co id="huntstoryinfo" />while (cur != NULL) { |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 265 | if ((!xmlStrcmp(cur->name, (const xmlChar *)"storyinfo"))){ |
| 266 | parseStory (doc, cur); |
| 267 | } |
| 268 | |
| 269 | cur = cur->next; |
| 270 | } |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 271 | </programlisting> |
| 272 | |
| 273 | <calloutlist> |
| 274 | <callout arearefs="getchildnode"> |
| 275 | <para>Get the first child node of <varname>cur</varname>. At this |
| 276 | point, <varname>cur</varname> points at the document root, which is |
| 277 | the element "story".</para> |
| 278 | </callout> |
| 279 | <callout arearefs="huntstoryinfo"> |
| 280 | <para>This loop iterates through the elements that are children of |
| 281 | "story", looking for one called "storyinfo". That |
| 282 | is the element that will contain the "keywords" we are |
| 283 | looking for. It uses the <application>libxml</application> string |
| 284 | comparison |
| 285 | function, <function><ulink |
| 286 | url="http://xmlsoft.org/html/libxml-parser.html#XMLSTRCMP">xmlStrcmp</ulink></function>. If there is a match, it calls the function <function>parseStory</function>.</para> |
| 287 | </callout> |
| 288 | </calloutlist> |
| 289 | </para> |
| 290 | |
| 291 | <para> |
| 292 | <programlisting> |
| 293 | void |
| 294 | parseStory (xmlDocPtr doc, xmlNodePtr cur) { |
| 295 | |
MST 2002 John Fleck | 7c67a83 | 2002-12-16 13:38:06 +0000 | [diff] [blame] | 296 | xmlChar *key; |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 297 | <co id="anothergetchild" /> cur = cur->xmlChildrenNode; |
| 298 | <co id="findkeyword" /> while (cur != NULL) { |
| 299 | if ((!xmlStrcmp(cur->name, (const xmlChar *)"keyword"))) { |
MST 2002 John Fleck | 7c67a83 | 2002-12-16 13:38:06 +0000 | [diff] [blame] | 300 | <co id="foundkeyword" /> key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); |
| 301 | printf("keyword: %s\n", key); |
| 302 | xmlFree(key); |
| 303 | } |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 304 | cur = cur->next; |
| 305 | } |
| 306 | return; |
| 307 | } |
| 308 | </programlisting> |
| 309 | <calloutlist> |
| 310 | <callout arearefs="anothergetchild"> |
| 311 | <para>Again we get the first child node.</para> |
| 312 | </callout> |
| 313 | <callout arearefs="findkeyword"> |
| 314 | <para>Like the loop above, we then iterate through the nodes, looking |
| 315 | for one that matches the element we're interested in, in this case |
| 316 | "keyword".</para> |
| 317 | </callout> |
| 318 | <callout arearefs="foundkeyword"> |
| 319 | <para>When we find the "keyword" element, we need to print |
| 320 | its contents. Remember that in <acronym>XML</acronym>, the text |
| 321 | contained within an element is a child node of that element, so we |
| 322 | turn to <varname>cur->xmlChildrenNode</varname>. To retrieve it, we |
| 323 | use the function <function><ulink |
| 324 | url="http://xmlsoft.org/html/libxml-tree.html#XMLNODELISTGETSTRING">xmlNodeListGetString</ulink></function>, which also takes the <varname>doc</varname> pointer as an argument. In this case, we just print it out.</para> |
MST 2002 John Fleck | 7c67a83 | 2002-12-16 13:38:06 +0000 | [diff] [blame] | 325 | <note> |
| 326 | <para>Because <function>xmlNodeListGetString</function> allocates |
| 327 | memory for the string it returns, you must use |
| 328 | <function>xmlFree</function> to free it.</para> |
| 329 | </note> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 330 | </callout> |
| 331 | </calloutlist> |
| 332 | </para> |
| 333 | |
| 334 | </sect1> |
MDT 2003 John Fleck | 63f3a47 | 2003-07-24 21:48:30 +0000 | [diff] [blame] | 335 | <sect1 id="xmltutorialxpath"> |
| 336 | <title>Using XPath to Retrieve Element Content</title> |
| 337 | <para>In addition to walking the document tree to find an element, |
| 338 | <application>Libxml2</application> includes support for |
| 339 | use of <application>XPath</application> expressions to retrieve sets of |
| 340 | nodes that match a specified criteria. Full documentation of the |
| 341 | <application>XPath</application> <acronym>API</acronym> is <ulink |
| 342 | url="http://xmlsoft.org/html/libxml-xpath.html">here</ulink>. |
| 343 | </para> |
| 344 | <para><application>XPath</application> allows searching through a document |
| 345 | for nodes that match specified criteria. In the example below we search |
| 346 | through a document for the contents of all <varname>keyword</varname> |
| 347 | elements. |
| 348 | <note> |
| 349 | <para>A full discussion of <application>XPath</application> is beyond |
| 350 | the scope of this document. For details on its use, see the <ulink |
| 351 | url="http://www.w3.org/TR/xpath">XPath specification</ulink>.</para> |
| 352 | </note> |
| 353 | Full code for this example is at <xref linkend="xpathappendix" />. |
| 354 | </para> |
| 355 | <para>Using <application>XPath</application> requires setting up an |
| 356 | xmlXPathContext and then supplying the <application>XPath</application> |
| 357 | expression and the context to the |
| 358 | <function>xmlXPathEvalExpression</function> function. The function returns |
| 359 | an xmlXPathObjectPtr, which includes the set of nodes satisfying the |
| 360 | <application>XPath</application> expression.</para> |
| 361 | <para> |
| 362 | <programlisting> |
| 363 | xmlXPathObjectPtr |
| 364 | getnodeset (xmlDocPtr doc, xmlChar *xpath){ |
| 365 | |
| 366 | <co id="cocontext" />xmlXPathContextPtr context; |
| 367 | xmlXPathObjectPtr result; |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 368 | |
MDT 2003 John Fleck | 63f3a47 | 2003-07-24 21:48:30 +0000 | [diff] [blame] | 369 | <co id="cocreatecontext" />context = xmlXPathNewContext(doc); |
| 370 | <co id="corunxpath" />result = xmlXPathEvalExpression(xpath, context); |
| 371 | <co id="cocheckxpathresult" />if(xmlXPathNodeSetIsEmpty(result->nodesetval)){ |
| 372 | printf("No result\n"); |
| 373 | return NULL; |
| 374 | } |
| 375 | xmlXPathFreeContext(context); |
| 376 | return result; |
| 377 | </programlisting> |
| 378 | <calloutlist> |
| 379 | <callout arearefs="cocontext"> |
| 380 | <para>First we declare our variables.</para> |
| 381 | </callout> |
| 382 | <callout arearefs="cocreatecontext"> |
| 383 | <para>Initialize the <varname>context</varname> variable.</para> |
| 384 | </callout> |
| 385 | <callout arearefs="corunxpath"> |
| 386 | <para>Apply the <application>XPath</application> expression.</para> |
| 387 | </callout> |
| 388 | <callout arearefs="cocheckxpathresult"> |
| 389 | <para>Check the result.</para> |
| 390 | </callout> |
| 391 | </calloutlist> |
| 392 | </para> |
| 393 | <para>The xmlPathObjectPtr returned by the function contains a set of nodes |
| 394 | and other information needed to iterate through the set and act on the |
| 395 | results. For this example, our functions returns the |
| 396 | <varname>xmlXPathObjectPtr</varname>. We use it to print the contents of |
| 397 | <varname>keyword</varname> nodes in our document. The node set object |
| 398 | includes the number of elements in the set (<varname>nodeNr</varname>) and |
| 399 | an array of nodes (<varname>nodeTab</varname>): |
| 400 | <programlisting> |
| 401 | <co id="conodesetcounter" />for (i=0; i < nodeset->nodeNr; i++) { |
| 402 | <co id="coprintkeywords" />keyword = xmlNodeListGetString(doc, nodeset->nodeTab[i]->xmlChildrenNode, 1); |
| 403 | printf("keyword: %s\n", keyword); |
MST 2004 John Fleck | d14bccc | 2004-02-15 01:57:42 +0000 | [diff] [blame] | 404 | xmlFree(keyword); |
MDT 2003 John Fleck | 63f3a47 | 2003-07-24 21:48:30 +0000 | [diff] [blame] | 405 | } |
| 406 | </programlisting> |
| 407 | <calloutlist> |
| 408 | <callout arearefs="conodesetcounter"> |
| 409 | <para>The value of <varname>nodeset->Nr</varname> holds the number of |
| 410 | elements in the node set. Here we use it to iterate through the array.</para> |
| 411 | </callout> |
| 412 | <callout arearefs="coprintkeywords"> |
| 413 | <para>Here we print the contents of each of the nodes returned. |
| 414 | <note> |
| 415 | <para>Note that we are printing the child node of the node that is |
| 416 | returned, because the contents of the <varname>keyword</varname> |
| 417 | element are a child text node.</para> |
| 418 | </note> |
| 419 | </para> |
| 420 | </callout> |
| 421 | </calloutlist> |
| 422 | </para> |
| 423 | </sect1> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 424 | <sect1 id="xmltutorialwritingcontent"> |
| 425 | <title>Writing element content</title> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 426 | <para><indexterm> |
| 427 | <primary>element</primary> |
| 428 | <secondary>writing content</secondary> |
| 429 | </indexterm> |
MDT 2003 John Fleck | 63f3a47 | 2003-07-24 21:48:30 +0000 | [diff] [blame] | 430 | Writing element content uses many of the same steps we used above |
| 431 | — parsing the document and walking the tree. We parse the document, |
| 432 | then traverse the tree to find the place we want to insert our element. For |
| 433 | this example, we want to again find the "storyinfo" element and |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 434 | this time insert a keyword. Then we'll write the file to disk. Full code: |
MDT 2003 John Fleck | 63f3a47 | 2003-07-24 21:48:30 +0000 | [diff] [blame] | 435 | <xref linkend="addkeywordappendix" /></para> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 436 | <para> |
| 437 | The main difference in this example is in |
| 438 | <function>parseStory</function>: |
| 439 | |
| 440 | <programlisting> |
| 441 | void |
| 442 | parseStory (xmlDocPtr doc, xmlNodePtr cur, char *keyword) { |
| 443 | |
| 444 | <co id="addkeyword" /> xmlNewTextChild (cur, NULL, "keyword", keyword); |
| 445 | return; |
| 446 | } |
| 447 | </programlisting> |
| 448 | <calloutlist> |
| 449 | <callout arearefs="addkeyword"> |
| 450 | <para>The <function><ulink |
| 451 | url="http://xmlsoft.org/html/libxml-tree.html#XMLNEWTEXTCHILD">xmlNewTextChild</ulink></function> |
| 452 | function adds a new child element at the |
| 453 | current node pointer's location in the |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 454 | tree, specified by <varname>cur</varname>.</para> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 455 | </callout> |
| 456 | </calloutlist> |
| 457 | </para> |
| 458 | |
| 459 | <para> |
MDT 2003 John Fleck | 8aff3b7 | 2003-04-26 03:54:07 +0000 | [diff] [blame] | 460 | <indexterm> |
| 461 | <primary>file</primary> |
| 462 | <secondary>saving</secondary> |
| 463 | </indexterm> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 464 | Once the node has been added, we would like to write the document to |
| 465 | file. Is you want the element to have a namespace, you can add it here as |
| 466 | well. In our case, the namespace is NULL. |
| 467 | <programlisting> |
| 468 | xmlSaveFormatFile (docname, doc, 1); |
| 469 | </programlisting> |
| 470 | The first parameter is the name of the file to be written. You'll notice |
| 471 | it is the same as the file we just read. In this case, we just write over |
| 472 | the old file. The second parameter is a pointer to the xmlDoc |
| 473 | structure. Setting the third parameter equal to one ensures indenting on output. |
| 474 | </para> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 475 | </sect1> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 476 | |
| 477 | <sect1 id="xmltutorialwritingattribute"> |
| 478 | <title>Writing Attribute</title> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 479 | <para><indexterm> |
| 480 | <primary>attribute</primary> |
| 481 | <secondary>writing</secondary> |
| 482 | </indexterm> |
| 483 | Writing an attribute is similar to writing text to a new element. In |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 484 | this case, we'll add a reference <acronym>URI</acronym> to our |
| 485 | document. Full code:<xref linkend="addattributeappendix" />.</para> |
| 486 | <para> |
| 487 | A <sgmltag>reference</sgmltag> is a child of the <sgmltag>story</sgmltag> |
| 488 | element, so finding the place to put our new element and attribute is |
| 489 | simple. As soon as we do the error-checking test in our |
| 490 | <function>parseDoc</function>, we are in the right spot to add our |
| 491 | element. But before we do that, we need to make a declaration using a |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 492 | data type we have not seen yet: |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 493 | <programlisting> |
| 494 | xmlAttrPtr newattr; |
| 495 | </programlisting> |
| 496 | We also need an extra xmlNodePtr: |
| 497 | <programlisting> |
| 498 | xmlNodePtr newnode; |
| 499 | </programlisting> |
| 500 | </para> |
| 501 | <para> |
| 502 | The rest of <function>parseDoc</function> is the same as before until we |
| 503 | check to see if our root element is <sgmltag>story</sgmltag>. If it is, |
| 504 | then we know we are at the right spot to add our element: |
| 505 | |
| 506 | <programlisting> |
| 507 | <co id="addreferencenode" /> newnode = xmlNewTextChild (cur, NULL, "reference", NULL); |
| 508 | <co id="addattributenode" /> newattr = xmlNewProp (newnode, "uri", uri); |
| 509 | </programlisting> |
| 510 | <calloutlist> |
| 511 | <callout arearefs="addreferencenode"> |
| 512 | <para>First we add a new node at the location of the current node |
| 513 | pointer, <varname>cur.</varname> using the <ulink |
| 514 | url="http://xmlsoft.org/html/libxml-tree.html#XMLNEWTEXTCHILD">xmlNewTextChild</ulink> function.</para> |
| 515 | </callout> |
| 516 | </calloutlist> |
| 517 | </para> |
| 518 | |
| 519 | <para>Once the node is added, the file is written to disk just as in the |
| 520 | previous example in which we added an element with text content.</para> |
| 521 | |
| 522 | </sect1> |
| 523 | |
MDT 2002 John Fleck | 5452083 | 2002-06-13 03:30:26 +0000 | [diff] [blame] | 524 | <sect1 id="xmltutorialattribute"> |
| 525 | <title>Retrieving Attributes</title> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 526 | <para><indexterm> |
| 527 | <primary>attribute</primary> |
| 528 | <secondary>retrieving value</secondary> |
| 529 | </indexterm> |
| 530 | Retrieving the value of an attribute is similar to the previous |
MDT 2002 John Fleck | 5452083 | 2002-06-13 03:30:26 +0000 | [diff] [blame] | 531 | example in which we retrieved a node's text contents. In this case we'll |
| 532 | extract the value of the <acronym>URI</acronym> we added in the previous |
| 533 | section. Full code: <xref linkend="getattributeappendix" />.</para> |
| 534 | <para> |
| 535 | The initial steps for this example are similar to the previous ones: parse |
| 536 | the doc, find the element you are interested in, then enter a function to |
| 537 | carry out the specific task required. In this case, we call |
| 538 | <function>getReference</function>: |
| 539 | <programlisting> |
| 540 | void |
| 541 | getReference (xmlDocPtr doc, xmlNodePtr cur) { |
| 542 | |
MST 2002 John Fleck | 44aacb3 | 2002-12-16 04:34:57 +0000 | [diff] [blame] | 543 | xmlChar *uri; |
MDT 2002 John Fleck | 5452083 | 2002-06-13 03:30:26 +0000 | [diff] [blame] | 544 | cur = cur->xmlChildrenNode; |
| 545 | while (cur != NULL) { |
| 546 | if ((!xmlStrcmp(cur->name, (const xmlChar *)"reference"))) { |
MST 2002 John Fleck | 44aacb3 | 2002-12-16 04:34:57 +0000 | [diff] [blame] | 547 | <co id="getattributevalue" /> uri = xmlGetProp(cur, "uri"); |
| 548 | printf("uri: %s\n", uri); |
| 549 | xmlFree(uri); |
| 550 | } |
MDT 2002 John Fleck | 5452083 | 2002-06-13 03:30:26 +0000 | [diff] [blame] | 551 | cur = cur->next; |
| 552 | } |
MST 2002 John Fleck | 44aacb3 | 2002-12-16 04:34:57 +0000 | [diff] [blame] | 553 | return; |
MDT 2002 John Fleck | 5452083 | 2002-06-13 03:30:26 +0000 | [diff] [blame] | 554 | } |
| 555 | </programlisting> |
| 556 | |
| 557 | <calloutlist> |
| 558 | <callout arearefs="getattributevalue"> |
| 559 | <para> |
| 560 | The key function is <function><ulink |
| 561 | url="http://xmlsoft.org/html/libxml-tree.html#XMLGETPROP">xmlGetProp</ulink></function>, which returns an |
| 562 | <varname>xmlChar</varname> containing the attribute's value. In this case, |
| 563 | we just print it out. |
| 564 | <note> |
| 565 | <para> |
| 566 | If you are using a <acronym>DTD</acronym> that declares a fixed or |
| 567 | default value for the attribute, this function will retrieve it. |
| 568 | </para> |
| 569 | </note> |
| 570 | </para> |
| 571 | </callout> |
| 572 | </calloutlist> |
| 573 | |
| 574 | </para> |
| 575 | </sect1> |
| 576 | |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 577 | <sect1 id="xmltutorialconvert"> |
| 578 | <title>Encoding Conversion</title> |
| 579 | |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 580 | <para><indexterm> |
| 581 | <primary>encoding</primary> |
| 582 | </indexterm> |
| 583 | Data encoding compatibility problems are one of the most common |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 584 | difficulties encountered by programmers new to <acronym>XML</acronym> in |
| 585 | general and <application>libxml</application> in particular. Thinking |
| 586 | through the design of your application in light of this issue will help |
| 587 | avoid difficulties later. Internally, <application>libxml</application> |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 588 | stores and manipulates data in the UTF-8 format. Data used by your program |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 589 | in other formats, such as the commonly used ISO-8859-1 encoding, must be |
| 590 | converted to UTF-8 before passing it to <application>libxml</application> |
| 591 | functions. If you want your program's output in an encoding other than |
| 592 | UTF-8, you also must convert it.</para> |
| 593 | |
| 594 | <para><application>Libxml</application> uses |
| 595 | <application>iconv</application> if it is available to convert |
| 596 | data. Without <application>iconv</application>, only UTF-8, UTF-16 and |
| 597 | ISO-8859-1 can be used as external formats. With |
| 598 | <application>iconv</application>, any format can be used provided |
| 599 | <application>iconv</application> is able to convert it to and from |
| 600 | UTF-8. Currently <application>iconv</application> supports about 150 |
| 601 | different character formats with ability to convert from any to any. While |
| 602 | the actual number of supported formats varies between implementations, every |
| 603 | <application>iconv</application> implementation is almost guaranteed to |
| 604 | support every format anyone has ever heard of.</para> |
| 605 | |
| 606 | <warning> |
| 607 | <para>A common mistake is to use different formats for the internal data |
| 608 | in different parts of one's code. The most common case is an application |
| 609 | that assumes ISO-8859-1 to be the internal data format, combined with |
| 610 | <application>libxml</application>, which assumes UTF-8 to be the |
| 611 | internal data format. The result is an application that treats internal |
| 612 | data differently, depending on which code section is executing. The one or |
| 613 | the other part of code will then, naturally, misinterpret the data. |
| 614 | </para> |
| 615 | </warning> |
| 616 | |
| 617 | <para>This example constructs a simple document, then adds content provided |
| 618 | at the command line to the document's root element and outputs the results |
| 619 | to <filename>stdout</filename> in the proper encoding. For this example, we |
| 620 | use ISO-8859-1 encoding. The encoding of the string input at the command |
| 621 | line is converted from ISO-8859-1 to UTF-8. Full code: <xref |
| 622 | linkend="convertappendix" /></para> |
| 623 | |
| 624 | <para>The conversion, encapsulated in the example code in the |
| 625 | <function>convert</function> function, uses |
| 626 | <application>libxml's</application> |
| 627 | <function>xmlFindCharEncodingHandler</function> function: |
| 628 | <programlisting> |
| 629 | <co id="handlerdatatype" />xmlCharEncodingHandlerPtr handler; |
| 630 | <co id="calcsize" />size = (int)strlen(in)+1; |
| 631 | out_size = size*2-1; |
| 632 | out = malloc((size_t)out_size); |
| 633 | |
| 634 | … |
| 635 | <co id="findhandlerfunction" />handler = xmlFindCharEncodingHandler(encoding); |
| 636 | … |
| 637 | <co id="callconversionfunction" />handler->input(out, &out_size, in, &temp); |
| 638 | … |
| 639 | <co id="outputencoding" />xmlSaveFormatFileEnc("-", doc, encoding, 1); |
| 640 | </programlisting> |
| 641 | <calloutlist> |
| 642 | <callout arearefs="handlerdatatype"> |
| 643 | <para><varname>handler</varname> is declared as a pointer to an |
| 644 | <function>xmlCharEncodingHandler</function> function.</para> |
| 645 | </callout> |
| 646 | <callout arearefs="calcsize"> |
| 647 | <para>The <function>xmlCharEncodingHandler</function> function needs |
| 648 | to be given the size of the input and output strings, which are |
| 649 | calculated here for strings <varname>in</varname> and |
| 650 | <varname>out</varname>.</para> |
| 651 | </callout> |
| 652 | <callout arearefs="findhandlerfunction"> |
| 653 | <para><function>xmlFindCharEncodingHandler</function> takes as its |
| 654 | argument the data's initial encoding and searches |
| 655 | <application>libxml's</application> built-in set of conversion |
| 656 | handlers, returning a pointer to the function or NULL if none is |
| 657 | found.</para> |
| 658 | </callout> |
| 659 | <callout arearefs="callconversionfunction"> |
| 660 | <para>The conversion function identified by <varname>handler</varname> |
| 661 | requires as its arguments pointers to the input and output strings, |
| 662 | along with the length of each. The lengths must be determined |
| 663 | separately by the application.</para> |
| 664 | </callout> |
| 665 | <callout arearefs="outputencoding"> |
| 666 | <para>To output in a specified encoding rather than UTF-8, we use |
| 667 | <function>xmlSaveFormatFileEnc</function>, specifying the |
| 668 | encoding.</para> |
| 669 | </callout> |
| 670 | </calloutlist> |
| 671 | </para> |
| 672 | </sect1> |
| 673 | |
MDT 2003 John Fleck | 8aff3b7 | 2003-04-26 03:54:07 +0000 | [diff] [blame] | 674 | <appendix id="compilation"> |
| 675 | <title>Compilation</title> |
| 676 | <para><indexterm> |
| 677 | <primary>compiler flags</primary> |
| 678 | </indexterm> |
| 679 | <application>Libxml</application> includes a script, |
| 680 | <application>xml2-config</application>, that can be used to generate |
| 681 | flags for compilation and linking of programs written with the |
| 682 | library. For pre-processor and compiler flags, use <command>xml2-config |
| 683 | --cflags</command>. For library linking flags, use <command>xml2-config |
| 684 | --libs</command>. Other options are available using <command>xml2-config |
| 685 | --help</command>.</para> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 686 | </appendix> |
MDT 2003 John Fleck | 8aff3b7 | 2003-04-26 03:54:07 +0000 | [diff] [blame] | 687 | |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 688 | <appendix id="sampledoc"> |
| 689 | <title>Sample Document</title> |
| 690 | <programlisting>&STORY;</programlisting> |
| 691 | </appendix> |
| 692 | <appendix id="keywordappendix"> |
| 693 | <title>Code for Keyword Example</title> |
| 694 | <para> |
| 695 | <programlisting>&KEYWORD;</programlisting> |
| 696 | </para> |
| 697 | </appendix> |
MDT 2003 John Fleck | 63f3a47 | 2003-07-24 21:48:30 +0000 | [diff] [blame] | 698 | <appendix id="xpathappendix"> |
| 699 | <title>Code for XPath Example</title> |
| 700 | <para> |
| 701 | <programlisting>&XPATH;</programlisting> |
| 702 | </para> |
| 703 | </appendix> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 704 | <appendix id="addkeywordappendix"> |
| 705 | <title>Code for Add Keyword Example</title> |
| 706 | <para> |
| 707 | <programlisting>&ADDKEYWORD;</programlisting> |
| 708 | </para> |
| 709 | </appendix> |
| 710 | <appendix id="addattributeappendix"> |
| 711 | <title>Code for Add Attribute Example</title> |
| 712 | <para> |
| 713 | <programlisting>&ADDATTRIBUTE;</programlisting> |
| 714 | </para> |
| 715 | </appendix> |
MDT 2002 John Fleck | 5452083 | 2002-06-13 03:30:26 +0000 | [diff] [blame] | 716 | <appendix id="getattributeappendix"> |
| 717 | <title>Code for Retrieving Attribute Value Example</title> |
| 718 | <para> |
| 719 | <programlisting>&GETATTRIBUTE;</programlisting> |
| 720 | </para> |
| 721 | </appendix> |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 722 | <appendix id="convertappendix"> |
| 723 | <title>Code for Encoding Conversion Example</title> |
| 724 | <para> |
| 725 | <programlisting>&CONVERT;</programlisting> |
| 726 | </para> |
| 727 | </appendix> |
| 728 | <appendix> |
| 729 | <title>Acknowledgements</title> |
| 730 | <para>A number of people have generously offered feedback, code and |
| 731 | suggested improvements to this tutorial. In no particular order: |
MST 2003 John Fleck | 731967e | 2003-01-27 00:39:50 +0000 | [diff] [blame] | 732 | <simplelist type="inline"> |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 733 | <member>Daniel Veillard</member> |
| 734 | <member>Marcus Labib Iskander</member> |
| 735 | <member>Christopher R. Harris</member> |
| 736 | <member>Igor Zlatkovic</member> |
MST 2002 John Fleck | 44aacb3 | 2002-12-16 04:34:57 +0000 | [diff] [blame] | 737 | <member>Niraj Tolia</member> |
MDT 2003 John Fleck | 63f3a47 | 2003-07-24 21:48:30 +0000 | [diff] [blame] | 738 | <member>David Turover</member> |
MST 2002 John Fleck | bd3b4fd | 2002-11-11 03:41:11 +0000 | [diff] [blame] | 739 | </simplelist> |
| 740 | </para> |
| 741 | </appendix> |
MDT 2003 John Fleck | 8aff3b7 | 2003-04-26 03:54:07 +0000 | [diff] [blame] | 742 | <index /> |
MDT 2002 John Fleck | 598f6eb | 2002-06-04 15:10:36 +0000 | [diff] [blame] | 743 | </article> |