add xml writer webdav

Sat, 18 Jan 2020 13:48:59 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 18 Jan 2020 13:48:59 +0100
branch
webdav
changeset 232
499711b2a970
parent 231
4714468b9b7e
child 233
c5985d2fc19a

add xml writer

src/server/test/main.c file | annotate | diff | comparison | revisions
src/server/test/objs.mk file | annotate | diff | comparison | revisions
src/server/test/testutils.c file | annotate | diff | comparison | revisions
src/server/test/testutils.h file | annotate | diff | comparison | revisions
src/server/test/writer.c file | annotate | diff | comparison | revisions
src/server/test/writer.h file | annotate | diff | comparison | revisions
src/server/test/xml.c file | annotate | diff | comparison | revisions
src/server/test/xml.h file | annotate | diff | comparison | revisions
src/server/util/writer.c file | annotate | diff | comparison | revisions
src/server/webdav/xml.c file | annotate | diff | comparison | revisions
src/server/webdav/xml.h file | annotate | diff | comparison | revisions
--- a/src/server/test/main.c	Fri Jan 17 22:23:30 2020 +0100
+++ b/src/server/test/main.c	Sat Jan 18 13:48:59 2020 +0100
@@ -42,6 +42,7 @@
 #include <ucx/test.h>
 
 #include "vfs.h"
+#include "writer.h"
 #include "xml.h"
 #include "webdav.h"
 
@@ -69,9 +70,15 @@
     ucx_test_register(suite, test_vfs_opendir);
     ucx_test_register(suite, test_vfs_readdir);
     
+    // writer tests
+    ucx_test_register(suite, test_writer_putc);
+    ucx_test_register(suite, test_writer_flush);
+    ucx_test_register(suite, test_writer_put);
+    
     // xml tests
     ucx_test_register(suite, test_wsxml_iterator);
     ucx_test_register(suite, test_wsxml_get_required_namespaces);
+    ucx_test_register(suite, test_wsxml_write_nodes);
     
     // webdav tests
     ucx_test_register(suite, test_webdav_plist_add);
--- a/src/server/test/objs.mk	Fri Jan 17 22:23:30 2020 +0100
+++ b/src/server/test/objs.mk	Sat Jan 18 13:48:59 2020 +0100
@@ -35,6 +35,7 @@
 TESTOBJ += webdav.o
 TESTOBJ += vfs.o
 TESTOBJ += xml.o
+TESTOBJ += writer.o
 
 TESTOBJS = $(TESTOBJ:%=$(TEST_OBJPRE)%)
 TESTSOURCE = $(TESTOBJ:%.o=test/%.c)
--- a/src/server/test/testutils.c	Fri Jan 17 22:23:30 2020 +0100
+++ b/src/server/test/testutils.c	Sat Jan 18 13:48:59 2020 +0100
@@ -34,6 +34,8 @@
 
 #include "../util/pblock.h"
 
