304 lines
8.5 KiB
C
304 lines
8.5 KiB
C
/*
|
|
* Copyright 2004 John M Bell <jmb202@ecs.soton.ac.uk>
|
|
* Copyright 2008 Michael Drake <tlsa@netsurf-browser.org>
|
|
*
|
|
* This file is part of NetSurf, http://www.netsurf-browser.org/
|
|
*
|
|
* NetSurf 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; version 2 of the License.
|
|
*
|
|
* NetSurf 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/** \file
|
|
* Text export of HTML (implementation).
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
#include "utils/config.h"
|
|
#include "content/content.h"
|
|
#include "content/hlcache.h"
|
|
#include "desktop/save_text.h"
|
|
#include "render/box.h"
|
|
#include "render/html.h"
|
|
#include "utils/log.h"
|
|
#include "utils/utf8.h"
|
|
#include "utils/utils.h"
|
|
|
|
static void extract_text(struct box *box, bool *first,
|
|
save_text_whitespace *before, struct save_text_state *save);
|
|
static bool save_text_add_to_buffer(const char *text, size_t length,
|
|
struct box *box, const char *whitespace_text,
|
|
size_t whitespace_length, struct save_text_state *save);
|
|
|
|
|
|
/**
|
|
* Extract the text from an HTML content and save it as a text file. Text is
|
|
* converted to the local encoding.
|
|
*
|
|
* \param c An HTML content.
|
|
* \param path Path to save text file too.
|
|
*/
|
|
|
|
void save_as_text(hlcache_handle *c, char *path)
|
|
{
|
|
FILE *out;
|
|
struct save_text_state save = { NULL, 0, 0 };
|
|
save_text_whitespace before = WHITESPACE_NONE;
|
|
bool first = true;
|
|
utf8_convert_ret ret;
|
|
char *result;
|
|
|
|
if (!c || content_get_type(c) != CONTENT_HTML) {
|
|
return;
|
|
}
|
|
|
|
extract_text(html_get_box_tree(c), &first, &before, &save);
|
|
if (!save.block)
|
|
return;
|
|
|
|
ret = utf8_to_local_encoding(save.block, save.length, &result);
|
|
free(save.block);
|
|
|
|
if (ret != UTF8_CONVERT_OK) {
|
|
LOG(("failed to convert to local encoding, return %d", ret));
|
|
return;
|
|
}
|
|
|
|
out = fopen(path, "w");
|
|
if (out) {
|
|
int res = fputs(result, out);
|
|
|
|
if (res < 0) {
|
|
LOG(("Warning: write failed"));
|
|
}
|
|
|
|
res = fputs("\n", out);
|
|
if (res < 0) {
|
|
LOG(("Warning: failed writing trailing newline"));
|
|
}
|
|
|
|
fclose(out);
|
|
}
|
|
|
|
free(result);
|
|
}
|
|
|
|
|
|
/**
|
|
* Decide what whitespace to place before the next bit of content-related text
|
|
* that is saved. Any existing whitespace is overridden if the whitespace for
|
|
* this box is more "significant".
|
|
*
|
|
* \param box Pointer to box.
|
|
* \param first Whether this is before the first bit of content-related
|
|
* text to be saved.
|
|
* \param before Type of whitespace currently intended to be placed
|
|
* before the next bit of content-related text to be saved.
|
|
* Updated if this box is worthy of more significant
|
|
* whitespace.
|
|
* \param whitespace_text Whitespace to place before next bit of
|
|
* content-related text to be saved.
|
|
* Updated if this box is worthy of more significant
|
|
* whitespace.
|
|
* \param whitespace_length Length of whitespace_text.
|
|
* Updated if this box is worthy of more significant
|
|
* whitespace.
|
|
*/
|
|
|
|
void save_text_solve_whitespace(struct box *box, bool *first,
|
|
save_text_whitespace *before, const char **whitespace_text,
|
|
size_t *whitespace_length)
|
|
{
|
|
/* work out what whitespace should be placed before the next bit of
|
|
* text */
|
|
if (*before < WHITESPACE_TWO_NEW_LINES &&
|
|
/* significant box type */
|
|
(box->type == BOX_BLOCK ||
|
|
box->type == BOX_TABLE ||
|
|
box->type == BOX_FLOAT_LEFT ||
|
|
box->type == BOX_FLOAT_RIGHT) &&
|
|
/* and not a list element */
|
|
!box->list_marker &&
|
|
/* and not a marker... */
|
|
(!(box->parent && box->parent->list_marker == box) ||
|
|
/* ...unless marker follows WHITESPACE_TAB */
|
|
((box->parent && box->parent->list_marker == box) &&
|
|
*before == WHITESPACE_TAB))) {
|
|
*before = WHITESPACE_TWO_NEW_LINES;
|
|
} else if (*before <= WHITESPACE_ONE_NEW_LINE &&
|
|
(box->type == BOX_TABLE_ROW ||
|
|
box->type == BOX_BR ||
|
|
(box->type != BOX_INLINE &&
|
|
(box->parent && box->parent->list_marker == box)) ||
|
|
(box->parent && box->parent->style &&
|
|
(css_computed_white_space(box->parent->style) ==
|
|
CSS_WHITE_SPACE_PRE ||
|
|
css_computed_white_space(box->parent->style) ==
|
|
CSS_WHITE_SPACE_PRE_WRAP) &&
|
|
box->type == BOX_INLINE_CONTAINER))) {
|
|
if (*before == WHITESPACE_ONE_NEW_LINE)
|
|
*before = WHITESPACE_TWO_NEW_LINES;
|
|
else
|
|
*before = WHITESPACE_ONE_NEW_LINE;
|
|
}
|
|
else if (*before < WHITESPACE_TAB &&
|
|
(box->type == BOX_TABLE_CELL ||
|
|
box->list_marker)) {
|
|
*before = WHITESPACE_TAB;
|
|
}
|
|
|
|
if (*first) {
|
|
/* before the first bit of text to be saved; there is
|
|
* no preceding whitespace */
|
|
*whitespace_text = "";
|
|
*whitespace_length = 0;
|
|
} else {
|
|
/* set the whitespace that has been decided on */
|
|
switch (*before) {
|
|
case WHITESPACE_TWO_NEW_LINES:
|
|
*whitespace_text = "\n\n";
|
|
*whitespace_length = 2;
|
|
break;
|
|
case WHITESPACE_ONE_NEW_LINE:
|
|
*whitespace_text = "\n";
|
|
*whitespace_length = 1;
|
|
break;
|
|
case WHITESPACE_TAB:
|
|
*whitespace_text = "\t";
|
|
*whitespace_length = 1;
|
|
break;
|
|
case WHITESPACE_NONE:
|
|
*whitespace_text = "";
|
|
*whitespace_length = 0;
|
|
break;
|
|
default:
|
|
*whitespace_text = "";
|
|
*whitespace_length = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Traverse though the box tree and add all text to a save buffer.
|
|
*
|
|
* \param box Pointer to box.
|
|
* \param first Whether this is before the first bit of content-related
|
|
* text to be saved.
|
|
* \param before Type of whitespace currently intended to be placed
|
|
* before the next bit of content-related text to be saved.
|
|
* Updated if this box is worthy of more significant
|
|
* whitespace.
|
|
* \param save our save_text_state workspace pointer
|
|
* \return true iff the file writing succeeded and traversal should continue.
|
|
*/
|
|
|
|
void extract_text(struct box *box, bool *first, save_text_whitespace *before,
|
|
struct save_text_state *save)
|
|
{
|
|
struct box *child;
|
|
const char *whitespace_text = "";
|
|
size_t whitespace_length = 0;
|
|
|
|
assert(box);
|
|
|
|
/* If box has a list marker */
|
|
if (box->list_marker) {
|
|
/* do the marker box before continuing with the rest of the
|
|
* list element */
|
|
extract_text(box->list_marker, first, before, save);
|
|
}
|
|
|
|
/* read before calling the handler in case it modifies the tree */
|
|
child = box->children;
|
|
|
|
save_text_solve_whitespace(box, first, before, &whitespace_text,
|
|
&whitespace_length);
|
|
|
|
if (box->type != BOX_BR && !((box->type == BOX_FLOAT_LEFT ||
|
|
box->type == BOX_FLOAT_RIGHT) && !box->text) &&
|
|
box->length > 0 && box->text) {
|
|
/* Box meets criteria for export; add text to buffer */
|
|
save_text_add_to_buffer(box->text, box->length, box,
|
|
whitespace_text, whitespace_length, save);
|
|
*first = false;
|
|
*before = WHITESPACE_NONE;
|
|
}
|
|
|
|
/* Work though the children of this box, extracting any text */
|
|
while (child) {
|
|
extract_text(child, first, before, save);
|
|
child = child->next;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/**
|
|
* Add text to save text buffer. Any preceding whitespace or following space is
|
|
* also added to the buffer.
|
|
*
|
|
* \param text Pointer to text being added.
|
|
* \param length Length of text to be appended (bytes).
|
|
* \param box Pointer to text box.
|
|
* \param whitespace_text Whitespace to place before text for formatting
|
|
* may be NULL.
|
|
* \param whitespace_length Length of whitespace_text.
|
|
* \param save Our save_text_state workspace pointer.
|
|
* \return true iff the file writing succeeded and traversal should continue.
|
|
*/
|
|
|
|
bool save_text_add_to_buffer(const char *text, size_t length, struct box *box,
|
|
const char *whitespace_text, size_t whitespace_length,
|
|
struct save_text_state *save)
|
|
{
|
|
size_t new_length;
|
|
int space = 0;
|
|
|
|
assert(save);
|
|
|
|
if (box->space > 0)
|
|
space = 1;
|
|
|
|
if (whitespace_text)
|
|
length += whitespace_length;
|
|
|
|
new_length = save->length + whitespace_length + length + space;
|
|
if (new_length >= save->alloc) {
|
|
size_t new_alloc = save->alloc + (save->alloc / 4);
|
|
char *new_block;
|
|
|
|
if (new_alloc < new_length) new_alloc = new_length;
|
|
|
|
new_block = realloc(save->block, new_alloc);
|
|
if (!new_block) return false;
|
|
|
|
save->block = new_block;
|
|
save->alloc = new_alloc;
|
|
}
|
|
if (whitespace_text) {
|
|
memcpy(save->block + save->length, whitespace_text,
|
|
whitespace_length);
|
|
}
|
|
memcpy(save->block + save->length + whitespace_length, text, length);
|
|
save->length += length;
|
|
|
|
if (space == 1)
|
|
save->block[save->length++] = ' ';
|
|
|
|
return true;
|
|
}
|