From f830ffd6a5a24b6b6ee18b73277f09abd881c860 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Wed, 4 Jun 2003 17:37:23 +0000 Subject: [PATCH] Add mxmlAdd() and mxmlRemove() functions. Documentation updates. General cleanup. --- CHANGES | 14 ++++- README | 26 +++++++-- index.html | 27 +++++++-- mxml-file.c | 61 +++++++++++++++---- mxml-node.c | 165 ++++++++++++++++++++++++++++++++++++++++++---------- mxml.h | 21 +++++-- mxmldoc.c | 75 +++++++----------------- testmxml.c | 56 +++++++++++++++--- 8 files changed, 327 insertions(+), 118 deletions(-) diff --git a/CHANGES b/CHANGES index d603494..ea4b620 100644 --- a/CHANGES +++ b/CHANGES @@ -4,9 +4,13 @@ README - 06/04/2003 CHANGES IN Mini-XML 0.93 + - Added mxmlAdd() and mxmlRemove() functions to add and + remove nodes from a tree. This provides more + flexibility over where the nodes are inserted and + allows nodes to be moved within the tree as needed. - mxmlLoadFile() now correctly handles comments. - - mxmlLoadFile() now supports the "gt" and "nbsp" - character entities. + - mxmlLoadFile() now supports the required "gt", "quot", + and "nbsp" character entities. - mxmlSaveFile() now uses newlines as whitespace when valid to do so. - mxmlFindElement() now also takes attribute name and @@ -19,7 +23,11 @@ CHANGES IN Mini-XML 0.93 now all provide "descend" arguments to control whether they descend into child nodes in the tree. - Fixed some whitespace issues in mxmlLoadFile(). - - Fixed Unicode output issues in mxmlSaveFile(). + - Fixed Unicode output and whitespace issues in + mxmlSaveFile(). + - mxmlSaveFile() now supports a whitespace callback to + provide more human-readable XML output under program + control. CHANGES IN Mini-XML 0.92 diff --git a/README b/README index 61a51b1..151f6b9 100644 --- a/README +++ b/README @@ -13,9 +13,27 @@ INTRODUCTION requires an ANSI C compatible compiler (GCC works, as do most vendors' ANSI C compilers) and a "make" program. - Mini-XML was created to support the basic hierarchy provided - by XML and some simple data types, but doesn't do validation - or other types of processing on the data. + Mini-XML provides the following functionality: + + - Reading and writing of UTF-8 encoded XML files. + - Data is stored in a linked-list tree structure, + preserving the XML data hierarchy. + - Supports arbitrary element names, attributes, and + attribute values with no preset limits, just available + memory. + - Supports integer, real, opaque ("cdata"), and text + data types in "leaf" nodes. + - Functions for creating and managing trees of data. + - "Find" and "walk" functions for easily locating and + navigating trees of data. + + Mini-XML doesn't do validation or other types of processing + on the data based upon schema files or other sources of + definition information, nor does it support character + entities other than those required by the XML + specification. Also, since Mini-XML does not support the + UTF-16 encoding, it is technically not a conforming XML + consumer/client. BUILDING Mini-XML @@ -86,7 +104,7 @@ DOCUMENTATION mxml_node_t *tree; fp = fopen("filename.xml", "w"); - mxmlSaveFile(tree, fp); + mxmlSaveFile(tree, fp, MXML_NO_CALLBACK); fclose(fp); You can find a named element/node using the diff --git a/index.html b/index.html index c147f4f..690a1f6 100644 --- a/index.html +++ b/index.html @@ -29,9 +29,28 @@ requiring large non-standard libraries. Mini-XML only requires an ANSI C compatible compiler (GCC works, as do most vendors' ANSI C compilers) and a "make" program.

-

Mini-XML was created to support the basic hierarchy provided -by XML and some simple data types, but doesn't do validation or -other types of processing on the data.

+

Mini-XML provides the following functionality:

+ + + +

