1
0
mirror of https://github.com/rkd77/elinks.git synced 2025-01-03 14:57:44 -05:00
elinks/src/document/dom/renderer.c

1083 lines
28 KiB
C
Raw Normal View History

/* DOM document renderer */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <sys/types.h> /* FreeBSD needs this before regex.h */
#ifdef HAVE_REGEX_H
#include <regex.h>
#endif
#include <string.h>
#include "elinks.h"
#include "bookmarks/bookmarks.h" /* get_bookmark() */
#include "cache/cache.h"
#include "document/css/css.h"
#include "document/css/parser.h"
#include "document/css/property.h"
#include "document/css/stylesheet.h"
#include "document/docdata.h"
#include "document/document.h"
#include "document/dom/renderer.h"
#include "document/renderer.h"
#include "dom/configuration.h"
#include "dom/scanner.h"
#include "dom/sgml/parser.h"
#include "dom/sgml/html/html.h"
#include "dom/sgml/rss/rss.h"
#include "dom/node.h"
#include "dom/stack.h"
#include "intl/charsets.h"
#include "globhist/globhist.h" /* get_global_history_item() */
#include "protocol/uri.h"
#include "terminal/draw.h"
#include "util/box.h"
#include "util/error.h"
#include "util/memory.h"
#include "util/snprintf.h"
#include "util/string.h"
struct dom_renderer {
enum sgml_document_type doctype;
struct document *document;
struct conv_table *convert_table;
enum convert_string_mode convert_mode;
struct uri *base_uri;
unsigned char *source;
unsigned char *end;
unsigned char *position;
int canvas_x, canvas_y;
#ifdef HAVE_REGEX_H
regex_t url_regex;
unsigned int find_url:1;
#endif
struct screen_char styles[DOM_NODES];
/* RSS renderer variables */
struct dom_node *channel;
struct dom_node_list *items;
struct dom_node *item;
struct dom_node *node;
struct dom_string text;
};
#define URL_REGEX "(file://|((f|ht|nt)tp(s)?|smb)://[[:alnum:]]+([-@:.]?[[:alnum:]])*\\.[[:alpha:]]{2,4}(:[[:digit:]]+)?)(/(%[[:xdigit:]]{2}|[-_~&=;?.a-z0-9])*)*"
#define URL_REGFLAGS (REG_ICASE | REG_EXTENDED)
static void
init_template(struct screen_char *template, struct document_options *options,
2006-01-04 12:08:48 -05:00
color_T background, color_T foreground, enum screen_char_attr attr)
{
struct color_pair colors = INIT_COLOR_PAIR(background, foreground);
template->attr = attr;
template->data = ' ';
set_term_color(template, &colors,
options->color_flags, options->color_mode);
}
/* Checks the user CSS for properties for each DOM node type name */
static inline void
init_dom_renderer(struct dom_renderer *renderer, struct document *document,
struct string *buffer, struct conv_table *convert_table)
{
enum dom_node_type type;
struct css_stylesheet *css = &default_stylesheet;
memset(renderer, 0, sizeof(*renderer));
renderer->document = document;
renderer->convert_table = convert_table;
renderer->convert_mode = document->options.plain ? CSM_NONE : CSM_DEFAULT;
renderer->source = buffer->source;
renderer->end = buffer->source + buffer->length;
renderer->position = renderer->source;
renderer->base_uri = get_uri_reference(document->uri);
#ifdef HAVE_REGEX_H
if (renderer->document->options.plain_display_links) {
if (regcomp(&renderer->url_regex, URL_REGEX, URL_REGFLAGS)) {
regfree(&renderer->url_regex);
} else {
renderer->find_url = 1;
}
}
#endif
for (type = 0; type < DOM_NODES; type++) {
struct screen_char *template = &renderer->styles[type];
color_T background = document->options.default_style.bg;
color_T foreground = document->options.default_style.fg;
enum screen_char_attr attr = 0;
static int i_want_struct_module_for_dom;
struct dom_string *name = get_dom_node_type_name(type);
struct css_selector *selector = NULL;
if (!i_want_struct_module_for_dom) {
static const unsigned char default_colors[] =
"document { color: yellow } "
"element { color: lightgreen } "
"entity-reference { color: red } "
"proc-instruction { color: red } "
"attribute { color: magenta } "
"comment { color: aqua } "
"cdata-section { color: orange2 } ";
unsigned char *styles = (unsigned char *) default_colors;
i_want_struct_module_for_dom = 1;
/* When someone will get here earlier than at 4am,
* this will be done in some init function, perhaps
* not overriding the user's default stylesheet. */
2007-04-29 15:06:03 -04:00
css_parse_stylesheet(css, NULL, styles, styles + sizeof(default_colors));
}
if (name)
if (is_dom_string_set(name))
selector = find_css_selector(&css->selectors,
CST_ELEMENT, CSR_ROOT,
name->string, name->length);
if (selector) {
struct css_property *property;
foreach (property, selector->properties) {
switch (property->type) {
case CSS_PT_BACKGROUND_COLOR:
case CSS_PT_BACKGROUND:
if (property->value_type == CSS_VT_COLOR)
background = property->value.color;
break;
case CSS_PT_COLOR:
foreground = property->value.color;
break;
case CSS_PT_FONT_WEIGHT:
if (property->value.font_attribute.add & AT_BOLD)
attr |= SCREEN_ATTR_BOLD;
break;
case CSS_PT_FONT_STYLE:
if (property->value.font_attribute.add & AT_UNDERLINE)
attr |= SCREEN_ATTR_UNDERLINE;
if (property->value.font_attribute.add & AT_ITALIC)
attr |= SCREEN_ATTR_ITALIC;
break;
case CSS_PT_TEXT_DECORATION:
if (property->value.font_attribute.add & AT_UNDERLINE)
attr |= SCREEN_ATTR_UNDERLINE;
break;
case CSS_PT_DISPLAY:
case CSS_PT_NONE:
case CSS_PT_TEXT_ALIGN:
case CSS_PT_WHITE_SPACE:
case CSS_PT_LAST:
break;
}
}
}
init_template(template, &document->options, background, foreground, attr);
}
}
static inline void
done_dom_renderer(struct dom_renderer *renderer)
{
#ifdef HAVE_REGEX_H
if (renderer->find_url)
regfree(&renderer->url_regex);
#endif
done_uri(renderer->base_uri);
}
/* Document maintainance */
static struct screen_char *
realloc_line(struct document *document, int x, int y)
{
struct line *line = realloc_lines(document, y);
if (!line) return NULL;
if (x > line->length) {
if (!ALIGN_LINE(&line->chars, line->length, x))
return NULL;
for (; line->length < x; line->length++) {
line->chars[line->length].data = ' ';
}
if (x > document->width) document->width = x;
}
return line->chars;
}
static struct node *
add_search_node(struct dom_renderer *renderer, int width)
{
struct node *node = mem_alloc(sizeof(*node));
if (node) {
set_box(&node->box, renderer->canvas_x, renderer->canvas_y,
width, 1);
add_to_list(renderer->document->nodes, node);
}
return node;
}
#define X(renderer) ((renderer)->canvas_x)
#define Y(renderer) ((renderer)->canvas_y)
#define POS(renderer) (&(renderer)->document->data[Y(renderer)].chars[X(renderer)])
#define WIDTH(renderer, add) ((renderer)->canvas_x + (add))
static void
render_dom_line(struct dom_renderer *renderer, struct screen_char *template,
unsigned char *string, int length)
{
struct document *document = renderer->document;
struct conv_table *convert = renderer->convert_table;
enum convert_string_mode mode = renderer->convert_mode;
int x, charlen;
#ifdef CONFIG_UTF8
2006-01-14 16:44:00 -05:00
int utf8 = document->options.utf8;
unsigned char *end;
#endif /* CONFIG_UTF8 */
assert(renderer && template && string && length);
string = convert_string(convert, string, length, document->options.cp,
mode, &length, NULL, NULL);
if (!string) return;
if (!realloc_line(document, WIDTH(renderer, length), Y(renderer))) {
mem_free(string);
return;
}
add_search_node(renderer, length);
#ifdef CONFIG_UTF8
end = string + length;
#endif /* CONFIG_UTF8 */
for (x = 0, charlen = 1; x < length;x += charlen, renderer->canvas_x++) {
unsigned char *text = &string[x];
/* This is mostly to be able to break out so the indentation
* level won't get to high. */
switch (*text) {
case ASCII_TAB:
{
int tab_width = 7 - (X(renderer) & 7);
int width = WIDTH(renderer, length - x + tab_width);
template->data = ' ';
if (!realloc_line(document, width, Y(renderer)))
break;
/* Only loop over the expanded tab chars and let the
* ``main loop'' add the actual tab char. */
for (; tab_width-- > 0; renderer->canvas_x++)
copy_screen_chars(POS(renderer), template, 1);
charlen = 1;
break;
}
default:
#ifdef CONFIG_UTF8
if (utf8) {
unicode_val_T data;
charlen = utf8charlen(text);
data = utf8_to_unicode(&text, end);
2006-01-14 16:44:00 -05:00
template->data = (unicode_val_T)data;
2006-01-14 16:44:00 -05:00
if (unicode_to_cell(data) == 2) {
copy_screen_chars(POS(renderer),
template, 1);
2006-01-14 16:44:00 -05:00
X(renderer)++;
template->data = UCS_NO_CHAR;
}
2006-01-14 16:44:00 -05:00
} else
#endif /* CONFIG_UTF8 */
template->data = isscreensafe(*text) ? *text:'.';
}
copy_screen_chars(POS(renderer), template, 1);
}
mem_free(string);
}
static inline unsigned char *
split_dom_line(unsigned char *line, int length, int *linelen)
{
unsigned char *end = line + length;
unsigned char *pos;
/* End of line detection.
* We handle \r, \r\n and \n types here. */
for (pos = line; pos < end; pos++) {
int step = 0;
if (pos[step] == ASCII_CR)
step++;
if (pos[step] == ASCII_LF)
step++;
if (step) {
*linelen = pos - line;
return pos + step;
}
}
*linelen = length;
return NULL;
}
static void
render_dom_text(struct dom_renderer *renderer, struct screen_char *template,
unsigned char *string, int length)
{
int linelen;
for (; length > 0; string += linelen, length -= linelen) {
unsigned char *newline = split_dom_line(string, length, &linelen);
if (linelen)
render_dom_line(renderer, template, string, linelen);
if (newline) {
renderer->canvas_y++;
renderer->canvas_x = 0;
linelen = newline - string;
}
}
}
#define realloc_document_links(doc, size) \
ALIGN_LINK(&(doc)->links, (doc)->nlinks, size)
static inline struct link *
add_dom_link(struct dom_renderer *renderer, unsigned char *string, int length,
unsigned char *uristring, int urilength)
{
struct document *document = renderer->document;
int x = renderer->canvas_x;
int y = renderer->canvas_y;
unsigned char *where;
struct link *link;
struct point *point;
struct screen_char template;
color_T fgcolor;
if (!realloc_document_links(document, document->nlinks + 1))
return NULL;
link = &document->links[document->nlinks];
if (!realloc_points(link, length))
return NULL;
uristring = convert_string(renderer->convert_table,
uristring, urilength, document->options.cp,
CSM_DEFAULT, NULL, NULL, NULL);
if (!uristring) return NULL;
where = join_urls(renderer->base_uri, uristring);
mem_free(uristring);
if (!where)
return NULL;
#ifdef CONFIG_GLOBHIST
else if (get_global_history_item(where))
fgcolor = document->options.default_vlink;
#endif
#ifdef CONFIG_BOOKMARKS
else if (get_bookmark(where))
fgcolor = document->options.default_bookmark_link;
#endif
else
fgcolor = document->options.default_link;
link->npoints = length;
link->type = LINK_HYPERTEXT;
link->where = where;
link->color.background = document->options.default_style.bg;
link->color.foreground = fgcolor;
link->number = document->nlinks;
init_template(&template, &document->options,
link->color.background, link->color.foreground, 0);
render_dom_text(renderer, &template, string, length);
for (point = link->points; length > 0; length--, point++, x++) {
point->x = x;
point->y = y;
}
document->nlinks++;
2006-01-28 08:13:41 -05:00
document->links_sorted = 0;
return link;
}
/* DOM Source Renderer */
#define check_dom_node_source(renderer, str, len) \
((renderer)->source <= (str) && (str) + (len) <= (renderer)->end)
#define assert_source(renderer, str, len) \
assertm(check_dom_node_source(renderer, str, len), "renderer[%p : %p] str[%p : %p]", \
(renderer)->source, (renderer)->end, (str), (str) + (len))
static inline void
render_dom_flush(struct dom_renderer *renderer, unsigned char *string)
{
struct screen_char *template = &renderer->styles[DOM_NODE_TEXT];
int length = string - renderer->position;
assert_source(renderer, renderer->position, 0);
assert_source(renderer, string, 0);
if (length <= 0) return;
render_dom_text(renderer, template, renderer->position, length);
renderer->position = string;
assert_source(renderer, renderer->position, 0);
}
static inline void
render_dom_node_text(struct dom_renderer *renderer, struct screen_char *template,
struct dom_node *node)
{
unsigned char *string = node->string.string;
int length = node->string.length;
if (node->type == DOM_NODE_ENTITY_REFERENCE) {
string -= 1;
length += 2;
}
if (check_dom_node_source(renderer, string, length)) {
render_dom_flush(renderer, string);
renderer->position = string + length;
assert_source(renderer, renderer->position, 0);
}
render_dom_text(renderer, template, string, length);
}
#ifdef HAVE_REGEX_H
static inline void
render_dom_node_enhanced_text(struct dom_renderer *renderer, struct dom_node *node)
{
regex_t *regex = &renderer->url_regex;
regmatch_t regmatch;
unsigned char *string = node->string.string;
int length = node->string.length;
struct screen_char *template = &renderer->styles[node->type];
unsigned char *alloc_string;
if (check_dom_node_source(renderer, string, length)) {
render_dom_flush(renderer, string);
renderer->position = string + length;
assert_source(renderer, renderer->position, 0);
}
alloc_string = memacpy(string, length);
if (alloc_string)
string = alloc_string;
while (length > 0 && !regexec(regex, string, 1, &regmatch, 0)) {
int matchlen = regmatch.rm_eo - regmatch.rm_so;
int offset = regmatch.rm_so;
if (!matchlen || offset < 0 || regmatch.rm_eo > length)
break;
if (offset > 0)
render_dom_text(renderer, template, string, offset);
string += offset;
length -= offset;
add_dom_link(renderer, string, matchlen, string, matchlen);
length -= matchlen;
string += matchlen;
}
if (length > 0)
render_dom_text(renderer, template, string, length);
mem_free_if(alloc_string);
}
#endif
static enum dom_code
render_dom_node_source(struct dom_stack *stack, struct dom_node *node, void *data)
{
struct dom_renderer *renderer = stack->current->data;
assert(node && renderer && renderer->document);
#ifdef HAVE_REGEX_H
if (renderer->find_url
&& (node->type == DOM_NODE_TEXT
|| node->type == DOM_NODE_CDATA_SECTION
|| node->type == DOM_NODE_COMMENT)) {
render_dom_node_enhanced_text(renderer, node);
} else
#endif
render_dom_node_text(renderer, &renderer->styles[node->type], node);
return DOM_CODE_OK;
}
/* This callback is also used for rendering processing instruction nodes. */
static enum dom_code
render_dom_element_source(struct dom_stack *stack, struct dom_node *node, void *data)
{
struct dom_renderer *renderer = stack->current->data;
assert(node && renderer && renderer->document);
render_dom_node_text(renderer, &renderer->styles[node->type], node);
return DOM_CODE_OK;
}
enum dom_code
render_dom_element_end_source(struct dom_stack *stack, struct dom_node *node, void *data)
{
struct dom_renderer *renderer = stack->current->data;
struct dom_stack_state *state = get_dom_stack_top(stack);
struct sgml_parser_state *pstate = get_dom_stack_state_data(stack->contexts[0], state);
struct dom_scanner_token *token = &pstate->end_token;
unsigned char *string = token->string.string;
int length = token->string.length;
assert(node && renderer && renderer->document);
if (!string || !length)
return DOM_CODE_OK;
if (check_dom_node_source(renderer, string, length)) {
render_dom_flush(renderer, string);
renderer->position = string + length;
assert_source(renderer, renderer->position, 0);
}
render_dom_text(renderer, &renderer->styles[node->type], string, length);
return DOM_CODE_OK;
}
static void
set_base_uri(struct dom_renderer *renderer, unsigned char *value, size_t valuelen)
{
unsigned char *href = memacpy(value, valuelen);
unsigned char *uristring;
struct uri *uri;
if (!href) return;
uristring = join_urls(renderer->base_uri, href);
mem_free(href);
if (!uristring) return;
uri = get_uri(uristring, 0);
mem_free(uristring);
if (!uri) return;
done_uri(renderer->base_uri);
renderer->base_uri = uri;
}
enum dom_code
render_dom_attribute_source(struct dom_stack *stack, struct dom_node *node, void *data)
{
struct dom_renderer *renderer = stack->current->data;
struct screen_char *template = &renderer->styles[node->type];
assert(node && renderer->document);
render_dom_node_text(renderer, template, node);
if (is_dom_string_set(&node->data.attribute.value)) {
int quoted = node->data.attribute.quoted == 1;
unsigned char *value = node->data.attribute.value.string - quoted;
int valuelen = node->data.attribute.value.length + quoted * 2;
if (check_dom_node_source(renderer, value, 0)) {
render_dom_flush(renderer, value);
renderer->position = value + valuelen;
assert_source(renderer, renderer->position, 0);
}
if (node->data.attribute.reference
&& valuelen - quoted * 2 > 0) {
int skips;
/* Need to flush the first quoting delimiter and any
* leading whitespace so that the renderers x position
* is at the start of the value string. */
for (skips = 0; skips < valuelen; skips++) {
if ((quoted && skips == 0)
|| isspace(value[skips])
|| value[skips] < ' ')
continue;
break;
}
if (skips > 0) {
render_dom_text(renderer, template, value, skips);
value += skips;
valuelen -= skips;
}
/* Figure out what should be skipped after the actual
* link text. */
for (skips = 0; skips < valuelen; skips++) {
if ((quoted && skips == 0)
|| isspace(value[valuelen - skips - 1])
|| value[valuelen - skips - 1] < ' ')
continue;
break;
}
if (renderer->doctype == SGML_DOCTYPE_HTML
&& node->data.attribute.type == HTML_ATTRIBUTE_HREF
&& node->parent->data.element.type == HTML_ELEMENT_BASE) {
set_base_uri(renderer, value, valuelen - skips);
}
add_dom_link(renderer, value, valuelen - skips,
value, valuelen - skips);
if (skips > 0) {
value += valuelen - skips;
render_dom_text(renderer, template, value, skips);
}
} else {
render_dom_text(renderer, template, value, valuelen);
}
}
return DOM_CODE_OK;
}
enum dom_code
render_dom_cdata_source(struct dom_stack *stack, struct dom_node *node, void *data)
{
struct dom_renderer *renderer = stack->current->data;
unsigned char *string = node->string.string;
assert(node && renderer && renderer->document);
/* Highlight the 'CDATA' part of <![CDATA[ if it is there. */
if (check_dom_node_source(renderer, string - 6, 6)) {
render_dom_flush(renderer, string - 6);
render_dom_text(renderer, &renderer->styles[DOM_NODE_ATTRIBUTE], string - 6, 5);
renderer->position = string - 1;
assert_source(renderer, renderer->position, 0);
}
render_dom_node_text(renderer, &renderer->styles[node->type], node);
return DOM_CODE_OK;
}
enum dom_code
render_dom_document_end(struct dom_stack *stack, struct dom_node *node, void *data)
{
struct dom_renderer *renderer = stack->current->data;
/* If there are no non-element nodes after the last element node make
* sure that we flush to the end of the cache entry source including
* the '>' of the last element tag if it has one. (bug 519) */
if (check_dom_node_source(renderer, renderer->position, 0)) {
render_dom_flush(renderer, renderer->end);
}
return DOM_CODE_OK;
}
static struct dom_stack_context_info dom_source_renderer_context_info = {
/* Object size: */ 0,
/* Push: */
{
/* */ NULL,
/* DOM_NODE_ELEMENT */ render_dom_element_source,
/* DOM_NODE_ATTRIBUTE */ render_dom_attribute_source,
/* DOM_NODE_TEXT */ render_dom_node_source,
/* DOM_NODE_CDATA_SECTION */ render_dom_cdata_source,
/* DOM_NODE_ENTITY_REFERENCE */ render_dom_node_source,
/* DOM_NODE_ENTITY */ render_dom_node_source,
/* DOM_NODE_PROC_INSTRUCTION */ render_dom_element_source,
/* DOM_NODE_COMMENT */ render_dom_node_source,
/* DOM_NODE_DOCUMENT */ NULL,
/* DOM_NODE_DOCUMENT_TYPE */ render_dom_node_source,
/* DOM_NODE_DOCUMENT_FRAGMENT */ render_dom_node_source,
/* DOM_NODE_NOTATION */ render_dom_node_source,
},
/* Pop: */
{
/* */ NULL,
/* DOM_NODE_ELEMENT */ render_dom_element_end_source,
/* DOM_NODE_ATTRIBUTE */ NULL,
/* DOM_NODE_TEXT */ NULL,
/* DOM_NODE_CDATA_SECTION */ NULL,
/* DOM_NODE_ENTITY_REFERENCE */ NULL,
/* DOM_NODE_ENTITY */ NULL,
/* DOM_NODE_PROC_INSTRUCTION */ NULL,
/* DOM_NODE_COMMENT */ NULL,
/* DOM_NODE_DOCUMENT */ render_dom_document_end,
/* DOM_NODE_DOCUMENT_TYPE */ NULL,
/* DOM_NODE_DOCUMENT_FRAGMENT */ NULL,
/* DOM_NODE_NOTATION */ NULL,
}
};
/* DOM RSS Renderer */
#define RSS_CONFIG_FLAGS \
(DOM_CONFIG_NORMALIZE_WHITESPACE | DOM_CONFIG_NORMALIZE_CHARACTERS)
enum dom_code
dom_rss_push_element(struct dom_stack *stack, struct dom_node *node, void *data)
{
struct dom_renderer *renderer = stack->current->data;
assert(node && renderer && renderer->document);
switch (node->data.element.type) {
case RSS_ELEMENT_CHANNEL:
/* The stack should have: #document * channel */
if (stack->depth != 3)
break;
if (!renderer->channel) {
renderer->channel = node;
}
break;
case RSS_ELEMENT_ITEM:
/* The stack should have: #document * channel item */
#if 0
/* Don't be so strict ... */
if (stack->depth != 4)
break;
#endif
/* ... but be exclusive. */
if (renderer->item)
break;
add_to_dom_node_list(&renderer->items, node, -1);
renderer->item = node;
break;
case RSS_ELEMENT_LINK:
case RSS_ELEMENT_DESCRIPTION:
case RSS_ELEMENT_TITLE:
case RSS_ELEMENT_AUTHOR:
case RSS_ELEMENT_PUBDATE:
if (!node->parent || renderer->node != node->parent)
break;
renderer->node = node;
}
return DOM_CODE_OK;
}
enum dom_code
dom_rss_pop_element(struct dom_stack *stack, struct dom_node *node, void *data)
{
struct dom_renderer *renderer = stack->current->data;
struct dom_node_list **list;
assert(node && renderer && renderer->document);
switch (node->data.element.type) {
case RSS_ELEMENT_ITEM:
if (is_dom_string_set(&renderer->text))
done_dom_string(&renderer->text);
renderer->item = NULL;
break;
case RSS_ELEMENT_LINK:
case RSS_ELEMENT_DESCRIPTION:
case RSS_ELEMENT_TITLE:
case RSS_ELEMENT_AUTHOR:
case RSS_ELEMENT_PUBDATE:
if (!is_dom_string_set(&renderer->text)
|| !node->parent
|| renderer->item != node->parent
|| renderer->node != node)
break;
/* Replace any child nodes with the normalized text node. */
list = get_dom_node_list(node->parent, node);
done_dom_node_list(*list);
if (is_dom_string_set(&renderer->text)) {
if (!add_dom_node(node, DOM_NODE_TEXT, &renderer->text))
done_dom_string(&renderer->text);
}
renderer->node = NULL;
break;
default:
break;
}
return DOM_CODE_OK;
}
static struct dom_string *
get_rss_text(struct dom_node *node, enum rss_element_type type)
{
node = get_dom_node_child(node, DOM_NODE_ELEMENT, type);
if (!node) return NULL;
node = get_dom_node_child(node, DOM_NODE_TEXT, 0);
return node ? &node->string: NULL;
}
static void
render_rss_item(struct dom_renderer *renderer, struct dom_node *item)
{
struct dom_string *title = get_rss_text(item, RSS_ELEMENT_TITLE);
struct dom_string *link = get_rss_text(item, RSS_ELEMENT_LINK);
struct dom_string *author = get_rss_text(item, RSS_ELEMENT_AUTHOR);
struct dom_string *date = get_rss_text(item, RSS_ELEMENT_PUBDATE);
if (title && is_dom_string_set(title)) {
2006-01-09 13:14:28 -05:00
if (item == renderer->channel) {
unsigned char *str;
str = convert_string(renderer->convert_table,
title->string, title->length,
renderer->document->options.cp,
CSM_DEFAULT, NULL, NULL, NULL);
if (str)
renderer->document->title = str;
}
render_dom_text(renderer, &renderer->styles[DOM_NODE_ELEMENT],
title->string, title->length);
}
if (link && is_dom_string_set(link)) {
X(renderer)++;
add_dom_link(renderer, "[link]", 6, link->string, link->length);
}
/* New line, and indent */
Y(renderer)++;
X(renderer) = 0;
if (author && is_dom_string_set(author)) {
render_dom_text(renderer, &renderer->styles[DOM_NODE_COMMENT],
author->string, author->length);
}
if (date && is_dom_string_set(date)) {
if (author && is_dom_string_set(author)) {
render_dom_text(renderer, &renderer->styles[DOM_NODE_COMMENT],
" - ", 3);
}
render_dom_text(renderer, &renderer->styles[DOM_NODE_COMMENT],
date->string, date->length);
}
if ((author && is_dom_string_set(author))
|| (date && is_dom_string_set(date))) {
/* New line, and indent */
Y(renderer)++;
X(renderer) = 0;
}
}
enum dom_code
dom_rss_pop_document(struct dom_stack *stack, struct dom_node *root, void *data)
{
struct dom_renderer *renderer = stack->current->data;
if (!renderer->channel)
return DOM_CODE_OK;
render_rss_item(renderer, renderer->channel);
if (renderer->items) {
struct dom_node *node;
int index;
foreach_dom_node (renderer->items, node, index) {
Y(renderer)++;
X(renderer) = 0;
render_rss_item(renderer, node);
}
}
if (is_dom_string_set(&renderer->text))
done_dom_string(&renderer->text);
mem_free_if(renderer->items);
done_dom_node(root);
return DOM_CODE_OK;
}
static struct dom_stack_context_info dom_rss_renderer_context_info = {
/* Object size: */ 0,
/* Push: */
{
/* */ NULL,
/* DOM_NODE_ELEMENT */ dom_rss_push_element,
/* DOM_NODE_ATTRIBUTE */ NULL,
/* DOM_NODE_TEXT */ NULL,
/* DOM_NODE_CDATA_SECTION */ NULL,
/* DOM_NODE_ENTITY_REFERENCE */ NULL,
/* DOM_NODE_ENTITY */ NULL,
/* DOM_NODE_PROC_INSTRUCTION */ NULL,
/* DOM_NODE_COMMENT */ NULL,
/* DOM_NODE_DOCUMENT */ NULL,
/* DOM_NODE_DOCUMENT_TYPE */ NULL,
/* DOM_NODE_DOCUMENT_FRAGMENT */ NULL,
/* DOM_NODE_NOTATION */ NULL,
},
/* Pop: */
{
/* */ NULL,
/* DOM_NODE_ELEMENT */ dom_rss_pop_element,
/* DOM_NODE_ATTRIBUTE */ NULL,
/* DOM_NODE_TEXT */ NULL,
/* DOM_NODE_CDATA_SECTION */ NULL,
/* DOM_NODE_ENTITY_REFERENCE */ NULL,
/* DOM_NODE_ENTITY */ NULL,
/* DOM_NODE_PROC_INSTRUCTION */ NULL,
/* DOM_NODE_COMMENT */ NULL,
/* DOM_NODE_DOCUMENT */ dom_rss_pop_document,
/* DOM_NODE_DOCUMENT_TYPE */ NULL,
/* DOM_NODE_DOCUMENT_FRAGMENT */ NULL,
/* DOM_NODE_NOTATION */ NULL,
}
};
static void
get_doctype(struct dom_renderer *renderer, struct cache_entry *cached)
{
if (!strcasecmp("application/rss+xml", cached->content_type)) {
renderer->doctype = SGML_DOCTYPE_RSS;
} else if (!strcasecmp("application/docbook+xml",
cached->content_type)) {
renderer->doctype = SGML_DOCTYPE_DOCBOOK;
} else if (!strcasecmp("application/xbel+xml", cached->content_type)
|| !strcasecmp("application/x-xbel", cached->content_type)
|| !strcasecmp("application/xbel", cached->content_type)) {
renderer->doctype = SGML_DOCTYPE_XBEL;
} else {
assertm(!strcasecmp("text/html", cached->content_type)
|| !strcasecmp("application/xhtml+xml",
cached->content_type),
"Couldn't resolve doctype '%s'", cached->content_type);
renderer->doctype = SGML_DOCTYPE_HTML;
}
}
/* Shared multiplexor between renderers */
void
render_dom_document(struct cache_entry *cached, struct document *document,
struct string *buffer)
{
unsigned char *head = empty_string_or_(cached->head);
struct dom_renderer renderer;
struct conv_table *convert_table;
struct sgml_parser *parser;
enum sgml_parser_type parser_type;
unsigned char *string = struri(cached->uri);
size_t length = strlen(string);
struct dom_string uri = INIT_DOM_STRING(string, length);
enum dom_code code;
convert_table = get_convert_table(head, document->options.cp,
document->options.assume_cp,
&document->cp,
&document->cp_status,
document->options.hard_assume);
init_dom_renderer(&renderer, document, buffer, convert_table);
document->bgcolor = document->options.default_style.bg;
#ifdef CONFIG_UTF8
2006-07-18 11:51:03 -04:00
document->options.utf8 = is_cp_utf8(document->options.cp);
#endif /* CONFIG_UTF8 */
if (document->options.plain)
parser_type = SGML_PARSER_STREAM;
else
parser_type = SGML_PARSER_TREE;
get_doctype(&renderer, cached);
parser = init_sgml_parser(parser_type, renderer.doctype, &uri, 0);
if (!parser) return;
2006-01-12 18:11:39 -05:00
if (document->options.plain) {
add_dom_stack_context(&parser->stack, &renderer,
&dom_source_renderer_context_info);
} else if (renderer.doctype == SGML_DOCTYPE_RSS) {
add_dom_stack_context(&parser->stack, &renderer,
&dom_rss_renderer_context_info);
2006-01-27 19:59:22 -05:00
add_dom_config_normalizer(&parser->stack, RSS_CONFIG_FLAGS);
}
/* FIXME: When rendering this way we don't really care about the code.
* However, it will be useful when we will be able to also
* incrementally parse new data. This will require the parser to live
* during the fetching of data. */
code = parse_sgml(parser, buffer->source, buffer->length, 1);
if (parser->root) {
assert(parser->stack.depth == 1);
get_dom_stack_top(&parser->stack)->immutable = 0;
/* For SGML_PARSER_STREAM this will free the DOM
* root node. */
pop_dom_node(&parser->stack);
}
done_dom_renderer(&renderer);
done_sgml_parser(parser);
}