![]() ![]() | ![]() |
version 1.131, 2011/03/22 14:05:45 | version 1.194, 2017/01/17 01:47:51 | ||
---|---|---|---|
|
|
||
/* $Id$ */ | /* $Id$ */ | ||
/* | /* | ||
* Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> | * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv> | ||
* Copyright (c) 2011 Ingo Schwarze <schwarze@openbsd.org> | * Copyright (c) 2011-2015, 2017 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 | ||
* copyright notice and this permission notice appear in all copies. | * copyright notice and this permission notice appear in all copies. | ||
* | * | ||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES | ||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR | ||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
*/ | */ | ||
#ifdef HAVE_CONFIG_H | |||
#include "config.h" | #include "config.h" | ||
#endif | |||
#include <sys/types.h> | #include <sys/types.h> | ||
|
|
||
#include <unistd.h> | #include <unistd.h> | ||
#include "mandoc.h" | #include "mandoc.h" | ||
#include "mandoc_aux.h" | |||
#include "out.h" | #include "out.h" | ||
#include "html.h" | #include "html.h" | ||
#include "manconf.h" | |||
#include "main.h" | #include "main.h" | ||
struct htmldata { | struct htmldata { | ||
|
|
||
{"dt", HTML_CLRLINE}, /* TAG_DT */ | {"dt", HTML_CLRLINE}, /* TAG_DT */ | ||
{"dd", HTML_CLRLINE}, /* TAG_DD */ | {"dd", HTML_CLRLINE}, /* TAG_DD */ | ||
{"blockquote", HTML_CLRLINE}, /* TAG_BLOCKQUOTE */ | {"blockquote", HTML_CLRLINE}, /* TAG_BLOCKQUOTE */ | ||
{"p", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_P */ | |||
{"pre", HTML_CLRLINE }, /* TAG_PRE */ | {"pre", HTML_CLRLINE }, /* TAG_PRE */ | ||
{"b", 0 }, /* TAG_B */ | {"b", 0 }, /* TAG_B */ | ||
{"i", 0 }, /* TAG_I */ | {"i", 0 }, /* TAG_I */ | ||
{"code", 0 }, /* TAG_CODE */ | {"code", 0 }, /* TAG_CODE */ | ||
{"small", 0 }, /* TAG_SMALL */ | {"small", 0 }, /* TAG_SMALL */ | ||
{"style", HTML_CLRLINE}, /* TAG_STYLE */ | |||
{"math", HTML_CLRLINE}, /* TAG_MATH */ | |||
{"mrow", 0}, /* TAG_MROW */ | |||
{"mi", 0}, /* TAG_MI */ | |||
{"mo", 0}, /* TAG_MO */ | |||
{"msup", 0}, /* TAG_MSUP */ | |||
{"msub", 0}, /* TAG_MSUB */ | |||
{"msubsup", 0}, /* TAG_MSUBSUP */ | |||
{"mfrac", 0}, /* TAG_MFRAC */ | |||
{"msqrt", 0}, /* TAG_MSQRT */ | |||
{"mfenced", 0}, /* TAG_MFENCED */ | |||
{"mtable", 0}, /* TAG_MTABLE */ | |||
{"mtr", 0}, /* TAG_MTR */ | |||
{"mtd", 0}, /* TAG_MTD */ | |||
{"munderover", 0}, /* TAG_MUNDEROVER */ | |||
{"munder", 0}, /* TAG_MUNDER*/ | |||
{"mover", 0}, /* TAG_MOVER*/ | |||
}; | }; | ||
static const char *const htmlattrs[ATTR_MAX] = { | static const char *const roffscales[SCALE_MAX] = { | ||
"http-equiv", /* ATTR_HTTPEQUIV */ | "cm", /* SCALE_CM */ | ||
"content", /* ATTR_CONTENT */ | "in", /* SCALE_IN */ | ||
"name", /* ATTR_NAME */ | "pc", /* SCALE_PC */ | ||
"rel", /* ATTR_REL */ | "pt", /* SCALE_PT */ | ||
"href", /* ATTR_HREF */ | "em", /* SCALE_EM */ | ||
"type", /* ATTR_TYPE */ | "em", /* SCALE_MM */ | ||
"media", /* ATTR_MEDIA */ | "ex", /* SCALE_EN */ | ||
"class", /* ATTR_CLASS */ | "ex", /* SCALE_BU */ | ||
"style", /* ATTR_STYLE */ | "em", /* SCALE_VS */ | ||
"width", /* ATTR_WIDTH */ | "ex", /* SCALE_FS */ | ||
"id", /* ATTR_ID */ | |||
"summary", /* ATTR_SUMMARY */ | |||
"align", /* ATTR_ALIGN */ | |||
"colspan", /* ATTR_COLSPAN */ | |||
}; | }; | ||
static void print_num(struct html *, const char *, size_t); | static void a2width(const char *, struct roffsu *); | ||
static void print_spec(struct html *, enum roffdeco, | static void bufncat(struct html *, const char *, size_t); | ||
const char *, size_t); | static void print_ctag(struct html *, struct tag *); | ||
static void print_res(struct html *, const char *, size_t); | static int print_escape(char); | ||
static void print_ctag(struct html *, enum htmltag); | static int print_encode(struct html *, const char *, int); | ||
static void print_doctype(struct html *); | static void print_metaf(struct html *, enum mandoc_esc); | ||
static void print_xmltype(struct html *); | static void print_attr(struct html *, const char *, const char *); | ||
static int print_encode(struct html *, const char *, int); | |||
static void print_metaf(struct html *, enum roffdeco); | |||
static void print_attr(struct html *, | |||
const char *, const char *); | |||
static void *ml_alloc(char *, enum htmltype); | |||
static void * | void * | ||
ml_alloc(char *outopts, enum htmltype type) | html_alloc(const struct manoutput *outopts) | ||
{ | { | ||
struct html *h; | struct html *h; | ||
const char *toks[4]; | |||
char *v; | |||
toks[0] = "style"; | |||
toks[1] = "man"; | |||
toks[2] = "includes"; | |||
toks[3] = NULL; | |||
h = mandoc_calloc(1, sizeof(struct html)); | h = mandoc_calloc(1, sizeof(struct html)); | ||
h->type = type; | |||
h->tags.head = NULL; | h->tags.head = NULL; | ||
h->symtab = chars_init(CHARS_HTML); | h->style = outopts->style; | ||
h->base_man = outopts->man; | |||
h->base_includes = outopts->includes; | |||
if (outopts->fragment) | |||
h->oflags |= HTML_FRAGMENT; | |||
while (outopts && *outopts) | return h; | ||
switch (getsubopt(&outopts, UNCONST(toks), &v)) { | |||
case (0): | |||
h->style = v; | |||
break; | |||
case (1): | |||
h->base_man = v; | |||
break; | |||
case (2): | |||
h->base_includes = v; | |||
break; | |||
default: | |||
break; | |||
} | |||
return(h); | |||
} | } | ||
void * | |||
html_alloc(char *outopts) | |||
{ | |||
return(ml_alloc(outopts, HTML_HTML_4_01_STRICT)); | |||
} | |||
void * | |||
xhtml_alloc(char *outopts) | |||
{ | |||
return(ml_alloc(outopts, HTML_XHTML_1_0_STRICT)); | |||
} | |||
void | void | ||
html_free(void *p) | html_free(void *p) | ||
{ | { | ||
|
|
||
h = (struct html *)p; | h = (struct html *)p; | ||
while ((tag = h->tags.head) != NULL) { | while ((tag = h->tags.head) != NULL) { | ||
h->tags.head = tag->next; | h->tags.head = tag->next; | ||
free(tag); | free(tag); | ||
} | } | ||
if (h->symtab) | |||
chars_free(h->symtab); | |||
free(h); | free(h); | ||
} | } | ||
void | void | ||
print_gen_head(struct html *h) | print_gen_head(struct html *h) | ||
{ | { | ||
struct htmlpair tag[4]; | struct tag *t; | ||
tag[0].key = ATTR_HTTPEQUIV; | print_otag(h, TAG_META, "?", "charset", "utf-8"); | ||
tag[0].val = "Content-Type"; | |||
tag[1].key = ATTR_CONTENT; | |||
tag[1].val = "text/html; charset=utf-8"; | |||
print_otag(h, TAG_META, 2, tag); | |||
tag[0].key = ATTR_NAME; | /* | ||
tag[0].val = "resource-type"; | * Print a default style-sheet. | ||
tag[1].key = ATTR_CONTENT; | */ | ||
tag[1].val = "document"; | t = print_otag(h, TAG_STYLE, ""); | ||
print_otag(h, TAG_META, 2, tag); | 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) { | if (h->style) | ||
tag[0].key = ATTR_REL; | print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet", | ||
tag[0].val = "stylesheet"; | h->style, "type", "text/css", "media", "all"); | ||
tag[1].key = ATTR_HREF; | |||
tag[1].val = h->style; | |||
tag[2].key = ATTR_TYPE; | |||
tag[2].val = "text/css"; | |||
tag[3].key = ATTR_MEDIA; | |||
tag[3].val = "all"; | |||
print_otag(h, TAG_LINK, 4, tag); | |||
} | |||
} | } | ||
/* ARGSUSED */ | |||
static void | static void | ||
print_num(struct html *h, const char *p, size_t len) | print_metaf(struct html *h, enum mandoc_esc deco) | ||
{ | { | ||
const char *rhs; | |||
rhs = chars_num2char(p, len); | |||
if (rhs) | |||
putchar((int)*rhs); | |||
} | |||
static void | |||
print_spec(struct html *h, enum roffdeco d, const char *p, size_t len) | |||
{ | |||
int cp; | |||
const char *rhs; | |||
size_t sz; | |||
if ((cp = chars_spec2cp(h->symtab, p, len)) > 0) { | |||
printf("&#%d;", cp); | |||
return; | |||
} else if (-1 == cp && DECO_SSPECIAL == d) { | |||
fwrite(p, 1, len, stdout); | |||
return; | |||
} else if (-1 == cp) | |||
return; | |||
if (NULL != (rhs = chars_spec2str(h->symtab, p, len, &sz))) | |||
fwrite(rhs, 1, sz, stdout); | |||
} | |||
static void | |||
print_res(struct html *h, const char *p, size_t len) | |||
{ | |||
int cp; | |||
const char *rhs; | |||
size_t sz; | |||
if ((cp = chars_res2cp(h->symtab, p, len)) > 0) { | |||
printf("&#%d;", cp); | |||
return; | |||
} else if (-1 == cp) | |||
return; | |||
if (NULL != (rhs = chars_res2str(h->symtab, p, len, &sz))) | |||
fwrite(rhs, 1, sz, stdout); | |||
} | |||
static void | |||
print_metaf(struct html *h, enum roffdeco deco) | |||
{ | |||
enum htmlfont font; | enum htmlfont font; | ||
switch (deco) { | switch (deco) { | ||
case (DECO_PREVIOUS): | case ESCAPE_FONTPREV: | ||
font = h->metal; | font = h->metal; | ||
break; | break; | ||
case (DECO_ITALIC): | case ESCAPE_FONTITALIC: | ||
font = HTMLFONT_ITALIC; | font = HTMLFONT_ITALIC; | ||
break; | break; | ||
case (DECO_BOLD): | case ESCAPE_FONTBOLD: | ||
font = HTMLFONT_BOLD; | font = HTMLFONT_BOLD; | ||
break; | break; | ||
case (DECO_ROMAN): | case ESCAPE_FONTBI: | ||
font = HTMLFONT_BI; | |||
break; | |||
case ESCAPE_FONT: | |||
case ESCAPE_FONTROMAN: | |||
font = HTMLFONT_NONE; | font = HTMLFONT_NONE; | ||
break; | break; | ||
default: | default: | ||
abort(); | abort(); | ||
/* NOTREACHED */ | |||
} | } | ||
if (h->metaf) { | if (h->metaf) { | ||
|
|
||
h->metal = h->metac; | h->metal = h->metac; | ||
h->metac = font; | h->metac = font; | ||
if (HTMLFONT_NONE != font) | switch (font) { | ||
h->metaf = HTMLFONT_BOLD == font ? | case HTMLFONT_ITALIC: | ||
print_otag(h, TAG_B, 0, NULL) : | h->metaf = print_otag(h, TAG_I, ""); | ||
print_otag(h, TAG_I, 0, NULL); | break; | ||
case HTMLFONT_BOLD: | |||
h->metaf = print_otag(h, TAG_B, ""); | |||
break; | |||
case HTMLFONT_BI: | |||
h->metaf = print_otag(h, TAG_B, ""); | |||
print_otag(h, TAG_I, ""); | |||
break; | |||
default: | |||
break; | |||
} | |||
} | } | ||
int | |||
html_strlen(const char *cp) | |||
{ | |||
size_t rsz; | |||
int skip, sz; | |||
/* | |||
* Account for escaped sequences within string length | |||
* calculations. This follows the logic in term_strlen() as we | |||
* must calculate the width of produced strings. | |||
* Assume that characters are always width of "1". This is | |||
* hacky, but it gets the job done for approximation of widths. | |||
*/ | |||
sz = 0; | |||
skip = 0; | |||
while (1) { | |||
rsz = strcspn(cp, "\\"); | |||
if (rsz) { | |||
cp += rsz; | |||
if (skip) { | |||
skip = 0; | |||
rsz--; | |||
} | |||
sz += rsz; | |||
} | |||
if ('\0' == *cp) | |||
break; | |||
cp++; | |||
switch (mandoc_escape(&cp, NULL, NULL)) { | |||
case ESCAPE_ERROR: | |||
return sz; | |||
case ESCAPE_UNICODE: | |||
case ESCAPE_NUMBERED: | |||
case ESCAPE_SPECIAL: | |||
case ESCAPE_OVERSTRIKE: | |||
if (skip) | |||
skip = 0; | |||
else | |||
sz++; | |||
break; | |||
case ESCAPE_SKIPCHAR: | |||
skip = 1; | |||
break; | |||
default: | |||
break; | |||
} | |||
} | |||
return sz; | |||
} | |||
static int | static int | ||
print_escape(char c) | |||
{ | |||
switch (c) { | |||
case '<': | |||
printf("<"); | |||
break; | |||
case '>': | |||
printf(">"); | |||
break; | |||
case '&': | |||
printf("&"); | |||
break; | |||
case '"': | |||
printf("""); | |||
break; | |||
case ASCII_NBRSP: | |||
printf(" "); | |||
break; | |||
case ASCII_HYPH: | |||
putchar('-'); | |||
break; | |||
case ASCII_BREAK: | |||
break; | |||
default: | |||
return 0; | |||
} | |||
return 1; | |||
} | |||
static int | |||
print_encode(struct html *h, const char *p, int norecurse) | print_encode(struct html *h, const char *p, int norecurse) | ||
{ | { | ||
size_t sz; | size_t sz; | ||
int len, nospace; | int c, len, nospace; | ||
const char *seq; | const char *seq; | ||
enum roffdeco deco; | enum mandoc_esc esc; | ||
static const char rejs[6] = { '\\', '<', '>', '&', ASCII_HYPH, '\0' }; | static const char rejs[9] = { '\\', '<', '>', '&', '"', | ||
ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' }; | |||
nospace = 0; | nospace = 0; | ||
for (; *p; p++) { | while ('\0' != *p) { | ||
if (HTML_SKIPCHAR & h->flags && '\\' != *p) { | |||
h->flags &= ~HTML_SKIPCHAR; | |||
p++; | |||
continue; | |||
} | |||
sz = strcspn(p, rejs); | sz = strcspn(p, rejs); | ||
fwrite(p, 1, sz, stdout); | fwrite(p, 1, sz, stdout); | ||
p += /* LINTED */ | p += (int)sz; | ||
sz; | |||
if ('<' == *p) { | if ('\0' == *p) | ||
printf("<"); | break; | ||
if (print_escape(*p++)) | |||
continue; | continue; | ||
} else if ('>' == *p) { | |||
printf(">"); | esc = mandoc_escape(&p, &seq, &len); | ||
if (ESCAPE_ERROR == esc) | |||
break; | |||
switch (esc) { | |||
case ESCAPE_FONT: | |||
case ESCAPE_FONTPREV: | |||
case ESCAPE_FONTBOLD: | |||
case ESCAPE_FONTITALIC: | |||
case ESCAPE_FONTBI: | |||
case ESCAPE_FONTROMAN: | |||
if (0 == norecurse) | |||
print_metaf(h, esc); | |||
continue; | continue; | ||
} else if ('&' == *p) { | case ESCAPE_SKIPCHAR: | ||
printf("&"); | h->flags |= HTML_SKIPCHAR; | ||
continue; | continue; | ||
} else if (ASCII_HYPH == *p) { | default: | ||
/* | |||
* Note: "soft hyphens" aren't graphically | |||
* displayed when not breaking the text; we want | |||
* them to be displayed. | |||
*/ | |||
/*printf("­");*/ | |||
putchar('-'); | |||
continue; | |||
} else if ('\0' == *p) | |||
break; | break; | ||
} | |||
seq = ++p; | if (h->flags & HTML_SKIPCHAR) { | ||
len = a2roffdeco(&deco, &seq, &sz); | h->flags &= ~HTML_SKIPCHAR; | ||
continue; | |||
} | |||
switch (deco) { | switch (esc) { | ||
case (DECO_NUMBERED): | case ESCAPE_UNICODE: | ||
print_num(h, seq, sz); | /* Skip past "u" header. */ | ||
c = mchars_num2uc(seq + 1, len - 1); | |||
break; | break; | ||
case (DECO_RESERVED): | case ESCAPE_NUMBERED: | ||
print_res(h, seq, sz); | c = mchars_num2char(seq, len); | ||
if (c < 0) | |||
continue; | |||
break; | break; | ||
case (DECO_SSPECIAL): | case ESCAPE_SPECIAL: | ||
/* FALLTHROUGH */ | c = mchars_spec2cp(seq, len); | ||
case (DECO_SPECIAL): | if (c <= 0) | ||
print_spec(h, deco, seq, sz); | continue; | ||
break; | break; | ||
case (DECO_PREVIOUS): | case ESCAPE_NOSPACE: | ||
/* FALLTHROUGH */ | if ('\0' == *p) | ||
case (DECO_BOLD): | nospace = 1; | ||
/* FALLTHROUGH */ | continue; | ||
case (DECO_ITALIC): | case ESCAPE_OVERSTRIKE: | ||
/* FALLTHROUGH */ | if (len == 0) | ||
case (DECO_ROMAN): | continue; | ||
if (norecurse) | c = seq[len - 1]; | ||
break; | |||
print_metaf(h, deco); | |||
break; | break; | ||
default: | default: | ||
break; | continue; | ||
} | } | ||
if ((c < 0x20 && c != 0x09) || | |||
p += len - 1; | (c > 0x7E && c < 0xA0)) | ||
c = 0xFFFD; | |||
if (DECO_NOSPACE == deco && '\0' == *(p + 1)) | if (c > 0x7E) | ||
nospace = 1; | printf("&#%d;", c); | ||
else if ( ! print_escape(c)) | |||
putchar(c); | |||
} | } | ||
return(nospace); | return nospace; | ||
} | } | ||
static void | static void | ||
print_attr(struct html *h, const char *key, const char *val) | print_attr(struct html *h, const char *key, const char *val) | ||
{ | { | ||
|
|
||
putchar('\"'); | putchar('\"'); | ||
} | } | ||
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; | |||
struct tag *t; | struct tag *t; | ||
char *s; | |||
int i, have_style; | |||
/* Push this tags onto the stack of open scopes. */ | /* Push this tags onto the stack of open scopes. */ | ||
|
|
||
/* Print out the tag name and attributes. */ | /* Print out the tag name and attributes. */ | ||
printf("<%s", htmltags[tag].name); | printf("<%s", htmltags[tag].name); | ||
for (i = 0; i < sz; i++) | |||
print_attr(h, htmlattrs[p[i].key], p[i].val); | |||
/* Add non-overridable attributes. */ | va_start(ap, fmt); | ||
if (TAG_HTML == tag && HTML_XHTML_1_0_STRICT == h->type) { | have_style = 0; | ||
print_attr(h, "xmlns", "http://www.w3.org/1999/xhtml"); | while (*fmt != '\0') { | ||
print_attr(h, "xml:lang", "en"); | if (*fmt == 's') { | ||
print_attr(h, "lang", "en"); | printf(" style=\""); | ||
have_style = 1; | |||
fmt++; | |||
break; | |||
} | |||
s = va_arg(ap, char *); | |||
switch (*fmt++) { | |||
case 'c': | |||
print_attr(h, "class", s); | |||
break; | |||
case 'h': | |||
print_attr(h, "href", s); | |||
break; | |||
case 'i': | |||
print_attr(h, "id", s); | |||
break; | |||
case '?': | |||
print_attr(h, s, va_arg(ap, char *)); | |||
break; | |||
default: | |||
abort(); | |||
} | |||
} | } | ||
/* Accomodate for XML "well-formed" singleton escaping. */ | /* Print out styles. */ | ||
if (HTML_AUTOCLOSE & htmltags[tag].flags) | s = NULL; | ||
switch (h->type) { | su = &mysu; | ||
case (HTML_XHTML_1_0_STRICT): | while (*fmt != '\0') { | ||
putchar('/'); | |||
/* First letter: input argument type. */ | |||
switch (*fmt++) { | |||
case 'h': | |||
i = va_arg(ap, int); | |||
SCALE_HS_INIT(su, i); | |||
break; | break; | ||
case 's': | |||
s = va_arg(ap, char *); | |||
break; | |||
case 'u': | |||
su = va_arg(ap, struct roffsu *); | |||
break; | |||
case 'v': | |||
i = va_arg(ap, int); | |||
SCALE_VS_INIT(su, i); | |||
break; | |||
case 'w': | |||
s = va_arg(ap, char *); | |||
a2width(s, su); | |||
break; | |||
default: | default: | ||
abort(); | |||
} | |||
/* Second letter: style name. */ | |||
bufinit(h); | |||
switch (*fmt++) { | |||
case 'b': | |||
bufcat_su(h, "margin-bottom", su); | |||
break; | break; | ||
case 'h': | |||
bufcat_su(h, "height", su); | |||
break; | |||
case 'i': | |||
bufcat_su(h, "text-indent", su); | |||
break; | |||
case 'l': | |||
bufcat_su(h, "margin-left", su); | |||
break; | |||
case 't': | |||
bufcat_su(h, "margin-top", su); | |||
break; | |||
case 'w': | |||
bufcat_su(h, "width", su); | |||
break; | |||
case 'W': | |||
bufcat_su(h, "min-width", su); | |||
break; | |||
case '?': | |||
bufcat_style(h, s, va_arg(ap, char *)); | |||
break; | |||
default: | |||
abort(); | |||
} | } | ||
printf("%s", h->buf); | |||
} | |||
if (have_style) | |||
putchar('"'); | |||
va_end(ap); | |||
/* Accommodate for "well-formed" singleton escaping. */ | |||
if (HTML_AUTOCLOSE & htmltags[tag].flags) | |||
putchar('/'); | |||
putchar('>'); | putchar('>'); | ||
h->flags |= HTML_NOSPACE; | h->flags |= HTML_NOSPACE; | ||
|
|
||
if ((HTML_AUTOCLOSE | HTML_CLRLINE) & htmltags[tag].flags) | if ((HTML_AUTOCLOSE | HTML_CLRLINE) & htmltags[tag].flags) | ||
putchar('\n'); | putchar('\n'); | ||
return(t); | return t; | ||
} | } | ||
static void | static void | ||
print_ctag(struct html *h, enum htmltag tag) | print_ctag(struct html *h, struct tag *tag) | ||
{ | { | ||
printf("</%s>", htmltags[tag].name); | /* | ||
if (HTML_CLRLINE & htmltags[tag].flags) { | * Remember to close out and nullify the current | ||
* meta-font and table, if applicable. | |||
*/ | |||
if (tag == h->metaf) | |||
h->metaf = NULL; | |||
if (tag == h->tblt) | |||
h->tblt = NULL; | |||
printf("</%s>", htmltags[tag->tag].name); | |||
if (HTML_CLRLINE & htmltags[tag->tag].flags) { | |||
h->flags |= HTML_NOSPACE; | h->flags |= HTML_NOSPACE; | ||
putchar('\n'); | putchar('\n'); | ||
} | } | ||
h->tags.head = tag->next; | |||
free(tag); | |||
} | } | ||
void | void | ||
print_gen_decls(struct html *h) | print_gen_decls(struct html *h) | ||
{ | { | ||
print_xmltype(h); | puts("<!DOCTYPE html>"); | ||
print_doctype(h); | |||
} | } | ||
static void | |||
print_xmltype(struct html *h) | |||
{ | |||
if (HTML_XHTML_1_0_STRICT == h->type) | |||
puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); | |||
} | |||
static void | |||
print_doctype(struct html *h) | |||
{ | |||
const char *doctype; | |||
const char *dtd; | |||
const char *name; | |||
switch (h->type) { | |||
case (HTML_HTML_4_01_STRICT): | |||
name = "HTML"; | |||
doctype = "-//W3C//DTD HTML 4.01//EN"; | |||
dtd = "http://www.w3.org/TR/html4/strict.dtd"; | |||
break; | |||
default: | |||
name = "html"; | |||
doctype = "-//W3C//DTD XHTML 1.0 Strict//EN"; | |||
dtd = "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"; | |||
break; | |||
} | |||
printf("<!DOCTYPE %s PUBLIC \"%s\" \"%s\">\n", | |||
name, doctype, dtd); | |||
} | |||
void | void | ||
print_text(struct html *h, const char *word) | print_text(struct html *h, const char *word) | ||
{ | { | ||
|
|
||
} | } | ||
assert(NULL == h->metaf); | assert(NULL == h->metaf); | ||
if (HTMLFONT_NONE != h->metac) | switch (h->metac) { | ||
h->metaf = HTMLFONT_BOLD == h->metac ? | case HTMLFONT_ITALIC: | ||
print_otag(h, TAG_B, 0, NULL) : | h->metaf = print_otag(h, TAG_I, ""); | ||
print_otag(h, TAG_I, 0, NULL); | break; | ||
case HTMLFONT_BOLD: | |||
h->metaf = print_otag(h, TAG_B, ""); | |||
break; | |||
case HTMLFONT_BI: | |||
h->metaf = print_otag(h, TAG_B, ""); | |||
print_otag(h, TAG_I, ""); | |||
break; | |||
default: | |||
break; | |||
} | |||
assert(word); | assert(word); | ||
if ( ! print_encode(h, word, 0)) | if ( ! print_encode(h, word, 0)) { | ||
if ( ! (h->flags & HTML_NONOSPACE)) | if ( ! (h->flags & HTML_NONOSPACE)) | ||
h->flags &= ~HTML_NOSPACE; | h->flags &= ~HTML_NOSPACE; | ||
h->flags &= ~HTML_NONEWLINE; | |||
} else | |||
h->flags |= HTML_NOSPACE | HTML_NONEWLINE; | |||
if (h->metaf) { | if (h->metaf) { | ||
print_tagq(h, h->metaf); | print_tagq(h, h->metaf); | ||
|
|
||
h->flags &= ~HTML_IGNDELIM; | h->flags &= ~HTML_IGNDELIM; | ||
} | } | ||
void | void | ||
print_tagq(struct html *h, const struct tag *until) | print_tagq(struct html *h, const struct tag *until) | ||
{ | { | ||
struct tag *tag; | struct tag *tag; | ||
while ((tag = h->tags.head) != NULL) { | while ((tag = h->tags.head) != NULL) { | ||
/* | print_ctag(h, tag); | ||
* Remember to close out and nullify the current | |||
* meta-font and table, if applicable. | |||
*/ | |||
if (tag == h->metaf) | |||
h->metaf = NULL; | |||
if (tag == h->tblt) | |||
h->tblt = NULL; | |||
print_ctag(h, tag->tag); | |||
h->tags.head = tag->next; | |||
free(tag); | |||
if (until && tag == until) | if (until && tag == until) | ||
return; | return; | ||
} | } | ||
} | } | ||
void | void | ||
print_stagq(struct html *h, const struct tag *suntil) | print_stagq(struct html *h, const struct tag *suntil) | ||
{ | { | ||
|
|
||
while ((tag = h->tags.head) != NULL) { | while ((tag = h->tags.head) != NULL) { | ||
if (suntil && tag == suntil) | if (suntil && tag == suntil) | ||
return; | return; | ||
/* | print_ctag(h, tag); | ||
* Remember to close out and nullify the current | |||
* meta-font and table, if applicable. | |||
*/ | |||
if (tag == h->metaf) | |||
h->metaf = NULL; | |||
if (tag == h->tblt) | |||
h->tblt = NULL; | |||
print_ctag(h, tag->tag); | |||
h->tags.head = tag->next; | |||
free(tag); | |||
} | } | ||
} | } | ||
void | |||
print_paragraph(struct html *h) | |||
{ | |||
struct tag *t; | |||
t = print_otag(h, TAG_DIV, "c", "spacer"); | |||
print_tagq(h, t); | |||
} | |||
/* | |||
* 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) | |||
{ | |||
if (a2roffsu(p, su, SCALE_MAX) < 2) { | |||
su->unit = SCALE_EN; | |||
su->scale = html_strlen(p); | |||
} else if (su->scale < 0.0) | |||
su->scale = 0.0; | |||
} | |||
void | void | ||
bufinit(struct html *h) | bufinit(struct html *h) | ||
{ | { | ||
|
|
||
h->buflen = 0; | h->buflen = 0; | ||
} | } | ||
void | void | ||
bufcat_style(struct html *h, const char *key, const char *val) | bufcat_style(struct html *h, const char *key, const char *val) | ||
{ | { | ||
bufcat(h, key); | bufcat(h, key); | ||
bufncat(h, ":", 1); | bufcat(h, ":"); | ||
bufcat(h, val); | bufcat(h, val); | ||
bufncat(h, ";", 1); | bufcat(h, ";"); | ||
} | } | ||
void | void | ||
bufcat(struct html *h, const char *p) | bufcat(struct html *h, const char *p) | ||
{ | { | ||
bufncat(h, p, strlen(p)); | /* | ||
* XXX This is broken and not easy to fix. | |||
* When using the -Oincludes option, buffmt_includes() | |||
* may pass in strings overrunning BUFSIZ, causing a crash. | |||
*/ | |||
h->buflen = strlcat(h->buf, p, BUFSIZ); | |||
assert(h->buflen < BUFSIZ); | |||
} | } | ||
void | void | ||
buffmt(struct html *h, const char *fmt, ...) | bufcat_fmt(struct html *h, const char *fmt, ...) | ||
{ | { | ||
va_list ap; | va_list ap; | ||
va_start(ap, fmt); | va_start(ap, fmt); | ||
(void)vsnprintf(h->buf + (int)h->buflen, | (void)vsnprintf(h->buf + (int)h->buflen, | ||
BUFSIZ - h->buflen - 1, fmt, ap); | BUFSIZ - h->buflen - 1, fmt, ap); | ||
va_end(ap); | va_end(ap); | ||
h->buflen = strlen(h->buf); | h->buflen = strlen(h->buf); | ||
} | } | ||
static void | |||
void | |||
bufncat(struct html *h, const char *p, size_t sz) | bufncat(struct html *h, const char *p, size_t sz) | ||
{ | { | ||
if (h->buflen + sz > BUFSIZ - 1) | assert(h->buflen + sz + 1 < BUFSIZ); | ||
sz = BUFSIZ - 1 - h->buflen; | strncat(h->buf, p, sz); | ||
(void)strncat(h->buf, p, sz); | |||
h->buflen += sz; | h->buflen += sz; | ||
} | } | ||
void | void | ||
buffmt_includes(struct html *h, const char *name) | buffmt_includes(struct html *h, const char *name) | ||
{ | { | ||
const char *p, *pp; | const char *p, *pp; | ||
pp = h->base_includes; | pp = h->base_includes; | ||
bufinit(h); | |||
while (NULL != (p = strchr(pp, '%'))) { | while (NULL != (p = strchr(pp, '%'))) { | ||
bufncat(h, pp, (size_t)(p - pp)); | bufncat(h, pp, (size_t)(p - pp)); | ||
switch (*(p + 1)) { | switch (*(p + 1)) { | ||
case('I'): | case 'I': | ||
bufcat(h, name); | bufcat(h, name); | ||
break; | break; | ||
default: | default: | ||
|
|
||
bufcat(h, pp); | bufcat(h, pp); | ||
} | } | ||
void | void | ||
buffmt_man(struct html *h, | buffmt_man(struct html *h, const char *name, const char *sec) | ||
const char *name, const char *sec) | |||
{ | { | ||
const char *p, *pp; | const char *p, *pp; | ||
pp = h->base_man; | pp = h->base_man; | ||
/* LINTED */ | bufinit(h); | ||
while (NULL != (p = strchr(pp, '%'))) { | while (NULL != (p = strchr(pp, '%'))) { | ||
bufncat(h, pp, (size_t)(p - pp)); | bufncat(h, pp, (size_t)(p - pp)); | ||
switch (*(p + 1)) { | switch (*(p + 1)) { | ||
case('S'): | case 'S': | ||
bufcat(h, sec ? sec : "1"); | bufcat(h, sec ? sec : "1"); | ||
break; | break; | ||
case('N'): | case 'N': | ||
buffmt(h, name); | bufcat_fmt(h, "%s", name); | ||
break; | break; | ||
default: | default: | ||
bufncat(h, p, 2); | bufncat(h, p, 2); | ||
|
|
||
bufcat(h, pp); | bufcat(h, pp); | ||
} | } | ||
void | void | ||
bufcat_su(struct html *h, const char *p, const struct roffsu *su) | bufcat_su(struct html *h, const char *p, const struct roffsu *su) | ||
{ | { | ||
double v; | double v; | ||
const char *u; | |||
v = su->scale; | 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; | |||
switch (su->unit) { | bufcat_fmt(h, "%s: %.2f%s;", p, v, roffscales[su->unit]); | ||
case (SCALE_CM): | |||
u = "cm"; | |||
break; | |||
case (SCALE_IN): | |||
u = "in"; | |||
break; | |||
case (SCALE_PC): | |||
u = "pc"; | |||
break; | |||
case (SCALE_PT): | |||
u = "pt"; | |||
break; | |||
case (SCALE_EM): | |||
u = "em"; | |||
break; | |||
case (SCALE_MM): | |||
if (0 == (v /= 100)) | |||
v = 1; | |||
u = "em"; | |||
break; | |||
case (SCALE_EN): | |||
u = "ex"; | |||
break; | |||
case (SCALE_BU): | |||
u = "ex"; | |||
break; | |||
case (SCALE_VS): | |||
u = "em"; | |||
break; | |||
default: | |||
u = "ex"; | |||
break; | |||
} | |||
/* | |||
* XXX: the CSS spec isn't clear as to which types accept | |||
* integer or real numbers, so we just make them all decimals. | |||
*/ | |||
buffmt(h, "%s: %.2f%s;", p, v, u); | |||
} | } | ||
void | void | ||
html_idcat(char *dst, const char *src, int sz) | bufcat_id(struct html *h, const char *src) | ||
{ | { | ||
int ssz; | |||
assert(sz > 2); | /* Cf. <http://www.w3.org/TR/html5/dom.html#the-id-attribute>. */ | ||
/* Cf. <http://www.w3.org/TR/html4/types.html#h-6.2>. */ | for (; '\0' != *src; src++) | ||
bufncat(h, *src == ' ' ? "_" : src, 1); | |||
/* We can't start with a number (bah). */ | |||
if ('#' == *dst) { | |||
dst++; | |||
sz--; | |||
} | |||
if ('\0' == *dst) { | |||
*dst++ = 'x'; | |||
*dst = '\0'; | |||
sz--; | |||
} | |||
for ( ; *dst != '\0' && sz; dst++, sz--) | |||
/* Jump to end. */ ; | |||
for ( ; *src != '\0' && sz > 1; src++) { | |||
ssz = snprintf(dst, (size_t)sz, "%.2x", *src); | |||
sz -= ssz; | |||
dst += ssz; | |||
} | |||
} | } |