From 1e01dbfc114e4ff96c428d1db38a4908ba8d0438 Mon Sep 17 00:00:00 2001 From: defanor Date: Tue, 5 Sep 2023 22:45:26 +0300 Subject: Implement XML serialization in C and in Rust Continuing replacement of libxml2, planning to use libexpat or a Rust XML parser as an alternative for XML parsing. --- emacs/xml_interface.c | 2 +- examples/basic.c | 4 +- src/Makefile.am | 3 +- src/rexmpp.c | 4 +- src/rexmpp_openpgp.c | 2 +- src/rexmpp_utf8.h | 93 +++++++++++++++++++ src/rexmpp_xml.c | 236 +++++++++++++++++++++++++++++++++++++++++++----- src/rexmpp_xml.h | 4 +- src/rexmpp_xml.rs | 246 +++++++++++++++++++++++++++++++++++++++++++++++++- 9 files changed, 561 insertions(+), 33 deletions(-) create mode 100644 src/rexmpp_utf8.h diff --git a/emacs/xml_interface.c b/emacs/xml_interface.c index 3dabf96..76e888c 100644 --- a/emacs/xml_interface.c +++ b/emacs/xml_interface.c @@ -26,7 +26,7 @@ and EOF ones, to simplify reading with libxml2. void print_xml (rexmpp_xml_t *node) { - char *s = rexmpp_xml_serialize(node); + char *s = rexmpp_xml_serialize(node, 0); printf("%s%c\n", s, '\0'); free(s); } diff --git a/examples/basic.c b/examples/basic.c index 66c54f6..0b77145 100644 --- a/examples/basic.c +++ b/examples/basic.c @@ -68,7 +68,7 @@ int my_sasl_property_cb (rexmpp_t *s, rexmpp_sasl_property prop) { /* An XML in callback, printing what was received. */ int my_xml_in_cb (rexmpp_t *s, rexmpp_xml_t *node) { - char *xml_buf = rexmpp_xml_serialize(node); + char *xml_buf = rexmpp_xml_serialize(node, 0); printf("recv: %s\n", xml_buf); free(xml_buf); return 0; @@ -76,7 +76,7 @@ int my_xml_in_cb (rexmpp_t *s, rexmpp_xml_t *node) { /* An XML out callback, printing what is about to be sent. */ int my_xml_out_cb (rexmpp_t *s, rexmpp_xml_t *node) { - char *xml_buf = rexmpp_xml_serialize(node); + char *xml_buf = rexmpp_xml_serialize(node, 0); printf("send: %s\n", xml_buf); free(xml_buf); return 0; diff --git a/src/Makefile.am b/src/Makefile.am index 9e38c41..5a5cbc3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -21,7 +21,8 @@ librexmpp_la_SOURCES = rexmpp_roster.h rexmpp_roster.c \ rexmpp_jingle.h rexmpp_jingle.c \ rexmpp_base64.h rexmpp_base64.c \ rexmpp_sasl.h rexmpp_sasl.c \ - rexmpp_xml.h rexmpp_xml.c + rexmpp_xml.h rexmpp_xml.c \ + rexmpp_utf8.h include_HEADERS = config.h rexmpp_roster.h rexmpp_tcp.h rexmpp_socks.h rexmpp.h \ rexmpp_dns.h rexmpp_tls.h rexmpp_jid.h rexmpp_openpgp.h rexmpp_console.h \ diff --git a/src/rexmpp.c b/src/rexmpp.c index a647245..77413f1 100644 --- a/src/rexmpp.c +++ b/src/rexmpp.c @@ -941,7 +941,7 @@ rexmpp_err_t rexmpp_send_continue (rexmpp_t *s) s->send_buffer = NULL; if (s->send_queue != NULL) { rexmpp_xml_t *node = s->send_queue; - unsigned char *buf = rexmpp_xml_serialize(node); + unsigned char *buf = rexmpp_xml_serialize(node, 0); ret = rexmpp_send_start(s, buf, strlen(buf)); free(buf); if (ret != REXMPP_SUCCESS) { @@ -1034,7 +1034,7 @@ rexmpp_err_t rexmpp_send (rexmpp_t *s, rexmpp_xml_t *node) } if (s->send_buffer == NULL) { - unsigned char *buf = rexmpp_xml_serialize(node); + unsigned char *buf = rexmpp_xml_serialize(node, 0); ret = rexmpp_send_raw(s, buf, strlen(buf)); free(buf); rexmpp_xml_free(node); diff --git a/src/rexmpp_openpgp.c b/src/rexmpp_openpgp.c index 0e2f634..c11d031 100644 --- a/src/rexmpp_openpgp.c +++ b/src/rexmpp_openpgp.c @@ -752,7 +752,7 @@ char *rexmpp_openpgp_payload (rexmpp_t *s, } /* Serialize the resulting XML. */ - char *plaintext = rexmpp_xml_serialize(elem); + char *plaintext = rexmpp_xml_serialize(elem, 0); rexmpp_xml_free(elem); /* Encrypt, base64-encode. */ diff --git a/src/rexmpp_utf8.h b/src/rexmpp_utf8.h new file mode 100644 index 0000000..4096aac --- /dev/null +++ b/src/rexmpp_utf8.h @@ -0,0 +1,93 @@ +/** + @file rexmpp_utf8.h + @brief UTF-8 helper functions + @author defanor + @date 2023 + @copyright MIT license. +*/ + +#ifndef REXMPP_UTF8_H +#define REXMPP_UTF8_H + +#include +#include + +#ifdef HAVE_ICU + +#include +#define REXMPP_U8_NEXT U8_NEXT + +#else + +#define REXMPP_U8_NEXT(str, pos, len, out) \ + rexmpp_utf8_next(str, &pos, len, &out); + +/** + @brief Similar to libicu's U8_NEXT macros: reads a single UTF-8 + code point, advances the position. + @param[in] str A string to read. + @param[in,out] pos Byte position within the string. Advanced by the + number of bytes read to produce a code point, not advanced on + failure. + @param[in] len String length. + @param[in,out] out A pointer to the location for writing the code + point. + @returns 0 on failure, 1 on success. +*/ +inline static +void rexmpp_utf8_next (const uint8_t *str, + size_t *pos, + size_t len, + int32_t *out) +{ + if (*pos >= len) { + *out = -1; + return; + } + + if ((str[*pos] & 0x80) == 0 + && *pos + 1 <= len) + /* U+0000 to U+007F: 0xxxxxxx */ + { + *out = str[*pos]; + *pos = *pos + 1; + } else if ((str[*pos] & 0xe0) == 0xc0 + && *pos + 2 <= len + && (str[*pos + 1] & 0xc0) == 0x80) + /* U+0080 to U+07FF: 110xxxxx 10xxxxxx */ + { + *out = (((int32_t)(str[*pos] & 0x1f) << 6) + | ((int32_t)str[*pos + 1] & 0x3f)); + *pos = *pos + 2; + } else if ((str[*pos] & 0xf0) == 0xe0 + && *pos + 3 <= len + && (str[*pos + 1] & 0xc0) == 0x80 + && (str[*pos + 2] & 0xc0) == 0x80) + /* U+0800 to U+FFFF: 1110xxxx 10xxxxxx 10xxxxxx */ + { + *out = (((((int32_t)(str[*pos] & 0xf) << 6) + | ((int32_t)str[*pos + 1] & 0x3f)) << 6) + | ((int32_t)str[*pos + 2] & 0x3f)); + *pos = *pos + 3; + } else if ((str[*pos] & 0xf8) == 0xf0 + && *pos + 4 <= len + && (str[*pos + 1] & 0xc0) == 0x80 + && (str[*pos + 2] & 0xc0) == 0x80 + && (str[*pos + 3] & 0xc0) == 0x80) + /* U+10000 to U+10FFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + { + *out = (((((((int32_t)(str[*pos] & 7) << 6) + | ((int32_t)str[*pos + 1] & 0x3f)) << 6) + | (((int32_t)str[*pos + 2] & 0x3f))) << 6) + | ((int32_t)str[*pos + 3] & 0x3f)); + *pos = *pos + 4; + } else + /* Invalid UTF-8 */ + { + *out = -1; + } +} + +#endif /* HAVE_ICU */ + +#endif /* REXMPP_UTF8_H */ diff --git a/src/rexmpp_xml.c b/src/rexmpp_xml.c index f384470..7e428e2 100644 --- a/src/rexmpp_xml.c +++ b/src/rexmpp_xml.c @@ -7,9 +7,11 @@ */ #include +#include #include #include #include "rexmpp.h" +#include "rexmpp_utf8.h" #include "rexmpp_xml.h" #ifndef USE_RUST @@ -353,6 +355,206 @@ int rexmpp_xml_remove_attr (rexmpp_xml_t *node, const char *name) { return rexmpp_xml_remove_attr_ns(node, name, NULL); } + +/* Adds a character, grows the string as needed. */ +inline char *rexmpp_str_putc (char *str, size_t *len, char c) { + char *ret = str; + if ((*len) % 1024 == 0) { + ret = realloc(str, (*len) + 1024); + if (ret == NULL) { + /* A failure to realloc. */ + if (str != NULL) { + free(str); + } + return NULL; + } + } + ret[*len] = c; + *len = (*len) + 1; + return ret; +} + +inline char *rexmpp_str_putc_escaped (char *str, size_t *len, char c) { + char *ret = str; + char buf[7]; + char *esc = buf; + size_t i = 0; + size_t esc_len; + if (c == '<') { + esc = "<"; + } else if (c == '>') { + esc = ">"; + } else if (c == '&') { + esc = "&"; + } else if (c == '\'') { + esc = "'"; + } else if (c == '"') { + esc = """; + } else { + snprintf(esc, 7, "&#%u;", c); + } + esc_len = strlen(esc); + while (i < esc_len) { + ret = rexmpp_str_putc(ret, len, esc[i]); + i++; + } + return ret; +} + +char *rexmpp_xml_print_name (char *str, size_t *len, const char *name) { + char *ret = str; + size_t name_len = strlen(name); + size_t i = 0; + int32_t c = 0; /* matches ICU's UChar32 */ + size_t prev_i = 0, j; + do { + REXMPP_U8_NEXT(name, i, name_len, c); + if (c >= 0) { + if (c == ':' + || (c >= 'A' && c <= 'Z') + || c == '_' + || (c >= 'a' && c <= 'z') + || (c >= 0xC0 && c <= 0xD6) + || (c >= 0xD8 && c <= 0xF6) + || (c >= 0xF8 && c <= 0x2FF) + || (c >= 0x370 && c <= 0x37D) + || (c >= 0x37F && c <= 0x1FFF) + || (c >= 0x200C && c <= 0x200D) + || (c >= 0x2070 && c <= 0x218F) + || (c >= 0x2C00 && c <= 0x2FEF) + || (c >= 0x3001 && c <= 0xD7FF) + || (c >= 0xF900 && c <= 0xFDCF) + || (c >= 0xFDF0 && c <= 0xFFF0) + || (c >= 0x10000 && c <= 0xEFFFF) + || ((i > 0) && + (c == '-' + || c == '.' + || (c >= '0' && c <= '9') + || c == 0xB7 + || (c >= 0x0300 && c <= 0x036F) + || (c >= 0x203F && c <= 0x2040)))) + { + /* Print the allowed characters. */ + for (j = prev_i; j < i; j++) { + ret = rexmpp_str_putc(ret, len, name[j]); + } + } + } else { + /* Skip invalid characters. */ + i++; + } + prev_i = i; + } while (i < name_len); + return ret; +} + +char *rexmpp_xml_print_text (char *str, size_t *len, const char *text) { + char *ret = str; + size_t i = 0; + size_t text_len = strlen(text); + while (i < text_len && ret != NULL) { + char c = text[i]; + if (strchr("<&>'\"", c)) { + /* Escape the few special characters. */ + ret = rexmpp_str_putc_escaped(ret, len, c); + } else { + /* Write others as is. */ + ret = rexmpp_str_putc(ret, len, c); + } + i++; + } + return ret; +} + +char *rexmpp_xml_print_raw (char *str, size_t *len, const char *text) { + char *ret = str; + size_t i = 0; + size_t text_len = strlen(text); + while (i < text_len && ret != NULL) { + char c = text[i]; + ret = rexmpp_str_putc(ret, len, c); + i++; + } + return ret; +} + +inline char *rexmpp_xml_print_indent (char *str, + size_t *len, + int indent) { + if (indent <= 0) { + return str; + } + int i; + char *ret = str; + for (i = 0; i < indent * 2; i++) { + ret = rexmpp_str_putc(ret, len, ' '); + } + return ret; +} + +char *rexmpp_xml_print (char *str, + size_t *len, + const rexmpp_xml_t *node, + int indent) { + char *ret = str; + if (node->type == REXMPP_XML_TEXT) { + ret = rexmpp_xml_print_text(ret, len, node->alt.text); + } else if (node->type == REXMPP_XML_ELEMENT) { + if (indent > 0) { + ret = rexmpp_str_putc(ret, len, '\n'); + ret = rexmpp_xml_print_indent(ret, len, indent); + } + ret = rexmpp_str_putc(ret, len, '<'); + ret = rexmpp_xml_print_name(ret, len, node->alt.elem.qname.name); + if (node->alt.elem.qname.namespace != NULL) { + ret = rexmpp_xml_print_raw(ret, len, " xmlns=\""); + ret = rexmpp_xml_print_text(ret, len, node->alt.elem.qname.namespace); + ret = rexmpp_str_putc(ret, len, '"'); + } + if (node->alt.elem.attributes != NULL) { + rexmpp_xml_attr_t *attr; + for (attr = node->alt.elem.attributes; attr != NULL; attr = attr->next) { + ret = rexmpp_str_putc(ret, len, ' '); + /* Ignoring namespaces here for now. */ + ret = rexmpp_xml_print_name(ret, len, attr->qname.name); + ret = rexmpp_xml_print_raw(ret, len, "=\""); + ret = rexmpp_xml_print_text(ret, len, attr->value); + ret = rexmpp_str_putc(ret, len, '"'); + } + } + if (node->alt.elem.children == NULL) { + ret = rexmpp_xml_print_raw(ret, len, "/>"); + } else { + ret = rexmpp_str_putc(ret, len, '>'); + rexmpp_xml_t *child; + int last_child_is_textual = 0; + for (child = rexmpp_xml_children(node); + child != NULL; + child = child->next) + { + ret = rexmpp_xml_print(ret, len, child, + indent > -1 ? indent + 1 : -1); + last_child_is_textual = child->type == REXMPP_XML_TEXT; + } + if (indent >= 0 && ! last_child_is_textual) { + ret = rexmpp_str_putc(ret, len, '\n'); + ret = rexmpp_xml_print_indent(ret, len, indent); + } + ret = rexmpp_xml_print_raw(ret, len, "alt.elem.qname.name); + ret = rexmpp_str_putc(ret, len, '>'); + } + } + return ret; +} + +char *rexmpp_xml_serialize (const rexmpp_xml_t *node, int pretty) { + size_t s_len = 0; + char *s = NULL; + s = rexmpp_xml_print(s, &s_len, node, pretty ? 0 : -1); + s = rexmpp_str_putc(s, &s_len, '\0'); + return s; +} #endif rexmpp_xml_t * @@ -368,19 +570,6 @@ rexmpp_xml_add_id (rexmpp_t *s, return node; } -char *rexmpp_xml_serialize (rexmpp_xml_t *node) { - xmlNodePtr node_libxml2 = rexmpp_xml_to_libxml2(node); - xmlBufferPtr buf = xmlBufferCreate(); - xmlSaveCtxtPtr ctx = xmlSaveToBuffer(buf, "utf-8", 0); - xmlSaveTree(ctx, node_libxml2); - xmlSaveFlush(ctx); - xmlSaveClose(ctx); - unsigned char *out = xmlBufferDetach(buf); - xmlBufferFree(buf); - xmlFreeNode(node_libxml2); - return out; -} - xmlNodePtr rexmpp_xml_parse_libxml2 (const char *str, int str_len) { xmlNodePtr elem = NULL; xmlDocPtr doc = xmlReadMemory(str, str_len, "", "utf-8", XML_PARSE_NONET); @@ -409,16 +598,19 @@ rexmpp_xml_t *rexmpp_xml_read_file (const char *path) { return ret; } +#ifndef USE_RUST int rexmpp_xml_write_file (const char *path, rexmpp_xml_t* node) { - xmlDocPtr doc = xmlNewDoc("1.0"); - xmlNodePtr node_lxml2 = rexmpp_xml_to_libxml2(node); - xmlDocSetRootElement(doc, node_lxml2); - xmlSaveFileEnc(path, doc, "utf-8"); - xmlFreeDoc(doc); + FILE *fd = fopen(path, "w"); + if (fd == NULL) { + return -1; + } + char *serialized = rexmpp_xml_serialize(node, 1); + fputs("\n", fd); + fputs(serialized, fd); + fclose(fd); return 0; } -#ifndef USE_RUST unsigned int rexmpp_xml_siblings_count (rexmpp_xml_t *node) { unsigned int i = 0; for (i = 0; node != NULL; i++) { @@ -546,8 +738,8 @@ rexmpp_xml_find_child (rexmpp_xml_t *node, int rexmpp_xml_eq (rexmpp_xml_t *n1, rexmpp_xml_t *n2) { /* Just serialize and compare strings for now: awkward, but simple. */ - char *n1str = rexmpp_xml_serialize(n1); - char *n2str = rexmpp_xml_serialize(n2); + char *n1str = rexmpp_xml_serialize(n1, 0); + char *n2str = rexmpp_xml_serialize(n2, 0); int eq = (strcmp(n1str, n2str) == 0); free(n1str); free(n2str); @@ -555,7 +747,7 @@ int rexmpp_xml_eq (rexmpp_xml_t *n1, rexmpp_xml_t *n2) { } #ifndef USE_RUST -rexmpp_xml_t *rexmpp_xml_children (rexmpp_xml_t *node) { +rexmpp_xml_t *rexmpp_xml_children (const rexmpp_xml_t *node) { if (node != NULL && node->type == REXMPP_XML_ELEMENT) { return node->alt.elem.children; } diff --git a/src/rexmpp_xml.h b/src/rexmpp_xml.h index dbd8ea8..0a3cb63 100644 --- a/src/rexmpp_xml.h +++ b/src/rexmpp_xml.h @@ -148,7 +148,7 @@ rexmpp_xml_add_id (rexmpp_t *s, @param[in] node An XML node. @returns A string (must be freed by the caller). */ -char *rexmpp_xml_serialize (rexmpp_xml_t *node); +char *rexmpp_xml_serialize (const rexmpp_xml_t *node, int pretty); /** @brief Count the number of siblings after a given node. @@ -207,7 +207,7 @@ rexmpp_xml_t *rexmpp_xml_find_child (rexmpp_xml_t *node, const char *namespace, const char *name); -rexmpp_xml_t *rexmpp_xml_children (rexmpp_xml_t *node); +rexmpp_xml_t *rexmpp_xml_children (const rexmpp_xml_t *node); char *rexmpp_xml_text (rexmpp_xml_t *node); diff --git a/src/rexmpp_xml.rs b/src/rexmpp_xml.rs index 56de5ff..30f5f38 100644 --- a/src/rexmpp_xml.rs +++ b/src/rexmpp_xml.rs @@ -4,9 +4,11 @@ use std::os::raw::{c_char, c_int, c_void, c_uint}; use std::ptr; use std::ffi::{CStr, CString}; use std::clone::Clone; +use std::fs::File; +use std::io::Write; + // extern { -// fn rexmpp_xml_serialize (node: *mut RexmppXML) -> *mut c_char; // fn rexmpp_xml_parse (str: *mut c_char, str_len: c_int) -> *mut RexmppXML; // } @@ -370,6 +372,212 @@ fn rexmpp_xml_remove_attr (node: *mut RexmppXML, } +fn rexmpp_xml_push_escaped (c: char, s: &mut String) { + if c == '<' { + s.push_str("<") + } else if c == '>' { + s.push_str(">") + } else if c == '&' { + s.push_str("&") + } else if c == '\'' { + s.push_str("'") + } else if c == '"' { + s.push_str(""") + } else { + s.push_str(format!("&#{};", u32::from(c)).as_str()); + }; +} + +fn rexmpp_xml_print_text (c: char, s: &mut String) { + if "<&>'\"".chars().any(|sc| sc == c) { + rexmpp_xml_push_escaped(c, s); + } else { + s.push(c); + } +} + +fn rexmpp_xml_print_name (i: usize, c: char, s: &mut String) { + if c == ':' + || (c >= 'A' && c <= 'Z') + || c == '_' + || (c >= 'a' && c <= 'z') + || (c >= '\u{C0}' && c <= '\u{D6}') + || (c >= '\u{D8}' && c <= '\u{F6}') + || (c >= '\u{F8}' && c <= '\u{2FF}') + || (c >= '\u{370}' && c <= '\u{37D}') + || (c >= '\u{37F}' && c <= '\u{1FFF}') + || (c >= '\u{200C}' && c <= '\u{200D}') + || (c >= '\u{2070}' && c <= '\u{218F}') + || (c >= '\u{2C00}' && c <= '\u{2FEF}') + || (c >= '\u{3001}' && c <= '\u{D7FF}') + || (c >= '\u{F900}' && c <= '\u{FDCF}') + || (c >= '\u{FDF0}' && c <= '\u{FFF0}') + || (c >= '\u{10000}' && c <= '\u{EFFFF}') + || ((i > 0) && + (c == '-' + || c == '.' + || (c >= '0' && c <= '9') + || c == '\u{B7}' + || (c >= '\u{0300}' && c <= '\u{036F}') + || (c >= '\u{203F}' && c <= '\u{2040}'))) + { + // Print the allowed characters. + s.push(c); + } +} + +fn rexmpp_xml_print_indent (indent: i32, s: &mut String) { + let mut i = 0; + while i < indent { + s.push_str(" "); + i = i + 1; + } +} + +fn rexmpp_xml_print (node_ptr: *const RexmppXML, + ret: &mut String, + indent: i32) + -> () +{ + unsafe { + let node : RexmppXML = *node_ptr; + match node { + RexmppXML { node_type : NodeType::Text, + alt : RexmppXMLAlt { text: text_ptr }, + next: _} => { + let text_cstr : &CStr = CStr::from_ptr(text_ptr); + let text_str : String = + String::from_utf8_lossy(text_cstr.to_bytes()) + .to_string(); + // let mut escaped = String::with_capacity(text_str.capacity()); + text_str.chars(). + for_each(|c| rexmpp_xml_print_text(c, ret)); + }, + RexmppXML { node_type : NodeType::Element, + alt : RexmppXMLAlt { elem: element }, + next: _} => { + // let mut ret = String::with_capacity(1024); + let name_cstr : &CStr = + CStr::from_ptr(element.qname.name); + let name_str : String = + String::from_utf8_lossy(name_cstr.to_bytes()) + .to_string(); + if indent > 0 { + ret.push('\n'); + rexmpp_xml_print_indent(indent, ret); + } + ret.push('<'); + name_str.char_indices(). + for_each(|(i, c)| rexmpp_xml_print_name(i, c, ret)); + if element.qname.namespace != ptr::null_mut() { + let namespace_cstr : &CStr = + CStr::from_ptr(element.qname.namespace); + let namespace_str : String = + String::from_utf8_lossy(namespace_cstr.to_bytes()) + .to_string(); + ret.push_str(" xmlns=\""); + namespace_str.chars(). + for_each(|c| rexmpp_xml_print_text(c, ret)); + ret.push('"'); + } + if element.attributes != ptr::null_mut() { + let mut attr_ptr : *mut RexmppXMLAttribute = + element.attributes; + while attr_ptr != ptr::null_mut() { + let attr : RexmppXMLAttribute = *attr_ptr; + let attr_name_cstr = + CStr::from_ptr(attr.qname.name); + let attr_name_str = + String::from_utf8_lossy(attr_name_cstr.to_bytes()) + .to_string(); + // TODO: handle attribute namespaces someday. + let attr_value_cstr = + CStr::from_ptr(attr.value); + let attr_value_str = + String::from_utf8_lossy(attr_value_cstr.to_bytes()) + .to_string(); + ret.push(' '); + attr_name_str.char_indices(). + for_each(|(i, c)| + rexmpp_xml_print_name(i, c, ret)); + ret.push_str("=\""); + attr_value_str.chars(). + for_each(|c| rexmpp_xml_print_text(c, ret)); + ret.push('"'); + attr_ptr = (*attr_ptr).next; + } + } + if element.children == ptr::null_mut() { + ret.push_str("/>"); + } else { + ret.push('>'); + let mut child = rexmpp_xml_children(node_ptr); + let mut last_child_is_textual = false; + while child != ptr::null_mut() { + rexmpp_xml_print(child, ret, + if indent > -1 { indent + 1 } + else { -1 } ); + last_child_is_textual = + (*child).node_type == NodeType::Text; + child = (*child).next; + } + if indent > 0 && ! last_child_is_textual { + ret.push('\n'); + rexmpp_xml_print_indent(indent, ret); + } + ret.push_str("'); + } + } + } + } +} + +fn rexmpp_xml_serialize_str (node_ptr: *const RexmppXML, + pretty: bool) + -> String +{ + let mut out = String::with_capacity(4096); + rexmpp_xml_print(node_ptr, &mut out, if pretty { 0 } else { -1 }); + return out; +} + +#[no_mangle] +extern "C" +fn rexmpp_xml_serialize (node_ptr: *const RexmppXML, + pretty: bool) + -> *mut c_char +{ + let out = rexmpp_xml_serialize_str(node_ptr, pretty); + match CString::new(out) { + Ok(cstr) => unsafe { strdup(cstr.as_ptr()) }, + Err(_) => ptr::null_mut() + } +} + +#[no_mangle] +extern "C" +fn rexmpp_xml_write_file (path: *const c_char, + node: *const RexmppXML) + -> c_int +{ + let path_cstr : &CStr = unsafe { CStr::from_ptr(path) }; + let path_str : String = + String::from_utf8_lossy(path_cstr.to_bytes()) + .to_string(); + match File::create(path_str) { + Ok(mut fd) => { + fd.write_all(b"\n"); + fd.write_all(rexmpp_xml_serialize_str(node, false).as_bytes()); + }, + Err(_) => { return -1; } + } + return 0; +} + #[no_mangle] extern "C" fn rexmpp_xml_siblings_count (mut node: *const RexmppXML) -> c_uint { @@ -567,7 +775,7 @@ fn rexmpp_xml_find_child (node_ptr: *mut RexmppXML, #[no_mangle] extern "C" -fn rexmpp_xml_children (node: *mut RexmppXML) +fn rexmpp_xml_children (node: *const RexmppXML) -> *mut RexmppXML { if node != ptr::null_mut() && unsafe { (*node).node_type } == NodeType::Element { @@ -624,3 +832,37 @@ fn rexmpp_xml_text_child (node: *mut RexmppXML) -> *mut c_char { rexmpp_xml_text(rexmpp_xml_children(node)) } + +#[no_mangle] +extern "C" +fn rexmpp_xml_reverse (mut node: *mut RexmppXML) + -> *mut RexmppXML { + let mut next; + let mut prev = ptr::null_mut(); + while node != ptr::null_mut() { + unsafe { + next = (*node).next; + (*node).next = prev; + prev = node; + node = next; + } + } + return prev; +} + +#[no_mangle] +extern "C" +fn rexmpp_xml_reverse_all (node: *mut RexmppXML) + -> *mut RexmppXML { + let mut cur = node; + while cur != ptr::null_mut() { + unsafe { + if (*cur).node_type == NodeType::Element { + (*cur).alt.elem.children = + rexmpp_xml_reverse_all((*cur).alt.elem.children); + } + cur = (*cur).next; + } + } + return node; +} -- cgit v1.2.3