/*
 * HTMLtree.c : implementation of access function for an HTML tree.
 *
 * See Copyright for the status of this software.
 *
 * daniel@veillard.com
 */


#define IN_LIBXML
#include "libxml.h"
#ifdef LIBXML_HTML_ENABLED

#include <string.h> /* for memset() only ! */

#ifdef HAVE_CTYPE_H
#include <ctype.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#include <libxml/xmlmemory.h>
#include <libxml/HTMLparser.h>
#include <libxml/HTMLtree.h>
#include <libxml/entities.h>
#include <libxml/valid.h>
#include <libxml/xmlerror.h>
#include <libxml/parserInternals.h>
#include <libxml/globals.h>
#include <libxml/uri.h>

/************************************************************************
 *                                                                      *
 *              Getting/Setting encoding meta tags                      *
 *                                                                      *
 ************************************************************************/

/**
 * htmlGetMetaEncoding:
 * @doc:  the document
 * 
 * Encoding definition lookup in the Meta tags
 *
 * Returns the current encoding as flagged in the HTML source
 */
const xmlChar *
htmlGetMetaEncoding(htmlDocPtr doc) {
    htmlNodePtr cur;
    const xmlChar *content;
    const xmlChar *encoding;

    if (doc == NULL)
        return(NULL);
    cur = doc->children;

    /*
     * Search the html
     */
    while (cur != NULL) {
        if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
            if (xmlStrEqual(cur->name, BAD_CAST"html"))
                break;
            if (xmlStrEqual(cur->name, BAD_CAST"head"))
                goto found_head;
            if (xmlStrEqual(cur->name, BAD_CAST"meta"))
                goto found_meta;
        }
        cur = cur->next;
    }
    if (cur == NULL)
        return(NULL);
    cur = cur->children;

    /*
     * Search the head
     */
    while (cur != NULL) {
        if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
            if (xmlStrEqual(cur->name, BAD_CAST"head"))
                break;
            if (xmlStrEqual(cur->name, BAD_CAST"meta"))
                goto found_meta;
        }
        cur = cur->next;
    }
    if (cur == NULL)
        return(NULL);
found_head:
    cur = cur->children;

    /*
     * Search the meta elements
     */
found_meta:
    while (cur != NULL) {
        if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
            if (xmlStrEqual(cur->name, BAD_CAST"meta")) {
                xmlAttrPtr attr = cur->properties;
                int http;
                const xmlChar *value;

                content = NULL;
                http = 0;
                while (attr != NULL) {
                    if ((attr->children != NULL) &&
                        (attr->children->type == XML_TEXT_NODE) &&
                        (attr->children->next == NULL)) {
                        value = attr->children->content;
                        if ((!xmlStrcasecmp(attr->name, BAD_CAST"http-equiv"))
                         && (!xmlStrcasecmp(value, BAD_CAST"Content-Type")))
                            http = 1;
                        else if ((value != NULL)
                         && (!xmlStrcasecmp(attr->name, BAD_CAST"content")))
                            content = value;
                        if ((http != 0) && (content != NULL))
                            goto found_content;
                    }
                    attr = attr->next;
                }
            }
        }
        cur = cur->next;
    }
    return(NULL);

found_content:
    encoding = xmlStrstr(content, BAD_CAST"charset=");
    if (encoding == NULL) 
        encoding = xmlStrstr(content, BAD_CAST"Charset=");
    if (encoding == NULL) 
        encoding = xmlStrstr(content, BAD_CAST"CHARSET=");
    if (encoding != NULL) {
        encoding += 8;
    } else {
        encoding = xmlStrstr(content, BAD_CAST"charset =");
        if (encoding == NULL) 
            encoding = xmlStrstr(content, BAD_CAST"Charset =");
        if (encoding == NULL) 
            encoding = xmlStrstr(content, BAD_CAST"CHARSET =");
        if (encoding != NULL)
            encoding += 9;
    }
    if (encoding != NULL) {
        while ((*encoding == ' ') || (*encoding == '\t')) encoding++;
    }
    return(encoding);
}

/**
 * htmlSetMetaEncoding:
 * @doc:  the document
 * @encoding:  the encoding string
 * 
 * Sets the current encoding in the Meta tags
 * NOTE: this will not change the document content encoding, just
 * the META flag associated.
 *
 * Returns 0 in case of success and -1 in case of error
 */
