From 0f8052e766feca3ba23080cb9feda5d592943639 Mon Sep 17 00:00:00 2001
From: Michael R Sweet
- /* - * 'type_cb()' - XML data type callback for mxmlLoadFile()... - */ - - mxml_type_t /* O - Data type */ - type_cb(mxml_node_t *node) /* I - Element node */ + mxml_type_t + type_cb(mxml_node_t *node) { - const char *type; /* Type string */ - + const char *type; /* - * You can lookup attributes and/or use the element name, hierarchy, etc... + * You can lookup attributes and/or use the + * element name, hierarchy, etc... */ - if ((type = mxmlElementGetAttr(node, "type")) == NULL) + type = mxmlElementGetAttr(node, "type"); + if (type == NULL) type = node->value.element.name; if (!strcmp(type, "integer")) @@ -84,10 +81,10 @@ call any of the load functions:FILE *fp; - mxml_node_t *tree; + mxml_node_t *tree; fp = fopen("filename.xml", "r"); - tree = mxmlLoadFile(NULL, fp, type_cb); + tree = mxmlLoadFile(NULL, fp, type_cb); fclose(fp);@@ -108,54 +105,66 @@ of MXML_WS_BEFORE_OPEN, MXML_WS_AFTER_OPEN, MXML_WS_BEFORE_CLOSE, or MXML_WS_AFTER_CLOSE. The callback function should return NULL if no whitespace should be added and the string to insert (spaces, -tabs, carriage returns, and newlines) otherwise. The following -whitespace callback can be used to add whitespace to XHTML -output to make it more readable in a standard text editor: +tabs, carriage returns, and newlines) otherwise. + +The following whitespace callback can be used to add +whitespace to XHTML output to make it more readable in a standard +text editor:
-- /* - * 'whitespace_cb()' - Let the mxmlSaveFile() function know when to insert - * newlines and tabs... - */ - - const char * /* O - Whitespace string or NULL */ - whitespace_cb(mxml_node_t *node, /* I - Element node */ - int where) /* I - Open or close tag? */ + const char * + whitespace_cb(mxml_node_t *node, + int where) { - const char *name; /* Name of element */ + const char *name; /* - * We can conditionally break to a new line before or after any element. - * These are just common HTML elements... + * We can conditionally break to a new line + * before or after any element. These are + * just common HTML elements... */ name = node->value.element.name; - if (!strcmp(name, "html") || !strcmp(name, "head") || !strcmp(name, "body") || - !strcmp(name, "pre") || !strcmp(name, "p") || - !strcmp(name, "h1") || !strcmp(name, "h2") || !strcmp(name, "h3") || - !strcmp(name, "h4") || !strcmp(name, "h5") || !strcmp(name, "h6")) + if (!strcmp(name, "html") || + !strcmp(name, "head") || + !strcmp(name, "body") || + !strcmp(name, "pre") || + !strcmp(name, "p") || + !strcmp(name, "h1") || + !strcmp(name, "h2") || + !strcmp(name, "h3") || + !strcmp(name, "h4") || + !strcmp(name, "h5") || + !strcmp(name, "h6")) { /* - * Newlines before open and after close... + * Newlines before open and after + * close... */ - if (where == MXML_WS_BEFORE_OPEN || where == MXML_WS_AFTER_CLOSE) + if (where == MXML_WS_BEFORE_OPEN || + where == MXML_WS_AFTER_CLOSE) return ("\n"); } - else if (!strcmp(name, "dl") || !strcmp(name, "ol") || !strcmp(name, "ul")) + else if (!strcmp(name, "dl") || + !strcmp(name, "ol") || + !strcmp(name, "ul")) { /* - * Put a newline before and after list elements... + * Put a newline before and after list + * elements... */ return ("\n"); } - else if (!strcmp(name, "dd") || !strcmp(name, "dt") || !strcmp(name, "li")) + else if (!strcmp(name, "dd") || + !strcmp(name, "dt") || + !strcmp(name, "li")) { /* - * Put a tab before <li>'s, <dd>'s, and <dt>'s, and a newline after them... + * Put a tab before <li>'s, * <dd>'s, + * and <dt>'s, and a newline after them... */ if (where == MXML_WS_BEFORE_OPEN) @@ -172,20 +181,20 @@ output to make it more readable in a standard text editor: }-To use this callback function, simply use the name when you call any of the save functions:
FILE *fp; - mxml_node_t *tree; + mxml_node_t *tree; fp = fopen("filename.xml", "w"); - mxmlSaveFile(tree, fp, whitespace_cb); + mxmlSaveFile(tree, fp, whitespace_cb); fclose(fp);+Custom Data Types
Mini-XML supports custom data types via global load and save @@ -205,22 +214,21 @@ following:
typedef struct { - unsigned year, /* Year */ - month, /* Month */ - day, /* Day */ - hour, /* Hour */ - minute, /* Minute */ - second; /* Second */ - time_t unix; /* UNIX time value */ + unsigned year, /* Year */ + month, /* Month */ + day, /* Day */ + hour, /* Hour */ + minute, /* Minute */ + second; /* Second */ + time_t unix; /* UNIX time */ } iso_date_time_t; - int /* I - 0 on success, -1 on error */ - load_custom(mxml_node_t *node, /* I - Node */ - const char *data) /* I - Value */ + int + load_custom(mxml_node_t *node, + const char *data) { - iso_date_time_t *dt; /* Date/time value */ - struct tm tmdata; /* UNIX time data */ - + iso_date_time_t *dt; + struct tm tmdata; /* * Allocate data structure... @@ -229,16 +237,19 @@ following: dt = calloc(1, sizeof(iso_date_time_t)); /* - * Try reading 6 unsigned integers from the data string... + * Try reading 6 unsigned integers from the + * data string... */ if (sscanf(data, "%u-%u-%uT%u:%u:%uZ", - &(dt->year), &(dt->month), &(dt->day), - &(dt->hour), &(dt->minute), &(dt->second)) != 6) + &(dt->year), &(dt->month), + &(dt->day), &(dt->hour), + &(dt->minute), + &(dt->second)) != 6) { /* - * Unable to read numbers, free the data structure and return an - * error... + * Unable to read numbers, free the data + * structure and return an error... */ free(dt); @@ -266,7 +277,8 @@ following: } /* - * Convert ISO time to UNIX time in seconds... + * Convert ISO time to UNIX time in + * seconds... */ tmdata.tm_year = dt->year - 1900; @@ -276,10 +288,11 @@ following: tmdata.tm_min = dt->minute; tmdata.tm_sec = dt->second; - dt->unix = gmtime(&tmdata); + dt->unix = gmtime(&tmdata); /* - * Assign custom node data and destroy function pointers... + * Assign custom node data and destroy + * function pointers... */ node->value.custom.data = dt; @@ -300,24 +313,24 @@ allocated custom data for the node and a pointer to a destructor function which will free the custom data when the node is deleted. -The save callback receives the node pointer and returns an allocated string containing the custom data value. The following save callback could be used for our ISO date/time type:
- char * /* I - Allocated string */ - save_custom(mxml_node_t *node) /* I - Node */ + char * + save_custom(mxml_node_t *node) { - char data[255]; /* Data string */ - iso_date_time_t *dt; /* ISO date/time pointer */ + char data[255]; + iso_date_time_t *dt; dt = (iso_date_time_t *)node->custom.data; - snprintf(data, sizeof(data), "%04u-%02u-%02uT%02u:%02u:%02uZ", - dt->year, dt->month, dt->day, dt->hour, - dt->minute, dt->second); + snprintf(data, sizeof(data), + "%04u-%02u-%02uT%02u:%02u:%02uZ", + dt->year, dt->month, dt->day, + dt->hour, dt->minute, dt->second); return (strdup(data)); } @@ -328,7 +341,8 @@ href='#mxmlSetCustomHandlers'>mxmlSetCustomHandlers() function:- mxmlSetCustomHandlers(load_custom, save_custom); + mxmlSetCustomHandlers(load_custom, + save_custom);@@ -344,30 +358,32 @@ without leaking memory. href='#mxmlSetElement'>mxmlSetElement(), mxmlSetInteger(), mxmlSetOpaque(), mxmlSetReal(), and mxmlSetText() functions. For +href='#mxmlSetReal'>mxmlSetReal(), mxmlSetText(), and mxmlSetTextf() functions. For example, use the following function call to change a text node to contain the text "new" with leading whitespace:- mxml_node_t *node; + mxml_node_t *node; - mxmlSetText(node, 1, "new"); + mxmlSetText(node, 1, "new");Formatted Text
-The mxmlNewTextf() and -mxmlSetTextf() functions -create and change text nodes, respectively, using -printf-style format strings and arguments. For example, -use the following function call to create a new text node:
+The mxmlNewTextf() and mxmlSetTextf() functions create +and change text nodes, respectively, using printf-style +format strings and arguments. For example, use the following +function call to create a new text node containing a constructed +filename:
- mxml_node_t *node; + mxml_node_t *node; - node = mxmlNewTextf(node, 1, "%s/%s", + node = mxmlNewTextf(node, 1, "%s/%s", path, filename);@@ -376,7 +392,7 @@ use the following function call to create a new text node:Mini-XML provides functions for managing indices of nodes. The current implementation provides the same functionality as -the mxmlFindElement(). +mxmlFindElement(). The advantage of using an index is that searching and enumeration of elements is significantly faster. The only disadvantage is that each index is a static snapshot of the XML @@ -392,10 +408,11 @@ href='#mxml_index_t'>mxml_index_t structures. The creates a new index:
- mxml_node_t *tree; - mxml_index_t *ind; + mxml_node_t *tree; + mxml_index_t *ind; - ind = mxmlIndexNew(tree, "element", "attribute"); + ind = mxmlIndexNew(tree, "element", + "attribute");The first argument is the XML node tree to index. Normally this @@ -424,8 +441,7 @@ function enumerates each of the nodes in the index and can be used in a loop as follows:
- mxml_node_t *node; - mxml_index_t *ind; + mxml_node_t *node; mxmlIndexReset(ind); @@ -441,12 +457,13 @@ attribute value in the index. It can be used to find all matching elements in an index, as follows:- mxml_node_t *node; - mxml_index_t *ind; + mxml_node_t *node; mxmlIndexReset(ind); - while ((node = mxmlIndexFind(ind, "element", "attr-value")) != NULL) + while ((node = mxmlIndexFind(ind, "element", + "attr-value")) + != NULL) { // do something with node } @@ -458,14 +475,11 @@ to return all elements or attributes in the index. Passing NULL for both the element name and attribute value is equivalent to calling mxmlIndexEnum. --When you are done using the index, delete it using the mxmlIndexDelete() function:
- mxml_index_t *ind; - mxmlIndexDelete(ind);diff --git a/doc/basics.html b/doc/basics.html index 1c53d46..7d4afc4 100644 --- a/doc/basics.html +++ b/doc/basics.html @@ -44,18 +44,20 @@ which determines which value you want to look at in the value union.New nodes can be created using the mxmlNewElement(), -mxmlNewInteger(), -mxmlNewOpaque(), -mxmlNewReal(), -and mxmlNewText() -functions. Only elements can have child nodes, and the top node -must be an element, usually "?xml".
+href='#mxmlNewElement'>mxmlNewElement(), mxmlNewInteger(), mxmlNewOpaque(), mxmlNewReal(), mxmlNewText() mxmlNewTextf() mxmlNewXML() functions. Only +elements can have child nodes, and the top node must be an +element, usually <?xml version="1.0"?>. -Each node has pointers for the node above (parent), +
Each node has a user_data member which allows you to +associate application-specific data with each node as needed.
+ +Node also have pointers for the node above (parent), below (child), to the left (prev), and to the right (next) of the current node. If you have an XML file like the following:
@@ -73,21 +75,20 @@ file like the following: </group> <node>val7</node> <node>val8</node> - <node>val9</node> </data>the node tree returned by mxmlLoadFile() would look -like the following in memory:
+the node tree for the file would look like the following in +memory:
?xml | data | - node - node - node - group - node - node - node - | | | | | | | - val1 val2 val3 | val7 val8 val9 + node - node - node - group - node - node + | | | | | | + val1 val2 val3 | val7 val8 | node - node - node | | | @@ -106,7 +107,80 @@ particular node or the entire tree: mxmlDelete(tree);- + +Creating XML Documents
+ +You can create and update XML documents in memory using the +various mxmlNew functions. The following code will +create the XML document described in the previous section:
+ ++ mxml_node_t *xml; /* <?xml ... ?> */ + mxml_node_t *data; /* <data> */ + mxml_node_t *node; /* <node> */ + mxml_node_t *group; /* <group> */ + + xml = mxmlNewXML("1.0"); + + data = mxmlNewElement(xml, "data"); + + node = mxmlNewElement(data, "node"); + mxmlNewText(node, 0, "val1"); + node = mxmlNewElement(data, "node"); + mxmlNewText(node, 0, "val2"); + node = mxmlNewElement(data, "node"); + mxmlNewText(node, 0, "val3"); + + group = mxmlNewElement(data, "group"); + + node = mxmlNewElement(group, "node"); + mxmlNewText(node, 0, "val4"); + node = mxmlNewElement(group, "node"); + mxmlNewText(node, 0, "val5"); + node = mxmlNewElement(group, "node"); + mxmlNewText(node, 0, "val6"); + + node = mxmlNewElement(data, "node"); + mxmlNewText(node, 0, "val7"); + node = mxmlNewElement(data, "node"); + mxmlNewText(node, 0, "val8"); ++ +We start by creating the <?xml version="1.0"?> +node common to all XML files using the mxmlNewXML function:
+ ++ xml = mxmlNewXML("1.0"); ++ +We then create the <data> node used for this +document using the mxmlNewElement function. The +first argument specifies the parent node (xml) while the +second specifies the element name (data):
+ ++ data = mxmlNewElement(xml, "data"); ++ +Each <node>...</node> in the file is +created using the mxmlNewElement and mxmlNewText functions. The first +argument of mxmlNewText specifies the parent node +(node). The second argument specifies whether whitespace +appears before the text - 0 or false in this case. The last +argument specifies the actual text to add:
+ ++ node = mxmlNewElement(data, "node"); + mxmlNewText(node, 0, "val1"); ++ +The resulting in-memory XML document can then be saved or +processed just like one loaded from disk or a string.
+ +Loading XML
You load an XML file using the
-FILE *fp; - mxml_node_t *tree; + mxml_node_t *tree; fp = fopen("filename.xml", "r"); - tree = mxmlLoadFile(NULL, fp, MXML_NO_CALLBACK); + tree = mxmlLoadFile(NULL, fp, + MXML_TEXT_CALLBACK); fclose(fp);@@ -133,14 +208,18 @@ opened by fopen() or popen(). You can also use stdin if you are implementing an XML filter program.The third argument specifies a callback function which -returns the value type of the immediate children for a new -element node: MXML_INTEGER, MXML_OPAQUE, -MXML_REAL, or MXML_TEXT. Load callbacks are -described in detail in Chapter 3. -The example code uses the MXML_NO_CALLBACK constant -which specifies that all data nodes in the document contain -whitespace-separated text values.
+The third argument specifies a callback function which returns +the value type of the immediate children for a new element node: +MXML_CUSTOM, MXML_IGNORE, +MXML_INTEGER, MXML_OPAQUE, MXML_REAL, +or MXML_TEXT. Load callbacks are described in detail in +Chapter 3. The example code uses +the MXML_TEXT_CALLBACK constant which specifies that all +data nodes in the document contain whitespace-separated text +values. Other standard callbacks include +MXML_IGNORE_CALLBACK, MXML_INTEGER_CALLBACK, +MXML_OPAQUE_CALLBACK, and +MXML_REAL_CALLBACK.
The mxmlLoadString() function loads XML node trees from a string:
@@ -148,11 +227,11 @@ function loads XML node trees from a string:char buffer[8192]; - mxml_node_t *tree; + mxml_node_t *tree; ... - tree = mxmlLoadString(NULL, buffer, - MXML_NO_CALLBACK); + tree = mxmlLoadString(NULL, buffer, + MXML_TEXT_CALLBACK);The first and third arguments are the same as used for @@ -162,6 +241,7 @@ document including the ?xml element if the parent node is NULL.
+Saving XML
You save an XML file using the mxmlSaveFile() function:
FILE *fp; - mxml_node_t *tree; + mxml_node_t *tree; fp = fopen("filename.xml", "w"); - mxmlSaveFile(tree, fp, MXML_NO_CALLBACK); + mxmlSaveFile(tree, fp, MXML_NO_CALLBACK); fclose(fp);@@ -186,8 +266,8 @@ by fopen() or popen(). You can also use program.The third argument is the whitespace callback to use when -saving the file. Whitespace callbacks are covered in detail in -Chapter 3. The example code above +saving the file. Whitespace callbacks are covered in detail in Chapter 3. The previous example code uses the MXML_NO_CALLBACK constant to specify that no special whitespace handling is required.
@@ -199,14 +279,14 @@ functions save XML node trees to strings:char buffer[8192]; char *ptr; - mxml_node_t *tree; + mxml_node_t *tree; ... - mxmlSaveString(tree, buffer, sizeof(buffer), + mxmlSaveString(tree, buffer, sizeof(buffer), MXML_NO_CALLBACK); ... - ptr = mxmlSaveAllocString(tree, MXML_NO_CALLBACK); + ptr = mxmlSaveAllocString(tree, MXML_NO_CALLBACK);The first and last arguments are the same as used for @@ -216,8 +296,25 @@ a fixed-size buffer, while mxmlSaveAllocString() returns a string buffer that was allocated using malloc().
+Controlling Line Wrapping
-Finding and Iterating Nodes
+When saving XML documents, Mini-XML normally wraps output +lines at column 75 so that the text is readable in terminal +windows. The mxmlSetWrapMargin function +overrides the default wrap margin:
+ ++ /* Set the margin to 132 columns */ + mxmlSetWrapMargin(132); + + /* Disable wrapping */ + mxmlSetWrapMargin(0); ++ + + +Finding and Iterating Nodes
The mxmlWalkPrev() @@ -226,11 +323,13 @@ href='#mxmlWalkNext'>mxmlWalkNext()functions can be used to iterate through the XML node tree:
- mxml_node_t *node; + mxml_node_t *node; - node = mxmlWalkPrev(current, tree, MXML_DESCEND); + node = mxmlWalkPrev(current, tree, + MXML_DESCEND); - node = mxmlWalkNext(current, tree, MXML_DESCEND); + node = mxmlWalkNext(current, tree, + MXML_DESCEND);In addition, you can find a named element/node using the mxmlFindElement() function:
- mxml_node_t *node; + mxml_node_t *node; - node = mxmlFindElement(tree, tree, "name", + node = mxmlFindElement(tree, tree, "name", "attr", "value", MXML_DESCEND);@@ -249,34 +348,43 @@ function: arguments can be passed as NULL to act as wildcards, e.g.: +/* Find the first "a" element */ - node = mxmlFindElement(tree, tree, "a", + node = mxmlFindElement(tree, tree, "a", NULL, NULL, MXML_DESCEND); - ++ +/* Find the first "a" element with "href" attribute */ - node = mxmlFindElement(tree, tree, "a", + node = mxmlFindElement(tree, tree, "a", "href", NULL, MXML_DESCEND); - ++ +/* Find the first "a" element with "href" to a URL */ - node = mxmlFindElement(tree, tree, "a", + node = mxmlFindElement(tree, tree, "a", "href", "http://www.easysw.com/", MXML_DESCEND); - ++ +/* Find the first element with a "src" attribute */ - node = mxmlFindElement(tree, tree, NULL, + node = mxmlFindElement(tree, tree, NULL, "src", NULL, MXML_DESCEND); - ++ +/* Find the first element with a "src" = "foo.jpg" */ - node = mxmlFindElement(tree, tree, NULL, + node = mxmlFindElement(tree, tree, NULL, "src", "foo.jpg", MXML_DESCEND);@@ -284,13 +392,15 @@ e.g.:You can also iterate with the same function:
- mxml_node_t *node; + mxml_node_t *node; - for (node = mxmlFindElement(tree, tree, "name", + for (node = mxmlFindElement(tree, tree, + "name", NULL, NULL, MXML_DESCEND); node != NULL; - node = mxmlFindElement(node, tree, "name", + node = mxmlFindElement(node, tree, + "name", NULL, NULL, MXML_DESCEND)) { @@ -298,6 +408,7 @@ e.g.: }+The MXML_DESCEND argument can actually be one of three constants:
@@ -309,7 +420,7 @@ three constants: node or top-of-tree is reached. The previous node from "group" would be the "node" element to the left, while the next node from "group" would be the "node" element - to the right. + to the right.
- ?xml - data - node - val1 - node - val2 - node - val3 - group - node - val4 - node - val5 - node - val6 - node - val7 - node - val8 - node - val9 -+
?xml data node val1 node val2 node val3 group node + val4 node val5 node val6 node val7 node val8
-If you started at "val9" and walked using +
If you started at "val8" and walked using mxmlWalkPrev(), the order would be reversed, ending at "?xml".
New node
+Create a new XML document tree. + +The "version" argument specifies the version number to put in the +?xml element node. If NULL, version 1.0 is assumed. + +
++mxml_node_t * +mxmlNewXML( + const char * version); ++
Name | Description |
---|---|
version | Version number to use |
New ?xml node
+Release a node. diff --git a/doc/relnotes.html b/doc/relnotes.html index 8f34b0d..a89786c 100644 --- a/doc/relnotes.html +++ b/doc/relnotes.html @@ -8,12 +8,14 @@