+#include "../util/io.h"
+
 #include "testutils.h"
 
 Session* testutil_session(void) {
@@ -92,3 +94,54 @@
 void testutil_destroy_session(Session *sn) {
     pool_destroy(sn->pool);
 }
+
+
+static ssize_t test_io_write(IOStream *io, void *buf, size_t size) {
+    TestIOStream *st = (TestIOStream*)io;
+    return ucx_buffer_write(buf, 1, size, st->buf);
+}
+
+static ssize_t test_io_writev(IOStream *io, struct iovec *iovec, int iovctn) {
+    return -1;
+}
+
+static ssize_t test_io_read(IOStream *io, void *buf, size_t size) {
+    return -1;
+}
+
+static void test_io_close(IOStream *io) {
+    
+}
+
+static void test_io_finish(IOStream *io) {
+    
+}
+
+static void test_io_setmode(IOStream *io, int mode) {
+    
+}
+
+static int test_io_poll(IOStream *io, EventHandler *ev, int events , Event *event) {
+    return 1;
+}
+
+TestIOStream* testutil_iostream(size_t size, int autoextend) {
+    TestIOStream *stream = calloc(1, sizeof(TestIOStream));
+    int flags = 0;
+    if(autoextend) {
+        flags = UCX_BUFFER_AUTOEXTEND;
+    }
+    stream->buf = ucx_buffer_new(NULL, size, flags);
+    
+    stream->io.write = test_io_write;
+    stream->io.writev = test_io_writev;
+    stream->io.close = test_io_close;
+    stream->io.finish = test_io_finish;
+    
+    return stream;
+}
+
+void testutil_iostream_destroy(TestIOStream *stream) {
+    ucx_buffer_free(stream->buf);
+    free(stream);
+}
--- a/src/server/test/testutils.h	Fri Jan 17 22:23:30 2020 +0100
+++ b/src/server/test/testutils.h	Sat Jan 18 13:48:59 2020 +0100
@@ -32,10 +32,18 @@
 #include "../public/nsapi.h"
 #include "../daemon/httprequest.h"
 
+#include "../util/io.h"
+#include <ucx/buffer.h>
+
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+typedef struct TestIOStream {
+    IOStream io;
+    UcxBuffer *buf;
+} TestIOStream;
+    
 Session* testutil_session(void);
 
 Request* testutil_request(pool_handle_t *pool, const char *method, const char *uri);
@@ -44,6 +52,9 @@
 
 void testutil_destroy_session(Session *sn);
 
+TestIOStream* testutil_iostream(size_t size, int autoextend);
+void testutil_iostream_destroy(TestIOStream *stream);
+
 
 #ifdef __cplusplus
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/writer.c	Sat Jan 18 13:48:59 2020 +0100
@@ -0,0 +1,151 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2020 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../util/writer.h"
+
+#include <ucx/buffer.h>
+
+#include "writer.h"
+#include "testutils.h"
+
+UCX_TEST(test_writer_putc) {
+    Session *sn = testutil_session();
+    TestIOStream *st = testutil_iostream(2048, TRUE);
+    UcxBuffer *buf = st->buf;
+    
+    UCX_TEST_BEGIN;
+    
+    Writer writer;
+    char wbuf[1024];
+    writer_init(&writer, st, wbuf, 4);
+    Writer *out = &writer;
+    
+    writer_putc(out, 'a');
+    UCX_TEST_ASSERT(wbuf[0] == 'a', "1: wrong char at pos 0");
+    UCX_TEST_ASSERT(writer.pos == 1, "1: wrong pos");
+    
+    writer_putc(out, 'b');
+    UCX_TEST_ASSERT(wbuf[1] == 'b', "2: wrong char at pos 1");
+    UCX_TEST_ASSERT(writer.pos == 2, "2: wrong pos");
+    
+    writer_putc(out, 'c');
+    writer_putc(out, 'd');
+    UCX_TEST_ASSERT(wbuf[2] == 'c', "3: wrong char at pos 2");
+    UCX_TEST_ASSERT(wbuf[3] == 'd', "4: wrong char at pos 3");
+    
+    writer_putc(out, 'f'); // should flush the buffer
+    UCX_TEST_ASSERT(wbuf[0] == 'f', "5: wrong char at pos 0");
+    UCX_TEST_ASSERT(writer.pos == 1, "5: wrong pos");
+    UCX_TEST_ASSERT(buf->space[0] == 'a', "5: wrong char at UcxBuffer pos 0");
+    UCX_TEST_ASSERT(buf->space[1] == 'b', "5: wrong char at UcxBuffer pos 1");
+    UCX_TEST_ASSERT(buf->pos == 4, "5: wrong UcxBuffer pos");    
+    
+    UCX_TEST_END;
+    testutil_iostream_destroy(st);
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_writer_flush) {
+    Session *sn = testutil_session();
+    TestIOStream *st = testutil_iostream(2048, TRUE);
+    UcxBuffer *buf = st->buf;
+    
+    UCX_TEST_BEGIN;
+    
+    Writer writer;
+    char wbuf[1024];
+    writer_init(&writer, st, wbuf, 4);
+    Writer *out = &writer;
+    
+    writer_putc(out, 'a');
+    UCX_TEST_ASSERT(wbuf[0] == 'a', "1: wrong char at pos 0");
+    UCX_TEST_ASSERT(writer.pos == 1, "1: wrong pos");
+    
+    writer_flush(out);
+    UCX_TEST_ASSERT(writer.pos == 0, "wrong pos after flush");
+    UCX_TEST_ASSERT(buf->space[0] == 'a', "wrong UcxBuffer content");
+    UCX_TEST_ASSERT(buf->pos == 1, "wrong UcxBuffer pos");
+    
+    writer_putc(out, 'b');
+    UCX_TEST_ASSERT(wbuf[0] == 'b', "2: wrong char at pos 0");
+    UCX_TEST_ASSERT(writer.pos == 1, "2: wrong pos");
+    
+    UCX_TEST_END;
+    testutil_iostream_destroy(st);
+    testutil_destroy_session(sn);
+}
+
+UCX_TEST(test_writer_put) {
+    Session *sn = testutil_session();
+    TestIOStream *st = testutil_iostream(2048, TRUE);
+    UcxBuffer *buf = st->buf;
+    
+    UCX_TEST_BEGIN;
+    
+    Writer writer;
+    char wbuf[1024];
+    writer_init(&writer, st, wbuf, 8);
+    Writer *out = &writer;
+    
+    writer_put(out, "abcd", 4);
+    UCX_TEST_ASSERT(!memcmp(wbuf, "abcd", 4), "1: wrong content");
+    UCX_TEST_ASSERT(writer.pos == 4, "1: wrong pos");
+    
+    writer_put(out, "efgh", 4);
+    UCX_TEST_ASSERT(!memcmp(wbuf, "abcdefgh", 8), "2: wrong content");
+    UCX_TEST_ASSERT(writer.pos == 8, "2: wrong pos");
+    
+    writer_put(out, "1234", 4);
+    UCX_TEST_ASSERT(!memcmp(wbuf, "1234", 4), "3: wrong content");
+    UCX_TEST_ASSERT(writer.pos == 4, "3: wrong pos");
+    UCX_TEST_ASSERT(!memcmp(buf->space, "abcdefgh", 8), "3: wrong UcxBuffer content");
+    UCX_TEST_ASSERT(buf->pos == 8, "3: wrong UcxBuffer pos");
+    
+    writer_put(out, "5678xx", 6);
+    UCX_TEST_ASSERT(!memcmp(wbuf, "xx", 2), "4: wrong content");
+    UCX_TEST_ASSERT(writer.pos == 2, "4: wrong pos");
+    UCX_TEST_ASSERT(!memcmp(buf->space, "abcdefgh12345678", 16), "4: wrong UcxBuffer content");
+    UCX_TEST_ASSERT(buf->pos == 16, "4: wrong UcxBuffer pos");
+    
+    writer_puts(out, S("345678abcdefgh12345678end."));
+    UCX_TEST_ASSERT(!memcmp(wbuf, "end.", 4), "5: wrong content");
+    UCX_TEST_ASSERT(writer.pos == 4, "5: wrong pos");
+    UCX_TEST_ASSERT(!memcmp(
+            buf->space,
+            "abcdefgh12345678xx345678abcdefgh12345678",
+            40),
+            "5: wrong UcxBuffer content");
+    UCX_TEST_ASSERT(buf->pos == 40, "5: wrong UcxBuffer pos");
+    
+    UCX_TEST_END;
+    testutil_iostream_destroy(st);
+    testutil_destroy_session(sn);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/test/writer.h	Sat Jan 18 13:48:59 2020 +0100
@@ -0,0 +1,47 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2020 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TEST_WRITER_H
+#define TEST_WRITER_H
+
+#include <ucx/test.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+UCX_TEST(test_writer_putc);
+UCX_TEST(test_writer_flush);
+UCX_TEST(test_writer_put);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TEST_WRITER_H */
+
--- a/src/server/test/xml.c	Fri Jan 17 22:23:30 2020 +0100
+++ b/src/server/test/xml.c	Sat Jan 18 13:48:59 2020 +0100
@@ -36,6 +36,10 @@
 #include "testutils.h"
 
 #include "../public/webdav.h"
+#include "../util/writer.h"
+
+#include "../webdav/webdav.h"
+#include "../webdav/xml.h"
 
 typedef struct Test1Data {
     int beginCounter;
@@ -45,6 +49,7 @@
     int textErr;
     int err;
     int endErr;
+    int nodesWithAttributesCounter;
     xmlNode *prev;
 } Test1Data;
 
@@ -126,6 +131,27 @@
     return 0;
 }
 
+static int test2_begin(xmlNode *node, void *userdata) {
+    Test1Data *data = userdata;
+    data->beginCounter++;
+    if(node->type == XML_ELEMENT_NODE) {
+        data->elmCounter++;
+        if(node->properties) {
+            data->nodesWithAttributesCounter++;
+        }
+    }
+    return 0;
+}
+
+static int test2_end(xmlNode *node, void *userdata) {
+    Test1Data *data = userdata;
+    data->endCounter++;
+    if(node->type == XML_ELEMENT_NODE) {
+        data->endElmCounter++;
+    }
+    return 0;
+}
+
 UCX_TEST(test_wsxml_iterator) {
     Session *sn = testutil_session();
     
@@ -135,8 +161,11 @@
             XML_TESTDATA1, strlen(XML_TESTDATA1), NULL, NULL, 0);
     xmlDoc *doc2 = xmlReadMemory(
             XML_TESTDATA2, strlen(XML_TESTDATA2), NULL, NULL, 0);
+    xmlDoc *doc6 = xmlReadMemory(
+            XML_TESTDATA6, strlen(XML_TESTDATA6), NULL, NULL, 0);
     UCX_TEST_ASSERT(doc, "doc is NULL");
     UCX_TEST_ASSERT(doc2, "doc2 is NULL");
+    UCX_TEST_ASSERT(doc6, "doc6 is NULL");
     
     xmlNode *root = xmlDocGetRootElement(doc);
     
@@ -161,8 +190,18 @@
     UCX_TEST_ASSERT(!testdata.textErr, "test2: text order error");
     UCX_TEST_ASSERT(testdata.beginCounter == testdata.endCounter, "test2: begin/end counter not equal");
     
+    // Test 3: iterate over document with all kinds of node types
+    xmlNode *root6 = xmlDocGetRootElement(doc6);
+    ZERO(&testdata, sizeof(Test1Data));
+    ret = wsxml_iterator(sn->pool, root6, test2_begin, test2_end, &testdata);
+    UCX_TEST_ASSERT(ret == 0, "test3: wsxml_iterator failed");
+    UCX_TEST_ASSERT(testdata.elmCounter == testdata.endElmCounter, "test3: begin/end counter not equal");
+    UCX_TEST_ASSERT(testdata.elmCounter == 12, "test3: wrong elm counter");
+    UCX_TEST_ASSERT(testdata.nodesWithAttributesCounter == 5, "test3: wrong entity ref counter");
+    
     xmlFreeDoc(doc);
     xmlFreeDoc(doc2);
+    xmlFreeDoc(doc6);
     UCX_TEST_END;
 }
 
