Initial revision
[TestXSLT.git] / libxml2 / HTMLtree.c
1 /*
2  * HTMLtree.c : implementation of access function for an HTML tree.
3  *
4  * See Copyright for the status of this software.
5  *
6  * daniel@veillard.com
7  */
8
9
10 #define IN_LIBXML
11 #include "libxml.h"
12 #ifdef LIBXML_HTML_ENABLED
13
14 #include <string.h> /* for memset() only ! */
15
16 #ifdef HAVE_CTYPE_H
17 #include <ctype.h>
18 #endif
19 #ifdef HAVE_STDLIB_H
20 #include <stdlib.h>
21 #endif
22
23 #include <libxml/xmlmemory.h>
24 #include <libxml/HTMLparser.h>
25 #include <libxml/HTMLtree.h>
26 #include <libxml/entities.h>
27 #include <libxml/valid.h>
28 #include <libxml/xmlerror.h>
29 #include <libxml/parserInternals.h>
30 #include <libxml/globals.h>
31 #include <libxml/uri.h>
32
33 /************************************************************************
34  *                                                                      *
35  *              Getting/Setting encoding meta tags                      *
36  *                                                                      *
37  ************************************************************************/
38
39 /**
40  * htmlGetMetaEncoding:
41  * @doc:  the document
42  * 
43  * Encoding definition lookup in the Meta tags
44  *
45  * Returns the current encoding as flagged in the HTML source
46  */
47 const xmlChar *
48 htmlGetMetaEncoding(htmlDocPtr doc) {
49     htmlNodePtr cur;
50     const xmlChar *content;
51     const xmlChar *encoding;
52
53     if (doc == NULL)
54         return(NULL);
55     cur = doc->children;
56
57     /*
58      * Search the html
59      */
60     while (cur != NULL) {
61         if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
62             if (xmlStrEqual(cur->name, BAD_CAST"html"))
63                 break;
64             if (xmlStrEqual(cur->name, BAD_CAST"head"))
65                 goto found_head;
66             if (xmlStrEqual(cur->name, BAD_CAST"meta"))
67                 goto found_meta;
68         }
69         cur = cur->next;
70     }
71     if (cur == NULL)
72         return(NULL);
73     cur = cur->children;
74
75     /*
76      * Search the head
77      */
78     while (cur != NULL) {
79         if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
80             if (xmlStrEqual(cur->name, BAD_CAST"head"))
81                 break;
82             if (xmlStrEqual(cur->name, BAD_CAST"meta"))
83                 goto found_meta;
84         }
85         cur = cur->next;
86     }
87     if (cur == NULL)
88         return(NULL);
89 found_head:
90     cur = cur->children;
91
92     /*
93      * Search the meta elements
94      */
95 found_meta:
96     while (cur != NULL) {
97         if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
98             if (xmlStrEqual(cur->name, BAD_CAST"meta")) {
99                 xmlAttrPtr attr = cur->properties;
100                 int http;
101                 const xmlChar *value;
102
103                 content = NULL;
104                 http = 0;
105                 while (attr != NULL) {
106                     if ((attr->children != NULL) &&
107                         (attr->children->type == XML_TEXT_NODE) &&
108                         (attr->children->next == NULL)) {
109                         value = attr->children->content;
110                         if ((!xmlStrcasecmp(attr->name, BAD_CAST"http-equiv"))
111                          && (!xmlStrcasecmp(value, BAD_CAST"Content-Type")))
112                             http = 1;
113                         else if ((value != NULL)
114                          && (!xmlStrcasecmp(attr->name, BAD_CAST"content")))
115                             content = value;
116                         if ((http != 0) && (content != NULL))
117                             goto found_content;
118                     }
119                     attr = attr->next;
120                 }
121             }
122         }
123         cur = cur->next;
124     }
125     return(NULL);
126
127 found_content:
128     encoding = xmlStrstr(content, BAD_CAST"charset=");
129     if (encoding == NULL) 
130         encoding = xmlStrstr(content, BAD_CAST"Charset=");
131     if (encoding == NULL) 
132         encoding = xmlStrstr(content, BAD_CAST"CHARSET=");
133     if (encoding != NULL) {
134         encoding += 8;
135     } else {
136         encoding = xmlStrstr(content, BAD_CAST"charset =");
137         if (encoding == NULL) 
138             encoding = xmlStrstr(content, BAD_CAST"Charset =");
139         if (encoding == NULL) 
140             encoding = xmlStrstr(content, BAD_CAST"CHARSET =");
141         if (encoding != NULL)
142             encoding += 9;
143     }
144     if (encoding != NULL) {
145         while ((*encoding == ' ') || (*encoding == '\t')) encoding++;
146     }
147     return(encoding);
148 }
149
150 /**
151  * htmlSetMetaEncoding:
152  * @doc:  the document
153  * @encoding:  the encoding string
154  * 
155  * Sets the current encoding in the Meta tags
156  * NOTE: this will not change the document content encoding, just
157  * the META flag associated.
158  *
159  * Returns 0 in case of success and -1 in case of error
160  */
161 int
162 htmlSetMetaEncoding(htmlDocPtr doc, const xmlChar *encoding) {
163     htmlNodePtr cur, meta;
164     const xmlChar *content;
165     char newcontent[100];
166
167
168     if (doc == NULL)
169         return(-1);
170
171     if (encoding != NULL) {
172         snprintf(newcontent, sizeof(newcontent), "text/html; charset=%s",
173                 encoding);
174         newcontent[sizeof(newcontent) - 1] = 0;
175     }
176
177     cur = doc->children;
178
179     /*
180      * Search the html
181      */
182     while (cur != NULL) {
183         if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
184             if (xmlStrcasecmp(cur->name, BAD_CAST"html") == 0)
185                 break;
186             if (xmlStrcasecmp(cur->name, BAD_CAST"head") == 0)
187                 goto found_head;
188             if (xmlStrcasecmp(cur->name, BAD_CAST"meta") == 0)
189                 goto found_meta;
190         }
191         cur = cur->next;
192     }
193     if (cur == NULL)
194         return(-1);
195     cur = cur->children;
196
197     /*
198      * Search the head
199      */
200     while (cur != NULL) {
201         if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
202             if (xmlStrcasecmp(cur->name, BAD_CAST"head") == 0)
203                 break;
204             if (xmlStrcasecmp(cur->name, BAD_CAST"meta") == 0)
205                 goto found_meta;
206         }
207         cur = cur->next;
208     }
209     if (cur == NULL)
210         return(-1);
211 found_head:
212     if (cur->children == NULL) {
213         if (encoding == NULL)
214             return(0);
215         meta = xmlNewDocNode(doc, NULL, BAD_CAST"meta", NULL);
216         xmlAddChild(cur, meta);
217         xmlNewProp(meta, BAD_CAST"http-equiv", BAD_CAST"Content-Type");
218         xmlNewProp(meta, BAD_CAST"content", BAD_CAST newcontent);
219         return(0);
220     }
221     cur = cur->children;
222
223 found_meta:
224     if (encoding != NULL) {
225         /*
226          * Create a new Meta element with the right attributes
227          */
228
229         meta = xmlNewDocNode(doc, NULL, BAD_CAST"meta", NULL);
230         xmlAddPrevSibling(cur, meta);
231         xmlNewProp(meta, BAD_CAST"http-equiv", BAD_CAST"Content-Type");
232         xmlNewProp(meta, BAD_CAST"content", BAD_CAST newcontent);
233     }
234
235     /*
236      * Search and destroy all the remaining the meta elements carrying
237      * encoding informations
238      */
239     while (cur != NULL) {
240         if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
241             if (xmlStrcasecmp(cur->name, BAD_CAST"meta") == 0) {
242                 xmlAttrPtr attr = cur->properties;
243                 int http;
244                 const xmlChar *value;
245
246                 content = NULL;
247                 http = 0;
248                 while (attr != NULL) {
249                     if ((attr->children != NULL) &&
250                         (attr->children->type == XML_TEXT_NODE) &&
251                         (attr->children->next == NULL)) {
252                         value = attr->children->content;
253                         if ((!xmlStrcasecmp(attr->name, BAD_CAST"http-equiv"))
254                          && (!xmlStrcasecmp(value, BAD_CAST"Content-Type")))
255                             http = 1;
256                         else 
257                         {
258                            if ((value != NULL) && 
259                                 (!xmlStrcasecmp(attr->name, BAD_CAST"content")))
260                               content = value;
261                         }
262                         if ((http != 0) && (content != NULL))
263                             break;
264                     }
265                     attr = attr->next;
266                 }
267                 if ((http != 0) && (content != NULL)) {
268                     meta = cur;
269                     cur = cur->next;
270                     xmlUnlinkNode(meta);
271                     xmlFreeNode(meta);
272                     continue;
273                 }
274
275             }
276         }
277         cur = cur->next;
278     }
279     return(0);
280 }
281
282 /**
283  * booleanHTMLAttrs:
284  *
285  * These are the HTML attributes which will be output
286  * in minimized form, i.e. <option selected="selected"> will be
287  * output as <option selected>, as per XSLT 1.0 16.2 "HTML Output Method"
288  *
289  */
290 static const char* htmlBooleanAttrs[] = {
291   "checked", "compact", "declare", "defer", "disabled", "ismap",
292   "multiple", "nohref", "noresize", "noshade", "nowrap", "readonly",
293   "selected", NULL
294 };
295
296
297 /**
298  * htmlIsBooleanAttr:
299  * @name:  the name of the attribute to check
300  *
301  * Determine if a given attribute is a boolean attribute.
302  * 
303  * returns: false if the attribute is not boolean, true otherwise.
304  */
305 int
306 htmlIsBooleanAttr(const xmlChar *name)
307 {
308     int i = 0;
309
310     while (htmlBooleanAttrs[i] != NULL) {
311         if (xmlStrcasecmp((const xmlChar *)htmlBooleanAttrs[i], name) == 0)
312             return 1;
313         i++;
314     }
315     return 0;
316 }
317
318 /************************************************************************
319  *                                                                      *
320  *              Dumping HTML tree content to a simple buffer            *
321  *                                                                      *
322  ************************************************************************/
323
324 static int
325 htmlNodeDumpFormat(xmlBufferPtr buf, xmlDocPtr doc, xmlNodePtr cur,
326                    int format);
327
328 /**
329  * htmlNodeDumpFormat:
330  * @buf:  the HTML buffer output
331  * @doc:  the document
332  * @cur:  the current node
333  * @format:  should formatting spaces been added
334  *
335  * Dump an HTML node, recursive behaviour,children are printed too.
336  *
337  * Returns the number of byte written or -1 in case of error
338  */
339 static int
340 htmlNodeDumpFormat(xmlBufferPtr buf, xmlDocPtr doc, xmlNodePtr cur,
341                    int format) {
342     unsigned int use;
343     int ret;
344     xmlOutputBufferPtr outbuf;
345
346     if (cur == NULL) {
347         return (-1);
348     }
349     if (buf == NULL) {
350         return (-1);
351     }
352     outbuf = (xmlOutputBufferPtr) xmlMalloc(sizeof(xmlOutputBuffer));
353     if (outbuf == NULL) {
354         xmlGenericError(xmlGenericErrorContext,
355                         "htmlNodeDumpFormat: out of memory!\n");
356         return (-1);
357     }
358     memset(outbuf, 0, (size_t) sizeof(xmlOutputBuffer));
359     outbuf->buffer = buf;
360     outbuf->encoder = NULL;
361     outbuf->writecallback = NULL;
362     outbuf->closecallback = NULL;
363     outbuf->context = NULL;
364     outbuf->written = 0;
365
366     use = buf->use;
367     htmlNodeDumpFormatOutput(outbuf, doc, cur, NULL, format);
368     xmlFree(outbuf);
369     ret = buf->use - use;
370     return (ret);
371 }
372
373 /**
374  * htmlNodeDump:
375  * @buf:  the HTML buffer output
376  * @doc:  the document
377  * @cur:  the current node
378  *
379  * Dump an HTML node, recursive behaviour,children are printed too,
380  * and formatting returns are added.
381  *
382  * Returns the number of byte written or -1 in case of error
383  */
384 int
385 htmlNodeDump(xmlBufferPtr buf, xmlDocPtr doc, xmlNodePtr cur) {
386     return(htmlNodeDumpFormat(buf, doc, cur, 1));
387 }
388
389 /**
390  * htmlNodeDumpFileFormat:
391  * @out:  the FILE pointer
392  * @doc:  the document
393  * @cur:  the current node
394  * @encoding: the document encoding
395  * @format:  should formatting spaces been added
396  *
397  * Dump an HTML node, recursive behaviour,children are printed too.
398  *
399  * TODO: if encoding == NULL try to save in the doc encoding
400  *
401  * returns: the number of byte written or -1 in case of failure.
402  */
403 int
404 htmlNodeDumpFileFormat(FILE *out, xmlDocPtr doc,
405                        xmlNodePtr cur, const char *encoding, int format) {
406     xmlOutputBufferPtr buf;
407     xmlCharEncodingHandlerPtr handler = NULL;
408     int ret;
409
410     if (encoding != NULL) {
411         xmlCharEncoding enc;
412
413         enc = xmlParseCharEncoding(encoding);
414         if (enc != XML_CHAR_ENCODING_UTF8) {
415             handler = xmlFindCharEncodingHandler(encoding);
416             if (handler == NULL)
417                 return(-1);
418         }
419     }
420
421     /*
422      * Fallback to HTML or ASCII when the encoding is unspecified
423      */
424     if (handler == NULL)
425         handler = xmlFindCharEncodingHandler("HTML");
426     if (handler == NULL)
427         handler = xmlFindCharEncodingHandler("ascii");
428
429     /* 
430      * save the content to a temp buffer.
431      */
432     buf = xmlOutputBufferCreateFile(out, handler);
433     if (buf == NULL) return(0);
434
435     htmlNodeDumpFormatOutput(buf, doc, cur, encoding, format);
436
437     ret = xmlOutputBufferClose(buf);
438     return(ret);
439 }
440
441 /**
442  * htmlNodeDumpFile:
443  * @out:  the FILE pointer
444  * @doc:  the document
445  * @cur:  the current node
446  *
447  * Dump an HTML node, recursive behaviour,children are printed too,
448  * and formatting returns are added.
449  */
450 void
451 htmlNodeDumpFile(FILE *out, xmlDocPtr doc, xmlNodePtr cur) {
452     htmlNodeDumpFileFormat(out, doc, cur, NULL, 1);
453 }
454
455 /**
456  * htmlDocDumpMemory:
457  * @cur:  the document
458  * @mem:  OUT: the memory pointer
459  * @size:  OUT: the memory length
460  *
461  * Dump an HTML document in memory and return the xmlChar * and it's size.
462  * It's up to the caller to free the memory.
463  */
464 void
465 htmlDocDumpMemory(xmlDocPtr cur, xmlChar**mem, int *size) {
466     xmlOutputBufferPtr buf;
467     xmlCharEncodingHandlerPtr handler = NULL;
468     const char *encoding;
469
470     if (cur == NULL) {
471 #ifdef DEBUG_TREE
472         xmlGenericError(xmlGenericErrorContext,
473                 "htmlDocDumpMemory : document == NULL\n");
474 #endif
475         *mem = NULL;
476         *size = 0;
477         return;
478     }
479
480     encoding = (const char *) htmlGetMetaEncoding(cur);
481
482     if (encoding != NULL) {
483         xmlCharEncoding enc;
484
485         enc = xmlParseCharEncoding(encoding);
486         if (enc != cur->charset) {
487             if (cur->charset != XML_CHAR_ENCODING_UTF8) {
488                 /*
489                  * Not supported yet
490                  */
491                 *mem = NULL;
492                 *size = 0;
493                 return;
494             }
495
496             handler = xmlFindCharEncodingHandler(encoding);
497             if (handler == NULL) {
498                 *mem = NULL;
499                 *size = 0;
500                 return;
501             }
502         }
503     }
504
505     /*
506      * Fallback to HTML or ASCII when the encoding is unspecified
507      */
508     if (handler == NULL)
509         handler = xmlFindCharEncodingHandler("HTML");
510     if (handler == NULL)
511         handler = xmlFindCharEncodingHandler("ascii");
512
513     buf = xmlAllocOutputBuffer(handler);
514     if (buf == NULL) {
515         *mem = NULL;
516         *size = 0;
517         return;
518     }
519
520     htmlDocContentDumpOutput(buf, cur, NULL);
521     xmlOutputBufferFlush(buf);
522     if (buf->conv != NULL) {
523         *size = buf->conv->use;
524         *mem = xmlStrndup(buf->conv->content, *size);
525     } else {
526         *size = buf->buffer->use;
527         *mem = xmlStrndup(buf->buffer->content, *size);
528     }
529     (void)xmlOutputBufferClose(buf);
530 }
531
532
533 /************************************************************************
534  *                                                                      *
535  *              Dumping HTML tree content to an I/O output buffer       *
536  *                                                                      *
537  ************************************************************************/
538
539 void xmlNsListDumpOutput(xmlOutputBufferPtr buf, xmlNsPtr cur);
540
541 /**
542  * htmlDtdDumpOutput:
543  * @buf:  the HTML buffer output
544  * @doc:  the document
545  * @encoding:  the encoding string
546  * 
547  * TODO: check whether encoding is needed
548  *
549  * Dump the HTML document DTD, if any.
550  */
551 static void
552 htmlDtdDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr doc,
553                   const char *encoding ATTRIBUTE_UNUSED) {
554     xmlDtdPtr cur = doc->intSubset;
555
556     if (cur == NULL) {
557         xmlGenericError(xmlGenericErrorContext,
558                 "htmlDtdDumpOutput : no internal subset\n");
559         return;
560     }
561     xmlOutputBufferWriteString(buf, "<!DOCTYPE ");
562     xmlOutputBufferWriteString(buf, (const char *)cur->name);
563     if (cur->ExternalID != NULL) {
564         xmlOutputBufferWriteString(buf, " PUBLIC ");
565         xmlBufferWriteQuotedString(buf->buffer, cur->ExternalID);
566         if (cur->SystemID != NULL) {
567             xmlOutputBufferWriteString(buf, " ");
568             xmlBufferWriteQuotedString(buf->buffer, cur->SystemID);
569         } 
570     }  else if (cur->SystemID != NULL) {
571         xmlOutputBufferWriteString(buf, " SYSTEM ");
572         xmlBufferWriteQuotedString(buf->buffer, cur->SystemID);
573     }
574     xmlOutputBufferWriteString(buf, ">\n");
575 }
576
577 /**
578  * htmlAttrDumpOutput:
579  * @buf:  the HTML buffer output
580  * @doc:  the document
581  * @cur:  the attribute pointer
582  * @encoding:  the encoding string
583  *
584  * Dump an HTML attribute
585  */
586 static void
587 htmlAttrDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr doc, xmlAttrPtr cur,
588                    const char *encoding ATTRIBUTE_UNUSED) {
589     xmlChar *value;
590
591     /*
592      * TODO: The html output method should not escape a & character
593      *       occurring in an attribute value immediately followed by
594      *       a { character (see Section B.7.1 of the HTML 4.0 Recommendation).
595      */
596
597     if (cur == NULL) {
598         xmlGenericError(xmlGenericErrorContext,
599                 "htmlAttrDumpOutput : property == NULL\n");
600         return;
601     }
602     xmlOutputBufferWriteString(buf, " ");
603     xmlOutputBufferWriteString(buf, (const char *)cur->name);
604     if ((cur->children != NULL) && (!htmlIsBooleanAttr(cur->name))) {
605         value = xmlNodeListGetString(doc, cur->children, 0);
606         if (value) {
607             xmlOutputBufferWriteString(buf, "=");
608             if ((!xmlStrcasecmp(cur->name, BAD_CAST "href")) ||
609                 (!xmlStrcasecmp(cur->name, BAD_CAST "src"))) {
610                 xmlChar *escaped;
611                 xmlChar *tmp = value;
612
613                 while (IS_BLANK(*tmp)) tmp++;
614
615                 escaped = xmlURIEscapeStr(tmp, BAD_CAST"@/:=?;#%&");
616                 if (escaped != NULL) {
617                     xmlBufferWriteQuotedString(buf->buffer, escaped);
618                     xmlFree(escaped);
619                 } else {
620                     xmlBufferWriteQuotedString(buf->buffer, value);
621                 }
622             } else {
623                 xmlBufferWriteQuotedString(buf->buffer, value);
624             }
625             xmlFree(value);
626         } else  {
627             xmlOutputBufferWriteString(buf, "=\"\"");
628         }
629     }
630 }
631
632 /**
633  * htmlAttrListDumpOutput:
634  * @buf:  the HTML buffer output
635  * @doc:  the document
636  * @cur:  the first attribute pointer
637  * @encoding:  the encoding string
638  *
639  * Dump a list of HTML attributes
640  */
641 static void
642 htmlAttrListDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr doc, xmlAttrPtr cur, const char *encoding) {
643     if (cur == NULL) {
644         xmlGenericError(xmlGenericErrorContext,
645                 "htmlAttrListDumpOutput : property == NULL\n");
646         return;
647     }
648     while (cur != NULL) {
649         htmlAttrDumpOutput(buf, doc, cur, encoding);
650         cur = cur->next;
651     }
652 }
653
654
655
656 /**
657  * htmlNodeListDumpOutput:
658  * @buf:  the HTML buffer output
659  * @doc:  the document
660  * @cur:  the first node
661  * @encoding:  the encoding string
662  * @format:  should formatting spaces been added
663  *
664  * Dump an HTML node list, recursive behaviour,children are printed too.
665  */
666 static void
667 htmlNodeListDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr doc,
668                        xmlNodePtr cur, const char *encoding, int format) {
669     if (cur == NULL) {
670         xmlGenericError(xmlGenericErrorContext,
671                 "htmlNodeListDumpOutput : node == NULL\n");
672         return;
673     }
674     while (cur != NULL) {
675         htmlNodeDumpFormatOutput(buf, doc, cur, encoding, format);
676         cur = cur->next;
677     }
678 }
679
680 /**
681  * htmlNodeDumpFormatOutput:
682  * @buf:  the HTML buffer output
683  * @doc:  the document
684  * @cur:  the current node
685  * @encoding:  the encoding string
686  * @format:  should formatting spaces been added
687  *
688  * Dump an HTML node, recursive behaviour,children are printed too.
689  */
690 void
691 htmlNodeDumpFormatOutput(xmlOutputBufferPtr buf, xmlDocPtr doc,
692                          xmlNodePtr cur, const char *encoding, int format) {
693     const htmlElemDesc * info;
694
695     if (cur == NULL) {
696         xmlGenericError(xmlGenericErrorContext,
697                 "htmlNodeDumpFormatOutput : node == NULL\n");
698         return;
699     }
700     /*
701      * Special cases.
702      */
703     if (cur->type == XML_DTD_NODE)
704         return;
705     if (cur->type == XML_HTML_DOCUMENT_NODE) {
706         htmlDocContentDumpOutput(buf, (xmlDocPtr) cur, encoding);
707         return;
708     }
709     if (cur->type == HTML_TEXT_NODE) {
710         if (cur->content != NULL) {
711             if (((cur->name == (const xmlChar *)xmlStringText) ||
712                  (cur->name != (const xmlChar *)xmlStringTextNoenc)) &&
713                 ((cur->parent == NULL) ||
714                  ((xmlStrcasecmp(cur->parent->name, BAD_CAST "script")) &&
715                   (xmlStrcasecmp(cur->parent->name, BAD_CAST "style"))))) {
716                 xmlChar *buffer;
717
718                 buffer = xmlEncodeEntitiesReentrant(doc, cur->content);
719                 if (buffer != NULL) {
720                     xmlOutputBufferWriteString(buf, (const char *)buffer);
721                     xmlFree(buffer);
722                 }
723             } else {
724                 xmlOutputBufferWriteString(buf, (const char *)cur->content);
725             }
726         }
727         return;
728     }
729     if (cur->type == HTML_COMMENT_NODE) {
730         if (cur->content != NULL) {
731             xmlOutputBufferWriteString(buf, "<!--");
732             xmlOutputBufferWriteString(buf, (const char *)cur->content);
733             xmlOutputBufferWriteString(buf, "-->");
734         }
735         return;
736     }
737     if (cur->type == HTML_PI_NODE) {
738         if (cur->name == NULL)
739             return;
740         xmlOutputBufferWriteString(buf, "<?");
741         xmlOutputBufferWriteString(buf, (const char *)cur->name);
742         if (cur->content != NULL) {
743             xmlOutputBufferWriteString(buf, " ");
744             xmlOutputBufferWriteString(buf, (const char *)cur->content);
745         }
746         xmlOutputBufferWriteString(buf, ">");
747         return;
748     }
749     if (cur->type == HTML_ENTITY_REF_NODE) {
750         xmlOutputBufferWriteString(buf, "&");
751         xmlOutputBufferWriteString(buf, (const char *)cur->name);
752         xmlOutputBufferWriteString(buf, ";");
753         return;
754     }
755     if (cur->type == HTML_PRESERVE_NODE) {
756         if (cur->content != NULL) {
757             xmlOutputBufferWriteString(buf, (const char *)cur->content);
758         }
759         return;
760     }
761
762     /*
763      * Get specific HTML info for that node.
764      */
765     if (cur->ns == NULL)
766         info = htmlTagLookup(cur->name);
767     else
768         info = NULL;
769
770     xmlOutputBufferWriteString(buf, "<");
771     if ((cur->ns != NULL) && (cur->ns->prefix != NULL)) {
772         xmlOutputBufferWriteString(buf, (const char *)cur->ns->prefix);
773         xmlOutputBufferWriteString(buf, ":");
774     }
775     xmlOutputBufferWriteString(buf, (const char *)cur->name);
776     if (cur->nsDef)
777         xmlNsListDumpOutput(buf, cur->nsDef);
778     if (cur->properties != NULL)
779         htmlAttrListDumpOutput(buf, doc, cur->properties, encoding);
780
781     if ((info != NULL) && (info->empty)) {
782         xmlOutputBufferWriteString(buf, ">");
783         if ((format) && (!info->isinline) && (cur->next != NULL)) {
784             if ((cur->next->type != HTML_TEXT_NODE) &&
785                 (cur->next->type != HTML_ENTITY_REF_NODE) &&
786                 (cur->parent != NULL) &&
787                 (!xmlStrEqual(cur->parent->name, BAD_CAST "pre")))
788                 xmlOutputBufferWriteString(buf, "\n");
789         }
790         return;
791     }
792     if (((cur->type == XML_ELEMENT_NODE) || (cur->content == NULL)) &&
793         (cur->children == NULL)) {
794         if ((info != NULL) && (info->saveEndTag != 0) &&
795             (xmlStrcmp(BAD_CAST info->name, BAD_CAST "html")) &&
796             (xmlStrcmp(BAD_CAST info->name, BAD_CAST "body"))) {
797             xmlOutputBufferWriteString(buf, ">");
798         } else {
799             xmlOutputBufferWriteString(buf, "></");
800             xmlOutputBufferWriteString(buf, (const char *)cur->name);
801             xmlOutputBufferWriteString(buf, ">");
802         }
803         if ((format) && (cur->next != NULL) &&
804             (info != NULL) && (!info->isinline)) {
805             if ((cur->next->type != HTML_TEXT_NODE) &&
806                 (cur->next->type != HTML_ENTITY_REF_NODE) &&
807                 (cur->parent != NULL) &&
808                 (!xmlStrEqual(cur->parent->name, BAD_CAST "pre")))
809                 xmlOutputBufferWriteString(buf, "\n");
810         }
811         return;
812     }
813     xmlOutputBufferWriteString(buf, ">");
814     if ((cur->type != XML_ELEMENT_NODE) &&
815         (cur->content != NULL)) {
816             /*
817              * Uses the OutputBuffer property to automatically convert
818              * invalids to charrefs
819              */
820
821             xmlOutputBufferWriteString(buf, (const char *) cur->content);
822     }
823     if (cur->children != NULL) {
824         if ((format) && (info != NULL) && (!info->isinline) &&
825             (cur->children->type != HTML_TEXT_NODE) &&
826             (cur->children->type != HTML_ENTITY_REF_NODE) &&
827             (cur->children != cur->last) &&
828             (!xmlStrEqual(cur->name, BAD_CAST "pre")))
829             xmlOutputBufferWriteString(buf, "\n");
830         htmlNodeListDumpOutput(buf, doc, cur->children, encoding, format);
831         if ((format) && (info != NULL) && (!info->isinline) &&
832             (cur->last->type != HTML_TEXT_NODE) &&
833             (cur->last->type != HTML_ENTITY_REF_NODE) &&
834             (cur->children != cur->last) &&
835             (!xmlStrEqual(cur->name, BAD_CAST "pre")))
836             xmlOutputBufferWriteString(buf, "\n");
837     }
838     xmlOutputBufferWriteString(buf, "</");
839     if ((cur->ns != NULL) && (cur->ns->prefix != NULL)) {
840         xmlOutputBufferWriteString(buf, (const char *)cur->ns->prefix);
841         xmlOutputBufferWriteString(buf, ":");
842     }
843     xmlOutputBufferWriteString(buf, (const char *)cur->name);
844     xmlOutputBufferWriteString(buf, ">");
845     if ((format) && (info != NULL) && (!info->isinline) &&
846         (cur->next != NULL)) {
847         if ((cur->next->type != HTML_TEXT_NODE) &&
848             (cur->next->type != HTML_ENTITY_REF_NODE) &&
849             (cur->parent != NULL) &&
850             (!xmlStrEqual(cur->parent->name, BAD_CAST "pre")))
851             xmlOutputBufferWriteString(buf, "\n");
852     }
853 }
854
855 /**
856  * htmlNodeDumpOutput:
857  * @buf:  the HTML buffer output
858  * @doc:  the document
859  * @cur:  the current node
860  * @encoding:  the encoding string
861  *
862  * Dump an HTML node, recursive behaviour,children are printed too,
863  * and formatting returns/spaces are added.
864  */
865 void
866 htmlNodeDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr doc,
867                    xmlNodePtr cur, const char *encoding) {
868     htmlNodeDumpFormatOutput(buf, doc, cur, encoding, 1);
869 }
870
871 /**
872  * htmlDocContentDumpFormatOutput:
873  * @buf:  the HTML buffer output
874  * @cur:  the document
875  * @encoding:  the encoding string
876  * @format:  should formatting spaces been added
877  *
878  * Dump an HTML document.
879  */
880 void
881 htmlDocContentDumpFormatOutput(xmlOutputBufferPtr buf, xmlDocPtr cur,
882                                const char *encoding, int format) {
883     int type;
884
885     /*
886      * force to output the stuff as HTML, especially for entities
887      */
888     type = cur->type;
889     cur->type = XML_HTML_DOCUMENT_NODE;
890     if (cur->intSubset != NULL) {
891         htmlDtdDumpOutput(buf, cur, NULL);
892     }
893     if (cur->children != NULL) {
894         htmlNodeListDumpOutput(buf, cur, cur->children, encoding, format);
895     }
896     xmlOutputBufferWriteString(buf, "\n");
897     cur->type = (xmlElementType) type;
898 }
899
900 /**
901  * htmlDocContentDumpOutput:
902  * @buf:  the HTML buffer output
903  * @cur:  the document
904  * @encoding:  the encoding string
905  *
906  * Dump an HTML document. Formating return/spaces are added.
907  */
908 void
909 htmlDocContentDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr cur,
910                          const char *encoding) {
911     htmlDocContentDumpFormatOutput(buf, cur, encoding, 1);
912 }
913
914 /************************************************************************
915  *                                                                      *
916  *              Saving functions front-ends                             *
917  *                                                                      *
918  ************************************************************************/
919
920 /**
921  * htmlDocDump:
922  * @f:  the FILE*
923  * @cur:  the document
924  *
925  * Dump an HTML document to an open FILE.
926  *
927  * returns: the number of byte written or -1 in case of failure.
928  */
929 int
930 htmlDocDump(FILE *f, xmlDocPtr cur) {
931     xmlOutputBufferPtr buf;
932     xmlCharEncodingHandlerPtr handler = NULL;
933     const char *encoding;
934     int ret;
935
936     if (cur == NULL) {
937 #ifdef DEBUG_TREE
938         xmlGenericError(xmlGenericErrorContext,
939                 "htmlDocDump : document == NULL\n");
940 #endif
941         return(-1);
942     }
943
944     encoding = (const char *) htmlGetMetaEncoding(cur);
945
946     if (encoding != NULL) {
947         xmlCharEncoding enc;
948
949         enc = xmlParseCharEncoding(encoding);
950         if (enc != cur->charset) {
951             if (cur->charset != XML_CHAR_ENCODING_UTF8) {
952                 /*
953                  * Not supported yet
954                  */
955                 return(-1);
956             }
957
958             handler = xmlFindCharEncodingHandler(encoding);
959             if (handler == NULL)
960                 return(-1);
961         }
962     }
963
964     /*
965      * Fallback to HTML or ASCII when the encoding is unspecified
966      */
967     if (handler == NULL)
968         handler = xmlFindCharEncodingHandler("HTML");
969     if (handler == NULL)
970         handler = xmlFindCharEncodingHandler("ascii");
971
972     buf = xmlOutputBufferCreateFile(f, handler);
973     if (buf == NULL) return(-1);
974     htmlDocContentDumpOutput(buf, cur, NULL);
975
976     ret = xmlOutputBufferClose(buf);
977     return(ret);
978 }
979
980 /**
981  * htmlSaveFile:
982  * @filename:  the filename (or URL)
983  * @cur:  the document
984  *
985  * Dump an HTML document to a file. If @filename is "-" the stdout file is
986  * used.
987  * returns: the number of byte written or -1 in case of failure.
988  */
989 int
990 htmlSaveFile(const char *filename, xmlDocPtr cur) {
991     xmlOutputBufferPtr buf;
992     xmlCharEncodingHandlerPtr handler = NULL;
993     const char *encoding;
994     int ret;
995
996     encoding = (const char *) htmlGetMetaEncoding(cur);
997
998     if (encoding != NULL) {
999         xmlCharEncoding enc;
1000
1001         enc = xmlParseCharEncoding(encoding);
1002         if (enc != cur->charset) {
1003             if (cur->charset != XML_CHAR_ENCODING_UTF8) {
1004                 /*
1005                  * Not supported yet
1006                  */
1007                 return(-1);
1008             }
1009
1010             handler = xmlFindCharEncodingHandler(encoding);
1011             if (handler == NULL)
1012                 return(-1);
1013         }
1014     }
1015
1016     /*
1017      * Fallback to HTML or ASCII when the encoding is unspecified
1018      */
1019     if (handler == NULL)
1020         handler = xmlFindCharEncodingHandler("HTML");
1021     if (handler == NULL)
1022         handler = xmlFindCharEncodingHandler("ascii");
1023
1024     /* 
1025      * save the content to a temp buffer.
1026      */
1027     buf = xmlOutputBufferCreateFilename(filename, handler, cur->compression);
1028     if (buf == NULL) return(0);
1029
1030     htmlDocContentDumpOutput(buf, cur, NULL);
1031
1032     ret = xmlOutputBufferClose(buf);
1033     return(ret);
1034 }
1035
1036 /**
1037  * htmlSaveFileFormat:
1038  * @filename:  the filename
1039  * @cur:  the document
1040  * @format:  should formatting spaces been added
1041  * @encoding: the document encoding
1042  *
1043  * Dump an HTML document to a file using a given encoding.
1044  * 
1045  * returns: the number of byte written or -1 in case of failure.
1046  */
1047 int
1048 htmlSaveFileFormat(const char *filename, xmlDocPtr cur,
1049                    const char *encoding, int format) {
1050     xmlOutputBufferPtr buf;
1051     xmlCharEncodingHandlerPtr handler = NULL;
1052     int ret;
1053
1054     if (encoding != NULL) {
1055         xmlCharEncoding enc;
1056
1057         enc = xmlParseCharEncoding(encoding);
1058         if (enc != cur->charset) {
1059             if (cur->charset != XML_CHAR_ENCODING_UTF8) {
1060                 /*
1061                  * Not supported yet
1062                  */
1063                 return(-1);
1064             }
1065
1066             handler = xmlFindCharEncodingHandler(encoding);
1067             if (handler == NULL)
1068                 return(-1);
1069             htmlSetMetaEncoding(cur, (const xmlChar *) encoding);
1070         }
1071     } else {
1072         htmlSetMetaEncoding(cur, (const xmlChar *) "UTF-8");
1073     }
1074
1075     /*
1076      * Fallback to HTML or ASCII when the encoding is unspecified
1077      */
1078     if (handler == NULL)
1079         handler = xmlFindCharEncodingHandler("HTML");
1080     if (handler == NULL)
1081         handler = xmlFindCharEncodingHandler("ascii");
1082
1083     /* 
1084      * save the content to a temp buffer.
1085      */
1086     buf = xmlOutputBufferCreateFilename(filename, handler, 0);
1087     if (buf == NULL) return(0);
1088
1089     htmlDocContentDumpFormatOutput(buf, cur, encoding, format);
1090
1091     ret = xmlOutputBufferClose(buf);
1092     return(ret);
1093 }
1094
1095 /**
1096  * htmlSaveFileEnc:
1097  * @filename:  the filename
1098  * @cur:  the document
1099  * @encoding: the document encoding
1100  *
1101  * Dump an HTML document to a file using a given encoding
1102  * and formatting returns/spaces are added.
1103  * 
1104  * returns: the number of byte written or -1 in case of failure.
1105  */
1106 int
1107 htmlSaveFileEnc(const char *filename, xmlDocPtr cur, const char *encoding) {
1108     return(htmlSaveFileFormat(filename, cur, encoding, 1));
1109 }
1110
1111
1112
1113 #endif /* LIBXML_HTML_ENABLED */