/* $NetBSD: html-text.cpp,v 1.1.1.1 2016/01/13 18:41:49 christos Exp $ */
// -*- C++ -*-
/* Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005
* Free Software Foundation, Inc.
*
* Gaius Mulley (gaius@glam.ac.uk) wrote html-text.cpp
*
* html-text.cpp
*
* provide a troff like state machine interface which
* generates html text.
*/
/*
This file is part of groff.
groff 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, or (at your option) any later
version.
groff 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 groff; see the file COPYING. If not, write to the Free Software
Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
#include "driver.h"
#include "stringclass.h"
#include "cset.h"
#if !defined(TRUE)
# define TRUE (1==1)
#endif
#if !defined(FALSE)
# define FALSE (1==0)
#endif
#include "html-text.h"
#undef DEBUGGING
// #define DEBUGGING
html_text::html_text (simple_output *op) :
stackptr(NULL), lastptr(NULL), out(op), space_emitted(TRUE),
current_indentation(-1), pageoffset(-1), linelength(-1),
blank_para(TRUE), start_space(FALSE)
{
}
html_text::~html_text ()
{
flush_text();
}
#if defined(DEBUGGING)
static int debugStack = FALSE;
/*
* turnDebug - flip the debugStack boolean and return the new value.
*/
static int turnDebug (void)
{
debugStack = 1-debugStack;
return debugStack;
}
/*
* dump_stack_element - display an element of the html stack, p.
*/
void html_text::dump_stack_element (tag_definition *p)
{
fprintf(stderr, " | ");
switch (p->type) {
case P_TAG: if (p->indent == NULL) {
fprintf(stderr, "<P %s>", (char *)p->arg1); break;
} else {
fprintf(stderr, "<P %s [TABLE]>", (char *)p->arg1); break;
}
case I_TAG: fprintf(stderr, "<I>"); break;
case B_TAG: fprintf(stderr, "<B>"); break;
case SUB_TAG: fprintf(stderr, "<SUB>"); break;
case SUP_TAG: fprintf(stderr, "<SUP>"); break;
case TT_TAG: fprintf(stderr, "<TT>"); break;
case PRE_TAG: if (p->indent == NULL) {
fprintf(stderr, "<PRE>"); break;
} else {
fprintf(stderr, "<PRE [TABLE]>"); break;
}
case SMALL_TAG: fprintf(stderr, "<SMALL>"); break;
case BIG_TAG: fprintf(stderr, "<BIG>"); break;
case BREAK_TAG: fprintf(stderr, "<BREAK>"); break;
case COLOR_TAG: {
if (p->col.is_default())
fprintf(stderr, "<COLOR (default)>");
else {
unsigned int r, g, b;
p->col.get_rgb(&r, &g, &b);
fprintf(stderr, "<COLOR %x %x %x>", r/0x101, g/0x101, b/0x101);
}
break;
}
default: fprintf(stderr, "unknown tag");
}
if (p->text_emitted)
fprintf(stderr, "[t] ");
}
/*
* dump_stack - debugging function only.
*/
void html_text::dump_stack (void)
{
if (debugStack) {
tag_definition *p = stackptr;
while (p != NULL) {
dump_stack_element(p);
p = p->next;
}
}
fprintf(stderr, "\n");
fflush(stderr);
}
#else
void html_text::dump_stack (void) {}
#endif
/*
* end_tag - shuts down the tag.
*/
void html_text::end_tag (tag_definition *t)
{
switch (t->type) {
case I_TAG: out->put_string("</i>"); break;
case B_TAG: out->put_string("</b>"); break;
case P_TAG: if (t->indent == NULL) {
out->put_string("</p>");
} else {
delete t->indent;
t->indent = NULL;
out->put_string("</p>");
}
out->enable_newlines(FALSE);
blank_para = TRUE; break;
case SUB_TAG: out->put_string("</sub>"); break;
case SUP_TAG: out->put_string("</sup>"); break;
case TT_TAG: out->put_string("</tt>"); break;
case PRE_TAG: out->put_string("</pre>"); out->enable_newlines(TRUE);
blank_para = TRUE;
if (t->indent != NULL)
delete t->indent;
t->indent = NULL;
break;
case SMALL_TAG: out->put_string("</small>"); break;
case BIG_TAG: out->put_string("</big>"); break;
case COLOR_TAG: out->put_string("</font>"); break;
default:
error("unrecognised tag");
}
}
/*
* issue_tag - writes out an html tag with argument.
* space == 0 if no space is requested
* space == 1 if a space is requested
* space == 2 if tag should not have a space style
*/
void html_text::issue_tag (const char *tagname, const char *arg,
int space)
{
if ((arg == 0) || (strlen(arg) == 0))
out->put_string(tagname);
else {
out->put_string(tagname);
out->put_string(" ");
out->put_string(arg);
}
if (space == TRUE) {
out->put_string(" style=\"margin-top: ");
out->put_string(STYLE_VERTICAL_SPACE);
out->put_string("\"");
}
if (space == TRUE || space == FALSE)
out->put_string(" valign=\"top\"");
out->put_string(">");
}
/*
* issue_color_begin - writes out an html color tag.
*/
void html_text::issue_color_begin (color *c)
{
unsigned int r, g, b;
char buf[6+1];
out->put_string("<font color=\"#");
if (c->is_default())
sprintf(buf, "000000");
else {
c->get_rgb(&r, &g, &b);
// we have to scale 0..0xFFFF to 0..0xFF
sprintf(buf, "%.2X%.2X%.2X", r/0x101, g/0x101, b/0x101);
}
out->put_string(buf);
out->put_string("\">");
}
/*
* start_tag - starts a tag.
*/
void html_text::start_tag (tag_definition *t)
{
switch (t->type) {
case I_TAG: issue_tag("<i", (char *)t->arg1); break;
case B_TAG: issue_tag("<b", (char *)t->arg1); break;
case P_TAG: if (t->indent != NULL) {
out->nl();
#if defined(DEBUGGING)
out->simple_comment("INDENTATION");
#endif
out->put_string("\n<p");
t->indent->begin(start_space);
issue_tag("", (char *)t->arg1);
} else {
out->nl();
issue_tag("\n<p", (char *)t->arg1, start_space);
}
out->enable_newlines(TRUE); break;
case SUB_TAG: issue_tag("<sub", (char *)t->arg1); break;
case SUP_TAG: issue_tag("<sup", (char *)t->arg1); break;
case TT_TAG: issue_tag("<tt", (char *)t->arg1); break;
case PRE_TAG: out->enable_newlines(TRUE);
out->nl(); out->put_string("<pre");
if (t->indent == NULL)
issue_tag("", (char *)t->arg1, start_space);
else {
t->indent->begin(start_space);
issue_tag("", (char *)t->arg1);
}
out->enable_newlines(FALSE); break;
case SMALL_TAG: issue_tag("<small", (char *)t->arg1); break;
case BIG_TAG: issue_tag("<big", (char *)t->arg1); break;
case BREAK_TAG: break;
case COLOR_TAG: issue_color_begin(&t->col); break;
default:
error("unrecognised tag");
}
}
/*
* flush_text - flushes html tags which are outstanding on the html stack.
*/
void html_text::flush_text (void)
{
int notext=TRUE;
tag_definition *p=stackptr;
while (stackptr != 0) {
notext = (notext && (! stackptr->text_emitted));
if (! notext) {
end_tag(stackptr);
}
p = stackptr;
stackptr = stackptr->next;
delete p;
}
lastptr = NULL;
}
/*
* is_present - returns TRUE if tag is already present on the stack.
*/
int html_text::is_present (HTML_TAG t)
{
tag_definition *p=stackptr;
while (p != NULL) {
if (t == p->type)
return TRUE;
p = p->next;
}
return FALSE;
}
/*
* uses_indent - returns TRUE if the current paragraph is using a
* html table to effect an indent.
*/
int html_text::uses_indent (void)
{
tag_definition *p = stackptr;
while (p != NULL) {
if (p->indent != NULL)
return TRUE;
p = p->next;
}
return FALSE;
}
extern void stop();
/*
* do_push - places, tag_definition, p, onto the stack
*/
void html_text::do_push (tag_definition *p)
{
HTML_TAG t = p->type;
#if defined(DEBUGGING)
if (t == PRE_TAG)
stop();
debugStack = TRUE;
fprintf(stderr, "\nentering do_push (");
dump_stack_element(p);
fprintf(stderr, ")\n");
dump_stack();
fprintf(stderr, ")\n");
fflush(stderr);
#endif
/*
* if t is a P_TAG or PRE_TAG make sure it goes on the end of the stack.
*/
if (((t == P_TAG) || (t == PRE_TAG)) && (lastptr != NULL)) {
/*
* store, p, at the end
*/
lastptr->next = p;
lastptr = p;
p->next = NULL;
} else {
p->next = stackptr;
if (stackptr == NULL)
lastptr = p;
stackptr = p;
}
#if defined(DEBUGGING)
dump_stack();
fprintf(stderr, "exiting do_push\n");
#endif
}
/*
* push_para - adds a new entry onto the html paragraph stack.
*/
void html_text::push_para (HTML_TAG t, void *arg, html_indent *in)
{
tag_definition *p= new tag_definition;
p->type = t;
p->arg1 = arg;
p->text_emitted = FALSE;
p->indent = in;
if (t == PRE_TAG && is_present(PRE_TAG))
fatal("cannot have multiple PRE_TAGs");
do_push(p);
}
void html_text::push_para (HTML_TAG t)
{
push_para(t, (void *)"", NULL);
}
void html_text::push_para (color *c)
{
tag_definition *p = new tag_definition;
p->type = COLOR_TAG;
p->arg1 = NULL;
p->col = *c;
p->text_emitted = FALSE;
p->indent = NULL;
do_push(p);
}
/*
* do_italic - changes to italic
*/
void html_text::do_italic (void)
{
if (! is_present(I_TAG))
push_para(I_TAG);
}
/*
* do_bold - changes to bold.
*/
void html_text::do_bold (void)
{
if (! is_present(B_TAG))
push_para(B_TAG);
}
/*
* do_tt - changes to teletype.
*/
void html_text::do_tt (void)
{
if ((! is_present(TT_TAG)) && (! is_present(PRE_TAG)))
push_para(TT_TAG);
}
/*
* do_pre - changes to preformated text.
*/
void html_text::do_pre (void)
{
done_tt();
if (is_present(P_TAG)) {
html_indent *i = remove_indent(P_TAG);
int space = retrieve_para_space();
(void)done_para();
if (! is_present(PRE_TAG))
push_para(PRE_TAG, NULL, i);
start_space = space;
} else if (! is_present(PRE_TAG))
push_para(PRE_TAG, NULL, NULL);
dump_stack();
}
/*
* is_in_pre - returns TRUE if we are currently within a preformatted
* <pre> block.
*/
int html_text::is_in_pre (void)
{
return is_present(PRE_TAG);
}
/*
* do_color - initiates a new color tag.
*/
void html_text::do_color (color *c)
{
shutdown(COLOR_TAG); // shutdown a previous color tag, if present
push_para(c);
}
/*
* done_color - shutdown an outstanding color tag, if it exists.
*/
void html_text::done_color (void)
{
shutdown(COLOR_TAG);
}
/*
* shutdown - shuts down an html tag.
*/
char *html_text::shutdown (HTML_TAG t)
{
char *arg=NULL;
if (is_present(t)) {
tag_definition *p =stackptr;
tag_definition *temp =NULL;
int notext =TRUE;
dump_stack();
while ((stackptr != NULL) && (stackptr->type != t)) {
notext = (notext && (! stackptr->text_emitted));
if (! notext) {
end_tag(stackptr);
}
/*
* pop tag
*/
p = stackptr;
stackptr = stackptr->next;
if (stackptr == NULL)
lastptr = NULL;
/*
* push tag onto temp stack
*/
p->next = temp;
temp = p;
}
/*
* and examine stackptr
*/
if ((stackptr != NULL) && (stackptr->type == t)) {
if (stackptr->text_emitted) {
end_tag(stackptr);
}
if (t == P_TAG) {
arg = (char *)stackptr->arg1;
}
p = stackptr;
stackptr = stackptr->next;
if (stackptr == NULL)
lastptr = NULL;
if (p->indent != NULL)
delete p->indent;
delete p;
}
/*
* and restore unaffected tags
*/
while (temp != NULL) {
if (temp->type == COLOR_TAG)
push_para(&temp->col);
else
push_para(temp->type, temp->arg1, temp->indent);
p = temp;
temp = temp->next;
delete p;
}
}
return arg;
}
/*
* done_bold - shuts downs a bold tag.
*/
void html_text::done_bold (void)
{
shutdown(B_TAG);
}
/*
* done_italic - shuts downs an italic tag.
*/
void html_text::done_italic (void)
{
shutdown(I_TAG);
}
/*
* done_sup - shuts downs a sup tag.
*/
void html_text::done_sup (void)
{
shutdown(SUP_TAG);
}
/*
* done_sub - shuts downs a sub tag.
*/
void html_text::done_sub (void)
{
shutdown(SUB_TAG);
}
/*
* done_tt - shuts downs a tt tag.
*/
void html_text::done_tt (void)
{
shutdown(TT_TAG);
}
/*
* done_pre - shuts downs a pre tag.
*/
void html_text::done_pre (void)
{
shutdown(PRE_TAG);
}
/*
* done_small - shuts downs a small tag.
*/
void html_text::done_small (void)
{
shutdown(SMALL_TAG);
}
/*
* done_big - shuts downs a big tag.
*/
void html_text::done_big (void)
{
shutdown(BIG_TAG);
}
/*
* check_emit_text - ensures that all previous tags have been emitted (in order)
* before the text is written.
*/
void html_text::check_emit_text (tag_definition *t)
{
if ((t != NULL) && (! t->text_emitted)) {
check_emit_text(t->next);
t->text_emitted = TRUE;
start_tag(t);
}
}
/*
* do_emittext - tells the class that text was written during the current tag.
*/
void html_text::do_emittext (const char *s, int length)
{
if ((! is_present(P_TAG)) && (! is_present(PRE_TAG)))
do_para("", FALSE);
if (is_present(BREAK_TAG)) {
int text = remove_break();
check_emit_text(stackptr);
if (text) {
if (is_present(PRE_TAG)) {
out->nl();
} else
out->put_string("<br>").nl();
}
} else
check_emit_text(stackptr);
out->put_string(s, length);
space_emitted = FALSE;
blank_para = FALSE;
}
/*
* do_para - starts a new paragraph
*/
void html_text::do_para (const char *arg, html_indent *in, int space)
{
if (! is_present(P_TAG)) {
if (is_present(PRE_TAG)) {
html_indent *i = remove_indent(PRE_TAG);
done_pre();
if ((arg == NULL || (strcmp(arg, "") == 0)) &&
(i == in || in == NULL))
in = i;
else
delete i;
}
remove_sub_sup();
push_para(P_TAG, (void *)arg, in);
start_space = space;
}
}
void html_text::do_para (const char *arg, int space)
{
do_para(arg, NULL, space);
}
void html_text::do_para (simple_output *op, const char *arg1,
int indentation_value, int page_offset,
int line_length, int space)
{
html_indent *ind;
if (indentation_value == 0)
ind = NULL;
else
ind = new html_indent(op, indentation_value, page_offset, line_length);
do_para(arg1, ind, space);
}
/*
* done_para - shuts down a paragraph tag.
*/
char *html_text::done_para (void)
{
char *result;
space_emitted = TRUE;
result = shutdown(P_TAG);
start_space = FALSE;
return result;
}
/*
* remove_indent - returns the indent associated with, tag.
* The indent associated with tag is set to NULL.
*/
html_indent *html_text::remove_indent (HTML_TAG tag)
{
tag_definition *p=stackptr;
while (p != NULL) {
if (tag == p->type) {
html_indent *i = p->indent;
p->indent = NULL;
return i;
}
p = p->next;
}
return NULL;
}
/*
* remove_para_space - removes the leading space to a paragraph
* (effectively this trims off a leading `.sp' tag).
*/
void html_text::remove_para_space (void)
{
start_space = FALSE;
}
/*
* do_space - issues an end of paragraph
*/
void html_text::do_space (void)
{
if (is_in_pre()) {
do_emittext("", 0);
out->force_nl();
space_emitted = TRUE;
} else {
html_indent *i = remove_indent(P_TAG);
do_para(done_para(), i, TRUE);
space_emitted = TRUE;
}
}
/*
* do_break - issue a break tag.
*/
void html_text::do_break (void)
{
if (! is_present(PRE_TAG))
if (emitted_text())
if (! is_present(BREAK_TAG))
push_para(BREAK_TAG);
space_emitted = TRUE;
}
/*
* do_newline - issue a newline providing that we are inside a <pre> tag.
*/
void html_text::do_newline (void)
{
if (is_present(PRE_TAG)) {
do_emittext("\n", 1);
space_emitted = TRUE;
}
}
/*
* emitted_text - returns FALSE if white space has just been written.
*/
int html_text::emitted_text (void)
{
return !space_emitted;
}
/*
* ever_emitted_text - returns TRUE if we have ever emitted text in this
* paragraph.
*/
int html_text::ever_emitted_text (void)
{
return !blank_para;
}
/*
* starts_with_space - returns TRUE if we started this paragraph with a .sp
*/
int html_text::starts_with_space (void)
{
return start_space;
}
/*
* retrieve_para_space - returns TRUE, if the paragraph starts with
* a space and text has not yet been emitted.
* If TRUE is returned, then the, start_space,
* variable is set to FALSE.
*/
int html_text::retrieve_para_space (void)
{
if (start_space && blank_para) {
start_space = FALSE;
return TRUE;
}
else
return FALSE;
}
/*
* emit_space - writes a space providing that text was written beforehand.
*/
void html_text::emit_space (void)
{
if (is_present(PRE_TAG))
do_emittext(" ", 1);
else
out->space_or_newline();
space_emitted = TRUE;
}
/*
* remove_def - removes a definition, t, from the stack.
*/
void html_text::remove_def (tag_definition *t)
{
tag_definition *p = stackptr;
tag_definition *l = 0;
tag_definition *q = 0;
while ((p != 0) && (p != t)) {
l = p;
p = p->next;
}
if ((p != 0) && (p == t)) {
if (p == stackptr) {
stackptr = stackptr->next;
if (stackptr == NULL)
lastptr = NULL;
q = stackptr;
} else if (l == 0) {
error("stack list pointers are wrong");
} else {
l->next = p->next;
q = p->next;
if (l->next == NULL)
lastptr = l;
}
delete p;
}
}
/*
* remove_tag - removes a tag from the stack.
*/
void html_text::remove_tag (HTML_TAG tag)
{
tag_definition *p = stackptr;
while ((p != 0) && (p->type != tag)) {
p = p->next;
}
if ((p != 0) && (p->type == tag))
remove_def(p);
}
/*
* remove_sub_sup - removes a sub or sup tag, should either exist
* on the stack.
*/
void html_text::remove_sub_sup (void)
{
if (is_present(SUB_TAG)) {
remove_tag(SUB_TAG);
}
if (is_present(SUP_TAG)) {
remove_tag(SUP_TAG);
}
if (is_present(PRE_TAG)) {
remove_tag(PRE_TAG);
}
}
/*
* remove_break - break tags are not balanced thus remove it once it has been emitted.
* It returns TRUE if text was emitted before the <br> was issued.
*/
int html_text::remove_break (void)
{
tag_definition *p = stackptr;
tag_definition *l = 0;
tag_definition *q = 0;
while ((p != 0) && (p->type != BREAK_TAG)) {
l = p;
p = p->next;
}
if ((p != 0) && (p->type == BREAK_TAG)) {
if (p == stackptr) {
stackptr = stackptr->next;
if (stackptr == NULL)
lastptr = NULL;
q = stackptr;
} else if (l == 0)
error("stack list pointers are wrong");
else {
l->next = p->next;
q = p->next;
if (l->next == NULL)
lastptr = l;
}
delete p;
}
/*
* now determine whether text was issued before <br>
*/
while (q != 0) {
if (q->text_emitted)
return TRUE;
else
q = q->next;
}
return FALSE;
}
/*
* remove_para_align - removes a paragraph which has a text
* argument. If the paragraph has no text
* argument then it is left alone.
*/
void html_text::remove_para_align (void)
{
if (is_present(P_TAG)) {
tag_definition *p=stackptr;
while (p != NULL) {
if (p->type == P_TAG && p->arg1 != NULL) {
html_indent *i = remove_indent(P_TAG);
int space = retrieve_para_space();
done_para();
do_para("", i, space);
return;
}
p = p->next;
}
}
}
/*
* get_alignment - returns the alignment for the paragraph.
* If no alignment was given then we return "".
*/
char *html_text::get_alignment (void)
{
if (is_present(P_TAG)) {
tag_definition *p=stackptr;
while (p != NULL) {
if (p->type == P_TAG && p->arg1 != NULL)
return (char *)p->arg1;
p = p->next;
}
}
return (char *)"";
}
/*
* do_small - potentially inserts a <small> tag into the html stream.
* However we check for a <big> tag, if present then we terminate it.
* Otherwise a <small> tag is inserted.
*/
void html_text::do_small (void)
{
if (is_present(BIG_TAG))
done_big();
else
push_para(SMALL_TAG);
}
/*
* do_big - is the mirror image of do_small.
*/
void html_text::do_big (void)
{
if (is_present(SMALL_TAG))
done_small();
else
push_para(BIG_TAG);
}
/*
* do_sup - save a superscript tag on the stack of tags.
*/
void html_text::do_sup (void)
{
push_para(SUP_TAG);
}
/*
* do_sub - save a subscript tag on the stack of tags.
*/
void html_text::do_sub (void)
{
push_para(SUB_TAG);
}