@@ -257,5 +296,42 @@
     UCX_TEST_ASSERT(!x3, "ns3: x3");
     UCX_TEST_ASSERT(!x4, "ns3: x4");
     
+    xmlFreeDoc(doc3);
+    xmlFreeDoc(doc4);
+    xmlFreeDoc(doc5);
     UCX_TEST_END;
 }
+
+UCX_TEST(test_wsxml_write_nodes) {
+    Session *sn = testutil_session();
+    TestIOStream *st = testutil_iostream(2048, TRUE);
+    
+    UCX_TEST_BEGIN;
+    xmlDoc *doc = xmlReadMemory(
+            XML_TESTDATA6, strlen(XML_TESTDATA6), NULL, NULL, 0);
+    UCX_TEST_ASSERT(doc, "xml parser error");
+    xmlNode *root = xmlDocGetRootElement(doc);
+    
+    Writer writer;
+    char buffer[1024];
+    writer_init(&writer, st, buffer, 1024);
+    
+    int err = wsxml_write_nodes(sn->pool, &writer, NULL, root);
+    writer_flush(&writer);
+    UCX_TEST_ASSERT(err == 0, "wsxml_write_nodes error");
+    UCX_TEST_ASSERT(st->buf->pos > 0, "buffer is empty");
+    
+    //printf("\n\n");
+    //printf("%.*s\n", (int)st->buf->size, st->buf->space);
+    //printf("\n\n");
+    
+    xmlDoc *genDoc = xmlReadMemory(
+            st->buf->space, st->buf->size, NULL, NULL, 0);
+    UCX_TEST_ASSERT(genDoc, "generated doc is not valid xml");
+    
+    xmlFreeDoc(doc);
+    xmlFreeDoc(genDoc);
+    
+    UCX_TEST_END;
+    testutil_iostream_destroy(st);
+}
--- a/src/server/test/xml.h	Fri Jan 17 22:23:30 2020 +0100
+++ b/src/server/test/xml.h	Sat Jan 18 13:48:59 2020 +0100
@@ -38,6 +38,7 @@
 
 UCX_TEST(test_wsxml_iterator);
 UCX_TEST(test_wsxml_get_required_namespaces);
