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(¯os, 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*)" "; 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*)"​"; 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*)"‑"; 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("<", sizeof("<")); 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(""", sizeof(""")); 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("&", sizeof("&")); 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 }