int
htmlSetMetaEncoding(htmlDocPtr doc, const xmlChar *encoding) {
    htmlNodePtr cur, meta;
    const xmlChar *content;
    char newcontent[100];


    if (doc == NULL)
        return(-1);

    if (encoding != NULL) {
        snprintf(newcontent, sizeof(newcontent), "text/html; charset=%s",
                (char *)encoding);
        newcontent[sizeof(newcontent) - 1] = 0;
    }

    cur = doc->children;

    /*
     * Search the html
     */
    while (cur != NULL) {
        if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
            if (xmlStrcasecmp(cur->name, BAD_CAST"html") == 0)
                break;
            if (xmlStrcasecmp(cur->name, BAD_CAST"head") == 0)
                goto found_head;
            if (xmlStrcasecmp(cur->name, BAD_CAST"meta") == 0)
                goto found_meta;
        }
        cur = cur->next;
    }
    if (cur == NULL)
        return(-1);
    cur = cur->children;

    /*
     * Search the head
     */
    while (cur != NULL) {
        if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
            if (xmlStrcasecmp(cur->name, BAD_CAST"head") == 0)
                break;
            if (xmlStrcasecmp(cur->name, BAD_CAST"meta") == 0)
                goto found_meta;
        }
        cur = cur->next;
    }
    if (cur == NULL)
        return(-1);
found_head:
    if (cur->children == NULL) {
        if (encoding == NULL)
            return(0);
        meta = xmlNewDocNode(doc, NULL, BAD_CAST"meta", NULL);
        xmlAddChild(cur, meta);
        xmlNewProp(meta, BAD_CAST"http-equiv", BAD_CAST"Content-Type");
        xmlNewProp(meta, BAD_CAST"content", BAD_CAST newcontent);
        return(0);
    }
    cur = cur->children;

found_meta:
    if (encoding != NULL) {
        /*
         * Create a new Meta element with the right attributes
         */

        meta = xmlNewDocNode(doc, NULL, BAD_CAST"meta", NULL);
        xmlAddPrevSibling(cur, meta);
        xmlNewProp(meta, BAD_CAST"http-equiv", BAD_CAST"Content-Type");
        xmlNewProp(meta, BAD_CAST"content", BAD_CAST newcontent);
    }

    /*
     * Search and destroy all the remaining the meta elements carrying
     * encoding informations
     */
    while (cur != NULL) {
        if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
            if (xmlStrcasecmp(cur->name, BAD_CAST"meta") == 0) {
                xmlAttrPtr attr = cur->properties;
                int http;
                const xmlChar *value;

                content = NULL;
                http = 0;
                while (attr != NULL) {
                    if ((attr->children != NULL) &&
                        (attr->children->type == XML_TEXT_NODE) &&
                        (attr->children->next == NULL)) {
                        value = attr->children->content;
                        if ((!xmlStrcasecmp(attr->name, BAD_CAST"http-equiv"))
                         && (!xmlStrcasecmp(value, BAD_CAST"Content-Type")))
                            http = 1;
                        else 
                        {
                           if ((value != NULL) && 
                                (!xmlStrcasecmp(attr->name, BAD_CAST"content")))
                              content = value;
                        }
                        if ((http != 0) && (content != NULL))
                            break;
                    }
                    attr = attr->next;
                }
                if ((http != 0) && (content != NULL)) {
                    meta = cur;
                    cur = cur->next;
                    xmlUnlinkNode(meta);
                    xmlFreeNode(meta);
                    continue;
                }

            }
        }
        cur = cur->next;
    }
    return(0);
}

/**
 * booleanHTMLAttrs:
 *
 * 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"
 *
 */
static const char* htmlBooleanAttrs[] = {
  "checked", "compact", "declare", "defer", "disabled", "ismap",
  "multiple", "nohref", "noresize", "noshade", "nowrap", "readonly",
  "selected", NULL
};


/**
 * htmlIsBooleanAttr:
 * @name:  the name of the attribute to check
 *
 * Determine if a given attribute is a boolean attribute.
 * 
 * returns: false if the attribute is not boolean, true otherwise.
 */
int
htmlIsBooleanAttr(const xmlChar *name)
{
    int i = 0;

    while (htmlBooleanAttrs[i] != NULL) {
        if (xmlStrcasecmp((const xmlChar *)htmlBooleanAttrs[i], name) == 0)
            return 1;
        i++;
    }
    return 0;
}

#ifdef LIBXML_OUTPUT_ENABLED
/************************************************************************
 *                                                                      *
 *                      Output error handlers                           *
 *                                                                      *
 ************************************************************************/
/**
 * htmlSaveErrMemory:
 * @extra:  extra informations
 *
 * Handle an out of memory condition
 */
static void
htmlSaveErrMemory(const char *extra)
{
    __xmlSimpleError(XML_FROM_OUTPUT, XML_ERR_NO_MEMORY, NULL, NULL, extra);
}