+UCX_TEST(test_wsxml_write_nodes);
 
 
 #define XML_TESTDATA1 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \
@@ -98,6 +99,30 @@
             <x4:elm4 xmlns:x4=\"http://example.com/ns_0/\" >str1</x4:elm4>\
         </x1:prop>"
 
+#define XML_TESTDATA6 "<?xml version=\"1.0\" encoding=\"utf-8\" ?> \n\
+        <x1:test \n\
+            xmlns:x1=\"http://example.com/ns1/\" \n\
+            xmlns:x2=\"http://example.com/ns2/\" > \n\
+            <x1:elm1>str1</x1:elm1>\n\
+            <x2:elm2>str1</x2:elm2>\n\
+            <x3:elm3 xmlns:x3=\"http://example.com/ns_0/\" >str1</x3:elm3>\n\
+            <x1:sub> \n\
+                <x1:a attr1=\"val1\"/> \n\
+                <x1:a attr2=\"val2\">text</x1:a>\n\
+                <x1:b x2:nsattr=\"nsval\"><x1:c/></x1:b>\n\
+            </x1:sub> \n\
+            <x1:newns xmlns:x4=\"http://example.com/0/\" x4:attr3=\"val3\">\n\
+            </x1:newns>\n\
+            <x1:text>Hello\n\
+            World\n\
+            end.\n\
+            </x1:text>\n\
+            <x1:entityref ea=\"test &amp; value\">\n\
+            entity reference test &amp;quote&amp; \n\
+            &#x3C;xml&#x3E;\n\
+            </x1:entityref>\n\
+        </x1:test>\n"
+
 #ifdef __cplusplus
 }
 #endif
