From 524f94c67874ba4a4b9074f3a2c08f209a62e107 Mon Sep 17 00:00:00 2001 From: kristapsdz Date: Wed, 22 Jan 2020 21:06:14 +0000 Subject: [PATCH] Overhaul how metadata is handled. Instead of extracting metadata during parse and needing to separately escape and smartypants it, have the renderer fill in a queue of metadata when it renders. This considerably simplifies areas of the code. The smartypants code now doesn't need separate considerations for buffers, as the metadata values are just normal text nodes. In doing so, the existing smartypants mechanism can be completely unhooked. While here, extensively simplify the renderers with regard to superfluous checks for whether buffers are NULL or empty (they're never NULL), left-over integer return values, and style(9). --- Makefile | 2 - buffer.c | 25 ++- diff.c | 18 +- document.c | 322 +++++++++++++-------------- extern.h | 2 + html.c | 590 +++++++++++++++++++++++++------------------------- html_escape.c | 6 + library.c | 66 ++---- lowdown.h | 44 ++-- main.c | 29 +-- nroff.c | 404 ++++++++++++++++++++-------------- smartypants.c | 126 +---------- term.c | 30 ++- tree.c | 31 +-- 14 files changed, 807 insertions(+), 888 deletions(-) diff --git a/Makefile b/Makefile index 44aadd0a..209e7637 100644 --- a/Makefile +++ b/Makefile @@ -10,13 +10,11 @@ OBJS = autolink.o \ entity.o \ html.o \ html_escape.o \ - html_smartypants.o \ library.o \ libdiff.o \ log.o \ nroff.o \ nroff_escape.o \ - nroff_smartypants.o \ smartypants.o \ term.o \ tree.o \ diff --git a/buffer.c b/buffer.c index db8e26e6..b90b5dc3 100644 --- a/buffer.c +++ b/buffer.c @@ -73,12 +73,22 @@ hbuf_truncate(hbuf *buf) buf->size = 0; } +int +hbuf_streq(const hbuf *buf1, const char *buf2) +{ + size_t sz; + + sz = strlen(buf2); + return buf1->size == sz && + memcmp(buf1->data, buf2, sz) == 0; +} + int hbuf_eq(const hbuf *buf1, const hbuf *buf2) { - return(buf1->size == buf2->size && - 0 == memcmp(buf1->data, buf2->data, buf1->size)); + return buf1->size == buf2->size && + memcmp(buf1->data, buf2->data, buf1->size) == 0; } /* @@ -140,6 +150,14 @@ hbuf_grow(hbuf *buf, size_t neosz) buf->asize = neoasz; } +void +hbuf_putb(hbuf *buf, const hbuf *b) +{ + + if (b != NULL) + hbuf_put(buf, b->data, b->size); +} + /* * Append raw data to a buffer. * May not be NULL. @@ -150,6 +168,9 @@ hbuf_put(hbuf *buf, const char *data, size_t size) { assert(buf && buf->unit); + if (data == NULL || size == 0) + return; + if (buf->size + size > buf->asize) hbuf_grow(buf, buf->size + size); diff --git a/diff.c b/diff.c index 98913fae..fd45770d 100644 --- a/diff.c +++ b/diff.c @@ -107,6 +107,7 @@ static const char *const names[LOWDOWN__MAX] = { "LOWDOWN_ENTITY", /* LOWDOWN_ENTITY */ "LOWDOWN_NORMAL_TEXT", /* LOWDOWN_NORMAL_TEXT */ "LOWDOWN_DOC_HEADER", /* LOWDOWN_DOC_HEADER */ + "LOWDOWN_META", /* LOWDOWN_META */ "LOWDOWN_DOC_FOOTER", /* LOWDOWN_DOC_FOOTER */ }; #endif @@ -601,20 +602,9 @@ node_clone(const struct lowdown_node *v, size_t id) n->id = id; switch (n->type) { - case LOWDOWN_DOC_HEADER: - n->rndr_doc_header.msz = - v->rndr_doc_header.msz; - if (0 == n->rndr_doc_header.msz) - break; - n->rndr_doc_header.m = xcalloc - (v->rndr_doc_header.msz, - sizeof(struct lowdown_meta)); - for (i = 0; i < n->rndr_doc_header.msz; i++) { - n->rndr_doc_header.m[i].key = xstrdup - (v->rndr_doc_header.m[i].key); - n->rndr_doc_header.m[i].value = xstrdup - (v->rndr_doc_header.m[i].value); - } + case LOWDOWN_META: + hbuf_clone(&v->rndr_meta.key, + &n->rndr_meta.key); break; case LOWDOWN_LIST: n->rndr_list.flags = v->rndr_list.flags; diff --git a/document.c b/document.c index c18b8e47..5b9f535b 100644 --- a/document.c +++ b/document.c @@ -3,7 +3,7 @@ * Copyright (c) 2008, Natacha Porté * Copyright (c) 2011, Vicent Martí * Copyright (c) 2014, Xavier Mendez, Devin Torres and the Hoedown authors - * Copyright (c) 2016--2017 Kristaps Dzonsons + * Copyright (c) 2016--2017, 2020 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -36,7 +36,9 @@ #define HOEDOWN_LI_END 8 /* internal list flag */ -/* Reference to a link. */ +/* + * Reference to a link. + */ struct link_ref { hbuf *name; /* identifier of link (or NULL) */ hbuf *link; /* link address */ @@ -44,10 +46,11 @@ struct link_ref { TAILQ_ENTRY(link_ref) entries; }; -/* Queue of links. */ TAILQ_HEAD(link_refq, link_ref); -/* Feference to a footnote. */ +/* + * Feference to a footnote. + */ struct footnote_ref { int is_used; /* whether has been referenced */ size_t num; /* if referenced, the order */ @@ -56,7 +59,6 @@ struct footnote_ref { TAILQ_ENTRY(footnote_ref) entries; }; -/* Queue of footnotes. */ TAILQ_HEAD(footnote_refq, footnote_ref); /* @@ -127,10 +129,9 @@ struct hdoc { unsigned int ext_flags; size_t cur_par; /* XXX: not used */ int in_link_body; - struct lowdown_meta *m; /* document meta-data */ - size_t msz; /* entries in "m" */ size_t nodes; /* number of nodes */ struct lowdown_node *current; + struct lowdown_metaq metaq; /* used for links */ }; /* Some forward declarations. */ @@ -1217,13 +1218,14 @@ char_link(hdoc *doc, char *data, size_t offset, size_t size) *linkp = NULL, *titlep = NULL; size_t i = 1, txt_e, link_b = 0, link_e = 0, title_b = 0, title_e = 0, nb_p, dims_b = 0, - dims_e = 0, j, sz; + dims_e = 0, sz; int ret = 0, in_title = 0, qtype = 0, is_img, is_footnote, is_metadata; - hbuf id, work; + hbuf id; struct link_ref *lr; struct footnote_ref *fr; struct lowdown_node *n; + struct lowdown_meta *m; is_img = offset && data[-1] == '!' && !is_escaped(data - offset, offset - 1); @@ -1304,18 +1306,15 @@ char_link(hdoc *doc, char *data, size_t offset, size_t size) id.data = data + 2; id.size = txt_e - 2; - for (j = 0; j < doc->msz; j++) { - sz = strlen(doc->m[j].key); - if (sz == id.size && - 0 == strncmp(doc->m[j].key, id.data, sz)) { - n = pushnode(doc, LOWDOWN_NORMAL_TEXT); - memset(&work, 0, sizeof(hbuf)); - work.data = doc->m[j].value; - work.size = strlen(doc->m[j].value); - pushbuffer(&n->rndr_normal_text.text, - work.data, work.size); - popnode(doc, n); - } + TAILQ_FOREACH(m, &doc->metaq, entries) { + sz = strlen(m->key); + if (sz != id.size || + strncmp(m->key, id.data, sz)) + continue; + n = pushnode(doc, LOWDOWN_NORMAL_TEXT); + pushbuffer(&n->rndr_normal_text.text, + m->value, strlen(m->value)); + popnode(doc, n); } ret = 1; @@ -2728,9 +2727,9 @@ parse_table_row(hbuf *ob, hdoc *doc, char *data, size_t size, size_t columns, enum htbl_flags *col_data, enum htbl_flags header_flag) { - size_t i = 0, col, len, cell_start, cell_end; - hbuf empty_cell; - struct lowdown_node *n, *nn; + size_t i = 0, col, len, cell_start, cell_end; + hbuf empty_cell; + struct lowdown_node *n, *nn; if (i < size && data[i] == '|') i++; @@ -2793,9 +2792,10 @@ parse_table_header(struct lowdown_node **np, size_t size, size_t *columns, enum htbl_flags **column_data) { - size_t i = 0, col, header_end, under_end, dashes; - ssize_t pipes = 0; - struct lowdown_node *n; + size_t i = 0, col, header_end, under_end, + dashes; + ssize_t pipes = 0; + struct lowdown_node *n; while (i < size && data[i] != '\n') if (data[i++] == '|') @@ -2886,11 +2886,10 @@ parse_table_header(struct lowdown_node **np, static size_t parse_table(hdoc *doc, char *data, size_t size) { - size_t i, columns, row_start, pipes; - hbuf *header_work = NULL, - *body_work = NULL; - enum htbl_flags *col_data = NULL; - struct lowdown_node *n = NULL, *nn; + size_t i, columns, row_start, pipes; + hbuf *header_work = NULL, *body_work = NULL; + enum htbl_flags *col_data = NULL; + struct lowdown_node *n = NULL, *nn; header_work = hbuf_new(64); body_work = hbuf_new(256); @@ -2939,10 +2938,10 @@ parse_table(hdoc *doc, char *data, size_t size) static void parse_block(hdoc *doc, char *data, size_t size) { - size_t beg = 0, end, i; - char *txt_data; - char oli_data[10]; - struct lowdown_node *n; + size_t beg = 0, end, i; + char *txt_data; + char oli_data[10]; + struct lowdown_node *n; /* * What kind of block are we? @@ -3049,10 +3048,11 @@ static int is_footnote(hdoc *doc, const char *data, size_t beg, size_t end, size_t *last) { - size_t i = 0, ind = 0, start = 0, id_offset, id_end; - hbuf *contents = NULL; - int in_empty = 0; - struct footnote_ref *ref; + size_t i = 0, ind = 0, start = 0, + id_offset, id_end; + hbuf *contents = NULL; + int in_empty = 0; + struct footnote_ref *ref; /* up to 3 optional leading spaces */ @@ -3168,9 +3168,9 @@ static int is_ref(struct hdoc *doc, const char *data, size_t beg, size_t end, size_t *last) { - size_t i, id_offset, id_end, link_offset, - link_end, title_offset, title_end, line_end; - struct link_ref *ref; + size_t i, id_offset, id_end, link_offset, + link_end, title_offset, title_end, line_end; + struct link_ref *ref; /* Up to 3 optional leading spaces. */ @@ -3345,27 +3345,20 @@ expand_tabs(hbuf *ob, const char *line, size_t size) } } -/* - * Allocate a new document processor instance. - */ hdoc * lowdown_doc_new(const struct lowdown_opts *opts) { - hdoc *doc = NULL; - unsigned int extensions; - - extensions = opts ? opts->feat : 0; + hdoc *doc; + unsigned int extensions = opts ? opts->feat : 0; doc = xcalloc(1, sizeof(hdoc)); - doc->opts = opts; doc->active_char['*'] = MD_CHAR_EMPHASIS; doc->active_char['_'] = MD_CHAR_EMPHASIS; if (extensions & LOWDOWN_STRIKE) doc->active_char['~'] = MD_CHAR_EMPHASIS; if (extensions & LOWDOWN_HILITE) doc->active_char['='] = MD_CHAR_EMPHASIS; - doc->active_char['`'] = MD_CHAR_CODESPAN; doc->active_char['\n'] = MD_CHAR_LINEBREAK; doc->active_char['['] = MD_CHAR_LINK; @@ -3373,19 +3366,17 @@ lowdown_doc_new(const struct lowdown_opts *opts) doc->active_char['<'] = MD_CHAR_LANGLE; doc->active_char['\\'] = MD_CHAR_ESCAPE; doc->active_char['&'] = MD_CHAR_ENTITY; - if (extensions & LOWDOWN_AUTOLINK) { doc->active_char[':'] = MD_CHAR_AUTOLINK_URL; doc->active_char['@'] = MD_CHAR_AUTOLINK_EMAIL; doc->active_char['w'] = MD_CHAR_AUTOLINK_WWW; } - if (extensions & LOWDOWN_SUPER) doc->active_char['^'] = MD_CHAR_SUPERSCRIPT; - if (extensions & LOWDOWN_MATH) doc->active_char['$'] = MD_CHAR_MATH; + doc->opts = opts; doc->ext_flags = extensions; return doc; @@ -3490,13 +3481,14 @@ parse_metadata_val(const char *data, size_t sz, size_t *len) static int parse_metadata(hdoc *doc, const char *data, size_t sz) { - size_t i, len, pos = 0, valsz; - const char *key, *val; - struct lowdown_meta *m; - char *cp; - - if (0 == sz || '\n' != data[sz - 1]) - return(0); + size_t i, j, pos = 0, valsz, keysz; + const char *key, *val; + struct lowdown_meta *m; + struct lowdown_node *n, *nn; + char *cp; + + if (sz == 0 || data[sz - 1] != '\n') + return 0; /* * Check the first line for a colon to see if we should do @@ -3506,66 +3498,80 @@ parse_metadata(hdoc *doc, const char *data, size_t sz) */ for (pos = 0; pos < sz; pos++) - if ('\n' == data[pos] || ':' == data[pos]) + if (data[pos] == '\n' || data[pos] == ':') break; - if (pos == sz || '\n' == data[pos]) - return(0); + if (pos == sz || data[pos] == '\n') + return 0; + + /* + * Also put the metadata into the document's metaq because we + * might set variables. + */ for (pos = 0; pos < sz; ) { + m = xcalloc(1, sizeof(struct lowdown_meta)); + TAILQ_INSERT_TAIL(&doc->metaq, m, entries); + + n = pushnode(doc, LOWDOWN_META); key = &data[pos]; for (i = pos; i < sz; i++) - if (':' == data[i]) + if (data[i] == ':') break; + keysz = i - pos; - doc->m = xreallocarray - (doc->m, doc->msz + 1, - sizeof(struct lowdown_meta)); - m = &doc->m[doc->msz++]; - memset(m, 0, sizeof(struct lowdown_meta)); + /* + * Start by normalising the key to have only lowercase + * alphanumerics, -, and _. + * The whitespace we discard; other characters we + * replace with a question mark. + */ + + n->rndr_meta.key.data = cp = malloc(keysz); + for (j = 0; j < keysz; j++) { + if (isalnum((unsigned char)key[j]) || + '-' == key[j] || '_' == key[j]) { + *cp++ = tolower((unsigned char)key[j]); + continue; + } else if (isspace((unsigned char)key[j])) + continue; + lmsg(doc->opts, + LOWDOWN_ERR_METADATA_BAD_CHAR, NULL); + *cp++ = '?'; + } + n->rndr_meta.key.size = cp - n->rndr_meta.key.data; + m->key = xstrndup + (n->rndr_meta.key.data, + n->rndr_meta.key.size); - m->key = xstrndup(key, i - pos); if (i == sz) { - m->value = xstrndup(key, 0); + popnode(doc, n); + m->value = xstrdup(""); break; } - /* Pass colon, space, value, then to next token. */ + /* Parse the value, creating a node if nonempty. */ + assert(data[i] == ':'); i++; while (i < sz && isspace((unsigned char)data[i])) i++; + if (i == sz) { + popnode(doc, n); + m->value = xstrdup(""); + break; + } + val = parse_metadata_val(&data[i], sz - i, &valsz); + nn = pushnode(doc, LOWDOWN_NORMAL_TEXT); + pushbuffer(&nn->rndr_normal_text.text, val, valsz); m->value = xstrndup(val, valsz); + popnode(doc, nn); + popnode(doc, n); pos = i + valsz + 1; } - /* - * Convert metadata keys into normalised form: lowercase - * alphanumerics, hyphen, underscore, with spaces stripped. - */ - - for (i = 0; i < doc->msz; i++) { - cp = doc->m[i].key; - while ('\0' != *cp) { - if (isalnum((int)*cp) || - '-' == *cp || '_' == *cp) { - *cp = tolower((int)*cp); - cp++; - continue; - } else if (isspace((int)*cp)) { - len = strlen(cp + 1) + 1; - memmove(cp, cp + 1, len); - continue; - } - lmsg(doc->opts, - LOWDOWN_ERR_METADATA_BAD_CHAR, - NULL); - *cp++ = '?'; - } - } - - return(1); + return 1; } /* @@ -3575,19 +3581,16 @@ parse_metadata(hdoc *doc, const char *data, size_t sz) * (Obviously only applicable if LOWDOWN_METADATA has been set.) */ struct lowdown_node * -lowdown_doc_parse(hdoc *doc, size_t *nsz, const char *data, - size_t size, struct lowdown_meta **mp, size_t *mszp) +lowdown_doc_parse(hdoc *doc, size_t *nsz, const char *data, size_t size) { - static const char UTF8_BOM[] = {0xEF, 0xBB, 0xBF}; - hbuf *text; - size_t beg, end, i; - int footnotes_enabled; - const char *sv; - struct lowdown_node *n, *root; + static const char UTF8_BOM[] = {0xEF, 0xBB, 0xBF}; + hbuf *text; + size_t beg, end; + int footnotes_enabled; + const char *sv; + struct lowdown_node *n, *root; doc->current = NULL; - doc->m = NULL; - doc->msz = 0; doc->in_link_body = 0; text = hbuf_new(64); @@ -3602,6 +3605,7 @@ lowdown_doc_parse(hdoc *doc, size_t *nsz, const char *data, /* Reset the references table. */ + TAILQ_INIT(&doc->metaq); TAILQ_INIT(&doc->refq); TAILQ_INIT(&doc->footnotes); @@ -3623,8 +3627,10 @@ lowdown_doc_parse(hdoc *doc, size_t *nsz, const char *data, * (Only parse if we must.) */ + n = pushnode(doc, LOWDOWN_DOC_HEADER); + if (LOWDOWN_METADATA & doc->ext_flags && - beg < size - 1 && isalnum((int)data[beg])) { + beg < size - 1 && isalnum((unsigned char)data[beg])) { sv = &data[beg]; for (end = beg + 1; end < size; end++) { if ('\n' == data[end] && @@ -3666,25 +3672,8 @@ lowdown_doc_parse(hdoc *doc, size_t *nsz, const char *data, beg = end; } - /* Pre-grow the output buffer to minimize allocations. */ - - /*hbuf_grow(ob, text->size + (text->size >> 1));*/ - /* Second pass: actual rendering. */ - n = pushnode(doc, LOWDOWN_DOC_HEADER); - n->rndr_doc_header.msz = doc->msz; - if (n->rndr_doc_header.msz) { - n->rndr_doc_header.m = - xcalloc(doc->msz, - sizeof(struct lowdown_meta)); - for (i = 0; i < doc->msz; i++) { - n->rndr_doc_header.m[i].key = - xstrdup(doc->m[i].key); - n->rndr_doc_header.m[i].value = - xstrdup(doc->m[i].value); - } - } popnode(doc, n); if (text->size) { @@ -3699,7 +3688,6 @@ lowdown_doc_parse(hdoc *doc, size_t *nsz, const char *data, if (footnotes_enabled) parse_footnote_list(doc); - n = pushnode(doc, LOWDOWN_DOC_FOOTER); popnode(doc, n); @@ -3707,97 +3695,70 @@ lowdown_doc_parse(hdoc *doc, size_t *nsz, const char *data, hbuf_free(text); free_link_refs(&doc->refq); - if (footnotes_enabled) - free_footnote_refs(&doc->footnotes); - - /* - * Copy our metadata to the given pointers. - * If we do this, they'll be freed by the caller. - * Do this only if both pointers are provided. - * Otherwise, free the pointers---we won't be needing them any - * more. - */ - - if (NULL != mp && NULL != mszp) { - *mp = doc->m; - *mszp = doc->msz; - } else { - for (i = 0; i < doc->msz; i++) { - free(doc->m[i].key); - free(doc->m[i].value); - } - free(doc->m); - } - - doc->m = NULL; - doc->msz = 0; + free_footnote_refs(&doc->footnotes); + lowdown_metaq_free(&doc->metaq); *nsz = doc->nodes; popnode(doc, root); - return(root); + return root; } void lowdown_node_free(struct lowdown_node *root) { struct lowdown_node *n; - size_t i; - if (NULL == root) + if (root == NULL) return; switch (root->type) { - case (LOWDOWN_DOC_HEADER): - for (i = 0; i < root->rndr_doc_header.msz; i++) { - free(root->rndr_doc_header.m[i].key); - free(root->rndr_doc_header.m[i].value); - } - free(root->rndr_doc_header.m); + case LOWDOWN_META: + hbuf_free(&root->rndr_meta.key); break; - case (LOWDOWN_NORMAL_TEXT): + case LOWDOWN_NORMAL_TEXT: hbuf_free(&root->rndr_normal_text.text); break; - case (LOWDOWN_CODESPAN): + case LOWDOWN_CODESPAN: hbuf_free(&root->rndr_codespan.text); break; - case (LOWDOWN_ENTITY): + case LOWDOWN_ENTITY: hbuf_free(&root->rndr_entity.text); break; - case (LOWDOWN_LINK_AUTO): + case LOWDOWN_LINK_AUTO: hbuf_free(&root->rndr_autolink.text); hbuf_free(&root->rndr_autolink.link); break; - case (LOWDOWN_RAW_HTML): + case LOWDOWN_RAW_HTML: hbuf_free(&root->rndr_raw_html.text); break; - case (LOWDOWN_LINK): + case LOWDOWN_LINK: hbuf_free(&root->rndr_link.link); hbuf_free(&root->rndr_link.title); break; - case (LOWDOWN_BLOCKCODE): + case LOWDOWN_BLOCKCODE: hbuf_free(&root->rndr_blockcode.text); hbuf_free(&root->rndr_blockcode.lang); break; - case (LOWDOWN_BLOCKHTML): + case LOWDOWN_BLOCKHTML: hbuf_free(&root->rndr_blockhtml.text); break; - case (LOWDOWN_TABLE_HEADER): + case LOWDOWN_TABLE_HEADER: free(root->rndr_table_header.flags); break; - case (LOWDOWN_IMAGE): + case LOWDOWN_IMAGE: hbuf_free(&root->rndr_image.link); hbuf_free(&root->rndr_image.title); hbuf_free(&root->rndr_image.dims); hbuf_free(&root->rndr_image.alt); break; - case (LOWDOWN_MATH_BLOCK): + case LOWDOWN_MATH_BLOCK: hbuf_free(&root->rndr_math.text); break; default: break; } - while (NULL != (n = TAILQ_FIRST(&root->children))) { + while ((n = TAILQ_FIRST(&root->children)) != NULL) { TAILQ_REMOVE(&root->children, n, entries); lowdown_node_free(n); } @@ -3805,6 +3766,19 @@ lowdown_node_free(struct lowdown_node *root) free(root); } +void +lowdown_metaq_free(struct lowdown_metaq *q) +{ + struct lowdown_meta *m; + + while ((m = TAILQ_FIRST(q)) != NULL) { + TAILQ_REMOVE(q, m, entries); + free(m->key); + free(m->value); + free(m); + } +} + void lowdown_doc_free(hdoc *doc) { diff --git a/extern.h b/extern.h index b9c69ab9..f9d24ea4 100644 --- a/extern.h +++ b/extern.h @@ -53,6 +53,7 @@ void smarty(struct lowdown_node *, size_t, enum lowdown_type); int32_t entity_find(const hbuf *); int hbuf_eq(const hbuf *, const hbuf *); +int hbuf_streq(const hbuf *, const char *); void hbuf_free(hbuf *); void hbuf_grow(hbuf *, size_t); hbuf *hbuf_clone(const hbuf *, hbuf *); @@ -61,6 +62,7 @@ int hbuf_prefix(const hbuf *, const char *); void hbuf_printf(hbuf *, const char *, ...) __attribute__((format (printf, 2, 3))); void hbuf_put(hbuf *, const char *, size_t); +void hbuf_putb(hbuf *, const hbuf *); void hbuf_putc(hbuf *, char); int hbuf_putf(hbuf *, FILE *); void hbuf_puts(hbuf *, const char *); diff --git a/html.c b/html.c index f25a235b..ca8ab513 100644 --- a/html.c +++ b/html.c @@ -3,7 +3,7 @@ * Copyright (c) 2008, Natacha Porté * Copyright (c) 2011, Vicent Martí * Copyright (c) 2014, Xavier Mendez, Devin Torres and the Hoedown authors - * Copyright (c) 2016--2017 Kristaps Dzonsons + * Copyright (c) 2016--2017, 2020 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -52,6 +52,29 @@ struct hstate { unsigned int flags; /* output flags */ }; +enum rndr_meta_key { + RNDR_META_AFFIL, + RNDR_META_AUTHOR, + RNDR_META_CSS, + RNDR_META_DATE, + RNDR_META_RCSAUTHOR, + RNDR_META_RCSDATE, + RNDR_META_SCRIPT, + RNDR_META_TITLE, + RNDR_META__MAX +}; + +static const char *rndr_meta_keys[RNDR_META__MAX] = { + "affiliation", /* RNDR_META_AFFIL */ + "author", /* RNDR_META_AUTHOR */ + "css", /* RNDR_META_CSS */ + "date", /* RNDR_META_DATE */ + "rcsauthor", /* RNDR_META_RCSAUTHOR */ + "rcsdate", /* RNDR_META_RCSDATE */ + "javascript", /* RNDR_META_SCRIPT */ + "title", /* RNDR_META_TITLE */ +}; + static void escape_html(hbuf *ob, const char *source, size_t length) { @@ -66,12 +89,12 @@ escape_href(hbuf *ob, const char *source, size_t length) hesc_href(ob, source, length); } -static int +static void rndr_autolink(hbuf *ob, const hbuf *link, enum halink_type type) { - if (!link || !link->size) - return 0; + if (link->size == 0) + return; HBUF_PUTSL(ob, "data + 7, link->size - 7); - } else { + else escape_html(ob, link->data, link->size); - } HBUF_PUTSL(ob, ""); - return 1; } static void @@ -100,17 +122,14 @@ rndr_blockcode(hbuf *ob, const hbuf *text, const hbuf *lang) if (ob->size) hbuf_putc(ob, '\n'); - if (lang && lang->size) { + if (lang->size) { HBUF_PUTSL(ob, "
data, lang->size);
 		HBUF_PUTSL(ob, "\">");