/**
 * htmlSaveErr:
 * @code:  the error number
 * @node:  the location of the error.
 * @extra:  extra informations
 *
 * Handle an out of memory condition
 */
static void
htmlSaveErr(int code, xmlNodePtr node, const char *extra)
{
    const char *msg = NULL;

    switch(code) {
        case XML_SAVE_NOT_UTF8:
            msg = "string is not in UTF-8\n";
            break;
        case XML_SAVE_CHAR_INVALID:
            msg = "invalid character value\n";
            break;
        case XML_SAVE_UNKNOWN_ENCODING:
            msg = "unknown encoding %s\n";
            break;
        case XML_SAVE_NO_DOCTYPE:
            msg = "HTML has no DOCTYPE\n";
            break;
        default:
            msg = "unexpected error number\n";
    }
    __xmlSimpleError(XML_FROM_OUTPUT, code, node, msg, extra);
}

/************************************************************************
 *                                                                      *
 *              Dumping HTML tree content to a simple buffer            *
 *                                                                      *
 ************************************************************************/

static int
htmlNodeDumpFormat(xmlBufferPtr buf, xmlDocPtr doc, xmlNodePtr cur,
                   int format);

/**
 * htmlNodeDumpFormat:
 * @buf:  the HTML buffer output
 * @doc:  the document
 * @cur:  the current node
 * @format:  should formatting spaces been added
 *
 * Dump an HTML node, recursive behaviour,children are printed too.
 *
 * Returns the number of byte written or -1 in case of error
 */