Mini-XML doesn't do validation or other types of processing +on the data based upon schema files or other sources of +definition information, nor does it support character entities +other than those required by the XML specification. Also, since +Mini-XML does not support the UTF-16 encoding, it is technically +not a conforming XML consumer/client.

Building Mini-XML

@@ -113,7 +132,7 @@ function:

mxml_node_t *tree; fp = fopen("filename.xml", "w"); - mxmlSaveFile(tree, fp); + mxmlSaveFile(tree, fp, MXML_NO_CALLBACK); fclose(fp); diff --git a/mxml-file.c b/mxml-file.c index 19ab436..74907ae 100644 --- a/mxml-file.c +++ b/mxml-file.c @@ -1,5 +1,5 @@ /* - * "$Id: mxml-file.c,v 1.6 2003/06/04 16:30:40 mike Exp $" + * "$Id: mxml-file.c,v 1.7 2003/06/04 17:37:23 mike Exp $" * * File loading code for mini-XML, a small XML-like file parsing library. * @@ -36,7 +36,8 @@ */ static int mxml_parse_element(mxml_node_t *node, FILE *fp); -static int mxml_write_node(mxml_node_t *node, FILE *fp, int col); +static int mxml_write_node(mxml_node_t *node, FILE *fp, + int (*cb)(mxml_node_t *, int), int col); static int mxml_write_string(const char *s, FILE *fp); @@ -367,7 +368,7 @@ mxmlLoadFile(mxml_node_t *top, /* I - Top node */ { /* * Add character entity to current buffer... Currently we only - * support <, &, >,  , &#nnn;, and &#xXXXX;... + * support <, &, >,  , ", &#nnn;, and &#xXXXX;... */ char entity[64], /* Entity string */ @@ -413,6 +414,8 @@ mxmlLoadFile(mxml_node_t *top, /* I - Top node */ ch = '<'; else if (!strcmp(entity, " ")) ch = 0xa0; + else if (!strcmp(entity, """)) + ch = '\"'; else { fprintf(stderr, "Entity name \"%s;\" not supported under parent <%s>!\n", @@ -507,17 +510,23 @@ mxmlLoadFile(mxml_node_t *top, /* I - Top node */ int /* O - 0 on success, -1 on error */ mxmlSaveFile(mxml_node_t *node, /* I - Node to write */ - FILE *fp) /* I - File to write to */ + FILE *fp, /* I - File to write to */ + int (*cb)(mxml_node_t *, int)) + /* I - Whitespace callback */ { + int col; /* Final column */ + + /* * Write the node... */ - if (mxml_write_node(node, fp, 0) < 0) + if ((col = mxml_write_node(node, fp, cb, 0)) < 0) return (-1); - if (putc('\n', fp) < 0) - return (-1); + if (col > 0) + if (putc('\n', fp) < 0) + return (-1); /* * Return 0 (success)... @@ -688,9 +697,12 @@ mxml_parse_element(mxml_node_t *node, /* I - Element node */ static int /* O - Column or -1 on error */ mxml_write_node(mxml_node_t *node, /* I - Node to write */ FILE *fp, /* I - File to write to */ + int (*cb)(mxml_node_t *, int), + /* I - Whitespace callback */ int col) /* I - Current column */ { int i; /* Looping var */ + int ch; /* Whitespace character */ int n; /* Chars written */ mxml_attr_t *attr; /* Current attribute */ @@ -704,6 +716,18 @@ mxml_write_node(mxml_node_t *node, /* I - Node to write */ switch (node->type) { case MXML_ELEMENT : + if (cb && (ch = (*cb)(node, MXML_SAVE_OPEN_TAG)) != 0) + { + if (putc(ch, fp) < 0) + return (-1); + else if (ch == '\n') + col = 0; + else if (ch == '\t') + col += 8; + else + col ++; + } + if ((n = fprintf(fp, "<%s", node->value.element.name)) < 0) return (-1); @@ -752,7 +776,7 @@ mxml_write_node(mxml_node_t *node, /* I - Node to write */ else col ++; - if ((col = mxml_write_node(node->child, fp, col)) < 0) + if ((col = mxml_write_node(node->child, fp, cb, col)) < 0) return (-1); if (node->value.element.name[0] != '?' && @@ -775,6 +799,18 @@ mxml_write_node(mxml_node_t *node, /* I - Node to write */ return (-1); else col += 2; + + if (cb && (ch = (*cb)(node, MXML_SAVE_CLOSE_TAG)) != 0) + { + if (putc(ch, fp) < 0) + return (-1); + else if (ch == '\n') + col = 0; + else if (ch == '\t') + col += 8; + else + col ++; + } break; case MXML_INTEGER : @@ -829,7 +865,7 @@ mxml_write_node(mxml_node_t *node, /* I - Node to write */ break; case MXML_TEXT : - if (node->value.text.whitespace) + if (node->value.text.whitespace && col > 0) { if (col > MXML_WRAP) { @@ -887,6 +923,11 @@ mxml_write_string(const char *s, /* I - String to write */ if (fputs(">", fp) < 0) return (-1); } + else if (*s == '\"') + { + if (fputs(""", fp) < 0) + return (-1); + } else if (*s & 128) { /* @@ -933,5 +974,5 @@ mxml_write_string(const char *s, /* I - String to write */ /* - * End of "$Id: mxml-file.c,v 1.6 2003/06/04 16:30:40 mike Exp $". + * End of "$Id: mxml-file.c,v 1.7 2003/06/04 17:37:23 mike Exp $". */ diff --git a/mxml-node.c b/mxml-node.c index dca8308..3e9849c 100644 --- a/mxml-node.c +++ b/mxml-node.c @@ -1,5 +1,5 @@ /* - * "$Id: mxml-node.c,v 1.1 2003/06/03 19:46:30 mike Exp $" + * "$Id: mxml-node.c,v 1.2 2003/06/04 17:37:23 mike Exp $" * * Node support code for mini-XML, a small XML-like file parsing library. * @@ -17,12 +17,14 @@ * * Contents: * + * mxmlAdd() - Add a node to a tree. * mxmlDelete() - Delete a node and all of its children. * mxmlNewElement() - Create a new element node. * mxmlNewInteger() - Create a new integer node. * mxmlNewOpaque() - Create a new opaque string. * mxmlNewReal() - Create a new real number node. * mxmlNewText() - Create a new text fragment node. + * mxmlRemove() - Remove a node from its parent. * mxml_new() - Create a new node. */ @@ -40,6 +42,98 @@ static mxml_node_t *mxml_new(mxml_node_t *parent, mxml_type_t type); +/* + * 'mxmlAdd()' - Add a node to a tree. + */ + +void +mxmlAdd(mxml_node_t *parent, /* I - Parent node */ + int where, /* I - Where to add */ + mxml_node_t *child, /* I - Child node for where */ + mxml_node_t *node) /* I - Node to add */ +{ + if (!parent) + return; + + if (node->parent) + mxmlRemove(node); + + node->parent = parent; + + switch (where) + { + case MXML_ADD_BEFORE : + if (!child || child == parent->child || child->parent != parent) + { + /* + * Insert as first node under parent... + */ + + node->next = parent->child; + + if (parent->child) + parent->child->prev = node; + else + parent->last_child = node; + + parent->child = node; + } + else + { + /* + * Insert node before this child... + */ + + node->next = child; + node->prev = child->prev; + + if (child->prev) + child->prev->next = node; + else + parent->child = node; + + child->prev = node; + } + break; + + case MXML_ADD_AFTER : + if (!child || child == parent->last_child || child->parent != parent) + { + /* + * Insert as last node under parent... + */ + + node->parent = parent; + node->prev = parent->last_child; + + if (parent->last_child) + parent->last_child->next = node; + else + parent->child = node; + + parent->last_child = node; + } + else + { + /* + * Insert node after this child... + */ + + node->prev = child; + node->next = child->next; + + if (child->next) + child->next->prev = node; + else + parent->last_child = node; + + child->next = node; + } + break; + } +} + + /* * 'mxmlDelete()' - Delete a node and all of its children. */ @@ -58,7 +152,13 @@ mxmlDelete(mxml_node_t *node) /* I - Node */ return; /* - * Delete children first... + * Remove the node from its parent, if any... + */ + + mxmlRemove(node); + + /* + * Delete children... */ while (node->child) @@ -103,23 +203,6 @@ mxmlDelete(mxml_node_t *node) /* I - Node */ break; } - /* - * Remove from parent, if any... - */ - - if (node->parent) - { - if (node->prev) - node->prev->next = node->next; - else - node->parent->child = node->next; - - if (node->next) - node->next->prev = node->prev; - else - node->parent->last_child = node->prev; - } - /* * Free this node... */ @@ -277,6 +360,36 @@ mxmlNewText(mxml_node_t *parent, /* I - Parent node */ } +/* + * 'mxmlRemove()' - Remove a node from its parent. + */ + +void +mxmlRemove(mxml_node_t *node) /* I - Node to remove */ +{ + /* + * Range check input... + */ + + if (!node || !node->parent) + return; + + /* + * Remove from parent... + */ + + if (node->prev) + node->prev->next = node->next; + else + node->parent->child = node->next; + + if (node->next) + node->next->prev = node->prev; + else + node->parent->last_child = node->prev; +} + + /* * 'mxml_new()' - Create a new node. */ @@ -306,17 +419,7 @@ mxml_new(mxml_node_t *parent, /* I - Parent node */ */ if (parent) - { - node->parent = parent; - node->prev = parent->last_child; - - if (parent->last_child) - parent->last_child->next = node; - else - parent->child = node; - - parent->last_child = node; - } + mxmlAdd(parent, MXML_ADD_AFTER, MXML_ADD_TO_PARENT, node); /* * Return the new node... @@ -327,5 +430,5 @@ mxml_new(mxml_node_t *parent, /* I - Parent node */ /* - * End of "$Id: mxml-node.c,v 1.1 2003/06/03 19:46:30 mike Exp $". + * End of "$Id: mxml-node.c,v 1.2 2003/06/04 17:37:23 mike Exp $". */ diff --git a/mxml.h b/mxml.h index d18c38a..8eef0fe 100644 --- a/mxml.h +++ b/mxml.h @@ -1,5 +1,5 @@ /* - * "$Id: mxml.h,v 1.5 2003/06/04 16:30:40 mike Exp $" + * "$Id: mxml.h,v 1.6 2003/06/04 17:37:23 mike Exp $" * * Header file for mini-XML, a small XML-like file parsing library. * @@ -38,14 +38,21 @@ * Constants... */ -# define MXML_NO_CALLBACK (mxml_type_t (*)(mxml_node_t *))0 - /* Don't use a type callback */ +# define MXML_NO_CALLBACK 0 /* Don't use a type callback */ # define MXML_WRAP 72 /* Wrap XML output at this column position */ # define MXML_DESCEND 1 /* Descend when finding/walking */ # define MXML_NO_DESCEND 0 /* Don't descend when finding/walking */ # define MXML_DESCEND_FIRST -1 /* Descend for first find */ +# define MXML_SAVE_OPEN_TAG 0 /* Callback for open tag */ +# define MXML_SAVE_CLOSE_TAG 1 /* Callback for close tag */ + + +# define MXML_ADD_BEFORE 0 /* Add node before specified node */ +# define MXML_ADD_AFTER 1 /* Add node after specified node */ +# define MXML_ADD_TO_PARENT NULL /* Add node relative to parent */ + /* * Data types... @@ -110,6 +117,8 @@ extern "C" { * Prototypes... */ +extern void mxmlAdd(mxml_node_t *parent, int where, + mxml_node_t *child, mxml_node_t *node); extern void mxmlDelete(mxml_node_t *node); extern const char *mxmlElementGetAttr(mxml_node_t *node, const char *name); extern void mxmlElementSetAttr(mxml_node_t *node, const char *name, @@ -125,7 +134,9 @@ extern mxml_node_t *mxmlNewOpaque(mxml_node_t *parent, const char *opaque); extern mxml_node_t *mxmlNewReal(mxml_node_t *parent, double real); extern mxml_node_t *mxmlNewText(mxml_node_t *parent, int whitespace, const char *string); -extern int mxmlSaveFile(mxml_node_t *node, FILE *fp); +extern void mxmlRemove(mxml_node_t *node); +extern int mxmlSaveFile(mxml_node_t *node, FILE *fp, + int (*cb)(mxml_node_t *, int)); extern mxml_node_t *mxmlWalkNext(mxml_node_t *node, mxml_node_t *top, int descend); extern mxml_node_t *mxmlWalkPrev(mxml_node_t *node, mxml_node_t *top, @@ -143,5 +154,5 @@ extern mxml_node_t *mxmlWalkPrev(mxml_node_t *node, mxml_node_t *top, /* - * End of "$Id: mxml.h,v 1.5 2003/06/04 16:30:40 mike Exp $". + * End of "$Id: mxml.h,v 1.6 2003/06/04 17:37:23 mike Exp $". */ diff --git a/mxmldoc.c b/mxmldoc.c index bf7ece6..7651a5f 100644 --- a/mxmldoc.c +++ b/mxmldoc.c @@ -1,5 +1,5 @@ /* - * "$Id: mxmldoc.c,v 1.2 2003/06/04 16:30:40 mike Exp $" + * "$Id: mxmldoc.c,v 1.3 2003/06/04 17:37:23 mike Exp $" * * Documentation generator using mini-XML, a small XML-like file parsing * library. @@ -102,9 +102,9 @@ * Local functions... */ -static void insert_node(mxml_node_t *tree, mxml_node_t *func); static int scan_file(const char *filename, FILE *fp, mxml_node_t *doc); +static void sort_node(mxml_node_t *tree, mxml_node_t *func); /* @@ -193,7 +193,7 @@ main(int argc, /* I - Number of command-line args */ * Write over the existing XML file... */ - if (mxmlSaveFile(doc, fp)) + if (mxmlSaveFile(doc, fp, MXML_NO_CALLBACK)) { fprintf(stderr, "Unable to write the XML documentation file \"%s\": %s!\n", argv[1], strerror(errno)); @@ -223,12 +223,24 @@ main(int argc, /* I - Number of command-line args */ /* - * 'insert_node()' - Insert a node into a tree. + * 'scan_file()' - Scan a source file. + */ + +static int /* O - 0 on success, -1 on error */ +scan_file(const char *filename, /* I - Filename */ + FILE *fp, /* I - File to scan */ + mxml_node_t *tree) /* I - Function tree */ +{ +} + + +/* + * 'sort_node()' - Insert a node sorted into a tree. */ static void -insert_node(mxml_node_t *tree, /* I - Tree to insert into */ - mxml_node_t *node) /* I - Node to add */ +sort_node(mxml_node_t *tree, /* I - Tree to sort into */ + mxml_node_t *node) /* I - Node to add */ { mxml_node_t *temp; /* Current node */ const char *tempname, /* Name of current node */ @@ -242,7 +254,7 @@ insert_node(mxml_node_t *tree, /* I - Tree to insert into */ nodename = mxmlElementGetAttr(node, "name"); /* - * Delete an existing definition, if one exists... + * Delete any existing definition at this level, if one exists... */ if ((temp = mxmlFindElement(tree, tree, node->value.element.name, @@ -250,7 +262,7 @@ insert_node(mxml_node_t *tree, /* I - Tree to insert into */ mxmlDelete(temp); /* - * Insert the node into the tree... + * Add the node into the tree at the proper place... */ for (temp = tree->child; temp; temp = temp->next) @@ -262,53 +274,10 @@ insert_node(mxml_node_t *tree, /* I - Tree to insert into */ break; } - if (temp) - { - /* - * Insert node before this temp... - */ - - node->next = temp; - node->prev = temp->prev; - - if (temp->prev) - temp->prev->next = node; - else - tree->child = node; - - temp->prev = node; - } - else - { - /* - * Append node to the end... - */ - - node->prev = tree->last_child; - - if (tree->last_child) - tree->last_child->next = node; - else - tree->last_child = node; - - if (!tree->child) - tree->child = node; - } -} - - -/* - * 'scan_file()' - Scan a source file. - */ - -static int /* O - 0 on success, -1 on error */ -scan_file(const char *filename, /* I - Filename */ - FILE *fp, /* I - File to scan */ - mxml_node_t *tree) /* I - Function tree */ -{ + mxmlAdd(tree, MXML_ADD_AFTER, temp, node); } /* - * End of "$Id: mxmldoc.c,v 1.2 2003/06/04 16:30:40 mike Exp $". + * End of "$Id: mxmldoc.c,v 1.3 2003/06/04 17:37:23 mike Exp $". */ diff --git a/testmxml.c b/testmxml.c index 02a6554..ef15702 100644 --- a/testmxml.c +++ b/testmxml.c @@ -1,5 +1,5 @@ /* - * "$Id: testmxml.c,v 1.5 2003/06/04 16:30:40 mike Exp $" + * "$Id: testmxml.c,v 1.6 2003/06/04 17:37:23 mike Exp $" * * Test program for mini-XML, a small XML-like file parsing library. * @@ -17,8 +17,10 @@ * * Contents: * - * main() - Main entry for test program. - * type_cb() - XML data type callback for mxmlLoadFile()... + * main() - Main entry for test program. + * type_cb() - XML data type callback for mxmlLoadFile()... + * whitespace_cb() - Let the mxmlSaveFile() function know when to insert + * newlines and tabs... */ /* @@ -33,6 +35,7 @@ */ mxml_type_t type_cb(mxml_node_t *node); +int whitespace_cb(mxml_node_t *node, int where); /* @@ -110,8 +113,7 @@ main(int argc, /* I - Number of command-line args */ * Print the XML tree... */ - mxmlSaveFile(tree, stdout); - puts(""); + mxmlSaveFile(tree, stdout, whitespace_cb); /* * Delete the tree and return... @@ -142,8 +144,7 @@ type_cb(mxml_node_t *node) /* I - Element node */ if (!strcmp(type, "integer")) return (MXML_INTEGER); - else if (!strcmp(type, "opaque") || - !strcmp(type, "pre") || !strcmp(type, "PRE")) + else if (!strcmp(type, "opaque") || !strcmp(type, "pre")) return (MXML_OPAQUE); else if (!strcmp(type, "real")) return (MXML_REAL); @@ -153,5 +154,44 @@ type_cb(mxml_node_t *node) /* I - Element node */ /* - * End of "$Id: testmxml.c,v 1.5 2003/06/04 16:30:40 mike Exp $". + * 'whitespace_cb()' - Let the mxmlSaveFile() function know when to insert + * newlines and tabs... + */ + +int /* O - Whitespace char or 0 */ +whitespace_cb(mxml_node_t *node, /* I - Element node */ + int where) /* I - Open or close tag? */ +{ + const char *name; /* Name of element */ + + /* + * 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")) + return ('\n'); + else if (!strcmp(name, "li")) + { + /* + * Put a tab before
  • 's and a newline after
  • 's... + */ + + if (where == MXML_SAVE_OPEN_TAG) + return ('\t'); + else + return ('\n'); + } + else + return (0); +} + + +/* + * End of "$Id: testmxml.c,v 1.6 2003/06/04 17:37:23 mike Exp $". */