--- a/src/server/util/writer.c	Fri Jan 17 22:23:30 2020 +0100
+++ b/src/server/util/writer.c	Sat Jan 18 13:48:59 2020 +0100
@@ -71,6 +71,7 @@
         return w->error;
     }
     
+    // available bytes
     size_t a = w->size - w->pos;
     if(a == 0) {
         if(writer_flush(w)) {
@@ -78,11 +79,13 @@
         }
     }
     
-    size_t cplen = len > a ? a : len;
-    memcpy(w->buffer, s, cplen);
+    size_t cplen = len > a ? a : len; // number of bytes we can copy
+    memcpy(w->buffer+w->pos, s, cplen);
     w->pos += cplen;
     
     if(cplen < len) {
+        // not all bytes copied -> call writer_put again
+        // the number of available bytes is 0 then, therefore flush is called
         return writer_put(w, s + cplen, len - cplen);
     } else {
         return 0;
--- a/src/server/webdav/xml.c	Fri Jan 17 22:23:30 2020 +0100
+++ b/src/server/webdav/xml.c	Sat Jan 18 13:48:59 2020 +0100
@@ -37,13 +37,15 @@
 
 #include "xml.h"
 
-/* ------------------------     utils      ------------------------ */
+/*****************************************************************************
+ *    Utility functions
+ *****************************************************************************/
 
 /*
  * generates a string key for an xml namespace
  * format: prefix '\0' href
  */
-static sstr_t namespace_key(UcxAllocator *a, WSNamespace *ns) {
+static sstr_t xml_namespace_key(UcxAllocator *a, WSNamespace *ns) {
     sstr_t key = sstrcat_a(a, 3,
             ns->prefix ? sstr((char*)ns->prefix) : S("\0"),
             S("\0"),
@@ -52,6 +54,10 @@
 }
 
 
+/*****************************************************************************
+ *    Public functions
+ *****************************************************************************/
+
 /* ------------------------ wsxml_iterator ------------------------ */
 
 typedef struct StackElm {
@@ -184,7 +190,7 @@
     return ret;
 }
 
-/* ------------------------ wsxml_get_namespaces ------------------------ */
+/* ------------------- wsxml_get_required_namespaces ------------------- */
 
 typedef struct WSNsCollector {
     UcxAllocator *a;
@@ -199,7 +205,7 @@
     if(node->type == XML_ELEMENT_NODE && node->ns) {
         // we create a list of unique prefix-href namespaces by putting
         // all namespaces in a map
-        sstr_t nskey = namespace_key(col->a, node->ns);
+        sstr_t nskey = xml_namespace_key(col->a, node->ns);
         if(!nskey.ptr) {
             col->error = 1;
             return 1;
@@ -271,7 +277,7 @@
         // what we get is a map that contains all missing namespace definitions
         WebdavNSList *def = col.def;
         while(def) {
-            sstr_t nskey = namespace_key(&a, def->namespace);
+            sstr_t nskey = xml_namespace_key(&a, def->namespace);
             if(!nskey.ptr) {
                 if(error) *error = 1;
                 break;
@@ -305,3 +311,243 @@
     ucx_map_free(nsmap);
     return list;
 }
+
+
+/*****************************************************************************
+ *    Non public functions
+ *****************************************************************************/
+
+typedef struct XmlWriter {
+    /*
+     * Memory pool for temp memory allocations
+     */
+    pool_handle_t *pool;
+    
+    /*
+     * Buffered output stream
+     */
+    Writer *out;
+    
+    /*
+     * Map for all previously defined namespaces
+     * key: (char*) namespace prefix
+     * value: WSNamespace*
+     */
+    UcxMap *namespaces;
+    
+    /*
+     * Should namespace definitions be created
+     */
+    WSBool define_namespaces;
+} XmlWriter;
+
+/*
+ * Serialize an XML text node
+ * This replaces some special characters with entity refs
+ */
+static void xml_ser_text(Writer *out, const char *text) {
+    size_t start = 0;
+    size_t i;
+    sstr_t entityref = { NULL, 0 };
+    for(i=0;text[i]!='\0';i++) {
+        switch(text[i]) {
+            case '<': entityref = S("&lt;"); break;
+            case '>': entityref = S("&gt;"); break;
+            case '&': entityref = S("&amp;"); break;
+            case '\"': entityref = S("&quot;"); break;
+            case '\'': entityref = S("&apos;"); break;
+        }
+        if(entityref.ptr) {
+            size_t len = i-start;
+            if(len > 0) {
+                writer_put(out, text+start, len);
+            }
+            writer_puts(out, entityref);
+            entityref.ptr = NULL;
+            entityref.length = 0;
+            start = i+1;
+        }
+    }
+    size_t len = i-start;
+    if(len > 0) {
+        writer_put(out, text+start, len);
+    }
+}
+
+/*
+ * Serialize an XML element node
+ */
+static void xml_ser_element(XmlWriter *xw, xmlNode *node) {
+    Writer *out = xw->out;
+    writer_putc(out, '<');
+    
+    // write prefix and ':'
+    if(node->ns && node->ns->prefix) {
+        writer_puts(out, sstr((char*)node->ns->prefix));
+        writer_putc(out, ':');
+    }
+    
+    // node name
+    writer_puts(out, sstr((char*)node->name));
+    
+    // namespace definitions
+    if(xw->define_namespaces) {
+        xmlNs *nsdef = node->nsDef;
+        while(nsdef) {
+            // we define only namespaces without prefix or namespaces
+            // with prefix, that are not already defined
+            // xw->namespaces contains all namespace, that were defined
+            // before xml serialization
+            if(!nsdef->prefix) {
+                writer_puts(out, S(" xmlns=\""));
+                writer_puts(out, sstr((char*)nsdef->href));
+                writer_putc(out, '"');
+            } else {
+                WSNamespace *n = xw->namespaces ?
+                    ucx_map_cstr_get(xw->namespaces, (char*)nsdef->prefix) :
+                    NULL;
+                if(!n) {
+                    writer_puts(out, S(" xmlns:"));
+                    writer_puts(out, sstr((char*)nsdef->prefix));
+                    writer_puts(out, S("=\""));
+                    writer_puts(out, sstr((char*)nsdef->href));
+                    writer_putc(out, '"');
+                }
+            }
+            
+            nsdef = nsdef->next;
+        }
+    }
+    
+    // attributes
+    xmlAttr *attr = node->properties;
+    while(attr) {
+        // format: ' [<prefix>:]<name>="<value>"'
+        writer_putc(out, ' ');
+        // optional namespace
+        if(attr->ns && attr->ns->prefix) {
+            writer_puts(out, sstr((char*)attr->ns->prefix));
+            writer_putc(out, ':');
+        }
+        // <name>="
+        writer_puts(out, sstr((char*)attr->name));
+        writer_puts(out, S("=\""));
+        // value
+        xmlNode *value = attr->children;
+        while(value) {
+            if(value->content) {
+                xml_ser_text(out, (const char*)value->content);
+            }
+            value = value->next;
+        }
+        // trailing quote
+        writer_putc(out, '"');
+        
+        attr = attr->next;
+    }
+    
+    if(node->children) {
+        writer_putc(out, '>');
+    } else {
+        writer_puts(out, S("/>"));
+    }
+}
+
+static int xml_ser_node_begin(xmlNode *node, void *userdata) {
+    XmlWriter *xw = userdata;
+    switch(node->type) {
+        case XML_ELEMENT_NODE: xml_ser_element(xw, node); break;
+        case XML_ATTRIBUTE_NODE: break;
+        case XML_TEXT_NODE: {
+            xml_ser_text(xw->out, (const char*)node->content);
+            break;
+        }
+        case XML_CDATA_SECTION_NODE: {
+            break;
+        }
+        case XML_ENTITY_REF_NODE: break;
+        case XML_ENTITY_NODE: break;
+        case XML_PI_NODE: break;
+        case XML_COMMENT_NODE: break;
+        case XML_DOCUMENT_NODE: break;
+        case XML_DOCUMENT_TYPE_NODE: break;
+        case XML_DOCUMENT_FRAG_NODE: break;
+        case XML_NOTATION_NODE: break;
+        case XML_HTML_DOCUMENT_NODE: break;
+        case XML_DTD_NODE: break;
+        case XML_ELEMENT_DECL: break;
+        case XML_ATTRIBUTE_DECL: break;
+        case XML_ENTITY_DECL: break;
+        case XML_NAMESPACE_DECL: break;
+        case XML_XINCLUDE_START: break;
+        case XML_XINCLUDE_END: break;
+        default: break;
+    }
+    return 0;
+}
+
+static int xml_ser_node_end(xmlNode *node, void *userdata) {
+    XmlWriter *xw = userdata;
+    Writer *out = xw->out;
+    if(node->type == XML_ELEMENT_NODE) {
+        if(node->children) {
+            writer_puts(xw->out, S("</"));
+            // write prefix and ':'
+            if(node->ns && node->ns->prefix) {
+                writer_puts(out, sstr((char*)node->ns->prefix));
+                writer_putc(out, ':');
+            }
+            // name and close tag
+            writer_puts(out, sstr((char*)node->name));
+            writer_putc(out, '>');
+            
+        } // element was already closed in xml_ser_node_begin
+    }
+    return 0;
+}
+
+
+static int xml_write_nodes(
+        pool_handle_t *pool,
+        Writer *out,
+        UcxMap *nsdefs,
+        WSBool createdefs,
+        xmlNode *node)
+{
+    XmlWriter xmlwriter;
+    xmlwriter.pool = pool;
+    xmlwriter.out = out;
+    xmlwriter.namespaces = nsdefs;
+    xmlwriter.define_namespaces = createdefs;
+    
+    // iterate over xml nodes
+    // this includes node->children and node->next
+    int err = wsxml_iterator(
+            pool,
+            node,
+            xml_ser_node_begin,
+            xml_ser_node_end,
+            &xmlwriter);
+    if(err) {
+        return -1;
+    }
+    
+    return out->error;
+}
+
+int wsxml_write_nodes(
+        pool_handle_t *pool,
+        Writer *out,
+        UcxMap *nsdefs,
+        xmlNode *node)
+{
+    return xml_write_nodes(pool, out, nsdefs, TRUE, node);
+}
+
+int wsxml_write_nodes_without_nsdef(
+        pool_handle_t *pool,
+        Writer *out,
+        xmlNode *node)
+{
+    return xml_write_nodes(pool, out, NULL, FALSE, node);
+}
--- a/src/server/webdav/xml.h	Fri Jan 17 22:23:30 2020 +0100
+++ b/src/server/webdav/xml.h	Sat Jan 18 13:48:59 2020 +0100
@@ -26,22 +26,43 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef XML_H
-#define XML_H
+#ifndef WEBDAV_XML_H
+#define WEBDAV_XML_H
 
 #include "../public/webdav.h"
 #include <libxml/tree.h>
 
+#include <ucx/map.h>
+
+#include "../util/writer.h"
+
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+/*
+ * Writes the xmlNode, all children and following nodes to the writer 'out'.
+ */
+int wsxml_write_nodes(
+        pool_handle_t *pool,
+        Writer *out,
+        UcxMap *nsdefs,
+        xmlNode *node);
 
+/*
+ * Writes the xmlNode, all children and following nodes to the writer 'out'
+ * without creating any namespace definitions. Therefore all namespaces must
+ * be already defined and previously written to 'out'.
+ */
+int wsxml_write_nodes_without_nsdef(
+        pool_handle_t *pool,
+        Writer *out,
+        xmlNode *node);
 
 
 #ifdef __cplusplus
 }
 #endif
 
-#endif /* XML_H */
+#endif /* WEBDAV_XML_H */
 

mercurial