/* * html_read.c * * Copyright (C) 1999, 2000 Rasca, Berlin * EMail: thron@gmx.de * Copyright (c) 2001 Andreas J. Guelzow * EMail: aguelzow@taliesin.ca * Copyright (c) 2002 Jody Goldberg * EMail: jody@gnome.org * * Contributors : * Almer S. Tigelaar * Andreas J. Guelzow * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include "html.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define CC2XML(s) ((const xmlChar *)(s)) #define C2XML(s) ((xmlChar *)(s)) #define CXML2C(s) ((const char *)(s)) #define XML2C(s) ((char *)(s)) typedef struct { Sheet *sheet; int row; } GnmHtmlTableCtxt; static Sheet * html_get_sheet (char const *name, Workbook *wb) { Sheet *sheet = NULL; if (name) { sheet = workbook_sheet_by_name (wb, name); if (sheet == NULL) { sheet = sheet_new (wb, name); workbook_sheet_attach (wb, sheet, NULL); } } else sheet = workbook_sheet_add (wb, NULL, FALSE); return sheet; } static void html_append_text (GString *buf, const xmlChar *text) { const xmlChar *p; while (*text) { while (g_ascii_isspace (*text)) text++; if (*text) { for (p = text; *p && !g_ascii_isspace (*p); p++) ; if (buf->len > 0) g_string_append_c (buf, ' '); g_string_append_len (buf, text, p - text); text = p; } } } static void html_read_content (htmlNodePtr cur, GString *buf, GnmStyle *mstyle, xmlBufferPtr a_buf, gboolean first, htmlDocPtr doc) { htmlNodePtr ptr; for (ptr = cur->children; ptr != NULL ; ptr = ptr->next) { if (ptr->type == XML_TEXT_NODE) { html_append_text (buf, ptr->content); } else if (ptr->type == XML_ELEMENT_NODE) { if (first) { if (xmlStrEqual (ptr->name, CC2XML ("i")) || xmlStrEqual (ptr->name, CC2XML ("em"))) mstyle_set_font_italic (mstyle, TRUE); if (xmlStrEqual (ptr->name, CC2XML ("b"))) mstyle_set_font_bold (mstyle, TRUE); } if (xmlStrEqual (ptr->name, CC2XML ("a"))) { xmlAttrPtr props; props = ptr->properties; while (props) { if (xmlStrEqual (props->name, CC2XML ("href")) && props->children) { htmlNodeDump (a_buf, doc, props->children); xmlBufferAdd (a_buf, CC2XML ("\n"), -1); } props = props->next; } } if (xmlStrEqual (ptr->name, CC2XML ("img"))) { xmlAttrPtr props; props = ptr->properties; while (props) { if (xmlStrEqual (props->name, CC2XML ("src")) && props->children) { htmlNodeDump (a_buf, doc, props->children); xmlBufferAdd (a_buf, CC2XML ("\n"), -1); } props = props->next; } } html_read_content (ptr, buf, mstyle, a_buf, first, doc); } first = FALSE; } } static void html_read_row (htmlNodePtr cur, htmlDocPtr doc, GnmHtmlTableCtxt *tc) { htmlNodePtr ptr; int col = -1; for (ptr = cur->children; ptr != NULL ; ptr = ptr->next) { if (xmlStrEqual (ptr->name, CC2XML ("td")) || xmlStrEqual (ptr->name, CC2XML ("th"))) { GString *buf; xmlBufferPtr a_buf; xmlAttrPtr props; int colspan = 1; int rowspan = 1; GnmCellPos pos; GnmStyle *mstyle; /* Check whether we need to skip merges from above */ pos.row = tc->row; pos.col = col + 1; while (sheet_merge_contains_pos (tc->sheet, &pos)) { col++; pos.col++; } /* Do we span across multiple rows or cols? */ props = ptr->properties; while (props) { if (xmlStrEqual (props->name, CC2XML ("colspan")) && props->children) colspan = atoi (CXML2C (props->children->content)); if (xmlStrEqual (props->name, CC2XML ("rowspan")) && props->children) rowspan = atoi (CXML2C (props->children->content)); props = props->next; } if (colspan < 1) colspan = 1; if (rowspan < 1) rowspan = 1; /* Let's figure out the content of the cell */ buf = g_string_new (NULL); a_buf = xmlBufferCreate (); mstyle = mstyle_new_default (); if (xmlStrEqual (ptr->name, CC2XML ("th"))) mstyle_set_font_bold (mstyle, TRUE); html_read_content (ptr, buf, mstyle, a_buf, TRUE, doc); if (buf->len > 0) { GnmCell *cell = sheet_cell_fetch (tc->sheet, col + 1, tc->row); sheet_style_set_pos (tc->sheet, col + 1, tc->row, mstyle); cell_set_text (cell, buf->str); } else mstyle_unref (mstyle); if (a_buf->use > 0) { char *name; name = g_strndup (CXML2C (a_buf->content), a_buf->use); cell_set_comment (tc->sheet, &pos, NULL, name); g_free (name); } g_string_free (buf, TRUE); xmlBufferFree (a_buf); /* If necessary create the merge */ if (colspan > 1 || rowspan > 1) { GnmRange range; GnmRange *r = ⦥ range_init (r, col + 1, tc->row, col + colspan, tc->row + rowspan - 1); sheet_merge_add (tc->sheet, r, FALSE, NULL); } col += colspan; } } } static void html_read_rows (htmlNodePtr cur, htmlDocPtr doc, Workbook *wb, GnmHtmlTableCtxt *tc) { htmlNodePtr ptr; for (ptr = cur->children; ptr != NULL ; ptr = ptr->next) { if (ptr->type != XML_ELEMENT_NODE) continue; if (xmlStrEqual (ptr->name, CC2XML ("tr"))) { tc->row++; if (tc->sheet == NULL) tc->sheet = html_get_sheet (NULL, wb); html_read_row (ptr, doc, tc); } } } static void html_read_table (htmlNodePtr cur, htmlDocPtr doc, WorkbookView *wb_view, GnmHtmlTableCtxt *tc) { Workbook *wb; htmlNodePtr ptr, ptr2; g_return_if_fail (cur != NULL); g_return_if_fail (wb_view != NULL); wb = wb_view_workbook (wb_view); for (ptr = cur->children; ptr != NULL ; ptr = ptr->next) { if (ptr->type != XML_ELEMENT_NODE) continue; if (xmlStrEqual (ptr->name, CC2XML ("caption"))) { xmlBufferPtr buf; buf = xmlBufferCreate (); for (ptr2 = ptr->children; ptr2 != NULL ; ptr2 = ptr2->next) { htmlNodeDump (buf, doc, ptr2); } if (buf->use > 0) { char *name; name = g_strndup (CXML2C (buf->content), buf->use); tc->sheet = html_get_sheet (name, wb); g_free (name); } xmlBufferFree (buf); } else if (xmlStrEqual (ptr->name, CC2XML ("thead")) || xmlStrEqual (ptr->name, CC2XML ("tfoot")) || xmlStrEqual (ptr->name, CC2XML ("tbody"))) { html_read_rows (ptr, doc, wb, tc); } else if (xmlStrEqual (ptr->name, CC2XML ("tr"))) { html_read_rows (cur, doc, wb, tc); break; } } } /* Element types which imply that we are inside a table */ static const char *table_start_elt_types[] = { "caption", "col", "colgroup", "tbody", "tfoot", "thead", "tr", NULL }; /* Element types which imply that we are inside a row */ static const char *row_start_elt_types[] = { "td", "th", NULL }; /* Element types which occur inside tables and rows, but also outside */ static const char *cont_elt_types[] = { "del", "ins", NULL }; static gboolean is_elt_type (htmlNodePtr ptr, const char** types) { const char **p; gboolean ret = FALSE; for (p = types; *p; p++) if (xmlStrEqual (ptr->name, CC2XML ((*p)))) { ret = TRUE; break; } return ret; } static gboolean starts_inferred_table (htmlNodePtr ptr) { return ((ptr->type == XML_ELEMENT_NODE) && is_elt_type (ptr, table_start_elt_types)); } static gboolean ends_inferred_table (htmlNodePtr ptr) { return ((ptr->type == XML_ELEMENT_NODE) && !(is_elt_type (ptr, table_start_elt_types) || is_elt_type (ptr, cont_elt_types))); } static gboolean starts_inferred_row (htmlNodePtr ptr) { return ((ptr->type == XML_ELEMENT_NODE) && is_elt_type (ptr, row_start_elt_types)); } static gboolean ends_inferred_row (htmlNodePtr ptr) { return ((ptr->type == XML_ELEMENT_NODE) && !(is_elt_type (ptr, row_start_elt_types) || is_elt_type (ptr, cont_elt_types))); } /* * Handles incomplete html fragments as may occur on the clipboard, * e.g. a without and in front of it. */ static void html_search_for_tables (htmlNodePtr cur, htmlDocPtr doc, WorkbookView *wb_view, GnmHtmlTableCtxt *tc) { htmlNodePtr ptr; if (cur == NULL) { xmlGenericError(xmlGenericErrorContext, "htmlNodeDumpFormatOutput : node == NULL\n"); return; } if (cur->type != XML_ELEMENT_NODE) return; if (xmlStrEqual (cur->name, CC2XML ("table"))) { html_read_table (cur, doc, wb_view, tc); } else if (starts_inferred_table (cur) || starts_inferred_row (cur)) { htmlNodePtr tnode = xmlNewNode (NULL, "table"); /* Link in a table node */ xmlAddPrevSibling (cur, tnode); if (starts_inferred_row (cur)) { htmlNodePtr rnode = xmlNewNode (NULL, "tr"); /* Link in a row node */ xmlAddChild (tnode, rnode); /* Make following elements children of the row node, * until we meet one which isn't legal in a row. */ while ((ptr = tnode->next) != NULL) { if (ends_inferred_row (ptr)) break; xmlUnlinkNode (ptr); xmlAddChild (rnode, ptr); } } /* Make following elements children of the row node, * until we meet one which isn't legal in a table. */ while ((ptr = tnode->next) != NULL) { if (ends_inferred_table (ptr)) break; xmlUnlinkNode (ptr); xmlAddChild (tnode, ptr); } html_read_table (tnode, doc, wb_view, tc); } else { for (ptr = cur->children; ptr != NULL ; ptr = ptr->next) { html_search_for_tables (ptr, doc, wb_view, tc); /* ptr may now have been pushed down in the tree, * if so, ptr->next is not the right pointer to * follow */ while (ptr->parent != cur) ptr = ptr->parent; } } } void html_file_open (GnmFileOpener const *fo, IOContext *io_context, WorkbookView *wb_view, GsfInput *input) { guint8 const *buf; gsf_off_t size; int len, bomlen; htmlParserCtxtPtr ctxt; htmlDocPtr doc = NULL; xmlCharEncoding enc; GnmHtmlTableCtxt tc; g_return_if_fail (input != NULL); size = gsf_input_size (input) - 4; buf = gsf_input_read (input, 4, NULL); if (buf != NULL) { enc = xmlDetectCharEncoding(buf, 4); switch (enc) { /* Skip byte order mark */ case XML_CHAR_ENCODING_UCS4BE: case XML_CHAR_ENCODING_UCS4LE: case XML_CHAR_ENCODING_UCS4_2143: case XML_CHAR_ENCODING_UCS4_3412: case XML_CHAR_ENCODING_EBCDIC: bomlen = 4; break; case XML_CHAR_ENCODING_UTF16BE: case XML_CHAR_ENCODING_UTF16LE: bomlen = 2; break; case XML_CHAR_ENCODING_UTF8: if (buf[0] == 0xef) bomlen = 3; else if (buf[0] == 0x3c) bomlen = 4; else bomlen = 0; break; default: bomlen = 0; } ctxt = htmlCreatePushParserCtxt (NULL, NULL, (const char *)(buf + bomlen), 4 - bomlen, gsf_input_name (input), enc); for (; size > 0 ; size -= len) { len = MIN (4096, size); buf = gsf_input_read (input, len, NULL); if (buf == NULL) break; htmlParseChunk (ctxt, (const char *)buf, len, 0); } htmlParseChunk (ctxt, (const char *)buf, 0, 1); doc = ctxt->myDoc; htmlFreeParserCtxt (ctxt); } if (doc != NULL) { xmlNodePtr ptr; tc.sheet = NULL; tc.row = -1; for (ptr = doc->children; ptr != NULL ; ptr = ptr->next) html_search_for_tables (ptr, doc, wb_view, &tc); xmlFreeDoc (doc); } else gnumeric_io_error_info_set (io_context, error_info_new_str (_("Unable to parse the html."))); }