static int
htmlNodeDumpFormat(xmlBufferPtr buf, xmlDocPtr doc, xmlNodePtr cur,
                   int format) {
    unsigned int use;
    int ret;
    xmlOutputBufferPtr outbuf;

    if (cur == NULL) {
        return (-1);
    }
    if (buf == NULL) {
        return (-1);
    }
    outbuf = (xmlOutputBufferPtr) xmlMalloc(sizeof(xmlOutputBuffer));
    if (outbuf == NULL) {
        htmlSaveErrMemory("allocating HTML output buffer");
        return (-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 = buf->use;
    htmlNodeDumpFormatOutput(outbuf, doc, cur, NULL, format);
    xmlFree(outbuf);
    ret = buf->use - use;
    return (ret);
}

/**
 * htmlNodeDump:
 * @buf:  the HTML buffer output
 * @doc:  the document
 * @cur:  the current node
 *
 * Dump an HTML node, recursive behaviour,children are printed too,
 * and formatting returns are added.
 *
 * Returns the number of byte written or -1 in case of error
 */
int
htmlNodeDump(xmlBufferPtr buf, xmlDocPtr doc, xmlNodePtr cur) {
    xmlInitParser();

    return(htmlNodeDumpFormat(buf, doc, cur, 1));
}

/**
 * htmlNodeDumpFileFormat:
 * @out:  the FILE pointer
 * @doc:  the document
 * @cur:  the current node
 * @encoding: the document encoding
 * @format:  should formatting spaces been added
 *
 * Dump an HTML node, recursive behaviour,children are printed too.
 *
 * TODO: if encoding == NULL try to save in the doc encoding
 *
 * returns: the number of byte written or -1 in case of failure.
 */
int
htmlNodeDumpFileFormat(FILE *out, xmlDocPtr doc,
                       xmlNodePtr cur, const char *encoding, int format) {
    xmlOutputBufferPtr buf;
    xmlCharEncodingHandlerPtr handler = NULL;
    int ret;

    xmlInitParser();

    if (encoding != NULL) {
        xmlCharEncoding enc;

        enc = xmlParseCharEncoding(encoding);
        if (enc != XML_CHAR_ENCODING_UTF8) {
            handler = xmlFindCharEncodingHandler(encoding);
            if (handler == NULL)
                return(-1);
        }
    }

    /*
     * Fallback to HTML or ASCII when the encoding is unspecified
     */
    if (handler == NULL)
        handler = xmlFindCharEncodingHandler("HTML");
    if (handler == NULL)
        handler = xmlFindCharEncodingHandler("ascii");

    /* 
     * save the content to a temp buffer.
     */
    buf = xmlOutputBufferCreateFile(out, handler);
    if (buf == NULL) return(0);

    htmlNodeDumpFormatOutput(buf, doc, cur, encoding, format);

    ret = xmlOutputBufferClose(buf);
    return(ret);
}

/**
 * htmlNodeDumpFile:
 * @out:  the FILE pointer
 * @doc:  the document
 * @cur:  the current node
 *
 * Dump an HTML node, recursive behaviour,children are printed too,
 * and formatting returns are added.
 */
void
htmlNodeDumpFile(FILE *out, xmlDocPtr doc, xmlNodePtr cur) {
    htmlNodeDumpFileFormat(out, doc, cur, NULL, 1);
}

/**
 * htmlDocDumpMemoryFormat:
 * @cur:  the document
 * @mem:  OUT: the memory pointer
 * @size:  OUT: the memory length
 * @format:  should formatting spaces been added
 *
 * Dump an HTML document in memory and return the xmlChar * and it's size.
 * It's up to the caller to free the memory.
 */
void
htmlDocDumpMemoryFormat(xmlDocPtr cur, xmlChar**mem, int *size, int format) {
    xmlOutputBufferPtr buf;
    xmlCharEncodingHandlerPtr handler = NULL;
    const char *encoding;

    xmlInitParser();

    if ((mem == NULL) || (size == NULL))
        return;
    if (cur == NULL) {
        *mem = NULL;
        *size = 0;
        return;
    }

    encoding = (const char *) htmlGetMetaEncoding(cur);

    if (encoding != NULL) {
        xmlCharEncoding enc;

        enc = xmlParseCharEncoding(encoding);
        if (enc != cur->charset) {
            if (cur->charset != XML_CHAR_ENCODING_UTF8) {
                /*
                 * Not supported yet
                 */
                *mem = NULL;
                *size = 0;
                return;
            }

            handler = xmlFindCharEncodingHandler(encoding);
            if (handler == NULL) {
                *mem = NULL;
                *size = 0;
                return;
            }
        } else {
            handler = xmlFindCharEncodingHandler(encoding);
        }
    }

    /*
     * Fallback to HTML or ASCII when the encoding is unspecified
     */
    if (handler == NULL)
        handler = xmlFindCharEncodingHandler("HTML");
    if (handler == NULL)
        handler = xmlFindCharEncodingHandler("ascii");

    buf = xmlAllocOutputBuffer(handler);
    if (buf == NULL) {
        *mem = NULL;
        *size = 0;
        return;
    }

        htmlDocContentDumpFormatOutput(buf, cur, NULL, format);

    xmlOutputBufferFlush(buf);
    if (buf->conv != NULL) {
        *size = buf->conv->use;
        *mem = xmlStrndup(buf->conv->content, *size);
    } else {
        *size = buf->buffer->use;
        *mem = xmlStrndup(buf->buffer->content, *size);
    }
    (void)xmlOutputBufferClose(buf);
}

/**
 * htmlDocDumpMemory:
 * @cur:  the document
 * @mem:  OUT: the memory pointer
 * @size:  OUT: the memory length
 *
 * Dump an HTML document in memory and return the xmlChar * and it's size.
 * It's up to the caller to free the memory.
 */
void
htmlDocDumpMemory(xmlDocPtr cur, xmlChar**mem, int *size) {
        htmlDocDumpMemoryFormat(cur, mem, size, 1);
}


/************************************************************************
 *                                                                      *
 *              Dumping HTML tree content to an I/O output buffer       *
 *                                                                      *
 ************************************************************************/

void xmlNsListDumpOutput(xmlOutputBufferPtr buf, xmlNsPtr cur);

/**
 * htmlDtdDumpOutput:
 * @buf:  the HTML buffer output
 * @doc:  the document
 * @encoding:  the encoding string
 * 
 * TODO: check whether encoding is needed
 *
 * Dump the HTML document DTD, if any.
 */
static void
htmlDtdDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr doc,
                  const char *encoding ATTRIBUTE_UNUSED) {
    xmlDtdPtr cur = doc->intSubset;

    if (cur == NULL) {
        htmlSaveErr(XML_SAVE_NO_DOCTYPE, (xmlNodePtr) doc, NULL);
        return;
    }
    xmlOutputBufferWriteString(buf, "<!DOCTYPE ");
    xmlOutputBufferWriteString(buf, (const char *)cur->name);
    if (cur->ExternalID != NULL) {
        xmlOutputBufferWriteString(buf, " PUBLIC ");
        xmlBufferWriteQuotedString(buf->buffer, cur->ExternalID);
        if (cur->SystemID != NULL) {
            xmlOutputBufferWriteString(buf, " ");
            xmlBufferWriteQuotedString(buf->buffer, cur->SystemID);
        } 
    }  else if (cur->SystemID != NULL) {
        xmlOutputBufferWriteString(buf, " SYSTEM ");
        xmlBufferWriteQuotedString(buf->buffer, cur->SystemID);
    }
    xmlOutputBufferWriteString(buf, ">\n");
}

/**
 * htmlAttrDumpOutput:
 * @buf:  the HTML buffer output
 * @doc:  the document
 * @cur:  the attribute pointer
 * @encoding:  the encoding string
 *
 * Dump an HTML attribute
 */
static void
htmlAttrDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr doc, xmlAttrPtr cur,
                   const char *encoding ATTRIBUTE_UNUSED) {
    xmlChar *value;

    /*
     * TODO: The html output method should not escape a & character
     *       occurring in an attribute value immediately followed by
     *       a { character (see Section B.7.1 of the HTML 4.0 Recommendation).
     */

    if (cur == NULL) {
        return;
    }
    xmlOutputBufferWriteString(buf, " ");
    if ((cur->ns != NULL) && (cur->ns->prefix != NULL)) {
        xmlOutputBufferWriteString(buf, (const char *)cur->ns->prefix);
        xmlOutputBufferWriteString(buf, ":");
    }
    xmlOutputBufferWriteString(buf, (const char *)cur->name);
    if ((cur->children != NULL) && (!htmlIsBooleanAttr(cur->name))) {
        value = xmlNodeListGetString(doc, cur->children, 0);
        if (value) {
            xmlOutputBufferWriteString(buf, "=");
            if ((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"))))) {
                xmlChar *escaped;
                xmlChar *tmp = value;

                while (IS_BLANK_CH(*tmp)) tmp++;

                escaped = xmlURIEscapeStr(tmp, BAD_CAST"@/:=?;#%&,+");
                if (escaped != NULL) {
                    xmlBufferWriteQuotedString(buf->buffer, escaped);
                    xmlFree(escaped);
                } else {
                    xmlBufferWriteQuotedString(buf->buffer, value);
                }
            } else {
                xmlBufferWriteQuotedString(buf->buffer, value);
            }
            xmlFree(value);
        } else  {
            xmlOutputBufferWriteString(buf, "=\"\"");
        }
    }
}

/**
 * htmlAttrListDumpOutput:
 * @buf:  the HTML buffer output
 * @doc:  the document
 * @cur:  the first attribute pointer
 * @encoding:  the encoding string
 *
 * Dump a list of HTML attributes
 */
static void
htmlAttrListDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr doc, xmlAttrPtr cur, const char *encoding) {
    if (cur == NULL) {
        return;
    }
    while (cur != NULL) {
        htmlAttrDumpOutput(buf, doc, cur, encoding);
        cur = cur->next;
    }
}



