/* xlPutC - put a character to a file or stream */ xlEXPORT void xlPutC(xlValue fptr,int ch) { int flags; /* count the character */ ++xlfsize; /* check for output to nil */ if (fptr == xlNil) return; /* otherwise, check for output to an unnamed stream */ switch (xlNodeType(fptr)) { case xlFSTREAM: fstream_putc(fptr,ch); break; case xlUSTREAM: ustream_putc(fptr,ch); break; case xlOSTREAM: ostream_putc(fptr,ch); break; default: xlError("expecting stream",fptr); break; } /* set the beginning of line flag */ flags = xlGetPFlags(fptr); if (ch == '\n') flags |= xlpfBOL; else flags &= ~xlpfBOL; xlSetPFlags(fptr,flags); }
/** * Emit attributes. */ static void xfmt_handle_pass2_attr(const char *uri, const char *local, const char *value, void *data) { struct xfmt_pass2 *xp2 = data; int c; bool apos_escape = FALSE; size_t len; size_t overhead; if (uri != NULL) { ostream_printf(xp2->os, " %s:", xfmt_uri_to_prefix(xp2, uri)); } else { ostream_putc(xp2->os, ' '); } /* * Inspect value to select proper quoting. */ c = xfmt_quoting_char(value); if ('\0' == c) { apos_escape = TRUE; c = '\''; /* We'll be quoting "'" so it's safe to use */ } /* * Now check for escaping of any '&', '<' or '>'. */ overhead = xfmt_text_escape_overhead(value, TRUE, apos_escape, &len); ostream_printf(xp2->os, "%s=%c", local, c); if (0 == overhead) { ostream_write(xp2->os, value, len); } else { char *escaped = xfmt_text_escape(value, TRUE, apos_escape, len + overhead); ostream_write(xp2->os, escaped, len + overhead); hfree(escaped); } ostream_putc(xp2->os, c); }
/** * Indent if we just emitted a new-line. */ static void xfmt_indent(const struct xfmt_pass2 *xp2) { if (xp2->options & XFMT_O_NO_INDENT) return; if (xp2->last_was_nl) { unsigned i; for (i = 1; i < xp2->depth; i++) { ostream_putc(xp2->os, '\t'); } } }
/** * Pass 2 handler on each tree node leave. */ static void xfmt_handle_pass2_leave(void *node, void *data) { xnode_t *xn = node; struct xfmt_pass2 *xp2 = data; if (xnode_is_element(xn)) { const char *uri = xnode_element_ns(xn); xfmt_indent(xp2); /* * We don't emit the URI if it is that of the default namespace. */ if ( uri != NULL && xp2->default_ns != NULL && 0 == strcmp(uri, xp2->default_ns) ) { uri = NULL; } if (uri != NULL) { const char *pre = xfmt_uri_to_prefix(xp2, uri); ostream_printf(xp2->os, "</%s:%s>", pre, xnode_element_name(xn)); } else { ostream_printf(xp2->os, "</%s>", xnode_element_name(xn)); } if (!(xp2->options & XFMT_O_SINGLE_LINE)) { ostream_putc(xp2->os, '\n'); } /* Reset for next element */ xp2->had_text = FALSE; xp2->last_was_nl = TRUE; } xfmt_pass2_leaving(xp2); }
/** * Pass 2 handler on each tree node entry. */ static bool xfmt_handle_pass2_enter(const void *node, void *data) { const xnode_t *xn = node; struct xfmt_pass2 *xp2 = data; xp2->depth++; if (xnode_is_element(xn)) { GSList *ns = xfmt_ns_declarations(xp2, xn); const char *nsuri = xnode_element_ns(xn); if (!xp2->had_text && !xp2->last_was_nl) { if (!(xp2->options & XFMT_O_SINGLE_LINE)) ostream_putc(xp2->os, '\n'); xp2->last_was_nl = TRUE; } xfmt_indent(xp2); /* * Look for the namespace matching the default namespace, in which * case we don't have to emit it. */ if ( nsuri != NULL && xp2->default_ns != NULL && 0 == strcmp(nsuri, xp2->default_ns) ) { nsuri = NULL; } if (nsuri != NULL) { const char *prefix = xfmt_uri_to_prefix(xp2, nsuri); ostream_printf(xp2->os, "<%s:%s", prefix, xnode_element_name(xn)); } else { ostream_printf(xp2->os, "<%s", xnode_element_name(xn)); } /* * Install default namespace on the root element, if any. */ if (1 == xp2->depth && xp2->default_ns != NULL) { int c = xfmt_quoting_char(xp2->default_ns); g_assert(c != '\0'); ostream_printf(xp2->os, " xmlns=%c%s%c", c, xp2->default_ns, c); } /* * Declare namespaces for the element's scope. */ xfmt_pass2_declare_ns(xp2, ns); g_slist_free(ns); /* * Emit attributes. */ xnode_prop_foreach(xn, xfmt_handle_pass2_attr, xp2); /* * Handle content-less elements specially: we don't let the * "leave" callback run. * * We consider an element with a single empty text child as * content-less, so we test with xnode_is_empty() instead of * !xnode_has_content(). */ xp2->had_text = FALSE; if (xnode_is_empty(xn)) { ostream_write(xp2->os, XFMT_EMPTY, CONST_STRLEN(XFMT_EMPTY)); if (!(xp2->options & XFMT_O_SINGLE_LINE)) ostream_putc(xp2->os, '\n'); xp2->last_was_nl = TRUE; xfmt_pass2_leaving(xp2); /* No children, no "leave" callback */ return FALSE; } ostream_write(xp2->os, XFMT_GT, CONST_STRLEN(XFMT_GT)); xp2->last_was_nl = FALSE; } else if (xnode_is_text(xn)) { const char *text = xnode_text(xn); size_t len; size_t overhead; bool amp; if (xp2->options & XFMT_O_SKIP_BLANKS) { const char *start; size_t tlen; start = xfmt_strip_blanks(text, &tlen); if (0 == tlen) goto ignore; /* FIXME: handle blank collapsing */ (void) start; } /* * If text is known to have entities, we must not escape the '&'. * This means the generated XML must define that entity in the DTD * part of the tree. * * Computes the required overhead to fully escape the text (0 meaning * that no escaping is required). If the overhead is larger than * a leading "<![CDATA[" and a closing ""]]>", we can emit a CDATA * section instead, provided the text does not contain "]]>". */ amp = !xnode_text_has_entities(xn); overhead = xfmt_text_escape_overhead(text, amp, FALSE, &len); if (0 == overhead) { ostream_write(xp2->os, text, len); } else if ( overhead >= XFMT_CDATA_OVERHEAD && NULL == strstr(text, XFMT_CDATA_END) ) { ostream_write(xp2->os, XFMT_CDATA_START, CONST_STRLEN(XFMT_CDATA_START)); ostream_write(xp2->os, text, len); ostream_write(xp2->os, XFMT_CDATA_END, CONST_STRLEN(XFMT_CDATA_END)); } else { char *escaped = xfmt_text_escape(text, amp, FALSE, len + overhead); ostream_write(xp2->os, escaped, len + overhead); hfree(escaped); } xp2->last_was_nl = FALSE; xp2->had_text = TRUE; } ignore: return TRUE; }
/** * Extended XML formatting of a tree. * * Namespaces, if any, are automatically assigned a prefix, whose format * is "ns%u", the counter being incremented from 0. * * Users can supply a vector mapping namespaces to prefixes, so that they * can force specific prefixes for a given well-known namespace. * * If there is a default namespace, all the tags belonging to that namespace * are emitted without any prefix. * * The output stream must be explicitly closed by the user upon return. * * Options can be supplied to tune the output: * * - XFMT_O_SKIP_BLANKS will skip pure white space nodes. * - XFMT_O_COLLAPSE_BLANKS will replace consecutive blanks with 1 space * - XFMT_O_NO_INDENT requests that no indentation of the tree be made. * - XFMT_O_PROLOGUE emits a leading <?xml?> prologue. * - XFMT_O_FORCE_10 force generation of XML 1.0 * - XFMT_O_SINGLE_LINE emits XML as one big line (implies XFMT_O_NO_INDENT). * * @param root the root of the tree to dump * @param os the output stream where tree is dumped * @param options formatting options, as documented above * @param pvec a vector of prefixes to be used for namespaces * @param pvcnt amount of entries in vector * @param default_ns default namespace to install at root element * * @return TRUE on success. */ bool xfmt_tree_extended(const xnode_t *root, ostream_t *os, uint32 options, const struct xfmt_prefix *pvec, size_t pvcnt, const char *default_ns) { struct xfmt_pass1 xp1; struct xfmt_pass2 xp2; struct xfmt_invert_ctx ictx; const char *dflt_ns; g_assert(root != NULL); g_assert(os != NULL); if (options & XFMT_O_COLLAPSE_BLANKS) { /* FIXME */ g_carp("XFMT_O_COLLAPSE_BLANKS not supported yet"); stacktrace_where_print(stderr); } if (options & XFMT_O_SINGLE_LINE) options |= XFMT_O_NO_INDENT; /* * First pass: look at namespaces and construct a table recording the * earliest tree depth at which a namespace is used. */ ZERO(&xp1); xp1.uri2node = htable_create(HASH_KEY_STRING, 0); xp1.uri2prefix = nv_table_make(FALSE); if (default_ns != NULL) xp1.attr_uris = hset_create(HASH_KEY_STRING, 0); htable_insert_const(xp1.uri2node, VXS_XML_URI, root); xnode_tree_enter_leave(deconstify_pointer(root), xfmt_handle_pass1_enter, xfmt_handle_pass1_leave, &xp1); g_assert(0 == xp1.depth); /* Sound traversal */ /* * If there was a default namespace, make sure it is used in the tree. * Otherwise, discard it. */ if (default_ns != NULL) { if (NULL == htable_lookup(xp1.uri2node, default_ns)) { g_carp("XFMT default namespace '%s' is not needed", default_ns); dflt_ns = NULL; } else { dflt_ns = default_ns; } } else { dflt_ns = NULL; } /* * Prepare context for second pass. */ ZERO(&xp2); xp2.node2uri = htable_create(HASH_KEY_SELF, 0); xp2.os = os; xp2.options = options; xp2.default_ns = dflt_ns; xp2.attr_uris = xp1.attr_uris; xp2.uri2prefix = xp1.uri2prefix; xp2.uris = symtab_make(); xp2.prefixes = symtab_make(); xp2.depth = 0; xp2.pcount = 0; xp2.last_was_nl = TRUE; /* * Iterate over the hash table we've built to create a table indexed * by tree node and listing the namespaces to declare for that node. */ ictx.uri2node = xp1.uri2node; ictx.node2uri = xp2.node2uri; htable_foreach(xp1.uri2node, xfmt_invert_uri_kv, &ictx); htable_free_null(&xp1.uri2node); /* * Emit prologue if requested. */ if (options & XFMT_O_PROLOGUE) { if (options & XFMT_O_FORCE_10) { ostream_write(os, XFMT_DECL_10, CONST_STRLEN(XFMT_DECL_10)); } else { ostream_write(os, XFMT_DECL, CONST_STRLEN(XFMT_DECL)); } if (!(options & XFMT_O_SINGLE_LINE)) { ostream_putc(os, '\n'); } } xfmt_prefix_declare(&xp2, VXS_XML_URI, VXS_XML); /* * Prepare user-defined URI -> prefix mappings. */ if (pvcnt != 0) { size_t i; for (i = 0; i < pvcnt; i++) { const struct xfmt_prefix *p = &pvec[i]; xfmt_prefix_declare(&xp2, p->uri, p->prefix); } } /* * Second pass: generation. */ xnode_tree_enter_leave(deconstify_pointer(root), xfmt_handle_pass2_enter, xfmt_handle_pass2_leave, &xp2); g_assert(0 == xp2.depth); /* Sound traversal */ /* * Done, cleanup. */ nv_table_free_null(&xp2.uri2prefix); symtab_free_null(&xp2.prefixes); symtab_free_null(&xp2.uris); htable_free_null(&xp2.node2uri); hset_free_null(&xp2.attr_uris); return !ostream_has_ioerr(os); }