/
vsysoev
/
libxml2
Обзор
Документация
Войти
/
vsysoev
/
libxml2
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
HTMLtree.c
1 314 строк
38 KB
Nick Wellnhofer
doc: Fix autolink errors
28 май 2025, 17:02
28 май 2025, 17:02
6a6a46f
Код
Авторство
О чём код?
/* * HTMLtree.c : implementation of access function for an HTML tree. * * See Copyright for the status of this software. * * Author: Daniel Veillard */ #define IN_LIBXML #include "libxml.h" #ifdef LIBXML_HTML_ENABLED #include <string.h> /* for memset() only ! */ #include <ctype.h> #include <stdlib.h> #include <libxml/xmlmemory.h> #include <libxml/HTMLparser.h> #include <libxml/HTMLtree.h> #include <libxml/entities.h> #include <libxml/xmlerror.h> #include <libxml/parserInternals.h> #include <libxml/uri.h> #include "private/buf.h" #include "private/html.h" #include "private/error.h" #include "private/html.h" #include "private/io.h" #include "private/save.h" #include "private/tree.h" /************************************************************************ * * * Getting/Setting encoding meta tags * * * ************************************************************************/ typedef struct { xmlAttrPtr attr; /* charset or content */ const xmlChar *attrValue; htmlMetaEncodingOffsets off; } htmlMetaEncoding; static htmlNodePtr htmlFindFirstChild(htmlNodePtr parent, const char *name) { htmlNodePtr child; for (child = parent->children; child != NULL; child = child->next) { if ((child->type == XML_ELEMENT_NODE) && (xmlStrcasecmp(child->name, BAD_CAST name) == 0)) return(child); } return(NULL); } static htmlNodePtr htmlFindHead(htmlDocPtr doc) { htmlNodePtr html; if (doc == NULL) return(NULL); html = htmlFindFirstChild((htmlNodePtr) doc, "html"); if (html == NULL) return(NULL); return(htmlFindFirstChild(html, "head")); } int htmlParseContentType(const xmlChar *val, htmlMetaEncodingOffsets *off) { const xmlChar *p = val; while (1) { size_t start, end; while ((*p != 'c') && (*p != 'C')) { if (*p == 0) return(0); p += 1; } p += 1; if (xmlStrncasecmp(p, BAD_CAST "harset", 6) != 0) continue; p += 6; while (IS_WS_HTML(*p)) p += 1; if (*p != '=') continue; p += 1; while (IS_WS_HTML(*p)) p += 1; if (*p == 0) return(0); if ((*p == '"') || (*p == '\'')) { int quote = *p; p += 1; while (IS_WS_HTML(*p)) p += 1; start = p - val; end = start; while (*p != quote) { if (*p == 0) return(0); if (!IS_WS_HTML(*p)) end = p + 1 - val; p += 1; } } else { start = p - val; while ((*p != 0) && (*p != ';') && (!IS_WS_HTML(*p))) p += 1; end = p - val; } off->start = start; off->end = end; off->size = p - val + strlen((char *) p); return(1); } return(0); } static xmlAttrPtr htmlFindMetaEncodingAttr(htmlNodePtr elem, int *outIsContentType) { xmlAttrPtr attr, contentAttr = NULL; int isContentType = 0; if (xmlStrcasecmp(elem->name, BAD_CAST "meta") != 0) return(NULL); for (attr = elem->properties; attr != NULL; attr = attr->next) { if (attr->ns != NULL) continue; if (xmlStrcasecmp(attr->name, BAD_CAST "charset") == 0) { *outIsContentType = 0; return(attr); } if (xmlStrcasecmp(attr->name, BAD_CAST "content") == 0) contentAttr = attr; if ((xmlStrcasecmp(attr->name, BAD_CAST "http-equiv") == 0) && (attr->children != NULL) && (attr->children->type == XML_TEXT_NODE) && (attr->children->next == NULL) && (xmlStrcasecmp(attr->children->content, BAD_CAST "Content-Type") == 0)) isContentType = 1; } if ((isContentType) && (contentAttr != NULL)) { *outIsContentType = 1; return(contentAttr); } return(NULL); } static int htmlParseMetaEncoding(htmlNodePtr elem, htmlMetaEncoding *menc) { xmlAttrPtr attr; const xmlChar *val = NULL; int isContentType; if ((elem->type != XML_ELEMENT_NODE) || (xmlStrcasecmp(elem->name, BAD_CAST "meta") != 0)) return(0); attr = htmlFindMetaEncodingAttr(elem, &isContentType); if (attr == NULL) return(0); if ((attr->children != NULL) && (attr->children->type == XML_TEXT_NODE) && (attr->children->next == NULL) && (attr->children->content != NULL)) val = attr->children->content; else val = BAD_CAST ""; if (!isContentType) { size_t size = strlen((char *) val); size_t start = 0; size_t end = size; while ((start < size) && (IS_WS_HTML(val[start]))) start += 1; while ((end > 0) && (IS_WS_HTML(val[end-1]))) end -= 1; menc->attr = attr; menc->attrValue = val; menc->off.start = start; menc->off.end = end; menc->off.size = size; return(1); } else { if (htmlParseContentType(val, &menc->off)) { menc->attr = attr; menc->attrValue = val; return(1); } } return(0); } static xmlChar * htmlUpdateMetaEncoding(htmlMetaEncoding *menc, const char *encoding) { xmlChar *newVal, *p; size_t size, oldEncSize, newEncSize; /* * The pseudo "HTML" encoding only produces ASCII. */ if (xmlStrcasecmp(BAD_CAST encoding, BAD_CAST "HTML") == 0) encoding = "ASCII"; oldEncSize = menc->off.end - menc->off.start; newEncSize = strlen((char *) encoding); size = menc->off.size - oldEncSize + newEncSize; newVal = xmlMalloc(size + 1); if (newVal == NULL) return(NULL); p = newVal; memcpy(p, menc->attrValue, menc->off.start); p += menc->off.start; memcpy(p, encoding, newEncSize); p += newEncSize; memcpy(p, menc->attrValue + menc->off.end, menc->off.size - menc->off.end); newVal[size] = 0; return(newVal); } /** * Look up and encoding declaration in the meta tags. * * The returned string points into attribute content and can contain * trailing garbage. It should be copied before modifying or freeing * nodes. * * @param doc the document * @returns the encoding ot NULL if not found. */ const xmlChar * htmlGetMetaEncoding(xmlDoc *doc) { htmlNodePtr head, node; head = htmlFindHead(doc); if (head == NULL) return(NULL); for (node = head->children; node != NULL; node = node->next) { htmlMetaEncoding menc; if (htmlParseMetaEncoding(node, &menc)) { /* * Returning a `const xmlChar *` only allows to return * a suffix. In http-equiv meta tags, there could be * more data after the charset, although it's probably * rare in practice. */ return(menc.attrValue + menc.off.start); } } return(NULL); } /** * Creates or updates a meta tag with an encoding declaration. * * NOTE: This will not change the document content encoding. * * @param doc the document * @param encoding the encoding string * @returns 0 in case of success, 1 if no head element was found or * arguments are invalid and -1 if memory allocation failed. */ int htmlSetMetaEncoding(xmlDoc *doc, const xmlChar *encoding) { htmlNodePtr head, meta; int found = 0; if (encoding == NULL) return(1); head = htmlFindHead(doc); if (head == NULL) return(1); for (meta = head->children; meta != NULL; meta = meta->next) { htmlMetaEncoding menc; if (htmlParseMetaEncoding(meta, &menc)) { xmlChar *newVal; int ret; found = 1; newVal = htmlUpdateMetaEncoding(&menc, (char *) encoding); if (newVal == NULL) return(-1); xmlNodeSetContent((xmlNodePtr) menc.attr, NULL); ret = xmlNodeAddContent((xmlNodePtr) menc.attr, newVal); xmlFree(newVal); if (ret < 0) return(-1); } } if (found) return(0); meta = xmlNewDocNode(head->doc, NULL, BAD_CAST "meta", NULL); if (meta == NULL) return(-1); if (xmlNewProp(meta, BAD_CAST "charset", encoding) == NULL) { xmlFreeNode(meta); return(-1); } if (head->children == NULL) xmlAddChild(head, meta); else xmlAddPrevSibling(head->children, meta); return(0); } /** * Determine if a given attribute is a boolean attribute. This * doesn't handle HTML5. * * @deprecated Internal function, don't use. * * @param name the name of the attribute to check * @returns false if the attribute is not boolean, true otherwise. */ int htmlIsBooleanAttr(const xmlChar *name) { const char *str = NULL; if (name == NULL) return(0); /* * These are the HTML attributes which will be output * in minimized form, i.e. `<option selected="selected">` will be * output as `<option selected>`, as per XSLT 1.0 16.2 "HTML Output * Method": * * "checked", "compact", "declare", "defer", "disabled", "ismap", * "multiple", "nohref", "noresize", "noshade", "nowrap", "readonly", * "selected" * * Additional attributes from HTML5 (not implemented yet): * * "allowfullscreen", "alpha", "async", "autofocus", "autoplay", * "controls", "default", "formnovalidate", "inert", "itemscope", * "loop", "muted", "nomodule", "novalidate", "open", "playsinline", * "required", "reversed", "shadowrootdelegatesfocus", * "shadowrootclonable", "shadowrootserializable", * "shadowrootcustomelementregistry", "truespeed" */ switch (name[0] | 0x20) { case 'c': name += 1; switch (name[0] | 0x20) { case 'h': str = "ecked"; break; case 'o': str = "mpact"; break; } break; case 'd': name += 1; switch (name[0] | 0x20) { case 'e': name += 1; switch (name[0] | 0x20) { case 'c': str = "lare"; break; case 'f': str = "er"; break; } break; case 'i': str = "sabled"; break; } break; case 'i': str = "smap"; break; case 'm': str = "ultiple"; break; case 'n': name += 1; if ((name[0] | 0x20) != 'o') break; name += 1; switch (name[0] | 0x20) { case 'h': str = "ref"; break; case 'r': str = "esize"; break; case 's': str = "hade"; break; case 'w': str = "rap"; break; } break; case 'r': str = "eadonly"; break; case 's': str = "elected"; break; } if (str == NULL) return(0); return(xmlStrcasecmp(name + 1, BAD_CAST str) == 0); } #ifdef LIBXML_OUTPUT_ENABLED /************************************************************************ * * * Dumping HTML tree content to a simple buffer * * * ************************************************************************/ static xmlParserErrors htmlFindOutputEncoder(const char *encoding, xmlCharEncodingHandler **out) { /* * Fallback to HTML if the encoding is unspecified */ if (encoding == NULL) encoding = "HTML"; return(xmlOpenCharEncodingHandler(encoding, /* output */ 1, out)); } /** * Serialize an HTML document to an xmlBuf. * * @param buf the xmlBuf output * @param doc the document (unused) * @param cur the current node * @param format should formatting newlines been added * @returns the number of bytes written or -1 in case of error */ static size_t htmlBufNodeDumpFormat(xmlBufPtr buf, xmlDocPtr doc ATTRIBUTE_UNUSED, xmlNodePtr cur, int format) { size_t use; size_t ret; xmlOutputBufferPtr outbuf; if (cur == NULL) { return ((size_t) -1); } if (buf == NULL) { return ((size_t) -1); } outbuf = (xmlOutputBufferPtr) xmlMalloc(sizeof(xmlOutputBuffer)); if (outbuf == NULL) return ((size_t) -1); memset(outbuf, 0, sizeof(xmlOutputBuffer)); outbuf->buffer = buf; outbuf->encoder = NULL; outbuf->writecallback = NULL; outbuf->closecallback = NULL; outbuf->context = NULL; outbuf->written = 0; use = xmlBufUse(buf); htmlNodeDumpInternal(outbuf, cur, NULL, format); if (outbuf->error) ret = (size_t) -1; else ret = xmlBufUse(buf) - use; xmlFree(outbuf); return (ret); } /** * Serialize an HTML node to an xmlBuffer. Always uses UTF-8. * * @param buf the HTML buffer output * @param doc the document * @param cur the current node * @returns the number of bytes written or -1 in case of error */ int htmlNodeDump(xmlBuffer *buf, xmlDoc *doc, xmlNode *cur) { xmlBufPtr buffer; size_t ret1; int ret2; if ((buf == NULL) || (cur == NULL)) return(-1); xmlInitParser(); buffer = xmlBufFromBuffer(buf); if (buffer == NULL) return(-1); ret1 = htmlBufNodeDumpFormat(buffer, doc, cur, 1); ret2 = xmlBufBackToBuffer(buffer, buf); if ((ret1 == (size_t) -1) || (ret2 < 0)) return(-1); return(ret1 > INT_MAX ? INT_MAX : ret1); } /** * Serialize an HTML node to an xmlBuffer. * * If encoding is NULL, ASCII with HTML 4.0 named character entities * will be used. This is inefficient compared to UTF-8 and might be * changed in a future version. * * @param out the FILE pointer * @param doc the document (unused) * @param cur the current node * @param encoding the document encoding (optional) * @param format should formatting newlines been added * @returns the number of bytes written or -1 in case of failure. */ int htmlNodeDumpFileFormat(FILE *out, xmlDoc *doc ATTRIBUTE_UNUSED, xmlNode *cur, const char *encoding, int format) { xmlOutputBufferPtr buf; xmlCharEncodingHandlerPtr handler; int ret; xmlInitParser(); /* * save the content to a temp buffer. */ if (htmlFindOutputEncoder(encoding, &handler) != XML_ERR_OK) return(-1); buf = xmlOutputBufferCreateFile(out, handler); if (buf == NULL) { xmlCharEncCloseFunc(handler); return(-1); } htmlNodeDumpInternal(buf, cur, NULL, format); ret = xmlOutputBufferClose(buf); return(ret); } /** * Same as #htmlNodeDumpFileFormat with `format` set to 1 which is * typically undesired. Use of this function is DISCOURAGED in favor * of #htmlNodeDumpFileFormat. * * @param out the FILE pointer * @param doc the document * @param cur the current node */ void htmlNodeDumpFile(FILE *out, xmlDoc *doc, xmlNode *cur) { htmlNodeDumpFileFormat(out, doc, cur, NULL, 1); } /** * Serialize an HTML node to a memory, also returning the size of * the result. It's up to the caller to free the memory. * * Uses the encoding of the document. If the document has no * encoding, ASCII with HTML 4.0 named character entities will * be used. This is inefficient compared to UTF-8 and might be * changed in a future version. * * @param cur the document * @param mem OUT: the memory pointer * @param size OUT: the memory length * @param format should formatting newlines been added */ void htmlDocDumpMemoryFormat(xmlDoc *cur, xmlChar**mem, int *size, int format) { xmlOutputBufferPtr buf; xmlCharEncodingHandlerPtr handler = NULL; xmlInitParser(); if ((mem == NULL) || (size == NULL)) return; *mem = NULL; *size = 0; if (cur == NULL) return; if (htmlFindOutputEncoder((char *) cur->encoding, &handler) != XML_ERR_OK) return; buf = xmlAllocOutputBuffer(handler); if (buf == NULL) { xmlCharEncCloseFunc(handler); return; } htmlDocContentDumpFormatOutput(buf, cur, NULL, format); xmlOutputBufferFlush(buf); if (!buf->error) { if (buf->conv != NULL) { *size = xmlBufUse(buf->conv); *mem = xmlStrndup(xmlBufContent(buf->conv), *size); } else { *size = xmlBufUse(buf->buffer); *mem = xmlStrndup(xmlBufContent(buf->buffer), *size); } } xmlOutputBufferClose(buf); } /** * Same as #htmlDocDumpMemoryFormat with `format` set to 1 which * is typically undesired. Also see the warnings there. Use of * this function is DISCOURAGED in favor of * #htmlDocContentDumpFormatOutput. * * @param cur the document * @param mem OUT: the memory pointer * @param size OUT: the memory length */ void htmlDocDumpMemory(xmlDoc *cur, xmlChar**mem, int *size) { htmlDocDumpMemoryFormat(cur, mem, size, 1); } /************************************************************************ * * * Dumping HTML tree content to an I/O output buffer * * * ************************************************************************/ /** * Serialize the HTML document's DTD, if any. * * Ignores `encoding` and uses the encoding of the output buffer. * * @param buf the HTML buffer output * @param doc the document * @param encoding the encoding string (unused) */ static void htmlDtdDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr doc, const char *encoding ATTRIBUTE_UNUSED) { xmlDtdPtr cur = doc->intSubset; if (cur == NULL) return; xmlOutputBufferWrite(buf, 10, "<!DOCTYPE "); xmlOutputBufferWriteString(buf, (const char *)cur->name); if (cur->ExternalID != NULL) { xmlOutputBufferWrite(buf, 8, " PUBLIC "); xmlOutputBufferWriteQuotedString(buf, cur->ExternalID); if (cur->SystemID != NULL) { xmlOutputBufferWrite(buf, 1, " "); xmlOutputBufferWriteQuotedString(buf, cur->SystemID); } } else if (cur->SystemID != NULL && xmlStrcmp(cur->SystemID, BAD_CAST "about:legacy-compat")) { xmlOutputBufferWrite(buf, 8, " SYSTEM "); xmlOutputBufferWriteQuotedString(buf, cur->SystemID); } xmlOutputBufferWrite(buf, 2, ">\n"); } static void htmlSerializeUri(xmlOutputBufferPtr buf, const xmlChar *content) { const xmlChar *tmp = content; /* * See appendix "B.2.1 Non-ASCII characters in URI attribute * values" in the HTML 4.01 spec. This is also recommended * by the HTML output method of the XSLT 1.0 spec. * * We also escape space and control chars. */ /* Skip over initial whitespace */ while (IS_WS_HTML(*tmp)) tmp++; if (tmp > content) { xmlOutputBufferWrite(buf, tmp - content, (char *) content); content = tmp; } while (1) { char escbuf[3]; const char *repl; int replSize; int c = *tmp; while ((c > 0x20) && (c < 0x7F) && (c != '"') && (c != '&')) { tmp += 1; c = *tmp; } if (tmp > content) xmlOutputBufferWrite(buf, tmp - content, (char *) content); if ((c <= 0x20) || (c >= 0x7F)) { static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; if (c == 0) break; escbuf[0] = '%'; escbuf[1] = hex[(c >> 4) & 0x0F]; escbuf[2] = hex[c & 0x0F]; repl = escbuf; replSize = 3; } else if (c == '"') { repl = """; replSize = 6; } else { repl = "&"; replSize = 5; } xmlOutputBufferWrite(buf, replSize, repl); tmp += 1; content = tmp; } } /** * Serialize an HTML attribute. * * @param buf the HTML buffer output * @param cur the attribute pointer */ static void htmlAttrDumpOutput(xmlOutputBufferPtr buf, xmlAttrPtr cur) { 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); /* * The HTML5 spec requires to always serialize empty attribute * values as `=""`. We should probably align with HTML5 at some * point. */ if ((cur->children != NULL) && (!htmlIsBooleanAttr(cur->name))) { xmlNodePtr child; int isUri; xmlOutputBufferWrite(buf, 2, "=\""); /* * Special handling of URIs doesn't conform to HTML5 and * should probably be removed at some point. */ isUri = (cur->ns == NULL) && (cur->parent != NULL) && (cur->parent->ns == NULL) && ((!xmlStrcasecmp(cur->name, BAD_CAST "href")) || (!xmlStrcasecmp(cur->name, BAD_CAST "action")) || (!xmlStrcasecmp(cur->name, BAD_CAST "src")) || ((!xmlStrcasecmp(cur->name, BAD_CAST "name")) && (!xmlStrcasecmp(cur->parent->name, BAD_CAST "a")))); for (child = cur->children; child != NULL; child = child->next) { if (child->type == XML_TEXT_NODE) { const xmlChar *content = child->content; if (content == NULL) continue; if (isUri) { htmlSerializeUri(buf, content); } else { xmlSerializeText(buf, content, SIZE_MAX, XML_ESCAPE_HTML | XML_ESCAPE_ATTR); } } else if (child->type == XML_ENTITY_REF_NODE) { /* TODO: We should probably expand entity refs */ xmlOutputBufferWrite(buf, 1, "&"); xmlOutputBufferWriteString(buf, (char *) child->name); xmlOutputBufferWrite(buf, 1, ";"); } } xmlOutputBufferWrite(buf, 1, "\""); } } /** * Serialize an HTML node to an output buffer. * * If `encoding` is specified, it is used to create or update meta * tags containing the character encoding. * * @param buf the HTML buffer output * @param cur the current node * @param encoding the encoding string (optional) * @param format should formatting newlines been added */ void htmlNodeDumpInternal(xmlOutputBuffer *buf, xmlNode *cur, const char *encoding, int format) { xmlNodePtr root, parent, metaHead = NULL; xmlAttrPtr attr; const htmlElemDesc * info; int isRaw = 0; xmlInitParser(); if ((cur == NULL) || (buf == NULL)) { return; } root = cur; parent = cur->parent; while (1) { switch (cur->type) { case XML_HTML_DOCUMENT_NODE: case XML_DOCUMENT_NODE: if (((xmlDocPtr) cur)->intSubset != NULL) { htmlDtdDumpOutput(buf, (xmlDocPtr) cur, NULL); } if (cur->children != NULL) { /* Always validate cur->parent when descending. */ if (cur->parent == parent) { parent = cur; cur = cur->children; continue; } } else { xmlOutputBufferWrite(buf, 1, "\n"); } break; case XML_ELEMENT_NODE: { htmlMetaEncoding menc; int isMeta = 0; int addMeta = 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)) { htmlNodeDumpInternal(buf, cur, encoding, format); break; } /* * Get specific HTML info for that node. */ if (cur->ns == NULL) info = htmlTagLookup(cur->name); else info = NULL; if (encoding != NULL) { isMeta = htmlParseMetaEncoding(cur, &menc); /* * Don't add meta tag for "HTML" encoding. */ if ((xmlStrcasecmp(BAD_CAST encoding, BAD_CAST "HTML") != 0) && (xmlStrcasecmp(cur->name, BAD_CAST "head") == 0) && (parent != NULL) && (xmlStrcasecmp(parent->name, BAD_CAST "html") == 0) && (parent->parent != NULL) && (parent->parent->parent == NULL) && (metaHead == NULL)) { xmlNodePtr n; metaHead = cur; addMeta = 1; for (n = cur->children; n != NULL; n = n->next) { int unused; if (htmlFindMetaEncodingAttr(n, &unused) != NULL) { metaHead = NULL; addMeta = 0; 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) xmlNsListDumpOutput(buf, cur->nsDef); attr = cur->properties; while (attr != NULL) { if ((!isMeta) || (attr != menc.attr)) { htmlAttrDumpOutput(buf, attr); } else { xmlOutputBufferWrite(buf, 1, " "); xmlOutputBufferWriteString(buf, (char *) attr->name); xmlOutputBufferWrite(buf, 2, "=\""); xmlSerializeText(buf, menc.attrValue, menc.off.start, XML_ESCAPE_HTML | XML_ESCAPE_ATTR); xmlSerializeText(buf, BAD_CAST encoding, SIZE_MAX, XML_ESCAPE_HTML | XML_ESCAPE_ATTR); xmlSerializeText(buf, menc.attrValue + menc.off.end, menc.off.size - menc.off.end, XML_ESCAPE_HTML | XML_ESCAPE_ATTR); xmlOutputBufferWrite(buf, 1, "\""); } attr = attr->next; } if ((info != NULL) && (info->empty)) { xmlOutputBufferWrite(buf, 1, ">"); } else if (cur->children == NULL) { if (addMeta) { xmlOutputBufferWrite(buf, 16, "><meta charset=\""); xmlSerializeText(buf, BAD_CAST encoding, SIZE_MAX, XML_ESCAPE_HTML | XML_ESCAPE_ATTR); xmlOutputBufferWrite(buf, 4, "\"></"); } else { xmlOutputBufferWrite(buf, 3, "></"); } 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, 1, ">"); } else { xmlOutputBufferWrite(buf, 1, ">"); if ((format) && ((addMeta) || ((info != NULL) && (!info->isinline) && (cur->children->type != HTML_TEXT_NODE) && (cur->children->type != HTML_ENTITY_REF_NODE) && (cur->children != cur->last) && (cur->name != NULL) && (cur->name[0] != 'p')))) /* p, pre, param */ xmlOutputBufferWrite(buf, 1, "\n"); if (addMeta) { xmlOutputBufferWrite(buf, 15, "<meta charset=\""); xmlSerializeText(buf, BAD_CAST encoding, SIZE_MAX, XML_ESCAPE_HTML | XML_ESCAPE_ATTR); xmlOutputBufferWrite(buf, 2, "\">"); if ((format) && (cur->children->type != HTML_TEXT_NODE) && (cur->children->type != HTML_ENTITY_REF_NODE)) xmlOutputBufferWrite(buf, 1, "\n"); } if ((info != NULL) && (info->dataMode >= DATA_RAWTEXT)) isRaw = 1; parent = cur; cur = cur->children; continue; } if ((format) && (cur->next != NULL) && (info != NULL) && (!info->isinline)) { if ((cur->next->type != HTML_TEXT_NODE) && (cur->next->type != HTML_ENTITY_REF_NODE) && (parent != NULL) && (parent->name != NULL) && (parent->name[0] != 'p')) /* p, pre, param */ xmlOutputBufferWrite(buf, 1, "\n"); } break; } case XML_ATTRIBUTE_NODE: htmlAttrDumpOutput(buf, (xmlAttrPtr) cur); break; case HTML_TEXT_NODE: if (cur->content == NULL) break; if ((cur->name == (const xmlChar *)xmlStringTextNoenc) || (isRaw)) { xmlOutputBufferWriteString(buf, (const char *)cur->content); } else { xmlSerializeText(buf, cur->content, SIZE_MAX, XML_ESCAPE_HTML); } break; case HTML_COMMENT_NODE: if (cur->content != NULL) { xmlOutputBufferWrite(buf, 4, "<!--"); xmlOutputBufferWriteString(buf, (const char *)cur->content); xmlOutputBufferWrite(buf, 3, "-->"); } break; case HTML_PI_NODE: if (cur->name != NULL) { xmlOutputBufferWrite(buf, 2, "<?"); xmlOutputBufferWriteString(buf, (const char *)cur->name); if (cur->content != NULL) { xmlOutputBufferWrite(buf, 1, " "); xmlOutputBufferWriteString(buf, (const char *)cur->content); } xmlOutputBufferWrite(buf, 1, ">"); } break; case HTML_ENTITY_REF_NODE: xmlOutputBufferWrite(buf, 1, "&"); xmlOutputBufferWriteString(buf, (const char *)cur->name); xmlOutputBufferWrite(buf, 1, ";"); break; case HTML_PRESERVE_NODE: if (cur->content != NULL) { xmlOutputBufferWriteString(buf, (const char *)cur->content); } break; default: break; } while (1) { if (cur == root) return; if (cur->next != NULL) { cur = cur->next; break; } isRaw = 0; cur = parent; /* cur->parent was validated when descending. */ parent = cur->parent; if ((cur->type == XML_HTML_DOCUMENT_NODE) || (cur->type == XML_DOCUMENT_NODE)) { xmlOutputBufferWrite(buf, 1, "\n"); } else { if ((format) && (cur->ns == NULL)) info = htmlTagLookup(cur->name); else info = NULL; if ((format) && (info != NULL) && (!info->isinline) && (cur->last->type != HTML_TEXT_NODE) && (cur->last->type != HTML_ENTITY_REF_NODE) && ((cur->children != cur->last) || (cur == metaHead)) && (cur->name != NULL) && (cur->name[0] != 'p')) /* p, pre, param */ xmlOutputBufferWrite(buf, 1, "\n"); xmlOutputBufferWrite(buf, 2, "</"); 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, 1, ">"); if ((format) && (info != NULL) && (!info->isinline) && (cur->next != NULL)) { if ((cur->next->type != HTML_TEXT_NODE) && (cur->next->type != HTML_ENTITY_REF_NODE) && (parent != NULL) && (parent->name != NULL) && (parent->name[0] != 'p')) /* p, pre, param */ xmlOutputBufferWrite(buf, 1, "\n"); } if (cur == metaHead) metaHead = NULL; } } } } /** * Serialize an HTML node to an output buffer. * * @param buf the HTML buffer output * @param doc the document (unused) * @param cur the current node * @param encoding the encoding string (unused) * @param format should formatting newlines been added */ void htmlNodeDumpFormatOutput(xmlOutputBuffer *buf, xmlDoc *doc ATTRIBUTE_UNUSED, xmlNode *cur, const char *encoding ATTRIBUTE_UNUSED, int format) { htmlNodeDumpInternal(buf, cur, NULL, format); } /** * Same as #htmlNodeDumpFormatOutput with `format` set to 1 which is * typically undesired. Use of this function is DISCOURAGED in favor * of #htmlNodeDumpFormatOutput. * * @param buf the HTML buffer output * @param doc the document (unused) * @param cur the current node * @param encoding the encoding string (unused) */ void htmlNodeDumpOutput(xmlOutputBuffer *buf, xmlDoc *doc ATTRIBUTE_UNUSED, xmlNode *cur, const char *encoding ATTRIBUTE_UNUSED) { htmlNodeDumpInternal(buf, cur, NULL, 1); } /** * Serialize an HTML document to an output buffer. * * @param buf the HTML buffer output * @param cur the document * @param encoding the encoding string (unused) * @param format should formatting newlines been added */ void htmlDocContentDumpFormatOutput(xmlOutputBuffer *buf, xmlDoc *cur, const char *encoding ATTRIBUTE_UNUSED, int format) { htmlNodeDumpInternal(buf, (xmlNodePtr) cur, NULL, format); } /** * Same as #htmlDocContentDumpFormatOutput with `format` set to 1 * which is typically undesired. Use of this function is DISCOURAGED * in favor of #htmlDocContentDumpFormatOutput. * * @param buf the HTML buffer output * @param cur the document * @param encoding the encoding string (unused) */ void htmlDocContentDumpOutput(xmlOutputBuffer *buf, xmlDoc *cur, const char *encoding ATTRIBUTE_UNUSED) { htmlNodeDumpInternal(buf, (xmlNodePtr) cur, NULL, 1); } /************************************************************************ * * * Saving functions front-ends * * * ************************************************************************/ /** * Serialize an HTML document to an open `FILE`. * * Uses the encoding of the document. If the document has no * encoding, ASCII with HTML 4.0 named character entities will * be used. This is inefficient compared to UTF-8 and might be * changed in a future version. * * Enables "formatting" unconditionally which is typically * undesired. * * Use of this function is DISCOURAGED in favor of * #htmlNodeDumpFileFormat. * * @param f the FILE* * @param cur the document * @returns the number of bytes written or -1 in case of failure. */ int htmlDocDump(FILE *f, xmlDoc *cur) { xmlOutputBufferPtr buf; xmlCharEncodingHandlerPtr handler = NULL; int ret; xmlInitParser(); if ((cur == NULL) || (f == NULL)) { return(-1); } if (htmlFindOutputEncoder((char *) cur->encoding, &handler) != XML_ERR_OK) return(-1); buf = xmlOutputBufferCreateFile(f, handler); if (buf == NULL) { xmlCharEncCloseFunc(handler); return(-1); } htmlDocContentDumpOutput(buf, cur, NULL); ret = xmlOutputBufferClose(buf); return(ret); } /** * Serialize an HTML document to a file. * * Same as #htmlSaveFileFormat with `encoding` set to NULL and * `format` set to 1 which is typically undesired. * * Use of this function is DISCOURAGED in favor of * #htmlSaveFileFormat. * * @param filename the filename (or URL) * @param cur the document * @returns the number of bytes written or -1 in case of failure. */ int htmlSaveFile(const char *filename, xmlDoc *cur) { return(htmlSaveFileFormat(filename, cur, NULL, 1)); } /** * Serialize an HTML document to a file using a given encoding. * * If `filename` is `"-"`, stdout is used. This is potentially * insecure and might be changed in a future version. * * If encoding is NULL, ASCII with HTML 4.0 named character entities * will be used. This is inefficient compared to UTF-8 and might be * changed in a future version. * * Sets or updates meta tags containing the character encoding. * * @param filename the filename * @param cur the document * @param format should formatting newlines been added * @param encoding the document encoding (optional) * @returns the number of bytes written or -1 in case of failure. */ int htmlSaveFileFormat(const char *filename, xmlDoc *cur, const char *encoding, int format) { xmlOutputBufferPtr buf; xmlCharEncodingHandlerPtr handler = NULL; int ret; if ((cur == NULL) || (filename == NULL)) return(-1); xmlInitParser(); if (htmlFindOutputEncoder(encoding, &handler) != XML_ERR_OK) return(-1); /* * save the content to a temp buffer. */ buf = xmlOutputBufferCreateFilename(filename, handler, cur->compression); if (buf == NULL) { xmlCharEncCloseFunc(handler); return(0); } htmlDocContentDumpFormatOutput(buf, cur, encoding, format); ret = xmlOutputBufferClose(buf); return(ret); } /** * Serialize an HTML document to a file. * * Same as #htmlSaveFileFormat with `format` set to 1 which is * typically undesired. Also see the warnings there. Use of this * function is DISCOURAGED in favor of #htmlSaveFileFormat. * * @param filename the filename * @param cur the document * @param encoding the document encoding * @returns the number of bytes written or -1 in case of failure. */ int htmlSaveFileEnc(const char *filename, xmlDoc *cur, const char *encoding) { return(htmlSaveFileFormat(filename, cur, encoding, 1)); } #endif /* LIBXML_OUTPUT_ENABLED */ #endif /* LIBXML_HTML_ENABLED */