/**
 * htmlNodeListDumpOutput:
 * @buf:  the HTML buffer output
 * @doc:  the document
 * @cur:  the first node
 * @encoding:  the encoding string
 * @format:  should formatting spaces been added
 *
 * Dump an HTML node list, recursive behaviour,children are printed too.
 */
static void
htmlNodeListDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr doc,
                       xmlNodePtr cur, const char *encoding, int format) {
    if (cur == NULL) {
        return;
    }
    while (cur != NULL) {
        htmlNodeDumpFormatOutput(buf, doc, cur, encoding, format);
        cur = cur->next;
    }
}

/**
 * htmlNodeDumpFormatOutput:
 * @buf:  the HTML buffer output
 * @doc:  the document
 * @cur:  the current node
 * @encoding:  the encoding string
 * @format:  should formatting spaces been added
 *
 * Dump an HTML node, recursive behaviour,children are printed too.
 */
void
htmlNodeDumpFormatOutput(xmlOutputBufferPtr buf, xmlDocPtr doc,
                         xmlNodePtr cur, const char *encoding, int format) {
    const htmlElemDesc * info;

    xmlInitParser();

    if ((cur == NULL) || (buf == NULL)) {
        return;
    }
    /*
     * Special cases.
     */
    if (cur->type == XML_DTD_NODE)
        return;
    if ((cur->type == XML_HTML_DOCUMENT_NODE) ||
        (cur->type == XML_DOCUMENT_NODE)){
        htmlDocContentDumpOutput(buf, (xmlDocPtr) cur, encoding);
        return;
    }
    if (cur->type == HTML_TEXT_NODE) {
        if (cur->content != NULL) {
            if (((cur->name == (const xmlChar *)xmlStringText) ||
                 (cur->name != (const xmlChar *)xmlStringTextNoenc)) &&
                ((cur->parent == NULL) ||
                 ((xmlStrcasecmp(cur->parent->name, BAD_CAST "script")) &&
                  (xmlStrcasecmp(cur->parent->name, BAD_CAST "style"))))) {
                xmlChar *buffer;

                buffer = xmlEncodeEntitiesReentrant(doc, cur->content);
                if (buffer != NULL) {
                    xmlOutputBufferWriteString(buf, (const char *)buffer);
                    xmlFree(buffer);
                }
            } else {
                xmlOutputBufferWriteString(buf, (const char *)cur->content);
            }
        }
        return;
    }
    if (cur->type == HTML_COMMENT_NODE) {
        if (cur->content != NULL) {
            xmlOutputBufferWriteString(buf, "<!--");
            xmlOutputBufferWriteString(buf, (const char *)cur->content);
            xmlOutputBufferWriteString(buf, "-->");
        }
        return;
    }
    if (cur->type == HTML_PI_NODE) {
        if (cur->name == NULL)
            return;
        xmlOutputBufferWriteString(buf, "<?");
        xmlOutputBufferWriteString(buf, (const char *)cur->name);
        if (cur->content != NULL) {
            xmlOutputBufferWriteString(buf, " ");
            xmlOutputBufferWriteString(buf, (const char *)cur->content);
        }
        xmlOutputBufferWriteString(buf, ">");
        return;
    }
    if (cur->type == HTML_ENTITY_REF_NODE) {
        xmlOutputBufferWriteString(buf, "&");
        xmlOutputBufferWriteString(buf, (const char *)cur->name);
        xmlOutputBufferWriteString(buf, ";");
        return;
    }
    if (cur->type == HTML_PRESERVE_NODE) {
        if (cur->content != NULL) {
            xmlOutputBufferWriteString(buf, (const char *)cur->content);
        }
        return;
    }

    /*
     * Get specific HTML info for that node.
     */
    if (cur->ns == NULL)
        info = htmlTagLookup(cur->name);
    else
        info = NULL;

    xmlOutputBufferWriteString(buf, "<");
    if ((cur->ns != NULL) && (cur->ns->prefix != NULL)) {
        xmlOutputBufferWriteString(buf, (const char *)cur->ns->prefix);
        xmlOutputBufferWriteString(buf, ":");
    }
    xmlOutputBufferWriteString(buf, (const char *)cur->name);
    if (cur->nsDef)
        xmlNsListDumpOutput(buf, cur->nsDef);
    if (cur->properties != NULL)
        htmlAttrListDumpOutput(buf, doc, cur->properties, encoding);

    if ((info != NULL) && (info->empty)) {
        xmlOutputBufferWriteString(buf, ">");
        if ((format) && (!info->isinline) && (cur->next != NULL)) {
            if ((cur->next->type != HTML_TEXT_NODE) &&
                (cur->next->type != HTML_ENTITY_REF_NODE) &&
                (cur->parent != NULL) &&
                (cur->parent->name != NULL) &&
                (cur->parent->name[0] != 'p')) /* p, pre, param */
                xmlOutputBufferWriteString(buf, "\n");
        }
        return;
    }
    if (((cur->type == XML_ELEMENT_NODE) || (cur->content == NULL)) &&
        (cur->children == NULL)) {
        if ((info != NULL) && (info->saveEndTag != 0) &&
            (xmlStrcmp(BAD_CAST info->name, BAD_CAST "html")) &&
            (xmlStrcmp(BAD_CAST info->name, BAD_CAST "body"))) {
            xmlOutputBufferWriteString(buf, ">");
        } else {
            xmlOutputBufferWriteString(buf, "></");
            if ((cur->ns != NULL) && (cur->ns->prefix != NULL)) {
                xmlOutputBufferWriteString(buf, (const char *)cur->ns->prefix);
                xmlOutputBufferWriteString(buf, ":");
            }
            xmlOutputBufferWriteString(buf, (const char *)cur->name);
            xmlOutputBufferWriteString(buf, ">");
        }
        if ((format) && (cur->next != NULL) &&
            (info != NULL) && (!info->isinline)) {
            if ((cur->next->type != HTML_TEXT_NODE) &&
                (cur->next->type != HTML_ENTITY_REF_NODE) &&
                (cur->parent != NULL) &&
                (cur->parent->name != NULL) &&
                (cur->parent->name[0] != 'p')) /* p, pre, param */
                xmlOutputBufferWriteString(buf, "\n");
        }
        return;
    }
    xmlOutputBufferWriteString(buf, ">");
    if ((cur->type != XML_ELEMENT_NODE) &&
        (cur->content != NULL)) {
            /*
             * Uses the OutputBuffer property to automatically convert
             * invalids to charrefs
             */

            xmlOutputBufferWriteString(buf, (const char *) cur->content);
    }
    if (cur->children != NULL) {
        if ((format) && (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 */
            xmlOutputBufferWriteString(buf, "\n");
        htmlNodeListDumpOutput(buf, doc, cur->children, encoding, format);
        if ((format) && (info != NULL) && (!info->isinline) &&
            (cur->last->type != HTML_TEXT_NODE) &&
            (cur->last->type != HTML_ENTITY_REF_NODE) &&
            (cur->children != cur->last) &&
            (cur->name != NULL) &&
            (cur->name[0] != 'p')) /* p, pre, param */
            xmlOutputBufferWriteString(buf, "\n");
    }
    xmlOutputBufferWriteString(buf, "</");
    if ((cur->ns != NULL) && (cur->ns->prefix != NULL)) {
        xmlOutputBufferWriteString(buf, (const char *)cur->ns->prefix);
        xmlOutputBufferWriteString(buf, ":");
    }
    xmlOutputBufferWriteString(buf, (const char *)cur->name);
    xmlOutputBufferWriteString(buf, ">");
    if ((format) && (info != NULL) && (!info->isinline) &&
        (cur->next != NULL)) {
        if ((cur->next->type != HTML_TEXT_NODE) &&
            (cur->next->type != HTML_ENTITY_REF_NODE) &&
            (cur->parent != NULL) &&
            (cur->parent->name != NULL) &&
            (cur->parent->name[0] != 'p')) /* p, pre, param */
            xmlOutputBufferWriteString(buf, "\n");
    }
}

/**
 * htmlNodeDumpOutput:
 * @buf:  the HTML buffer output
 * @doc:  the document
 * @cur:  the current node
 * @encoding:  the encoding string
 *
 * Dump an HTML node, recursive behaviour,children are printed too,
 * and formatting returns/spaces are added.
 */
void
htmlNodeDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr doc,
                   xmlNodePtr cur, const char *encoding) {
    htmlNodeDumpFormatOutput(buf, doc, cur, encoding, 1);
}

/**
 * htmlDocContentDumpFormatOutput:
 * @buf:  the HTML buffer output
 * @cur:  the document
 * @encoding:  the encoding string
 * @format:  should formatting spaces been added
 *
 * Dump an HTML document.
 */
void
htmlDocContentDumpFormatOutput(xmlOutputBufferPtr buf, xmlDocPtr cur,
                               const char *encoding, int format) {
    int type;

    xmlInitParser();

    if ((buf == NULL) || (cur == NULL))
        return;

    /*
     * force to output the stuff as HTML, especially for entities
     */
    type = cur->type;
    cur->type = XML_HTML_DOCUMENT_NODE;
    if (cur->intSubset != NULL) {
        htmlDtdDumpOutput(buf, cur, NULL);
    }
    if (cur->children != NULL) {
        htmlNodeListDumpOutput(buf, cur, cur->children, encoding, format);
    }
    xmlOutputBufferWriteString(buf, "\n");
    cur->type = (xmlElementType) type;
}

/**
 * htmlDocContentDumpOutput:
 * @buf:  the HTML buffer output
 * @cur:  the document
 * @encoding:  the encoding string
 *
 * Dump an HTML document. Formating return/spaces are added.
 */
void
htmlDocContentDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr cur,
                         const char *encoding) {
    htmlDocContentDumpFormatOutput(buf, cur, encoding, 1);
}

