about summary refs log tree commit diff
diff options
context:
space:
mode:
authorsternenseemann <sternenseemann@systemli.org>2020-08-11 14:58:57 +0200
committersternenseemann <sternenseemann@systemli.org>2020-08-11 14:58:57 +0200
commit6c51d03ca0b2acb182ae7d168178ba192476c2c7 (patch)
tree95f1b1ebd7addea5d1db32c88b6e6018e7ce5f58
parent5e41e6e81915801da14d0ef582fb7376b77247b4 (diff)
feat(xml): add support for CDATA sections
* xml_open_cdata and xml_close_cdata add convenient support for this
* tracked on stack using the newly introduced field type which is of
  enum xml_tag_type which may either be XML_NORMAL_TAG or XML_CDATA.
* xml_close_all and xml_close_including also support closing CDATA
  sections now
* xml_close_including now behaves like xml_close_all if NULL is given
  as tag, xml_close_all newly aliased to xml_close_including(..., NULL)
-rw-r--r--main.c4
-rw-r--r--xml.c96
-rw-r--r--xml.h58
3 files changed, 131 insertions, 27 deletions
diff --git a/main.c b/main.c
index 6f29d10..4af412e 100644
--- a/main.c
+++ b/main.c
@@ -325,9 +325,9 @@ void blog_rss(char script_name[]) {
 
         if(entries[i].text_size > 0) {
             xml_open_tag(&ctx, "description");
-            xml_raw(&ctx, "<![CDATA[");
+            xml_open_cdata(&ctx);
             xml_raw(&ctx, entries[i].text);
-            xml_raw(&ctx, "]]>");
+            xml_close_cdata(&ctx);
             xml_close_tag(&ctx, "description");
         }
 
diff --git a/xml.c b/xml.c
index ca319c9..06ad654 100644
--- a/xml.c
+++ b/xml.c
@@ -176,6 +176,7 @@ void xml_open_tag_attrs(struct xml_context *ctx, const char *tag, size_t attr_co
     ctx->stack->next = old_stack;
 
     size_t tag_size = strlen(tag) + 1;
+    ctx->stack->type = XML_NORMAL_TAG;
     ctx->stack->tag = malloc(sizeof(char) * tag_size);
     memcpy(ctx->stack->tag, tag, tag_size);
 }
@@ -190,13 +191,18 @@ void xml_close_tag(struct xml_context *ctx, const char *tag) {
         return;
     }
 
-    if(ctx->stack == NULL || strcmp(tag, ctx->stack->tag) != 0) {
-        DEBUG_WARN(ctx, "Refusing to close tag %s, ", tag);
-        if(ctx->stack == NULL) {
-            DEBUG_WARN(ctx, "no tags left to be closed\n");
-        } else {
-            DEBUG_WARN(ctx, "unclosed tags remaining\n");
-        }
+    if(ctx->stack == NULL) {
+        DEBUG_WARN(ctx, "Refusing to close tag %s, no tags left to be closed\n", tag);
+        return;
+    }
+
+    if(ctx->stack->type != XML_NORMAL_TAG) {
+        DEBUG_WARN(ctx, "Refusing to close tag %s, wrong tag type\n", tag);
+        return;
+    }
+
+    if(strcmp(tag, ctx->stack->tag) != 0) {
+        DEBUG_WARN(ctx, "Refusing to close tag %s, unclosed tags remaining\n", tag);
         return;
     }
 
@@ -213,35 +219,91 @@ void xml_close_tag(struct xml_context *ctx, const char *tag) {
 }
 
 void xml_close_all(struct xml_context *ctx) {
+    xml_close_including(ctx, NULL);
+}
+
+void xml_close_including(struct xml_context *ctx, const char *tag) {
     if(ctx == NULL) {
         DEBUG_WARN(ctx, "Got no ctx\n");
         return;
     }
 
     if(ctx->stack == NULL) {
+        if(tag != NULL) {
+            DEBUG_WARN(ctx, "Hit end of tag stack while searching for tag %s to close\n", tag);
+        }
         return;
     } else {
-        xml_close_tag(ctx, ctx->stack->tag);
-        xml_close_all(ctx);
+        int last_tag = tag != NULL && strcmp(tag, ctx->stack->tag) == 0;
+
+        switch(ctx->stack->type) {
+            case XML_NORMAL_TAG:
+                xml_close_tag(ctx, ctx->stack->tag);
+                break;
+            case XML_CDATA:
+                xml_close_cdata(ctx);
+                break;
+            default:
+                DEBUG_WARN(ctx, "Unexpected tag type on stack, aborting\n");
+                return;
+        }
+
+        if(!last_tag) {
+            xml_close_including(ctx, tag);
+        }
     }
 }
 
-void xml_close_including(struct xml_context *ctx, const char *tag) {
+void xml_open_cdata(struct xml_context *ctx) {
     if(ctx == NULL) {
         DEBUG_WARN(ctx, "Got no ctx\n");
         return;
     }
 
+    struct xml_stack *old_stack = ctx->stack;
+
+    ctx->stack = malloc(sizeof(struct xml_stack));
+
     if(ctx->stack == NULL) {
-        DEBUG_WARN(ctx, "Hit end of tag stack while searching for tag %s to close\n", tag);
+        ctx->stack = old_stack;
+
+        DEBUG_WARN(ctx, "Could not allocate memory for tag stack, now everything will break.\n");
         return;
-    } else {
-        int last_tag = strcmp(tag, ctx->stack->tag) == 0;
+    }
 
-        xml_close_tag(ctx, ctx->stack->tag);
+    ctx->stack->next = old_stack;
+    ctx->stack->tag = NULL;
+    ctx->stack->type = XML_CDATA;
 
-        if(!last_tag) {
-            xml_close_including(ctx, tag);
-        }
+    fputs("<![CDATA[", ctx->out);
+}
+
+void xml_close_cdata(struct xml_context *ctx) {
+    if(ctx == NULL) {
+        DEBUG_WARN(ctx, "Got no ctx\n");
+        return;
+    }
+
+    if(ctx->stack == NULL) {
+        DEBUG_WARN(ctx, "No CDATA to close\n");
+        return;
     }
+
+    if(ctx->stack->type != XML_CDATA) {
+        DEBUG_WARN(ctx, "No CDATA on top of stack, refusing to close\n");
+        return;
+    }
+
+    struct xml_stack *old_head = ctx->stack;
+
+    ctx->stack = old_head->next;
+
+    if(old_head->tag != NULL) {
+        // shouldn't happen though
+        free(old_head->tag);
+    }
+
+    free(old_head);
+
+    fputs("]]>", ctx->out);
 }
diff --git a/xml.h b/xml.h
index be43faa..869a579 100644
--- a/xml.h
+++ b/xml.h
@@ -14,15 +14,13 @@
  * programmer's XML nesting. For information on its sanity
  * checking abilities see the documentation of `xml_close_tag()`.
  *
- * Currently it has some limitations:
+ * Currently it has some limitations (possibly incomplete list):
  *
  * * It does not give the calling code feedback if errors occurred
  * * It doesn't do validity checking of tags and attributes
  *   (legal characters etc.)
  * * It can't generate pretty output (i. e. properly indented),
  *   its output is currently always "minified".
- * * It currently has no satisfying support for `CDATA`,
- *   XML declarations and probably a lot more
  *
  * For handling arbitrary data this library is probably not a good
  * fit, it is mainly intended and tested for generating HTML and
@@ -37,6 +35,17 @@
 #include <stdio.h>
 
 /*!
+ * @brief Type of an XML "tag"
+ *
+ * This is mostly internally used to be able
+ * to keep track of CDATA using `xml_stack`.
+ */
+enum xml_tag_type {
+    XML_NORMAL_TAG,
+    XML_CDATA
+};
+
+/*!
  * @brief Internal linked list type
  *
  * Linked list used internally to keep track of tags to close.
@@ -44,7 +53,8 @@
  * @see struct xml_context
  */
 struct xml_stack {
-    char *tag;               //!< tag name
+    enum xml_tag_type type;  //! type of the tag
+    char *tag;               //!< tag name if `XML_NORMAL_TAG`, otherwise `NULL`
     struct xml_stack *next;  //!< tag to be closed after the current one
 };
 
@@ -241,9 +251,11 @@ void xml_close_tag(struct xml_context *ctx, const char *tag);
  * @brief Close all remaining unclosed tags
  *
  * `xml_close_all()` iterates through the `xml_stack` and calls
- * `xml_close_tag()` for every tag in it. A call to it will thus
- * result in an empty `xml_stack` and all previously opened tags
- * being closed correctly.
+ * `xml_close_tag()` or `xml_close_cdata()` respectively for every
+ * entry in it. A call to it will thus result in an empty `xml_stack`
+ * and all previously opened tags being closed correctly.
+ *
+ * Internally it's an alias for `xml_close_all(ctx, NULL)`
  *
  * Note that `xml_close_all()` will limit error checking, since it
  * (by nature) always succeeds and has no insight into what the
@@ -261,7 +273,8 @@ void xml_close_all(struct xml_context *ctx);
  * `xml_close_including()` works like `xml_close_all()`, but
  * will stop after it hits a tag of the given name.
  * If the given tag is not present in the stack, it behaves
- * like `xml_close_all()`.
+ * like `xml_close_all()`. It is not possible to match
+ * a `CDATA` section using `xml_close_including()`.
  *
  * Be aware that it might lead to unexpected results if
  * multiple tags of the same are nested. Consider the
@@ -296,3 +309,32 @@ void xml_close_all(struct xml_context *ctx);
  * @see struct xml_stack
  */
 void xml_close_including(struct xml_context *ctx, const char *tag);
+
+/*!
+ * @brief Start CDATA section
+ *
+ * Behaves like xml_open_tag(), but for opening `CDATA` sections.
+ * Internally the `XML_CDATA` type of `struct xml_stack` is used.
+ *
+ * Note that this function won't prevent `CDATA` sections or XML
+ * elements inside a `CDATA` section, since this is sometimes
+ * useful.
+ *
+ * @see xml_close_cdata
+ * @see enum xml_tag_type
+ * @see struct xml_stack
+ */
+void xml_open_cdata(struct xml_context *ctx);
+
+/*!
+ * @brief Close CDATA section
+ *
+ * Behaves like xml_close_tag(), but for `CDATA` sections.
+ *
+ * Checks the top of the stack if it is a `CDATA` section.
+ * In that case closes it and updates the stack, otherwise
+ * does nothing and if applicable outputs a warning.
+ *
+ * @see xml_open_cdata
+ */
+void xml_close_cdata(struct xml_context *ctx);