-	} else {
+	} else
 		HBUF_PUTSL(ob, "
");
-	}
-
-	if (text)
-		escape_html(ob, text->data, text->size);
 
+	escape_html(ob, text->data, text->size);
 	HBUF_PUTSL(ob, "
\n"); } @@ -120,77 +139,60 @@ rndr_blockquote(hbuf *ob, const hbuf *content) if (ob->size) hbuf_putc(ob, '\n'); HBUF_PUTSL(ob, "
\n"); - if (content) - hbuf_put(ob, content->data, content->size); + hbuf_put(ob, content->data, content->size); HBUF_PUTSL(ob, "
\n"); } -static int +static void rndr_codespan(hbuf *ob, const hbuf *text) { HBUF_PUTSL(ob, ""); - if (text) - escape_html(ob, text->data, text->size); + escape_html(ob, text->data, text->size); HBUF_PUTSL(ob, ""); - return 1; } -static int +static void rndr_strikethrough(hbuf *ob, const hbuf *content) { - if (!content || !content->size) - return 0; HBUF_PUTSL(ob, ""); - hbuf_put(ob, content->data, content->size); + hbuf_putb(ob, content); HBUF_PUTSL(ob, ""); - return 1; } -static int +static void rndr_double_emphasis(hbuf *ob, const hbuf *content) { - if (!content || !content->size) - return 0; HBUF_PUTSL(ob, ""); - hbuf_put(ob, content->data, content->size); + hbuf_putb(ob, content); HBUF_PUTSL(ob, ""); - - return 1; } -static int +static void rndr_emphasis(hbuf *ob, const hbuf *content) { - if (!content || !content->size) - return 0; + HBUF_PUTSL(ob, ""); - if (content) - hbuf_put(ob, content->data, content->size); + hbuf_putb(ob, content); HBUF_PUTSL(ob, ""); - return 1; } -static int +static void rndr_highlight(hbuf *ob, const hbuf *content) { - if (!content || !content->size) - return 0; HBUF_PUTSL(ob, ""); - hbuf_put(ob, content->data, content->size); + hbuf_putb(ob, content); HBUF_PUTSL(ob, ""); - return 1; } -static int +static void rndr_linebreak(hbuf *ob) { - hbuf_puts(ob, "
\n"); - return 1; + HBUF_PUTSL(ob, "
\n"); } /* @@ -212,7 +214,7 @@ rndr_header_id(hbuf *ob, const hbuf *header, struct hstate *state) if (strlen(hentry->str) != header->size) continue; if (strncmp(hentry->str, - (const char *)header->data, header->size) == 0) + header->data, header->size) == 0) break; } @@ -237,8 +239,7 @@ rndr_header_id(hbuf *ob, const hbuf *header, struct hstate *state) hentry = xcalloc(1, sizeof(struct hentry)); hentry->count = 1; - hentry->str = xstrndup - ((const char *)header->data, header->size); + hentry->str = xstrndup(header->data, header->size); TAILQ_INSERT_TAIL(&state->headers_used, hentry, entries); } @@ -250,40 +251,30 @@ rndr_header(hbuf *ob, const hbuf *content, if (ob->size) hbuf_putc(ob, '\n'); - if (content != NULL && content->size && - LOWDOWN_HTML_HEAD_IDS & state->flags) { + if (content->size && (state->flags & LOWDOWN_HTML_HEAD_IDS)) { hbuf_printf(ob, ""); } else hbuf_printf(ob, "", level); - if (content != NULL) - hbuf_put(ob, content->data, content->size); - + hbuf_putb(ob, content); hbuf_printf(ob, "\n", level); } -static int +static void rndr_link(hbuf *ob, const hbuf *content, const hbuf *link, const hbuf *title) { HBUF_PUTSL(ob, "size) - escape_href(ob, link->data, link->size); - - if (title && title->size) { + escape_href(ob, link->data, link->size); + if (title->size) { HBUF_PUTSL(ob, "\" title=\""); escape_html(ob, title->data, title->size); } - HBUF_PUTSL(ob, "\">"); - - if (content && content->size) - hbuf_put(ob, content->data, content->size); + hbuf_putb(ob, content); HBUF_PUTSL(ob, ""); - return 1; } static void @@ -292,16 +283,17 @@ rndr_list(hbuf *ob, const hbuf *content, const struct rndr_list *p) if (ob->size) hbuf_putc(ob, '\n'); - if (p->flags & HLIST_FL_ORDERED) { + if ((p->flags & HLIST_FL_ORDERED)) { if (p->start[0] != '\0') hbuf_printf(ob, "
    \n", p->start); else HBUF_PUTSL(ob, "
      \n"); } else HBUF_PUTSL(ob, "
        \n"); - if (content) - hbuf_put(ob, content->data, content->size); - if (p->flags & HLIST_FL_ORDERED) + + hbuf_putb(ob, content); + + if ((p->flags & HLIST_FL_ORDERED)) HBUF_PUTSL(ob, "
    \n"); else HBUF_PUTSL(ob, "\n"); @@ -314,6 +306,12 @@ rndr_listitem(hbuf *ob, const hbuf *content, size_t size; HBUF_PUTSL(ob, "
  1. "); + + /* + * Cut off any trailing space. + * XXX: this should be removed. + */ + if (content) { size = content->size; while (size && content->data[size - 1] == '\n') @@ -331,10 +329,11 @@ rndr_paragraph(hbuf *ob, const hbuf *content, struct hstate *state) if (ob->size) hbuf_putc(ob, '\n'); - if (!content || !content->size) + if (content->size == 0) return; - while (i < content->size && isspace(content->data[i])) + while (i < content->size && + isspace((unsigned char)content->data[i])) i++; if (i == content->size) @@ -374,11 +373,8 @@ rndr_raw_block(hbuf *ob, const hbuf *text, const struct hstate *state) { size_t org, sz; - if (text == NULL) - return; - - if (state->flags & LOWDOWN_HTML_SKIP_HTML || - state->flags & LOWDOWN_HTML_ESCAPE) { + if ((state->flags & LOWDOWN_HTML_SKIP_HTML) || + (state->flags & LOWDOWN_HTML_ESCAPE)) { escape_html(ob, text->data, text->size); return; } @@ -391,7 +387,6 @@ rndr_raw_block(hbuf *ob, const hbuf *text, const struct hstate *state) sz = text->size; while (sz > 0 && text->data[sz - 1] == '\n') sz--; - org = 0; while (org < sz && text->data[org] == '\n') org++; @@ -406,17 +401,13 @@ rndr_raw_block(hbuf *ob, const hbuf *text, const struct hstate *state) hbuf_putc(ob, '\n'); } -static int +static void rndr_triple_emphasis(hbuf *ob, const hbuf *content) { - if (!content || !content->size) - return 0; - HBUF_PUTSL(ob, ""); - hbuf_put(ob, content->data, content->size); + hbuf_putb(ob, content); HBUF_PUTSL(ob, ""); - return 1; } static void @@ -428,7 +419,7 @@ rndr_hrule(hbuf *ob) hbuf_puts(ob, "
    \n"); } -static int +static void rndr_image(hbuf *ob, const hbuf *link, const hbuf *title, const hbuf *dims, const hbuf *alt) { @@ -441,38 +432,36 @@ rndr_image(hbuf *ob, const hbuf *link, const hbuf *title, * that as a cap to the size. */ - if (dims != NULL && dims->size && - dims->size < sizeof(dimbuf) - 1) { + if (dims->size && dims->size < sizeof(dimbuf) - 1) { memset(dimbuf, 0, sizeof(dimbuf)); memcpy(dimbuf, dims->data, dims->size); rc = sscanf(dimbuf, "%ux%u", &x, &y); } + /* Require an "alt", even if blank. */ + HBUF_PUTSL(ob, "data, link->size); + escape_href(ob, link->data, link->size); HBUF_PUTSL(ob, "\" alt=\""); - if (alt != NULL && alt->size) - escape_html(ob, alt->data, alt->size); + escape_html(ob, alt->data, alt->size); HBUF_PUTSL(ob, "\""); - if (dims != NULL && rc > 0) { + if (dims->size && rc > 0) { hbuf_printf(ob, " width=\"%u\"", x); if (rc > 1) hbuf_printf(ob, " height=\"%u\"", y); } - if (title && title->size) { + if (title->size) { HBUF_PUTSL(ob, " title=\""); escape_html(ob, title->data, title->size); HBUF_PUTSL(ob, "\""); } hbuf_puts(ob, " />"); - return 1; } -static int +static void rndr_raw_html(hbuf *ob, const hbuf *text, const struct hstate *state) { @@ -482,16 +471,15 @@ rndr_raw_html(hbuf *ob, const hbuf *text, const struct hstate *state) * escapes all of them. */ - if ((state->flags & LOWDOWN_HTML_ESCAPE) != 0) { + if ((state->flags & LOWDOWN_HTML_ESCAPE)) { escape_html(ob, text->data, text->size); - return 1; + return; } - if ((state->flags & LOWDOWN_HTML_SKIP_HTML) != 0) - return 1; + if ((state->flags & LOWDOWN_HTML_SKIP_HTML)) + return; - hbuf_put(ob, text->data, text->size); - return 1; + hbuf_putb(ob, text); } static void @@ -501,7 +489,7 @@ rndr_table(hbuf *ob, const hbuf *content) if (ob->size) hbuf_putc(ob, '\n'); HBUF_PUTSL(ob, "\n"); - hbuf_put(ob, content->data, content->size); + hbuf_putb(ob, content); HBUF_PUTSL(ob, "
    \n"); } @@ -513,7 +501,7 @@ rndr_table_header(hbuf *ob, const hbuf *content, if (ob->size) hbuf_putc(ob, '\n'); HBUF_PUTSL(ob, "\n"); - hbuf_put(ob, content->data, content->size); + hbuf_putb(ob, content); HBUF_PUTSL(ob, "\n"); } @@ -524,7 +512,7 @@ rndr_table_body(hbuf *ob, const hbuf *content) if (ob->size) hbuf_putc(ob, '\n'); HBUF_PUTSL(ob, "\n"); - hbuf_put(ob, content->data, content->size); + hbuf_putb(ob, content); HBUF_PUTSL(ob, "\n"); } @@ -533,8 +521,7 @@ rndr_tablerow(hbuf *ob, const hbuf *content) { HBUF_PUTSL(ob, "\n"); - if (content) - hbuf_put(ob, content->data, content->size); + hbuf_putb(ob, content); HBUF_PUTSL(ob, "\n"); } @@ -543,7 +530,7 @@ rndr_tablecell(hbuf *ob, const hbuf *content, enum htbl_flags flags, size_t col, size_t columns) { - if (flags & HTBL_FL_HEADER) + if ((flags & HTBL_FL_HEADER)) HBUF_PUTSL(ob, ""); } - if (content) - hbuf_put(ob, content->data, content->size); + hbuf_putb(ob, content); - if (flags & HTBL_FL_HEADER) + if ((flags & HTBL_FL_HEADER)) HBUF_PUTSL(ob, "\n"); else HBUF_PUTSL(ob, "\n"); } -static int +static void rndr_superscript(hbuf *ob, const hbuf *content) { - if (!content || !content->size) - return 0; - HBUF_PUTSL(ob, ""); - hbuf_put(ob, content->data, content->size); + hbuf_putb(ob, content); HBUF_PUTSL(ob, ""); - return 1; } static void @@ -601,10 +583,7 @@ rndr_footnotes(hbuf *ob, const hbuf *content) HBUF_PUTSL(ob, "
    \n"); hbuf_puts(ob, "
    \n"); HBUF_PUTSL(ob, "
      \n"); - - if (content) - hbuf_put(ob, content->data, content->size); - + hbuf_putb(ob, content); HBUF_PUTSL(ob, "\n
    \n
    \n"); } @@ -616,21 +595,19 @@ rndr_footnote_def(hbuf *ob, const hbuf *content, unsigned int num) /* Insert anchor at the end of first paragraph block. */ - if (content) { - while ((i+3) < content->size) { - if (content->data[i++] != '<') - continue; - if (content->data[i++] != '/') - continue; - if (content->data[i++] != 'p' && - content->data[i] != 'P') - continue; - if (content->data[i] != '>') - continue; - i -= 3; - pfound = 1; - break; - } + while ((i+3) < content->size) { + if (content->data[i++] != '<') + continue; + if (content->data[i++] != '/') + continue; + if (content->data[i++] != 'p' && + content->data[i] != 'P') + continue; + if (content->data[i] != '>') + continue; + i -= 3; + pfound = 1; + break; } hbuf_printf(ob, "\n
  2. \n", num); @@ -641,14 +618,13 @@ rndr_footnote_def(hbuf *ob, const hbuf *content, unsigned int num) "" "↩", num); hbuf_put(ob, content->data + i, content->size - i); - } else if (content) { - hbuf_put(ob, content->data, content->size); - } + } else + hbuf_putb(ob, content); HBUF_PUTSL(ob, "
  3. \n"); } -static int +static void rndr_footnote_ref(hbuf *ob, unsigned int num) { @@ -656,10 +632,9 @@ rndr_footnote_ref(hbuf *ob, unsigned int num) "" "" "%d", num, num, num); - return 1; } -static int +static void rndr_math(hbuf *ob, const struct rndr_math *n) { @@ -674,288 +649,309 @@ rndr_math(hbuf *ob, const struct rndr_math *n) HBUF_PUTSL(ob, "\\]"); else HBUF_PUTSL(ob, "\\)"); - return 1; +} + +static void +rndr_doc_footer(hbuf *ob, const struct hstate *st) +{ + + if ((st->flags & LOWDOWN_STANDALONE)) + HBUF_PUTSL(ob, "\n"); +} + +static void +rndr_root(hbuf *ob, const hbuf *content, const struct hstate *st) +{ + + if ((st->flags & LOWDOWN_STANDALONE)) + HBUF_PUTSL(ob, + "\n" + "\n"); + hbuf_putb(ob, content); + if ((st->flags & LOWDOWN_STANDALONE)) + HBUF_PUTSL(ob, "\n"); } /* - * Itereate through multiple multi-white-space separated values in - * "val", filling them in between "start" and "end". - * If "href", escape the value as an HTML attribute. - * Otherwise, just do the minimal HTML escaping. + * Split "val" into multiple strings delimited by two or more whitespace + * characters, padding the output with "starttag" and "endtag". */ static void -rndr_doc_header_multi(hbuf *ob, int href, - const char *val, const char *starttag, const char *endtag) +rndr_doc_header_multi(hbuf *ob, const hbuf *b, + const char *starttag, const char *endtag) { - const char *cp, *start; - size_t sz; + const char *start; + size_t sz, i; - for (cp = val; '\0' != *cp; ) { - while (isspace((unsigned char)*cp)) - cp++; - if ('\0' == *cp) + for (i = 0; i < b->size; i++) { + while (i < b->size && + isspace((unsigned char)b->data[i])) + i++; + if (i == b->size) continue; - start = cp; + start = &b->data[i]; sz = 0; - while ('\0' != *cp) { - if (!isspace((unsigned char)cp[0]) || - !isspace((unsigned char)cp[1])) { - sz++; - cp++; - continue; - } - cp += 2; - break; - } - if (sz == 0) + + for (; i < b->size; i++) + if (i < b->size - 1 && + isspace((unsigned char)b->data[i]) && + isspace((unsigned char)b->data[i + 1])) + break; + + if ((sz = &b->data[i] - start) == 0) continue; + hbuf_puts(ob, starttag); - hbuf_putc(ob, '"'); - if (href) - hesc_href(ob, start, sz); - else - hesc_html(ob, start, sz, 0); - hbuf_putc(ob, '"'); + HBUF_PUTSL(ob, "\""); + hbuf_put(ob, start, sz); + HBUF_PUTSL(ob, "\""); hbuf_puts(ob, endtag); - hbuf_putc(ob, '\n'); + HBUF_PUTSL(ob, "\n"); } } static void -rndr_doc_footer(hbuf *ob, const struct hstate *st) -{ +rndr_meta(hbuf *ob, const hbuf *tmp, struct lowdown_metaq *mq, + const struct lowdown_node *n, const struct hstate *st) +{ + enum rndr_meta_key key; + struct lowdown_meta *m; + + if (mq != NULL) { + m = xcalloc(1, sizeof(struct lowdown_meta)); + TAILQ_INSERT_TAIL(mq, m, entries); + m->key = xstrndup + (n->rndr_meta.key.data, + n->rndr_meta.key.size); + m->value = xstrndup(tmp->data, tmp->size); + } + + if (!(st->flags & LOWDOWN_STANDALONE)) + return; + + for (key = 0; key < RNDR_META__MAX; key++) + if (hbuf_streq(&n->rndr_meta.key, rndr_meta_keys[key])) + break; + + /* TODO: rcsauthor, rcsdate. */ - if (LOWDOWN_STANDALONE & st->flags) - HBUF_PUTSL(ob, "\n\n"); + switch (key) { + case RNDR_META_AFFIL: + rndr_doc_header_multi(ob, tmp, + ""); + break; + case RNDR_META_AUTHOR: + rndr_doc_header_multi(ob, tmp, + ""); + break; + case RNDR_META_CSS: + rndr_doc_header_multi(ob, tmp, + ""); + break; + case RNDR_META_DATE: + hbuf_printf(ob, "\n"); + break; + case RNDR_META_SCRIPT: + rndr_doc_header_multi(ob, tmp, + ""); + break; + case RNDR_META_TITLE: + HBUF_PUTSL(ob, ""); + hbuf_putb(ob, tmp); + HBUF_PUTSL(ob, "\n"); + break; + default: + break; + }; } static void -rndr_doc_header(hbuf *ob, - const struct lowdown_meta *m, size_t msz, - const struct hstate *st) +rndr_doc_header(hbuf *ob, const hbuf *content, + const struct hstate *st, struct lowdown_node *n) { - const char *author = NULL, *title = "Untitled article", - *css = NULL, *affil = NULL, *script = NULL, - *date = NULL; - size_t i; - struct tm *tm; - time_t t; - char buf[32]; + struct lowdown_node *nn; if (!(LOWDOWN_STANDALONE & st->flags)) return; - /* - * Acquire metadata that we'll fill in. - * We format this as well. - */ - - for (i = 0; i < msz; i++) - if (0 == strcmp(m[i].key, "title")) - title = m[i].value; - else if (0 == strcmp(m[i].key, "affiliation")) - affil = m[i].value; - else if (0 == strcmp(m[i].key, "author")) - author = m[i].value; - else if (0 == strcmp(m[i].key, "rcsauthor")) - author = rcsauthor2str(m[i].value); - else if (0 == strcmp(m[i].key, "css")) - css = m[i].value; - else if (0 == strcmp(m[i].key, "javascript")) - script = m[i].value; - else if (0 == strcmp(m[i].key, "rcsdate")) - date = rcsdate2str(m[i].value); - else if (0 == strcmp(m[i].key, "date")) - date = date2str(m[i].value); - - /* FIXME: convert to buf without strftime. */ - - if (NULL == date) { - t = time(NULL); - tm = localtime(&t); - strftime(buf, sizeof(buf), "%Y-%m-%d", tm); - date = buf; - } - HBUF_PUTSL(ob, - "\n" - "\n" "\n" "\n" - "\n"); - - hbuf_printf(ob, "\n", date); - if (NULL != author) - rndr_doc_header_multi(ob, 0, author, - ""); - if (NULL != affil) - rndr_doc_header_multi(ob, 0, affil, - ""); - if (NULL != script) - rndr_doc_header_multi(ob, 1, script, - ""); - if (NULL != css) - rndr_doc_header_multi(ob, 1, css, - ""); - - /* HTML-escape and trim the title (0-length ok but weird). */ - - while (isspace((int)*title)) - title++; - - HBUF_PUTSL(ob, ""); - hesc_html(ob, title, strlen(title), 0); - HBUF_PUTSL(ob, - "\n" - " \n" - "\n"); + "\n"); + + hbuf_putb(ob, content); + + /* If we don't have a title, print out a default one. */ + + TAILQ_FOREACH(nn, &n->children, entries) { + if (nn->type != LOWDOWN_META) + continue; + if (hbuf_streq(&nn->rndr_meta.key, "title")) + break; + } + if (nn == NULL) + hbuf_puts(ob, "Untitled Article"); + + HBUF_PUTSL(ob, " \n\n"); } void -lowdown_html_rndr(hbuf *ob, void *ref, struct lowdown_node *root) +lowdown_html_rndr(hbuf *ob, struct lowdown_metaq *metaq, + void *ref, struct lowdown_node *root) { struct lowdown_node *n; hbuf *tmp; int32_t ent; + struct hstate *st = ref; tmp = hbuf_new(64); TAILQ_FOREACH(n, &root->children, entries) - lowdown_html_rndr(tmp, ref, n); + lowdown_html_rndr(tmp, metaq, st, n); /* * These elements can be put in either a block or an inline * context, so we're safe to just use them and forget. */ - if (LOWDOWN_CHNG_INSERT == root->chng) + if (root->chng == LOWDOWN_CHNG_INSERT) HBUF_PUTSL(ob, ""); - else if (LOWDOWN_CHNG_DELETE == root->chng) + if (root->chng == LOWDOWN_CHNG_DELETE) HBUF_PUTSL(ob, ""); switch (root->type) { - case (LOWDOWN_BLOCKCODE): + case LOWDOWN_ROOT: + rndr_root(ob, tmp, st); + break; + case LOWDOWN_BLOCKCODE: rndr_blockcode(ob, &root->rndr_blockcode.text, &root->rndr_blockcode.lang); break; - case (LOWDOWN_BLOCKQUOTE): + case LOWDOWN_BLOCKQUOTE: rndr_blockquote(ob, tmp); break; - case (LOWDOWN_DOC_HEADER): - rndr_doc_header(ob, - root->rndr_doc_header.m, - root->rndr_doc_header.msz, ref); + case LOWDOWN_DOC_HEADER: + rndr_doc_header(ob, tmp, st, root); break; - case (LOWDOWN_DOC_FOOTER): - rndr_doc_footer(ob, ref); + case LOWDOWN_META: + rndr_meta(ob, tmp, metaq, root, st); break; - case (LOWDOWN_HEADER): + case LOWDOWN_DOC_FOOTER: + rndr_doc_footer(ob, st); + break; + case LOWDOWN_HEADER: rndr_header(ob, tmp, - root->rndr_header.level, ref); + root->rndr_header.level, st); break; - case (LOWDOWN_HRULE): + case LOWDOWN_HRULE: rndr_hrule(ob); break; - case (LOWDOWN_LIST): + case LOWDOWN_LIST: rndr_list(ob, tmp, &root->rndr_list); break; - case (LOWDOWN_LISTITEM): + case LOWDOWN_LISTITEM: rndr_listitem(ob, tmp, root->rndr_listitem.flags, root->rndr_listitem.num); break; - case (LOWDOWN_PARAGRAPH): - rndr_paragraph(ob, tmp, ref); + case LOWDOWN_PARAGRAPH: + rndr_paragraph(ob, tmp, st); break; - case (LOWDOWN_TABLE_BLOCK): + case LOWDOWN_TABLE_BLOCK: rndr_table(ob, tmp); break; - case (LOWDOWN_TABLE_HEADER): + case LOWDOWN_TABLE_HEADER: rndr_table_header(ob, tmp, root->rndr_table_header.flags, root->rndr_table_header.columns); break; - case (LOWDOWN_TABLE_BODY): + case LOWDOWN_TABLE_BODY: rndr_table_body(ob, tmp); break; - case (LOWDOWN_TABLE_ROW): + case LOWDOWN_TABLE_ROW: rndr_tablerow(ob, tmp); break; - case (LOWDOWN_TABLE_CELL): + case LOWDOWN_TABLE_CELL: rndr_tablecell(ob, tmp, root->rndr_table_cell.flags, root->rndr_table_cell.col, root->rndr_table_cell.columns); break; - case (LOWDOWN_FOOTNOTES_BLOCK): + case LOWDOWN_FOOTNOTES_BLOCK: rndr_footnotes(ob, tmp); break; - case (LOWDOWN_FOOTNOTE_DEF): + case LOWDOWN_FOOTNOTE_DEF: rndr_footnote_def(ob, tmp, root->rndr_footnote_def.num); break; - case (LOWDOWN_BLOCKHTML): + case LOWDOWN_BLOCKHTML: rndr_raw_block(ob, - &root->rndr_blockhtml.text, ref); + &root->rndr_blockhtml.text, st); break; - case (LOWDOWN_LINK_AUTO): + case LOWDOWN_LINK_AUTO: rndr_autolink(ob, &root->rndr_autolink.link, root->rndr_autolink.type); break; - case (LOWDOWN_CODESPAN): + case LOWDOWN_CODESPAN: rndr_codespan(ob, &root->rndr_codespan.text); break; - case (LOWDOWN_DOUBLE_EMPHASIS): + case LOWDOWN_DOUBLE_EMPHASIS: rndr_double_emphasis(ob, tmp); break; - case (LOWDOWN_EMPHASIS): + case LOWDOWN_EMPHASIS: rndr_emphasis(ob, tmp); break; - case (LOWDOWN_HIGHLIGHT): + case LOWDOWN_HIGHLIGHT: rndr_highlight(ob, tmp); break; - case (LOWDOWN_IMAGE): + case LOWDOWN_IMAGE: rndr_image(ob, &root->rndr_image.link, &root->rndr_image.title, &root->rndr_image.dims, &root->rndr_image.alt); break; - case (LOWDOWN_LINEBREAK): + case LOWDOWN_LINEBREAK: rndr_linebreak(ob); break; - case (LOWDOWN_LINK): + case LOWDOWN_LINK: rndr_link(ob, tmp, &root->rndr_link.link, &root->rndr_link.title); break; - case (LOWDOWN_TRIPLE_EMPHASIS): + case LOWDOWN_TRIPLE_EMPHASIS: rndr_triple_emphasis(ob, tmp); break; - case (LOWDOWN_STRIKETHROUGH): + case LOWDOWN_STRIKETHROUGH: rndr_strikethrough(ob, tmp); break; - case (LOWDOWN_SUPERSCRIPT): + case LOWDOWN_SUPERSCRIPT: rndr_superscript(ob, tmp); break; - case (LOWDOWN_FOOTNOTE_REF): + case LOWDOWN_FOOTNOTE_REF: rndr_footnote_ref(ob, root->rndr_footnote_ref.num); break; - case (LOWDOWN_MATH_BLOCK): + case LOWDOWN_MATH_BLOCK: rndr_math(ob, &root->rndr_math); break; - case (LOWDOWN_RAW_HTML): - rndr_raw_html(ob, &root->rndr_raw_html.text, ref); + case LOWDOWN_RAW_HTML: + rndr_raw_html(ob, &root->rndr_raw_html.text, st); break; - case (LOWDOWN_NORMAL_TEXT): + case LOWDOWN_NORMAL_TEXT: rndr_normal_text(ob, &root->rndr_normal_text.text); break; - case (LOWDOWN_ENTITY): + case LOWDOWN_ENTITY: /* * Prefer numeric entities. * This is because we're emitting XML (XHTML5) and it's @@ -975,15 +971,14 @@ lowdown_html_rndr(hbuf *ob, void *ref, struct lowdown_node *root) break; } - if (LOWDOWN_CHNG_INSERT == root->chng) + if (root->chng == LOWDOWN_CHNG_INSERT) HBUF_PUTSL(ob, ""); - else if (LOWDOWN_CHNG_DELETE == root->chng) + if (root->chng == LOWDOWN_CHNG_DELETE) HBUF_PUTSL(ob, ""); hbuf_free(tmp); } -/* allocates a regular HTML renderer */ void * lowdown_html_new(const struct lowdown_opts *opts) { @@ -997,9 +992,6 @@ lowdown_html_new(const struct lowdown_opts *opts) return state; } -/* - * Deallocate an HTML renderer. - */ void lowdown_html_free(void *renderer) { diff --git a/html_escape.c b/html_escape.c index d77c0dd1..f49e8a8e 100644 --- a/html_escape.c +++ b/html_escape.c @@ -122,6 +122,9 @@ hesc_href(hbuf *ob, const char *data, size_t size) size_t i = 0, mark; char hex_str[3]; + if (size == 0) + return; + hex_str[0] = '%'; while (i < size) { @@ -197,6 +200,9 @@ hesc_html(hbuf *ob, const char *data, size_t size, int secure) { size_t i = 0, mark; + if (size == 0) + return; + while (1) { mark = i; while (i < size && diff --git a/library.c b/library.c index 9558c47a..beaf3977 100644 --- a/library.c +++ b/library.c @@ -53,19 +53,16 @@ lowdown_errstr(enum lowdown_err err) return errs[err]; } -/* - * Documented in lowdown_buf(3). - */ void lowdown_buf(const struct lowdown_opts *opts, const char *data, size_t datasz, char **res, size_t *rsz, - struct lowdown_meta **m, size_t *msz) + struct lowdown_metaq *metaq) { - hbuf *ob, *spb; + hbuf *ob; void *renderer = NULL; hdoc *document; - size_t i, maxn; + size_t maxn; enum lowdown_type t; struct lowdown_node *n; @@ -93,28 +90,10 @@ lowdown_buf(const struct lowdown_opts *opts, /* Parse the output and free resources. */ - n = lowdown_doc_parse(document, &maxn, data, datasz, m, msz); + n = lowdown_doc_parse(document, &maxn, data, datasz); assert(n == NULL || n->type == LOWDOWN_ROOT); lowdown_doc_free(document); - /* Escape all of our metadata values. */ - - if (t != LOWDOWN_TREE && t != LOWDOWN_TERM) { - spb = hbuf_new(HBUF_START_SMALL); - for (i = 0; i < *msz; i++) { - hbuf_truncate(spb); - if (t == LOWDOWN_HTML) - hesc_html(spb, (*m)[i].value, - strlen((*m)[i].value), 0); - else - hesc_nroff(spb, (*m)[i].value, - strlen((*m)[i].value), 0, 1); - free((*m)[i].value); - (*m)[i].value = xstrndup(spb->data, spb->size); - } - hbuf_free(spb); - } - /* Conditionally apply smartypants. */ if (opts != NULL && @@ -125,20 +104,20 @@ lowdown_buf(const struct lowdown_opts *opts, switch (t) { case LOWDOWN_HTML: - lowdown_html_rndr(ob, renderer, n); + lowdown_html_rndr(ob, metaq, renderer, n); lowdown_html_free(renderer); break; case LOWDOWN_MAN: case LOWDOWN_NROFF: - lowdown_nroff_rndr(ob, renderer, n); + lowdown_nroff_rndr(ob, metaq, renderer, n); lowdown_nroff_free(renderer); break; case LOWDOWN_TERM: - lowdown_term_rndr(ob, renderer, n); + lowdown_term_rndr(ob, metaq, renderer, n); lowdown_term_free(renderer); break; case LOWDOWN_TREE: - lowdown_tree_rndr(ob, renderer, n); + lowdown_tree_rndr(ob, metaq, renderer, n); lowdown_tree_free(renderer); break; } @@ -192,7 +171,8 @@ lowdown_buf_diff(const struct lowdown_opts *optnew, const char *new, size_t newsz, const struct lowdown_opts *optold, const char *old, size_t oldsz, - char **res, size_t *rsz) + char **res, size_t *rsz, + struct lowdown_metaq *metaq) { hbuf *ob; void *renderer = NULL; @@ -228,11 +208,11 @@ lowdown_buf_diff(const struct lowdown_opts *optnew, /* Parse the output and free resources. */ doc = lowdown_doc_new(optnew); - nnew = lowdown_doc_parse(doc, &maxnew, new, newsz, NULL, NULL); + nnew = lowdown_doc_parse(doc, &maxnew, new, newsz); lowdown_doc_free(doc); doc = lowdown_doc_new(optold); - nold = lowdown_doc_parse(doc, &maxold, old, oldsz, NULL, NULL); + nold = lowdown_doc_parse(doc, &maxold, old, oldsz); lowdown_doc_free(doc); /* Merge adjacent text nodes. */ @@ -255,20 +235,20 @@ lowdown_buf_diff(const struct lowdown_opts *optnew, switch (t) { case LOWDOWN_HTML: - lowdown_html_rndr(ob, renderer, ndiff); + lowdown_html_rndr(ob, metaq, renderer, ndiff); lowdown_html_free(renderer); break; case LOWDOWN_MAN: case LOWDOWN_NROFF: - lowdown_nroff_rndr(ob, renderer, ndiff); + lowdown_nroff_rndr(ob, metaq, renderer, ndiff); lowdown_nroff_free(renderer); break; case LOWDOWN_TERM: - lowdown_term_rndr(ob, renderer, ndiff); + lowdown_term_rndr(ob, metaq, renderer, ndiff); lowdown_term_free(renderer); break; case LOWDOWN_TREE: - lowdown_tree_rndr(ob, renderer, ndiff); + lowdown_tree_rndr(ob, metaq, renderer, ndiff); lowdown_tree_free(renderer); break; } @@ -277,13 +257,9 @@ lowdown_buf_diff(const struct lowdown_opts *optnew, hbuf_free(ob); } -/* - * Documented in lowdown_file(3). - */ int -lowdown_file(const struct lowdown_opts *opts, - FILE *fin, char **res, size_t *rsz, - struct lowdown_meta **m, size_t *msz) +lowdown_file(const struct lowdown_opts *opts, FILE *fin, + char **res, size_t *rsz, struct lowdown_metaq *metaq) { hbuf *ib; @@ -294,7 +270,7 @@ lowdown_file(const struct lowdown_opts *opts, return 0; } - lowdown_buf(opts, ib->data, ib->size, res, rsz, m, msz); + lowdown_buf(opts, ib->data, ib->size, res, rsz, metaq); hbuf_free(ib); return 1; } @@ -302,7 +278,7 @@ lowdown_file(const struct lowdown_opts *opts, int lowdown_file_diff(const struct lowdown_opts *optnew, FILE *fnew, const struct lowdown_opts *optold, FILE *fold, - char **res, size_t *rsz) + char **res, size_t *rsz, struct lowdown_metaq *metaq) { hbuf *src, *dst; @@ -316,7 +292,7 @@ lowdown_file_diff(const struct lowdown_opts *optnew, FILE *fnew, } lowdown_buf_diff(optnew, src->data, src->size, - optold, dst->data, dst->size, res, rsz); + optold, dst->data, dst->size, res, rsz, metaq); hbuf_free(src); hbuf_free(dst); return 1; diff --git a/lowdown.h b/lowdown.h index f931ca1e..1e3b8f60 100644 --- a/lowdown.h +++ b/lowdown.h @@ -95,6 +95,7 @@ enum lowdown_rndrt { LOWDOWN_ENTITY, LOWDOWN_NORMAL_TEXT, LOWDOWN_DOC_HEADER, + LOWDOWN_META, LOWDOWN_DOC_FOOTER, LOWDOWN__MAX }; @@ -107,8 +108,6 @@ typedef struct hbuf { int buffer_free; /* obj should be freed */ } hbuf; -/* - */ TAILQ_HEAD(lowdown_nodeq, lowdown_node); enum htbl_flags { @@ -137,8 +136,11 @@ enum hlist_fl { struct lowdown_meta { char *key; char *value; + TAILQ_ENTRY(lowdown_meta) entries; }; +TAILQ_HEAD(lowdown_metaq, lowdown_meta); + enum lowdown_chng { LOWDOWN_CHNG_NONE = 0, LOWDOWN_CHNG_INSERT, @@ -154,10 +156,9 @@ struct lowdown_node { enum lowdown_chng chng; /* change type */ size_t id; /* unique identifier */ union { - struct rndr_doc_header { - struct lowdown_meta *m; /* unescaped */ - size_t msz; - } rndr_doc_header; + struct rndr_meta { + hbuf key; + } rndr_meta; struct rndr_list { enum hlist_fl flags; /* only HLIST_FL_ORDERED */ /* @@ -285,19 +286,17 @@ const char *lowdown_errstr(enum lowdown_err); */ void lowdown_buf(const struct lowdown_opts *, const char *, size_t, - char **, size_t *, - struct lowdown_meta **, size_t *); + char **, size_t *, struct lowdown_metaq *); void lowdown_buf_diff(const struct lowdown_opts *, const char *, size_t, const struct lowdown_opts *, const char *, size_t, - char **, size_t *); + char **, size_t *, struct lowdown_metaq *); int lowdown_file(const struct lowdown_opts *, - FILE *, char **, size_t *, - struct lowdown_meta **, size_t *); + FILE *, char **, size_t *, struct lowdown_metaq *); int lowdown_file_diff(const struct lowdown_opts *, FILE *, const struct lowdown_opts *, FILE *, - char **, size_t *); + char **, size_t *, struct lowdown_metaq *); /* * Low-level functions. @@ -307,35 +306,34 @@ int lowdown_file_diff(const struct lowdown_opts *, FILE *, hdoc *lowdown_doc_new(const struct lowdown_opts *); struct lowdown_node - *lowdown_doc_parse(hdoc *, size_t *, const char *, - size_t, struct lowdown_meta **, size_t *); + *lowdown_doc_parse(hdoc *, size_t *, const char *, size_t); struct lowdown_node *lowdown_diff(const struct lowdown_node *, const struct lowdown_node *, size_t *); void lowdown_doc_free(hdoc *); +void lowdown_metaq_free(struct lowdown_metaq *); void lowdown_node_free(struct lowdown_node *); void lowdown_html_free(void *); void *lowdown_html_new(const struct lowdown_opts *); -void lowdown_html_rndr(hbuf *, void *, struct lowdown_node *); +void lowdown_html_rndr(hbuf *, struct lowdown_metaq *, + void *, struct lowdown_node *); void lowdown_term_free(void *); void *lowdown_term_new(void); -void lowdown_term_rndr(hbuf *, void *, struct lowdown_node *); +void lowdown_term_rndr(hbuf *, struct lowdown_metaq *, + void *, struct lowdown_node *); void lowdown_nroff_free(void *); void *lowdown_nroff_new(const struct lowdown_opts *); -void lowdown_nroff_rndr(hbuf *, void *, struct lowdown_node *); +void lowdown_nroff_rndr(hbuf *, struct lowdown_metaq *, + void *, struct lowdown_node *); void lowdown_tree_free(void *); void *lowdown_tree_new(void); -void lowdown_tree_rndr(hbuf *, void *, struct lowdown_node *); - -/* XXX: will be deprecated. */ - -void lowdown_html_smrt(hbuf *, const char *, size_t); -void lowdown_nroff_smrt(hbuf *, const char *, size_t); +void lowdown_tree_rndr(hbuf *, struct lowdown_metaq *, + void *, struct lowdown_node *); __END_DECLS diff --git a/main.c b/main.c index 4eaaaaf2..0ef5918c 100644 --- a/main.c +++ b/main.c @@ -233,8 +233,9 @@ main(int argc, char *argv[]) diff = 0; char *ret = NULL; int feat; - size_t i, retsz = 0, msz = 0; - struct lowdown_meta *m = NULL; + size_t retsz = 0; + const struct lowdown_meta *m = NULL; + struct lowdown_metaq mq; memset(&opts, 0, sizeof(struct lowdown_opts)); @@ -361,24 +362,27 @@ main(int argc, char *argv[]) if (standalone) opts.oflags |= LOWDOWN_STANDALONE; + TAILQ_INIT(&mq); + if (diff) { dopts = opts; opts.arg = (void *)fnin; dopts.arg = (void *)fndin; - if (!lowdown_file_diff(&opts, fin, &dopts, din, &ret, &retsz)) + if (!lowdown_file_diff + (&opts, fin, &dopts, din, &ret, &retsz, &mq)) err(EXIT_FAILURE, "%s", fnin); } else { opts.arg = (void *)fnin; - if (!lowdown_file(&opts, fin, &ret, &retsz, &m, &msz)) + if (!lowdown_file(&opts, fin, &ret, &retsz, &mq)) err(EXIT_FAILURE, "%s", fnin); } if (extract != NULL) { - for (i = 0; i < msz; i++) - if (strcasecmp(m[i].key, extract) == 0) + TAILQ_FOREACH(m, &mq, entries) + if (strcasecmp(m->key, extract) == 0) break; - if (i < msz) { - fprintf(fout, "%s\n", m[i].value); + if (m != NULL) { + fprintf(fout, "%s\n", m->value); } else { status = EXIT_FAILURE; warnx("%s: unknown keyword", extract); @@ -389,15 +393,12 @@ main(int argc, char *argv[]) free(ret); if (fout != stdout) fclose(fout); - if (NULL != din) + if (din != NULL) fclose(din); if (fin != stdin) fclose(fin); - for (i = 0; i < msz; i++) { - free(m[i].key); - free(m[i].value); - } - free(m); + + lowdown_metaq_free(&mq); return status; usage: fprintf(stderr, "usage: lowdown " diff --git a/nroff.c b/nroff.c index d3338f64..6376145a 100644 --- a/nroff.c +++ b/nroff.c @@ -3,7 +3,7 @@ * Copyright (c) 2008, Natacha Porté * Copyright (c) 2011, Vicent Martí * Copyright (c) 2014, Xavier Mendez, Devin Torres and the Hoedown authors - * Copyright (c) 2016--2017 Kristaps Dzonsons + * Copyright (c) 2016--2017, 2020 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -44,6 +44,10 @@ do if ((_sz) > 0 && '\n' != (_buf)[(_sz) - 1]) \ hbuf_putc((_ob), '\n'); \ while (/* CONSTCOND */ 0) + +/* + * See BUFFER_NEWLINE(). + */ #define HBUF_NEWLINE(_buf, _ob) \ BUFFER_NEWLINE((_buf)->data, (_buf)->size, (_ob)) @@ -64,6 +68,27 @@ enum nscope { NSCOPE_SPAN }; +enum rndr_meta_key { + RNDR_META_AFFIL, + RNDR_META_AUTHOR, + RNDR_META_COPY, + RNDR_META_DATE, + RNDR_META_RCSAUTHOR, + RNDR_META_RCSDATE, + RNDR_META_TITLE, + RNDR_META__MAX +}; + +static const char *rndr_meta_keys[RNDR_META__MAX] = { + "affiliation", /* RNDR_META_AFFIL */ + "author", /* RNDR_META_AUTHOR */ + "copyright", /* RNDR_META_COPY */ + "date", /* RNDR_META_DATE */ + "rcsauthor", /* RNDR_META_RCSAUTHOR */ + "rcsdate", /* RNDR_META_RCSDATE */ + "title", /* RNDR_META_TITLE */ +}; + struct nstate { int mdoc; /* whether mdoc(7) */ unsigned int flags; /* output flags */ @@ -104,30 +129,77 @@ static const enum nscope nscopes[LOWDOWN__MAX] = { NSCOPE_SPAN, /* LOWDOWN_ENTITY */ NSCOPE_SPAN, /* LOWDOWN_NORMAL_TEXT */ NSCOPE_BLOCK, /* LOWDOWN_DOC_HEADER */ + NSCOPE_BLOCK, /* LOWDOWN_META */ NSCOPE_BLOCK /* LOWDOWN_DOC_FOOTER */ }; +/* + * Output "source" of size "length" on as many lines as required, + * starting on a line with existing content. + * Escapes text so as not to be roff. + */ static void -escape_span(hbuf *ob, const char *source, size_t length) +rndr_span(hbuf *ob, const char *source, size_t length) { hesc_nroff(ob, source, length, 1, 0); } +/* + * Output "source" of size "length" on as many lines as required, + * starting on its own line. + * Escapes text so as not to be roff. + */ static void -escape_block(hbuf *ob, const char *source, size_t length) +rndr_block(hbuf *ob, const char *source, size_t length) { hesc_nroff(ob, source, length, 0, 0); } +/* + * Output "source" of size "length" on one line, starting on a line with + * existing content. + * Escapes text so as not to be roff. + */ static void -escape_oneline_span(hbuf *ob, const char *source, size_t length) +rndr_one_line_span(hbuf *ob, const char *source, size_t length) { hesc_nroff(ob, source, length, 1, 1); } +/* + * Output "source" of size "length" on a single line. + * Does not escape the given text, which should already have been + * escaped, unless "ownline" is given, in which case make sure we don't + * start with roff. + */ +static void +rndr_one_line_noescape(hbuf *ob, + const char *source, size_t length, int ownline) +{ + size_t i; + + if (ownline && length && source[0] == '.') + HBUF_PUTSL(ob, "\\&"); + for (i = 0; i < length; i++) + if (isspace((unsigned char)source[i])) + HBUF_PUTSL(ob, " "); + else + hbuf_putc(ob, source[i]); +} + +/* + * See rndr_one_line_noescape(). + */ +static void +rndr_one_lineb_noescape(hbuf *ob, const hbuf *b, int ownline) +{ + + return rndr_one_line_noescape(ob, b->data, b->size, ownline); +} + /* * Return the font string for the current set of fonts. * FIXME: I don't think this works for combinations of fixed-width, @@ -300,7 +372,7 @@ rndr_autolink(hbuf *ob, const hbuf *link, enum halink_type type, struct nstate *st, int nln) { - if (link == NULL || link->size == 0) + if (link->size == 0) return 1; if (!nln) @@ -316,7 +388,7 @@ rndr_blockcode(hbuf *ob, const hbuf *content, const hbuf *lang, const struct nstate *st) { - if (content == NULL || content->size == 0) + if (content->size == 0) return; if (st->mdoc) { @@ -326,7 +398,7 @@ rndr_blockcode(hbuf *ob, const hbuf *content, HBUF_PUTSL(ob, ".LD\n"); HBUF_PUTSL(ob, ".ft CR\n"); - escape_block(ob, content->data, content->size); + rndr_block(ob, content->data, content->size); HBUF_NEWLINE(content, ob); HBUF_PUTSL(ob, ".ft\n"); @@ -340,7 +412,7 @@ static void rndr_blockquote(hbuf *ob, const hbuf *content) { - if (content == NULL || content->size == 0) + if (content->size == 0) return; HBUF_PUTSL(ob, ".RS\n"); @@ -353,32 +425,26 @@ static void rndr_codespan(hbuf *ob, const hbuf *content) { - if (content == NULL || content->size == 0) - return; - escape_span(ob, content->data, content->size); + rndr_span(ob, content->data, content->size); } /* * FIXME: not supported. */ -static int +static void rndr_strikethrough(hbuf *ob, const hbuf *content) { - if (content == NULL || content->size == 0) - return 0; - hbuf_put(ob, content->data, content->size); - return 1; + hbuf_putb(ob, content); } -static int +static void rndr_linebreak(hbuf *ob) { /* FIXME: should this always have a newline? */ HBUF_PUTSL(ob, "\n.br\n"); - return 1; } /* @@ -397,7 +463,7 @@ rndr_header(hbuf *ob, const hbuf *content, int level, const struct nstate *st) { - if (content == NULL || content->size == 0) + if (content->size == 0) return; if (st->mdoc) { @@ -405,7 +471,7 @@ rndr_header(hbuf *ob, const hbuf *content, int level, HBUF_PUTSL(ob, ".SH "); else HBUF_PUTSL(ob, ".SS "); - escape_oneline_span(ob, content->data, content->size); + rndr_one_line_span(ob, content->data, content->size); HBUF_PUTSL(ob, "\n"); return; } @@ -420,7 +486,7 @@ rndr_header(hbuf *ob, const hbuf *content, int level, if ((st->flags & LOWDOWN_NROFF_NUMBERED) && (st->flags & LOWDOWN_NROFF_GROFF)) { HBUF_PUTSL(ob, ".XN "); - escape_oneline_span(ob, content->data, content->size); + rndr_one_line_span(ob, content->data, content->size); HBUF_PUTSL(ob, "\n"); } else { hbuf_put(ob, content->data, content->size); @@ -434,8 +500,7 @@ rndr_link(hbuf *ob, const hbuf *content, const hbuf *link, struct lowdown_node *next, int nln) { - if ((content == NULL || content->size == 0) && - (link == NULL || link->size == 0)) + if (content->size == 0 && link->size == 0) return 1; if (!nln) @@ -455,7 +520,7 @@ rndr_listitem(hbuf *ob, const hbuf *content, char *cdata; size_t csize; - if (content == NULL || content->size == 0) + if (content->size == 0) return; /* @@ -516,7 +581,7 @@ rndr_paragraph(hbuf *ob, const hbuf *content, { size_t i = 0, org; - if (content == NULL || content->size == 0) + if (content->size == 0) return; /* Strip away initial white-space. */ @@ -562,11 +627,11 @@ rndr_raw_block(hbuf *ob, const hbuf *content, const struct nstate *st) { size_t org, sz; - if (content == NULL) + if (content->size == 0) return; if ((st->flags & LOWDOWN_NROFF_SKIP_HTML)) { - escape_block(ob, content->data, content->size); + rndr_block(ob, content->data, content->size); return; } @@ -619,8 +684,7 @@ rndr_image(hbuf *ob, const hbuf *link, const struct nstate *st, return; } - cp = memrchr(link->data, '.', link->size); - if (NULL == cp) { + if ((cp = memrchr(link->data, '.', link->size)) == NULL) { warnx("warning: no image suffix (ignoring)"); return; } @@ -648,14 +712,13 @@ rndr_image(hbuf *ob, const hbuf *link, const struct nstate *st, (int)link->size, link->data); } -static int +static void rndr_raw_html(hbuf *ob, const hbuf *text, const struct nstate *st) { - if ((st->flags & LOWDOWN_NROFF_SKIP_HTML) != 0) - return 1; - escape_block(ob, text->data, text->size); - return 1; + if ((st->flags & LOWDOWN_NROFF_SKIP_HTML)) + return; + rndr_block(ob, text->data, text->size); } static void @@ -718,21 +781,21 @@ rndr_table_header(hbuf *ob, const hbuf *content, /* Now the table data. */ - hbuf_put(ob, content->data, content->size); + hbuf_putb(ob, content); } static void rndr_table_body(hbuf *ob, const hbuf *content) { - hbuf_put(ob, content->data, content->size); + hbuf_putb(ob, content); } static void rndr_tablerow(hbuf *ob, const hbuf *content) { - hbuf_put(ob, content->data, content->size); + hbuf_putb(ob, content); HBUF_PUTSL(ob, "\n"); } @@ -742,19 +805,19 @@ rndr_tablecell(hbuf *ob, const hbuf *content, size_t col) if (col > 0) HBUF_PUTSL(ob, "|"); - if (content != NULL && content->size) { + if (content->size) { HBUF_PUTSL(ob, "T{\n"); - hbuf_put(ob, content->data, content->size); + hbuf_putb(ob, content); HBUF_PUTSL(ob, "\nT}"); } } -static int +static void rndr_superscript(hbuf *ob, const hbuf *content) { - if (content == NULL || content->size == 0) - return 0; + if (content->size == 0) + return; /* * If we have a macro contents, it might be the usual macro @@ -768,17 +831,14 @@ rndr_superscript(hbuf *ob, const hbuf *content) HBUF_PUTSL(ob, "\\u\\s-3"); if (content->data[0] != '\n') HBUF_PUTSL(ob, "\n"); - hbuf_put(ob, content->data, content->size); - if (content->size && - content->data[content->size - 1] != '\n') - HBUF_PUTSL(ob, "\n"); + hbuf_putb(ob, content); + HBUF_NEWLINE(content, ob); HBUF_PUTSL(ob, "\\s+3\\d\n"); } else { HBUF_PUTSL(ob, "\\u\\s-3"); - hbuf_put(ob, content->data, content->size); + hbuf_putb(ob, content); HBUF_PUTSL(ob, "\\s+3\\d"); } - return 1; } static void @@ -790,7 +850,7 @@ rndr_normal_text(hbuf *ob, const hbuf *content, size_t offs, size_t i, size; const char *data; - if (content == NULL || content->size == 0) + if (content->size == 0) return; data = content->data + offs; @@ -812,26 +872,24 @@ rndr_normal_text(hbuf *ob, const hbuf *content, size_t offs, for (i = 0; i < size; i++) if (!isspace((unsigned char)data[i])) break; - escape_block(ob, data + i, size - i); + rndr_block(ob, data + i, size - i); } else - escape_span(ob, data, size); + rndr_span(ob, data, size); } static void rndr_footnotes(hbuf *ob, const hbuf *content, const struct nstate *st) { - if (content == NULL || content->size == 0) - return; - /* Put a horizontal line in the case of man(7). */ - if (st->mdoc) { + if (content->size && st->mdoc) { HBUF_PUTSL(ob, ".LP\n"); HBUF_PUTSL(ob, ".sp 3\n"); HBUF_PUTSL(ob, "\\l\'2i'\n"); } - hbuf_put(ob, content->data, content->size); + + hbuf_putb(ob, content); } static void @@ -889,138 +947,164 @@ rndr_footnote_ref(hbuf *ob, unsigned int num, const struct nstate *st) hbuf_printf(ob, "\\u\\s-3%u\\s+3\\d", num); } -static int +static void rndr_math(void) { /* FIXME: use lowdown_opts warnings. */ warnx("warning: math not supported"); - return 1; } /* - * Itereate through multiple multi-white-space separated values in - * "val", filling them in to "env". + * Split "b" at sequential white-space, outputting the results in the + * line-based "env" macro. + * The content in "b" has already been escaped, so there's no need to do + * anything but manage white-space. */ static void -rndr_doc_header_multi(hbuf *ob, const char *val, const char *env) +rndr_meta_multi(hbuf *ob, const hbuf *b, const char *env) { - const char *cp, *start; - size_t sz; + const char *start; + size_t sz, i; - for (cp = val; *cp != '\0'; ) { - while (isspace((unsigned char)*cp)) - cp++; - if (*cp == '\0') + for (i = 0; i < b->size; i++) { + while (i < b->size && + isspace((unsigned char)b->data[i])) + i++; + if (i == b->size) continue; - start = cp; + start = &b->data[i]; sz = 0; - while (*cp != '\0') { - if (!isspace((unsigned char)cp[0]) || - !isspace((unsigned char)cp[1])) { - sz++; - cp++; - continue; - } - cp += 2; - break; - } - if (sz == 0) + + for (; i < b->size; i++) + if (i < b->size - 1 && + isspace((unsigned char)b->data[i]) && + isspace((unsigned char)b->data[i + 1])) + break; + if ((sz = &b->data[i] - start) == 0) continue; hbuf_printf(ob, ".%s\n", env); - hesc_nroff(ob, start, sz, 0, 1); - hbuf_putc(ob, '\n'); + rndr_one_line_noescape(ob, start, sz, 1); + HBUF_PUTSL(ob, "\n"); } } static void -rndr_doc_header(hbuf *ob, - const struct lowdown_meta *m, size_t msz, - const struct nstate *st) +rndr_meta(hbuf *ob, const hbuf *tmp, struct lowdown_metaq *mq, + const struct lowdown_node *n, const struct nstate *st) { - const char *date = NULL, *author = NULL, - *title = "Untitled article", *affil = NULL, - *copy = NULL; - time_t t; - char buf[32]; - struct tm *tm; - size_t i; - - if ( ! (LOWDOWN_STANDALONE & st->flags)) - return; - - /* Acquire metadata that we'll fill in. */ - - for (i = 0; i < msz; i++) - if (strcmp(m[i].key, "title") == 0) - title = m[i].value; - else if (strcmp(m[i].key, "affiliation") == 0) - affil = m[i].value; - else if (strcmp(m[i].key, "author") == 0) - author = m[i].value; - else if (strcmp(m[i].key, "rcsauthor") == 0) - author = rcsauthor2str(m[i].value); - else if (strcmp(m[i].key, "rcsdate") == 0) - date = rcsdate2str(m[i].value); - else if (strcmp(m[i].key, "date") == 0) - date = date2str(m[i].value); - else if (strcmp(m[i].key, "copyright") == 0) - copy = m[i].value; - - /* FIXME: convert to buf without strftime. */ - - if (date == NULL) { - t = time(NULL); - tm = localtime(&t); - strftime(buf, sizeof(buf), "%Y-%m-%d", tm); - date = buf; + enum rndr_meta_key key; + const struct lowdown_node *copy; + struct lowdown_meta *m; + + if (mq != NULL) { + m = xcalloc(1, sizeof(struct lowdown_meta)); + TAILQ_INSERT_TAIL(mq, m, entries); + m->key = xstrndup + (n->rndr_meta.key.data, + n->rndr_meta.key.size); + m->value = xstrndup(tmp->data, tmp->size); } - /* Strip leading newlines (empty ok but weird) */ + if (!(st->flags & LOWDOWN_STANDALONE)) + return; - while (isspace((unsigned char)*title)) - title++; + for (key = 0; key < RNDR_META__MAX; key++) + if (hbuf_streq(&n->rndr_meta.key, rndr_meta_keys[key])) + break; - if (copy != NULL) - while (isspace((unsigned char)*copy)) - copy++; - if (affil != NULL) - while (isspace((unsigned char)*affil)) - affil++; + /* + * If we're printing the date and have a copyright, we'll also + * set the copyright date. + */ - /* Emit our authors and title. */ + TAILQ_FOREACH(copy, &n->parent->children, entries) { + if (copy->type != LOWDOWN_META) + continue; + if (hbuf_streq(©->rndr_meta.key, "copyright")) + break; + } + + /* FIXME: AU must happen after TL. */ if (!st->mdoc) { - HBUF_PUTSL(ob, ".nr PS 10\n"); - HBUF_PUTSL(ob, ".nr GROWPS 3\n"); - HBUF_PUTSL(ob, ".nr PD 1.0v\n"); - if (copy != NULL) { - hbuf_printf(ob, ".ds LF \\s-2" - "Copyright \\(co %s\\s+2\n", copy); - hbuf_printf(ob, ".ds RF \\s-2%s\\s+2\n", date); - } else - hbuf_printf(ob, ".DA \\s-2%s\\s+2\n", date); - HBUF_PUTSL(ob, ".TL\n"); - escape_block(ob, title, strlen(title)); - HBUF_PUTSL(ob, "\n"); - if (author != NULL) - rndr_doc_header_multi(ob, author, "AU"); - if (affil != NULL) - rndr_doc_header_multi(ob, affil, "AI"); + switch (key) { + case RNDR_META_COPY: + HBUF_PUTSL(ob, ".ds LF \\s-2Copyright \\(co "); + rndr_one_lineb_noescape(ob, tmp, 0); + HBUF_PUTSL(ob, "\\s+2\n"); + break; + case RNDR_META_DATE: + if (copy != NULL) { + HBUF_PUTSL(ob, ".ds RF \\s-2"); + rndr_one_lineb_noescape(ob, tmp, 0); + HBUF_PUTSL(ob, "\\s+2\n"); + } + HBUF_PUTSL(ob, ".DA \\s-2"); + rndr_one_lineb_noescape(ob, tmp, 0); + HBUF_PUTSL(ob, "\\s+2\n"); + break; + case RNDR_META_TITLE: + HBUF_PUTSL(ob, ".TL\n"); + rndr_one_lineb_noescape(ob, tmp, 1); + HBUF_PUTSL(ob, "\n"); + break; + case RNDR_META_AUTHOR: + rndr_meta_multi(ob, tmp, "AU"); + break; + case RNDR_META_AFFIL: + rndr_meta_multi(ob, tmp, "AI"); + break; + default: + break; + } } else { - HBUF_PUTSL(ob, ".TH \""); - escape_oneline_span(ob, title, strlen(title)); - hbuf_printf(ob, "\" 7 %s\n", date); + switch (key) { + case RNDR_META_TITLE: + HBUF_PUTSL(ob, ".TH \""); + rndr_one_lineb_noescape(ob, tmp, 0); + HBUF_PUTSL(ob, "\" 7\n"); + /* FIXME: print date. */ + /*hbuf_printf(ob, "\" 7 %s\n", date);*/ + break; + default: + break; + } } } +static void +rndr_doc_header(hbuf *ob, const hbuf *tmp, + const struct lowdown_node *n, const struct nstate *st) +{ + struct lowdown_node *title; + + if (!(st->flags & LOWDOWN_STANDALONE)) + return; + + TAILQ_FOREACH(title, &n->children, entries) { + if (title->type != LOWDOWN_META) + continue; + if (hbuf_streq(&title->rndr_meta.key, "title")) + break; + } + + if (title == NULL && !st->mdoc) + HBUF_PUTSL(ob, ".TL\nUntitled Article\n"); + else if (title == NULL) + HBUF_PUTSL(ob, ".TH \"Untitled Article\" 7\n"); + + hbuf_putb(ob, tmp); +} + /* * Actually render the node "root" and all of its children into the * output buffer "ob". * Return whether we should remove nodes relative to "root". */ static void -rndr(hbuf *ob, struct nstate *ref, struct lowdown_node *root) +rndr(hbuf *ob, struct lowdown_metaq *metaq, + struct nstate *ref, struct lowdown_node *root) { struct lowdown_node *n, *next, *prev; hbuf *tmp; @@ -1064,7 +1148,7 @@ rndr(hbuf *ob, struct nstate *ref, struct lowdown_node *root) } TAILQ_FOREACH(n, &root->children, entries) - rndr(tmp, ref, n); + rndr(tmp, metaq, ref, n); /* * Compute whether the previous output does have a newline: @@ -1111,9 +1195,10 @@ rndr(hbuf *ob, struct nstate *ref, struct lowdown_node *root) rndr_blockquote(ob, tmp); break; case LOWDOWN_DOC_HEADER: - rndr_doc_header(ob, - root->rndr_doc_header.m, - root->rndr_doc_header.msz, ref); + rndr_doc_header(ob, tmp, root, ref); + break; + case LOWDOWN_META: + rndr_meta(ob, tmp, metaq, root, ref); break; case LOWDOWN_HEADER: rndr_header(ob, tmp, @@ -1217,9 +1302,9 @@ rndr(hbuf *ob, struct nstate *ref, struct lowdown_node *root) } - if (LOWDOWN_CHNG_INSERT == root->chng || - LOWDOWN_CHNG_DELETE == root->chng) { - if (NSCOPE_BLOCK == nscopes[root->type]) + if (root->chng == LOWDOWN_CHNG_INSERT || + root->chng == LOWDOWN_CHNG_DELETE) { + if (nscopes[root->type] == NSCOPE_BLOCK) HBUF_PUTSL(ob, ".gcolor\n"); else HBUF_PUTSL(ob, "\\m[]"); @@ -1255,12 +1340,13 @@ rndr(hbuf *ob, struct nstate *ref, struct lowdown_node *root) } void -lowdown_nroff_rndr(hbuf *ob, void *ref, struct lowdown_node *root) +lowdown_nroff_rndr(hbuf *ob, struct lowdown_metaq *metaq, + void *ref, struct lowdown_node *root) { struct nstate *st = ref; memset(st->fonts, 0, sizeof(st->fonts)); - rndr(ob, ref, root); + rndr(ob, metaq, ref, root); } void * diff --git a/smartypants.c b/smartypants.c index 670ba0a5..47e254b5 100644 --- a/smartypants.c +++ b/smartypants.c @@ -150,6 +150,7 @@ static const enum type types[LOWDOWN__MAX] = { TYPE_OPAQUE, /* LOWDOWN_ENTITY */ TYPE_TEXT, /* LOWDOWN_NORMAL_TEXT */ TYPE_BLOCK, /* LOWDOWN_DOC_HEADER */ + TYPE_BLOCK, /* LOWDOWN_META */ TYPE_BLOCK, /* LOWDOWN_DOC_FOOTER */ }; @@ -208,119 +209,6 @@ smarty_iswb(char c) ispunct((unsigned char)c); } -static void -buf_append(char **buf, size_t *sz, size_t *max, const char *val) -{ - size_t valsz; - - valsz = strlen(val); - if (*sz + valsz + 1 > *max) { - *max += valsz + 128; - *buf = xrealloc(*buf, *max); - } - - strlcat(*buf, val, *max); -} - -static void -buf_appendc(char **buf, size_t *sz, size_t *max, char val) -{ - - if (*sz + 2 > *max) { - *max += 128; - *buf = xrealloc(*buf, *max); - } - (*buf)[(*sz)++] = val; - (*buf)[(*sz)++] = '\0'; -} - -static char * -smarty_buf(const char *buf, enum lowdown_type type) -{ - char *out = NULL; - size_t outsz = 0, outmax = 0, i, j, bufsz, sz; - int left_wb = 1; - - bufsz = strlen(buf); - - for (i = 0; i < bufsz; i++) { - switch (buf[i]) { - case '.': - case '(': - case '-': - for (j = 0; syms[j].key != NULL; j++) { - sz = strlen(syms[j].key); - if (i + sz - 1 >= bufsz) - continue; - if (memcmp(syms[j].key, &buf[i], sz)) - continue; - buf_append(&out, &outsz, &outmax, - ent_htmls[syms[j].ent]); - left_wb = 0; - i += sz - 1; - break; - } - if (syms[j].key != NULL) - continue; - break; - case '"': - if (!left_wb) { - if (i + 1 < bufsz - 1 && - !smarty_iswb(buf[i + 1])) - break; - buf_append(&out, &outsz, &outmax, - ent_htmls[ENT_RDQUO]); - left_wb = 0; - continue; - } - buf_append(&out, &outsz, &outmax, - ent_htmls[ENT_LDQUO]); - left_wb = 0; - continue; - case '\'': - if (!left_wb) { - if (i + 1 < bufsz - 1 && - !smarty_iswb(buf[i + 1])) - buf_append(&out, &outsz, &outmax, - ent_htmls[ENT_RSQUO]); - left_wb = 0; - continue; - } - buf_append(&out, &outsz, &outmax, - ent_htmls[ENT_LDQUO]); - left_wb = 0; - continue; - case '1': - case '3': - if (!left_wb) - break; - for (j = 0; syms2[j].key != NULL; j++) { - sz = strlen(syms2[j].key); - if (i + sz - 1 >= bufsz) - continue; - if (memcmp(syms2[j].key, &buf[i], sz)) - continue; - if (i + sz < bufsz - 1 && - !smarty_iswb(buf[i + sz])) - buf_append(&out, &outsz, &outmax, - ent_htmls[syms2[j].ent]); - left_wb = 0; - break; - } - if (syms2[j].key != NULL) - continue; - break; - default: - break; - } - - buf_appendc(&out, &outsz, &outmax, buf[i]); - left_wb = smarty_iswb(buf[i]); - } - - return out; -} - /* * Recursive scan for next white-space. * If "skip" is set, we're on the starting node and shouldn't do a check @@ -355,6 +243,7 @@ smarty_right_wb_r(const struct lowdown_node *n, int skip) /* Now scan back up. */ do { + /* FIXME: don't go up to block. */ if ((nn = TAILQ_NEXT(n, entries)) != NULL) return smarty_right_wb_r(nn, 0); } while ((n = n->parent) != NULL); @@ -490,17 +379,6 @@ smarty_block(struct lowdown_node *root, { struct smarty s; struct lowdown_node *n; - size_t i; - char *v; - - if (root->type == LOWDOWN_DOC_HEADER) - for (i = 0; i < root->rndr_doc_header.msz; i++) { - v = smarty_buf - (root->rndr_doc_header.m[i].value, - type); - free(root->rndr_doc_header.m[i].value); - root->rndr_doc_header.m[i].value = v; - } s.left_wb = 1; diff --git a/term.c b/term.c index 0c277412..7fdc3303 100644 --- a/term.c +++ b/term.c @@ -123,6 +123,7 @@ static const struct sty *stys[LOWDOWN__MAX] = { NULL, /* LOWDOWN_ENTITY */ NULL, /* LOWDOWN_NORMAL_TEXT */ NULL, /* LOWDOWN_DOC_HEADER */ + NULL, /* LOWDOWN_META */ NULL /* LOWDOWN_DOC_FOOTER */ }; @@ -648,11 +649,11 @@ rndr_entity(hbuf *buf, int32_t val) } void -lowdown_term_rndr(hbuf *ob, void *arg, struct lowdown_node *n) +lowdown_term_rndr(hbuf *ob, struct lowdown_metaq *metaq, + void *arg, struct lowdown_node *n) { struct lowdown_node *child; struct term *p = arg; - size_t i; int32_t entity; /* Current nodes we're servicing. */ @@ -684,6 +685,7 @@ lowdown_term_rndr(hbuf *ob, void *arg, struct lowdown_node *n) case LOWDOWN_HRULE: case LOWDOWN_LINEBREAK: case LOWDOWN_LISTITEM: + case LOWDOWN_META: case LOWDOWN_TABLE_ROW: rndr_buf_vspace(p, ob, n, 1); break; @@ -704,6 +706,12 @@ lowdown_term_rndr(hbuf *ob, void *arg, struct lowdown_node *n) HBUF_PUTSL(p->tmp, "^"); rndr_buf(p, ob, n, p->tmp, 0, NULL); break; + case LOWDOWN_META: + rndr_buf(p, ob, n, &n->rndr_meta.key, 0, &sty_meta_key); + hbuf_truncate(p->tmp); + HBUF_PUTSL(p->tmp, ": "); + rndr_buf(p, ob, n, p->tmp, 0, &sty_meta_key); + break; default: break; } @@ -713,7 +721,7 @@ lowdown_term_rndr(hbuf *ob, void *arg, struct lowdown_node *n) TAILQ_FOREACH(child, &n->children, entries) { p->stackpos++; assert(p->stackpos < 128); - lowdown_term_rndr(ob, p, child); + lowdown_term_rndr(ob, metaq, p, child); p->stackpos--; } @@ -777,19 +785,6 @@ lowdown_term_rndr(hbuf *ob, void *arg, struct lowdown_node *n) case LOWDOWN_NORMAL_TEXT: rndr_buf(p, ob, n, &n->rndr_normal_text.text, 0, NULL); break; - case LOWDOWN_DOC_HEADER: - for (i = 0; i < n->rndr_doc_header.msz; i++) { - hbuf_truncate(p->tmp); - hbuf_printf(p->tmp, "%s:", - n->rndr_doc_header.m[i].key); - rndr_buf(p, ob, n, p->tmp, 0, &sty_meta_key); - hbuf_truncate(p->tmp); - hbuf_puts(p->tmp, - n->rndr_doc_header.m[i].value); - rndr_buf(p, ob, n, p->tmp, 1, NULL); - rndr_buf_vspace(p, ob, n, 1); - } - break; default: break; } @@ -813,11 +808,12 @@ lowdown_term_rndr(hbuf *ob, void *arg, struct lowdown_node *n) rndr_buf_vspace(p, ob, n, 1); break; case LOWDOWN_DOC_HEADER: - if (n->rndr_doc_header.msz) + if (!TAILQ_EMPTY(&n->children)) rndr_buf_vspace(p, ob, n, 2); break; case LOWDOWN_HRULE: case LOWDOWN_LISTITEM: + case LOWDOWN_META: case LOWDOWN_ROOT: case LOWDOWN_TABLE_ROW: rndr_buf_vspace(p, ob, n, 1); diff --git a/tree.c b/tree.c index 6c31127e..786997db 100644 --- a/tree.c +++ b/tree.c @@ -66,6 +66,7 @@ static const char *const names[LOWDOWN__MAX] = { "LOWDOWN_ENTITY", /* LOWDOWN_ENTITY */ "LOWDOWN_NORMAL_TEXT", /* LOWDOWN_NORMAL_TEXT */ "LOWDOWN_DOC_HEADER", /* LOWDOWN_DOC_HEADER */ + "LOWDOWN_META", /* LOWDOWN_META */ "LOWDOWN_DOC_FOOTER", /* LOWDOWN_DOC_FOOTER */ }; @@ -87,11 +88,12 @@ rndr_short(hbuf *ob, const hbuf *b) } static void -rndr(hbuf *ob, const struct lowdown_node *root, size_t indent) +rndr(hbuf *ob, struct lowdown_metaq *metaq, + const struct lowdown_node *root, size_t indent) { - const struct lowdown_node *n; - hbuf *tmp; - size_t i, j; + const struct lowdown_node *n; + hbuf *tmp; + size_t i, j; for (i = 0; i < indent; i++) HBUF_PUTSL(ob, " "); @@ -177,14 +179,12 @@ rndr(hbuf *ob, const struct lowdown_node *root, size_t indent) HLIST_FL_ORDERED & root->rndr_list.flags ? "ordered" : "unordered"); break; - case LOWDOWN_DOC_HEADER: - for (i = 0; i < root->rndr_doc_header.msz; i++) { - for (j = 0; j < indent + 1; j++) - HBUF_PUTSL(ob, " "); - hbuf_printf(ob, "metadata: %zu Bytes: %s\n", - strlen(root->rndr_doc_header.m[i].value), - root->rndr_doc_header.m[i].key); - } + case LOWDOWN_META: + for (j = 0; j < indent + 1; j++) + HBUF_PUTSL(ob, " "); + hbuf_printf(ob, "key: "); + rndr_short(ob, &root->rndr_meta.key); + HBUF_PUTSL(ob, "\n"); break; case LOWDOWN_MATH_BLOCK: for (i = 0; i < indent + 1; i++) @@ -220,17 +220,18 @@ rndr(hbuf *ob, const struct lowdown_node *root, size_t indent) tmp = hbuf_new(64); TAILQ_FOREACH(n, &root->children, entries) - rndr(tmp, n, indent + 1); + rndr(tmp, metaq, n, indent + 1); hbuf_put(ob, tmp->data, tmp->size); hbuf_free(tmp); } void -lowdown_tree_rndr(hbuf *ob, void *ref, struct lowdown_node *root) +lowdown_tree_rndr(hbuf *ob, struct lowdown_metaq *metaq, + void *ref, struct lowdown_node *root) { assert(ref == NULL); - rndr(ob, root, 0); + rndr(ob, metaq, root, 0); } void *