Return to html.c CVS log | Up to [cvsweb.bsd.lv] / mandoc |
version 1.192, 2016/01/04 12:45:29 | version 1.234, 2018/06/25 13:26:57 | ||
---|---|---|---|
|
|
||
/* $Id$ */ | /* $Id$ */ | ||
/* | /* | ||
* Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv> | * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv> | ||
* Copyright (c) 2011-2015 Ingo Schwarze <schwarze@openbsd.org> | * Copyright (c) 2011-2015, 2017, 2018 Ingo Schwarze <schwarze@openbsd.org> | ||
* | * | ||
* Permission to use, copy, modify, and distribute this software for any | * Permission to use, copy, modify, and distribute this software for any | ||
* purpose with or without fee is hereby granted, provided that the above | * purpose with or without fee is hereby granted, provided that the above | ||
|
|
||
#include <assert.h> | #include <assert.h> | ||
#include <ctype.h> | #include <ctype.h> | ||
#include <stdarg.h> | #include <stdarg.h> | ||
#include <stddef.h> | |||
#include <stdio.h> | #include <stdio.h> | ||
#include <stdint.h> | #include <stdint.h> | ||
#include <stdlib.h> | #include <stdlib.h> | ||
#include <string.h> | #include <string.h> | ||
#include <unistd.h> | #include <unistd.h> | ||
#include "mandoc.h" | |||
#include "mandoc_aux.h" | #include "mandoc_aux.h" | ||
#include "mandoc_ohash.h" | |||
#include "mandoc.h" | |||
#include "roff.h" | |||
#include "out.h" | #include "out.h" | ||
#include "html.h" | #include "html.h" | ||
#include "manconf.h" | #include "manconf.h" | ||
|
|
||
struct htmldata { | struct htmldata { | ||
const char *name; | const char *name; | ||
int flags; | int flags; | ||
#define HTML_CLRLINE (1 << 0) | #define HTML_NOSTACK (1 << 0) | ||
#define HTML_NOSTACK (1 << 1) | #define HTML_AUTOCLOSE (1 << 1) | ||
#define HTML_AUTOCLOSE (1 << 2) /* Tag has auto-closure. */ | #define HTML_NLBEFORE (1 << 2) | ||
#define HTML_NLBEGIN (1 << 3) | |||
#define HTML_NLEND (1 << 4) | |||
#define HTML_NLAFTER (1 << 5) | |||
#define HTML_NLAROUND (HTML_NLBEFORE | HTML_NLAFTER) | |||
#define HTML_NLINSIDE (HTML_NLBEGIN | HTML_NLEND) | |||
#define HTML_NLALL (HTML_NLAROUND | HTML_NLINSIDE) | |||
#define HTML_INDENT (1 << 6) | |||
#define HTML_NOINDENT (1 << 7) | |||
}; | }; | ||
static const struct htmldata htmltags[TAG_MAX] = { | static const struct htmldata htmltags[TAG_MAX] = { | ||
{"html", HTML_CLRLINE}, /* TAG_HTML */ | {"html", HTML_NLALL}, | ||
{"head", HTML_CLRLINE}, /* TAG_HEAD */ | {"head", HTML_NLALL | HTML_INDENT}, | ||
{"body", HTML_CLRLINE}, /* TAG_BODY */ | {"body", HTML_NLALL}, | ||
{"meta", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_META */ | {"meta", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, | ||
{"title", HTML_CLRLINE}, /* TAG_TITLE */ | {"title", HTML_NLAROUND}, | ||
{"div", HTML_CLRLINE}, /* TAG_DIV */ | {"div", HTML_NLAROUND}, | ||
{"h1", 0}, /* TAG_H1 */ | {"div", 0}, | ||
{"h2", 0}, /* TAG_H2 */ | {"h1", HTML_NLAROUND}, | ||
{"span", 0}, /* TAG_SPAN */ | {"h2", HTML_NLAROUND}, | ||
{"link", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_LINK */ | {"span", 0}, | ||
{"br", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_BR */ | {"link", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, | ||
{"a", 0}, /* TAG_A */ | {"br", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, | ||
{"table", HTML_CLRLINE}, /* TAG_TABLE */ | {"a", 0}, | ||
{"tbody", HTML_CLRLINE}, /* TAG_TBODY */ | {"table", HTML_NLALL | HTML_INDENT}, | ||
{"col", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_COL */ | {"colgroup", HTML_NLALL | HTML_INDENT}, | ||
{"tr", HTML_CLRLINE}, /* TAG_TR */ | {"col", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, | ||
{"td", HTML_CLRLINE}, /* TAG_TD */ | {"tr", HTML_NLALL | HTML_INDENT}, | ||
{"li", HTML_CLRLINE}, /* TAG_LI */ | {"td", HTML_NLAROUND}, | ||
{"ul", HTML_CLRLINE}, /* TAG_UL */ | {"li", HTML_NLAROUND | HTML_INDENT}, | ||
{"ol", HTML_CLRLINE}, /* TAG_OL */ | {"ul", HTML_NLALL | HTML_INDENT}, | ||
{"dl", HTML_CLRLINE}, /* TAG_DL */ | {"ol", HTML_NLALL | HTML_INDENT}, | ||
{"dt", HTML_CLRLINE}, /* TAG_DT */ | {"dl", HTML_NLALL | HTML_INDENT}, | ||
{"dd", HTML_CLRLINE}, /* TAG_DD */ | {"dt", HTML_NLAROUND}, | ||
{"blockquote", HTML_CLRLINE}, /* TAG_BLOCKQUOTE */ | {"dd", HTML_NLAROUND | HTML_INDENT}, | ||
{"pre", HTML_CLRLINE }, /* TAG_PRE */ | {"pre", HTML_NLALL | HTML_NOINDENT}, | ||
{"b", 0 }, /* TAG_B */ | {"var", 0}, | ||
{"i", 0 }, /* TAG_I */ | {"cite", 0}, | ||
{"code", 0 }, /* TAG_CODE */ | {"b", 0}, | ||
{"small", 0 }, /* TAG_SMALL */ | {"i", 0}, | ||
{"style", HTML_CLRLINE}, /* TAG_STYLE */ | {"code", 0}, | ||
{"math", HTML_CLRLINE}, /* TAG_MATH */ | {"small", 0}, | ||
{"mrow", 0}, /* TAG_MROW */ | {"style", HTML_NLALL | HTML_INDENT}, | ||
{"mi", 0}, /* TAG_MI */ | {"math", HTML_NLALL | HTML_INDENT}, | ||
{"mo", 0}, /* TAG_MO */ | {"mrow", 0}, | ||
{"msup", 0}, /* TAG_MSUP */ | {"mi", 0}, | ||
{"msub", 0}, /* TAG_MSUB */ | {"mn", 0}, | ||
{"msubsup", 0}, /* TAG_MSUBSUP */ | {"mo", 0}, | ||
{"mfrac", 0}, /* TAG_MFRAC */ | {"msup", 0}, | ||
{"msqrt", 0}, /* TAG_MSQRT */ | {"msub", 0}, | ||
{"mfenced", 0}, /* TAG_MFENCED */ | {"msubsup", 0}, | ||
{"mtable", 0}, /* TAG_MTABLE */ | {"mfrac", 0}, | ||
{"mtr", 0}, /* TAG_MTR */ | {"msqrt", 0}, | ||
{"mtd", 0}, /* TAG_MTD */ | {"mfenced", 0}, | ||
{"munderover", 0}, /* TAG_MUNDEROVER */ | {"mtable", 0}, | ||
{"munder", 0}, /* TAG_MUNDER*/ | {"mtr", 0}, | ||
{"mover", 0}, /* TAG_MOVER*/ | {"mtd", 0}, | ||
{"munderover", 0}, | |||
{"munder", 0}, | |||
{"mover", 0}, | |||
}; | }; | ||
static const char *const htmlattrs[ATTR_MAX] = { | |||
"name", /* ATTR_NAME */ | |||
"rel", /* ATTR_REL */ | |||
"href", /* ATTR_HREF */ | |||
"type", /* ATTR_TYPE */ | |||
"media", /* ATTR_MEDIA */ | |||
"class", /* ATTR_CLASS */ | |||
"style", /* ATTR_STYLE */ | |||
"id", /* ATTR_ID */ | |||
"colspan", /* ATTR_COLSPAN */ | |||
"charset", /* ATTR_CHARSET */ | |||
"open", /* ATTR_OPEN */ | |||
"close", /* ATTR_CLOSE */ | |||
"mathvariant", /* ATTR_MATHVARIANT */ | |||
}; | |||
static const char *const roffscales[SCALE_MAX] = { | static const char *const roffscales[SCALE_MAX] = { | ||
"cm", /* SCALE_CM */ | "cm", /* SCALE_CM */ | ||
"in", /* SCALE_IN */ | "in", /* SCALE_IN */ | ||
|
|
||
"ex", /* SCALE_FS */ | "ex", /* SCALE_FS */ | ||
}; | }; | ||
static void bufncat(struct html *, const char *, size_t); | /* Avoid duplicate HTML id= attributes. */ | ||
static struct ohash id_unique; | |||
static void a2width(const char *, struct roffsu *); | |||
static void print_byte(struct html *, char); | |||
static void print_endword(struct html *); | |||
static void print_indent(struct html *); | |||
static void print_word(struct html *, const char *); | |||
static void print_ctag(struct html *, struct tag *); | static void print_ctag(struct html *, struct tag *); | ||
static int print_escape(char); | static int print_escape(struct html *, char); | ||
static int print_encode(struct html *, const char *, int); | static int print_encode(struct html *, const char *, const char *, int); | ||
static void print_href(struct html *, const char *, const char *, int); | |||
static void print_metaf(struct html *, enum mandoc_esc); | static void print_metaf(struct html *, enum mandoc_esc); | ||
static void print_attr(struct html *, const char *, const char *); | |||
void * | void * | ||
|
|
||
h = mandoc_calloc(1, sizeof(struct html)); | h = mandoc_calloc(1, sizeof(struct html)); | ||
h->tags.head = NULL; | h->tag = NULL; | ||
h->style = outopts->style; | h->style = outopts->style; | ||
h->base_man = outopts->man; | h->base_man = outopts->man; | ||
h->base_includes = outopts->includes; | h->base_includes = outopts->includes; | ||
if (outopts->fragment) | if (outopts->fragment) | ||
h->oflags |= HTML_FRAGMENT; | h->oflags |= HTML_FRAGMENT; | ||
mandoc_ohash_init(&id_unique, 4, 0); | |||
return h; | return h; | ||
} | } | ||
|
|
||
{ | { | ||
struct tag *tag; | struct tag *tag; | ||
struct html *h; | struct html *h; | ||
char *cp; | |||
unsigned int slot; | |||
h = (struct html *)p; | h = (struct html *)p; | ||
while ((tag = h->tag) != NULL) { | |||
while ((tag = h->tags.head) != NULL) { | h->tag = tag->next; | ||
h->tags.head = tag->next; | |||
free(tag); | free(tag); | ||
} | } | ||
free(h); | free(h); | ||
cp = ohash_first(&id_unique, &slot); | |||
while (cp != NULL) { | |||
free(cp); | |||
cp = ohash_next(&id_unique, &slot); | |||
} | |||
ohash_delete(&id_unique); | |||
} | } | ||
void | void | ||
print_gen_head(struct html *h) | print_gen_head(struct html *h) | ||
{ | { | ||
struct htmlpair tag[4]; | |||
struct tag *t; | struct tag *t; | ||
tag[0].key = ATTR_CHARSET; | print_otag(h, TAG_META, "?", "charset", "utf-8"); | ||
tag[0].val = "utf-8"; | if (h->style != NULL) { | ||
print_otag(h, TAG_META, 1, tag); | print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet", | ||
h->style, "type", "text/css", "media", "all"); | |||
return; | |||
} | |||
/* | /* | ||
* Print a default style-sheet. | * Print a minimal embedded style sheet. | ||
*/ | */ | ||
t = print_otag(h, TAG_STYLE, 0, NULL); | |||
print_text(h, "table.head, table.foot { width: 100%; }\n" | |||
"td.head-rtitle, td.foot-os { text-align: right; }\n" | |||
"td.head-vol { text-align: center; }\n" | |||
"table.foot td { width: 50%; }\n" | |||
"table.head td { width: 33%; }\n" | |||
"div.spacer { margin: 1em 0; }\n"); | |||
print_tagq(h, t); | |||
if (h->style) { | t = print_otag(h, TAG_STYLE, ""); | ||
tag[0].key = ATTR_REL; | print_text(h, "table.head, table.foot { width: 100%; }"); | ||
tag[0].val = "stylesheet"; | print_endline(h); | ||
tag[1].key = ATTR_HREF; | print_text(h, "td.head-rtitle, td.foot-os { text-align: right; }"); | ||
tag[1].val = h->style; | print_endline(h); | ||
tag[2].key = ATTR_TYPE; | print_text(h, "td.head-vol { text-align: center; }"); | ||
tag[2].val = "text/css"; | print_endline(h); | ||
tag[3].key = ATTR_MEDIA; | print_text(h, "div.Pp { margin: 1ex 0ex; }"); | ||
tag[3].val = "all"; | print_endline(h); | ||
print_otag(h, TAG_LINK, 4, tag); | print_text(h, "div.Nd, div.Bf, div.Op { display: inline; }"); | ||
} | print_endline(h); | ||
print_text(h, "span.Pa, span.Ad { font-style: italic; }"); | |||
print_endline(h); | |||
print_text(h, "span.Ms { font-weight: bold; }"); | |||
print_endline(h); | |||
print_text(h, "dl.Bl-diag "); | |||
print_byte(h, '>'); | |||
print_text(h, " dt { font-weight: bold; }"); | |||
print_endline(h); | |||
print_text(h, "code.Nm, code.Fl, code.Cm, code.Ic, " | |||
"code.In, code.Fd, code.Fn,"); | |||
print_endline(h); | |||
print_text(h, "code.Cd { font-weight: bold; " | |||
"font-family: inherit; }"); | |||
print_tagq(h, t); | |||
} | } | ||
static void | static void | ||
|
|
||
switch (font) { | switch (font) { | ||
case HTMLFONT_ITALIC: | case HTMLFONT_ITALIC: | ||
h->metaf = print_otag(h, TAG_I, 0, NULL); | h->metaf = print_otag(h, TAG_I, ""); | ||
break; | break; | ||
case HTMLFONT_BOLD: | case HTMLFONT_BOLD: | ||
h->metaf = print_otag(h, TAG_B, 0, NULL); | h->metaf = print_otag(h, TAG_B, ""); | ||
break; | break; | ||
case HTMLFONT_BI: | case HTMLFONT_BI: | ||
h->metaf = print_otag(h, TAG_B, 0, NULL); | h->metaf = print_otag(h, TAG_B, ""); | ||
print_otag(h, TAG_I, 0, NULL); | print_otag(h, TAG_I, ""); | ||
break; | break; | ||
default: | default: | ||
break; | break; | ||
} | } | ||
} | } | ||
char * | |||
html_make_id(const struct roff_node *n, int unique) | |||
{ | |||
const struct roff_node *nch; | |||
char *buf, *bufs, *cp; | |||
unsigned int slot; | |||
int suffix; | |||
for (nch = n->child; nch != NULL; nch = nch->next) | |||
if (nch->type != ROFFT_TEXT) | |||
return NULL; | |||
buf = NULL; | |||
deroff(&buf, n); | |||
if (buf == NULL) | |||
return NULL; | |||
/* | |||
* In ID attributes, only use ASCII characters that are | |||
* permitted in URL-fragment strings according to the | |||
* explicit list at: | |||
* https://url.spec.whatwg.org/#url-fragment-string | |||
*/ | |||
for (cp = buf; *cp != '\0'; cp++) | |||
if (isalnum((unsigned char)*cp) == 0 && | |||
strchr("!$&'()*+,-./:;=?@_~", *cp) == NULL) | |||
*cp = '_'; | |||
if (unique == 0) | |||
return buf; | |||
/* Avoid duplicate HTML id= attributes. */ | |||
bufs = NULL; | |||
suffix = 1; | |||
slot = ohash_qlookup(&id_unique, buf); | |||
cp = ohash_find(&id_unique, slot); | |||
if (cp != NULL) { | |||
while (cp != NULL) { | |||
free(bufs); | |||
if (++suffix > 127) { | |||
free(buf); | |||
return NULL; | |||
} | |||
mandoc_asprintf(&bufs, "%s_%d", buf, suffix); | |||
slot = ohash_qlookup(&id_unique, bufs); | |||
cp = ohash_find(&id_unique, slot); | |||
} | |||
free(buf); | |||
buf = bufs; | |||
} | |||
ohash_insert(&id_unique, slot, buf); | |||
return buf; | |||
} | |||
int | int | ||
html_strlen(const char *cp) | html_strlen(const char *cp) | ||
{ | { | ||
|
|
||
} | } | ||
static int | static int | ||
print_escape(char c) | print_escape(struct html *h, char c) | ||
{ | { | ||
switch (c) { | switch (c) { | ||
case '<': | case '<': | ||
printf("<"); | print_word(h, "<"); | ||
break; | break; | ||
case '>': | case '>': | ||
printf(">"); | print_word(h, ">"); | ||
break; | break; | ||
case '&': | case '&': | ||
printf("&"); | print_word(h, "&"); | ||
break; | break; | ||
case '"': | case '"': | ||
printf("""); | print_word(h, """); | ||
break; | break; | ||
case ASCII_NBRSP: | case ASCII_NBRSP: | ||
printf(" "); | print_word(h, " "); | ||
break; | break; | ||
case ASCII_HYPH: | case ASCII_HYPH: | ||
putchar('-'); | print_byte(h, '-'); | ||
break; | break; | ||
case ASCII_BREAK: | case ASCII_BREAK: | ||
break; | break; | ||
|
|
||
} | } | ||
static int | static int | ||
print_encode(struct html *h, const char *p, int norecurse) | print_encode(struct html *h, const char *p, const char *pend, int norecurse) | ||
{ | { | ||
size_t sz; | char numbuf[16]; | ||
int c, len, nospace; | struct tag *t; | ||
const char *seq; | const char *seq; | ||
size_t sz; | |||
int c, len, breakline, nospace; | |||
enum mandoc_esc esc; | enum mandoc_esc esc; | ||
static const char rejs[9] = { '\\', '<', '>', '&', '"', | static const char rejs[10] = { ' ', '\\', '<', '>', '&', '"', | ||
ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' }; | ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' }; | ||
if (pend == NULL) | |||
pend = strchr(p, '\0'); | |||
breakline = 0; | |||
nospace = 0; | nospace = 0; | ||
while ('\0' != *p) { | while (p < pend) { | ||
if (HTML_SKIPCHAR & h->flags && '\\' != *p) { | if (HTML_SKIPCHAR & h->flags && '\\' != *p) { | ||
h->flags &= ~HTML_SKIPCHAR; | h->flags &= ~HTML_SKIPCHAR; | ||
p++; | p++; | ||
continue; | continue; | ||
} | } | ||
sz = strcspn(p, rejs); | for (sz = strcspn(p, rejs); sz-- && p < pend; p++) | ||
print_byte(h, *p); | |||
fwrite(p, 1, sz, stdout); | if (breakline && | ||
p += (int)sz; | (p >= pend || *p == ' ' || *p == ASCII_NBRSP)) { | ||
t = print_otag(h, TAG_DIV, ""); | |||
print_text(h, "\\~"); | |||
print_tagq(h, t); | |||
breakline = 0; | |||
while (p < pend && (*p == ' ' || *p == ASCII_NBRSP)) | |||
p++; | |||
continue; | |||
} | |||
if ('\0' == *p) | if (p >= pend) | ||
break; | break; | ||
if (print_escape(*p++)) | if (*p == ' ') { | ||
print_endword(h); | |||
p++; | |||
continue; | continue; | ||
} | |||
if (print_escape(h, *p++)) | |||
continue; | |||
esc = mandoc_escape(&p, &seq, &len); | esc = mandoc_escape(&p, &seq, &len); | ||
if (ESCAPE_ERROR == esc) | if (ESCAPE_ERROR == esc) | ||
break; | break; | ||
|
|
||
if (c <= 0) | if (c <= 0) | ||
continue; | continue; | ||
break; | break; | ||
case ESCAPE_BREAK: | |||
breakline = 1; | |||
continue; | |||
case ESCAPE_NOSPACE: | case ESCAPE_NOSPACE: | ||
if ('\0' == *p) | if ('\0' == *p) | ||
nospace = 1; | nospace = 1; | ||
|
|
||
if ((c < 0x20 && c != 0x09) || | if ((c < 0x20 && c != 0x09) || | ||
(c > 0x7E && c < 0xA0)) | (c > 0x7E && c < 0xA0)) | ||
c = 0xFFFD; | c = 0xFFFD; | ||
if (c > 0x7E) | if (c > 0x7E) { | ||
printf("&#%d;", c); | (void)snprintf(numbuf, sizeof(numbuf), "&#x%.4X;", c); | ||
else if ( ! print_escape(c)) | print_word(h, numbuf); | ||
putchar(c); | } else if (print_escape(h, c) == 0) | ||
print_byte(h, c); | |||
} | } | ||
return nospace; | return nospace; | ||
} | } | ||
static void | static void | ||
print_attr(struct html *h, const char *key, const char *val) | print_href(struct html *h, const char *name, const char *sec, int man) | ||
{ | { | ||
printf(" %s=\"", key); | const char *p, *pp; | ||
(void)print_encode(h, val, 1); | |||
putchar('\"'); | pp = man ? h->base_man : h->base_includes; | ||
while ((p = strchr(pp, '%')) != NULL) { | |||
print_encode(h, pp, p, 1); | |||
if (man && p[1] == 'S') { | |||
if (sec == NULL) | |||
print_byte(h, '1'); | |||
else | |||
print_encode(h, sec, NULL, 1); | |||
} else if ((man && p[1] == 'N') || | |||
(man == 0 && p[1] == 'I')) | |||
print_encode(h, name, NULL, 1); | |||
else | |||
print_encode(h, p, p + 2, 1); | |||
pp = p + 2; | |||
} | |||
if (*pp != '\0') | |||
print_encode(h, pp, NULL, 1); | |||
} | } | ||
struct tag * | struct tag * | ||
print_otag(struct html *h, enum htmltag tag, | print_otag(struct html *h, enum htmltag tag, const char *fmt, ...) | ||
int sz, const struct htmlpair *p) | |||
{ | { | ||
int i; | va_list ap; | ||
struct roffsu mysu, *su; | |||
char numbuf[16]; | |||
struct tag *t; | struct tag *t; | ||
const char *attr; | |||
char *arg1, *arg2; | |||
double v; | |||
int i, have_style, tflags; | |||
/* Push this tags onto the stack of open scopes. */ | tflags = htmltags[tag].flags; | ||
if ( ! (HTML_NOSTACK & htmltags[tag].flags)) { | /* Push this tag onto the stack of open scopes. */ | ||
if ((tflags & HTML_NOSTACK) == 0) { | |||
t = mandoc_malloc(sizeof(struct tag)); | t = mandoc_malloc(sizeof(struct tag)); | ||
t->tag = tag; | t->tag = tag; | ||
t->next = h->tags.head; | t->next = h->tag; | ||
h->tags.head = t; | h->tag = t; | ||
} else | } else | ||
t = NULL; | t = NULL; | ||
if ( ! (HTML_NOSPACE & h->flags)) | if (tflags & HTML_NLBEFORE) | ||
if ( ! (HTML_CLRLINE & htmltags[tag].flags)) { | print_endline(h); | ||
/* Manage keeps! */ | if (h->col == 0) | ||
if ( ! (HTML_KEEP & h->flags)) { | print_indent(h); | ||
if (HTML_PREKEEP & h->flags) | else if ((h->flags & HTML_NOSPACE) == 0) { | ||
h->flags |= HTML_KEEP; | if (h->flags & HTML_KEEP) | ||
putchar(' '); | print_word(h, " "); | ||
} else | else { | ||
printf(" "); | if (h->flags & HTML_PREKEEP) | ||
h->flags |= HTML_KEEP; | |||
print_endword(h); | |||
} | } | ||
} | |||
if ( ! (h->flags & HTML_NONOSPACE)) | if ( ! (h->flags & HTML_NONOSPACE)) | ||
h->flags &= ~HTML_NOSPACE; | h->flags &= ~HTML_NOSPACE; | ||
|
|
||
/* Print out the tag name and attributes. */ | /* Print out the tag name and attributes. */ | ||
printf("<%s", htmltags[tag].name); | print_byte(h, '<'); | ||
for (i = 0; i < sz; i++) | print_word(h, htmltags[tag].name); | ||
print_attr(h, htmlattrs[p[i].key], p[i].val); | |||
va_start(ap, fmt); | |||
have_style = 0; | |||
while (*fmt != '\0') { | |||
if (*fmt == 's') { | |||
have_style = 1; | |||
fmt++; | |||
break; | |||
} | |||
/* Parse a non-style attribute and its arguments. */ | |||
arg1 = va_arg(ap, char *); | |||
switch (*fmt++) { | |||
case 'c': | |||
attr = "class"; | |||
break; | |||
case 'h': | |||
attr = "href"; | |||
break; | |||
case 'i': | |||
attr = "id"; | |||
break; | |||
case '?': | |||
attr = arg1; | |||
arg1 = va_arg(ap, char *); | |||
break; | |||
default: | |||
abort(); | |||
} | |||
arg2 = NULL; | |||
if (*fmt == 'M') | |||
arg2 = va_arg(ap, char *); | |||
if (arg1 == NULL) | |||
continue; | |||
/* Print the non-style attributes. */ | |||
print_byte(h, ' '); | |||
print_word(h, attr); | |||
print_byte(h, '='); | |||
print_byte(h, '"'); | |||
switch (*fmt) { | |||
case 'I': | |||
print_href(h, arg1, NULL, 0); | |||
fmt++; | |||
break; | |||
case 'M': | |||
print_href(h, arg1, arg2, 1); | |||
fmt++; | |||
break; | |||
case 'R': | |||
print_byte(h, '#'); | |||
print_encode(h, arg1, NULL, 1); | |||
fmt++; | |||
break; | |||
case 'T': | |||
print_encode(h, arg1, NULL, 1); | |||
print_word(h, "\" title=\""); | |||
print_encode(h, arg1, NULL, 1); | |||
fmt++; | |||
break; | |||
default: | |||
print_encode(h, arg1, NULL, 1); | |||
break; | |||
} | |||
print_byte(h, '"'); | |||
} | |||
/* Print out styles. */ | |||
while (*fmt != '\0') { | |||
arg1 = NULL; | |||
su = NULL; | |||
/* First letter: input argument type. */ | |||
switch (*fmt++) { | |||
case 'h': | |||
i = va_arg(ap, int); | |||
su = &mysu; | |||
SCALE_HS_INIT(su, i); | |||
break; | |||
case 's': | |||
arg1 = va_arg(ap, char *); | |||
break; | |||
case 'u': | |||
su = va_arg(ap, struct roffsu *); | |||
break; | |||
case 'w': | |||
if ((arg2 = va_arg(ap, char *)) != NULL) { | |||
su = &mysu; | |||
a2width(arg2, su); | |||
} | |||
if (*fmt == '+') { | |||
if (su != NULL) { | |||
/* Make even bold text fit. */ | |||
su->scale *= 1.2; | |||
/* Add padding. */ | |||
su->scale += 3.0; | |||
} | |||
fmt++; | |||
} | |||
break; | |||
default: | |||
abort(); | |||
} | |||
/* Second letter: style name. */ | |||
switch (*fmt++) { | |||
case 'h': | |||
attr = "height"; | |||
break; | |||
case 'l': | |||
attr = "margin-left"; | |||
break; | |||
case 'w': | |||
attr = "width"; | |||
break; | |||
case 'W': | |||
attr = "min-width"; | |||
break; | |||
case '?': | |||
attr = arg1; | |||
arg1 = va_arg(ap, char *); | |||
break; | |||
default: | |||
abort(); | |||
} | |||
if (su == NULL && arg1 == NULL) | |||
continue; | |||
if (have_style == 1) | |||
print_word(h, " style=\""); | |||
else | |||
print_byte(h, ' '); | |||
print_word(h, attr); | |||
print_byte(h, ':'); | |||
print_byte(h, ' '); | |||
if (su != NULL) { | |||
v = su->scale; | |||
if (su->unit == SCALE_MM && (v /= 100.0) == 0.0) | |||
v = 1.0; | |||
else if (su->unit == SCALE_BU) | |||
v /= 24.0; | |||
(void)snprintf(numbuf, sizeof(numbuf), "%.2f", v); | |||
print_word(h, numbuf); | |||
print_word(h, roffscales[su->unit]); | |||
} else | |||
print_word(h, arg1); | |||
print_byte(h, ';'); | |||
have_style = 2; | |||
} | |||
if (have_style == 2) | |||
print_byte(h, '"'); | |||
va_end(ap); | |||
/* Accommodate for "well-formed" singleton escaping. */ | /* Accommodate for "well-formed" singleton escaping. */ | ||
if (HTML_AUTOCLOSE & htmltags[tag].flags) | if (HTML_AUTOCLOSE & htmltags[tag].flags) | ||
putchar('/'); | print_byte(h, '/'); | ||
putchar('>'); | print_byte(h, '>'); | ||
h->flags |= HTML_NOSPACE; | if (tflags & HTML_NLBEGIN) | ||
print_endline(h); | |||
else | |||
h->flags |= HTML_NOSPACE; | |||
if ((HTML_AUTOCLOSE | HTML_CLRLINE) & htmltags[tag].flags) | if (tflags & HTML_INDENT) | ||
putchar('\n'); | h->indent++; | ||
if (tflags & HTML_NOINDENT) | |||
h->noindent++; | |||
return t; | return t; | ||
} | } | ||
|
|
||
static void | static void | ||
print_ctag(struct html *h, struct tag *tag) | print_ctag(struct html *h, struct tag *tag) | ||
{ | { | ||
int tflags; | |||
/* | /* | ||
* Remember to close out and nullify the current | * Remember to close out and nullify the current | ||
|
|
||
if (tag == h->tblt) | if (tag == h->tblt) | ||
h->tblt = NULL; | h->tblt = NULL; | ||
printf("</%s>", htmltags[tag->tag].name); | tflags = htmltags[tag->tag].flags; | ||
if (HTML_CLRLINE & htmltags[tag->tag].flags) { | |||
h->flags |= HTML_NOSPACE; | |||
putchar('\n'); | |||
} | |||
h->tags.head = tag->next; | if (tflags & HTML_INDENT) | ||
h->indent--; | |||
if (tflags & HTML_NOINDENT) | |||
h->noindent--; | |||
if (tflags & HTML_NLEND) | |||
print_endline(h); | |||
print_indent(h); | |||
print_byte(h, '<'); | |||
print_byte(h, '/'); | |||
print_word(h, htmltags[tag->tag].name); | |||
print_byte(h, '>'); | |||
if (tflags & HTML_NLAFTER) | |||
print_endline(h); | |||
h->tag = tag->next; | |||
free(tag); | free(tag); | ||
} | } | ||
void | void | ||
print_gen_decls(struct html *h) | print_gen_decls(struct html *h) | ||
{ | { | ||
print_word(h, "<!DOCTYPE html>"); | |||
print_endline(h); | |||
} | |||
puts("<!DOCTYPE html>"); | void | ||
print_gen_comment(struct html *h, struct roff_node *n) | |||
{ | |||
int wantblank; | |||
print_word(h, "<!-- This is an automatically generated file." | |||
" Do not edit."); | |||
h->indent = 1; | |||
wantblank = 0; | |||
while (n != NULL && n->type == ROFFT_COMMENT) { | |||
if (strstr(n->string, "-->") == NULL && | |||
(wantblank || *n->string != '\0')) { | |||
print_endline(h); | |||
print_indent(h); | |||
print_word(h, n->string); | |||
wantblank = *n->string != '\0'; | |||
} | |||
n = n->next; | |||
} | |||
if (wantblank) | |||
print_endline(h); | |||
print_word(h, " -->"); | |||
print_endline(h); | |||
h->indent = 0; | |||
} | } | ||
void | void | ||
print_text(struct html *h, const char *word) | print_text(struct html *h, const char *word) | ||
{ | { | ||
if (h->col && (h->flags & HTML_NOSPACE) == 0) { | |||
if ( ! (HTML_NOSPACE & h->flags)) { | |||
/* Manage keeps! */ | |||
if ( ! (HTML_KEEP & h->flags)) { | if ( ! (HTML_KEEP & h->flags)) { | ||
if (HTML_PREKEEP & h->flags) | if (HTML_PREKEEP & h->flags) | ||
h->flags |= HTML_KEEP; | h->flags |= HTML_KEEP; | ||
putchar(' '); | print_endword(h); | ||
} else | } else | ||
printf(" "); | print_word(h, " "); | ||
} | } | ||
assert(NULL == h->metaf); | assert(NULL == h->metaf); | ||
switch (h->metac) { | switch (h->metac) { | ||
case HTMLFONT_ITALIC: | case HTMLFONT_ITALIC: | ||
h->metaf = print_otag(h, TAG_I, 0, NULL); | h->metaf = print_otag(h, TAG_I, ""); | ||
break; | break; | ||
case HTMLFONT_BOLD: | case HTMLFONT_BOLD: | ||
h->metaf = print_otag(h, TAG_B, 0, NULL); | h->metaf = print_otag(h, TAG_B, ""); | ||
break; | break; | ||
case HTMLFONT_BI: | case HTMLFONT_BI: | ||
h->metaf = print_otag(h, TAG_B, 0, NULL); | h->metaf = print_otag(h, TAG_B, ""); | ||
print_otag(h, TAG_I, 0, NULL); | print_otag(h, TAG_I, ""); | ||
break; | break; | ||
default: | default: | ||
print_indent(h); | |||
break; | break; | ||
} | } | ||
assert(word); | assert(word); | ||
if ( ! print_encode(h, word, 0)) { | if ( ! print_encode(h, word, NULL, 0)) { | ||
if ( ! (h->flags & HTML_NONOSPACE)) | if ( ! (h->flags & HTML_NONOSPACE)) | ||
h->flags &= ~HTML_NOSPACE; | h->flags &= ~HTML_NOSPACE; | ||
h->flags &= ~HTML_NONEWLINE; | h->flags &= ~HTML_NONEWLINE; | ||
|
|
||
{ | { | ||
struct tag *tag; | struct tag *tag; | ||
while ((tag = h->tags.head) != NULL) { | while ((tag = h->tag) != NULL) { | ||
print_ctag(h, tag); | print_ctag(h, tag); | ||
if (until && tag == until) | if (until && tag == until) | ||
return; | return; | ||
|
|
||
{ | { | ||
struct tag *tag; | struct tag *tag; | ||
while ((tag = h->tags.head) != NULL) { | while ((tag = h->tag) != NULL) { | ||
if (suntil && tag == suntil) | if (suntil && tag == suntil) | ||
return; | return; | ||
print_ctag(h, tag); | print_ctag(h, tag); | ||
|
|
||
print_paragraph(struct html *h) | print_paragraph(struct html *h) | ||
{ | { | ||
struct tag *t; | struct tag *t; | ||
struct htmlpair tag; | |||
PAIR_CLASS_INIT(&tag, "spacer"); | t = print_otag(h, TAG_DIV, "c", "Pp"); | ||
t = print_otag(h, TAG_DIV, 1, &tag); | |||
print_tagq(h, t); | print_tagq(h, t); | ||
} | } | ||
void | /*********************************************************************** | ||
bufinit(struct html *h) | * Low level output functions. | ||
{ | * They implement line breaking using a short static buffer. | ||
***********************************************************************/ | |||
h->buf[0] = '\0'; | /* | ||
h->buflen = 0; | * Buffer one HTML output byte. | ||
} | * If the buffer is full, flush and deactivate it and start a new line. | ||
* If the buffer is inactive, print directly. | |||
void | */ | ||
bufcat_style(struct html *h, const char *key, const char *val) | static void | ||
print_byte(struct html *h, char c) | |||
{ | { | ||
if ((h->flags & HTML_BUFFER) == 0) { | |||
putchar(c); | |||
h->col++; | |||
return; | |||
} | |||
bufcat(h, key); | if (h->col + h->bufcol < sizeof(h->buf)) { | ||
bufcat(h, ":"); | h->buf[h->bufcol++] = c; | ||
bufcat(h, val); | return; | ||
bufcat(h, ";"); | } | ||
} | |||
void | putchar('\n'); | ||
bufcat(struct html *h, const char *p) | h->col = 0; | ||
{ | print_indent(h); | ||
putchar(' '); | |||
/* | putchar(' '); | ||
* XXX This is broken and not easy to fix. | fwrite(h->buf, h->bufcol, 1, stdout); | ||
* When using the -Oincludes option, buffmt_includes() | putchar(c); | ||
* may pass in strings overrunning BUFSIZ, causing a crash. | h->col = (h->indent + 1) * 2 + h->bufcol + 1; | ||
*/ | h->bufcol = 0; | ||
h->flags &= ~HTML_BUFFER; | |||
h->buflen = strlcat(h->buf, p, BUFSIZ); | |||
assert(h->buflen < BUFSIZ); | |||
} | } | ||
/* | |||
* If something was printed on the current output line, end it. | |||
* Not to be called right after print_indent(). | |||
*/ | |||
void | void | ||
bufcat_fmt(struct html *h, const char *fmt, ...) | print_endline(struct html *h) | ||
{ | { | ||
va_list ap; | if (h->col == 0) | ||
return; | |||
va_start(ap, fmt); | if (h->bufcol) { | ||
(void)vsnprintf(h->buf + (int)h->buflen, | putchar(' '); | ||
BUFSIZ - h->buflen - 1, fmt, ap); | fwrite(h->buf, h->bufcol, 1, stdout); | ||
va_end(ap); | h->bufcol = 0; | ||
h->buflen = strlen(h->buf); | } | ||
putchar('\n'); | |||
h->col = 0; | |||
h->flags |= HTML_NOSPACE; | |||
h->flags &= ~HTML_BUFFER; | |||
} | } | ||
/* | |||
* Flush the HTML output buffer. | |||
* If it is inactive, activate it. | |||
*/ | |||
static void | static void | ||
bufncat(struct html *h, const char *p, size_t sz) | print_endword(struct html *h) | ||
{ | { | ||
if (h->noindent) { | |||
print_byte(h, ' '); | |||
return; | |||
} | |||
assert(h->buflen + sz + 1 < BUFSIZ); | if ((h->flags & HTML_BUFFER) == 0) { | ||
strncat(h->buf, p, sz); | h->col++; | ||
h->buflen += sz; | h->flags |= HTML_BUFFER; | ||
} | } else if (h->bufcol) { | ||
putchar(' '); | |||
void | fwrite(h->buf, h->bufcol, 1, stdout); | ||
buffmt_includes(struct html *h, const char *name) | h->col += h->bufcol + 1; | ||
{ | |||
const char *p, *pp; | |||
pp = h->base_includes; | |||
bufinit(h); | |||
while (NULL != (p = strchr(pp, '%'))) { | |||
bufncat(h, pp, (size_t)(p - pp)); | |||
switch (*(p + 1)) { | |||
case'I': | |||
bufcat(h, name); | |||
break; | |||
default: | |||
bufncat(h, p, 2); | |||
break; | |||
} | |||
pp = p + 2; | |||
} | } | ||
if (pp) | h->bufcol = 0; | ||
bufcat(h, pp); | |||
} | } | ||
void | /* | ||
buffmt_man(struct html *h, const char *name, const char *sec) | * If at the beginning of a new output line, | ||
* perform indentation and mark the line as containing output. | |||
* Make sure to really produce some output right afterwards, | |||
* but do not use print_otag() for producing it. | |||
*/ | |||
static void | |||
print_indent(struct html *h) | |||
{ | { | ||
const char *p, *pp; | size_t i; | ||
pp = h->base_man; | if (h->col) | ||
return; | |||
bufinit(h); | if (h->noindent == 0) { | ||
while (NULL != (p = strchr(pp, '%'))) { | h->col = h->indent * 2; | ||
bufncat(h, pp, (size_t)(p - pp)); | for (i = 0; i < h->col; i++) | ||
switch (*(p + 1)) { | putchar(' '); | ||
case 'S': | |||
bufcat(h, sec ? sec : "1"); | |||
break; | |||
case 'N': | |||
bufcat_fmt(h, "%s", name); | |||
break; | |||
default: | |||
bufncat(h, p, 2); | |||
break; | |||
} | |||
pp = p + 2; | |||
} | } | ||
if (pp) | h->flags &= ~HTML_NOSPACE; | ||
bufcat(h, pp); | |||
} | } | ||
void | /* | ||
bufcat_su(struct html *h, const char *p, const struct roffsu *su) | * Print or buffer some characters | ||
* depending on the current HTML output buffer state. | |||
*/ | |||
static void | |||
print_word(struct html *h, const char *cp) | |||
{ | { | ||
double v; | while (*cp != '\0') | ||
print_byte(h, *cp++); | |||
v = su->scale; | |||
if (SCALE_MM == su->unit && 0.0 == (v /= 100.0)) | |||
v = 1.0; | |||
else if (SCALE_BU == su->unit) | |||
v /= 24.0; | |||
bufcat_fmt(h, "%s: %.2f%s;", p, v, roffscales[su->unit]); | |||
} | } | ||
void | /* | ||
bufcat_id(struct html *h, const char *src) | * Calculate the scaling unit passed in a `-width' argument. This uses | ||
* either a native scaling unit (e.g., 1i, 2m) or the string length of | |||
* the value. | |||
*/ | |||
static void | |||
a2width(const char *p, struct roffsu *su) | |||
{ | { | ||
const char *end; | |||
/* Cf. <http://www.w3.org/TR/html5/dom.html#the-id-attribute>. */ | end = a2roffsu(p, su, SCALE_MAX); | ||
if (end == NULL || *end != '\0') { | |||
for (; '\0' != *src; src++) | su->unit = SCALE_EN; | ||
bufncat(h, *src == ' ' ? "_" : src, 1); | su->scale = html_strlen(p); | ||
} else if (su->scale < 0.0) | |||
su->scale = 0.0; | |||
} | } |