/************************************************************************
 *                                                                      *
 *              Saving functions front-ends                             *
 *                                                                      *
 ************************************************************************/

/**
 * htmlDocDump:
 * @f:  the FILE*
 * @cur:  the document
 *
 * Dump an HTML document to an open FILE.
 *
 * returns: the number of byte written or -1 in case of failure.
 */
int
htmlDocDump(FILE *f, xmlDocPtr cur) {
    xmlOutputBufferPtr buf;
    xmlCharEncodingHandlerPtr handler = NULL;
    const char *encoding;
    int ret;

    xmlInitParser();

    if ((cur == NULL) || (f == NULL)) {
        return(-1);
    }

    encoding = (const char *) htmlGetMetaEncoding(cur);

    if (encoding != NULL) {
        xmlCharEncoding enc;

        enc = xmlParseCharEncoding(encoding);
        if (enc != cur->charset) {
            if (cur->charset != XML_CHAR_ENCODING_UTF8) {
                /*
                 * Not supported yet
                 */
                return(-1);
            }

            handler = xmlFindCharEncodingHandler(encoding);
            if (handler == NULL)
                return(-1);
        } else {
            handler = xmlFindCharEncodingHandler(encoding);
        }
    }

    /*
     * Fallback to HTML or ASCII when the encoding is unspecified
     */
    if (handler == NULL)
        handler = xmlFindCharEncodingHandler("HTML");
    if (handler == NULL)
        handler = xmlFindCharEncodingHandler("ascii");

    buf = xmlOutputBufferCreateFile(f, handler);
    if (buf == NULL) return(-1);
    htmlDocContentDumpOutput(buf, cur, NULL);

    ret = xmlOutputBufferClose(buf);
    return(ret);
}

