/* * xmlsave.c: Implementation of the document serializer * * See Copyright for the status of this software. * * Author: Daniel Veillard */ #define IN_LIBXML #include "libxml.h" #include #include #include #include #include #include #include #define MAX_INDENT 60 #include #include "private/buf.h" #include "private/enc.h" #include "private/error.h" #include "private/html.h" #include "private/io.h" #include "private/save.h" #ifdef LIBXML_OUTPUT_ENABLED #define XHTML_NS_NAME BAD_CAST "http://www.w3.org/1999/xhtml" struct _xmlSaveCtxt { const xmlChar *encoding; xmlCharEncodingHandlerPtr handler; xmlOutputBufferPtr buf; int options; int level; int format; char indent[MAX_INDENT + 1]; /* array for indenting output */ int indent_nr; int indent_size; xmlCharEncodingOutputFunc escape; /* used for element content */ }; /************************************************************************ * * * Output error handlers * * * ************************************************************************/ /** * Handle an out of memory condition * * @param out an output buffer */ static void xmlSaveErrMemory(xmlOutputBufferPtr out) { if (out != NULL) out->error = XML_ERR_NO_MEMORY; xmlRaiseMemoryError(NULL, NULL, NULL, XML_FROM_OUTPUT, NULL); } /** * Handle an out of memory condition * * @param out an output buffer * @param code the error number * @param node the location of the error. * @param extra extra information */ static void xmlSaveErr(xmlOutputBufferPtr out, int code, xmlNodePtr node, const char *extra) { const char *msg = NULL; int res; /* Don't overwrite catastrophic errors */ if ((out != NULL) && (out->error != XML_ERR_OK) && (xmlIsCatastrophicError(XML_ERR_FATAL, out->error))) return; if (code == XML_ERR_NO_MEMORY) { xmlSaveErrMemory(out); return; } if (out != NULL) out->error = code; if (code == XML_ERR_UNSUPPORTED_ENCODING) { msg = "Unsupported encoding: %s"; } else { msg = xmlErrString(code); extra = NULL; } res = xmlRaiseError(NULL, NULL, NULL, NULL, node, XML_FROM_OUTPUT, code, XML_ERR_ERROR, NULL, 0, extra, NULL, NULL, 0, 0, msg, extra); if (res < 0) xmlSaveErrMemory(out); } /************************************************************************ * * * Allocation and deallocation * * * ************************************************************************/ /** * Sets the indent string. * * @since 2.14.0 * * @param ctxt save context * @param indent indent string * @returns 0 on success, -1 if the string is NULL, empty or too long. */ int xmlSaveSetIndentString(xmlSaveCtxt *ctxt, const char *indent) { size_t len; int i; if ((ctxt == NULL) || (indent == NULL)) return(-1); len = strlen(indent); if ((len <= 0) || (len > MAX_INDENT)) return(-1); ctxt->indent_size = len; ctxt->indent_nr = MAX_INDENT / ctxt->indent_size; for (i = 0; i < ctxt->indent_nr; i++) memcpy(&ctxt->indent[i * ctxt->indent_size], indent, len); return(0); } /** * Initialize a saving context * * @param ctxt the saving context * @param options save options */ static void xmlSaveCtxtInit(xmlSaveCtxtPtr ctxt, int options) { if (ctxt == NULL) return; xmlSaveSetIndentString(ctxt, xmlTreeIndentString); if (options & XML_SAVE_FORMAT) ctxt->format = 1; else if (options & XML_SAVE_WSNONSIG) ctxt->format = 2; if (((options & XML_SAVE_EMPTY) == 0) && (xmlSaveNoEmptyTags)) options |= XML_SAVE_NO_EMPTY; ctxt->options = options; } /** * Free a saving context, destroying the output in any remaining buffer */ static void xmlFreeSaveCtxt(xmlSaveCtxtPtr ctxt) { if (ctxt == NULL) return; if (ctxt->encoding != NULL) xmlFree((char *) ctxt->encoding); if (ctxt->buf != NULL) xmlOutputBufferClose(ctxt->buf); xmlFree(ctxt); } /** * Create a new saving context * * @returns the new structure or NULL in case of error */ static xmlSaveCtxtPtr xmlNewSaveCtxt(const char *encoding, int options) { xmlSaveCtxtPtr ret; ret = (xmlSaveCtxtPtr) xmlMalloc(sizeof(xmlSaveCtxt)); if (ret == NULL) { xmlSaveErrMemory(NULL); return ( NULL ); } memset(ret, 0, sizeof(xmlSaveCtxt)); if (encoding != NULL) { xmlParserErrors res; res = xmlOpenCharEncodingHandler(encoding, /* output */ 1, &ret->handler); if (res != XML_ERR_OK) { xmlSaveErr(NULL, res, NULL, encoding); xmlFreeSaveCtxt(ret); return(NULL); } ret->encoding = xmlStrdup((const xmlChar *)encoding); } xmlSaveCtxtInit(ret, options); return(ret); } /************************************************************************ * * * Dumping XML tree content to a simple buffer * * * ************************************************************************/ static void xmlSaveWriteText(xmlSaveCtxt *ctxt, const xmlChar *text, unsigned flags) { if (ctxt->encoding == NULL) flags |= XML_ESCAPE_NON_ASCII; xmlSerializeText(ctxt->buf, text, SIZE_MAX, flags); } /** * Serialize the attribute in the buffer * * @param ctxt save context * @param attr the attribute pointer */ static void xmlSaveWriteAttrContent(xmlSaveCtxt *ctxt, xmlAttrPtr attr) { xmlNodePtr children; xmlOutputBufferPtr buf = ctxt->buf; children = attr->children; while (children != NULL) { switch (children->type) { case XML_TEXT_NODE: xmlSaveWriteText(ctxt, children->content, XML_ESCAPE_ATTR); break; case XML_ENTITY_REF_NODE: xmlOutputBufferWrite(buf, 1, "&"); xmlOutputBufferWriteString(buf, (const char *) children->name); xmlOutputBufferWrite(buf, 1, ";"); break; default: /* should not happen unless we have a badly built tree */ break; } children = children->next; } } /** * This will dump the content the notation declaration as an XML DTD definition * * @param buf the XML buffer output * @param nota A notation declaration */ static void xmlBufDumpNotationDecl(xmlOutputBufferPtr buf, xmlNotationPtr nota) { xmlOutputBufferWrite(buf, 11, "name); if (nota->PublicID != NULL) { xmlOutputBufferWrite(buf, 8, " PUBLIC "); xmlOutputBufferWriteQuotedString(buf, nota->PublicID); if (nota->SystemID != NULL) { xmlOutputBufferWrite(buf, 1, " "); xmlOutputBufferWriteQuotedString(buf, nota->SystemID); } } else { xmlOutputBufferWrite(buf, 8, " SYSTEM "); xmlOutputBufferWriteQuotedString(buf, nota->SystemID); } xmlOutputBufferWrite(buf, 3, " >\n"); } /** * This is called with the hash scan function, and just reverses args * * @param nota A notation declaration * @param buf the XML buffer output * @param name unused */ static void xmlBufDumpNotationDeclScan(void *nota, void *buf, const xmlChar *name ATTRIBUTE_UNUSED) { xmlBufDumpNotationDecl((xmlOutputBufferPtr) buf, (xmlNotationPtr) nota); } /** * This will dump the content of the notation table as an XML DTD definition * * @param buf an xmlBuf output * @param table A notation table */ static void xmlBufDumpNotationTable(xmlOutputBufferPtr buf, xmlNotationTablePtr table) { xmlHashScan(table, xmlBufDumpNotationDeclScan, buf); } /** * Dump the occurrence operator of an element. * * @param buf output buffer * @param cur element table */ static void xmlBufDumpElementOccur(xmlOutputBufferPtr buf, xmlElementContentPtr cur) { switch (cur->ocur) { case XML_ELEMENT_CONTENT_ONCE: break; case XML_ELEMENT_CONTENT_OPT: xmlOutputBufferWrite(buf, 1, "?"); break; case XML_ELEMENT_CONTENT_MULT: xmlOutputBufferWrite(buf, 1, "*"); break; case XML_ELEMENT_CONTENT_PLUS: xmlOutputBufferWrite(buf, 1, "+"); break; } } /** * This will dump the content of the element table as an XML DTD definition * * @param buf output buffer * @param content element table */ static void xmlBufDumpElementContent(xmlOutputBufferPtr buf, xmlElementContentPtr content) { xmlElementContentPtr cur; if (content == NULL) return; xmlOutputBufferWrite(buf, 1, "("); cur = content; do { if (cur == NULL) return; switch (cur->type) { case XML_ELEMENT_CONTENT_PCDATA: xmlOutputBufferWrite(buf, 7, "#PCDATA"); break; case XML_ELEMENT_CONTENT_ELEMENT: if (cur->prefix != NULL) { xmlOutputBufferWriteString(buf, (const char *) cur->prefix); xmlOutputBufferWrite(buf, 1, ":"); } xmlOutputBufferWriteString(buf, (const char *) cur->name); break; case XML_ELEMENT_CONTENT_SEQ: case XML_ELEMENT_CONTENT_OR: if ((cur != content) && (cur->parent != NULL) && ((cur->type != cur->parent->type) || (cur->ocur != XML_ELEMENT_CONTENT_ONCE))) xmlOutputBufferWrite(buf, 1, "("); cur = cur->c1; continue; } while (cur != content) { xmlElementContentPtr parent = cur->parent; if (parent == NULL) return; if (((cur->type == XML_ELEMENT_CONTENT_OR) || (cur->type == XML_ELEMENT_CONTENT_SEQ)) && ((cur->type != parent->type) || (cur->ocur != XML_ELEMENT_CONTENT_ONCE))) xmlOutputBufferWrite(buf, 1, ")"); xmlBufDumpElementOccur(buf, cur); if (cur == parent->c1) { if (parent->type == XML_ELEMENT_CONTENT_SEQ) xmlOutputBufferWrite(buf, 3, " , "); else if (parent->type == XML_ELEMENT_CONTENT_OR) xmlOutputBufferWrite(buf, 3, " | "); cur = parent->c2; break; } cur = parent; } } while (cur != content); xmlOutputBufferWrite(buf, 1, ")"); xmlBufDumpElementOccur(buf, content); } /** * This will dump the content of the element declaration as an XML * DTD definition * * @param buf an xmlBuf output * @param elem An element table */ static void xmlBufDumpElementDecl(xmlOutputBufferPtr buf, xmlElementPtr elem) { xmlOutputBufferWrite(buf, 10, "prefix != NULL) { xmlOutputBufferWriteString(buf, (const char *) elem->prefix); xmlOutputBufferWrite(buf, 1, ":"); } xmlOutputBufferWriteString(buf, (const char *) elem->name); xmlOutputBufferWrite(buf, 1, " "); switch (elem->etype) { case XML_ELEMENT_TYPE_EMPTY: xmlOutputBufferWrite(buf, 5, "EMPTY"); break; case XML_ELEMENT_TYPE_ANY: xmlOutputBufferWrite(buf, 3, "ANY"); break; case XML_ELEMENT_TYPE_MIXED: case XML_ELEMENT_TYPE_ELEMENT: xmlBufDumpElementContent(buf, elem->content); break; default: /* assert(0); */ break; } xmlOutputBufferWrite(buf, 2, ">\n"); } /** * This will dump the content of the enumeration * * @param buf output buffer * @param cur an enumeration */ static void xmlBufDumpEnumeration(xmlOutputBufferPtr buf, xmlEnumerationPtr cur) { while (cur != NULL) { xmlOutputBufferWriteString(buf, (const char *) cur->name); if (cur->next != NULL) xmlOutputBufferWrite(buf, 3, " | "); cur = cur->next; } xmlOutputBufferWrite(buf, 1, ")"); } /** * This will dump the content of the attribute declaration as an XML * DTD definition * * @param ctxt save context * @param attr an attribute declaration */ static void xmlSaveWriteAttributeDecl(xmlSaveCtxtPtr ctxt, xmlAttributePtr attr) { xmlOutputBufferPtr buf = ctxt->buf; xmlOutputBufferWrite(buf, 10, "elem); xmlOutputBufferWrite(buf, 1, " "); if (attr->prefix != NULL) { xmlOutputBufferWriteString(buf, (const char *) attr->prefix); xmlOutputBufferWrite(buf, 1, ":"); } xmlOutputBufferWriteString(buf, (const char *) attr->name); switch (attr->atype) { case XML_ATTRIBUTE_CDATA: xmlOutputBufferWrite(buf, 6, " CDATA"); break; case XML_ATTRIBUTE_ID: xmlOutputBufferWrite(buf, 3, " ID"); break; case XML_ATTRIBUTE_IDREF: xmlOutputBufferWrite(buf, 6, " IDREF"); break; case XML_ATTRIBUTE_IDREFS: xmlOutputBufferWrite(buf, 7, " IDREFS"); break; case XML_ATTRIBUTE_ENTITY: xmlOutputBufferWrite(buf, 7, " ENTITY"); break; case XML_ATTRIBUTE_ENTITIES: xmlOutputBufferWrite(buf, 9, " ENTITIES"); break; case XML_ATTRIBUTE_NMTOKEN: xmlOutputBufferWrite(buf, 8, " NMTOKEN"); break; case XML_ATTRIBUTE_NMTOKENS: xmlOutputBufferWrite(buf, 9, " NMTOKENS"); break; case XML_ATTRIBUTE_ENUMERATION: xmlOutputBufferWrite(buf, 2, " ("); xmlBufDumpEnumeration(buf, attr->tree); break; case XML_ATTRIBUTE_NOTATION: xmlOutputBufferWrite(buf, 11, " NOTATION ("); xmlBufDumpEnumeration(buf, attr->tree); break; default: /* assert(0); */ break; } switch (attr->def) { case XML_ATTRIBUTE_NONE: break; case XML_ATTRIBUTE_REQUIRED: xmlOutputBufferWrite(buf, 10, " #REQUIRED"); break; case XML_ATTRIBUTE_IMPLIED: xmlOutputBufferWrite(buf, 9, " #IMPLIED"); break; case XML_ATTRIBUTE_FIXED: xmlOutputBufferWrite(buf, 7, " #FIXED"); break; default: /* assert(0); */ break; } if (attr->defaultValue != NULL) { xmlOutputBufferWrite(buf, 2, " \""); xmlSaveWriteText(ctxt, attr->defaultValue, XML_ESCAPE_ATTR); xmlOutputBufferWrite(buf, 1, "\""); } xmlOutputBufferWrite(buf, 2, ">\n"); } /** * This will dump the quoted string value, taking care of the special * treatment required by % * * @param buf output buffer * @param content entity content. */ static void xmlBufDumpEntityContent(xmlOutputBufferPtr buf, const xmlChar *content) { const char * base, *cur; if (content == NULL) return; xmlOutputBufferWrite(buf, 1, "\""); base = cur = (const char *) content; while (*cur != 0) { if (*cur == '"') { if (base != cur) xmlOutputBufferWrite(buf, cur - base, base); xmlOutputBufferWrite(buf, 6, """); cur++; base = cur; } else if (*cur == '%') { if (base != cur) xmlOutputBufferWrite(buf, cur - base, base); xmlOutputBufferWrite(buf, 6, "%"); cur++; base = cur; } else { cur++; } } if (base != cur) xmlOutputBufferWrite(buf, cur - base, base); xmlOutputBufferWrite(buf, 1, "\""); } /** * This will dump the content of the entity table as an XML DTD definition * * @param buf an xmlBuf output * @param ent An entity table */ static void xmlBufDumpEntityDecl(xmlOutputBufferPtr buf, xmlEntityPtr ent) { if ((ent->etype == XML_INTERNAL_PARAMETER_ENTITY) || (ent->etype == XML_EXTERNAL_PARAMETER_ENTITY)) xmlOutputBufferWrite(buf, 11, "name); xmlOutputBufferWrite(buf, 1, " "); if ((ent->etype == XML_EXTERNAL_GENERAL_PARSED_ENTITY) || (ent->etype == XML_EXTERNAL_GENERAL_UNPARSED_ENTITY) || (ent->etype == XML_EXTERNAL_PARAMETER_ENTITY)) { if (ent->ExternalID != NULL) { xmlOutputBufferWrite(buf, 7, "PUBLIC "); xmlOutputBufferWriteQuotedString(buf, ent->ExternalID); xmlOutputBufferWrite(buf, 1, " "); } else { xmlOutputBufferWrite(buf, 7, "SYSTEM "); } xmlOutputBufferWriteQuotedString(buf, ent->SystemID); } if (ent->etype == XML_EXTERNAL_GENERAL_UNPARSED_ENTITY) { if (ent->content != NULL) { /* Should be true ! */ xmlOutputBufferWrite(buf, 7, " NDATA "); if (ent->orig != NULL) xmlOutputBufferWriteString(buf, (const char *) ent->orig); else xmlOutputBufferWriteString(buf, (const char *) ent->content); } } if ((ent->etype == XML_INTERNAL_GENERAL_ENTITY) || (ent->etype == XML_INTERNAL_PARAMETER_ENTITY)) { /* * We could save the original quote character and avoid * calling xmlOutputBufferWriteQuotedString here. */ if (ent->orig != NULL) xmlOutputBufferWriteQuotedString(buf, ent->orig); else xmlBufDumpEntityContent(buf, ent->content); } xmlOutputBufferWrite(buf, 2, ">\n"); } /************************************************************************ * * * Dumping XML tree content to an I/O output buffer * * * ************************************************************************/ static int xmlSaveSwitchEncoding(xmlSaveCtxtPtr ctxt, const char *encoding) { xmlOutputBufferPtr buf = ctxt->buf; xmlCharEncodingHandler *handler; xmlParserErrors res; /* shouldn't happen */ if ((buf->encoder != NULL) || (buf->conv != NULL)) return(-1); res = xmlOpenCharEncodingHandler(encoding, /* output */ 1, &handler); if (res != XML_ERR_OK) { xmlSaveErr(buf, res, NULL, encoding); return(-1); } if (handler != NULL) { xmlBufPtr newbuf; newbuf = xmlBufCreate(4000 /* MINLEN */); if (newbuf == NULL) { xmlCharEncCloseFunc(handler); xmlSaveErrMemory(buf); return(-1); } buf->conv = buf->buffer; buf->buffer = newbuf; buf->encoder = handler; } ctxt->encoding = (const xmlChar *) encoding; /* * initialize the state, e.g. if outputting a BOM */ xmlCharEncOutput(buf, 1); return(0); } static int xmlSaveClearEncoding(xmlSaveCtxtPtr ctxt) { xmlOutputBufferPtr buf = ctxt->buf; xmlOutputBufferFlush(buf); if (buf->encoder != NULL) { xmlCharEncCloseFunc(buf->encoder); buf->encoder = NULL; xmlBufFree(buf->buffer); buf->buffer = buf->conv; buf->conv = NULL; } ctxt->encoding = NULL; return(0); } #ifdef LIBXML_HTML_ENABLED static void xhtmlNodeDumpOutput(xmlSaveCtxtPtr ctxt, xmlNodePtr cur); #endif static void xmlNodeDumpOutputInternal(xmlSaveCtxtPtr ctxt, xmlNodePtr cur); static int xmlSaveDocInternal(xmlSaveCtxtPtr ctxt, xmlDocPtr cur, const xmlChar *encoding); static void xmlSaveWriteIndent(xmlSaveCtxtPtr ctxt, int extra) { int level; if ((ctxt->options & XML_SAVE_NO_INDENT) || (((ctxt->options & XML_SAVE_INDENT) == 0) && (xmlIndentTreeOutput == 0))) return; level = ctxt->level + extra; if (level > ctxt->indent_nr) level = ctxt->indent_nr; xmlOutputBufferWrite(ctxt->buf, ctxt->indent_size * level, ctxt->indent); } /** * Write out formatting for non-significant whitespace output. * * @param ctxt The save context * @param extra Number of extra indents to apply to ctxt->level */ static void xmlOutputBufferWriteWSNonSig(xmlSaveCtxtPtr ctxt, int extra) { int i; if ((ctxt == NULL) || (ctxt->buf == NULL)) return; xmlOutputBufferWrite(ctxt->buf, 1, "\n"); for (i = 0; i < (ctxt->level + extra); i += ctxt->indent_nr) { xmlOutputBufferWrite(ctxt->buf, ctxt->indent_size * ((ctxt->level + extra - i) > ctxt->indent_nr ? ctxt->indent_nr : (ctxt->level + extra - i)), ctxt->indent); } } /** * Dump a local Namespace definition. * Should be called in the context of attributes dumps. * If `ctxt` is supplied, `buf` should be its buffer. * * @param buf the XML buffer output * @param cur a namespace * @param ctxt the output save context. Optional. */ static void xmlNsDumpOutput(xmlOutputBufferPtr buf, xmlNsPtr cur, xmlSaveCtxtPtr ctxt) { unsigned escapeFlags = XML_ESCAPE_ATTR; if ((cur == NULL) || (buf == NULL)) return; if ((ctxt == NULL) || (ctxt->encoding == NULL)) escapeFlags |= XML_ESCAPE_NON_ASCII; if ((cur->type == XML_LOCAL_NAMESPACE) && (cur->href != NULL)) { if (xmlStrEqual(cur->prefix, BAD_CAST "xml")) return; if (ctxt != NULL && ctxt->format == 2) xmlOutputBufferWriteWSNonSig(ctxt, 2); else xmlOutputBufferWrite(buf, 1, " "); /* Within the context of an element attributes */ if (cur->prefix != NULL) { xmlOutputBufferWrite(buf, 6, "xmlns:"); xmlOutputBufferWriteString(buf, (const char *)cur->prefix); } else xmlOutputBufferWrite(buf, 5, "xmlns"); xmlOutputBufferWrite(buf, 2, "=\""); xmlSerializeText(buf, cur->href, SIZE_MAX, escapeFlags); xmlOutputBufferWrite(buf, 1, "\""); } } /** * Dump a list of local namespace definitions to a save context. * Should be called in the context of attribute dumps. * * @param ctxt the save context * @param cur the first namespace */ static void xmlNsListDumpOutputCtxt(xmlSaveCtxtPtr ctxt, xmlNsPtr cur) { while (cur != NULL) { xmlNsDumpOutput(ctxt->buf, cur, ctxt); cur = cur->next; } } /** * Serialize a list of namespace definitions. * * @param buf the XML buffer output * @param cur the first namespace */ void xmlNsListDumpOutput(xmlOutputBuffer *buf, xmlNs *cur) { while (cur != NULL) { xmlNsDumpOutput(buf, cur, NULL); cur = cur->next; } } /** * Dump the XML document DTD, if any. * * @param ctxt the save context * @param dtd the pointer to the DTD */ static void xmlDtdDumpOutput(xmlSaveCtxtPtr ctxt, xmlDtdPtr dtd) { xmlOutputBufferPtr buf; xmlNodePtr cur; int format, level; if (dtd == NULL) return; if ((ctxt == NULL) || (ctxt->buf == NULL)) return; buf = ctxt->buf; xmlOutputBufferWrite(buf, 10, "name); if (dtd->ExternalID != NULL) { xmlOutputBufferWrite(buf, 8, " PUBLIC "); xmlOutputBufferWriteQuotedString(buf, dtd->ExternalID); xmlOutputBufferWrite(buf, 1, " "); xmlOutputBufferWriteQuotedString(buf, dtd->SystemID); } else if (dtd->SystemID != NULL) { xmlOutputBufferWrite(buf, 8, " SYSTEM "); xmlOutputBufferWriteQuotedString(buf, dtd->SystemID); } if ((dtd->entities == NULL) && (dtd->elements == NULL) && (dtd->attributes == NULL) && (dtd->notations == NULL) && (dtd->pentities == NULL)) { xmlOutputBufferWrite(buf, 1, ">"); return; } xmlOutputBufferWrite(buf, 3, " [\n"); /* * Dump the notations first they are not in the DTD children list * Do this only on a standalone DTD or on the internal subset though. */ if ((dtd->notations != NULL) && ((dtd->doc == NULL) || (dtd->doc->intSubset == dtd))) { xmlBufDumpNotationTable(buf, (xmlNotationTablePtr) dtd->notations); } format = ctxt->format; level = ctxt->level; ctxt->format = 0; ctxt->level = -1; for (cur = dtd->children; cur != NULL; cur = cur->next) { xmlNodeDumpOutputInternal(ctxt, cur); } ctxt->format = format; ctxt->level = level; xmlOutputBufferWrite(buf, 2, "]>"); } /** * Dump an XML attribute * * @param ctxt the save context * @param cur the attribute pointer */ static void xmlAttrDumpOutput(xmlSaveCtxtPtr ctxt, xmlAttrPtr cur) { xmlOutputBufferPtr buf; if (cur == NULL) return; buf = ctxt->buf; if (buf == NULL) return; if (ctxt->format == 2) xmlOutputBufferWriteWSNonSig(ctxt, 2); else xmlOutputBufferWrite(buf, 1, " "); if ((cur->ns != NULL) && (cur->ns->prefix != NULL)) { xmlOutputBufferWriteString(buf, (const char *)cur->ns->prefix); xmlOutputBufferWrite(buf, 1, ":"); } xmlOutputBufferWriteString(buf, (const char *)cur->name); xmlOutputBufferWrite(buf, 2, "=\""); #ifdef LIBXML_HTML_ENABLED if ((ctxt->options & XML_SAVE_XHTML) && (cur->ns == NULL) && ((cur->children == NULL) || (cur->children->content == NULL) || (cur->children->content[0] == 0)) && (htmlIsBooleanAttr(cur->name))) { xmlOutputBufferWriteString(buf, (const char *) cur->name); } else #endif { xmlSaveWriteAttrContent(ctxt, cur); } xmlOutputBufferWrite(buf, 1, "\""); } #ifdef LIBXML_HTML_ENABLED /** * Dump an HTML node, recursive behaviour, children are printed too. * * @param ctxt the save context * @param cur the current node */ static int htmlNodeDumpOutputInternal(xmlSaveCtxtPtr ctxt, xmlNodePtr cur) { int switched_encoding = 0; int format = 0; xmlInitParser(); if (ctxt->encoding == NULL) { const char *encoding = NULL; if (cur->doc != NULL) encoding = (char *) cur->doc->encoding; if (encoding == NULL) encoding = "HTML"; if (xmlSaveSwitchEncoding(ctxt, encoding) < 0) return(-1); switched_encoding = 1; } if (ctxt->options & XML_SAVE_FORMAT) format = 1; htmlNodeDumpInternal(ctxt->buf, cur, (char *) ctxt->encoding, format); if (switched_encoding) { xmlSaveClearEncoding(ctxt); } return(0); } #endif /** * Dump an XML node, recursive behaviour, children are printed too. * * @param ctxt the save context * @param cur the current node */ static void xmlNodeDumpOutputInternal(xmlSaveCtxtPtr ctxt, xmlNodePtr cur) { int format = ctxt->format; xmlNodePtr tmp, root, unformattedNode = NULL, parent; xmlAttrPtr attr; xmlChar *start, *end; xmlOutputBufferPtr buf; if (cur == NULL) return; buf = ctxt->buf; root = cur; parent = cur->parent; while (1) { switch (cur->type) { case XML_DOCUMENT_NODE: case XML_HTML_DOCUMENT_NODE: xmlSaveDocInternal(ctxt, (xmlDocPtr) cur, ctxt->encoding); break; case XML_DTD_NODE: xmlDtdDumpOutput(ctxt, (xmlDtdPtr) cur); break; case XML_DOCUMENT_FRAG_NODE: /* Always validate cur->parent when descending. */ if ((cur->parent == parent) && (cur->children != NULL)) { parent = cur; cur = cur->children; continue; } break; case XML_ELEMENT_DECL: xmlBufDumpElementDecl(buf, (xmlElementPtr) cur); break; case XML_ATTRIBUTE_DECL: xmlSaveWriteAttributeDecl(ctxt, (xmlAttributePtr) cur); break; case XML_ENTITY_DECL: xmlBufDumpEntityDecl(buf, (xmlEntityPtr) cur); break; case XML_ELEMENT_NODE: if ((cur != root) && (ctxt->format == 1)) xmlSaveWriteIndent(ctxt, 0); /* * Some users like lxml are known to pass nodes with a corrupted * tree structure. Fall back to a recursive call to handle this * case. */ if ((cur->parent != parent) && (cur->children != NULL)) { xmlNodeDumpOutputInternal(ctxt, cur); break; } xmlOutputBufferWrite(buf, 1, "<"); if ((cur->ns != NULL) && (cur->ns->prefix != NULL)) { xmlOutputBufferWriteString(buf, (const char *)cur->ns->prefix); xmlOutputBufferWrite(buf, 1, ":"); } xmlOutputBufferWriteString(buf, (const char *)cur->name); if (cur->nsDef) xmlNsListDumpOutputCtxt(ctxt, cur->nsDef); for (attr = cur->properties; attr != NULL; attr = attr->next) xmlAttrDumpOutput(ctxt, attr); if (cur->children == NULL) { if ((ctxt->options & XML_SAVE_NO_EMPTY) == 0) { if (ctxt->format == 2) xmlOutputBufferWriteWSNonSig(ctxt, 0); xmlOutputBufferWrite(buf, 2, "/>"); } else { if (ctxt->format == 2) xmlOutputBufferWriteWSNonSig(ctxt, 1); xmlOutputBufferWrite(buf, 3, ">ns != NULL) && (cur->ns->prefix != NULL)) { xmlOutputBufferWriteString(buf, (const char *)cur->ns->prefix); xmlOutputBufferWrite(buf, 1, ":"); } xmlOutputBufferWriteString(buf, (const char *)cur->name); if (ctxt->format == 2) xmlOutputBufferWriteWSNonSig(ctxt, 0); xmlOutputBufferWrite(buf, 1, ">"); } } else { if (ctxt->format == 1) { tmp = cur->children; while (tmp != NULL) { if ((tmp->type == XML_TEXT_NODE) || (tmp->type == XML_CDATA_SECTION_NODE) || (tmp->type == XML_ENTITY_REF_NODE)) { ctxt->format = 0; unformattedNode = cur; break; } tmp = tmp->next; } } if (ctxt->format == 2) xmlOutputBufferWriteWSNonSig(ctxt, 1); xmlOutputBufferWrite(buf, 1, ">"); if (ctxt->format == 1) xmlOutputBufferWrite(buf, 1, "\n"); if (ctxt->level >= 0) ctxt->level++; parent = cur; cur = cur->children; continue; } break; case XML_TEXT_NODE: if (cur->content == NULL) break; if (cur->name != xmlStringTextNoenc) { if (ctxt->escape) xmlOutputBufferWriteEscape(buf, cur->content, ctxt->escape); #ifdef TEST_OUTPUT_BUFFER_WRITE_ESCAPE else if (ctxt->encoding) xmlOutputBufferWriteEscape(buf, cur->content, NULL); #endif else xmlSaveWriteText(ctxt, cur->content, /* flags */ 0); } else { /* * Disable escaping, needed for XSLT */ xmlOutputBufferWriteString(buf, (const char *) cur->content); } break; case XML_PI_NODE: if ((cur != root) && (ctxt->format == 1)) xmlSaveWriteIndent(ctxt, 0); if (cur->content != NULL) { xmlOutputBufferWrite(buf, 2, "name); if (cur->content != NULL) { if (ctxt->format == 2) xmlOutputBufferWriteWSNonSig(ctxt, 0); else xmlOutputBufferWrite(buf, 1, " "); xmlOutputBufferWriteString(buf, (const char *)cur->content); } xmlOutputBufferWrite(buf, 2, "?>"); } else { xmlOutputBufferWrite(buf, 2, "name); if (ctxt->format == 2) xmlOutputBufferWriteWSNonSig(ctxt, 0); xmlOutputBufferWrite(buf, 2, "?>"); } break; case XML_COMMENT_NODE: if ((cur != root) && (ctxt->format == 1)) xmlSaveWriteIndent(ctxt, 0); if (cur->content != NULL) { xmlOutputBufferWrite(buf, 4, ""); } break; case XML_ENTITY_REF_NODE: xmlOutputBufferWrite(buf, 1, "&"); xmlOutputBufferWriteString(buf, (const char *)cur->name); xmlOutputBufferWrite(buf, 1, ";"); break; case XML_CDATA_SECTION_NODE: if (cur->content == NULL || *cur->content == '\0') { xmlOutputBufferWrite(buf, 12, ""); } else { start = end = cur->content; while (*end != '\0') { if ((*end == ']') && (*(end + 1) == ']') && (*(end + 2) == '>')) { end = end + 2; xmlOutputBufferWrite(buf, 9, ""); start = end; } end++; } if (start != end) { xmlOutputBufferWrite(buf, 9, ""); } } break; case XML_ATTRIBUTE_NODE: xmlAttrDumpOutput(ctxt, (xmlAttrPtr) cur); break; case XML_NAMESPACE_DECL: xmlNsDumpOutput(buf, (xmlNsPtr) cur, ctxt); break; default: break; } while (1) { if (cur == root) return; if ((ctxt->format == 1) && (cur->type != XML_XINCLUDE_START) && (cur->type != XML_XINCLUDE_END)) xmlOutputBufferWrite(buf, 1, "\n"); if (cur->next != NULL) { cur = cur->next; break; } cur = parent; /* cur->parent was validated when descending. */ parent = cur->parent; if (cur->type == XML_ELEMENT_NODE) { if (ctxt->level > 0) ctxt->level--; if (ctxt->format == 1) xmlSaveWriteIndent(ctxt, 0); xmlOutputBufferWrite(buf, 2, "ns != NULL) && (cur->ns->prefix != NULL)) { xmlOutputBufferWriteString(buf, (const char *)cur->ns->prefix); xmlOutputBufferWrite(buf, 1, ":"); } xmlOutputBufferWriteString(buf, (const char *)cur->name); if (ctxt->format == 2) xmlOutputBufferWriteWSNonSig(ctxt, 0); xmlOutputBufferWrite(buf, 1, ">"); if (cur == unformattedNode) { ctxt->format = format; unformattedNode = NULL; } } } } } /** * Dump an XML document. * * @param ctxt the save context * @param cur the document * @param encoding character encoding (optional) */ static int xmlSaveDocInternal(xmlSaveCtxtPtr ctxt, xmlDocPtr cur, const xmlChar *encoding) { #ifdef LIBXML_HTML_ENABLED xmlDtdPtr dtd; int is_xhtml = 0; #endif xmlOutputBufferPtr buf = ctxt->buf; int switched_encoding = 0; xmlInitParser(); if ((cur->type != XML_HTML_DOCUMENT_NODE) && (cur->type != XML_DOCUMENT_NODE)) return(-1); if (encoding == NULL) encoding = cur->encoding; if (((cur->type == XML_HTML_DOCUMENT_NODE) && ((ctxt->options & XML_SAVE_AS_XML) == 0) && ((ctxt->options & XML_SAVE_XHTML) == 0)) || (ctxt->options & XML_SAVE_AS_HTML)) { #ifdef LIBXML_HTML_ENABLED int format = 0; if (ctxt->encoding == NULL) { if (encoding == NULL) encoding = BAD_CAST "HTML"; if (xmlSaveSwitchEncoding(ctxt, (const char*) encoding) < 0) { return(-1); } switched_encoding = 1; } if (ctxt->options & XML_SAVE_FORMAT) format = 1; htmlNodeDumpInternal(buf, (htmlNodePtr) cur, (char *) ctxt->encoding, format); #else return(-1); #endif } else if ((cur->type == XML_DOCUMENT_NODE) || (ctxt->options & XML_SAVE_AS_XML) || (ctxt->options & XML_SAVE_XHTML)) { if ((encoding != NULL) && (ctxt->encoding == NULL)) { if (xmlSaveSwitchEncoding(ctxt, (const char *) encoding) < 0) return(-1); switched_encoding = 1; } /* * Save the XML declaration */ if ((ctxt->options & XML_SAVE_NO_DECL) == 0) { xmlOutputBufferWrite(buf, 15, "version != NULL) xmlOutputBufferWriteString(buf, (char *) cur->version); else xmlOutputBufferWrite(buf, 3, "1.0"); xmlOutputBufferWrite(buf, 1, "\""); if (encoding != NULL) { xmlOutputBufferWrite(buf, 11, " encoding=\""); xmlOutputBufferWriteString(buf, (char *) encoding); xmlOutputBufferWrite(buf, 1, "\""); } switch (cur->standalone) { case 0: xmlOutputBufferWrite(buf, 16, " standalone=\"no\""); break; case 1: xmlOutputBufferWrite(buf, 17, " standalone=\"yes\""); break; } xmlOutputBufferWrite(buf, 3, "?>\n"); } #ifdef LIBXML_HTML_ENABLED if (ctxt->options & XML_SAVE_XHTML) is_xhtml = 1; if ((ctxt->options & XML_SAVE_NO_XHTML) == 0) { dtd = xmlGetIntSubset(cur); if (dtd != NULL) { is_xhtml = xmlIsXHTML(dtd->SystemID, dtd->ExternalID); if (is_xhtml < 0) is_xhtml = 0; } } #endif if (cur->children != NULL) { xmlNodePtr child = cur->children; while (child != NULL) { ctxt->level = 0; #ifdef LIBXML_HTML_ENABLED if (is_xhtml) xhtmlNodeDumpOutput(ctxt, child); else #endif xmlNodeDumpOutputInternal(ctxt, child); if ((child->type != XML_XINCLUDE_START) && (child->type != XML_XINCLUDE_END)) xmlOutputBufferWrite(buf, 1, "\n"); child = child->next; } } } /* * Restore the state of the saving context at the end of the document */ if (switched_encoding) { xmlSaveClearEncoding(ctxt); } return(0); } #ifdef LIBXML_HTML_ENABLED /************************************************************************ * * * Functions specific to XHTML serialization * * * ************************************************************************/ /** * Check if a node is an empty xhtml node * * @param node the node * @returns 1 if the node is an empty node, 0 if not and -1 in case of error */ static int xhtmlIsEmpty(xmlNodePtr node) { if (node == NULL) return(-1); if (node->type != XML_ELEMENT_NODE) return(0); if ((node->ns != NULL) && (!xmlStrEqual(node->ns->href, XHTML_NS_NAME))) return(0); if (node->children != NULL) return(0); switch (node->name ? node->name[0] : 0) { case 'a': if (xmlStrEqual(node->name, BAD_CAST "area")) return(1); return(0); case 'b': if (xmlStrEqual(node->name, BAD_CAST "br")) return(1); if (xmlStrEqual(node->name, BAD_CAST "base")) return(1); if (xmlStrEqual(node->name, BAD_CAST "basefont")) return(1); return(0); case 'c': if (xmlStrEqual(node->name, BAD_CAST "col")) return(1); return(0); case 'f': if (xmlStrEqual(node->name, BAD_CAST "frame")) return(1); return(0); case 'h': if (xmlStrEqual(node->name, BAD_CAST "hr")) return(1); return(0); case 'i': if (xmlStrEqual(node->name, BAD_CAST "img")) return(1); if (xmlStrEqual(node->name, BAD_CAST "input")) return(1); if (xmlStrEqual(node->name, BAD_CAST "isindex")) return(1); return(0); case 'l': if (xmlStrEqual(node->name, BAD_CAST "link")) return(1); return(0); case 'm': if (xmlStrEqual(node->name, BAD_CAST "meta")) return(1); return(0); case 'p': if (xmlStrEqual(node->name, BAD_CAST "param")) return(1); return(0); } return(0); } /** * Dump a list of XML attributes * * @param ctxt the save context * @param cur the first attribute pointer */ static void xhtmlAttrListDumpOutput(xmlSaveCtxtPtr ctxt, xmlAttrPtr cur) { xmlAttrPtr xml_lang = NULL; xmlAttrPtr lang = NULL; xmlAttrPtr name = NULL; xmlAttrPtr id = NULL; xmlNodePtr parent; xmlOutputBufferPtr buf; if (cur == NULL) return; buf = ctxt->buf; parent = cur->parent; while (cur != NULL) { if ((cur->ns == NULL) && (xmlStrEqual(cur->name, BAD_CAST "id"))) id = cur; else if ((cur->ns == NULL) && (xmlStrEqual(cur->name, BAD_CAST "name"))) name = cur; else if ((cur->ns == NULL) && (xmlStrEqual(cur->name, BAD_CAST "lang"))) lang = cur; else if ((cur->ns != NULL) && (xmlStrEqual(cur->name, BAD_CAST "lang")) && (xmlStrEqual(cur->ns->prefix, BAD_CAST "xml"))) xml_lang = cur; xmlAttrDumpOutput(ctxt, cur); cur = cur->next; } /* * C.8 */ if ((name != NULL) && (id == NULL)) { if ((parent != NULL) && (parent->name != NULL) && ((xmlStrEqual(parent->name, BAD_CAST "a")) || (xmlStrEqual(parent->name, BAD_CAST "p")) || (xmlStrEqual(parent->name, BAD_CAST "div")) || (xmlStrEqual(parent->name, BAD_CAST "img")) || (xmlStrEqual(parent->name, BAD_CAST "map")) || (xmlStrEqual(parent->name, BAD_CAST "applet")) || (xmlStrEqual(parent->name, BAD_CAST "form")) || (xmlStrEqual(parent->name, BAD_CAST "frame")) || (xmlStrEqual(parent->name, BAD_CAST "iframe")))) { xmlOutputBufferWrite(buf, 5, " id=\""); xmlSaveWriteAttrContent(ctxt, name); xmlOutputBufferWrite(buf, 1, "\""); } } /* * C.7. */ if ((lang != NULL) && (xml_lang == NULL)) { xmlOutputBufferWrite(buf, 11, " xml:lang=\""); xmlSaveWriteAttrContent(ctxt, lang); xmlOutputBufferWrite(buf, 1, "\""); } else if ((xml_lang != NULL) && (lang == NULL)) { xmlOutputBufferWrite(buf, 7, " lang=\""); xmlSaveWriteAttrContent(ctxt, xml_lang); xmlOutputBufferWrite(buf, 1, "\""); } } /** * Dump an XHTML node, recursive behaviour, children are printed too. * * @param ctxt the save context * @param cur the current node */ static void xhtmlNodeDumpOutput(xmlSaveCtxtPtr ctxt, xmlNodePtr cur) { int format = ctxt->format, addmeta, oldoptions; xmlNodePtr tmp, root, unformattedNode = NULL, parent; xmlChar *start, *end; xmlOutputBufferPtr buf = ctxt->buf; if (cur == NULL) return; oldoptions = ctxt->options; ctxt->options |= XML_SAVE_XHTML; root = cur; parent = cur->parent; while (1) { switch (cur->type) { case XML_DOCUMENT_NODE: case XML_HTML_DOCUMENT_NODE: xmlSaveDocInternal(ctxt, (xmlDocPtr) cur, ctxt->encoding); break; case XML_NAMESPACE_DECL: xmlNsDumpOutput(buf, (xmlNsPtr) cur, ctxt); break; case XML_DTD_NODE: xmlDtdDumpOutput(ctxt, (xmlDtdPtr) cur); break; case XML_DOCUMENT_FRAG_NODE: /* Always validate cur->parent when descending. */ if ((cur->parent == parent) && (cur->children != NULL)) { parent = cur; cur = cur->children; continue; } break; case XML_ELEMENT_DECL: xmlBufDumpElementDecl(buf, (xmlElementPtr) cur); break; case XML_ATTRIBUTE_DECL: xmlSaveWriteAttributeDecl(ctxt, (xmlAttributePtr) cur); break; case XML_ENTITY_DECL: xmlBufDumpEntityDecl(buf, (xmlEntityPtr) cur); break; case XML_ELEMENT_NODE: addmeta = 0; if ((cur != root) && (ctxt->format == 1)) xmlSaveWriteIndent(ctxt, 0); /* * Some users like lxml are known to pass nodes with a corrupted * tree structure. Fall back to a recursive call to handle this * case. */ if ((cur->parent != parent) && (cur->children != NULL)) { xhtmlNodeDumpOutput(ctxt, cur); break; } xmlOutputBufferWrite(buf, 1, "<"); if ((cur->ns != NULL) && (cur->ns->prefix != NULL)) { xmlOutputBufferWriteString(buf, (const char *)cur->ns->prefix); xmlOutputBufferWrite(buf, 1, ":"); } xmlOutputBufferWriteString(buf, (const char *)cur->name); if (cur->nsDef) xmlNsListDumpOutputCtxt(ctxt, cur->nsDef); if ((xmlStrEqual(cur->name, BAD_CAST "html") && (cur->ns == NULL) && (cur->nsDef == NULL))) { /* * 3.1.1. Strictly Conforming Documents A.3.1.1 3/ */ xmlOutputBufferWriteString(buf, " xmlns=\"http://www.w3.org/1999/xhtml\""); } if (cur->properties != NULL) xhtmlAttrListDumpOutput(ctxt, cur->properties); if ((parent != NULL) && (parent->parent == (xmlNodePtr) cur->doc) && xmlStrEqual(cur->name, BAD_CAST"head") && xmlStrEqual(parent->name, BAD_CAST"html")) { tmp = cur->children; while (tmp != NULL) { if (xmlStrEqual(tmp->name, BAD_CAST"meta")) { int res; xmlChar *httpequiv; res = xmlNodeGetAttrValue(tmp, BAD_CAST "http-equiv", NULL, &httpequiv); if (res < 0) { xmlSaveErrMemory(buf); } else if (res == 0) { if (xmlStrcasecmp(httpequiv, BAD_CAST"Content-Type") == 0) { xmlFree(httpequiv); break; } xmlFree(httpequiv); } } tmp = tmp->next; } if (tmp == NULL) addmeta = 1; } if (cur->children == NULL) { if (((cur->ns == NULL) || (cur->ns->prefix == NULL)) && ((xhtmlIsEmpty(cur) == 1) && (addmeta == 0))) { /* * C.2. Empty Elements */ xmlOutputBufferWrite(buf, 3, " />"); } else { if (addmeta == 1) { xmlOutputBufferWrite(buf, 1, ">"); if (ctxt->format == 1) { xmlOutputBufferWrite(buf, 1, "\n"); xmlSaveWriteIndent(ctxt, 1); } xmlOutputBufferWriteString(buf, ""); if (ctxt->format == 1) xmlOutputBufferWrite(buf, 1, "\n"); } else { xmlOutputBufferWrite(buf, 1, ">"); } /* * C.3. Element Minimization and Empty Element Content */ xmlOutputBufferWrite(buf, 2, "ns != NULL) && (cur->ns->prefix != NULL)) { xmlOutputBufferWriteString(buf, (const char *)cur->ns->prefix); xmlOutputBufferWrite(buf, 1, ":"); } xmlOutputBufferWriteString(buf, (const char *)cur->name); xmlOutputBufferWrite(buf, 1, ">"); } } else { xmlOutputBufferWrite(buf, 1, ">"); if (addmeta == 1) { if (ctxt->format == 1) { xmlOutputBufferWrite(buf, 1, "\n"); xmlSaveWriteIndent(ctxt, 1); } xmlOutputBufferWriteString(buf, ""); } if (ctxt->format == 1) { tmp = cur->children; while (tmp != NULL) { if ((tmp->type == XML_TEXT_NODE) || (tmp->type == XML_ENTITY_REF_NODE)) { unformattedNode = cur; ctxt->format = 0; break; } tmp = tmp->next; } } if (ctxt->format == 1) xmlOutputBufferWrite(buf, 1, "\n"); if (ctxt->level >= 0) ctxt->level++; parent = cur; cur = cur->children; continue; } break; case XML_TEXT_NODE: if (cur->content == NULL) break; if ((cur->name == xmlStringText) || (cur->name != xmlStringTextNoenc)) { if (ctxt->escape) xmlOutputBufferWriteEscape(buf, cur->content, ctxt->escape); else xmlSaveWriteText(ctxt, cur->content, /* flags */ 0); } else { /* * Disable escaping, needed for XSLT */ xmlOutputBufferWriteString(buf, (const char *) cur->content); } break; case XML_PI_NODE: if (cur->content != NULL) { xmlOutputBufferWrite(buf, 2, "name); if (cur->content != NULL) { xmlOutputBufferWrite(buf, 1, " "); xmlOutputBufferWriteString(buf, (const char *)cur->content); } xmlOutputBufferWrite(buf, 2, "?>"); } else { xmlOutputBufferWrite(buf, 2, "name); xmlOutputBufferWrite(buf, 2, "?>"); } break; case XML_COMMENT_NODE: if (cur->content != NULL) { xmlOutputBufferWrite(buf, 4, ""); } break; case XML_ENTITY_REF_NODE: xmlOutputBufferWrite(buf, 1, "&"); xmlOutputBufferWriteString(buf, (const char *)cur->name); xmlOutputBufferWrite(buf, 1, ";"); break; case XML_CDATA_SECTION_NODE: if (cur->content == NULL || *cur->content == '\0') { xmlOutputBufferWrite(buf, 12, ""); } else { start = end = cur->content; while (*end != '\0') { if (*end == ']' && *(end + 1) == ']' && *(end + 2) == '>') { end = end + 2; xmlOutputBufferWrite(buf, 9, ""); start = end; } end++; } if (start != end) { xmlOutputBufferWrite(buf, 9, ""); } } break; case XML_ATTRIBUTE_NODE: xmlAttrDumpOutput(ctxt, (xmlAttrPtr) cur); break; default: break; } while (1) { if (cur == root) return; if (ctxt->format == 1) xmlOutputBufferWrite(buf, 1, "\n"); if (cur->next != NULL) { cur = cur->next; break; } cur = parent; /* cur->parent was validated when descending. */ parent = cur->parent; if (cur->type == XML_ELEMENT_NODE) { if (ctxt->level > 0) ctxt->level--; if (ctxt->format == 1) xmlSaveWriteIndent(ctxt, 0); xmlOutputBufferWrite(buf, 2, "ns != NULL) && (cur->ns->prefix != NULL)) { xmlOutputBufferWriteString(buf, (const char *)cur->ns->prefix); xmlOutputBufferWrite(buf, 1, ":"); } xmlOutputBufferWriteString(buf, (const char *)cur->name); xmlOutputBufferWrite(buf, 1, ">"); if (cur == unformattedNode) { ctxt->format = format; unformattedNode = NULL; } } } } ctxt->options = oldoptions; } #endif /************************************************************************ * * * Public entry points * * * ************************************************************************/ /** * Create a document saving context serializing to a file descriptor * with the encoding and the options given. * * If `encoding` is NULL, #xmlSaveDoc uses the document's * encoding and #xmlSaveTree uses UTF-8. * * This function doesn't allow to distinguish unsupported * encoding errors from failed memory allocations. * * @param fd a file descriptor number * @param encoding the encoding name to use (optional) * @param options a set of xmlSaveOptions * @returns a new serialization context or NULL in case of error. */ xmlSaveCtxt * xmlSaveToFd(int fd, const char *encoding, int options) { xmlSaveCtxtPtr ret; ret = xmlNewSaveCtxt(encoding, options); if (ret == NULL) return(NULL); ret->buf = xmlOutputBufferCreateFd(fd, ret->handler); if (ret->buf == NULL) { xmlCharEncCloseFunc(ret->handler); xmlFreeSaveCtxt(ret); return(NULL); } return(ret); } /** * Create a document saving context serializing to a filename * with the encoding and the options given. * * If `encoding` is NULL, #xmlSaveDoc uses the document's * encoding and #xmlSaveTree uses UTF-8. * * This function doesn't allow to distinguish unsupported * encoding errors from failed memory allocations. * * @param filename a file name or an URL * @param encoding the encoding name to use or NULL * @param options a set of xmlSaveOptions * @returns a new serialization context or NULL in case of error. */ xmlSaveCtxt * xmlSaveToFilename(const char *filename, const char *encoding, int options) { xmlSaveCtxtPtr ret; int compression = 0; /* TODO handle compression option */ ret = xmlNewSaveCtxt(encoding, options); if (ret == NULL) return(NULL); ret->buf = xmlOutputBufferCreateFilename(filename, ret->handler, compression); if (ret->buf == NULL) { xmlCharEncCloseFunc(ret->handler); xmlFreeSaveCtxt(ret); return(NULL); } return(ret); } /** * Create a document saving context serializing to a buffer * with the encoding and the options given. * * If `encoding` is NULL, #xmlSaveDoc uses the document's * encoding and #xmlSaveTree uses UTF-8. * * This function doesn't allow to distinguish unsupported * encoding errors from failed memory allocations. * * @param buffer a buffer * @param encoding the encoding name to use or NULL * @param options a set of xmlSaveOptions * @returns a new serialization context or NULL in case of error. */ xmlSaveCtxt * xmlSaveToBuffer(xmlBuffer *buffer, const char *encoding, int options) { xmlSaveCtxtPtr ret; ret = xmlNewSaveCtxt(encoding, options); if (ret == NULL) return(NULL); ret->buf = xmlOutputBufferCreateBuffer(buffer, ret->handler); if (ret->buf == NULL) { xmlCharEncCloseFunc(ret->handler); xmlFreeSaveCtxt(ret); return(NULL); } return(ret); } /** * Create a document saving context serializing to a file descriptor * with the encoding and the options given * * If `encoding` is NULL, #xmlSaveDoc uses the document's * encoding and #xmlSaveTree uses UTF-8. * * This function doesn't allow to distinguish unsupported * encoding errors from failed memory allocations. * * @param iowrite an I/O write function * @param ioclose an I/O close function * @param ioctx an I/O handler * @param encoding the encoding name to use or NULL * @param options a set of xmlSaveOptions * @returns a new serialization context or NULL in case of error. */ xmlSaveCtxt * xmlSaveToIO(xmlOutputWriteCallback iowrite, xmlOutputCloseCallback ioclose, void *ioctx, const char *encoding, int options) { xmlSaveCtxtPtr ret; ret = xmlNewSaveCtxt(encoding, options); if (ret == NULL) return(NULL); ret->buf = xmlOutputBufferCreateIO(iowrite, ioclose, ioctx, ret->handler); if (ret->buf == NULL) { xmlCharEncCloseFunc(ret->handler); xmlFreeSaveCtxt(ret); return(NULL); } return(ret); } /** * Serialize a document. * * If the save context has no encoding, uses the document's * encoding. If the document has no encoding, uses ASCII * without an encoding declaration. * * @param ctxt a document saving context * @param doc a document * @returns 0 on success or -1 in case of error. */ long xmlSaveDoc(xmlSaveCtxt *ctxt, xmlDoc *doc) { long ret = 0; if ((ctxt == NULL) || (doc == NULL)) return(-1); if (xmlSaveDocInternal(ctxt, doc, ctxt->encoding) < 0) return(-1); return(ret); } /** * Serialize a subtree starting. * * If the save context has no encoding, uses UTF-8. * * @param ctxt a document saving context * @param cur the root of the subtree to save * @returns 0 on success or -1 in case of error. */ long xmlSaveTree(xmlSaveCtxt *ctxt, xmlNode *cur) { long ret = 0; if ((ctxt == NULL) || (cur == NULL)) return(-1); #ifdef LIBXML_HTML_ENABLED if (ctxt->options & XML_SAVE_XHTML) { xhtmlNodeDumpOutput(ctxt, cur); return(ret); } if (((cur->type != XML_NAMESPACE_DECL) && (cur->doc != NULL) && (cur->doc->type == XML_HTML_DOCUMENT_NODE) && ((ctxt->options & XML_SAVE_AS_XML) == 0)) || (ctxt->options & XML_SAVE_AS_HTML)) { htmlNodeDumpOutputInternal(ctxt, cur); return(ret); } #endif xmlNodeDumpOutputInternal(ctxt, cur); return(ret); } /** * Serialize a notation declaration. * * @param ctxt save context * @param cur notation * @returns 0 on succes, -1 on error. */ int xmlSaveNotationDecl(xmlSaveCtxt *ctxt, xmlNotation *cur) { if (ctxt == NULL) return(-1); xmlBufDumpNotationDecl(ctxt->buf, cur); return(0); } /** * Serialize notation declarations of a document. * * @param ctxt save context * @param cur notation table * @returns 0 on succes, -1 on error. */ int xmlSaveNotationTable(xmlSaveCtxt *ctxt, xmlNotationTable *cur) { if (ctxt == NULL) return(-1); xmlBufDumpNotationTable(ctxt->buf, cur); return(0); } /** * Flush a document saving context, i.e. make sure that all * buffered input has been processed. * * @param ctxt a document saving context * @returns the number of bytes written or -1 in case of error. */ int xmlSaveFlush(xmlSaveCtxt *ctxt) { if (ctxt == NULL) return(-1); if (ctxt->buf == NULL) return(-1); return(xmlOutputBufferFlush(ctxt->buf)); } /** * Close a document saving context, i.e. make sure that all * buffered input has been processed and free the context struct. * * @param ctxt a document saving context * @returns the number of bytes written or -1 in case of error. */ int xmlSaveClose(xmlSaveCtxt *ctxt) { int ret; if (ctxt == NULL) return(-1); ret = xmlSaveFlush(ctxt); xmlFreeSaveCtxt(ctxt); return(ret); } /** * Close a document saving context, i.e. make sure that all * buffered input has been processed and free the context struct. * * @since 2.13.0 * * @param ctxt a document saving context * @returns an xmlParserErrors code. */ xmlParserErrors xmlSaveFinish(xmlSaveCtxt *ctxt) { int ret; if (ctxt == NULL) return(XML_ERR_INTERNAL_ERROR); ret = xmlOutputBufferClose(ctxt->buf); ctxt->buf = NULL; if (ret < 0) ret = -ret; else ret = XML_ERR_OK; xmlFreeSaveCtxt(ctxt); return(ret); } /** * Set a custom escaping function to be used for text in element * content. * * @deprecated Don't use. * * @param ctxt a document saving context * @param escape the escaping function * @returns 0 if successful or -1 in case of error. */ int xmlSaveSetEscape(xmlSaveCtxt *ctxt, xmlCharEncodingOutputFunc escape) { if (ctxt == NULL) return(-1); ctxt->escape = escape; return(0); } /** * Has no effect. * * @deprecated Don't use. * * @param ctxt a document saving context * @param escape the escaping function * @returns 0 if successful or -1 in case of error. */ int xmlSaveSetAttrEscape(xmlSaveCtxt *ctxt, xmlCharEncodingOutputFunc escape ATTRIBUTE_UNUSED) { if (ctxt == NULL) return(-1); return(0); } /************************************************************************ * * * Public entry points based on buffers * * * ************************************************************************/ /** * Serialize attribute text to an output buffer. * * @param buf output buffer * @param doc the document * @param string the text content */ void xmlBufAttrSerializeTxtContent(xmlOutputBuffer *buf, xmlDoc *doc, const xmlChar *string) { int flags = XML_ESCAPE_ATTR; if ((doc == NULL) || (doc->encoding == NULL)) flags |= XML_ESCAPE_NON_ASCII; xmlSerializeText(buf, string, SIZE_MAX, flags); } /** * Serialize attribute text to an xmlBuffer. * * @param buf the XML buffer output * @param doc the document * @param attr the attribute node * @param string the text content */ void xmlAttrSerializeTxtContent(xmlBuffer *buf, xmlDoc *doc, xmlAttr *attr ATTRIBUTE_UNUSED, const xmlChar *string) { xmlOutputBufferPtr out; if ((buf == NULL) || (string == NULL)) return; out = xmlOutputBufferCreateBuffer(buf, NULL); xmlBufAttrSerializeTxtContent(out, doc, string); xmlOutputBufferFlush(out); if ((out == NULL) || (out->error)) xmlFree(xmlBufferDetach(buf)); xmlOutputBufferClose(out); } /** * Serialize an XML node to an xmlBuffer. * * Uses the document's encoding. If the document has no encoding, * uses ASCII without an encoding declaration. * * Note that `format` only works if the document was parsed with * XML_PARSE_NOBLANKS. * * Since this is using xmlBuffer structures it is limited to 2GB and * somewhat deprecated, use #xmlNodeDumpOutput instead. * * @param buf the XML buffer output * @param doc the document * @param cur the current node * @param level the initial indenting level * @param format is formatting allowed * @returns the number of bytes written to the buffer or -1 in case of error */ int xmlNodeDump(xmlBuffer *buf, xmlDoc *doc, xmlNode *cur, int level, int format) { xmlBufPtr buffer; size_t ret1; int ret2; if ((buf == NULL) || (cur == NULL)) return(-1); if (level < 0) level = 0; else if (level > 100) level = 100; buffer = xmlBufFromBuffer(buf); if (buffer == NULL) return(-1); ret1 = xmlBufNodeDump(buffer, doc, cur, level, format); ret2 = xmlBufBackToBuffer(buffer, buf); if ((ret1 == (size_t) -1) || (ret2 < 0)) return(-1); return(ret1 > INT_MAX ? INT_MAX : ret1); } /** * Serialize an XML node to an xmlBuf. * * Uses the document's encoding. If the document has no encoding, * uses ASCII without an encoding declaration. * * Note that `format` only works if the document was parsed with * XML_PARSE_NOBLANKS. * * @param buf the XML buffer output * @param doc the document * @param cur the current node * @param level the imbrication level for indenting * @param format is formatting allowed * @returns the number of bytes written to the buffer, in case of error 0 * is returned or `buf` stores the error */ size_t xmlBufNodeDump(xmlBuf *buf, xmlDoc *doc, xmlNode *cur, int level, int format) { size_t use; size_t ret; xmlOutputBufferPtr outbuf; xmlInitParser(); if (cur == NULL) { return ((size_t) -1); } if (buf == NULL) { return ((size_t) -1); } outbuf = (xmlOutputBufferPtr) xmlMalloc(sizeof(xmlOutputBuffer)); if (outbuf == NULL) { xmlSaveErrMemory(NULL); return ((size_t) -1); } memset(outbuf, 0, (size_t) sizeof(xmlOutputBuffer)); outbuf->buffer = buf; outbuf->encoder = NULL; outbuf->writecallback = NULL; outbuf->closecallback = NULL; outbuf->context = NULL; outbuf->written = 0; use = xmlBufUse(buf); xmlNodeDumpOutput(outbuf, doc, cur, level, format, NULL); if (outbuf->error) ret = (size_t) -1; else ret = xmlBufUse(buf) - use; xmlFree(outbuf); return (ret); } /** * Serialize an XML node to a `FILE`. * * Uses the document's encoding. If the document has no encoding, * uses ASCII without an encoding declaration. * * @param f the FILE * for the output * @param doc the document * @param cur the current node */ void xmlElemDump(FILE * f, xmlDoc *doc, xmlNode *cur) { xmlOutputBufferPtr outbuf; xmlInitParser(); if (cur == NULL) { return; } outbuf = xmlOutputBufferCreateFile(f, NULL); if (outbuf == NULL) return; #ifdef LIBXML_HTML_ENABLED if ((doc != NULL) && (doc->type == XML_HTML_DOCUMENT_NODE)) htmlNodeDumpOutput(outbuf, doc, cur, NULL); else #endif /* LIBXML_HTML_ENABLED */ xmlNodeDumpOutput(outbuf, doc, cur, 0, 1, NULL); xmlOutputBufferClose(outbuf); } /************************************************************************ * * * Saving functions front-ends * * * ************************************************************************/ /** * Serialize an XML node to an output buffer. * * If `encoding` is NULL, uses the document's encoding. If the * document has no encoding, serializes as ASCII without an * encoding declaration. * * Note that `format` only works if the document was parsed with * XML_PARSE_NOBLANKS. * * @param buf the XML buffer output * @param doc the document * @param cur the current node * @param level the imbrication level for indenting * @param format is formatting allowed * @param encoding an optional encoding string */ void xmlNodeDumpOutput(xmlOutputBuffer *buf, xmlDoc *doc, xmlNode *cur, int level, int format, const char *encoding) { xmlSaveCtxt ctxt; int options; #ifdef LIBXML_HTML_ENABLED xmlDtdPtr dtd; int is_xhtml = 0; #endif (void) doc; xmlInitParser(); if ((buf == NULL) || (cur == NULL)) return; if (level < 0) level = 0; else if (level > 100) level = 100; if (encoding == NULL) encoding = "UTF-8"; memset(&ctxt, 0, sizeof(ctxt)); ctxt.buf = buf; ctxt.level = level; ctxt.encoding = (const xmlChar *) encoding; options = XML_SAVE_AS_XML; if (format) options |= XML_SAVE_FORMAT; xmlSaveCtxtInit(&ctxt, options); #ifdef LIBXML_HTML_ENABLED dtd = xmlGetIntSubset(doc); if (dtd != NULL) { is_xhtml = xmlIsXHTML(dtd->SystemID, dtd->ExternalID); if (is_xhtml < 0) is_xhtml = 0; } if (is_xhtml) xhtmlNodeDumpOutput(&ctxt, cur); else #endif xmlNodeDumpOutputInternal(&ctxt, cur); } static void xmlDocDumpInternal(xmlOutputBufferPtr buf, xmlDocPtr doc, const char *encoding, int format) { xmlSaveCtxt ctxt; int options; memset(&ctxt, 0, sizeof(ctxt)); ctxt.buf = buf; if (buf->encoder != NULL) { /* * Keep original encoding */ encoding = buf->encoder->name; ctxt.encoding = BAD_CAST encoding; } options = XML_SAVE_AS_XML; if (format) options |= XML_SAVE_FORMAT; xmlSaveCtxtInit(&ctxt, options); xmlSaveDocInternal(&ctxt, doc, (const xmlChar *) encoding); } /** * Serialize an XML document to memory. * * If `encoding` is NULL, uses the document's encoding. If the * document has no encoding, serializes as ASCII without an * encoding declaration. * * It is up to the caller of this function to free the returned * memory with #xmlFree. * * Note that `format` only works if the document was parsed with * XML_PARSE_NOBLANKS. * * @param out_doc Document to generate XML text from * @param doc_txt_ptr Memory pointer for allocated XML text * @param doc_txt_len Length of the generated XML text * @param txt_encoding Character encoding to use when generating XML text * @param format should formatting spaces been added */ void xmlDocDumpFormatMemoryEnc(xmlDoc *out_doc, xmlChar **doc_txt_ptr, int * doc_txt_len, const char * txt_encoding, int format) { xmlOutputBufferPtr buf = NULL; if (doc_txt_len != NULL) *doc_txt_len = 0; if (doc_txt_ptr == NULL) return; *doc_txt_ptr = NULL; if (out_doc == NULL) return; buf = xmlAllocOutputBuffer(NULL); if (buf == NULL) { xmlSaveErrMemory(NULL); return; } xmlDocDumpInternal(buf, out_doc, txt_encoding, format); xmlOutputBufferFlush(buf); if (!buf->error) { if (doc_txt_len != NULL) *doc_txt_len = xmlBufUse(buf->buffer); *doc_txt_ptr = xmlBufDetach(buf->buffer); } xmlOutputBufferClose(buf); } /** * Same as #xmlDocDumpFormatMemoryEnc with `encoding` set to * NULL and `format` set to 0. * * @param cur the document * @param mem OUT: the memory pointer * @param size OUT: the memory length */ void xmlDocDumpMemory(xmlDoc *cur, xmlChar**mem, int *size) { xmlDocDumpFormatMemoryEnc(cur, mem, size, NULL, 0); } /** * Same as #xmlDocDumpFormatMemoryEnc with `encoding` set to * NULL. * * @param cur the document * @param mem OUT: the memory pointer * @param size OUT: the memory length * @param format should formatting spaces been added */ void xmlDocDumpFormatMemory(xmlDoc *cur, xmlChar**mem, int *size, int format) { xmlDocDumpFormatMemoryEnc(cur, mem, size, NULL, format); } /** * Same as #xmlDocDumpFormatMemoryEnc with `format` set to 0. * * @param out_doc Document to generate XML text from * @param doc_txt_ptr Memory pointer for allocated XML text * @param doc_txt_len Length of the generated XML text * @param txt_encoding Character encoding to use when generating XML text */ void xmlDocDumpMemoryEnc(xmlDoc *out_doc, xmlChar **doc_txt_ptr, int * doc_txt_len, const char * txt_encoding) { xmlDocDumpFormatMemoryEnc(out_doc, doc_txt_ptr, doc_txt_len, txt_encoding, 0); } /** * Serialize an XML document to a `FILE`. * * Uses the document's encoding. If the document has no encoding, * uses ASCII without an encoding declaration. * * Note that `format` only works if the document was parsed with * XML_PARSE_NOBLANKS. * * @param f the FILE* * @param cur the document * @param format should formatting spaces been added * @returns the number of bytes written or -1 in case of failure. */ int xmlDocFormatDump(FILE *f, xmlDoc *cur, int format) { xmlOutputBufferPtr buf; if (cur == NULL) { return(-1); } buf = xmlOutputBufferCreateFile(f, NULL); if (buf == NULL) return(-1); xmlDocDumpInternal(buf, cur, NULL, format); return(xmlOutputBufferClose(buf)); } /** * Serialize an XML document to a `FILE`. * * Uses the document's encoding. If the document has no encoding, * uses ASCII without an encoding declaration. * * @param f the FILE* * @param cur the document * @returns the number of bytes written or -1 in case of failure. */ int xmlDocDump(FILE *f, xmlDoc *cur) { return(xmlDocFormatDump (f, cur, 0)); } /** * Same as #xmlSaveFormatFileTo with `format` set to 0. * * WARNING: This calls #xmlOutputBufferClose and frees `buf`. * * @param buf an output I/O buffer * @param cur the document * @param encoding the encoding if any assuming the I/O layer handles the transcoding * @returns the number of bytes written or -1 in case of failure. */ int xmlSaveFileTo(xmlOutputBuffer *buf, xmlDoc *cur, const char *encoding) { return(xmlSaveFormatFileTo(buf, cur, encoding, 0)); } /** * Serialize an XML document to an output buffer. * * If the output buffer already uses a (non-default) encoding, * `encoding` is ignored. If the output buffer has no encoding * and `encoding` is NULL, uses the document's encoding or * ASCII without an encoding declaration. * * Note that `format` only works if the document was parsed with * XML_PARSE_NOBLANKS. * * WARNING: This calls #xmlOutputBufferClose and frees `buf`. * * @param buf an output I/O buffer * @param cur the document * @param encoding the encoding if any assuming the I/O layer handles the transcoding * @param format should formatting spaces been added * @returns the number of bytes written or -1 in case of failure. */ int xmlSaveFormatFileTo(xmlOutputBuffer *buf, xmlDoc *cur, const char *encoding, int format) { if (buf == NULL) return(-1); if ((cur == NULL) || ((cur->type != XML_DOCUMENT_NODE) && (cur->type != XML_HTML_DOCUMENT_NODE))) { xmlOutputBufferClose(buf); return(-1); } xmlDocDumpInternal(buf, cur, encoding, format); return(xmlOutputBufferClose(buf)); } /** * Serialize an XML document to a file using the given encoding. * If `filename` is `"-"`, stdout is used. This is potentially * insecure and might be changed in a future version. * * If `encoding` is NULL, uses the document's encoding. If the * document has no encoding, serializes as ASCII without an * encoding declaration. * * Note that `format` only works if the document was parsed with * XML_PARSE_NOBLANKS. * * @param filename the filename or URL to output * @param cur the document being saved * @param encoding the name of the encoding to use or NULL. * @param format should formatting spaces be added. * @returns the number of bytes written or -1 in case of error. */ int xmlSaveFormatFileEnc( const char * filename, xmlDoc *cur, const char * encoding, int format ) { xmlOutputBufferPtr buf; if (cur == NULL) return(-1); #ifdef LIBXML_ZLIB_ENABLED if (cur->compression < 0) cur->compression = xmlGetCompressMode(); #endif /* * save the content to a temp buffer. */ buf = xmlOutputBufferCreateFilename(filename, NULL, cur->compression); if (buf == NULL) return(-1); xmlDocDumpInternal(buf, cur, encoding, format); return(xmlOutputBufferClose(buf)); } /** * Same as #xmlSaveFormatFileEnc with `format` set to 0. * * @param filename the filename (or URL) * @param cur the document * @param encoding the name of an encoding (or NULL) * @returns the number of bytes written or -1 in case of failure. */ int xmlSaveFileEnc(const char *filename, xmlDoc *cur, const char *encoding) { return ( xmlSaveFormatFileEnc( filename, cur, encoding, 0 ) ); } /** * Same as #xmlSaveFormatFileEnc with `encoding` set to NULL. * * @param filename the filename (or URL) * @param cur the document * @param format should formatting spaces been added * @returns the number of bytes written or -1 in case of failure. */ int xmlSaveFormatFile(const char *filename, xmlDoc *cur, int format) { return ( xmlSaveFormatFileEnc( filename, cur, NULL, format ) ); } /** * Same as #xmlSaveFormatFileEnc with `encoding` set to NULL * and `format` set to 0. * * @param filename the filename (or URL) * @param cur the document * @returns the number of bytes written or -1 in case of failure. */ int xmlSaveFile(const char *filename, xmlDoc *cur) { return(xmlSaveFormatFileEnc(filename, cur, NULL, 0)); } #endif /* LIBXML_OUTPUT_ENABLED */