slweb

Једноставни генератор статичких веб страна
git clone https://git.sr.ht/~strahinja/slweb
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

slweb.c (133252B)


      1 /* This program is licensed under the terms of GNU GPL v3 or (at your option)
      2  * any later version. Copyright (C) 2020-2026  Страхиња Радић.
      3  * See the file LICENSE for exact copyright and license details. */
      4 
      5 #include <assert.h>
      6 #include <ctype.h>
      7 #include <dirent.h>
      8 #include <errno.h>
      9 #include <signal.h>
     10 #include <stdarg.h>
     11 #include <stdio.h>
     12 #include <stdlib.h>
     13 #include <stdint.h>
     14 #include <string.h>
     15 #include <sys/stat.h>
     16 #include <sys/types.h>
     17 #include <sys/wait.h>
     18 #include <unistd.h>
     19 
     20 #include "utf8.h"
     21 #include "defs.h"
     22 #include "version.h"
     23 
     24 static KeyValue* footnotes	      = NULL;
     25 static KeyValue* links		      = NULL;
     26 static KeyValue* pfootnotes	      = NULL;
     27 static KeyValue* plinks		      = NULL;
     28 static KeyValue* macros		      = NULL;
     29 static KeyValue* pmacros	      = NULL;
     30 static KeyValue* pvars		      = NULL;
     31 static KeyValue* vars		      = NULL;
     32 static u8** inline_footnotes	      = NULL;
     33 static u8* csv_template		      = NULL;
     34 static u8* tsv_template		      = NULL;
     35 static char* basedir		      = NULL;
     36 static char* csv_filename	      = NULL;
     37 static char* global_link_prefix	      = NULL;
     38 static char* incdir		      = NULL;
     39 static char* input_dirname	      = NULL;
     40 static char* input_filename	      = NULL;
     41 static char* tsv_filename	      = NULL;
     42 static unsigned long long state	      = ST_NONE;
     43 static size_t basedir_size	      = 0;
     44 static size_t colno		      = 1;
     45 static size_t csv_iter		      = 0;
     46 static size_t csv_template_len	      = 0;
     47 static size_t csv_template_size	      = 0;
     48 static size_t current_footnote	      = 0;
     49 static size_t current_inline_footnote = 0;
     50 static size_t footnote_count	      = 0;
     51 static size_t global_link_prefix_size = 0;
     52 static size_t ifn_size		      = 0;
     53 static size_t inline_footnote_count   = 0;
     54 static size_t lineno		      = 0;
     55 static size_t links_count	      = 0;
     56 static size_t macros_count	      = 0;
     57 static size_t tsv_iter		      = 0;
     58 static size_t tsv_template_len	      = 0;
     59 static size_t tsv_template_size	      = 0;
     60 static size_t vars_count	      = 0;
     61 static int incdir_only_summary	      = 0;
     62 static int output_firstcol	      = 1;
     63 
     64 static void add_css(FILE* output, int output_yaml);
     65 static int error(const int code, const char* file, const char* func,
     66 	const int line, const char* fmt, ...);
     67 static u8* format_date(const u8* date_arg, const char* timestamp_format);
     68 static void free_keyvalue(KeyValue** list, const size_t list_count);
     69 static u8* get_value(KeyValue* list, const size_t list_count, const u8* key,
     70 	KeyValue** cursor);
     71 static void print_meta_var(FILE* output, u8** tsv_header, u8** tsv_register);
     72 static int print_output(FILE* output, const char* fmt, ...);
     73 static void process_horizontal_rule(FILE* output);
     74 static void process_incdir_subdir(FILE* output, const char* subdirname,
     75 	const u8* link_prefix, const int details_open, const u8* macro_body,
     76 	const u8* footer_permalink_text, const char* permalink_url,
     77 	const int ext_in_permalink, const int list_only);
     78 static void process_inline_image(FILE* output, const u8* image_text,
     79 	const u8* image_file_prefix, const u8* link_prefix, const u8* image_url,
     80 	const int add_link, const int add_figcaption);
     81 static void process_inline_stylesheet(FILE* output, const u8* css_filename,
     82 	int output_yaml);
     83 static void process_list_end(FILE* output);
     84 static void process_list_item_end(FILE* output);
     85 static void process_numlist_end(FILE* output);
     86 static void process_stylesheet(FILE* output, const u8* css_filename,
     87 	int output_yaml);
     88 static void process_timestamp(FILE* output, const u8* link_prefix,
     89 	const char* link, const u8* permalink_macro, const u8* date,
     90 	const int ext_in_permalink);
     91 static void process_tsv(FILE* output, const u8* arg_token,
     92 	const int read_yaml_macros_and_links, const int end_tag);
     93 static void process_tsv_count(FILE* output, const u8* arg_token,
     94 	const int read_yaml_macros_and_links, const int is_tsv);
     95 static void read_csv(FILE* output, const char* filename,
     96 	csv_callback_t callback);
     97 static int read_file_into_buffer(FILE** input, u8** buffer, size_t* buffer_size,
     98 	const char* input_filename, char** input_dirname);
     99 static int read_tsv(FILE* output, const char* filename, tsv_callback_t callback);
    100 static int reverse_alphacompare(const struct dirent** a,
    101 	const struct dirent** b);
    102 static int set_basedir(char** basedir, size_t* basedir_size, const char* arg);
    103 static int simple_parse_yaml_line(const u8* line, KeyValue** vars,
    104 	size_t* vars_count, KeyValue** pvars);
    105 static int slweb_parse(FILE* output, const char* source_filename,
    106 	const size_t sfn_size, const u8* buffer, const int body_only,
    107 	const int read_yaml_macros_and_links);
    108 static int startswith(const char* s, const char* what);
    109 static char* strip_ext(const char* fn, const size_t fn_size);
    110 static int url_is_local(const char* url);
    111 static int warning(const int code, const u8* fmt, ...);
    112 
    113 static void
    114 add_css(FILE* output, int output_yaml)
    115 {
    116 	KeyValue* pvars = vars;
    117 	if (!vars)
    118 		return;
    119 	while (pvars < vars + vars_count)
    120 	{
    121 		assert(pvars->key != NULL);
    122 		if (!strcmp((char*)pvars->key, "stylesheet"))
    123 			process_stylesheet(output, pvars->value, output_yaml);
    124 		else if (!strcmp((char*)pvars->key, "inline-stylesheet"))
    125 			process_inline_stylesheet(output, pvars->value,
    126 				output_yaml);
    127 		pvars++;
    128 	}
    129 }
    130 
    131 static int
    132 all_numeric(const u8* start, const u8* end)
    133 {
    134 	const u8* ps = start;
    135 	assert((start != NULL) && (end != NULL));
    136 	while (ps != end)
    137 	{
    138 		if (*ps && !isdigit((const int)*ps))
    139 			return 0;
    140 		ps++;
    141 	}
    142 	return 1;
    143 }
    144 
    145 static int
    146 begin_article(FILE* output, const char* source_filename, const size_t sfn_size,
    147 	const u8* link_prefix, const int add_article_header, const u8* author,
    148 	const u8* title, const u8* header_text, const char* title_heading_level,
    149 	u8* date, const int ext_in_permalink, const char* permalink_url)
    150 {
    151 	char* temp	= NULL;
    152 	int save_incdir = IN(state, ST_INCDIR);
    153 
    154 	UNUSED(add_article_header);
    155 	CLEAR(state, ST_INCDIR);
    156 
    157 	if (author || date || header_text || title)
    158 		print_output(output, "<header>\n");
    159 
    160 	if (title)
    161 		print_output(output, "<h%s>%s</h%s>\n",
    162 			title_heading_level ? title_heading_level : "2",
    163 			(char*)title,
    164 			title_heading_level ? title_heading_level : "2");
    165 
    166 	if (header_text)
    167 		print_output(output, "<p>%s\n", (char*)header_text);
    168 
    169 	if (author)
    170 		print_output(output, "<address>%s</address>\n", author);
    171 
    172 	if (date && source_filename)
    173 	{
    174 		u8* permalink_macro = NULL;
    175 		char* link	    = NULL;
    176 		size_t link_len;
    177 
    178 		permalink_macro = get_value(macros, macros_count,
    179 			(u8*)"permalink", NULL);
    180 		link = strip_ext((const char*)source_filename, BUFSIZE);
    181 		assert(sfn_size != 0);
    182 		link_len = strlen(link);
    183 		if (ext_in_permalink)
    184 			MEMCCPY((link + link_len), timestamp_output_ext,
    185 				sfn_size - link_len, temp);
    186 		if (permalink_url)
    187 			process_timestamp(output, link_prefix, permalink_url,
    188 				permalink_macro, date, ext_in_permalink);
    189 		else
    190 			process_timestamp(output, link_prefix, link,
    191 				permalink_macro, date, ext_in_permalink);
    192 		free(link);
    193 	}
    194 
    195 	if (author || date || header_text || title)
    196 		print_output(output, "</header>\n");
    197 
    198 	output_firstcol = author || date || header_text || title;
    199 
    200 	if (save_incdir)
    201 		SET(state, ST_INCDIR);
    202 
    203 	return 0;
    204 }
    205 
    206 static int
    207 begin_html_and_head(FILE* output)
    208 {
    209 	KeyValue* cursor      = NULL;
    210 	KeyValue* cursor_type = NULL;
    211 	KeyValue* cursor_desc = NULL;
    212 	u8* lang	      = get_value(vars, vars_count, (u8*)"lang", NULL);
    213 	u8* site_name	= get_value(vars, vars_count, (u8*)"site-name", NULL);
    214 	u8* site_desc	= get_value(vars, vars_count, (u8*)"site-desc", NULL);
    215 	u8* canonical	= get_value(vars, vars_count, (u8*)"canonical", NULL);
    216 	u8* favicon_url = get_value(vars, vars_count, (u8*)"favicon-url", NULL);
    217 	u8* meta	= get_value(vars, vars_count, (u8*)"meta", NULL);
    218 	u8* feed	= NULL;
    219 	u8* feed_type	= NULL;
    220 	u8* feed_desc	= NULL;
    221 	char* favicon	= NULL;
    222 	char* filename	= NULL;
    223 
    224 	print_output(output,
    225 		"<!DOCTYPE html>\n"
    226 		"<html lang=\"%s\">\n"
    227 		"<head>\n"
    228 		"<title>%s</title>\n"
    229 		"<meta charset=\"utf-8\">\n",
    230 		lang ? (char*)lang : "en", site_name ? (char*)site_name : "");
    231 
    232 	CALLOC(favicon, char, BUFSIZE);
    233 	snprintf(favicon, BUFSIZE - 1, "%s/favicon.ico", basedir);
    234 	if (!access(favicon, R_OK))
    235 		print_output(output,
    236 			"<link rel=\"shortcut icon\" type=\"image/x-icon\""
    237 			" href=\"%s\">\n",
    238 			favicon_url ? (char*)favicon_url : "/favicon.ico");
    239 	free(favicon);
    240 
    241 	if (meta)
    242 	{
    243 		CALLOC(filename, char, BUFSIZE);
    244 		snprintf(filename, BUFSIZE, "%s/%s", input_dirname, (char*)meta);
    245 		read_tsv(output, filename, &print_meta_var);
    246 		free(filename);
    247 	}
    248 
    249 	if (canonical && *canonical)
    250 		print_output(output, "<link rel=\"canonical\" href=\"%s\">\n",
    251 			(char*)canonical);
    252 
    253 	cursor	    = vars;
    254 	cursor_type = vars;
    255 	cursor_desc = vars;
    256 	do
    257 	{
    258 		feed	  = get_value(cursor, vars_count - (cursor - vars),
    259 			     (u8*)"feed", &cursor);
    260 		feed_type = get_value(cursor_type,
    261 			vars_count - (cursor_type - vars), (u8*)"feed-type",
    262 			&cursor_type);
    263 		feed_desc = get_value(cursor_desc,
    264 			vars_count - (cursor_desc - vars), (u8*)"feed-desc",
    265 			&cursor_desc);
    266 		if (feed && *feed && feed_type && *feed_type && feed_desc
    267 			&& *feed_desc)
    268 			print_output(output,
    269 				"<link rel=\"alternate\""
    270 				" type=\"%s\" href=\"%s\" "
    271 				"title=\"%s\">\n",
    272 				(char*)feed_type, (char*)feed,
    273 				(char*)feed_desc);
    274 	} while (feed && feed_type && feed_desc);
    275 
    276 	if (site_desc && *site_desc)
    277 		print_output(output,
    278 			"<meta name=\"description\" content=\"%s\">\n",
    279 			(char*)site_desc);
    280 
    281 	print_output(output,
    282 		"<meta name=\"viewport\" content=\"width=device-width,"
    283 		" initial-scale=1\">\n<meta name=\"generator\" "
    284 		"content=\"slweb\">\n");
    285 
    286 	output_firstcol = 1;
    287 	return 0;
    288 }
    289 
    290 static int
    291 cleanup(void)
    292 {
    293 	free(basedir);
    294 	free(input_dirname);
    295 	while (inline_footnote_count--)
    296 		free(inline_footnotes[inline_footnote_count]);
    297 	free(inline_footnotes);
    298 	free_keyvalue(&footnotes, footnote_count);
    299 	free_keyvalue(&links, links_count);
    300 	free_keyvalue(&macros, macros_count);
    301 	free_keyvalue(&vars, vars_count);
    302 	free(footnotes);
    303 	free(links);
    304 	free(macros);
    305 	free(vars);
    306 	return 0;
    307 }
    308 
    309 static int
    310 end_body_and_html(FILE* output)
    311 {
    312 	print_output(output, "</body>\n</html>\n");
    313 	output_firstcol = 1;
    314 	return 0;
    315 }
    316 
    317 static int
    318 end_footnotes(FILE* output, int add_footnote_div)
    319 {
    320 	KeyValue* pfootnote;
    321 	size_t footnote = 0;
    322 
    323 	CLEAR(state, ST_PARA_OPEN);
    324 
    325 	if (add_footnote_div)
    326 	{
    327 		print_output(output, "<div class=\"footnotes\">\n");
    328 		output_firstcol = 1;
    329 	}
    330 
    331 	process_horizontal_rule(output);
    332 
    333 	for (footnote = 0; footnote < inline_footnote_count; footnote++)
    334 	{
    335 		print_output(output,
    336 			"<p id=\"inline-footnote-%d\">"
    337 			"<a href=\"#inline-footnote-text-%d\">%d.</a> "
    338 			"%s\n",
    339 			footnote + 1, footnote + 1, footnote + 1,
    340 			(char*)inline_footnotes[footnote]);
    341 		output_firstcol = 1;
    342 	}
    343 
    344 	pfootnote = footnotes;
    345 	footnote  = 0;
    346 	while (pfootnote && footnote < footnote_count)
    347 	{
    348 		print_output(output,
    349 			"<p id=\"footnote-%d\">"
    350 			"<a href=\"#footnote-text-%d\">%d.</a> %s\n",
    351 			footnote + 1, footnote + 1, footnote + 1,
    352 			(char*)pfootnote->value);
    353 		output_firstcol = 1;
    354 		pfootnote++;
    355 		footnote++;
    356 	}
    357 
    358 	if (add_footnote_div)
    359 	{
    360 		print_output(output, "</div><!--footnotes-->\n");
    361 		output_firstcol = 1;
    362 	}
    363 
    364 	return 0;
    365 }
    366 
    367 static int
    368 end_head_start_body(FILE* output)
    369 {
    370 	print_output(output, "</head>\n<body>\n");
    371 	output_firstcol = 1;
    372 	return 0;
    373 }
    374 
    375 static int
    376 error(const int code, const char* file, const char* func, const int line,
    377 	const char* fmt, ...)
    378 {
    379 	char buf[BUFSIZE];
    380 	va_list args;
    381 	va_start(args, fmt);
    382 	vsnprintf(buf, sizeof(buf), (const char*)fmt, args);
    383 	va_end(args);
    384 	fprintf(stderr, "%s:%s:%lu:%lu:%s:%s:%d: %s\n", PROGRAMNAME,
    385 		input_filename ? input_filename : "(init/stdin)", lineno, colno,
    386 		func, file, line, buf);
    387 	return code;
    388 }
    389 
    390 static int
    391 filter_slw(const struct dirent* node)
    392 {
    393 	size_t node_len;
    394 	size_t slw_len;
    395 
    396 	assert(node != NULL);
    397 	if (((*node->d_name == '.')
    398 		    && (!*(node->d_name + 1)
    399 			    || ((*(node->d_name + 1) == '.')
    400 				    && !(*(node->d_name + 2))))))
    401 		return 0;
    402 	node_len = strlen(node->d_name);
    403 	slw_len	 = sizeof(".slw") - 1;
    404 	if (slw_len >= node_len)
    405 		return 0;
    406 	return !strcmp(node->d_name + node_len - slw_len, ".slw");
    407 }
    408 
    409 static int
    410 filter_subdirs(const struct dirent* node)
    411 {
    412 	struct stat st;
    413 	char* nodename = NULL;
    414 
    415 	assert(node != NULL);
    416 	if (((*node->d_name == '.')
    417 		    && (!*(node->d_name + 1)
    418 			    || ((*(node->d_name + 1) == '.')
    419 				    && !(*(node->d_name + 2))))))
    420 		return 0;
    421 	CALLOC(nodename, char, BUFSIZE);
    422 	snprintf(nodename, BUFSIZE, "%s/%s", incdir, node->d_name);
    423 	if (lstat(nodename, &st) < 0 || !S_ISDIR(st.st_mode))
    424 	{
    425 		free(nodename);
    426 		return 0;
    427 	}
    428 	free(nodename);
    429 	return 1;
    430 }
    431 
    432 static u8*
    433 format_date(const u8* date_arg, const char* timestamp_format)
    434 {
    435 	u8* day			      = NULL;
    436 	u8* month		      = NULL;
    437 	u8* year		      = NULL;
    438 	u8* formatted_date	      = NULL;
    439 	u8* ptr			      = NULL;
    440 	u8* date		      = NULL;
    441 	const char* ptimestamp_format = NULL;
    442 	char* temp		      = NULL;
    443 	size_t formatted_date_len;
    444 
    445 	assert((date_arg != NULL) && (timestamp_format != NULL));
    446 	errno = 0;
    447 	date  = (u8*)strdup((const char*)date_arg);
    448 	if (!date)
    449 	{
    450 		perror(PROGRAMNAME ": strdup");
    451 		exit(error(errno, __FILE__, __func__, __LINE__,
    452 			"Allocation error"));
    453 	}
    454 	CALLOC(formatted_date, u8, DATEBUFSIZE);
    455 	ptr  = NULL;
    456 	year = (u8*)strtok_r((char*)date, "-", (char**)&ptr);
    457 	if (!year)
    458 		goto format_date_cleanup;
    459 	month = (u8*)strtok_r(NULL, "-", (char**)&ptr);
    460 	if (!month)
    461 		goto format_date_cleanup;
    462 	day = (u8*)strtok_r(NULL, "T", (char**)&ptr);
    463 	if (!day)
    464 		goto format_date_cleanup;
    465 	ptimestamp_format  = timestamp_format;
    466 	formatted_date_len = 0;
    467 	while (*ptimestamp_format)
    468 	{
    469 		if (*ptimestamp_format == 'd' || *ptimestamp_format == 'D')
    470 		{
    471 			MEMCCPY((formatted_date + formatted_date_len), day,
    472 				DATEBUFSIZE - formatted_date_len, temp);
    473 			formatted_date_len += strlen((char*)day);
    474 		}
    475 		else if (*ptimestamp_format == 'm' || *ptimestamp_format == 'M')
    476 		{
    477 			MEMCCPY((formatted_date + formatted_date_len), month,
    478 				DATEBUFSIZE - formatted_date_len, temp);
    479 			formatted_date_len += strlen((char*)month);
    480 		}
    481 		else if (*ptimestamp_format == 'y' || *ptimestamp_format == 'Y')
    482 		{
    483 			MEMCCPY((formatted_date + formatted_date_len), year,
    484 				DATEBUFSIZE - formatted_date_len, temp);
    485 			formatted_date_len += strlen((char*)year);
    486 		}
    487 		else
    488 			*(formatted_date + formatted_date_len++)
    489 				= *ptimestamp_format;
    490 		ptimestamp_format++;
    491 	}
    492 	return formatted_date;
    493 
    494 format_date_cleanup:
    495 	free(formatted_date);
    496 	free(date);
    497 	return NULL;
    498 }
    499 
    500 static void
    501 free_keyvalue(KeyValue** list, const size_t list_count)
    502 {
    503 	assert(list != NULL);
    504 	if (!*list)
    505 		return;
    506 	for (size_t index = 0; index < list_count; index++)
    507 	{
    508 		KeyValue* current = *list + index;
    509 		free(current->value);
    510 		free(current->key);
    511 	}
    512 	return;
    513 }
    514 
    515 static u8*
    516 get_value(KeyValue* list, const size_t list_count, const u8* key,
    517 	KeyValue** cursor)
    518 {
    519 	KeyValue* plist = list;
    520 	if (!list)
    521 		return NULL;
    522 	assert(key != NULL);
    523 	while (plist < list + list_count)
    524 	{
    525 		if (!strcmp((char*)plist->key, (char*)key))
    526 		{
    527 			if (cursor)
    528 			{
    529 				if (plist + 1 < list + list_count)
    530 					*cursor = plist + 1;
    531 				else
    532 					*cursor = NULL;
    533 			}
    534 			return plist->value;
    535 		}
    536 		plist++;
    537 	}
    538 	if (cursor)
    539 		*cursor = NULL;
    540 	return NULL;
    541 }
    542 
    543 #define PIPE_READ_INDEX	 0
    544 #define PIPE_WRITE_INDEX 1
    545 
    546 static int
    547 print_command(FILE* output, const char* command, const u8* pass_arguments[],
    548 	const u8* pipe_arguments[], const int strip_newlines)
    549 {
    550 	FILE* cmd_input	    = NULL;
    551 	FILE* cmd_output    = NULL;
    552 	u8* cmd_output_line = NULL;
    553 	char* eol	    = NULL;
    554 	pid_t pid	    = 0;
    555 	pid_t wpid;
    556 	int pstatus = 0;
    557 	int arg_pipe_fds[2];
    558 	int output_pipe_fds[2];
    559 
    560 	assert((output != NULL) && (command != NULL)
    561 		&& (pass_arguments != NULL));
    562 	fflush(output);
    563 	pipe(arg_pipe_fds);
    564 	pipe(output_pipe_fds);
    565 	errno = 0;
    566 	pid   = fork();
    567 	if (pid == 0)
    568 	{
    569 		close(arg_pipe_fds[PIPE_WRITE_INDEX]);
    570 		close(output_pipe_fds[PIPE_READ_INDEX]);
    571 
    572 		dup2(arg_pipe_fds[PIPE_READ_INDEX], STDIN_FILENO);
    573 		dup2(output_pipe_fds[PIPE_WRITE_INDEX], STDOUT_FILENO);
    574 
    575 		execvp(command, (char* const*)pass_arguments);
    576 		exit(1);
    577 	}
    578 	else if (pid < 0)
    579 	{
    580 		perror(PROGRAMNAME ": fork");
    581 		exit(error(errno, __FILE__, __func__, __LINE__, "fork failed"));
    582 	}
    583 
    584 	/* Parent */
    585 	close(arg_pipe_fds[PIPE_READ_INDEX]);
    586 	close(output_pipe_fds[PIPE_WRITE_INDEX]);
    587 	errno	  = 0;
    588 	cmd_input = fdopen(arg_pipe_fds[PIPE_WRITE_INDEX], "w");
    589 	if (cmd_input)
    590 		cmd_output = fdopen(output_pipe_fds[PIPE_READ_INDEX], "r");
    591 	if (!cmd_input || !cmd_output)
    592 	{
    593 		perror(PROGRAMNAME ": fdopen");
    594 		exit(error(errno, __FILE__, __func__, __LINE__,
    595 			"fdopen failed"));
    596 	}
    597 
    598 	if (pipe_arguments)
    599 	{
    600 		const u8** ppipe_argument = pipe_arguments;
    601 		while (ppipe_argument && *ppipe_argument)
    602 		{
    603 			fprintf(cmd_input, "%s\n", *ppipe_argument);
    604 			ppipe_argument++;
    605 		}
    606 	}
    607 	fclose(cmd_input);
    608 
    609 	CALLOC(cmd_output_line, u8, BUFSIZE);
    610 	while (!feof(cmd_output))
    611 	{
    612 		if (!fgets((char*)cmd_output_line, BUFSIZE, cmd_output))
    613 			continue;
    614 		eol = strchr((char*)cmd_output_line, '\n');
    615 		if (eol)
    616 			*eol = 0;
    617 		print_output(output, "%s%s", cmd_output_line,
    618 			strip_newlines ? "" : "\n");
    619 	}
    620 	if (!strip_newlines)
    621 		output_firstcol = 1;
    622 	free(cmd_output_line);
    623 	fclose(cmd_output);
    624 	kill(pid, SIGKILL);
    625 	wpid = waitpid(pid, &pstatus, 0);
    626 	if (wpid < 0)
    627 		warning(pstatus,
    628 			(u8*)"Child returned nonzero status, errno = %d",
    629 			errno);
    630 	if (WIFEXITED(pstatus))
    631 		return WEXITSTATUS(pstatus);
    632 	return wpid;
    633 }
    634 
    635 #define PRINTCSVIF(format, arg)                                           \
    636 	do                                                                \
    637 	{                                                                 \
    638 		if ((ALL(csv_state, ST_CS_COND_NONEMPTY)                  \
    639 			    && *csv_register[conditional_index - 1])      \
    640 			|| (ALL(csv_state, ST_CS_COND_EMPTY)              \
    641 				&& !*csv_register[conditional_index - 1]) \
    642 			|| !ANY(csv_state,                                \
    643 				ST_CS_COND_EMPTY | ST_CS_COND_NONEMPTY))  \
    644 		{                                                         \
    645 			fprintf(output, format, arg);                     \
    646 		}                                                         \
    647 	} while (0)
    648 
    649 static void
    650 print_csv_row(FILE* output, u8** csv_header, u8** csv_register)
    651 {
    652 	u8* pcsv_template		= csv_template;
    653 	unsigned char csv_state		= ST_CS_NONE;
    654 	unsigned char num		= 0;
    655 	unsigned char conditional_index = 0;
    656 	assert((output != NULL) && (csv_header != NULL)
    657 		&& (csv_register != NULL));
    658 csv_template_start:
    659 	if (!*pcsv_template)
    660 		return;
    661 	if (IN(csv_state, ST_CS_ESCAPE))
    662 	{
    663 		PRINTCSVIF("%c", *pcsv_template);
    664 		CLEAR(csv_state, ST_CS_ESCAPE);
    665 		pcsv_template++;
    666 		goto csv_template_start;
    667 	}
    668 
    669 	switch (*pcsv_template)
    670 	{
    671 	case '\\':
    672 		SET(csv_state, ST_CS_ESCAPE);
    673 		pcsv_template++;
    674 		break;
    675 	case '$':
    676 		if (IN(csv_state, ST_CS_REGISTER))
    677 		{
    678 			PRINTCSVIF("%c", '$');
    679 			CLEAR(csv_state, ST_CS_REGISTER);
    680 		}
    681 		else
    682 			SET(csv_state, ST_CS_REGISTER);
    683 		pcsv_template++;
    684 		break;
    685 	case '#':
    686 		if (IN(csv_state, ST_CS_HEADER))
    687 		{
    688 			error(ERR_BAD_HR_MARK, __FILE__, __func__, __LINE__,
    689 				"Invalid header register mark");
    690 			CLEAR(csv_state, ST_CS_REGISTER | ST_CS_HEADER);
    691 		}
    692 		else if (IN(csv_state, ST_CS_REGISTER))
    693 		{
    694 			CLEAR(csv_state, ST_CS_REGISTER);
    695 			SET(csv_state, ST_CS_HEADER);
    696 		}
    697 		else
    698 			PRINTCSVIF("%c", '#');
    699 		pcsv_template++;
    700 		break;
    701 	case '?':
    702 		if (IN(csv_state, ST_CS_COND))
    703 		{
    704 			error(ERR_BAD_CND_MARK, __FILE__, __func__, __LINE__,
    705 				"Invalid conditional mark");
    706 			CLEAR(csv_state, ST_CS_COND);
    707 		}
    708 		else if (IN(csv_state, ST_CS_REGISTER))
    709 		{
    710 			CLEAR(csv_state, ST_CS_REGISTER);
    711 			SET(csv_state, ST_CS_COND);
    712 		}
    713 		else
    714 			PRINTCSVIF("%c", '?');
    715 		pcsv_template++;
    716 		break;
    717 	case '!':
    718 		if (ALL(csv_state, ST_CS_COND | ST_CS_COND_NONEMPTY))
    719 		{
    720 			CLEAR(csv_state, ST_CS_COND | ST_CS_COND_NONEMPTY);
    721 			SET(csv_state, ST_CS_COND_EMPTY);
    722 		}
    723 		else if (IN(csv_state, ST_CS_COND))
    724 		{
    725 			error(ERR_BAD_EMPTY_CND, __FILE__, __func__, __LINE__,
    726 				"Empty conditional before/without nonempty "
    727 				"conditional");
    728 			CLEAR(csv_state, ST_CS_COND | ST_CS_COND_EMPTY);
    729 		}
    730 		else
    731 			PRINTCSVIF("%c", '!');
    732 		pcsv_template++;
    733 		break;
    734 	case '/':
    735 		if (ALL(csv_state, ST_CS_COND | ST_CS_COND_EMPTY))
    736 			CLEAR(csv_state, ST_CS_COND | ST_CS_COND_EMPTY);
    737 		else if (ALL(csv_state, ST_CS_COND))
    738 			CLEAR(csv_state, ST_CS_COND | ST_CS_COND_NONEMPTY);
    739 		else
    740 			PRINTCSVIF("%c", '/');
    741 		pcsv_template++;
    742 		break;
    743 	case '1':
    744 	case '2':
    745 	case '3':
    746 	case '4':
    747 	case '5':
    748 	case '6':
    749 	case '7':
    750 	case '8':
    751 	case '9':
    752 		if (IN(csv_state, ST_CS_COND))
    753 		{
    754 			conditional_index = *pcsv_template - '0';
    755 			CLEAR(csv_state, ST_CS_COND);
    756 			SET(csv_state, ST_CS_COND_NONEMPTY);
    757 		}
    758 		else if (IN(csv_state, ST_CS_HEADER))
    759 		{
    760 			num = *pcsv_template - '0';
    761 			PRINTCSVIF("%s", csv_header[num - 1]);
    762 			CLEAR(csv_state, ST_CS_HEADER);
    763 		}
    764 		else if (IN(csv_state, ST_CS_REGISTER))
    765 		{
    766 			num = *pcsv_template - '0';
    767 			PRINTCSVIF("%s", csv_register[num - 1]);
    768 			CLEAR(csv_state, ST_CS_REGISTER);
    769 		}
    770 		else
    771 			PRINTCSVIF("%c", *pcsv_template);
    772 		pcsv_template++;
    773 		break;
    774 	default:
    775 		if (IN(csv_state, ST_CS_HEADER))
    776 		{
    777 			error(ERR_BAD_HR_MARK, __FILE__, __func__, __LINE__,
    778 				"Invalid header register mark");
    779 			CLEAR(csv_state, ST_CS_HEADER);
    780 		}
    781 		else if (IN(csv_state, ST_CS_REGISTER))
    782 		{
    783 			error(ERR_BAD_REG_MARK, __FILE__, __func__, __LINE__,
    784 				"Invalid register mark");
    785 			CLEAR(csv_state, ST_CS_REGISTER);
    786 		}
    787 		else
    788 			PRINTCSVIF("%c", *pcsv_template);
    789 		pcsv_template++;
    790 	}
    791 	goto csv_template_start;
    792 }
    793 
    794 static int
    795 print_image_dimensions(FILE* output, const u8* image_file_prefix, const u8* path)
    796 {
    797 	struct stat sb;
    798 	FILE* cmd_output = NULL;
    799 	char command[BUFSIZE];
    800 	char cmd_output_line[BUFSIZE];
    801 	char image_path[BUFSIZE - strlen(CMD_IDENTIFY)];
    802 	char* eol = NULL;
    803 
    804 	assert((output != NULL) && (path != NULL) && (basedir != NULL));
    805 	if (!*path)
    806 		return 1;
    807 	snprintf(image_path, BUFSIZE - strlen(CMD_IDENTIFY) - 1, "%s%s%s",
    808 		image_file_prefix ? (char*)image_file_prefix : basedir,
    809 		*path == '/' ? "" : "/", path);
    810 	errno = 0;
    811 	if (stat(image_path, &sb) < 0)
    812 	{
    813 		perror(PROGRAMNAME ": stat");
    814 		return errno;
    815 	}
    816 	snprintf(command, BUFSIZE - 1, CMD_IDENTIFY, image_path);
    817 	cmd_output = popen(command, "r");
    818 	/* popen(3): "The popen() function does not reliably set errno." */
    819 	if (!cmd_output)
    820 		return error(ERR_POPEN_FAIL, __FILE__, __func__, __LINE__,
    821 			"popen failed");
    822 	if (!fgets(cmd_output_line, BUFSIZE, cmd_output))
    823 		goto print_dimensions_cleanup;
    824 
    825 	eol = strchr((char*)cmd_output_line, '\n');
    826 	if (eol)
    827 		*eol = 0;
    828 	print_output(output, "%s", cmd_output_line);
    829 	output_firstcol = 0;
    830 
    831 print_dimensions_cleanup:
    832 	pclose(cmd_output);
    833 	return 0;
    834 }
    835 
    836 static void
    837 print_meta_var(FILE* output, u8** tsv_header, u8** tsv_register)
    838 {
    839 	u8* pvalue;
    840 	u8* var_name = NULL;
    841 	u8* value    = NULL;
    842 	size_t value_len;
    843 	UNUSED(tsv_header);
    844 	assert((output != NULL) && (tsv_header != NULL)
    845 		&& (tsv_register != NULL));
    846 	if (!*tsv_register || !*(tsv_register + 1))
    847 		return;
    848 	pvalue	  = *(tsv_register + 1);
    849 	value_len = strlen((char*)pvalue);
    850 	if (*pvalue == '%' && *(pvalue + value_len - 1) == '%')
    851 	{
    852 		errno	 = 0;
    853 		var_name = (u8*)strdup((char*)pvalue + 1);
    854 		if (!var_name)
    855 		{
    856 			perror(PROGRAMNAME ": strdup");
    857 			exit(error(errno, __FILE__, __func__, __LINE__,
    858 				"Allocation failed"));
    859 		}
    860 		*(var_name + value_len - 2) = 0;
    861 		value = get_value(vars, vars_count, var_name, NULL);
    862 		if (value)
    863 			print_output(output,
    864 				"<meta name=\"%s\" content=\"%s\">\n",
    865 				*tsv_register, value);
    866 		free(var_name);
    867 	}
    868 	else
    869 		print_output(output, "<meta name=\"%s\" content=\"%s\">\n",
    870 			*tsv_register, *(tsv_register + 1));
    871 }
    872 
    873 #define PRINTTSVIF(format, arg)                                           \
    874 	do                                                                \
    875 	{                                                                 \
    876 		if ((ALL(tsv_state, ST_CS_COND_NONEMPTY)                  \
    877 			    && *tsv_register[conditional_index - 1])      \
    878 			|| (ALL(tsv_state, ST_CS_COND_EMPTY)              \
    879 				&& !*tsv_register[conditional_index - 1]) \
    880 			|| !ANY(tsv_state,                                \
    881 				ST_CS_COND_EMPTY | ST_CS_COND_NONEMPTY))  \
    882 			fprintf(output, format, arg);                     \
    883 	} while (0)
    884 
    885 static void
    886 print_tsv_row(FILE* output, u8** tsv_header, u8** tsv_register)
    887 {
    888 	u8* ptsv_template		= tsv_template;
    889 	unsigned char tsv_state		= ST_CS_NONE;
    890 	unsigned char num		= 0;
    891 	unsigned char conditional_index = 0;
    892 
    893 	assert((output != NULL) && (tsv_header != NULL)
    894 		&& (tsv_register != NULL));
    895 tsv_template_start:
    896 	if (!*ptsv_template)
    897 		return;
    898 	if (IN(tsv_state, ST_CS_ESCAPE))
    899 	{
    900 		PRINTTSVIF("%c", *ptsv_template);
    901 		CLEAR(tsv_state, ST_CS_ESCAPE);
    902 		ptsv_template++;
    903 		goto tsv_template_start;
    904 	}
    905 
    906 	switch (*ptsv_template)
    907 	{
    908 	case '\\':
    909 		SET(tsv_state, ST_CS_ESCAPE);
    910 		ptsv_template++;
    911 		break;
    912 	case '$':
    913 		if (IN(tsv_state, ST_CS_REGISTER))
    914 		{
    915 			PRINTTSVIF("%c", '$');
    916 			CLEAR(tsv_state, ST_CS_REGISTER);
    917 		}
    918 		else
    919 			SET(tsv_state, ST_CS_REGISTER);
    920 		ptsv_template++;
    921 		break;
    922 	case '#':
    923 		if (IN(tsv_state, ST_CS_HEADER))
    924 		{
    925 			error(ERR_BAD_HR_MARK, __FILE__, __func__, __LINE__,
    926 				"Invalid header register mark");
    927 			CLEAR(tsv_state, ST_CS_REGISTER | ST_CS_HEADER);
    928 		}
    929 		else if (IN(tsv_state, ST_CS_REGISTER))
    930 		{
    931 			CLEAR(tsv_state, ST_CS_REGISTER);
    932 			SET(tsv_state, ST_CS_HEADER);
    933 		}
    934 		else
    935 			PRINTTSVIF("%c", '#');
    936 		ptsv_template++;
    937 		break;
    938 	case '?':
    939 		if (IN(tsv_state, ST_CS_COND))
    940 		{
    941 			error(ERR_BAD_CND_MARK, __FILE__, __func__, __LINE__,
    942 				"Invalid conditional mark");
    943 			CLEAR(tsv_state, ST_CS_COND);
    944 		}
    945 		else if (IN(tsv_state, ST_CS_REGISTER))
    946 		{
    947 			CLEAR(tsv_state, ST_CS_REGISTER);
    948 			SET(tsv_state, ST_CS_COND);
    949 		}
    950 		else
    951 			PRINTTSVIF("%c", '?');
    952 		ptsv_template++;
    953 		break;
    954 	case '!':
    955 		if (ALL(tsv_state, ST_CS_COND | ST_CS_COND_NONEMPTY))
    956 		{
    957 			CLEAR(tsv_state, ST_CS_COND | ST_CS_COND_NONEMPTY);
    958 			SET(tsv_state, ST_CS_COND_EMPTY);
    959 		}
    960 		else if (IN(tsv_state, ST_CS_COND))
    961 		{
    962 			error(ERR_BAD_EMPTY_CND, __FILE__, __func__, __LINE__,
    963 				"Empty conditional before/without nonempty "
    964 				"conditional");
    965 			CLEAR(tsv_state, ST_CS_COND | ST_CS_COND_EMPTY);
    966 		}
    967 		else
    968 			PRINTTSVIF("%c", '!');
    969 		ptsv_template++;
    970 		break;
    971 	case '/':
    972 		if (ALL(tsv_state, ST_CS_COND | ST_CS_COND_EMPTY))
    973 			CLEAR(tsv_state, ST_CS_COND | ST_CS_COND_EMPTY);
    974 		else if (ALL(tsv_state, ST_CS_COND))
    975 			CLEAR(tsv_state, ST_CS_COND | ST_CS_COND_NONEMPTY);
    976 		else
    977 			PRINTTSVIF("%c", '/');
    978 		ptsv_template++;
    979 		break;
    980 	case '1':
    981 	case '2':
    982 	case '3':
    983 	case '4':
    984 	case '5':
    985 	case '6':
    986 	case '7':
    987 	case '8':
    988 	case '9':
    989 		if (IN(tsv_state, ST_CS_COND))
    990 		{
    991 			conditional_index = *ptsv_template - '0';
    992 			CLEAR(tsv_state, ST_CS_COND);
    993 			SET(tsv_state, ST_CS_COND_NONEMPTY);
    994 		}
    995 		else if (IN(tsv_state, ST_CS_HEADER))
    996 		{
    997 			num = *ptsv_template - '0';
    998 			PRINTTSVIF("%s", tsv_header[num - 1]);
    999 			CLEAR(tsv_state, ST_CS_HEADER);
   1000 		}
   1001 		else if (IN(tsv_state, ST_CS_REGISTER))
   1002 		{
   1003 			num = *ptsv_template - '0';
   1004 			PRINTTSVIF("%s", tsv_register[num - 1]);
   1005 			CLEAR(tsv_state, ST_CS_REGISTER);
   1006 		}
   1007 		else
   1008 			PRINTTSVIF("%c", *ptsv_template);
   1009 		ptsv_template++;
   1010 		break;
   1011 	default:
   1012 		if (IN(tsv_state, ST_CS_HEADER))
   1013 		{
   1014 			error(ERR_BAD_HR_MARK, __FILE__, __func__, __LINE__,
   1015 				"Invalid header register mark");
   1016 			CLEAR(tsv_state, ST_CS_HEADER);
   1017 		}
   1018 		else if (IN(tsv_state, ST_CS_REGISTER))
   1019 		{
   1020 			error(ERR_BAD_REG_MARK, __FILE__, __func__, __LINE__,
   1021 				"Invalid register mark");
   1022 			CLEAR(tsv_state, ST_CS_REGISTER);
   1023 		}
   1024 		else
   1025 			PRINTTSVIF("%c", *ptsv_template);
   1026 		ptsv_template++;
   1027 	}
   1028 	goto tsv_template_start;
   1029 }
   1030 
   1031 static int
   1032 print_output(FILE* output, const char* fmt, ...)
   1033 {
   1034 	u8* var_incdir_only_summary = NULL;
   1035 	u8* temp		    = NULL;
   1036 	char buf[BUFSIZE];
   1037 	va_list args;
   1038 	int incdir_only_summary = 0;
   1039 
   1040 	assert((output != NULL) && (fmt != NULL));
   1041 	va_start(args, fmt);
   1042 	var_incdir_only_summary
   1043 		= get_value(vars, vars_count, (u8*)"incdir-only-summary", NULL);
   1044 	incdir_only_summary
   1045 		= var_incdir_only_summary && (*var_incdir_only_summary == '1');
   1046 	if (IN(state, ST_INCDIR) && !IN(state, ST_SUMMARY)
   1047 		&& incdir_only_summary)
   1048 		return 0;
   1049 	else if (IN(state, ST_CSV_BODY))
   1050 	{
   1051 		size_t buf_len = 0;
   1052 		vsnprintf((char*)buf, BUFSIZE, fmt, args);
   1053 		buf_len = strlen(buf);
   1054 		if (!csv_template)
   1055 		{
   1056 			csv_template_size = BUFSIZE;
   1057 			CALLOC(csv_template, u8, csv_template_size);
   1058 			MEMCCPY(csv_template, buf, csv_template_size, temp);
   1059 		}
   1060 		else
   1061 		{
   1062 			csv_template_len = strlen((char*)csv_template);
   1063 			ENSURE_SIZE(csv_template, temp, csv_template_size,
   1064 				csv_template_len + buf_len + 1,
   1065 				csv_template_len + buf_len + 1,
   1066 				print_output_alloc_error, u8);
   1067 			MEMCCPY((csv_template + csv_template_len), buf,
   1068 				csv_template_size - csv_template_len, temp);
   1069 		}
   1070 	}
   1071 	else if (IN(state, ST_TSV_BODY))
   1072 	{
   1073 		size_t buf_len = 0;
   1074 		vsnprintf((char*)buf, BUFSIZE, fmt, args);
   1075 		buf_len = strlen(buf);
   1076 		if (!tsv_template)
   1077 		{
   1078 			tsv_template_size = BUFSIZE;
   1079 			CALLOC(tsv_template, u8, tsv_template_size);
   1080 			MEMCCPY(tsv_template, buf, tsv_template_size, temp);
   1081 		}
   1082 		else
   1083 		{
   1084 			tsv_template_len = strlen((char*)tsv_template);
   1085 			ENSURE_SIZE(tsv_template, temp, tsv_template_size,
   1086 				tsv_template_len + buf_len + 1,
   1087 				tsv_template_len + buf_len + 1,
   1088 				print_output_alloc_error, u8);
   1089 			MEMCCPY((tsv_template + tsv_template_len), buf,
   1090 				tsv_template_size - tsv_template_len, temp);
   1091 		}
   1092 	}
   1093 	else
   1094 		vfprintf(output, fmt, args);
   1095 	va_end(args);
   1096 	return 0;
   1097 print_output_alloc_error:
   1098 	exit(error(errno, __FILE__, __func__, __LINE__, "Allocation error"));
   1099 	return 1;
   1100 }
   1101 
   1102 static int
   1103 process_blockquote(FILE* output, const int end_tag)
   1104 {
   1105 	print_output(output, "<%sblockquote>", end_tag ? "/" : "");
   1106 	return 0;
   1107 }
   1108 
   1109 static int
   1110 process_bold(FILE* output, const int end_tag)
   1111 {
   1112 	print_output(output, "<%sstrong>", end_tag ? "/" : "");
   1113 	return 0;
   1114 }
   1115 
   1116 static int
   1117 process_code(FILE* output, const int end_tag)
   1118 {
   1119 	print_output(output, "<%scode>", end_tag ? "/" : "");
   1120 	return 0;
   1121 }
   1122 
   1123 static int
   1124 process_csv(FILE* output, const u8* arg_token,
   1125 	const int read_yaml_macros_and_links, const int end_tag)
   1126 {
   1127 	u8* saveptr   = NULL;
   1128 	u8* args      = NULL;
   1129 	u8* args_base = NULL;
   1130 	size_t args_len;
   1131 	assert((output != NULL) && (arg_token != NULL));
   1132 	if (end_tag)
   1133 	{
   1134 		CLEAR(state, ST_CSV_BODY);
   1135 		if (read_yaml_macros_and_links)
   1136 			return 0;
   1137 		read_csv(output, csv_filename, &print_csv_row);
   1138 		free(csv_filename);
   1139 		csv_filename = NULL;
   1140 		free(csv_template);
   1141 		csv_template	  = NULL;
   1142 		csv_template_size = 0;
   1143 	}
   1144 	else
   1145 	{
   1146 		if (ANY(state, ST_CSV_BODY | ST_TSV_BODY))
   1147 			exit(error(ERR_NEST_CSV, __FILE__, __func__, __LINE__,
   1148 				"Can't nest csv/tsv directives"));
   1149 		SET(state, ST_CSV_BODY);
   1150 		if (read_yaml_macros_and_links)
   1151 			return 0;
   1152 		csv_iter = 0;
   1153 		(void)strtok_r((char*)arg_token, " ", (char**)&saveptr);
   1154 		args = (u8*)strtok_r(NULL, " ", (char**)&saveptr);
   1155 		if (!args)
   1156 			exit(error(ERR_NO_ARG, __FILE__, __func__, __LINE__,
   1157 				"Arguments required"));
   1158 		args_len = strlen((char*)args);
   1159 		if (*args != '"' || *(args + args_len - 1) != '"')
   1160 			exit(error(ERR_ARG_NOTSTR, __FILE__, __func__, __LINE__,
   1161 				"First argument must be a string"));
   1162 		if (!csv_filename)
   1163 			CALLOC(csv_filename, char, BUFSIZE);
   1164 		args_base = (u8*)strdup((char*)args + 1);
   1165 		*(args_base + strlen((char*)args_base) - 1) = 0;
   1166 		snprintf(csv_filename, BUFSIZE, "%s/%s.csv", input_dirname,
   1167 			(char*)args_base);
   1168 		free(args_base);
   1169 		args = (u8*)strtok_r(NULL, " ", (char**)&saveptr);
   1170 		if (args)
   1171 		{
   1172 			errno	 = 0;
   1173 			csv_iter = (size_t)strtol((char*)args, NULL, 10);
   1174 			if (errno)
   1175 			{
   1176 				perror(PROGRAMNAME ": strtol");
   1177 				exit(error(errno, __FILE__, __func__, __LINE__,
   1178 					"Invalid argument '%s'", args));
   1179 			}
   1180 		}
   1181 	}
   1182 	return 0;
   1183 }
   1184 
   1185 static int
   1186 process_footnote(FILE* output, const u8* token, const int footnote_definition,
   1187 	const int footnote_output)
   1188 {
   1189 	char* temp = NULL;
   1190 
   1191 	assert(token != NULL);
   1192 	current_footnote++;
   1193 	if (footnote_definition)
   1194 	{
   1195 		footnote_count++;
   1196 		if (footnote_count == 1 && inline_footnote_count > 0)
   1197 			warning(1,
   1198 				(u8*)"Both inline and regular footnotes "
   1199 				     "present");
   1200 		else if (!footnotes)
   1201 		{
   1202 			footnote_count = 1;
   1203 			CALLOC(footnotes, KeyValue, footnote_count);
   1204 			pfootnotes = footnotes;
   1205 		}
   1206 		else
   1207 		{
   1208 			REALLOCARRAY(footnotes, KeyValue, footnote_count);
   1209 			pfootnotes = footnotes + footnote_count - 1;
   1210 		}
   1211 		CALLOC(pfootnotes->key, u8, KEYSIZE);
   1212 		MEMCCPY(pfootnotes->key, (char*)token, KEYSIZE, temp);
   1213 		pfootnotes->value      = NULL;
   1214 		pfootnotes->value_size = 0;
   1215 	}
   1216 	if (footnote_output)
   1217 	{
   1218 		print_output(output,
   1219 			"<a href=\"#footnote-%d\" id=\"footnote-text-%d\">"
   1220 			"<sup>%d</sup></a>",
   1221 			current_footnote, current_footnote, current_footnote);
   1222 		output_firstcol = 0;
   1223 	}
   1224 	return 0;
   1225 }
   1226 
   1227 static int
   1228 process_formula(FILE* output, const u8* token, const int display_formula)
   1229 {
   1230 	const u8* pipe_args[] = {token, NULL};
   1231 	int result	      = 0;
   1232 
   1233 	assert(token != NULL);
   1234 	result = print_command(output, CMD_KATEX,
   1235 		display_formula ? (const u8**)CMD_KATEX_DISPLAY_ARGS
   1236 				: (const u8**)CMD_KATEX_INLINE_ARGS,
   1237 		(const u8**)pipe_args, 1);
   1238 	if (result)
   1239 	{
   1240 		print_output(output, "%s$%s$%s", display_formula ? "$" : "",
   1241 			token, display_formula ? "$" : "");
   1242 		output_firstcol = 0;
   1243 	}
   1244 	return result;
   1245 }
   1246 
   1247 static int
   1248 process_git_log(FILE* output)
   1249 {
   1250 	u8* pipe_args[]	     = {NULL, NULL};
   1251 	char* basename	     = NULL;
   1252 	char* temp	     = NULL;
   1253 	char* slash	     = NULL;
   1254 	size_t basename_size = BUFSIZE;
   1255 	pid_t result	     = 0;
   1256 
   1257 	assert(output != NULL);
   1258 	if (!input_filename)
   1259 		return warning(1, (u8*)"Cannot use 'git-log' in stdin");
   1260 
   1261 	slash = strrchr(input_filename, '/');
   1262 	CALLOC(basename, char, basename_size);
   1263 	if (slash)
   1264 		MEMCCPY(basename, slash + 1, basename_size, temp);
   1265 	else
   1266 		MEMCCPY(basename, input_filename, basename_size, temp);
   1267 	pipe_args[0] = (u8*)basename;
   1268 	print_output(output, "<div id=\"git-log\">\nPrevious commit:\n");
   1269 	result = print_command(output, CMD_GIT_LOG,
   1270 		(const u8**)CMD_GIT_LOG_ARGS, (const u8**)pipe_args, 0);
   1271 	print_output(output, "</div><!--git-log-->\n");
   1272 	output_firstcol = 1;
   1273 	free(basename);
   1274 	return result ? warning(result, (u8*)"git-log: Cannot run git") : 0;
   1275 }
   1276 
   1277 static int
   1278 process_heading(FILE* output, const u8* token, const unsigned char heading_level)
   1279 {
   1280 	assert((output != NULL) && (token != NULL));
   1281 	if (!*token)
   1282 		warning(1, (u8*)"Empty heading");
   1283 	print_output(output, "%s</h%hhu>", (char*)token, heading_level);
   1284 	return 0;
   1285 }
   1286 
   1287 static int
   1288 process_heading_start(FILE* output, const unsigned char heading_level,
   1289 	const size_t heading_count)
   1290 {
   1291 	print_output(output, "<h%hhu id=\"heading-%zu\">", heading_level,
   1292 		heading_count);
   1293 	return 0;
   1294 }
   1295 
   1296 static void
   1297 process_horizontal_rule(FILE* output)
   1298 {
   1299 	print_output(output, "<hr>\n");
   1300 	if (IN(state, ST_PARA_OPEN))
   1301 		print_output(output, "<p>\n");
   1302 	output_firstcol = 1;
   1303 }
   1304 
   1305 static void
   1306 process_image(FILE* output, const u8* image_text, const u8* image_file_prefix,
   1307 	const u8* link_prefix, const u8* image_id, const int add_link,
   1308 	const int add_figcaption)
   1309 {
   1310 	u8* url = get_value(links, links_count, image_id, NULL);
   1311 	process_inline_image(output, image_text, image_file_prefix, link_prefix,
   1312 		url, add_link, add_figcaption);
   1313 }
   1314 
   1315 static int
   1316 process_incdir(FILE* output, const u8* token, const u8* link_prefix,
   1317 	const u8* footer_permalink_text, const char* permalink_url,
   1318 	const int ext_in_permalink, const int read_yaml_macros_and_links)
   1319 {
   1320 	struct dirent** namelist;
   1321 	struct dirent** pnamelist;
   1322 	u8* saveptr = NULL;
   1323 	/* skipping the first token (incdir) */
   1324 	u8* arg	       = NULL;
   1325 	u8* macro_body = NULL;
   1326 	u8* parg       = NULL;
   1327 	size_t arg_len = 0;
   1328 	int names_output;
   1329 	int num		 = 5;
   1330 	int names_total	 = 0;
   1331 	int details_open = 1;
   1332 	int list_only	 = 0;
   1333 	int pass;
   1334 
   1335 	assert((output != NULL) && (token != NULL));
   1336 	if (read_yaml_macros_and_links)
   1337 		return 0;
   1338 	(void)strtok_r((char*)token, " ", (char**)&saveptr);
   1339 	arg = (u8*)strtok_r(NULL, " ", (char**)&saveptr);
   1340 	if (!arg)
   1341 		exit(error(ERR_NO_ARG, __FILE__, __func__, __LINE__,
   1342 			"Arguments required"));
   1343 	arg_len = strlen((char*)arg);
   1344 	if (*arg != '"' || *(arg + arg_len - 1) != '"')
   1345 		exit(error(ERR_ARG_NOTSTR, __FILE__, __func__, __LINE__,
   1346 			"First argument not string"));
   1347 
   1348 	incdir			       = strdup((char*)(arg + 1));
   1349 	*(incdir + strlen(incdir) - 1) = 0; /* trim ending " */
   1350 
   1351 	pass = 0;
   1352 incdir_next_arg:
   1353 	arg = (u8*)strtok_r(NULL, " ", (char**)&saveptr);
   1354 	if (!arg)
   1355 		goto incdir_done_args;
   1356 	parg = arg;
   1357 	if (*parg == '=')
   1358 		macro_body = get_value(macros, macros_count, arg + 1, NULL);
   1359 	else if (!strcmp((const char*)arg, "listonly"))
   1360 		list_only = 1;
   1361 	else
   1362 	{
   1363 		while (parg && *parg)
   1364 		{
   1365 			if (*parg < '0' || *parg > '9')
   1366 				exit(error(ERR_ARG_NOTNUM, __FILE__, __func__,
   1367 					__LINE__, "Argument not a number"));
   1368 			parg++;
   1369 		}
   1370 		errno = 0;
   1371 		num   = strtol((char*)arg, NULL, 10);
   1372 		if (errno)
   1373 		{
   1374 			perror(PROGRAMNAME ": strtol");
   1375 			exit(error(errno, __FILE__, __func__, __LINE__,
   1376 				"Invalid parameter 'num'"));
   1377 		}
   1378 	}
   1379 	pass++;
   1380 	if (pass < 3)
   1381 		goto incdir_next_arg;
   1382 
   1383 incdir_done_args:
   1384 	print_output(output, "<ul class=\"incdir\">\n");
   1385 	/* scandir(3) does not set errno */
   1386 	if ((names_total = scandir(incdir, &namelist, &filter_subdirs,
   1387 		     &reverse_alphacompare))
   1388 		< 0)
   1389 		exit(error(ERR_SCANDIR_FAIL, __FILE__, __func__, __LINE__,
   1390 			"scandir '%s' failed", incdir));
   1391 	pnamelist    = namelist;
   1392 	names_output = 0;
   1393 	while (names_output < MIN(names_total, num))
   1394 	{
   1395 		/*if (list_only)
   1396 			print_output(output, "<li>%s\n",
   1397 			(*pnamelist)->d_name);*/
   1398 		process_incdir_subdir(output, (*pnamelist)->d_name, link_prefix,
   1399 			details_open, macro_body, footer_permalink_text,
   1400 			permalink_url, ext_in_permalink, list_only);
   1401 		details_open = 0;
   1402 		pnamelist++;
   1403 		names_output++;
   1404 		/*if (list_only)
   1405 			print_output(output, "</li>\n");*/
   1406 	}
   1407 	while (names_total--)
   1408 		free(namelist[names_total]);
   1409 	free(namelist);
   1410 	free(incdir);
   1411 	print_output(output, "</ul>\n");
   1412 	output_firstcol = 1;
   1413 	return 0;
   1414 }
   1415 
   1416 static void
   1417 process_incdir_subdir(FILE* output, const char* subdirname,
   1418 	const u8* link_prefix, const int details_open, const u8* macro_body,
   1419 	const u8* footer_permalink_text, const char* permalink_url,
   1420 	const int ext_in_permalink, const int list_only)
   1421 {
   1422 	struct dirent** namelist;
   1423 	struct dirent** pnamelist;
   1424 	FILE* input		       = NULL;
   1425 	u8* buffer		       = NULL;
   1426 	u8* date		       = NULL;
   1427 	u8* formatted_date	       = NULL;
   1428 	u8* title		       = NULL;
   1429 	u8* var_incdir_only_summary    = NULL;
   1430 	char* abs_subdirname	       = NULL;
   1431 	char* temp		       = NULL;
   1432 	char* filename		       = NULL;
   1433 	char* link		       = NULL;
   1434 	unsigned long long saved_state = ST_NONE;
   1435 	size_t buffer_size	       = 0;
   1436 	size_t link_len;
   1437 	pid_t pid;
   1438 	int names_output;
   1439 	int names_total		= 0;
   1440 	int pstatus		= 0;
   1441 	int incdir_only_summary = 0;
   1442 	int result		= 0;
   1443 
   1444 	UNUSED(link_prefix);
   1445 	assert((output != NULL) && (subdirname != NULL));
   1446 
   1447 	if (list_only)
   1448 		; /*print_output(output, "<ul>\n");*/
   1449 	else
   1450 		print_output(output, "<li>\n<details%s>\n<summary>",
   1451 			details_open ? " open" : "");
   1452 	if (macro_body)
   1453 		print_output(output, "%s", macro_body);
   1454 	if (!list_only)
   1455 		print_output(output, "%s</summary>\n<div>\n", subdirname);
   1456 
   1457 	CALLOC(abs_subdirname, char, BUFSIZE);
   1458 	snprintf(abs_subdirname, BUFSIZE, "%s/%s/%s", basedir, incdir,
   1459 		subdirname);
   1460 
   1461 	if ((names_total = scandir(abs_subdirname, &namelist, &filter_slw,
   1462 		     &reverse_alphacompare))
   1463 		< 0)
   1464 	{
   1465 		exit(error(ERR_SCANDIR_FAIL, __FILE__, __func__, __LINE__,
   1466 			"scandir '%s' failed", abs_subdirname));
   1467 	}
   1468 
   1469 	pnamelist    = namelist;
   1470 	names_output = 0;
   1471 	while (names_output < names_total)
   1472 	{
   1473 		pstatus = 0;
   1474 		if (!list_only)
   1475 			print_output(output, "<article>\n");
   1476 		fflush(output);
   1477 		errno = 0;
   1478 		pid   = fork();
   1479 		if (pid > 0)
   1480 			wait(&pstatus);
   1481 		else if (pid == 0)
   1482 		{
   1483 			output = stdout;
   1484 			if (!strcmp(basedir, "."))
   1485 				set_basedir(&basedir, &basedir_size,
   1486 					abs_subdirname);
   1487 			CALLOC(filename, char, BUFSIZE);
   1488 			snprintf(filename, BUFSIZE, "%s/%s", abs_subdirname,
   1489 				(*pnamelist)->d_name);
   1490 			link = strip_ext(filename, BUFSIZE);
   1491 
   1492 			read_file_into_buffer(&input, &buffer, &buffer_size,
   1493 				filename, &input_dirname);
   1494 
   1495 			free_keyvalue(&links, links_count);
   1496 			free(links);
   1497 			links	    = NULL;
   1498 			links_count = 0;
   1499 
   1500 			free_keyvalue(&footnotes, footnote_count);
   1501 			free(footnotes);
   1502 			footnotes	 = NULL;
   1503 			footnote_count	 = 0;
   1504 			current_footnote = 0;
   1505 			while (inline_footnote_count--)
   1506 				free(inline_footnotes[inline_footnote_count]);
   1507 			free(inline_footnotes);
   1508 			inline_footnotes	= NULL;
   1509 			inline_footnote_count	= 0;
   1510 			current_inline_footnote = 0;
   1511 
   1512 			saved_state = state;
   1513 			state	    = ST_INCDIR;
   1514 
   1515 			/* First pass: read YAML, macros and links */
   1516 			result = slweb_parse(output, filename, BUFSIZE, buffer,
   1517 				1, 1);
   1518 			if (result)
   1519 			{
   1520 				warning(1,
   1521 					(u8*)"process_incdir_subdir: "
   1522 					     "slweb_parse error");
   1523 				goto incdir_subdir_child_cleanup;
   1524 			}
   1525 
   1526 			title = get_value(vars, vars_count, (u8*)"title", NULL);
   1527 			date  = get_value(vars, vars_count, (u8*)"date", NULL);
   1528 			formatted_date
   1529 				= format_date(date, list_timestamp_format);
   1530 			if (!formatted_date)
   1531 				exit(error(ERR_FORMAT_DATE_FAIL, __FILE__,
   1532 					__func__, __LINE__,
   1533 					"format_date error"));
   1534 
   1535 			/* Have to temporarily reset this because of potential
   1536 			 * interaction with print_output */
   1537 			var_incdir_only_summary = get_value(vars, vars_count,
   1538 				(u8*)"incdir-only-summary", NULL);
   1539 			incdir_only_summary	= var_incdir_only_summary
   1540 				&& *var_incdir_only_summary == '1';
   1541 			if (list_only && var_incdir_only_summary)
   1542 				var_incdir_only_summary[0] = '0';
   1543 
   1544 			if (list_only)
   1545 			{
   1546 				print_output(output,
   1547 					"<li>%s <a href=\"%s\">%s</a></li>\n",
   1548 					formatted_date,
   1549 					permalink_url ? permalink_url : link,
   1550 					title);
   1551 				fflush(output);
   1552 				/* ... and fallthrough to cleanup */
   1553 			}
   1554 			else if (!result)
   1555 			{
   1556 				if (incdir_only_summary
   1557 					&& var_incdir_only_summary)
   1558 					var_incdir_only_summary[0] = '1';
   1559 				goto skip_incdir_subdir_child_cleanup;
   1560 			}
   1561 
   1562 		incdir_subdir_child_cleanup:
   1563 			cleanup();
   1564 			free(formatted_date);
   1565 			free(link);
   1566 			free(filename);
   1567 			free(buffer);
   1568 			free(abs_subdirname);
   1569 			while (names_total--)
   1570 				free(namelist[names_total]);
   1571 			free(namelist);
   1572 			exit(result);
   1573 
   1574 		skip_incdir_subdir_child_cleanup:
   1575 			state			= ST_INCDIR;
   1576 			current_footnote	= 0;
   1577 			current_inline_footnote = 0;
   1578 
   1579 			/* Second pass: parse and output */
   1580 			result = slweb_parse(output, filename, BUFSIZE, buffer,
   1581 				1, 0);
   1582 
   1583 			state = saved_state;
   1584 
   1585 			if (footer_permalink_text && incdir_only_summary)
   1586 			{
   1587 				if (ext_in_permalink)
   1588 				{
   1589 					link_len = strlen(link);
   1590 					MEMCCPY((link + link_len),
   1591 						timestamp_output_ext,
   1592 						BUFSIZE - link_len, temp);
   1593 				}
   1594 
   1595 				/* TODO: Get rid of orphan <p> just before
   1596 				 * <footer>; would probably need another flag
   1597 				 * and another layer of code, so maybe not
   1598 				 */
   1599 				print_output(output,
   1600 					"<footer>\n"
   1601 					"<p><a href=\"%s\">%s</a>\n"
   1602 					"</footer>\n",
   1603 					permalink_url ? permalink_url : link,
   1604 					footer_permalink_text);
   1605 			}
   1606 
   1607 			fflush(output);
   1608 			cleanup();
   1609 			free(formatted_date);
   1610 			free(link);
   1611 			free(filename);
   1612 			free(buffer);
   1613 			free(abs_subdirname);
   1614 			while (names_total--)
   1615 				free(namelist[names_total]);
   1616 			free(namelist);
   1617 			exit(result);
   1618 		}
   1619 		else
   1620 		{
   1621 			perror(PROGRAMNAME ": fork");
   1622 			exit(error(errno, __FILE__, __func__, __LINE__,
   1623 				"fork failed"));
   1624 		}
   1625 
   1626 		if (!list_only)
   1627 			print_output(output, "</article>\n");
   1628 
   1629 		pnamelist++;
   1630 		names_output++;
   1631 	}
   1632 
   1633 	while (names_total--)
   1634 		free(namelist[names_total]);
   1635 	free(namelist);
   1636 	free(abs_subdirname);
   1637 
   1638 	if (list_only)
   1639 		; /*print_output(output, "</ul>\n");*/
   1640 	else
   1641 		print_output(output, "</div>\n</details>\n</li>\n");
   1642 
   1643 	output_firstcol = 0;
   1644 }
   1645 
   1646 static void
   1647 process_include(FILE* output, const u8* token,
   1648 	const int read_yaml_macros_and_links)
   1649 {
   1650 	FILE* child_output    = NULL;
   1651 	FILE* input	      = NULL;
   1652 	u8* child_output_line = NULL;
   1653 	u8* ptoken;
   1654 	u8* buffer		= NULL;
   1655 	char* include_filename	= NULL;
   1656 	char* pinclude_filename = NULL;
   1657 	char* eol		= NULL;
   1658 	char* filename		= NULL;
   1659 	int arg_pipe_fds[2];
   1660 	int output_pipe_fds[2];
   1661 	size_t buffer_size = 0;
   1662 	pid_t pid	   = 0;
   1663 	int pstatus	   = 0;
   1664 	int result	   = 0;
   1665 
   1666 	assert((output != NULL) && (token != NULL));
   1667 	ptoken = (u8*)strchr((char*)token, ' ');
   1668 	if (!input_filename)
   1669 	{
   1670 		warning(1,
   1671 			(u8*)"process_include: Cannot use 'include' in stdin");
   1672 		return;
   1673 	}
   1674 	if (!ptoken)
   1675 		exit(error(ERR_NO_ARG, __FILE__, __func__, __LINE__,
   1676 			"Directive 'include' requires an argument"));
   1677 	fflush(output);
   1678 	pipe(arg_pipe_fds);
   1679 	pipe(output_pipe_fds);
   1680 	errno = 0;
   1681 	pid   = fork();
   1682 	if (pid > 0) /* parent */
   1683 	{
   1684 		child_output	  = NULL;
   1685 		child_output_line = NULL;
   1686 		close(arg_pipe_fds[PIPE_READ_INDEX]);
   1687 		close(output_pipe_fds[PIPE_WRITE_INDEX]);
   1688 		errno = 0;
   1689 		if (!(child_output
   1690 			    = fdopen(output_pipe_fds[PIPE_READ_INDEX], "r")))
   1691 		{
   1692 			int save_errno = errno;
   1693 			perror(PROGRAMNAME ": fdopen");
   1694 			wait(&pstatus);
   1695 			exit(error(save_errno, __FILE__, __func__, __LINE__,
   1696 				"fdopen failed"));
   1697 		}
   1698 		CALLOC(child_output_line, u8, BUFSIZE);
   1699 		while (!feof(child_output))
   1700 		{
   1701 			eol = NULL;
   1702 			if (!fgets((char*)child_output_line, BUFSIZE,
   1703 				    child_output))
   1704 				continue;
   1705 			eol = strchr((char*)child_output_line, '\n');
   1706 			if (eol)
   1707 				*eol = 0;
   1708 			simple_parse_yaml_line(child_output_line, &vars,
   1709 				&vars_count, &pvars);
   1710 		}
   1711 		free(child_output_line);
   1712 		fclose(child_output);
   1713 		wait(&pstatus);
   1714 	}
   1715 	else if (pid == 0) /* child */
   1716 	{
   1717 		close(arg_pipe_fds[PIPE_WRITE_INDEX]);
   1718 		close(output_pipe_fds[PIPE_READ_INDEX]);
   1719 		errno = 0;
   1720 		if (!(child_output
   1721 			    = fdopen(output_pipe_fds[PIPE_WRITE_INDEX], "w")))
   1722 		{
   1723 			perror(PROGRAMNAME ": fdopen");
   1724 			exit(error(errno, __FILE__, __func__, __LINE__,
   1725 				"fdopen failed"));
   1726 		}
   1727 		CALLOC(include_filename, char, BUFSIZE);
   1728 		pinclude_filename = include_filename;
   1729 		ptoken++;
   1730 		while (ptoken && *ptoken)
   1731 			if (*ptoken != '"')
   1732 				*pinclude_filename++ = *ptoken++;
   1733 			else
   1734 				ptoken++;
   1735 		if (!strcmp(basedir, "."))
   1736 			set_basedir(&basedir, &basedir_size, input_dirname);
   1737 		CALLOC(filename, char, BUFSIZE);
   1738 		snprintf(filename, BUFSIZE, "%s/%s.slw", basedir,
   1739 			include_filename);
   1740 		free(include_filename);
   1741 
   1742 		result = read_file_into_buffer(&input, &buffer, &buffer_size,
   1743 			filename, &input_dirname);
   1744 		if (result)
   1745 			exit(result);
   1746 
   1747 		free_keyvalue(&links, links_count);
   1748 		free(links);
   1749 		links	    = NULL;
   1750 		links_count = 0;
   1751 
   1752 		free_keyvalue(&footnotes, footnote_count);
   1753 		free(footnotes);
   1754 		footnotes	 = NULL;
   1755 		footnote_count	 = 0;
   1756 		current_footnote = 0;
   1757 		while (inline_footnote_count--)
   1758 			free(inline_footnotes[inline_footnote_count]);
   1759 		free(inline_footnotes);
   1760 		inline_footnotes	= NULL;
   1761 		inline_footnote_count	= 0;
   1762 		current_inline_footnote = 0;
   1763 		state			= ST_NONE;
   1764 
   1765 		/* First pass: read YAML, macros and links */
   1766 		result = slweb_parse(output, filename, BUFSIZE, buffer, 1, 1);
   1767 
   1768 		add_css(child_output, 1);
   1769 		if (result || read_yaml_macros_and_links)
   1770 			goto process_include_child_cleanup;
   1771 
   1772 		state			= ST_NONE;
   1773 		current_footnote	= 0;
   1774 		current_inline_footnote = 0;
   1775 
   1776 		/* Second pass: parse and output */
   1777 		result = slweb_parse(output, filename, BUFSIZE, buffer, 1, 0);
   1778 
   1779 	process_include_child_cleanup:
   1780 		fflush(output);
   1781 		cleanup();
   1782 		free(filename);
   1783 		free(buffer);
   1784 		fclose(child_output);
   1785 		exit(result);
   1786 	}
   1787 	else
   1788 	{
   1789 		perror(PROGRAMNAME ": fork");
   1790 		exit(error(errno, __FILE__, __func__, __LINE__, "fork failed"));
   1791 	}
   1792 }
   1793 
   1794 static int
   1795 process_inline_footnote(const u8* token, const int read_yaml_macros_and_links,
   1796 	FILE* output)
   1797 {
   1798 	char* temp = NULL;
   1799 	size_t token_len;
   1800 	assert(token != NULL);
   1801 	current_inline_footnote++;
   1802 	if (read_yaml_macros_and_links)
   1803 	{
   1804 		token_len = strlen((char*)token);
   1805 		inline_footnote_count++;
   1806 		if (inline_footnote_count == 1 && footnote_count > 0)
   1807 			warning(1,
   1808 				(u8*)"Both inline and regular footnotes "
   1809 				     "present");
   1810 		else if (!inline_footnotes)
   1811 		{
   1812 			inline_footnote_count = 1;
   1813 			CALLOC(inline_footnotes, u8*, inline_footnote_count);
   1814 		}
   1815 		else
   1816 			REALLOC(inline_footnotes, u8*,
   1817 				sizeof(u8*) * inline_footnote_count);
   1818 		CALLOC(inline_footnotes[inline_footnote_count - 1], u8,
   1819 			token_len + 1);
   1820 		MEMCCPY(inline_footnotes[inline_footnote_count - 1],
   1821 			(char*)token, token_len + 1, temp);
   1822 	}
   1823 	else
   1824 	{
   1825 		print_output(output,
   1826 			"<a href=\"#inline-footnote-%d\" "
   1827 			"id=\"inline-footnote-text-%d\"><sup>%d</sup></a>",
   1828 			current_inline_footnote, current_inline_footnote,
   1829 			current_inline_footnote);
   1830 		output_firstcol = 0;
   1831 	}
   1832 	return 0;
   1833 }
   1834 
   1835 static void
   1836 process_inline_image(FILE* output, const u8* image_text,
   1837 	const u8* image_file_prefix, const u8* link_prefix, const u8* image_url,
   1838 	const int add_link, const int add_figcaption)
   1839 {
   1840 	assert((output != NULL) && (image_text != NULL));
   1841 	if (add_figcaption)
   1842 		print_output(output, "<figure>\n");
   1843 	if (add_link)
   1844 		print_output(output,
   1845 			"<a href=\"%s%s%s\" title=\"%s\" class=\"image\""
   1846 			" target=\"_blank\">",
   1847 			global_link_prefix ? (const char*)global_link_prefix
   1848 					   : "",
   1849 			link_prefix ? (const char*)link_prefix : "",
   1850 			image_url ? (char*)image_url : "", image_text);
   1851 	print_output(output, "<img src=\"%s\" alt=\"%s\"%s",
   1852 		image_url ? (char*)image_url : "", image_text,
   1853 		image_url ? " " : "");
   1854 	if (image_url)
   1855 		print_image_dimensions(output, image_file_prefix, image_url);
   1856 	print_output(output, ">");
   1857 	output_firstcol = 0;
   1858 	if (add_link)
   1859 		print_output(output, "</a>");
   1860 	if (add_figcaption)
   1861 	{
   1862 		print_output(output, "<figcaption>%s</figcaption>\n</figure>\n",
   1863 			image_text);
   1864 		output_firstcol = 1;
   1865 	}
   1866 }
   1867 
   1868 static int
   1869 process_inline_link(FILE* output, const u8* link_text, const u8* link_prefix,
   1870 	const u8* link_macro_body, const u8* link_url)
   1871 {
   1872 	print_output(output, "<a href=\"%s%s%s\">%s%s</a>",
   1873 		global_link_prefix ? (const char*)global_link_prefix : "",
   1874 		link_prefix ? (const char*)link_prefix : "",
   1875 		link_url ? (char*)link_url : "",
   1876 		link_macro_body ? (char*)link_macro_body : "", link_text);
   1877 	output_firstcol = 0;
   1878 	return 0;
   1879 }
   1880 
   1881 static void
   1882 process_inline_stylesheet(FILE* output, const u8* css_filename, int output_yaml)
   1883 {
   1884 	FILE* css		 = NULL;
   1885 	u8* bufline		 = NULL;
   1886 	char* css_pathname	 = NULL;
   1887 	size_t css_filename_len	 = 0;
   1888 	size_t css_pathname_size = 0;
   1889 
   1890 	assert((output != NULL) && (css_filename != NULL));
   1891 	css_filename_len  = strlen((char*)css_filename);
   1892 	css_pathname_size = basedir_size + css_filename_len + 1;
   1893 
   1894 	CALLOC(css_pathname, char, css_pathname_size);
   1895 	if ((size_t)snprintf(css_pathname, css_pathname_size, "%s/%s", basedir,
   1896 		    css_filename)
   1897 		> css_pathname_size)
   1898 		warning(1, (u8*)"snprintf:%d: Overflow", __LINE__);
   1899 	errno = 0;
   1900 	if (!(css = fopen((const char*)css_pathname, "rt")))
   1901 	{
   1902 		perror(PROGRAMNAME ": fopen");
   1903 		exit(error(errno, __FILE__, __func__, __LINE__,
   1904 			"fopen failed: %s", css_pathname));
   1905 	}
   1906 
   1907 	if (output_yaml)
   1908 	{
   1909 		print_output(output, "inline-stylesheet: %s\n", css_filename);
   1910 		output_firstcol = 1;
   1911 		goto process_inline_stylesheet_cleanup;
   1912 	}
   1913 
   1914 	CALLOC(bufline, u8, BUFSIZE);
   1915 	print_output(output, "<style>\n");
   1916 	while (!feof(css))
   1917 	{
   1918 		if (!fgets((char*)bufline, BUFSIZE, css))
   1919 			break;
   1920 		print_output(output, "%s", bufline);
   1921 	}
   1922 	print_output(output, "</style>\n");
   1923 	output_firstcol = 1;
   1924 
   1925 process_inline_stylesheet_cleanup:
   1926 	free(css_pathname);
   1927 	fclose(css);
   1928 	free(bufline);
   1929 }
   1930 
   1931 static void
   1932 process_italic(FILE* output, const int end_tag)
   1933 {
   1934 	print_output(output, "<%sem>", end_tag ? "/" : "");
   1935 }
   1936 
   1937 static void
   1938 process_kbd(FILE* output, const int end_tag)
   1939 {
   1940 	print_output(output, "<%skbd>", end_tag ? "/" : "");
   1941 }
   1942 
   1943 static void
   1944 process_line_start(FILE* output, const u8* line, const u8* link_prefix,
   1945 	const int first_line_in_doc, const int previous_line_blank,
   1946 	int* previous_line_block, const int read_yaml_macros_and_links,
   1947 	const int list_para, const int new_para_ok)
   1948 {
   1949 	UNUSED(line);
   1950 	UNUSED(link_prefix);
   1951 	if ((first_line_in_doc || previous_line_blank || *previous_line_block)
   1952 		&& !(ANY(state, ST_BLOCKQUOTE | ST_PRE)))
   1953 	{
   1954 		if (!list_para)
   1955 		{
   1956 			if (IN(state, ST_LIST))
   1957 			{
   1958 				CLEAR(state, ST_LIST);
   1959 				if (!read_yaml_macros_and_links)
   1960 				{
   1961 					process_list_item_end(output);
   1962 					process_list_end(output);
   1963 				}
   1964 			}
   1965 
   1966 			if (IN(state, ST_NUMLIST))
   1967 			{
   1968 				CLEAR(state, ST_NUMLIST);
   1969 				if (!read_yaml_macros_and_links)
   1970 				{
   1971 					process_list_item_end(output);
   1972 					process_numlist_end(output);
   1973 				}
   1974 			}
   1975 
   1976 			if (IN(state, ST_FOOTNOTE_TEXT))
   1977 				CLEAR(state, ST_FOOTNOTE_TEXT | ST_PARA_OPEN);
   1978 		}
   1979 		if (new_para_ok
   1980 			&& !ANY(state,
   1981 				ST_TABLE | ST_TABLE_HEADER | ST_TABLE_LINE))
   1982 		{
   1983 			if (!read_yaml_macros_and_links)
   1984 			{
   1985 				print_output(output, "<p>");
   1986 				*previous_line_block = 0;
   1987 				output_firstcol	     = 0;
   1988 			}
   1989 			SET(state, ST_PARA_OPEN);
   1990 		}
   1991 	}
   1992 }
   1993 
   1994 static int
   1995 process_link(FILE* output, const u8* link_text, const u8* link_prefix,
   1996 	const u8* link_macro_body, const u8* link_id)
   1997 {
   1998 	u8* url = get_value(links, links_count, link_id, NULL);
   1999 	return process_inline_link(output, link_text, link_prefix,
   2000 		link_macro_body, url);
   2001 }
   2002 
   2003 static void
   2004 process_list_end(FILE* output)
   2005 {
   2006 	print_output(output, "</ul>\n");
   2007 	output_firstcol = 1;
   2008 }
   2009 
   2010 static void
   2011 process_list_item_end(FILE* output)
   2012 {
   2013 	CLEAR(state, ST_PARA_OPEN);
   2014 	print_output(output, "</li>");
   2015 	output_firstcol = 0;
   2016 }
   2017 
   2018 static void
   2019 process_list_item_start(FILE* output)
   2020 {
   2021 	print_output(output, "%s<li><p>", output_firstcol ? "" : "\n");
   2022 }
   2023 
   2024 static void
   2025 process_list_start(FILE* output)
   2026 {
   2027 	print_output(output, "<ul>");
   2028 }
   2029 
   2030 static int
   2031 process_macro(FILE* output, const u8* token, const int end_tag)
   2032 {
   2033 	u8* macro_body;
   2034 	u8* eol;
   2035 	size_t macro_body_len;
   2036 	UNUSED(end_tag);
   2037 	assert((output != NULL) && (token != NULL));
   2038 	macro_body = get_value(macros, macros_count, token + 1, NULL);
   2039 	if (!macro_body)
   2040 		exit(error(ERR_MACRO_UNDEF, __FILE__, __func__, __LINE__,
   2041 			"Macro '%s' undefined", token + 1));
   2042 
   2043 	eol	       = (u8*)strrchr((char*)macro_body, '\n');
   2044 	macro_body_len = strlen((char*)macro_body);
   2045 	if (eol)
   2046 		output_firstcol = macro_body + macro_body_len
   2047 				- (u8*)strrchr((char*)macro_body, '\n')
   2048 			> 0;
   2049 	else
   2050 		output_firstcol = !*macro_body
   2051 			|| *(macro_body + macro_body_len - 1) == '\n';
   2052 	print_output(output, "%s", macro_body);
   2053 	return 0;
   2054 }
   2055 
   2056 static int
   2057 process_macro_def(FILE* output, const u8* token,
   2058 	const int read_yaml_macros_and_links, const int end_tag)
   2059 {
   2060 	char* temp = NULL;
   2061 	assert((output != NULL) && (token != NULL));
   2062 	if (end_tag)
   2063 	{
   2064 		fflush(output);
   2065 		CLEAR(state, ST_MACRO_BODY);
   2066 		return 0;
   2067 	}
   2068 	if (IN(state, ST_MACRO_BODY))
   2069 		exit(error(ERR_NEST_MACRO, __FILE__, __func__, __LINE__,
   2070 			"Cannot nest definitions"));
   2071 
   2072 	if (read_yaml_macros_and_links)
   2073 	{
   2074 		macros_count++;
   2075 		if (!macros)
   2076 		{
   2077 			CALLOC(macros, KeyValue, macros_count);
   2078 			pmacros = macros;
   2079 		}
   2080 		else
   2081 		{
   2082 			REALLOC(macros, KeyValue,
   2083 				macros_count * sizeof(KeyValue));
   2084 			pmacros = macros + macros_count - 1;
   2085 		}
   2086 		CALLOC(pmacros->key, u8, KEYSIZE);
   2087 		MEMCCPY(pmacros->key, (char*)token + 2, KEYSIZE, temp);
   2088 		pmacros->value	    = NULL;
   2089 		pmacros->value_size = 0;
   2090 	}
   2091 	fflush(output);
   2092 	SET(state, ST_MACRO_BODY);
   2093 	return 0;
   2094 }
   2095 
   2096 static void
   2097 process_madeby(FILE* output)
   2098 {
   2099 	print_output(output,
   2100 		"<div id=\"made-by\"><p><small>\n"
   2101 		"Generated by <a href=\"%s\">slweb</a>\n"
   2102 		"© %s Strahinya Radich.\n"
   2103 		"</small></div><!--made-by-->\n",
   2104 		MADEBY_URL, COPYRIGHT_YEAR);
   2105 }
   2106 
   2107 static void
   2108 process_numlist_end(FILE* output)
   2109 {
   2110 	print_output(output, "</ol>\n");
   2111 	output_firstcol = 1;
   2112 }
   2113 
   2114 static void
   2115 process_numlist_start(FILE* output)
   2116 {
   2117 	print_output(output, "<ol>");
   2118 	output_firstcol = 0;
   2119 }
   2120 
   2121 static void
   2122 process_strike(FILE* output, const int end_tag)
   2123 {
   2124 	print_output(output, "<%ss>", end_tag ? "/" : "");
   2125 }
   2126 
   2127 static void
   2128 process_stylesheet(FILE* output, const u8* css_filename, int output_yaml)
   2129 {
   2130 	if (output_yaml)
   2131 		print_output(output, "stylesheet: %s\n", css_filename);
   2132 	else
   2133 		print_output(output, "<link rel=\"stylesheet\" href=\"%s\">\n",
   2134 			css_filename);
   2135 	output_firstcol = 1;
   2136 }
   2137 
   2138 static void
   2139 process_table_body_cell(FILE* output)
   2140 {
   2141 	print_output(output, "</td><td>");
   2142 }
   2143 
   2144 static void
   2145 process_table_body_row_end(FILE* output)
   2146 {
   2147 	print_output(output, "</td></tr>\n");
   2148 }
   2149 
   2150 static void
   2151 process_table_body_row_start(FILE* output)
   2152 {
   2153 	print_output(output, "<tr><td>");
   2154 }
   2155 
   2156 static void
   2157 process_table_body_start(FILE* output, const int start_row)
   2158 {
   2159 	print_output(output, "<tbody>\n");
   2160 	if (start_row)
   2161 		print_output(output, "<tr><td>");
   2162 }
   2163 
   2164 static void
   2165 process_table_end(FILE* output)
   2166 {
   2167 	print_output(output, "</tbody>\n</table>\n");
   2168 }
   2169 
   2170 static void
   2171 process_table_header_cell(FILE* output)
   2172 {
   2173 	print_output(output, "</th><th>");
   2174 }
   2175 
   2176 static void
   2177 process_table_header_end(FILE* output)
   2178 {
   2179 	print_output(output, "</th></tr>\n</thead>\n");
   2180 }
   2181 
   2182 static void
   2183 process_table_start(FILE* output)
   2184 {
   2185 	print_output(output,
   2186 		"<table>\n"
   2187 		"<thead>\n<tr><th>");
   2188 }
   2189 
   2190 static int
   2191 process_tag(FILE* output, const u8* token, const char* source_filename,
   2192 	const u8* link_prefix, const u8* footer_permalink_text,
   2193 	const char* permalink_url, const int ext_in_permalink,
   2194 	const int read_yaml_macros_and_links, int* skip_eol, const int end_tag)
   2195 {
   2196 	UNUSED(source_filename);
   2197 	assert((output != NULL) && (token != NULL) && (source_filename != NULL)
   2198 		&& (skip_eol != NULL));
   2199 	if (!*token)
   2200 		return warning(1, (u8*)"%s:%ld:%ld: Empty tag name",
   2201 			input_filename, lineno, colno);
   2202 	if (!strcmp((char*)token, "git-log")
   2203 		&& !read_yaml_macros_and_links) /* {git-log} */
   2204 	{
   2205 		process_git_log(output);
   2206 	}
   2207 	else if (startswith((char*)token, "csv-count")) /* {csv-count} */
   2208 		process_tsv_count(output, token, read_yaml_macros_and_links, 0);
   2209 	else if (startswith((char*)token, "csv")) /* {csv} */
   2210 		process_csv(output, token, read_yaml_macros_and_links, end_tag);
   2211 	else if (startswith((char*)token, "tsv-count")) /* {tsv-count} */
   2212 		process_tsv_count(output, token, read_yaml_macros_and_links, 1);
   2213 	else if (startswith((char*)token, "tsv")) /* {tsv} */
   2214 		process_tsv(output, token, read_yaml_macros_and_links, end_tag);
   2215 	else if (startswith((char*)token, "include")) /* {include} */
   2216 	{
   2217 		process_include(output, token, read_yaml_macros_and_links);
   2218 		*skip_eol = 1;
   2219 	}
   2220 	else if (startswith((char*)token, "incdir")) /* {incdir} */
   2221 	{
   2222 		process_incdir(output, token, link_prefix,
   2223 			footer_permalink_text, permalink_url, ext_in_permalink,
   2224 			read_yaml_macros_and_links);
   2225 		*skip_eol = 1;
   2226 	}
   2227 	else if (*token == '=') /* {=macro} */
   2228 	{
   2229 		if (*(token + 1) == '!')
   2230 			process_macro_def(output, token,
   2231 				read_yaml_macros_and_links, end_tag);
   2232 		else if ((*(token + 1) != '!') && !read_yaml_macros_and_links)
   2233 			process_macro(output, token, end_tag);
   2234 		*skip_eol = 1;
   2235 	}
   2236 	else if (!read_yaml_macros_and_links) /* general tags */
   2237 	{
   2238 		print_output(output, "<");
   2239 		if (end_tag)
   2240 			print_output(output, "/");
   2241 
   2242 		if (*token == '.' || *token == '#')
   2243 		{
   2244 			print_output(output, "div");
   2245 		}
   2246 
   2247 		while (*token && *token != '#' && *token != '.')
   2248 			print_output(output, "%c", *token++);
   2249 
   2250 		if (!end_tag)
   2251 		{
   2252 			if (*token == '#')
   2253 			{
   2254 				token++;
   2255 				print_output(output, " id=\"");
   2256 				while (*token && *token != '.')
   2257 					print_output(output, "%c", *token++);
   2258 				print_output(output, "\"");
   2259 				if (*token == '.')
   2260 				{
   2261 					token++;
   2262 					print_output(output, " class=\"");
   2263 					while (*token)
   2264 						print_output(output, "%c",
   2265 							*token++);
   2266 					print_output(output, "\"");
   2267 				}
   2268 			}
   2269 			else if (*token == '.')
   2270 			{
   2271 				token++;
   2272 				print_output(output, " class=\"");
   2273 				while (*token && *token != '#')
   2274 					print_output(output, "%c", *token++);
   2275 				print_output(output, "\"");
   2276 				if (*token == '#')
   2277 				{
   2278 					token++;
   2279 					print_output(output, " id=\"");
   2280 					while (*token)
   2281 						print_output(output, "%c",
   2282 							*token++);
   2283 					print_output(output, "\"");
   2284 				}
   2285 			}
   2286 		}
   2287 		print_output(output, ">");
   2288 		output_firstcol = 0;
   2289 	}
   2290 	return 0;
   2291 }
   2292 
   2293 static void
   2294 process_text_token(FILE* output, const u8* line, const u8* link_prefix,
   2295 	const int first_line_in_doc, const int previous_line_blank,
   2296 	int* previous_line_block, const int processed_start_of_line,
   2297 	const int read_yaml_macros_and_links, const int list_para, u8** token,
   2298 	u8** ptoken, size_t* token_size, const int add_enclosing_paragraph,
   2299 	const int new_para_ok)
   2300 {
   2301 	u8* eol;
   2302 	assert((token != NULL) && (ptoken != NULL));
   2303 	if (!(IN(state, ST_YAML)))
   2304 	{
   2305 		if (add_enclosing_paragraph && !processed_start_of_line)
   2306 			process_line_start(output, line, link_prefix,
   2307 				first_line_in_doc, previous_line_blank,
   2308 				previous_line_block, read_yaml_macros_and_links,
   2309 				list_para, new_para_ok);
   2310 		**ptoken = 0;
   2311 		if (**token && !read_yaml_macros_and_links
   2312 			&& !(IN(state, ST_MACRO_BODY)))
   2313 		{
   2314 			print_output(output, "%s", *token);
   2315 			eol = (u8*)strrchr((char*)*token, '\n');
   2316 			if (eol)
   2317 				output_firstcol
   2318 					= *token + strlen((char*)*token) > eol;
   2319 			else
   2320 				output_firstcol = 0;
   2321 		}
   2322 	}
   2323 	RESET_TOKEN(*token, *ptoken, *token_size);
   2324 }
   2325 
   2326 static void
   2327 process_timestamp(FILE* output, const u8* link_prefix, const char* link,
   2328 	const u8* permalink_macro, const u8* date, const int ext_in_permalink)
   2329 {
   2330 	u8* formatted_date = NULL;
   2331 	char* in_filename  = NULL;
   2332 	char* in_line	   = NULL;
   2333 
   2334 	assert((output != NULL) && (link != NULL));
   2335 	formatted_date = format_date(date, article_timestamp_format);
   2336 	if (!formatted_date)
   2337 		goto process_timestamp_cleanup;
   2338 	print_output(output,
   2339 		"<a href=\"%s%s%s%s\""
   2340 		" class=\"timestamp\">%s%s</a>\n",
   2341 		url_is_local(link) && global_link_prefix
   2342 			? (const char*)global_link_prefix
   2343 			: "",
   2344 		url_is_local(link) && link_prefix ? (const char*)link_prefix
   2345 						  : "",
   2346 		link, ext_in_permalink ? timestamp_output_ext : "",
   2347 		permalink_macro ? (char*)permalink_macro : "", formatted_date);
   2348 	output_firstcol = 1;
   2349 
   2350 process_timestamp_cleanup:
   2351 	free(formatted_date);
   2352 	free(in_line);
   2353 	free(in_filename);
   2354 }
   2355 
   2356 static void
   2357 process_tsv(FILE* output, const u8* arg_token,
   2358 	const int read_yaml_macros_and_links, const int end_tag)
   2359 {
   2360 	u8* saveptr   = NULL;
   2361 	u8* args      = NULL;
   2362 	u8* args_base = NULL;
   2363 	size_t args_len;
   2364 
   2365 	assert((output != NULL) && (arg_token != NULL));
   2366 	if (end_tag)
   2367 	{
   2368 		CLEAR(state, ST_TSV_BODY);
   2369 		if (read_yaml_macros_and_links)
   2370 			return;
   2371 		read_tsv(output, tsv_filename, &print_tsv_row);
   2372 		free(tsv_filename);
   2373 		tsv_filename = NULL;
   2374 		free(tsv_template);
   2375 		tsv_template	  = NULL;
   2376 		tsv_template_size = 0;
   2377 	}
   2378 	else
   2379 	{
   2380 		if (ANY(state, ST_CSV_BODY | ST_TSV_BODY))
   2381 			exit(error(ERR_NEST_CSV, __FILE__, __func__, __LINE__,
   2382 				"Can't nest csv/tsv directives"));
   2383 		SET(state, ST_TSV_BODY);
   2384 		if (read_yaml_macros_and_links)
   2385 			return;
   2386 		tsv_iter = 0;
   2387 		(void)strtok_r((char*)arg_token, " ", (char**)&saveptr);
   2388 		args = (u8*)strtok_r(NULL, " ", (char**)&saveptr);
   2389 		if (!args)
   2390 			exit(error(ERR_NO_ARG, __FILE__, __func__, __LINE__,
   2391 				"Arguments required"));
   2392 		args_len = strlen((char*)args);
   2393 		if (*args != '"' || *(args + args_len - 1) != '"')
   2394 			exit(error(ERR_ARG_NOTSTR, __FILE__, __func__, __LINE__,
   2395 				"First argument must be a string"));
   2396 		if (!tsv_filename)
   2397 			CALLOC(tsv_filename, char, BUFSIZE);
   2398 		args_base = (u8*)strdup((char*)args + 1);
   2399 		*(args_base + strlen((char*)args_base) - 1) = 0;
   2400 		snprintf(tsv_filename, BUFSIZE, "%s/%s.tsv", input_dirname,
   2401 			(char*)args_base);
   2402 		free(args_base);
   2403 		args = (u8*)strtok_r(NULL, " ", (char**)&saveptr);
   2404 		if (args)
   2405 		{
   2406 			errno	 = 0;
   2407 			tsv_iter = (size_t)strtol((char*)args, NULL, 10);
   2408 			if (errno)
   2409 			{
   2410 				perror(PROGRAMNAME ": strtol");
   2411 				exit(error(errno, __FILE__, __func__, __LINE__,
   2412 					"Invalid argument '%s'", args));
   2413 			}
   2414 		}
   2415 	}
   2416 }
   2417 
   2418 static void
   2419 process_tsv_count(FILE* output, const u8* arg_token,
   2420 	const int read_yaml_macros_and_links, const int is_tsv)
   2421 {
   2422 	FILE* tsv     = NULL;
   2423 	u8* saveptr   = NULL;
   2424 	u8* args      = NULL;
   2425 	u8* args_base = NULL;
   2426 	char* bufline = NULL;
   2427 	char* eol     = NULL;
   2428 	size_t args_len;
   2429 	size_t tsv_lines = 0;
   2430 
   2431 	assert((output != NULL) && (arg_token != NULL));
   2432 	if (read_yaml_macros_and_links)
   2433 		return;
   2434 	tsv_iter = 0;
   2435 	(void)strtok_r((char*)arg_token, " ", (char**)&saveptr);
   2436 	args = (u8*)strtok_r(NULL, " ", (char**)&saveptr);
   2437 	if (!args)
   2438 		exit(error(ERR_NO_ARG, __FILE__, __func__, __LINE__,
   2439 			"Arguments required"));
   2440 	args_len = strlen((char*)args);
   2441 	if (*args != '"' || *(args + args_len - 1) != '"')
   2442 		exit(error(ERR_ARG_NOTSTR, __FILE__, __func__, __LINE__,
   2443 			"First argument must be a string"));
   2444 	if (!tsv_filename)
   2445 		CALLOC(tsv_filename, char, BUFSIZE);
   2446 	args_base = (u8*)strdup((char*)args + 1);
   2447 	*(args_base + strlen((char*)args_base) - 1) = 0;
   2448 	snprintf(tsv_filename, BUFSIZE, "%s/%s.%csv", input_dirname,
   2449 		(char*)args_base, is_tsv ? 't' : 'c');
   2450 	free(args_base);
   2451 	errno = 0;
   2452 	if (!(tsv = fopen(tsv_filename, "rt")))
   2453 	{
   2454 		perror(PROGRAMNAME ": fopen");
   2455 		exit(error(errno, __FILE__, __func__, __LINE__,
   2456 			"fopen failed: %s", tsv_filename));
   2457 	}
   2458 	CALLOC(bufline, char, BUFSIZE);
   2459 	while (!feof(tsv))
   2460 	{
   2461 		eol = NULL;
   2462 		if (!fgets(bufline, BUFSIZE, tsv))
   2463 			break;
   2464 		eol = strchr(bufline, '\n');
   2465 		if (eol)
   2466 		{
   2467 			*eol = 0;
   2468 			tsv_lines++;
   2469 		}
   2470 	}
   2471 	fclose(tsv);
   2472 
   2473 	/* And take back one kadam for the header row */
   2474 	print_output(output, "%ld", tsv_lines - 1);
   2475 
   2476 	free(bufline);
   2477 	free(tsv_filename);
   2478 	tsv_filename = NULL;
   2479 }
   2480 
   2481 static void
   2482 read_csv(FILE* output, const char* filename, csv_callback_t callback)
   2483 {
   2484 	FILE* csv    = NULL;
   2485 	u8* bufline  = NULL;
   2486 	u8* pbufline = NULL;
   2487 	u8* token    = NULL;
   2488 	u8* ptoken   = NULL;
   2489 	u8* csv_header[MAX_CSV_REGISTERS];
   2490 	u8* csv_register[MAX_CSV_REGISTERS];
   2491 	u8* csv_delimiter	       = NULL;
   2492 	u8* eol			       = NULL;
   2493 	char* temp		       = NULL;
   2494 	size_t csv_lineno	       = 0;
   2495 	size_t token_size	       = 0;
   2496 	unsigned char csv_state	       = ST_CS_NONE;
   2497 	unsigned char current_header   = 0;
   2498 	unsigned char current_register = 0;
   2499 	unsigned char i;
   2500 
   2501 	assert((output != NULL) && (filename != NULL) && (callback != NULL));
   2502 	csv_delimiter = get_value(vars, vars_count, (u8*)"csv-delimiter", NULL);
   2503 	errno	      = 0;
   2504 	if (!(csv = fopen(filename, "rt")))
   2505 	{
   2506 		perror(PROGRAMNAME ": fopen");
   2507 		exit(error(errno, __FILE__, __func__, __LINE__,
   2508 			"fopen failed: %s", filename));
   2509 	}
   2510 	CALLOC(bufline, u8, BUFSIZE);
   2511 	token_size = BUFSIZE;
   2512 	CALLOC(token, u8, token_size);
   2513 	for (i = 0; i < MAX_CSV_REGISTERS; i++)
   2514 		CALLOC(csv_header[i], u8, BUFSIZE);
   2515 	for (i = 0; i < MAX_CSV_REGISTERS; i++)
   2516 		CALLOC(csv_register[i], u8, BUFSIZE);
   2517 	while (!feof(csv) && (!csv_iter || csv_lineno <= csv_iter))
   2518 	{
   2519 		eol = NULL;
   2520 		if (!fgets((char*)bufline, BUFSIZE, csv))
   2521 			break;
   2522 		eol = (u8*)strchr((char*)bufline, '\n');
   2523 		if (eol)
   2524 			*eol = 0;
   2525 		pbufline = bufline;
   2526 		RESET_TOKEN(token, ptoken, token_size);
   2527 		current_register = 0;
   2528 	csv_bufline_start:
   2529 		if (!*pbufline)
   2530 			goto csv_bufline_done;
   2531 		switch (*pbufline)
   2532 		{
   2533 		case '"':
   2534 			if (IN(csv_state, ST_CS_QUOTE))
   2535 				csv_state &= ~ST_CS_QUOTE;
   2536 			else
   2537 				csv_state |= ST_CS_QUOTE;
   2538 			pbufline++;
   2539 			break;
   2540 		case ';':
   2541 		case ',':
   2542 			if (IN(csv_state, ST_CS_QUOTE))
   2543 				CHECKCOPY(token, ptoken, token_size, pbufline);
   2544 			else
   2545 			{
   2546 				*ptoken = 0;
   2547 				if (csv_lineno > 0
   2548 					&& current_register < MAX_CSV_REGISTERS)
   2549 				{
   2550 					MEMCCPY(csv_register[current_register],
   2551 						(char*)token, BUFSIZE, temp);
   2552 					current_register++;
   2553 				}
   2554 				else if (csv_lineno <= 0
   2555 					&& current_header < MAX_CSV_REGISTERS)
   2556 				{
   2557 					MEMCCPY(csv_header[current_header],
   2558 						(char*)token, BUFSIZE, temp);
   2559 					current_header++;
   2560 				}
   2561 				RESET_TOKEN(token, ptoken, token_size);
   2562 				pbufline++;
   2563 			}
   2564 			break;
   2565 		default:
   2566 			if (IN(csv_state, ST_CS_QUOTE))
   2567 				CHECKCOPY(token, ptoken, token_size, pbufline);
   2568 			else if (csv_delimiter && *pbufline == *csv_delimiter)
   2569 			{
   2570 				*ptoken = 0;
   2571 				if (csv_lineno > 0
   2572 					&& current_register < MAX_CSV_REGISTERS)
   2573 				{
   2574 					MEMCCPY(csv_register[current_register],
   2575 						(char*)token, BUFSIZE, temp);
   2576 					current_register++;
   2577 				}
   2578 				else if (csv_lineno <= 0
   2579 					&& current_header < MAX_CSV_REGISTERS)
   2580 				{
   2581 					MEMCCPY(csv_header[current_header],
   2582 						(char*)token, BUFSIZE, temp);
   2583 					current_header++;
   2584 				}
   2585 				RESET_TOKEN(token, ptoken, token_size);
   2586 				pbufline++;
   2587 			}
   2588 			else
   2589 				CHECKCOPY(token, ptoken, token_size, pbufline);
   2590 		}
   2591 		goto csv_bufline_start;
   2592 	csv_bufline_done:
   2593 		*ptoken = 0;
   2594 		if (csv_lineno > 0)
   2595 		{
   2596 			if (current_register < MAX_CSV_REGISTERS)
   2597 			{
   2598 				MEMCCPY(csv_register[current_register],
   2599 					(char*)token, BUFSIZE, temp);
   2600 				current_register++;
   2601 			}
   2602 		}
   2603 		else
   2604 		{
   2605 			if (current_header < MAX_CSV_REGISTERS)
   2606 			{
   2607 				MEMCCPY(csv_header[current_header],
   2608 					(char*)token, BUFSIZE, temp);
   2609 				current_header++;
   2610 			}
   2611 		}
   2612 		RESET_TOKEN(token, ptoken, token_size);
   2613 
   2614 		if (csv_lineno > 0 && pbufline != bufline)
   2615 			(*callback)(output, csv_header, csv_register);
   2616 
   2617 		for (i = 0; i < MAX_CSV_REGISTERS; i++)
   2618 			*csv_register[i] = 0;
   2619 		csv_lineno++;
   2620 	}
   2621 	fclose(csv);
   2622 	for (i = MAX_CSV_REGISTERS; i > 0; i--)
   2623 		free(csv_register[i - 1]);
   2624 	for (i = MAX_CSV_REGISTERS; i > 0; i--)
   2625 		free(csv_header[i - 1]);
   2626 	free(token);
   2627 	free(bufline);
   2628 }
   2629 
   2630 static int
   2631 read_file_into_buffer(FILE** input, u8** buffer, size_t* buffer_size,
   2632 	const char* input_filename, char** input_dirname)
   2633 {
   2634 	struct stat fs;
   2635 	char* slash = NULL;
   2636 	char* pinput_dirname;
   2637 	const char* pinput_filename;
   2638 
   2639 	assert((input != NULL) && (buffer != NULL) && (buffer_size != NULL)
   2640 		&& (input_dirname != NULL));
   2641 	errno  = 0;
   2642 	*input = fopen(input_filename, "r");
   2643 	if (!*input)
   2644 	{
   2645 		perror(PROGRAMNAME ": fopen");
   2646 		return error(errno, __FILE__, __func__, __LINE__,
   2647 			"fopen failed: %s", input_filename);
   2648 	}
   2649 
   2650 	errno = 0;
   2651 	fstat(fileno(*input), &fs);
   2652 	if (S_ISDIR(fs.st_mode))
   2653 		return error(EISDIR, __FILE__, __func__, __LINE__,
   2654 			"Is a directory: %s", input_filename);
   2655 
   2656 	free(*buffer);
   2657 	*buffer_size = fs.st_size + 1;
   2658 	CALLOC(*buffer, u8, *buffer_size);
   2659 	fread((void*)*buffer, 1, *buffer_size, *input);
   2660 	free(*input_dirname);
   2661 
   2662 	slash = strrchr(input_filename, '/');
   2663 	if (slash)
   2664 	{
   2665 		CALLOC(*input_dirname, char, strlen(input_filename) + 1);
   2666 		pinput_dirname	= *input_dirname;
   2667 		pinput_filename = input_filename;
   2668 		while (pinput_filename && *pinput_filename
   2669 			&& pinput_filename != slash)
   2670 			*pinput_dirname++ = *pinput_filename++;
   2671 	}
   2672 	else
   2673 	{
   2674 		CALLOC(*input_dirname, char, 2);
   2675 		**input_dirname = '.';
   2676 	}
   2677 	fclose(*input);
   2678 
   2679 	return 0;
   2680 }
   2681 
   2682 static int
   2683 read_tsv(FILE* output, const char* filename, tsv_callback_t callback)
   2684 {
   2685 	FILE* tsv = NULL;
   2686 	u8* tsv_header[MAX_TSV_REGISTERS];
   2687 	u8* tsv_register[MAX_TSV_REGISTERS];
   2688 	u8* bufline		       = NULL;
   2689 	u8* pbufline		       = NULL;
   2690 	u8* token		       = NULL;
   2691 	u8* ptoken		       = NULL;
   2692 	u8* eol			       = NULL;
   2693 	char* ctemp		       = NULL;
   2694 	size_t tsv_lineno	       = 0;
   2695 	size_t token_size	       = 0;
   2696 	unsigned char current_header   = 0;
   2697 	unsigned char current_register = 0;
   2698 	unsigned char i;
   2699 
   2700 	assert((output != NULL) && (filename != NULL) && (callback != NULL));
   2701 	errno = 0;
   2702 	if (!(tsv = fopen(filename, "rt")))
   2703 	{
   2704 		perror(PROGRAMNAME ": fopen");
   2705 		exit(error(errno, __FILE__, __func__, __LINE__,
   2706 			"fopen failed: %s", filename));
   2707 	}
   2708 
   2709 	CALLOC(bufline, u8, BUFSIZE);
   2710 	token_size = BUFSIZE;
   2711 	CALLOC(token, u8, token_size);
   2712 	for (i = 0; i < MAX_TSV_REGISTERS; i++)
   2713 		CALLOC(tsv_header[i], u8, BUFSIZE);
   2714 	for (i = 0; i < MAX_TSV_REGISTERS; i++)
   2715 		CALLOC(tsv_register[i], u8, BUFSIZE);
   2716 
   2717 	while (!feof(tsv) && (!tsv_iter || tsv_lineno <= tsv_iter))
   2718 	{
   2719 		eol = NULL;
   2720 		if (!fgets((char*)bufline, BUFSIZE, tsv))
   2721 			break;
   2722 		eol = (u8*)strchr((char*)bufline, '\n');
   2723 		if (eol)
   2724 			*eol = 0;
   2725 		pbufline = bufline;
   2726 		RESET_TOKEN(token, ptoken, token_size);
   2727 		current_register = 0;
   2728 	tsv_bufline_start:
   2729 		if (!*pbufline)
   2730 			goto tsv_bufline_done;
   2731 		switch (*pbufline)
   2732 		{
   2733 		case '\t':
   2734 			*ptoken = 0;
   2735 			if (tsv_lineno > 0
   2736 				&& current_register < MAX_TSV_REGISTERS)
   2737 			{
   2738 				MEMCCPY(tsv_register[current_register],
   2739 					(char*)token, BUFSIZE, ctemp);
   2740 				current_register++;
   2741 			}
   2742 			else if (tsv_lineno <= 0
   2743 				&& current_header < MAX_TSV_REGISTERS)
   2744 			{
   2745 				MEMCCPY(tsv_header[current_header],
   2746 					(char*)token, BUFSIZE, ctemp);
   2747 				current_header++;
   2748 			}
   2749 			RESET_TOKEN(token, ptoken, token_size);
   2750 			pbufline++;
   2751 			break;
   2752 		default:
   2753 			CHECKCOPY(token, ptoken, token_size, pbufline);
   2754 		}
   2755 		goto tsv_bufline_start;
   2756 	tsv_bufline_done:
   2757 		*ptoken = 0;
   2758 		if (tsv_lineno > 0)
   2759 		{
   2760 			if (current_register < MAX_TSV_REGISTERS)
   2761 			{
   2762 				MEMCCPY(tsv_register[current_register],
   2763 					(char*)token, BUFSIZE, ctemp);
   2764 				current_register++;
   2765 			}
   2766 		}
   2767 		else
   2768 		{
   2769 			if (current_header < MAX_TSV_REGISTERS)
   2770 			{
   2771 				MEMCCPY(tsv_header[current_header],
   2772 					(char*)token, BUFSIZE, ctemp);
   2773 				current_header++;
   2774 			}
   2775 		}
   2776 		RESET_TOKEN(token, ptoken, token_size);
   2777 
   2778 		if (tsv_lineno > 0 && pbufline != bufline)
   2779 			(*callback)(output, tsv_header, tsv_register);
   2780 
   2781 		for (i = 0; i < MAX_TSV_REGISTERS; i++)
   2782 			*tsv_register[i] = 0;
   2783 		tsv_lineno++;
   2784 	}
   2785 	fclose(tsv);
   2786 	for (i = MAX_TSV_REGISTERS; i > 0; i--)
   2787 		free(tsv_register[i - 1]);
   2788 	for (i = MAX_TSV_REGISTERS; i > 0; i--)
   2789 		free(tsv_header[i - 1]);
   2790 	free(token);
   2791 	free(bufline);
   2792 
   2793 	return 0;
   2794 }
   2795 
   2796 static int
   2797 reverse_alphacompare(const struct dirent** a, const struct dirent** b)
   2798 {
   2799 	assert((a != NULL) && (b != NULL));
   2800 	return -1 * strcmp((*a)->d_name, (*b)->d_name);
   2801 }
   2802 
   2803 static int
   2804 set_basedir(char** basedir, size_t* basedir_size, const char* arg)
   2805 {
   2806 	size_t arg_len;
   2807 	char* temp = NULL;
   2808 	assert((basedir != NULL) && (*basedir != NULL) && (basedir_size != NULL)
   2809 		&& (arg != NULL));
   2810 	arg_len = strlen(arg);
   2811 	ENSURE_SIZE(*basedir, temp, *basedir_size, (arg_len + 1), (arg_len + 1),
   2812 		set_basedir_alloc_error, char);
   2813 	MEMCCPY(*basedir, arg, *basedir_size, temp);
   2814 	return 0;
   2815 set_basedir_alloc_error:
   2816 	exit(error(errno, __FILE__, __func__, __LINE__, "Allocation error"));
   2817 	return 1;
   2818 }
   2819 
   2820 static int
   2821 set_global_link_prefix(char** global_link_prefix,
   2822 	size_t* global_link_prefix_size, const char* arg)
   2823 {
   2824 	char* temp = NULL;
   2825 	size_t arg_len;
   2826 	assert((arg != NULL) && (global_link_prefix != NULL)
   2827 		&& (*global_link_prefix != NULL)
   2828 		&& (global_link_prefix_size != NULL));
   2829 	arg_len = strlen(arg);
   2830 	ENSURE_SIZE(*global_link_prefix, temp, *global_link_prefix_size,
   2831 		(arg_len + 1), (arg_len + 1),
   2832 		set_global_link_prefix_alloc_error, char);
   2833 	MEMCCPY(*global_link_prefix, arg, *global_link_prefix_size, temp);
   2834 	return 0;
   2835 set_global_link_prefix_alloc_error:
   2836 	exit(error(errno, __FILE__, __func__, __LINE__, "Allocation error"));
   2837 	return 1;
   2838 }
   2839 
   2840 static int
   2841 simple_parse_yaml_line(const u8* line, KeyValue** vars, size_t* vars_count,
   2842 	KeyValue** pvars)
   2843 {
   2844 	KeyValue* cvar	= NULL;
   2845 	u8* token	= NULL;
   2846 	u8* ptoken	= NULL;
   2847 	u8* var_key	= NULL;
   2848 	const u8* pline = line;
   2849 	char* temp	= NULL;
   2850 	int in_arg	= 0;
   2851 	int found	= 0;
   2852 
   2853 	assert((line != NULL) && (vars != NULL) && (vars_count != NULL)
   2854 		&& (pvars != NULL));
   2855 	CALLOC(token, u8, BUFSIZE);
   2856 	ptoken	= token;
   2857 	*ptoken = 0;
   2858 	while (*pline)
   2859 	{
   2860 		if (!in_arg && *pline == ':')
   2861 		{
   2862 			in_arg	= 1;
   2863 			*ptoken = 0;
   2864 			var_key = (u8*)strdup((char*)token);
   2865 			if (!var_key)
   2866 				exit(error(ENOMEM, __FILE__, __func__, __LINE__,
   2867 					"strdup failed (out of "
   2868 					"memory?)"));
   2869 			ptoken	= token;
   2870 			*ptoken = 0;
   2871 			pline++;
   2872 		}
   2873 		else if (*pline == ' ' || *pline == '\t')
   2874 			pline++;
   2875 		else
   2876 			*ptoken++ = *pline++;
   2877 	}
   2878 	if (*token)
   2879 		*ptoken = 0;
   2880 	if (var_key && *var_key && *token)
   2881 	{
   2882 		found = 0;
   2883 		cvar  = *vars;
   2884 		while (!found && cvar != *vars + *vars_count)
   2885 		{
   2886 			if (!strcmp((const char*)cvar->key, (const char*)var_key)
   2887 				&& cvar->value_size
   2888 				&& !strcmp((const char*)cvar->value,
   2889 					(const char*)token))
   2890 				found = 1;
   2891 			cvar++;
   2892 		}
   2893 		if (!found)
   2894 		{
   2895 			(*vars_count)++;
   2896 			if (!*vars)
   2897 			{
   2898 				CALLOC(*vars, KeyValue, *vars_count);
   2899 				*pvars = *vars;
   2900 			}
   2901 			else
   2902 			{
   2903 				REALLOCARRAY(*vars, KeyValue, *vars_count);
   2904 				*pvars = *vars + *vars_count - 1;
   2905 			}
   2906 			CALLOC((*pvars)->key, u8, KEYSIZE);
   2907 			MEMCCPY((*pvars)->key, (char*)var_key, KEYSIZE, temp);
   2908 			(*pvars)->value_size = strlen((char*)token) + 1;
   2909 			CALLOC((*pvars)->value, u8, (*pvars)->value_size);
   2910 			MEMCCPY((*pvars)->value, (char*)token,
   2911 				(*pvars)->value_size, temp);
   2912 		}
   2913 	}
   2914 	free(var_key);
   2915 	free(token);
   2916 	return 0;
   2917 }
   2918 
   2919 static int
   2920 startswith(const char* s, const char* what)
   2921 {
   2922 	assert((s != NULL) && (what != NULL));
   2923 	do
   2924 	{
   2925 		if (*s++ != *what++)
   2926 			return 0;
   2927 	} while (*s && *what);
   2928 	return !*what;
   2929 }
   2930 
   2931 static char*
   2932 strip_ext(const char* fn, const size_t fn_size)
   2933 {
   2934 	char* newname	= NULL;
   2935 	char* pnewname	= NULL;
   2936 	const char* pfn = NULL;
   2937 	char* dot	= NULL;
   2938 
   2939 	assert(fn != NULL);
   2940 	dot = strrchr(fn, '.');
   2941 	if (!dot)
   2942 		return NULL;
   2943 	errno	= 0;
   2944 	newname = strndup(fn, fn_size);
   2945 	if (!newname)
   2946 	{
   2947 		perror(PROGRAMNAME ": strndup");
   2948 		exit(error(errno, __FILE__, __func__, __LINE__,
   2949 			"Allocation error"));
   2950 	}
   2951 	pnewname = newname;
   2952 	pfn	 = fn;
   2953 	while (pfn != dot && *pfn)
   2954 		*pnewname++ = *pfn++;
   2955 	*pnewname = 0;
   2956 	return newname;
   2957 }
   2958 
   2959 static int
   2960 url_is_local(const char* url)
   2961 {
   2962 	return !(startswith(url, "http://") || startswith(url, "https://")
   2963 		|| startswith(url, "gemini://") || startswith(url, "ftp://")
   2964 		|| startswith(url, "ftps://") || startswith(url, "mailto://"));
   2965 }
   2966 
   2967 static int
   2968 usage(void)
   2969 {
   2970 	printf("Usage:\t%s -h | --help | -V | --full-version | -v | --version\n"
   2971 	       "\t%s [-b | --body-only] [-d directory | --basedir directory]"
   2972 	       " [-p URL | --global-link-prefix URL] [filename]\n",
   2973 		PROGRAMNAME, PROGRAMNAME);
   2974 	return 0;
   2975 }
   2976 
   2977 static int
   2978 version(const int full)
   2979 {
   2980 	printf("%s %s, committed on %s\n", PROGRAMNAME, VERSION, DATE);
   2981 	if (full)
   2982 		puts(COPYRIGHT);
   2983 	return 0;
   2984 }
   2985 
   2986 static int
   2987 warning(const int code, const u8* fmt, ...)
   2988 {
   2989 	char buf[BUFSIZE];
   2990 	va_list args;
   2991 	va_start(args, fmt);
   2992 	vsnprintf(buf, sizeof(buf), (const char*)fmt, args);
   2993 	va_end(args);
   2994 	fprintf(stderr, "Warning: %s\n", buf);
   2995 	return code;
   2996 }
   2997 
   2998 #define APPEND_TOKEN(what, what_len)                                       \
   2999 	do                                                                 \
   3000 	{                                                                  \
   3001 		token_len = ptoken - token;                                \
   3002 		ENSURE_SIZE(token, temp, token_size, token_len + what_len, \
   3003 			token_len + what_len, parse_alloc_error, u8);      \
   3004 		MEMCCPY((token + token_len), what, token_size - token_len, \
   3005 			temp);                                             \
   3006 		ptoken = temp ? temp - 1 : &token[token_size - 1];         \
   3007 	} while (0)
   3008 
   3009 static int
   3010 slweb_parse(FILE* output, const char* source_filename, const size_t sfn_size,
   3011 	const u8* buffer, const int body_only,
   3012 	const int read_yaml_macros_and_links)
   3013 {
   3014 	const u8* pbuffer	    = NULL;
   3015 	u8* author		    = NULL;
   3016 	u8* date		    = NULL;
   3017 	u8* eol			    = NULL;
   3018 	u8* ext_in_permalink	    = NULL;
   3019 	u8* footer_permalink_text   = NULL;
   3020 	u8* header_text		    = NULL;
   3021 	u8* image_file_prefix	    = NULL;
   3022 	u8* line		    = NULL;
   3023 	u8* link_macro		    = NULL;
   3024 	u8* link_prefix		    = NULL;
   3025 	u8* link_text		    = NULL;
   3026 	u8* permalink_url	    = NULL;
   3027 	u8* pline		    = NULL;
   3028 	u8* ptoken		    = NULL;
   3029 	u8* temp		    = NULL;
   3030 	u8* temp2		    = NULL;
   3031 	u8* title		    = NULL;
   3032 	u8* title_heading_level	    = NULL;
   3033 	u8* token		    = NULL;
   3034 	u8* var_add_article_header  = NULL;
   3035 	u8* var_add_figcaption	    = NULL;
   3036 	u8* var_add_footnote_div    = NULL;
   3037 	u8* var_add_image_links	    = NULL;
   3038 	u8* var_incdir_only_summary = NULL;
   3039 	size_t entity_len	    = 0;
   3040 	size_t heading_count	    = 0;
   3041 	size_t line_len		    = 0;
   3042 	size_t line_size	    = 0;
   3043 	size_t link_size	    = 0;
   3044 	size_t pline_len	    = 0;
   3045 	size_t token_len	    = 0;
   3046 	size_t token_size	    = 0;
   3047 	size_t value_len	    = 0;
   3048 	int add_figcaption	    = 1;
   3049 	int add_footnote_div	    = 0;
   3050 	int add_image_links	    = 1;
   3051 	int break_mark		    = 0;
   3052 	int end_tag		    = 0;
   3053 	int first_line_in_doc	    = 1;
   3054 	int footnote_at_line_start  = 0;
   3055 	int keep_token		    = 0;
   3056 	int list_para		    = 0;
   3057 	int madeby_present	    = 0;
   3058 	int previous_line_blank	    = 0;
   3059 	int previous_line_block	    = 0;
   3060 	int processed_start_of_line = 0;
   3061 	int save_errno;
   3062 	int skip_change_first_line_in_doc = 0;
   3063 	int skip_eol			  = 0;
   3064 	unsigned char heading_level	  = 0;
   3065 
   3066 	assert((output != NULL) && (buffer != NULL));
   3067 	title = get_value(vars, vars_count, (u8*)"title", NULL);
   3068 	title_heading_level
   3069 		= get_value(vars, vars_count, (u8*)"title-heading-level", NULL);
   3070 	header_text   = get_value(vars, vars_count, (u8*)"header-text", NULL);
   3071 	author	      = get_value(vars, vars_count, (u8*)"author", NULL);
   3072 	date	      = get_value(vars, vars_count, (u8*)"date", NULL);
   3073 	permalink_url = get_value(vars, vars_count, (u8*)"permalink-url", NULL);
   3074 	image_file_prefix
   3075 		= get_value(vars, vars_count, (u8*)"image-file-prefix", NULL);
   3076 	ext_in_permalink
   3077 		= get_value(vars, vars_count, (u8*)"ext-in-permalink", NULL);
   3078 	var_add_article_header
   3079 		= get_value(vars, vars_count, (u8*)"add-article-header", NULL);
   3080 	var_add_image_links
   3081 		= get_value(vars, vars_count, (u8*)"add-image-links", NULL);
   3082 	add_image_links = !(var_add_image_links && *var_add_image_links == '0');
   3083 	var_add_figcaption
   3084 		= get_value(vars, vars_count, (u8*)"add-figcaption", NULL);
   3085 	add_figcaption = !(var_add_figcaption && *var_add_figcaption == '0');
   3086 	var_add_footnote_div
   3087 		= get_value(vars, vars_count, (u8*)"add-footnote-div", NULL);
   3088 	add_footnote_div = var_add_footnote_div && *var_add_footnote_div == '1';
   3089 	link_prefix = get_value(vars, vars_count, (u8*)"link-prefix", NULL);
   3090 	footer_permalink_text = get_value(vars, vars_count,
   3091 		(u8*)"incdir-footer-permalink-text", NULL);
   3092 	var_incdir_only_summary
   3093 		= get_value(vars, vars_count, (u8*)"incdir-only-summary", NULL);
   3094 	incdir_only_summary
   3095 		= var_incdir_only_summary && *var_incdir_only_summary == '1';
   3096 
   3097 	line_size = BUFSIZE;
   3098 	CALLOC(line, u8, line_size);
   3099 	token_size = BUFSIZE;
   3100 	CALLOC(token, u8, BUFSIZE);
   3101 	CALLOC(link_macro, u8, BUFSIZE);
   3102 
   3103 	pbuffer	   = buffer;
   3104 	pvars	   = vars;
   3105 	pmacros	   = macros;
   3106 	plinks	   = links;
   3107 	pfootnotes = footnotes;
   3108 	lineno	   = 0;
   3109 
   3110 	if (!read_yaml_macros_and_links && !body_only)
   3111 	{
   3112 		begin_html_and_head(output);
   3113 		add_css(output, 0);
   3114 		end_head_start_body(output);
   3115 	}
   3116 
   3117 	if (!read_yaml_macros_and_links)
   3118 		begin_article(output, source_filename, sfn_size, link_prefix,
   3119 			var_add_article_header && *var_add_article_header == '1',
   3120 			author, title, header_text, (char*)title_heading_level,
   3121 			date, ext_in_permalink && *ext_in_permalink != '0',
   3122 			(char*)permalink_url);
   3123 
   3124 	RESET_TOKEN(token, ptoken, token_size);
   3125 
   3126 do_buffer:
   3127 	eol = (u8*)strchr((char*)pbuffer, '\n');
   3128 	if (!eol)
   3129 		goto done_buffer;
   3130 
   3131 	pline = line;
   3132 	while (pbuffer != eol)
   3133 		CHECKCOPY(line, pline, line_size, pbuffer);
   3134 
   3135 	if (pbuffer == eol)
   3136 		pbuffer++;
   3137 
   3138 	*pline	 = 0;
   3139 	pline	 = line;
   3140 	line_len = strlen((char*)line);
   3141 
   3142 	lineno++;
   3143 	colno			= 1;
   3144 	output_firstcol		= 1;
   3145 	processed_start_of_line = 0;
   3146 	skip_eol		= 0;
   3147 	list_para		= 0;
   3148 
   3149 do_line:
   3150 	if (!pline || !*pline)
   3151 		goto done_line;
   3152 	switch (*pline)
   3153 	{
   3154 	case '-':
   3155 		if (ANY(state,
   3156 			    ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA
   3157 				    | ST_MACRO_BODY | ST_PRE | ST_YAML_VAL))
   3158 		{
   3159 			CHECKCOPY(token, ptoken, token_size, pline);
   3160 			colno++;
   3161 		}
   3162 		else if (colno == 1 && strlen((char*)pline) > 2
   3163 			&& startswith((char*)pline, "---"))
   3164 		{
   3165 			skip_eol = 1;
   3166 
   3167 			if (!(IN(state, ST_YAML)) && lineno > 1
   3168 				&& !read_yaml_macros_and_links)
   3169 				process_horizontal_rule(output);
   3170 			else
   3171 			{
   3172 				if (lineno == 1)
   3173 					SET(state, ST_YAML);
   3174 				else
   3175 					CLEAR(state, ST_YAML);
   3176 
   3177 				skip_change_first_line_in_doc = 1;
   3178 			}
   3179 			pline = NULL;
   3180 		}
   3181 		else if (colno == 1 && *(pline + 1) == ' ')
   3182 		{
   3183 			if (IN(state, ST_NUMLIST))
   3184 			{
   3185 				CLEAR(state, ST_NUMLIST);
   3186 				if (!read_yaml_macros_and_links)
   3187 				{
   3188 					process_list_item_end(output);
   3189 					process_numlist_end(output);
   3190 				}
   3191 			}
   3192 			if (!read_yaml_macros_and_links)
   3193 			{
   3194 				if (!(IN(state, ST_LIST)))
   3195 					process_list_start(output);
   3196 				else
   3197 					process_list_item_end(output);
   3198 				process_list_item_start(output);
   3199 				SET(state, ST_PARA_OPEN);
   3200 			}
   3201 
   3202 			processed_start_of_line = 1;
   3203 			skip_eol		= 0;
   3204 
   3205 			state |= ST_LIST;
   3206 
   3207 			pline += 2;
   3208 			colno += 2;
   3209 		}
   3210 		else if (IN(state, ST_SUMMARY) && *(pline + 1) == '/')
   3211 		{
   3212 			/* Output existing text up to -/ */
   3213 			process_text_token(output, line, link_prefix,
   3214 				first_line_in_doc, previous_line_blank,
   3215 				&previous_line_block, processed_start_of_line,
   3216 				read_yaml_macros_and_links, list_para, &token,
   3217 				&ptoken, &token_size, 1, 1);
   3218 			processed_start_of_line = 1;
   3219 
   3220 			state &= ~(ST_SUMMARY | ST_PARA_OPEN);
   3221 
   3222 			pline += 2;
   3223 			colno += 2;
   3224 		}
   3225 		else
   3226 		{
   3227 			CHECKCOPY(token, ptoken, token_size, pline);
   3228 			colno++;
   3229 		}
   3230 		break;
   3231 
   3232 	case ':':
   3233 		if (IN(state, ST_YAML)
   3234 			&& !(ANY(state,
   3235 				ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA
   3236 					| ST_HEADING | ST_IMAGE | ST_MACRO_BODY
   3237 					| ST_PRE | ST_TAG | ST_YAML_VAL))
   3238 			&& read_yaml_macros_and_links)
   3239 		{
   3240 			*ptoken = 0;
   3241 
   3242 			vars_count++;
   3243 
   3244 			if (!vars)
   3245 			{
   3246 				CALLOC(vars, KeyValue, vars_count);
   3247 				pvars = vars;
   3248 			}
   3249 			else
   3250 			{
   3251 				REALLOCARRAY(vars, KeyValue, vars_count);
   3252 				pvars = vars + vars_count - 1;
   3253 			}
   3254 			CALLOC(pvars->key, u8, KEYSIZE);
   3255 			MEMCCPY(pvars->key, (char*)token, KEYSIZE, temp);
   3256 			pvars->value	  = NULL;
   3257 			pvars->value_size = 0;
   3258 
   3259 			state |= ST_YAML_VAL;
   3260 			RESET_TOKEN(token, ptoken, token_size);
   3261 			pline++;
   3262 			colno++;
   3263 
   3264 			while (pline && (*pline == ' ' || *pline == '\t'))
   3265 			{
   3266 				pline++;
   3267 				colno++;
   3268 			}
   3269 		}
   3270 		else
   3271 		{
   3272 			CHECKCOPY(token, ptoken, token_size, pline);
   3273 			colno++;
   3274 		}
   3275 		break;
   3276 
   3277 	case '~':
   3278 		if (ANY(state,
   3279 			    ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA
   3280 				    | ST_HTML_TAG | ST_IMAGE | ST_KBD
   3281 				    | ST_LINK_SECOND_ARG
   3282 				    | ST_LINK_SECOND_ARG_END | ST_MACRO_BODY
   3283 				    | ST_PRE | ST_TAG | ST_YAML))
   3284 		{
   3285 			CHECKCOPY(token, ptoken, token_size, pline);
   3286 			colno++;
   3287 			break;
   3288 		}
   3289 
   3290 		if (strlen((char*)pline) > 1 && *(pline + 1) == '~')
   3291 		{
   3292 			u8* entity = (u8*)"&nbsp;";
   3293 			entity_len = strlen((char*)entity);
   3294 			/* Handle ~~ within footnotes, headings and link
   3295 			 * text specially */
   3296 			if (ANY(state,
   3297 				    ST_INLINE_FOOTNOTE | ST_HEADING
   3298 					    | ST_FOOTNOTE_TEXT | ST_LINK))
   3299 				APPEND_TOKEN(entity, entity_len);
   3300 			else
   3301 			{
   3302 				/* Output existing text up to ~~ */
   3303 				process_text_token(output, line, link_prefix,
   3304 					first_line_in_doc, previous_line_blank,
   3305 					&previous_line_block,
   3306 					processed_start_of_line,
   3307 					read_yaml_macros_and_links, list_para,
   3308 					&token, &ptoken, &token_size, 1, 1);
   3309 				processed_start_of_line = 1;
   3310 
   3311 				if (!read_yaml_macros_and_links
   3312 					&& !(ANY(state,
   3313 						ST_PRE | ST_CODE | ST_HEADING)))
   3314 					print_output(output, (char*)entity);
   3315 			}
   3316 
   3317 			pline += 2;
   3318 			colno += 2;
   3319 		}
   3320 		else if (strlen((char*)pline) > 1 && *(pline + 1) == '!')
   3321 		{
   3322 			u8* entity = (u8*)"&#8203;";
   3323 			entity_len = strlen((char*)entity);
   3324 			/* Handle ~! within footnotes, headings and link
   3325 			 * text specially */
   3326 			if (ANY(state,
   3327 				    ST_INLINE_FOOTNOTE | ST_HEADING
   3328 					    | ST_FOOTNOTE_TEXT | ST_LINK))
   3329 				APPEND_TOKEN(entity, entity_len);
   3330 			else
   3331 			{
   3332 				/* Output existing text up to ~! */
   3333 				process_text_token(output, line, link_prefix,
   3334 					first_line_in_doc, previous_line_blank,
   3335 					&previous_line_block,
   3336 					processed_start_of_line,
   3337 					read_yaml_macros_and_links, list_para,
   3338 					&token, &ptoken, &token_size, 1, 1);
   3339 				processed_start_of_line = 1;
   3340 
   3341 				if (!read_yaml_macros_and_links
   3342 					&& !(ANY(state,
   3343 						ST_PRE | ST_CODE | ST_HEADING)))
   3344 					print_output(output, (char*)entity);
   3345 			}
   3346 
   3347 			pline += 2;
   3348 			colno += 2;
   3349 		}
   3350 		else if (strlen((char*)pline) > 1 && *(pline + 1) == '-')
   3351 		{
   3352 			u8* entity = (u8*)"&#8209;";
   3353 			entity_len = strlen((char*)entity);
   3354 			/* Handle ~- within footnotes, headings and link
   3355 			 * text specially */
   3356 			if (ANY(state,
   3357 				    ST_INLINE_FOOTNOTE | ST_HEADING
   3358 					    | ST_FOOTNOTE_TEXT | ST_LINK))
   3359 				APPEND_TOKEN(entity, entity_len);
   3360 			else
   3361 			{
   3362 				/* Output existing text up to ~- */
   3363 				process_text_token(output, line, link_prefix,
   3364 					first_line_in_doc, previous_line_blank,
   3365 					&previous_line_block,
   3366 					processed_start_of_line,
   3367 					read_yaml_macros_and_links, list_para,
   3368 					&token, &ptoken, &token_size, 1, 1);
   3369 				processed_start_of_line = 1;
   3370 
   3371 				if (!read_yaml_macros_and_links
   3372 					&& !(ANY(state,
   3373 						ST_PRE | ST_CODE | ST_HEADING)))
   3374 					print_output(output, (char*)entity);
   3375 			}
   3376 
   3377 			pline += 2;
   3378 			colno += 2;
   3379 		}
   3380 		else
   3381 		{
   3382 			/* Handle ~ within footnotes, headings and link
   3383 			 * text specially */
   3384 			if (ANY(state,
   3385 				    ST_INLINE_FOOTNOTE | ST_HEADING
   3386 					    | ST_FOOTNOTE_TEXT | ST_LINK))
   3387 				APPEND_TOKEN(IN(state, ST_STRIKE) ? "</s>"
   3388 								  : "<s>",
   3389 					sizeof(IN(state, ST_STRIKE) ? "</s>"
   3390 								    : "<s>"));
   3391 			else
   3392 			{
   3393 				/* Output existing text up to ~ */
   3394 				process_text_token(output, line, link_prefix,
   3395 					first_line_in_doc, previous_line_blank,
   3396 					&previous_line_block,
   3397 					processed_start_of_line,
   3398 					read_yaml_macros_and_links, list_para,
   3399 					&token, &ptoken, &token_size, 1, 1);
   3400 				processed_start_of_line = 1;
   3401 
   3402 				if (!read_yaml_macros_and_links
   3403 					&& !(ANY(state,
   3404 						ST_PRE | ST_CODE | ST_HEADING)))
   3405 					process_strike(output,
   3406 						(IN(state, ST_STRIKE)));
   3407 			}
   3408 
   3409 			state ^= ST_STRIKE;
   3410 			pline++;
   3411 			colno++;
   3412 		}
   3413 		break;
   3414 
   3415 	case '`':
   3416 		if (ANY(state,
   3417 			    ST_DISPLAY_FORMULA | ST_FORMULA | ST_IMAGE
   3418 				    | ST_MACRO_BODY))
   3419 		{
   3420 			CHECKCOPY(token, ptoken, token_size, pline);
   3421 			colno++;
   3422 			break;
   3423 		}
   3424 
   3425 		if (colno == 1 && strlen((char*)pline) > 2
   3426 			&& startswith((char*)pline, "```"))
   3427 		{
   3428 			state ^= ST_PRE;
   3429 
   3430 			if (!read_yaml_macros_and_links)
   3431 			{
   3432 				if (IN(state, ST_PRE))
   3433 				{
   3434 					if (IN(state, ST_PARA_OPEN))
   3435 						state &= ~ST_PARA_OPEN;
   3436 					print_output(output, "<pre>");
   3437 				}
   3438 				else
   3439 				{
   3440 					print_output(output, "</pre>");
   3441 					previous_line_block = 1;
   3442 					/*if (IN(state, ST_PARA_OPEN))
   3443 						print_output(output, "<p>");*/
   3444 				}
   3445 				output_firstcol = 0;
   3446 			}
   3447 
   3448 			/* Skip the rest of the line (language) */
   3449 			pline = NULL;
   3450 		}
   3451 		else if (!ANY(state, ST_PRE | ST_TAG | ST_YAML))
   3452 		{
   3453 			/* Handle ` within footnotes, headings and link
   3454 			 * text specially */
   3455 			if (ANY(state,
   3456 				    ST_INLINE_FOOTNOTE | ST_HEADING
   3457 					    | ST_FOOTNOTE_TEXT | ST_LINK))
   3458 				APPEND_TOKEN(IN(state, ST_CODE) ? "</code>"
   3459 								: "<code>",
   3460 					sizeof(IN(state, ST_CODE) ? "</code>"
   3461 								  : "<code>"));
   3462 			else
   3463 			{
   3464 				/* Output existing text up to ` */
   3465 				process_text_token(output, line, link_prefix,
   3466 					first_line_in_doc, previous_line_blank,
   3467 					&previous_line_block,
   3468 					processed_start_of_line,
   3469 					read_yaml_macros_and_links, list_para,
   3470 					&token, &ptoken, &token_size, 1, 1);
   3471 				processed_start_of_line = 1;
   3472 
   3473 				if (!read_yaml_macros_and_links
   3474 					&& !(ANY(state, ST_PRE | ST_HEADING)))
   3475 					process_code(output, IN(state, ST_CODE));
   3476 			}
   3477 
   3478 			state ^= ST_CODE;
   3479 
   3480 			pline++;
   3481 			colno++;
   3482 		}
   3483 		else
   3484 		{
   3485 			CHECKCOPY(token, ptoken, token_size, pline);
   3486 			colno++;
   3487 		}
   3488 		break;
   3489 
   3490 	case '#':
   3491 		if (colno == 1
   3492 			&& !(ANY(state,
   3493 				ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA
   3494 					| ST_MACRO_BODY | ST_PRE | ST_YAML)))
   3495 		{
   3496 			if (IN(state, ST_LIST))
   3497 			{
   3498 				state &= ~ST_LIST;
   3499 				if (!read_yaml_macros_and_links)
   3500 				{
   3501 					process_list_item_end(output);
   3502 					process_list_end(output);
   3503 				}
   3504 			}
   3505 
   3506 			if (IN(state, ST_NUMLIST))
   3507 			{
   3508 				state &= ~ST_NUMLIST;
   3509 				if (!read_yaml_macros_and_links)
   3510 				{
   3511 					process_list_item_end(output);
   3512 					process_numlist_end(output);
   3513 				}
   3514 			}
   3515 
   3516 			state |= ST_HEADING;
   3517 			heading_level = 1;
   3518 			pline++;
   3519 			colno++;
   3520 		}
   3521 		else if (IN(state, ST_HEADING) && *(pline - 1) == '#')
   3522 		{
   3523 			if (heading_level < MAX_HEADING_LEVEL)
   3524 				heading_level++;
   3525 			pline++;
   3526 			colno++;
   3527 		}
   3528 		else
   3529 		{
   3530 			CHECKCOPY(token, ptoken, token_size, pline);
   3531 			colno++;
   3532 		}
   3533 		break;
   3534 
   3535 	case '_':
   3536 		if (ANY(state,
   3537 			    ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA
   3538 				    | ST_HTML_TAG | ST_IMAGE | ST_LINK_SECOND_ARG
   3539 				    | ST_LINK_SECOND_ARG_END | ST_MACRO_BODY
   3540 				    | ST_PRE | ST_TAG | ST_YAML))
   3541 		{
   3542 			CHECKCOPY(token, ptoken, token_size, pline);
   3543 			colno++;
   3544 			break;
   3545 		}
   3546 
   3547 		if (strlen((char*)pline) > 1 && *(pline + 1) == '_')
   3548 		{
   3549 			/* Handle __ within footnotes, headings and link
   3550 			 * text specially */
   3551 			if (ANY(state,
   3552 				    ST_INLINE_FOOTNOTE | ST_HEADING
   3553 					    | ST_FOOTNOTE_TEXT | ST_LINK))
   3554 				APPEND_TOKEN(IN(state, ST_BOLD) ? "</strong>"
   3555 								: "<strong>",
   3556 					sizeof(IN(state, ST_BOLD)
   3557 							? "</strong>"
   3558 							: "<strong>"));
   3559 			else
   3560 			{
   3561 				/* Output existing text up to __ */
   3562 				process_text_token(output, line, link_prefix,
   3563 					first_line_in_doc, previous_line_blank,
   3564 					&previous_line_block,
   3565 					processed_start_of_line,
   3566 					read_yaml_macros_and_links, list_para,
   3567 					&token, &ptoken, &token_size, 1, 1);
   3568 				processed_start_of_line = 1;
   3569 
   3570 				if (!read_yaml_macros_and_links
   3571 					&& !(ANY(state,
   3572 						ST_PRE | ST_CODE | ST_HEADING)))
   3573 					process_bold(output, IN(state, ST_BOLD));
   3574 			}
   3575 
   3576 			state ^= ST_BOLD;
   3577 			pline += 2;
   3578 			colno += 2;
   3579 		}
   3580 		else
   3581 		{
   3582 			/* Handle _ within footnotes, headings and link
   3583 			 * text specially */
   3584 			if (ANY(state,
   3585 				    ST_INLINE_FOOTNOTE | ST_HEADING
   3586 					    | ST_FOOTNOTE_TEXT | ST_LINK))
   3587 				APPEND_TOKEN(IN(state, ST_ITALIC) ? "</em>"
   3588 								  : "<em>",
   3589 					sizeof(IN(state, ST_ITALIC) ? "</em>"
   3590 								    : "<em>"));
   3591 			else
   3592 			{
   3593 				/* Output existing text up to _ */
   3594 				process_text_token(output, line, link_prefix,
   3595 					first_line_in_doc, previous_line_blank,
   3596 					&previous_line_block,
   3597 					processed_start_of_line,
   3598 					read_yaml_macros_and_links, list_para,
   3599 					&token, &ptoken, &token_size, 1, 1);
   3600 				processed_start_of_line = 1;
   3601 
   3602 				if (!read_yaml_macros_and_links
   3603 					&& !(ANY(state,
   3604 						ST_PRE | ST_CODE | ST_HEADING)))
   3605 					process_italic(output,
   3606 						IN(state, ST_ITALIC));
   3607 			}
   3608 
   3609 			state ^= ST_ITALIC;
   3610 			pline++;
   3611 			colno++;
   3612 		}
   3613 		break;
   3614 
   3615 	case 'o':
   3616 		if (ANY(state,
   3617 			    ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA
   3618 				    | ST_HTML_TAG | ST_IMAGE | ST_MACRO_BODY
   3619 				    | ST_PRE | ST_YAML))
   3620 		{
   3621 			CHECKCOPY(token, ptoken, token_size, pline);
   3622 			colno++;
   3623 			break;
   3624 		}
   3625 
   3626 		if (colno == 1 && strlen((char*)pline) > 1
   3627 			&& *(pline + 1) == ' ')
   3628 		{
   3629 			if (IN(state, ST_NUMLIST))
   3630 			{
   3631 				state &= ~ST_NUMLIST;
   3632 				if (!read_yaml_macros_and_links)
   3633 				{
   3634 					process_list_item_end(output);
   3635 					process_numlist_end(output);
   3636 				}
   3637 			}
   3638 			if (!read_yaml_macros_and_links)
   3639 			{
   3640 				if (!(IN(state, ST_LIST)))
   3641 					process_list_start(output);
   3642 				else
   3643 					process_list_item_end(output);
   3644 				process_list_item_start(output);
   3645 				state |= ST_PARA_OPEN;
   3646 			}
   3647 
   3648 			processed_start_of_line = 1;
   3649 			skip_eol		= 0;
   3650 
   3651 			state |= ST_LIST;
   3652 
   3653 			pline += 2;
   3654 			colno += 2;
   3655 		}
   3656 		else
   3657 		{
   3658 			CHECKCOPY(token, ptoken, token_size, pline);
   3659 			colno++;
   3660 		}
   3661 		break;
   3662 
   3663 	case '*':
   3664 		if (ANY(state,
   3665 			    ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA
   3666 				    | ST_HTML_TAG | ST_IMAGE | ST_MACRO_BODY
   3667 				    | ST_PRE | ST_YAML))
   3668 		{
   3669 			CHECKCOPY(token, ptoken, token_size, pline);
   3670 			colno++;
   3671 			break;
   3672 		}
   3673 
   3674 		pline_len = strlen((char*)pline);
   3675 		if (colno == 1 && pline_len > 1 && *(pline + 1) == '[')
   3676 		{
   3677 			process_line_start(output, line, link_prefix,
   3678 				first_line_in_doc, previous_line_blank,
   3679 				&previous_line_block,
   3680 				read_yaml_macros_and_links, list_para, 1);
   3681 
   3682 			/* Ignore abbreviations (for now) */
   3683 			pline = NULL;
   3684 		}
   3685 		else if (colno == 1 && pline_len > 2
   3686 			&& startswith((char*)pline, "***"))
   3687 		{
   3688 			skip_eol = 1;
   3689 			if (!read_yaml_macros_and_links)
   3690 				process_horizontal_rule(output);
   3691 			pline = NULL;
   3692 		}
   3693 		else if (colno == 1 && strlen((char*)pline) > 1
   3694 			&& *(pline + 1) == ' ')
   3695 		{
   3696 			if (IN(state, ST_NUMLIST))
   3697 			{
   3698 				CLEAR(state, ST_NUMLIST);
   3699 				if (!read_yaml_macros_and_links)
   3700 				{
   3701 					process_list_item_end(output);
   3702 					process_numlist_end(output);
   3703 				}
   3704 			}
   3705 			if (!read_yaml_macros_and_links)
   3706 			{
   3707 				if (!(IN(state, ST_LIST)))
   3708 					process_list_start(output);
   3709 				else
   3710 					process_list_item_end(output);
   3711 				process_list_item_start(output);
   3712 				SET(state, ST_PARA_OPEN);
   3713 			}
   3714 
   3715 			processed_start_of_line = 1;
   3716 			skip_eol		= 0;
   3717 			SET(state, ST_LIST);
   3718 			pline += 2;
   3719 			colno += 2;
   3720 		}
   3721 		else if (pline_len > 1 && *(pline + 1) == '*')
   3722 		{
   3723 			/* Handle ** within footnotes, headings and link
   3724 			 * text specially */
   3725 			if (ANY(state,
   3726 				    ST_INLINE_FOOTNOTE | ST_HEADING
   3727 					    | ST_FOOTNOTE_TEXT | ST_LINK))
   3728 				APPEND_TOKEN(IN(state, ST_BOLD) ? "</strong>"
   3729 								: "<strong>",
   3730 					sizeof(IN(state, ST_BOLD)
   3731 							? "</strong>"
   3732 							: "<strong>"));
   3733 			else
   3734 			{
   3735 				/* Output existing text up to * */
   3736 				process_text_token(output, line, link_prefix,
   3737 					first_line_in_doc, previous_line_blank,
   3738 					&previous_line_block,
   3739 					processed_start_of_line,
   3740 					read_yaml_macros_and_links, list_para,
   3741 					&token, &ptoken, &token_size, 1, 1);
   3742 				processed_start_of_line = 1;
   3743 
   3744 				if (!read_yaml_macros_and_links
   3745 					&& !(ANY(state,
   3746 						ST_PRE | ST_CODE | ST_HEADING)))
   3747 					process_bold(output, IN(state, ST_BOLD));
   3748 			}
   3749 			TOGGLE(state, ST_BOLD);
   3750 			pline += 2;
   3751 			colno += 2;
   3752 		}
   3753 		else
   3754 		{
   3755 			/* Handle * within footnotes, headings and link
   3756 			 * text specially */
   3757 			if (ANY(state,
   3758 				    ST_INLINE_FOOTNOTE | ST_HEADING
   3759 					    | ST_FOOTNOTE_TEXT | ST_LINK))
   3760 				APPEND_TOKEN(IN(state, ST_ITALIC) ? "</em>"
   3761 								  : "<em>",
   3762 					sizeof(IN(state, ST_ITALIC) ? "</em>"
   3763 								    : "<em>"));
   3764 			else
   3765 			{
   3766 				/* Output existing text up to * */
   3767 				process_text_token(output, line, link_prefix,
   3768 					first_line_in_doc, previous_line_blank,
   3769 					&previous_line_block,
   3770 					processed_start_of_line,
   3771 					read_yaml_macros_and_links, list_para,
   3772 					&token, &ptoken, &token_size, 1, 1);
   3773 				processed_start_of_line = 1;
   3774 				if (!read_yaml_macros_and_links
   3775 					&& !(ANY(state,
   3776 						ST_PRE | ST_CODE | ST_HEADING)))
   3777 					process_italic(output,
   3778 						IN(state, ST_ITALIC));
   3779 			}
   3780 			TOGGLE(state, ST_ITALIC);
   3781 			pline++;
   3782 			colno++;
   3783 		}
   3784 		break;
   3785 
   3786 	case ' ':
   3787 		if (ANY(state,
   3788 			    ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA | ST_IMAGE
   3789 				    | ST_MACRO_BODY | ST_PRE | ST_YAML_VAL))
   3790 		{
   3791 			CHECKCOPY(token, ptoken, token_size, pline);
   3792 			colno++;
   3793 			break;
   3794 		}
   3795 
   3796 		if ((IN(state, ST_HEADING)) && !(IN(state, ST_HEADING_TEXT)))
   3797 		{
   3798 			if (!read_yaml_macros_and_links)
   3799 			{
   3800 				heading_count++;
   3801 				process_heading_start(output, heading_level,
   3802 					heading_count);
   3803 				output_firstcol = 0;
   3804 			}
   3805 			SET(state, ST_HEADING_TEXT);
   3806 			pline++;
   3807 			colno++;
   3808 			break;
   3809 		}
   3810 
   3811 		if (colno == 1 && strlen((char*)pline) > 1
   3812 			&& startswith((char*)pline, "  "))
   3813 		{
   3814 			list_para = 1;
   3815 			process_text_token(output, line, link_prefix,
   3816 				first_line_in_doc, previous_line_blank,
   3817 				&previous_line_block, processed_start_of_line,
   3818 				read_yaml_macros_and_links, list_para, &token,
   3819 				&ptoken, &token_size, 1, 1);
   3820 			processed_start_of_line = 1;
   3821 			while (*pline == ' ' || *pline == '\t')
   3822 			{
   3823 				pline++;
   3824 				colno++;
   3825 			}
   3826 			break;
   3827 		}
   3828 
   3829 		if (strlen((char*)pline) == 2 && *(pline + 1) == ' ')
   3830 		{
   3831 			APPEND_TOKEN("<br>", sizeof("<br>"));
   3832 			output_firstcol = 0;
   3833 			pline += 2;
   3834 			colno++;
   3835 		}
   3836 		else if (IN(state, ST_LINK_MACRO))
   3837 		{
   3838 			*ptoken = 0;
   3839 			MEMCCPY(link_macro, (char*)token, BUFSIZE, temp);
   3840 			*(link_macro + strlen((char*)token)) = 0;
   3841 			RESET_TOKEN(token, ptoken, token_size);
   3842 			CLEAR(state, ST_LINK_MACRO);
   3843 		}
   3844 		else if (!(IN(state, ST_HEADING) && *(pline - 1) == '#'))
   3845 			CHECKCOPY(token, ptoken, token_size, pline);
   3846 		colno++;
   3847 		break;
   3848 
   3849 	case '{':
   3850 		if (ANY(state,
   3851 			    ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA
   3852 				    | ST_HTML_TAG | ST_PRE | ST_YAML
   3853 				    | ST_YAML_VAL))
   3854 		{
   3855 			CHECKCOPY(token, ptoken, token_size, pline);
   3856 			colno++;
   3857 			break;
   3858 		}
   3859 
   3860 		if (!IN(state, ST_MACRO_BODY))
   3861 			goto parse_lbrace_not_macro;
   3862 
   3863 		*ptoken	  = 0;
   3864 		token_len = strlen((char*)token);
   3865 		skip_eol  = 1;
   3866 		if (read_yaml_macros_and_links)
   3867 		{
   3868 			if (!macros)
   3869 			{
   3870 				macros_count = 1;
   3871 				CALLOC(macros, KeyValue, macros_count);
   3872 				pmacros = macros;
   3873 			}
   3874 			if (pmacros->value)
   3875 			{
   3876 				size_t value_len
   3877 					= strlen((char*)pmacros->value);
   3878 				if (pmacros->value_size
   3879 					< value_len + token_len + 1)
   3880 				{
   3881 					pmacros->value_size += token_len;
   3882 					REALLOC(pmacros->value, u8,
   3883 						pmacros->value_size);
   3884 				}
   3885 				MEMCCPY((pmacros->value + value_len), token,
   3886 					pmacros->value_size - value_len, temp);
   3887 			}
   3888 			else
   3889 			{
   3890 				pmacros->value_size = token_len + 1;
   3891 				CALLOC(pmacros->value, u8, pmacros->value_size);
   3892 				MEMCCPY(pmacros->value, (char*)token,
   3893 					pmacros->value_size, temp);
   3894 			}
   3895 		}
   3896 		goto parse_lbrace_end;
   3897 
   3898 	parse_lbrace_not_macro:
   3899 		/* Output existing text up to { */
   3900 		process_text_token(output, line, link_prefix, first_line_in_doc,
   3901 			previous_line_blank, &previous_line_block,
   3902 			processed_start_of_line, read_yaml_macros_and_links,
   3903 			list_para, &token, &ptoken, &token_size, 0, 1);
   3904 		processed_start_of_line = 1;
   3905 
   3906 	parse_lbrace_end:
   3907 		SET(state, ST_TAG);
   3908 		RESET_TOKEN(token, ptoken, token_size);
   3909 		pline++;
   3910 		colno++;
   3911 		break;
   3912 
   3913 	case '/':
   3914 		if (ANY(state,
   3915 			    ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA | ST_IMAGE
   3916 				    | ST_PRE | ST_HEADING | ST_YAML_VAL))
   3917 		{
   3918 			CHECKCOPY(token, ptoken, token_size, pline);
   3919 			colno++;
   3920 			break;
   3921 		}
   3922 
   3923 		if ((IN(state, ST_TAG)) && pline != line && *(pline - 1) == '{')
   3924 		{
   3925 			end_tag = 1;
   3926 			pline++;
   3927 		}
   3928 		else if (*(pline + 1) == '-')
   3929 		{
   3930 			/* Output existing text up to /- */
   3931 			process_text_token(output, line, link_prefix,
   3932 				first_line_in_doc, previous_line_blank,
   3933 				&previous_line_block, processed_start_of_line,
   3934 				read_yaml_macros_and_links, list_para, &token,
   3935 				&ptoken, &token_size, 0, 0);
   3936 			processed_start_of_line = 1;
   3937 			SET(state, ST_SUMMARY);
   3938 			if (!read_yaml_macros_and_links)
   3939 			{
   3940 				SET(state, ST_PARA_OPEN);
   3941 				print_output(output, "<p>\n");
   3942 				output_firstcol = 1;
   3943 			}
   3944 			pline += 2;
   3945 			colno++;
   3946 		}
   3947 		else
   3948 			CHECKCOPY(token, ptoken, token_size, pline);
   3949 
   3950 		colno++;
   3951 		break;
   3952 
   3953 	case '}':
   3954 		if (ANY(state,
   3955 			    ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA | ST_IMAGE
   3956 				    | ST_PRE | ST_YAML | ST_YAML_VAL))
   3957 		{
   3958 			CHECKCOPY(token, ptoken, token_size, pline);
   3959 			colno++;
   3960 			break;
   3961 		}
   3962 
   3963 		*ptoken = 0;
   3964 		if (IN(state, ST_MACRO_BODY)
   3965 			&& !(end_tag && startswith((const char*)token, "=")))
   3966 		{
   3967 			if (read_yaml_macros_and_links)
   3968 			{
   3969 				token_len = strlen((char*)token);
   3970 				skip_eol  = 1;
   3971 				value_len = strlen((char*)pmacros->value);
   3972 				if (pmacros->value_size
   3973 					< value_len + token_len + 4)
   3974 				{
   3975 					pmacros->value_size += token_len + 3;
   3976 					REALLOC(pmacros->value, u8,
   3977 						pmacros->value_size);
   3978 				}
   3979 				if ((size_t)snprintf((char*)pmacros->value,
   3980 					    pmacros->value_size, "{%s%s}",
   3981 					    end_tag ? "/" : "", (char*)token)
   3982 					> pmacros->value_size)
   3983 					warning(1, (u8*)"snprintf:%d: Overflow",
   3984 						__LINE__);
   3985 			}
   3986 
   3987 			*token = 0;
   3988 			ptoken = token;
   3989 			CLEAR(state, ST_TAG);
   3990 			end_tag = 0;
   3991 		}
   3992 		else if (IN(state, ST_TAG))
   3993 		{
   3994 			CLEAR(state, ST_TAG);
   3995 			if (break_mark)
   3996 			{
   3997 				CLEAR(state, ST_PARA_OPEN);
   3998 				if (IN(state, ST_LIST))
   3999 				{
   4000 					CLEAR(state, ST_LIST);
   4001 					if (!read_yaml_macros_and_links)
   4002 					{
   4003 						process_list_item_end(output);
   4004 						process_list_end(output);
   4005 					}
   4006 				}
   4007 				else if (IN(state, ST_NUMLIST))
   4008 				{
   4009 					CLEAR(state, ST_NUMLIST);
   4010 					if (!read_yaml_macros_and_links)
   4011 					{
   4012 						process_list_item_end(output);
   4013 						process_numlist_end(output);
   4014 					}
   4015 				}
   4016 			}
   4017 			if (!strcmp((char*)token, "made-by"))
   4018 				madeby_present = 1;
   4019 			else
   4020 				process_tag(output, token, source_filename,
   4021 					link_prefix, footer_permalink_text,
   4022 					(const char*)permalink_url,
   4023 					ext_in_permalink
   4024 						&& *ext_in_permalink != '0',
   4025 					read_yaml_macros_and_links, &skip_eol,
   4026 					end_tag);
   4027 
   4028 			RESET_TOKEN(token, ptoken, token_size);
   4029 			break_mark	    = 0;
   4030 			end_tag		    = 0;
   4031 			previous_line_block = 0;
   4032 		}
   4033 		pline++;
   4034 		colno++;
   4035 		break;
   4036 
   4037 	case '|':
   4038 		if (ANY(state,
   4039 			    ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA
   4040 				    | ST_HTML_TAG | ST_IMAGE | ST_PRE | ST_TAG
   4041 				    | ST_YAML))
   4042 		{
   4043 			CHECKCOPY(token, ptoken, token_size, pline);
   4044 			colno++;
   4045 			break;
   4046 		}
   4047 
   4048 		if (strlen((char*)pline) > 1 && *(pline + 1) == '|')
   4049 		{
   4050 			/* Handle || within footnotes, headings and link
   4051 			 * text specially */
   4052 			if (ANY(state,
   4053 				    ST_INLINE_FOOTNOTE | ST_HEADING
   4054 					    | ST_FOOTNOTE_TEXT | ST_LINK))
   4055 				APPEND_TOKEN(IN(state, ST_KBD) ? "</kbd>"
   4056 							       : "<kbd>",
   4057 					sizeof(IN(state, ST_KBD) ? "</kbd>"
   4058 								 : "<kbd>"));
   4059 			else
   4060 			{
   4061 				/* Output existing text up to || */
   4062 				process_text_token(output, line, link_prefix,
   4063 					first_line_in_doc, previous_line_blank,
   4064 					&previous_line_block,
   4065 					processed_start_of_line,
   4066 					read_yaml_macros_and_links, list_para,
   4067 					&token, &ptoken, &token_size, 1, 1);
   4068 				processed_start_of_line = 1;
   4069 
   4070 				if (!read_yaml_macros_and_links
   4071 					&& !(ANY(state,
   4072 						ST_PRE | ST_CODE | ST_HEADING)))
   4073 				{
   4074 					TOGGLE(state, ST_KBD);
   4075 					process_kbd(output,
   4076 						!(IN(state, ST_KBD)));
   4077 					output_firstcol = 0;
   4078 				}
   4079 			}
   4080 			pline += 2;
   4081 			colno += 2;
   4082 		}
   4083 		/* Partial tables (for templating) */
   4084 		else if (!(IN(state, ST_PRE)) && colno == 1
   4085 			&& strlen((char*)pline) > 1 && *(pline + 1) == '@')
   4086 		{
   4087 			skip_eol = 1;
   4088 			pline += 2;
   4089 			colno += 2;
   4090 			switch (*pline)
   4091 			{
   4092 			case '\\':
   4093 				if (!read_yaml_macros_and_links)
   4094 				{
   4095 					process_table_start(output);
   4096 					output_firstcol = 0;
   4097 				}
   4098 				pline = NULL;
   4099 				break;
   4100 
   4101 			case '#':
   4102 				SET(state, ST_TABLE_HEADER);
   4103 				pline++;
   4104 				colno++;
   4105 				break;
   4106 
   4107 			case '-':
   4108 				CLEAR(state, ST_TABLE_HEADER);
   4109 				if (!read_yaml_macros_and_links)
   4110 				{
   4111 					process_table_body_start(output, 0);
   4112 					output_firstcol = 1;
   4113 				}
   4114 				pline = NULL;
   4115 				break;
   4116 
   4117 			case ' ':
   4118 				SET(state, ST_TABLE);
   4119 				if (!read_yaml_macros_and_links)
   4120 				{
   4121 					process_table_body_row_start(output);
   4122 					output_firstcol = 0;
   4123 				}
   4124 				break;
   4125 
   4126 			case '/':
   4127 				CLEAR(state, ST_TABLE);
   4128 				if (!read_yaml_macros_and_links)
   4129 				{
   4130 					process_table_end(output);
   4131 					output_firstcol = 1;
   4132 				}
   4133 				pline = NULL;
   4134 				break;
   4135 
   4136 			default:
   4137 				warning(1, (u8*)"Invalid partial table line");
   4138 				CHECKCOPY(token, ptoken, token_size, pline);
   4139 				colno++;
   4140 			}
   4141 		}
   4142 		else if (!(IN(state, ST_PRE)) && colno == 1)
   4143 		{
   4144 			skip_eol = 1;
   4145 			if (IN(state, ST_TABLE_HEADER))
   4146 			{
   4147 				CLEAR(state, ST_TABLE_HEADER);
   4148 				SET(state, ST_TABLE_LINE);
   4149 				pline = NULL;
   4150 				break;
   4151 			}
   4152 
   4153 			if (IN(state, ST_TABLE_LINE))
   4154 			{
   4155 				CLEAR(state, ST_TABLE_LINE);
   4156 				SET(state, ST_TABLE);
   4157 				if (!read_yaml_macros_and_links)
   4158 				{
   4159 					process_table_body_start(output, 1);
   4160 					output_firstcol = 0;
   4161 				}
   4162 			}
   4163 			else if (IN(state, ST_TABLE))
   4164 			{
   4165 				if (!read_yaml_macros_and_links)
   4166 				{
   4167 					process_table_body_row_start(output);
   4168 					output_firstcol = 0;
   4169 				}
   4170 			}
   4171 			else
   4172 			{
   4173 				SET(state, ST_TABLE_HEADER);
   4174 				if (!read_yaml_macros_and_links)
   4175 				{
   4176 					process_table_start(output);
   4177 					output_firstcol = 0;
   4178 				}
   4179 			}
   4180 
   4181 			pline++;
   4182 			colno++;
   4183 		}
   4184 		else if (IN(state, ST_TABLE_HEADER))
   4185 		{
   4186 			*ptoken = 0;
   4187 			if (!read_yaml_macros_and_links)
   4188 			{
   4189 				print_output(output, "%s", token);
   4190 				if (*token)
   4191 					output_firstcol = 0;
   4192 				if (strlen((char*)pline) > 1)
   4193 				{
   4194 					process_table_header_cell(output);
   4195 					output_firstcol = 0;
   4196 				}
   4197 				else
   4198 				{
   4199 					process_table_header_end(output);
   4200 					output_firstcol = 1;
   4201 				}
   4202 			}
   4203 			RESET_TOKEN(token, ptoken, token_size);
   4204 			pline++;
   4205 			colno++;
   4206 		}
   4207 		else if (IN(state, ST_TABLE))
   4208 		{
   4209 			*ptoken = 0;
   4210 			if (!read_yaml_macros_and_links)
   4211 			{
   4212 				print_output(output, "%s", token);
   4213 				if (*token)
   4214 					output_firstcol = 0;
   4215 				if (strlen((char*)pline) > 1)
   4216 				{
   4217 					process_table_body_cell(output);
   4218 					output_firstcol = 0;
   4219 				}
   4220 				else
   4221 				{
   4222 					process_table_body_row_end(output);
   4223 					output_firstcol = 1;
   4224 				}
   4225 			}
   4226 			RESET_TOKEN(token, ptoken, token_size);
   4227 			pline++;
   4228 			colno++;
   4229 		}
   4230 		else
   4231 		{
   4232 			CHECKCOPY(token, ptoken, token_size, pline);
   4233 			colno++;
   4234 		}
   4235 		break;
   4236 
   4237 	case '<':
   4238 		if (read_yaml_macros_and_links
   4239 			|| ANY(state,
   4240 				ST_DISPLAY_FORMULA | ST_FORMULA | ST_IMAGE))
   4241 		{
   4242 			CHECKCOPY(token, ptoken, token_size, pline);
   4243 			colno++;
   4244 			break;
   4245 		}
   4246 
   4247 		if (ANY(state, ST_CODE | ST_KBD | ST_PRE))
   4248 		{
   4249 			APPEND_TOKEN("&lt;", sizeof("&lt;"));
   4250 			pline++;
   4251 			colno++;
   4252 			break;
   4253 		}
   4254 		SET(state, ST_HTML_TAG);
   4255 		CHECKCOPY(token, ptoken, token_size, pline);
   4256 		colno++;
   4257 		break;
   4258 
   4259 	case '>':
   4260 		CLEAR(state, ST_HTML_TAG);
   4261 		if (ANY(state,
   4262 			    ST_DISPLAY_FORMULA | ST_FORMULA | ST_IMAGE
   4263 				    | ST_MACRO_BODY))
   4264 		{
   4265 			CHECKCOPY(token, ptoken, token_size, pline);
   4266 			colno++;
   4267 			break;
   4268 		}
   4269 
   4270 		if (!read_yaml_macros_and_links
   4271 			&& !ANY(state, ST_CODE | ST_HEADING | ST_PRE)
   4272 			&& colno == 1)
   4273 		{
   4274 			if (!read_yaml_macros_and_links
   4275 				&& !(IN(state, ST_BLOCKQUOTE)))
   4276 			{
   4277 				CLEAR(state, ST_PARA_OPEN);
   4278 				process_blockquote(output, 0);
   4279 				output_firstcol = 0;
   4280 			}
   4281 			SET(state, ST_BLOCKQUOTE);
   4282 			pline++;
   4283 			colno++;
   4284 		}
   4285 		else
   4286 		{
   4287 			CHECKCOPY(token, ptoken, token_size, pline);
   4288 			colno++;
   4289 		}
   4290 		break;
   4291 
   4292 	case '!':
   4293 		if (ANY(state,
   4294 			    ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA
   4295 				    | ST_FOOTNOTE_TEXT | ST_HEADING | ST_IMAGE
   4296 				    | ST_INLINE_FOOTNOTE | ST_LINK
   4297 				    | ST_MACRO_BODY | ST_PRE))
   4298 		{
   4299 			CHECKCOPY(token, ptoken, token_size, pline);
   4300 			colno++;
   4301 			break;
   4302 		}
   4303 
   4304 		if ((IN(state, ST_TAG)) && pline != line
   4305 			&& (*(pline - 1) == '{'
   4306 				|| (end_tag && pline != line + 1
   4307 					&& *(pline - 1) == '/'
   4308 					&& *(pline - 2) == '{')))
   4309 		{
   4310 			break_mark = 1;
   4311 			pline++;
   4312 		}
   4313 		else if (strlen((char*)pline) > 1 && *(pline + 1) == '[')
   4314 		{
   4315 			/* Output existing text up to ! */
   4316 			*ptoken = 0;
   4317 			process_text_token(output, line, link_prefix,
   4318 				first_line_in_doc, previous_line_blank,
   4319 				&previous_line_block, processed_start_of_line,
   4320 				read_yaml_macros_and_links, list_para, &token,
   4321 				&ptoken, &token_size, 0, 1);
   4322 			processed_start_of_line = 1;
   4323 			RESET_TOKEN(token, ptoken, token_size);
   4324 			SET(state, ST_IMAGE);
   4325 			keep_token = 1;
   4326 			pline += 2;
   4327 			colno += 2;
   4328 		}
   4329 		else
   4330 		{
   4331 			CHECKCOPY(token, ptoken, token_size, pline);
   4332 			colno++;
   4333 		}
   4334 
   4335 		break;
   4336 
   4337 	case '=':
   4338 		if (ANY(state,
   4339 			    ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA
   4340 				    | ST_MACRO_BODY | ST_PRE | ST_TAG))
   4341 		{
   4342 			CHECKCOPY(token, ptoken, token_size, pline);
   4343 			colno++;
   4344 			break;
   4345 		}
   4346 
   4347 		if (colno != 1 && *(pline - 1) == '[')
   4348 		{
   4349 			SET(state, ST_LINK_MACRO);
   4350 			pline++;
   4351 			colno++;
   4352 		}
   4353 		else
   4354 		{
   4355 			CHECKCOPY(token, ptoken, token_size, pline);
   4356 			colno++;
   4357 		}
   4358 		break;
   4359 
   4360 	case '[':
   4361 		pline_len = strlen((char*)pline);
   4362 		if (ANY(state,
   4363 			    ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA
   4364 				    | ST_HEADING | ST_IMAGE | ST_MACRO_BODY
   4365 				    | ST_PRE))
   4366 		{
   4367 			CHECKCOPY(token, ptoken, token_size, pline);
   4368 			colno++;
   4369 			break;
   4370 		}
   4371 
   4372 		if (IN(state, ST_FOOTNOTE_TEXT))
   4373 			CLEAR(state, ST_FOOTNOTE_TEXT | ST_PARA_OPEN);
   4374 
   4375 		if (pline_len > 1 && *(pline + 1) == '^')
   4376 		{
   4377 			footnote_at_line_start = colno == 1;
   4378 			if (*token)
   4379 			{
   4380 				/* Output existing text up to [^ */
   4381 				*ptoken = 0;
   4382 				process_text_token(output, line, link_prefix,
   4383 					first_line_in_doc, previous_line_blank,
   4384 					&previous_line_block,
   4385 					processed_start_of_line,
   4386 					read_yaml_macros_and_links, list_para,
   4387 					&token, &ptoken, &token_size, 1, 1);
   4388 			}
   4389 			processed_start_of_line = 1;
   4390 
   4391 			RESET_TOKEN(token, ptoken, token_size);
   4392 			SET(state, ST_FOOTNOTE);
   4393 			pline += 2;
   4394 			colno += 2;
   4395 			break;
   4396 		}
   4397 
   4398 		if (*token)
   4399 		{
   4400 			/* Output existing text up to [ */
   4401 			*ptoken = 0;
   4402 			process_text_token(output, line, link_prefix,
   4403 				first_line_in_doc, previous_line_blank,
   4404 				&previous_line_block, processed_start_of_line,
   4405 				read_yaml_macros_and_links, list_para, &token,
   4406 				&ptoken, &token_size, 1, 1);
   4407 		}
   4408 		processed_start_of_line = 1;
   4409 
   4410 		RESET_TOKEN(token, ptoken, token_size);
   4411 		SET(state, ST_LINK);
   4412 		keep_token  = 1;
   4413 		*link_macro = 0;
   4414 		pline++;
   4415 		colno++;
   4416 
   4417 		if (*pline == '(')
   4418 		{
   4419 			APPEND_TOKEN("<span>", sizeof("<span>"));
   4420 			SET(state, ST_LINK_SPAN);
   4421 			pline++;
   4422 			colno++;
   4423 		}
   4424 
   4425 		break;
   4426 
   4427 	case '(':
   4428 		if (ANY(state,
   4429 			    ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA
   4430 				    | ST_FOOTNOTE_TEXT | ST_HEADING
   4431 				    | ST_INLINE_FOOTNOTE | ST_LINK_SECOND_ARG_END
   4432 				    | ST_MACRO_BODY | ST_PRE))
   4433 			CHECKCOPY(token, ptoken, token_size, pline);
   4434 		else if (*link_macro && (IN(state, ST_LINK)))
   4435 		{
   4436 			APPEND_TOKEN("<span>", sizeof("<span>"));
   4437 			SET(state, ST_LINK_SPAN);
   4438 			pline++;
   4439 		}
   4440 		else
   4441 			CHECKCOPY(token, ptoken, token_size, pline);
   4442 		colno++;
   4443 		break;
   4444 
   4445 	case ')':
   4446 		if (ANY(state,
   4447 			    ST_DISPLAY_FORMULA | ST_FORMULA
   4448 				    | ST_LINK_SECOND_ARG_END))
   4449 		{
   4450 			CHECKCOPY(token, ptoken, token_size, pline);
   4451 			colno++;
   4452 			break;
   4453 		}
   4454 
   4455 		pline_len = strlen((char*)pline);
   4456 
   4457 		if (IN(state, ST_LINK_SPAN) && pline_len > 1
   4458 			&& *(pline + 1) == ']')
   4459 		{
   4460 			APPEND_TOKEN("</span>", sizeof("</span>"));
   4461 			CLEAR(state, ST_LINK_SPAN);
   4462 			pline++;
   4463 			colno++;
   4464 		}
   4465 		else if (ANY(state, ST_LINK_SECOND_ARG | ST_IMAGE_SECOND_ARG))
   4466 		{
   4467 			*ptoken = 0;
   4468 			if (!read_yaml_macros_and_links)
   4469 			{
   4470 				if (IN(state, ST_LINK_SECOND_ARG))
   4471 					process_inline_link(output, link_text,
   4472 						link_prefix,
   4473 						get_value(macros, macros_count,
   4474 							link_macro, NULL),
   4475 						token);
   4476 				else
   4477 				{
   4478 					if (add_figcaption
   4479 						&& IN(state, ST_PARA_OPEN))
   4480 						CLEAR(state, ST_PARA_OPEN);
   4481 					process_inline_image(output, link_text,
   4482 						image_file_prefix, link_prefix,
   4483 						token, add_image_links,
   4484 						add_figcaption);
   4485 					if (add_figcaption)
   4486 						previous_line_block = 1;
   4487 				}
   4488 			}
   4489 			RESET_TOKEN(token, ptoken, token_size);
   4490 			CLEAR(state,
   4491 				ST_LINK | ST_LINK_SECOND_ARG | ST_IMAGE
   4492 					| ST_IMAGE_SECOND_ARG);
   4493 			pline++;
   4494 		}
   4495 		else
   4496 			CHECKCOPY(token, ptoken, token_size, pline);
   4497 		colno++;
   4498 		break;
   4499 
   4500 	case ']':
   4501 		if (ANY(state,
   4502 			    ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA
   4503 				    | ST_HEADING | ST_MACRO_BODY | ST_PRE))
   4504 		{
   4505 			CHECKCOPY(token, ptoken, token_size, pline);
   4506 			colno++;
   4507 			break;
   4508 		}
   4509 
   4510 		*ptoken = 0;
   4511 		if (IN(state, ST_INLINE_FOOTNOTE))
   4512 		{
   4513 			process_inline_footnote(token,
   4514 				read_yaml_macros_and_links, output);
   4515 			keep_token = 0;
   4516 			RESET_TOKEN(token, ptoken, token_size);
   4517 			CLEAR(state, ST_INLINE_FOOTNOTE);
   4518 			pline++;
   4519 			colno++;
   4520 		}
   4521 		else if (IN(state, ST_FOOTNOTE))
   4522 		{
   4523 			int footnote_definition = (strlen((char*)pline) > 1)
   4524 				&& (*(pline + 1) == ':')
   4525 				&& footnote_at_line_start;
   4526 			process_footnote(output, token,
   4527 				footnote_definition
   4528 					&& read_yaml_macros_and_links,
   4529 				!footnote_definition
   4530 					&& !read_yaml_macros_and_links);
   4531 			RESET_TOKEN(token, ptoken, token_size);
   4532 			CLEAR(state, ST_FOOTNOTE);
   4533 			footnote_at_line_start = 0;
   4534 			pline++;
   4535 			colno++;
   4536 			if (footnote_definition)
   4537 			{
   4538 				pline++;
   4539 				colno++;
   4540 				while (*pline == ' ' || *pline == '\t')
   4541 				{
   4542 					pline++;
   4543 					colno++;
   4544 				}
   4545 				SET(state, ST_FOOTNOTE_TEXT);
   4546 			}
   4547 		}
   4548 		else if (IN(state, ST_LINK_SECOND_ARG))
   4549 		{
   4550 			if (!read_yaml_macros_and_links)
   4551 				process_link(output, link_text, link_prefix,
   4552 					get_value(macros, macros_count,
   4553 						link_macro, NULL),
   4554 					token);
   4555 			RESET_TOKEN(token, ptoken, token_size);
   4556 			CLEAR(state, ST_LINK | ST_LINK_SECOND_ARG);
   4557 			pline++;
   4558 			colno++;
   4559 		}
   4560 		else if (IN(state, ST_IMAGE_SECOND_ARG))
   4561 		{
   4562 			if (!read_yaml_macros_and_links)
   4563 			{
   4564 				if (add_figcaption && IN(state, ST_PARA_OPEN))
   4565 					CLEAR(state, ST_PARA_OPEN);
   4566 				process_image(output, link_text,
   4567 					image_file_prefix, link_prefix, token,
   4568 					add_image_links, add_figcaption);
   4569 				if (add_figcaption)
   4570 					previous_line_block = 1;
   4571 			}
   4572 			RESET_TOKEN(token, ptoken, token_size);
   4573 			CLEAR(state, ST_IMAGE | ST_IMAGE_SECOND_ARG);
   4574 			pline++;
   4575 			colno++;
   4576 		}
   4577 		else if (strlen((char*)pline) > 1)
   4578 		{
   4579 			switch (*(pline + 1))
   4580 			{
   4581 			case ':':
   4582 				process_line_start(output, line, link_prefix,
   4583 					first_line_in_doc, previous_line_blank,
   4584 					&previous_line_block,
   4585 					read_yaml_macros_and_links, list_para,
   4586 					0);
   4587 
   4588 				links_count++;
   4589 
   4590 				if (!links)
   4591 				{
   4592 					CALLOC(links, KeyValue, links_count);
   4593 					plinks = links;
   4594 				}
   4595 				else
   4596 				{
   4597 					REALLOCARRAY(links, KeyValue,
   4598 						links_count);
   4599 					plinks = links + links_count - 1;
   4600 				}
   4601 				CALLOC(plinks->key, u8, KEYSIZE);
   4602 				MEMCCPY(plinks->key, (char*)token, KEYSIZE,
   4603 					temp);
   4604 				plinks->value	   = NULL;
   4605 				plinks->value_size = 0;
   4606 				pline += 2;
   4607 				colno += 2;
   4608 				while (pline
   4609 					&& (*pline == ' ' || *pline == '\t'))
   4610 				{
   4611 					pline++;
   4612 					colno++;
   4613 				}
   4614 				RESET_TOKEN(token, ptoken, token_size);
   4615 				keep_token = 0;
   4616 				SET(state, ST_LINK_SECOND_ARG_END);
   4617 				break;
   4618 
   4619 			case '[':
   4620 			case '(':
   4621 				token_len = strlen((char*)token);
   4622 				if (link_text && token_len + 1 > link_size)
   4623 				{
   4624 					link_size = token_len + 1;
   4625 					REALLOC(link_text, u8, link_size);
   4626 				}
   4627 				else if (!link_text)
   4628 				{
   4629 					link_size = token_len + 1;
   4630 					CALLOC(link_text, u8, link_size);
   4631 				}
   4632 				MEMCCPY(link_text, (char*)token, link_size,
   4633 					temp);
   4634 				pline += 2;
   4635 				colno += 2;
   4636 				RESET_TOKEN(token, ptoken, token_size);
   4637 				keep_token = 0;
   4638 				if (IN(state, ST_LINK))
   4639 					SET(state, ST_LINK_SECOND_ARG);
   4640 				else
   4641 					SET(state, ST_IMAGE_SECOND_ARG);
   4642 				break;
   4643 			default:
   4644 				CHECKCOPY(token, ptoken, token_size, pline);
   4645 				colno++;
   4646 				CLEAR(state, ST_LINK | ST_IMAGE);
   4647 				break;
   4648 			}
   4649 		}
   4650 		else
   4651 		{
   4652 			CHECKCOPY(token, ptoken, token_size, pline);
   4653 			colno++;
   4654 		}
   4655 		break;
   4656 
   4657 	case '^':
   4658 		if (ANY(state,
   4659 			    ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA
   4660 				    | ST_FOOTNOTE_TEXT | ST_IMAGE
   4661 				    | ST_INLINE_FOOTNOTE | ST_PRE | ST_YAML))
   4662 		{
   4663 			CHECKCOPY(token, ptoken, token_size, pline);
   4664 			colno++;
   4665 			break;
   4666 		}
   4667 
   4668 		if (strlen((char*)pline) > 1 && *(pline + 1) == '[')
   4669 		{
   4670 			/* Output existing text up to ^[ */
   4671 			*ptoken = 0;
   4672 			process_text_token(output, line, link_prefix,
   4673 				first_line_in_doc, previous_line_blank,
   4674 				&previous_line_block, processed_start_of_line,
   4675 				read_yaml_macros_and_links, list_para, &token,
   4676 				&ptoken, &token_size, 1, 1);
   4677 			processed_start_of_line = 1;
   4678 			RESET_TOKEN(token, ptoken, token_size);
   4679 			SET(state, ST_INLINE_FOOTNOTE);
   4680 			keep_token = 1;
   4681 			pline += 2;
   4682 			colno += 2;
   4683 			break;
   4684 		}
   4685 
   4686 		CHECKCOPY(token, ptoken, token_size, pline);
   4687 		colno++;
   4688 		break;
   4689 
   4690 	case '\\':
   4691 		*ptoken = 0;
   4692 		if (ANY(state,
   4693 			    ST_DISPLAY_FORMULA | ST_FORMULA | ST_HTML_TAG
   4694 				    | ST_IMAGE | ST_LINK_SECOND_ARG
   4695 				    | ST_LINK_SECOND_ARG_END | ST_MACRO_BODY))
   4696 		{
   4697 			CHECKCOPY(token, ptoken, token_size, pline);
   4698 			colno++;
   4699 			break;
   4700 		}
   4701 
   4702 		if (strlen((char*)pline) > 1)
   4703 		{
   4704 			pline++;
   4705 			colno++;
   4706 
   4707 			CHECKCOPY(token, ptoken, token_size, pline);
   4708 		}
   4709 		else
   4710 		{
   4711 			pline = NULL;
   4712 			break;
   4713 		}
   4714 
   4715 		colno++;
   4716 		break;
   4717 
   4718 	case '$':
   4719 		if (ANY(state,
   4720 			    ST_CODE | ST_CSV_BODY | ST_IMAGE | ST_PRE
   4721 				    | ST_TSV_BODY | ST_YAML))
   4722 		{
   4723 			CHECKCOPY(token, ptoken, token_size, pline);
   4724 			colno++;
   4725 			break;
   4726 		}
   4727 
   4728 		if (strlen((char*)pline) > 1 && *(pline + 1) == '$')
   4729 		{
   4730 			if (IN(state, ST_FORMULA))
   4731 				exit(error(ERR_NEST_MATH, __FILE__, __func__,
   4732 					__LINE__,
   4733 					"Display formula within an open "
   4734 					"formula"));
   4735 
   4736 			if (!(IN(state, ST_DISPLAY_FORMULA)))
   4737 			{
   4738 				/* Output existing text up to $$ */
   4739 				process_text_token(output, line, link_prefix,
   4740 					first_line_in_doc, previous_line_blank,
   4741 					&previous_line_block,
   4742 					processed_start_of_line,
   4743 					read_yaml_macros_and_links, list_para,
   4744 					&token, &ptoken, &token_size, 1, 1);
   4745 			}
   4746 			processed_start_of_line = 1;
   4747 			TOGGLE(state, ST_DISPLAY_FORMULA);
   4748 			if (IN(state, ST_DISPLAY_FORMULA))
   4749 				keep_token = 1;
   4750 			else
   4751 			{
   4752 				*ptoken = 0;
   4753 				if (!read_yaml_macros_and_links)
   4754 					process_formula(output, token, 1);
   4755 				keep_token = 0;
   4756 				RESET_TOKEN(token, ptoken, token_size);
   4757 			}
   4758 			pline += 2;
   4759 			colno += 2;
   4760 		}
   4761 		else
   4762 		{
   4763 			if (IN(state, ST_DISPLAY_FORMULA))
   4764 				exit(error(ERR_NEST_MATH, __FILE__, __func__,
   4765 					__LINE__,
   4766 					"Formula within an open display "
   4767 					"formula"));
   4768 			if (!(IN(state, ST_FORMULA)))
   4769 			{
   4770 				/* Output existing text up to $ */
   4771 				process_text_token(output, line, link_prefix,
   4772 					first_line_in_doc, previous_line_blank,
   4773 					&previous_line_block,
   4774 					processed_start_of_line,
   4775 					read_yaml_macros_and_links, list_para,
   4776 					&token, &ptoken, &token_size, 1, 1);
   4777 			}
   4778 			processed_start_of_line = 1;
   4779 			TOGGLE(state, ST_FORMULA);
   4780 			if (IN(state, ST_FORMULA))
   4781 				keep_token = 1;
   4782 			else
   4783 			{
   4784 				*ptoken = 0;
   4785 				if (!read_yaml_macros_and_links)
   4786 					process_formula(output, token, 0);
   4787 				keep_token = 0;
   4788 				RESET_TOKEN(token, ptoken, token_size);
   4789 			}
   4790 			pline++;
   4791 			colno++;
   4792 		}
   4793 		break;
   4794 
   4795 	case '1': /* FALLTHROUGH */
   4796 	case '2':
   4797 	case '3':
   4798 	case '4':
   4799 	case '5':
   4800 	case '6':
   4801 	case '7':
   4802 	case '8':
   4803 	case '9':
   4804 	case '0':
   4805 		if (ANY(state,
   4806 			    ST_CODE | ST_DISPLAY_FORMULA | ST_FORMULA
   4807 				    | ST_MACRO_BODY | ST_PRE | ST_YAML_VAL))
   4808 		{
   4809 			CHECKCOPY(token, ptoken, token_size, pline);
   4810 			colno++;
   4811 		}
   4812 		else if ((colno == 1 || all_numeric(line, pline))
   4813 			&& strlen((char*)pline) > 1
   4814 			&& (*(pline + 1) == '.' || *(pline + 1) == ')'))
   4815 		{
   4816 			ptoken	= token;
   4817 			*ptoken = 0;
   4818 			if (IN(state, ST_LIST))
   4819 			{
   4820 				CLEAR(state, ST_LIST);
   4821 				if (!read_yaml_macros_and_links)
   4822 				{
   4823 					process_list_item_end(output);
   4824 					process_list_end(output);
   4825 				}
   4826 			}
   4827 			if (!read_yaml_macros_and_links)
   4828 			{
   4829 				if (!(IN(state, ST_NUMLIST)))
   4830 					process_numlist_start(output);
   4831 				else
   4832 					process_list_item_end(output);
   4833 				process_list_item_start(output);
   4834 				SET(state, ST_PARA_OPEN);
   4835 			}
   4836 			processed_start_of_line = 1;
   4837 			skip_eol		= 0;
   4838 			SET(state, ST_NUMLIST);
   4839 			pline += 2;
   4840 			colno += 2;
   4841 		}
   4842 		else
   4843 		{
   4844 			CHECKCOPY(token, ptoken, token_size, pline);
   4845 			colno++;
   4846 		}
   4847 		break;
   4848 
   4849 	case '"':
   4850 		if (ANY(state, ST_CODE | ST_KBD | ST_PRE))
   4851 		{
   4852 			APPEND_TOKEN("&quot;", sizeof("&quot;"));
   4853 			pline++;
   4854 		}
   4855 		else
   4856 			CHECKCOPY(token, ptoken, token_size, pline);
   4857 		colno++;
   4858 
   4859 		break;
   4860 
   4861 	case '&':
   4862 		if (ANY(state, ST_CODE | ST_KBD | ST_PRE) || !*(pline + 1)
   4863 			|| isspace(*(pline + 1)))
   4864 		{
   4865 			APPEND_TOKEN("&amp;", sizeof("&amp;"));
   4866 			pline++;
   4867 		}
   4868 		else
   4869 			CHECKCOPY(token, ptoken, token_size, pline);
   4870 		colno++;
   4871 
   4872 		break;
   4873 
   4874 	default:
   4875 		CHECKCOPY(token, ptoken, token_size, pline);
   4876 		colno++;
   4877 	}
   4878 	goto do_line;
   4879 
   4880 done_line:
   4881 	*ptoken = 0;
   4882 
   4883 	if (*token && read_yaml_macros_and_links && (IN(state, ST_YAML_VAL)))
   4884 	{
   4885 		*ptoken = 0;
   4886 		if (!vars)
   4887 		{
   4888 			vars_count = 1;
   4889 			CALLOC(vars, KeyValue, vars_count);
   4890 			pvars = vars;
   4891 		}
   4892 		pvars->value_size = strlen((char*)token) + 1;
   4893 		CALLOC(pvars->value, u8, pvars->value_size);
   4894 		MEMCCPY(pvars->value, (char*)token, pvars->value_size, temp);
   4895 	}
   4896 	else if (keep_token)
   4897 		APPEND_TOKEN(ANY(state, ST_IMAGE | ST_LINK) ? " " : "\n", 1);
   4898 	else
   4899 	{
   4900 		if (*token)
   4901 		{
   4902 			*ptoken	  = 0;
   4903 			token_len = strlen((char*)token);
   4904 			if (IN(state, ST_MACRO_BODY))
   4905 			{
   4906 				skip_eol = 1;
   4907 				if (!read_yaml_macros_and_links)
   4908 					goto skip_macro_read;
   4909 				if (!macros)
   4910 				{
   4911 					macros_count = 1;
   4912 					CALLOC(macros, KeyValue, macros_count);
   4913 					pmacros = macros;
   4914 				}
   4915 				if (pmacros->value)
   4916 				{
   4917 					size_t value_len
   4918 						= strlen((char*)pmacros->value);
   4919 
   4920 					if (pmacros->value_size
   4921 						< value_len + token_len + 1)
   4922 					{
   4923 						pmacros->value_size += BUFSIZE;
   4924 						REALLOC(pmacros->value, u8,
   4925 							pmacros->value_size);
   4926 					}
   4927 					MEMCCPY((pmacros->value + value_len),
   4928 						token,
   4929 						pmacros->value_size - value_len,
   4930 						temp);
   4931 					value_len += token_len;
   4932 					if (temp)
   4933 						MEMCCPY_EXT((temp - 1),
   4934 							pmacros->value, "\n",
   4935 							pmacros->value_size
   4936 								- value_len,
   4937 							temp2);
   4938 				}
   4939 				else
   4940 				{
   4941 					pmacros->value_size = BUFSIZE;
   4942 					CALLOC(pmacros->value, u8,
   4943 						pmacros->value_size);
   4944 					MEMCCPY(pmacros->value, (char*)token,
   4945 						pmacros->value_size, temp);
   4946 					if (!temp)
   4947 						goto skip_macro_cat;
   4948 					MEMCCPY_EXT((temp - 1), pmacros->value,
   4949 						"\n",
   4950 						pmacros->value_size
   4951 							- ((u8*)temp
   4952 								- pmacros->value),
   4953 						temp2);
   4954 				skip_macro_cat:;
   4955 				}
   4956 			skip_macro_read:;
   4957 			}
   4958 			else if (IN(state, ST_FOOTNOTE_TEXT))
   4959 			{
   4960 				skip_eol = 1;
   4961 				if (!read_yaml_macros_and_links)
   4962 					goto skip_footnote_read;
   4963 				if (!footnotes)
   4964 				{
   4965 					footnote_count = 1;
   4966 					CALLOC(footnotes, KeyValue,
   4967 						footnote_count);
   4968 					pfootnotes = footnotes;
   4969 				}
   4970 				if (pfootnotes->value)
   4971 				{
   4972 					size_t value_len = strlen(
   4973 						(char*)pfootnotes->value);
   4974 					if (pfootnotes->value_size
   4975 						< value_len + token_len + 1)
   4976 					{
   4977 						pfootnotes->value_size
   4978 							+= BUFSIZE;
   4979 						REALLOC(pfootnotes->value, u8,
   4980 							pfootnotes->value_size);
   4981 					}
   4982 					MEMCCPY((pfootnotes->value + value_len),
   4983 						token,
   4984 						pfootnotes->value_size
   4985 							- value_len,
   4986 						temp);
   4987 					if (temp)
   4988 						MEMCCPY_EXT((temp - 1),
   4989 							(pfootnotes->value
   4990 								+ value_len),
   4991 							"\n",
   4992 							pfootnotes->value_size
   4993 								- value_len,
   4994 							temp2);
   4995 				}
   4996 				else
   4997 				{
   4998 					pfootnotes->value_size = BUFSIZE;
   4999 					CALLOC(pfootnotes->value, u8,
   5000 						pfootnotes->value_size);
   5001 					MEMCCPY(pfootnotes->value, token,
   5002 						pfootnotes->value_size, temp);
   5003 					if (temp)
   5004 						MEMCCPY_EXT((temp - 1),
   5005 							(pfootnotes->value
   5006 								+ value_len),
   5007 							"\n",
   5008 							pfootnotes->value_size
   5009 								- value_len,
   5010 							temp2);
   5011 				}
   5012 				*token = 0;
   5013 				ptoken = token;
   5014 			skip_footnote_read:;
   5015 			}
   5016 			else if (ANY(state,
   5017 					 ST_LINK_SECOND_ARG
   5018 						 | ST_LINK_SECOND_ARG_END))
   5019 			{
   5020 				if (read_yaml_macros_and_links)
   5021 				{
   5022 					if (!links)
   5023 					{
   5024 						links_count = 1;
   5025 						CALLOC(links, KeyValue,
   5026 							links_count);
   5027 						plinks = links;
   5028 					}
   5029 					plinks->value_size
   5030 						= strlen((char*)token) + 1;
   5031 					CALLOC(plinks->value, u8,
   5032 						plinks->value_size);
   5033 					MEMCCPY(plinks->value, (char*)token,
   5034 						plinks->value_size, temp);
   5035 				}
   5036 			}
   5037 			else if (IN(state, ST_HEADING))
   5038 			{
   5039 				CLEAR(state, ST_HEADING | ST_HEADING_TEXT);
   5040 				if (!read_yaml_macros_and_links)
   5041 				{
   5042 					process_heading(output, token,
   5043 						heading_level);
   5044 					output_firstcol = 0;
   5045 				}
   5046 				first_line_in_doc = 0;
   5047 				RESET_TOKEN(token, ptoken, token_size);
   5048 				heading_level = 0;
   5049 			}
   5050 			else if (!ANY(state,
   5051 					 ST_DISPLAY_FORMULA | ST_FORMULA
   5052 						 | ST_IMAGE | ST_LINK))
   5053 				process_text_token(output, line, link_prefix,
   5054 					first_line_in_doc, previous_line_blank,
   5055 					&previous_line_block,
   5056 					processed_start_of_line,
   5057 					read_yaml_macros_and_links, list_para,
   5058 					&token, &ptoken, &token_size, 1, 1);
   5059 		}
   5060 
   5061 		if (!ANY(state,
   5062 			    ST_PRE | ST_IMAGE_SECOND_ARG | ST_LINK_SECOND_ARG
   5063 				    | ST_LINK_SECOND_ARG_END)
   5064 			&& (!*pbuffer || *pbuffer == '\n'))
   5065 		{
   5066 			if (IN(state, ST_PARA_OPEN))
   5067 			{
   5068 				if (IN(state, ST_LIST))
   5069 					skip_eol = 1;
   5070 				CLEAR(state, ST_PARA_OPEN);
   5071 			}
   5072 
   5073 			if (!*pbuffer && (IN(state, ST_LIST)))
   5074 			{
   5075 				if (!read_yaml_macros_and_links)
   5076 				{
   5077 					process_list_item_end(output);
   5078 					process_list_end(output);
   5079 				}
   5080 				CLEAR(state, ST_LIST);
   5081 			}
   5082 
   5083 			if (!*pbuffer && (IN(state, ST_NUMLIST)))
   5084 			{
   5085 				if (!read_yaml_macros_and_links)
   5086 				{
   5087 					process_list_item_end(output);
   5088 					process_numlist_end(output);
   5089 				}
   5090 				CLEAR(state, ST_NUMLIST);
   5091 			}
   5092 
   5093 			if (!*pbuffer && (IN(state, ST_FOOTNOTE_TEXT)))
   5094 			{
   5095 				CLEAR(state, ST_FOOTNOTE_TEXT);
   5096 				*ptoken	  = 0;
   5097 				token_len = strlen((char*)token);
   5098 				skip_eol  = 1;
   5099 				if (!read_yaml_macros_and_links)
   5100 					goto skip_footnote_text_read;
   5101 				if (!footnotes)
   5102 				{
   5103 					footnote_count = 1;
   5104 					CALLOC(footnotes, KeyValue,
   5105 						footnote_count);
   5106 					pfootnotes = footnotes;
   5107 				}
   5108 				if (pfootnotes->value)
   5109 				{
   5110 					size_t value_len = strlen(
   5111 						(char*)pfootnotes->value);
   5112 					if (pfootnotes->value_size
   5113 						< value_len + token_len + 1)
   5114 					{
   5115 						pfootnotes->value_size
   5116 							= value_len + token_len
   5117 							+ 1;
   5118 						REALLOC(pfootnotes->value, u8,
   5119 							pfootnotes->value_size);
   5120 					}
   5121 					MEMCCPY((pfootnotes->value + value_len),
   5122 						token,
   5123 						pfootnotes->value_size
   5124 							- value_len,
   5125 						temp);
   5126 				}
   5127 				else
   5128 				{
   5129 					pfootnotes->value_size = token_len + 1;
   5130 					CALLOC(pfootnotes->value, u8,
   5131 						pfootnotes->value_size);
   5132 					MEMCCPY(pfootnotes->value, (char*)token,
   5133 						pfootnotes->value_size, temp);
   5134 				}
   5135 				*token = 0;
   5136 				ptoken = token;
   5137 			skip_footnote_text_read:;
   5138 			}
   5139 		}
   5140 
   5141 		if (IN(state, ST_BLOCKQUOTE) && (!*pbuffer || *pbuffer != '>'))
   5142 		{
   5143 			CLEAR(state, ST_BLOCKQUOTE);
   5144 			if (!read_yaml_macros_and_links)
   5145 			{
   5146 				process_blockquote(output, 1);
   5147 				previous_line_block = 1;
   5148 				output_firstcol	    = 0;
   5149 			}
   5150 		}
   5151 
   5152 		if (ANY(state, ST_TABLE_HEADER | ST_TABLE_LINE)
   5153 			&& (!line_len || !*pbuffer))
   5154 		{
   5155 			if (!read_yaml_macros_and_links)
   5156 			{
   5157 				warning(1, (u8*)"Malformed table");
   5158 				process_table_end(output);
   5159 				output_firstcol = 1;
   5160 			}
   5161 			CLEAR(state, ST_TABLE_HEADER | ST_TABLE_LINE);
   5162 		}
   5163 
   5164 		if ((IN(state, ST_TABLE)) && (!line_len || !*pbuffer))
   5165 		{
   5166 			if (!read_yaml_macros_and_links)
   5167 			{
   5168 				process_table_end(output);
   5169 				output_firstcol = 1;
   5170 			}
   5171 			CLEAR(state, ST_TABLE);
   5172 		}
   5173 
   5174 		previous_line_blank = 0;
   5175 	}
   5176 
   5177 	if (!skip_eol && !keep_token && !read_yaml_macros_and_links
   5178 		&& !ANY(state,
   5179 			ST_YAML | ST_YAML_VAL | ST_LINK_SECOND_ARG
   5180 				| ST_LINK_SECOND_ARG_END))
   5181 	{
   5182 		print_output(output, "\n");
   5183 		output_firstcol = 1;
   5184 	}
   5185 
   5186 	if (!keep_token)
   5187 		RESET_TOKEN(token, ptoken, token_size);
   5188 
   5189 	if (!skip_change_first_line_in_doc && !(IN(state, ST_YAML)))
   5190 		first_line_in_doc = 0;
   5191 
   5192 	skip_change_first_line_in_doc = 0;
   5193 	if (line_len == 0)
   5194 		previous_line_blank = 1;
   5195 
   5196 	/* Lasts until the end of line */
   5197 	CLEAR(state,
   5198 		ST_YAML_VAL | ST_IMAGE_SECOND_ARG | ST_LINK_SECOND_ARG
   5199 			| ST_LINK_SECOND_ARG_END);
   5200 
   5201 	if (pbuffer && *pbuffer)
   5202 		goto do_buffer;
   5203 done_buffer:
   5204 	if (!read_yaml_macros_and_links)
   5205 	{
   5206 		if ((footnote_count > 0) || (inline_footnote_count > 0))
   5207 			end_footnotes(output, add_footnote_div);
   5208 
   5209 		if (madeby_present)
   5210 		{
   5211 			process_madeby(output);
   5212 			output_firstcol = 1;
   5213 		}
   5214 
   5215 		if (!body_only)
   5216 			end_body_and_html(output);
   5217 	}
   5218 
   5219 	free(link_text);
   5220 	free(link_macro);
   5221 	free(token);
   5222 	free(line);
   5223 	return ERR_NONE;
   5224 
   5225 parse_alloc_error:
   5226 	save_errno = errno;
   5227 	free(link_text);
   5228 	free(link_macro);
   5229 	free(token);
   5230 	free(line);
   5231 	return save_errno;
   5232 }
   5233 
   5234 int
   5235 main(const int argc, const char** argv)
   5236 {
   5237 	FILE* input  = NULL;
   5238 	FILE* output = stdout;
   5239 	u8* buffer   = NULL;
   5240 	u8* eol	     = NULL;
   5241 	const char* arg;
   5242 	char* temp	   = NULL;
   5243 	Command cmd	   = CMD_NONE;
   5244 	size_t buffer_size = 0;
   5245 	int body_only	   = 0;
   5246 	int result	   = ERR_NONE;
   5247 
   5248 	UNUSED(argc);
   5249 	basedir_size = 2;
   5250 	CALLOC(basedir, char, basedir_size);
   5251 	*basedir = '.';
   5252 
   5253 	while ((arg = *++argv))
   5254 	{
   5255 		if (*arg == '-')
   5256 		{
   5257 			char c;
   5258 			arg++;
   5259 			c = *arg++;
   5260 
   5261 			if (c == '-')
   5262 			{
   5263 				if (!strcmp(arg, "full-version"))
   5264 					cmd = CMD_FULL_VERSION;
   5265 				else if (!strcmp(arg, "version"))
   5266 					cmd = CMD_VERSION;
   5267 
   5268 				else if (!strcmp(arg, "body-only"))
   5269 					body_only = 1;
   5270 				else if (startswith(arg, "basedir"))
   5271 				{
   5272 					arg += strlen("basedir");
   5273 					result = set_basedir(&basedir,
   5274 						&basedir_size, arg);
   5275 					if (result)
   5276 						exit(result);
   5277 				}
   5278 				else if (startswith(arg, "global-link-prefix"))
   5279 				{
   5280 					arg += strlen("global-link-prefix");
   5281 					result = set_global_link_prefix(
   5282 						&global_link_prefix,
   5283 						&global_link_prefix_size, arg);
   5284 					if (result)
   5285 						exit(result);
   5286 				}
   5287 				else if (!strcmp(arg, "help"))
   5288 					exit(usage());
   5289 				else
   5290 				{
   5291 					usage();
   5292 					exit(error(ERR_BAD_CL_ARG, __FILE__,
   5293 						__func__, __LINE__,
   5294 						"Invalid argument: --%s", arg));
   5295 				}
   5296 			}
   5297 			else
   5298 			{
   5299 				switch (c)
   5300 				{
   5301 				case 'b':
   5302 					body_only = 1;
   5303 					break;
   5304 				case 'd':
   5305 					cmd = CMD_BASEDIR;
   5306 					break;
   5307 				case 'h':
   5308 					exit(usage());
   5309 					break;
   5310 				case 'p':
   5311 					cmd = CMD_LINK_PREFIX;
   5312 					break;
   5313 				case 'V':
   5314 					cmd = CMD_FULL_VERSION;
   5315 					break;
   5316 				case 'v':
   5317 					cmd = CMD_VERSION;
   5318 					break;
   5319 				default:
   5320 					usage();
   5321 					return error(ERR_BAD_CL_ARG, __FILE__,
   5322 						__func__, __LINE__,
   5323 						"Invalid argument: -%c", c);
   5324 				}
   5325 			}
   5326 		}
   5327 		else
   5328 		{
   5329 			switch (cmd)
   5330 			{
   5331 			case CMD_BASEDIR:
   5332 				result = set_basedir(&basedir, &basedir_size,
   5333 					arg);
   5334 				if (result)
   5335 					exit(result);
   5336 				break;
   5337 			case CMD_LINK_PREFIX:
   5338 				result = set_global_link_prefix(&global_link_prefix,
   5339 					&global_link_prefix_size, arg);
   5340 				if (result)
   5341 					exit(result);
   5342 				break;
   5343 			default:
   5344 				ifn_size = BUFSIZE;
   5345 				CALLOC(input_filename, char, ifn_size);
   5346 				MEMCCPY(input_filename, arg, ifn_size, temp);
   5347 			}
   5348 			cmd = CMD_NONE;
   5349 		}
   5350 	}
   5351 
   5352 	switch (cmd)
   5353 	{
   5354 	case CMD_BASEDIR:
   5355 		exit(error(ERR_NO_ARG, __FILE__, __func__, __LINE__,
   5356 			"Argument required"));
   5357 	case CMD_FULL_VERSION:
   5358 		version(1);
   5359 		exit(0);
   5360 	case CMD_VERSION:
   5361 		version(0);
   5362 		exit(0);
   5363 	default:;
   5364 	}
   5365 
   5366 	if (input_filename)
   5367 	{
   5368 		result = read_file_into_buffer(&input, &buffer, &buffer_size,
   5369 			input_filename, &input_dirname);
   5370 		if (result)
   5371 			exit(result);
   5372 	}
   5373 	else
   5374 	{
   5375 		u8* bufline	    = NULL;
   5376 		u8* pbufline	    = NULL;
   5377 		size_t buffer_len   = 0;
   5378 		size_t buffer_size  = 0;
   5379 		size_t bufline_len  = 0;
   5380 		size_t bufline_size = 0;
   5381 
   5382 		input = stdin;
   5383 
   5384 		CALLOC(input_dirname, char, 2);
   5385 		*input_dirname = '.';
   5386 
   5387 		bufline_size = BUFSIZE;
   5388 		CALLOC(bufline, u8, bufline_size);
   5389 		buffer_size = BUFSIZE;
   5390 		CALLOC(buffer, u8, buffer_size); /* freed in slweb_cleanup */
   5391 		pbufline = bufline;
   5392 
   5393 	do_input:
   5394 		if (feof(input))
   5395 			goto done_input;
   5396 
   5397 		if (!fgets((char*)pbufline, bufline_size - (pbufline - bufline),
   5398 			    input))
   5399 			goto done_input;
   5400 
   5401 		eol = (u8*)strchr((char*)pbufline, '\n');
   5402 		if (eol)
   5403 			*(eol + 1) = 0; /* slweb_parse will look for newline */
   5404 		else
   5405 		{
   5406 			bufline_size += BUFSIZE;
   5407 			REALLOC(bufline, u8, bufline_size);
   5408 			pbufline = bufline + bufline_size - BUFSIZE - 1;
   5409 			goto do_input;
   5410 		}
   5411 
   5412 		/* bufline is done, add it to buffer */
   5413 		bufline_len = strlen((char*)bufline);
   5414 		if (buffer_len + bufline_len + 1 > buffer_size)
   5415 		{
   5416 			buffer_size += BUFSIZE;
   5417 			REALLOC(buffer, u8, buffer_size);
   5418 		}
   5419 
   5420 		MEMCCPY((buffer + buffer_len), bufline,
   5421 			buffer_size - buffer_len, temp);
   5422 		buffer_len += bufline_len;
   5423 		goto do_input;
   5424 
   5425 	done_input:
   5426 		free(bufline);
   5427 	}
   5428 
   5429 	vars		      = NULL;
   5430 	vars_count	      = 0;
   5431 	macros		      = NULL;
   5432 	macros_count	      = 0;
   5433 	links		      = NULL;
   5434 	links_count	      = 0;
   5435 	footnotes	      = NULL;
   5436 	footnote_count	      = 0;
   5437 	inline_footnotes      = NULL;
   5438 	inline_footnote_count = 0;
   5439 
   5440 	/* First pass: read YAML, macros and links */
   5441 	if ((result = slweb_parse(output, input_filename, ifn_size, buffer,
   5442 		     body_only, 1)))
   5443 		goto slweb_cleanup;
   5444 
   5445 	state			= ST_NONE;
   5446 	current_footnote	= 0;
   5447 	current_inline_footnote = 0;
   5448 
   5449 	/* Second pass: parse and output */
   5450 	if ((result = slweb_parse(output, input_filename, ifn_size, buffer,
   5451 		     body_only, 0)))
   5452 		goto slweb_cleanup;
   5453 
   5454 slweb_cleanup:
   5455 	cleanup();
   5456 	free(buffer);
   5457 	return result;
   5458 }