/**
 * htmlSaveFile:
 * @filename:  the filename (or URL)
 * @cur:  the document
 *
 * Dump an HTML document to a file. If @filename is "-" the stdout file is
 * used.
 * returns: the number of byte written or -1 in case of failure.
 */
int
htmlSaveFile(const char *filename, xmlDocPtr cur) {
    xmlOutputBufferPtr buf;
    xmlCharEncodingHandlerPtr handler = NULL;
    const char *encoding;
    int ret;

    if ((cur == NULL) || (filename == NULL))
        return(-1);
       
    xmlInitParser();

    encoding = (const char *) htmlGetMetaEncoding(cur);

    if (encoding != NULL) {
        xmlCharEncoding enc;

        enc = xmlParseCharEncoding(encoding);
        if (enc != cur->charset) {
            if (cur->charset != XML_CHAR_ENCODING_UTF8) {
                /*
                 * Not supported yet
                 */
                return(-1);
            }

            handler = xmlFindCharEncodingHandler(encoding);
            if (handler == NULL)
                return(-1);
        }
    }

    /*
     * Fallback to HTML or ASCII when the encoding is unspecified
     */
    if (handler == NULL)
        handler = xmlFindCharEncodingHandler("HTML");
    if (handler == NULL)
        handler = xmlFindCharEncodingHandler("ascii");

    /* 
     * save the content to a temp buffer.
     */
    buf = xmlOutputBufferCreateFilename(filename, handler, cur->compression);
    if (buf == NULL) return(0);

    htmlDocContentDumpOutput(buf, cur, NULL);

    ret = xmlOutputBufferClose(buf);
    return(ret);
}

/**
 * htmlSaveFileFormat:
 * @filename:  the filename
 * @cur:  the document
 * @format:  should formatting spaces been added
 * @encoding: the document encoding
 *
 * Dump an HTML document to a file using a given encoding.
 * 
 * returns: the number of byte written or -1 in case of failure.
 */
int
htmlSaveFileFormat(const char *filename, xmlDocPtr cur,
                   const char *encoding, int format) {
    xmlOutputBufferPtr buf;
    xmlCharEncodingHandlerPtr handler = NULL;
    int ret;

    if ((cur == NULL) || (filename == NULL))
        return(-1);
       
    xmlInitParser();

    if (encoding != NULL) {
        xmlCharEncoding enc;

        enc = xmlParseCharEncoding(encoding);
        if (enc != cur->charset) {
            if (cur->charset != XML_CHAR_ENCODING_UTF8) {
                /*
                 * Not supported yet
                 */
                return(-1);
            }

            handler = xmlFindCharEncodingHandler(encoding);
            if (handler == NULL)
                return(-1);
            htmlSetMetaEncoding(cur, (const xmlChar *) encoding);
        }
    } else {
        htmlSetMetaEncoding(cur, (const xmlChar *) "UTF-8");
    }

    /*
     * Fallback to HTML or ASCII when the encoding is unspecified
     */
    if (handler == NULL)
        handler = xmlFindCharEncodingHandler("HTML");
    if (handler == NULL)
        handler = xmlFindCharEncodingHandler("ascii");

    /* 
     * save the content to a temp buffer.
     */
    buf = xmlOutputBufferCreateFilename(filename, handler, 0);
    if (buf == NULL) return(0);

    htmlDocContentDumpFormatOutput(buf, cur, encoding, format);

    ret = xmlOutputBufferClose(buf);
    return(ret);
}

/**
 * htmlSaveFileEnc:
 * @filename:  the filename
 * @cur:  the document
 * @encoding: the document encoding
 *
 * Dump an HTML document to a file using a given encoding
 * and formatting returns/spaces are added.
 * 
 * returns: the number of byte written or -1 in case of failure.
 */
int
htmlSaveFileEnc(const char *filename, xmlDocPtr cur, const char *encoding) {
    return(htmlSaveFileFormat(filename, cur, encoding, 1));
}

#endif /* LIBXML_OUTPUT_ENABLED */

#define bottom_HTMLtree
#include "elfgcchack.h"
#endif /* LIBXML_HTML_ENABLED */
