Andres Freund <and...@anarazel.de> writes: > On Monday 10 August 2009 01:21:35 Andrew Dunstan wrote: >> That ";" after the attribute is almost certainly wrong. This is a classic >> case of what I was talking about a month or two ago. Building up XML (or >> any structured doc, really, XML is not special in this regard) by ad hoc >> methods is horribly error prone. if you don't want to rely on libxml, then >> I think you need to develop a lightweight abstraction rather than just >> appending to a StringInfo.
> While it would be possible to add another step inbetween and generate a > format > neutral tree and generate the different formats out of it I am not sure that > this is worthwile. > The current text format will need to stay special cased anyway because its > far > to inconsistent to generate it from anything abstract and I don't see any > completely new formats coming (i.e. not just optional parts)? The patch as-submitted does have a lightweight abstraction layer of sorts, but the main code line feels free to ignore that and hack around it. I agree that we can't avoid special-casing the text output, but I'm trying to improve the encapsulation otherwise. What I've got at the moment is attached. I'd be interested in comments on the grouping notion in particular --- I reverse-engineered that out of what the code was doing, and I'm sure it could use improvement. regards, tom lane /* * Explain a property, such as sort keys or targets, that takes the form of * a list of unlabeled items. "data" is a list of C strings. */ static void ExplainPropertyList(const char *qlabel, List *data, ExplainState *es) { ListCell *lc; bool first = true; switch (es->format) { case EXPLAIN_FORMAT_TEXT: appendStringInfoSpaces(es->str, es->indent * 2); appendStringInfo(es->str, " %s: ", qlabel); foreach(lc, data) { if (!first) appendStringInfoString(es->str, ", "); appendStringInfoString(es->str, (const char *) lfirst(lc)); first = false; } appendStringInfoChar(es->str, '\n'); break; case EXPLAIN_FORMAT_XML: ExplainXMLTag(qlabel, X_OPENING, es); foreach(lc, data) { char *str; appendStringInfoSpaces(es->str, es->indent * 2 + 2); appendStringInfoString(es->str, "<Item>"); str = escape_xml((const char *) lfirst(lc)); appendStringInfoString(es->str, str); pfree(str); appendStringInfoString(es->str, "</Item>\n"); } ExplainXMLTag(qlabel, X_CLOSING, es); break; case EXPLAIN_FORMAT_JSON: ExplainJSONLineEnding(es); appendStringInfoSpaces(es->str, es->indent * 2); escape_json(es->str, qlabel); appendStringInfoString(es->str, ": ["); foreach(lc, data) { if (!first) appendStringInfoString(es->str, ", "); escape_json(es->str, (const char *) lfirst(lc)); first = false; } appendStringInfoChar(es->str, ']'); break; } } #define ExplainPropertyText(qlabel, value, es) \ ExplainProperty(qlabel, value, false, es) /* * Explain a simple property. * * If "numeric" is true, the value is a number (or other value that * doesn't need quoting in JSON). */ static void ExplainProperty(const char *qlabel, const char *value, bool numeric, ExplainState *es) { switch (es->format) { case EXPLAIN_FORMAT_TEXT: appendStringInfoSpaces(es->str, es->indent * 2); appendStringInfo(es->str, " %s: %s\n", qlabel, value); break; case EXPLAIN_FORMAT_XML: { char *str; appendStringInfoSpaces(es->str, es->indent * 2); ExplainXMLTag(qlabel, X_OPENING | X_NOWHITESPACE, es); str = escape_xml(value); appendStringInfoString(es->str, str); pfree(str); ExplainXMLTag(qlabel, X_CLOSING | X_NOWHITESPACE, es); appendStringInfoChar(es->str, '\n'); } break; case EXPLAIN_FORMAT_JSON: ExplainJSONLineEnding(es); appendStringInfoSpaces(es->str, es->indent * 2); escape_json(es->str, qlabel); appendStringInfoString(es->str, ": "); if (numeric) appendStringInfoString(es->str, value); else escape_json(es->str, value); break; } } /* * Explain an integer-valued property. */ static void ExplainPropertyInteger(const char *qlabel, int value, ExplainState *es) { char buf[32]; snprintf(buf, sizeof(buf), "%d", value); ExplainProperty(qlabel, buf, true, es); } /* * Explain a long-integer-valued property. */ static void ExplainPropertyLong(const char *qlabel, long value, ExplainState *es) { char buf[32]; snprintf(buf, sizeof(buf), "%ld", value); ExplainProperty(qlabel, buf, true, es); } /* * Explain a float-valued property, using the specified number of * fractional digits. */ static void ExplainPropertyFloat(const char *qlabel, double value, int ndigits, ExplainState *es) { char buf[256]; snprintf(buf, sizeof(buf), "%.*f", ndigits, value); ExplainProperty(qlabel, buf, true, es); } /* * Open a group of related objects. * * objtype is the type of the group object, labelname is its label within * a containing object (if any). * * If labeled is true, the group members will be labeled properties, * while if it's false, they'll be unlabeled objects. */ static void ExplainOpenGroup(const char *objtype, const char *labelname, bool labeled, ExplainState *es) { switch (es->format) { case EXPLAIN_FORMAT_TEXT: /* nothing to do */ break; case EXPLAIN_FORMAT_XML: ExplainXMLTag(objtype, X_OPENING, es); break; case EXPLAIN_FORMAT_JSON: ExplainJSONLineEnding(es); appendStringInfoSpaces(es->str, 2 * es->indent); if (labelname) { escape_json(es->str, labelname); appendStringInfoString(es->str, ": "); } appendStringInfoChar(es->str, labeled ? '{' : '['); /* * In JSON mode, the grouping_stack is an integer list. 0 means * we've emitted nothing at this grouping level, 1 means we've * emitted something (and so the next item needs a comma). * See ExplainJSONLineEnding(). */ es->grouping_stack = lcons_int(0, es->grouping_stack); break; } es->indent++; } /* * Close a group of related objects. * Parameters must match the corresponding ExplainOpenGroup call. */ static void ExplainCloseGroup(const char *objtype, const char *labelname, bool labeled, ExplainState *es) { es->indent--; switch (es->format) { case EXPLAIN_FORMAT_TEXT: /* nothing to do */ break; case EXPLAIN_FORMAT_XML: ExplainXMLTag(objtype, X_CLOSING, es); break; case EXPLAIN_FORMAT_JSON: appendStringInfoChar(es->str, '\n'); appendStringInfoSpaces(es->str, 2 * es->indent); appendStringInfoChar(es->str, labeled ? '}' : ']'); es->grouping_stack = list_delete_first(es->grouping_stack); break; } } /* * Emit opening or closing XML tag. * * "flags" must contain X_OPENING or X_CLOSING, which optionally can be * OR'ed with X_NOWHITESPACE to suppress the whitespace we'd normally add. * * XML tag names can't contain white space, so we replace any spaces in * "tagname" with dashes. */ static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es) { const char *s; if ((flags & X_NOWHITESPACE) == 0) appendStringInfoSpaces(es->str, 2 * es->indent); appendStringInfoCharMacro(es->str, '<'); if ((flags & X_CLOSING) != 0) appendStringInfoCharMacro(es->str, '/'); for (s = tagname; *s; s++) appendStringInfoCharMacro(es->str, (*s == ' ') ? '-' : *s); appendStringInfoCharMacro(es->str, '>'); if ((flags & X_NOWHITESPACE) == 0) appendStringInfoCharMacro(es->str, '\n'); } /* * Emit a JSON line ending. * * JSON requires a comma after each property but the last. To facilitate this, * in JSON mode, the text emitted for each property begins just prior to the * preceding line-break (and comma, if applicable). */ static void ExplainJSONLineEnding(ExplainState *es) { Assert(es->format == EXPLAIN_FORMAT_JSON); if (linitial_int(es->grouping_stack) != 0) appendStringInfoChar(es->str, ','); else linitial_int(es->grouping_stack) = 1; appendStringInfoChar(es->str, '\n'); } /* * Produce a JSON string literal, properly escaping characters in the text. */ static void escape_json(StringInfo buf, const char *str) { const char *p; appendStringInfoCharMacro(buf, '\"'); for (p = str; *p; p++) { switch (*p) { case '\b': appendStringInfoString(buf, "\\b"); break; case '\f': appendStringInfoString(buf, "\\f"); break; case '\n': appendStringInfoString(buf, "\\n"); break; case '\r': appendStringInfoString(buf, "\\r"); break; case '\t': appendStringInfoString(buf, "\\t"); break; case '"': appendStringInfoString(buf, "\\\""); break; case '\\': appendStringInfoString(buf, "\\\\"); break; default: if ((unsigned char) *p < ' ') appendStringInfo(buf, "\\u%04x", (int) *p); else appendStringInfoCharMacro(buf, *p); break; } } appendStringInfoCharMacro(buf, '\"'); } -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers