1 /* $NetBSD: html.c,v 1.2 2020/06/05 12:47:28 rin Exp $ */ 2 3 /* html.c -- html-related utilities. 4 Id: html.c,v 1.28 2004/12/06 01:13:06 karl Exp 5 6 Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 Free Software 7 Foundation, Inc. 8 9 This program is free software; you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation; either version 2, or (at your option) 12 any later version. 13 14 This program is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with this program; if not, write to the Free Software Foundation, 21 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ 22 23 #include "system.h" 24 #include "cmds.h" 25 #include "files.h" 26 #include "html.h" 27 #include "lang.h" 28 #include "makeinfo.h" 29 #include "node.h" 30 #include "sectioning.h" 31 32 33 /* Append CHAR to BUFFER, (re)allocating as necessary. We don't handle 35 null characters. */ 36 37 typedef struct 38 { 39 unsigned size; /* allocated */ 40 unsigned length; /* used */ 41 char *buffer; 42 } buffer_type; 43 44 static buffer_type * 45 init_buffer (void) 46 { 47 buffer_type *buf = xmalloc (sizeof (buffer_type)); 48 buf->length = 0; 49 buf->size = 0; 50 buf->buffer = NULL; 51 52 return buf; 53 } 54 55 static void 56 append_char (buffer_type *buf, int c) 57 { 58 buf->length++; 59 if (buf->length >= buf->size) 60 { 61 buf->size += 100; 62 buf->buffer = xrealloc (buf->buffer, buf->size); 63 } 64 buf->buffer[buf->length - 1] = c; 65 buf->buffer[buf->length] = 0; 66 } 67 68 /* Read the cascading style-sheet file FILENAME. Write out any @import 69 commands, which must come first, by the definition of css. If the 70 file contains any actual css code following the @imports, return it; 71 else return NULL. */ 72 static char * 73 process_css_file (char *filename) 74 { 75 int c; 76 int lastchar = 0; 77 FILE *f; 78 buffer_type *import_text = init_buffer (); 79 buffer_type *inline_text = init_buffer (); 80 unsigned lineno = 1; 81 enum { null_state, comment_state, import_state, inline_state } state 82 = null_state, prev_state; 83 84 prev_state = null_state; 85 86 /* read from stdin if `-' is the filename. */ 87 f = STREQ (filename, "-") ? stdin : fopen (filename, "r"); 88 if (!f) 89 { 90 error (_("%s: could not open --css-file: %s"), progname, filename); 91 return NULL; 92 } 93 94 /* Read the file. The @import statements must come at the beginning, 95 with only whitespace and comments allowed before any inline css code. */ 96 while ((c = getc (f)) >= 0) 97 { 98 if (c == '\n') 99 lineno++; 100 101 switch (state) 102 { 103 case null_state: /* between things */ 104 if (c == '@') 105 { /* Only @import and @charset should switch into 106 import_state, other @-commands, such as @media, should 107 put us into inline_state. I don't think any other css 108 @-commands start with `i' or `c', although of course 109 this will break when such a command is defined. */ 110 int nextchar = getc (f); 111 if (nextchar == 'i' || nextchar == 'c') 112 { 113 append_char (import_text, c); 114 state = import_state; 115 } 116 else 117 { 118 ungetc (nextchar, f); /* wasn't an @import */ 119 state = inline_state; 120 } 121 } 122 else if (c == '/') 123 { /* possible start of a comment */ 124 int nextchar = getc (f); 125 if (nextchar == '*') 126 state = comment_state; 127 else 128 { 129 ungetc (nextchar, f); /* wasn't a comment */ 130 state = inline_state; 131 } 132 } 133 else if (isspace (c)) 134 ; /* skip whitespace; maybe should use c_isspace? */ 135 136 else 137 /* not an @import, not a comment, not whitespace: we must 138 have started the inline text. */ 139 state = inline_state; 140 141 if (state == inline_state) 142 append_char (inline_text, c); 143 144 if (state != null_state) 145 prev_state = null_state; 146 break; 147 148 case comment_state: 149 if (c == '/' && lastchar == '*') 150 state = prev_state; /* end of comment */ 151 break; /* else ignore this comment char */ 152 153 case import_state: 154 append_char (import_text, c); /* include this import char */ 155 if (c == ';') 156 { /* done with @import */ 157 append_char (import_text, '\n'); /* make the output nice */ 158 state = null_state; 159 prev_state = import_state; 160 } 161 break; 162 163 case inline_state: 164 /* No harm in writing out comments, so don't bother parsing 165 them out, just append everything. */ 166 append_char (inline_text, c); 167 break; 168 } 169 170 lastchar = c; 171 } 172 173 /* Reached the end of the file. We should not be still in a comment. */ 174 if (state == comment_state) 175 warning (_("%s:%d: --css-file ended in comment"), filename, lineno); 176 177 /* Write the @import text, if any. */ 178 if (import_text->buffer) 179 { 180 add_word (import_text->buffer); 181 free (import_text->buffer); 182 free (import_text); 183 } 184 185 /* We're wasting the buffer struct memory, but so what. */ 186 return inline_text->buffer; 187 } 188 189 HSTACK *htmlstack = NULL; 191 192 /* See html.h. */ 193 int html_output_head_p = 0; 194 int html_title_written = 0; 195 196 void 197 html_output_head (void) 198 { 199 static const char *html_title = NULL; 200 char *encoding; 201 202 if (html_output_head_p) 203 return; 204 html_output_head_p = 1; 205 206 encoding = current_document_encoding (); 207 208 /* The <title> should not have markup, so use text_expansion. */ 209 if (!html_title) 210 html_title = escape_string (title ? 211 text_expansion (title) : (char *) _("Untitled")); 212 213 /* Make sure this is the very first string of the output document. */ 214 output_paragraph_offset = 0; 215 216 add_html_block_elt_args ("<html lang=\"%s\">\n<head>\n", 217 language_table[language_code].abbrev); 218 219 /* When splitting, add current node's name to title if it's available and not 220 Top. */ 221 if (splitting && current_node && !STREQ (current_node, "Top")) 222 add_word_args ("<title>%s - %s</title>\n", 223 escape_string (xstrdup (current_node)), html_title); 224 else 225 add_word_args ("<title>%s</title>\n", html_title); 226 227 add_word ("<meta http-equiv=\"Content-Type\" content=\"text/html"); 228 if (encoding && *encoding) 229 add_word_args ("; charset=%s", encoding); 230 231 add_word ("\">\n"); 232 233 if (!document_description) 234 document_description = html_title; 235 236 add_word_args ("<meta name=\"description\" content=\"%s\">\n", 237 document_description); 238 add_word_args ("<meta name=\"generator\" content=\"makeinfo %s\">\n", 239 VERSION); 240 241 /* Navigation bar links. */ 242 if (!splitting) 243 add_word ("<link title=\"Top\" rel=\"top\" href=\"#Top\">\n"); 244 else if (tag_table) 245 { 246 /* Always put a top link. */ 247 add_word ("<link title=\"Top\" rel=\"start\" href=\"index.html#Top\">\n"); 248 249 /* We already have a top link, avoid duplication. */ 250 if (tag_table->up && !STREQ (tag_table->up, "Top")) 251 add_link (tag_table->up, "rel=\"up\""); 252 253 if (tag_table->prev) 254 add_link (tag_table->prev, "rel=\"prev\""); 255 256 if (tag_table->next) 257 add_link (tag_table->next, "rel=\"next\""); 258 259 /* fixxme: Look for a way to put links to various indices in the 260 document. Also possible candidates to be added here are First and 261 Last links. */ 262 } 263 else 264 { 265 /* We are splitting, but we neither have a tag_table. So this must be 266 index.html. So put a link to Top. */ 267 add_word ("<link title=\"Top\" rel=\"start\" href=\"#Top\">\n"); 268 } 269 270 add_word ("<link href=\"http://www.gnu.org/software/texinfo/\" \ 271 rel=\"generator-home\" title=\"Texinfo Homepage\">\n"); 272 273 if (copying_text) 274 { /* It is not ideal that we include the html markup here within 275 <head>, so we use text_expansion. */ 276 insert_string ("<!--\n"); 277 insert_string (text_expansion (copying_text)); 278 insert_string ("-->\n"); 279 } 280 281 /* Put the style definitions in a comment for the sake of browsers 282 that don't support <style>. */ 283 add_word ("<meta http-equiv=\"Content-Style-Type\" content=\"text/css\">\n"); 284 add_word ("<style type=\"text/css\"><!--\n"); 285 286 { 287 char *css_inline = NULL; 288 289 if (css_include) 290 /* This writes out any @import commands from the --css-file, 291 and returns any actual css code following the imports. */ 292 css_inline = process_css_file (css_include); 293 294 /* This seems cleaner than adding <br>'s at the end of each line for 295 these "roman" displays. It's hardly the end of the world if the 296 browser doesn't do <style>s, in any case; they'll just come out in 297 typewriter. */ 298 #define CSS_FONT_INHERIT "font-family:inherit" 299 add_word_args (" pre.display { %s }\n", CSS_FONT_INHERIT); 300 add_word_args (" pre.format { %s }\n", CSS_FONT_INHERIT); 301 302 /* Alternatively, we could do <font size=-1> in insertion.c, but this 303 way makes it easier to override. */ 304 #define CSS_FONT_SMALLER "font-size:smaller" 305 add_word_args (" pre.smalldisplay { %s; %s }\n", CSS_FONT_INHERIT, 306 CSS_FONT_SMALLER); 307 add_word_args (" pre.smallformat { %s; %s }\n", CSS_FONT_INHERIT, 308 CSS_FONT_SMALLER); 309 add_word_args (" pre.smallexample { %s }\n", CSS_FONT_SMALLER); 310 add_word_args (" pre.smalllisp { %s }\n", CSS_FONT_SMALLER); 311 312 /* Since HTML doesn't have a sc element, we use span with a bit of 313 CSS spice instead. */ 314 #define CSS_FONT_SMALL_CAPS "font-variant:small-caps" 315 add_word_args (" span.sc { %s }\n", CSS_FONT_SMALL_CAPS); 316 317 /* Roman (default) font class, closest we can come. */ 318 #define CSS_FONT_ROMAN "font-family:serif; font-weight:normal;" 319 add_word_args (" span.roman { %s } \n", CSS_FONT_ROMAN); 320 321 /* Sans serif font class. */ 322 #define CSS_FONT_SANSSERIF "font-family:sans-serif; font-weight:normal;" 323 add_word_args (" span.sansserif { %s } \n", CSS_FONT_SANSSERIF); 324 325 /* Write out any css code from the user's --css-file. */ 326 if (css_inline) 327 insert_string (css_inline); 328 329 add_word ("--></style>\n"); 330 } 331 332 add_word ("</head>\n<body>\n"); 333 334 if (title && !html_title_written && titlepage_cmd_present) 335 { 336 add_word_args ("<h1 class=\"settitle\">%s</h1>\n", html_title); 337 html_title_written = 1; 338 } 339 340 free (encoding); 341 } 342 343 /* Escape HTML special characters in the string if necessary, 345 returning a pointer to a possibly newly-allocated one. */ 346 char * 347 escape_string (char *string) 348 { 349 char *newstring; 350 int i = 0, newlen = 0; 351 352 do 353 { 354 /* Find how much to allocate. */ 355 switch (string[i]) 356 { 357 case '"': 358 newlen += 6; /* `"' */ 359 break; 360 case '&': 361 newlen += 5; /* `&' */ 362 break; 363 case '<': 364 case '>': 365 newlen += 4; /* `<', `>' */ 366 break; 367 default: 368 newlen++; 369 } 370 } 371 while (string[i++]); 372 373 if (newlen == i) return string; /* Already OK. */ 374 375 newstring = xmalloc (newlen); 376 i = 0; 377 do 378 { 379 switch (string[i]) 380 { 381 case '"': 382 strcpy (newstring, """); 383 newstring += 6; 384 break; 385 case '&': 386 strcpy (newstring, "&"); 387 newstring += 5; 388 break; 389 case '<': 390 strcpy (newstring, "<"); 391 newstring += 4; 392 break; 393 case '>': 394 strcpy (newstring, ">"); 395 newstring += 4; 396 break; 397 default: 398 newstring[0] = string[i]; 399 newstring++; 400 } 401 } 402 while (string[i++]); 403 free (string); 404 return newstring - newlen; 405 } 406 407 /* Save current tag. */ 409 static void 410 push_tag (char *tag, char *attribs) 411 { 412 HSTACK *newstack = xmalloc (sizeof (HSTACK)); 413 414 newstack->tag = tag; 415 newstack->attribs = xstrdup (attribs); 416 newstack->next = htmlstack; 417 htmlstack = newstack; 418 } 419 420 /* Get last tag. */ 421 static void 422 pop_tag (void) 423 { 424 HSTACK *tos = htmlstack; 425 426 if (!tos) 427 { 428 line_error (_("[unexpected] no html tag to pop")); 429 return; 430 } 431 432 free (htmlstack->attribs); 433 434 htmlstack = htmlstack->next; 435 free (tos); 436 } 437 438 /* Check if tag is an empty or a whitespace only element. 439 If so, remove it, keeping whitespace intact. */ 440 int 441 rollback_empty_tag (char *tag) 442 { 443 int check_position = output_paragraph_offset; 444 int taglen = strlen (tag); 445 int rollback_happened = 0; 446 char *contents = ""; 447 char *contents_canon_white = ""; 448 449 /* If output_paragraph is empty, we cannot rollback :-\ */ 450 if (output_paragraph_offset <= 0) 451 return 0; 452 453 /* Find the end of the previous tag. */ 454 while (check_position > 0 && output_paragraph[check_position-1] != '>') 455 check_position--; 456 457 /* Save stuff between tag's end to output_paragraph's end. */ 458 if (check_position != output_paragraph_offset) 459 { 460 contents = xmalloc (output_paragraph_offset - check_position + 1); 461 memcpy (contents, output_paragraph + check_position, 462 output_paragraph_offset - check_position); 463 464 contents[output_paragraph_offset - check_position] = '\0'; 465 466 contents_canon_white = xstrdup (contents); 467 canon_white (contents_canon_white); 468 } 469 470 /* Find the start of the previous tag. */ 471 while (check_position > 0 && output_paragraph[check_position-1] != '<') 472 check_position--; 473 474 /* Check to see if this is the tag. */ 475 if (strncmp ((char *) output_paragraph + check_position, tag, taglen) == 0 476 && (whitespace (output_paragraph[check_position + taglen]) 477 || output_paragraph[check_position + taglen] == '>')) 478 { 479 if (!contents_canon_white || !*contents_canon_white) 480 { 481 /* Empty content after whitespace removal, so roll it back. */ 482 output_paragraph_offset = check_position - 1; 483 rollback_happened = 1; 484 485 /* Original contents may not be empty (whitespace.) */ 486 if (contents && *contents) 487 { 488 insert_string (contents); 489 free (contents); 490 } 491 } 492 } 493 494 return rollback_happened; 495 } 496 497 /* Open or close TAG according to START_OR_END. */ 498 void 499 #if defined (VA_FPRINTF) && __STDC__ 500 insert_html_tag_with_attribute (int start_or_end, char *tag, char *format, ...) 501 #else 502 insert_html_tag_with_attribute (start_or_end, tag, format, va_alist) 503 int start_or_end; 504 char *tag; 505 char *format; 506 va_dcl 507 #endif 508 { 509 char *old_tag = NULL; 510 char *old_attribs = NULL; 511 char formatted_attribs[2000]; /* xx no fixed limits */ 512 int do_return = 0; 513 extern int in_html_elt; 514 515 if (start_or_end != START) 516 pop_tag (); 517 518 if (htmlstack) 519 { 520 old_tag = htmlstack->tag; 521 old_attribs = htmlstack->attribs; 522 } 523 524 if (format) 525 { 526 #ifdef VA_SPRINTF 527 va_list ap; 528 #endif 529 530 VA_START (ap, format); 531 #ifdef VA_SPRINTF 532 VA_SPRINTF (formatted_attribs, format, ap); 533 #else 534 sprintf (formatted_attribs, format, a1, a2, a3, a4, a5, a6, a7, a8); 535 #endif 536 va_end (ap); 537 } 538 else 539 formatted_attribs[0] = '\0'; 540 541 /* Exception: can nest multiple spans. */ 542 if (htmlstack 543 && STREQ (htmlstack->tag, tag) 544 && !(STREQ (tag, "span") && STREQ (old_attribs, formatted_attribs))) 545 do_return = 1; 546 547 if (start_or_end == START) 548 push_tag (tag, formatted_attribs); 549 550 if (do_return) 551 return; 552 553 in_html_elt++; 554 555 /* texinfo.tex doesn't support more than one font attribute 556 at the same time. */ 557 if ((start_or_end == START) && old_tag && *old_tag 558 && !rollback_empty_tag (old_tag)) 559 add_word_args ("</%s>", old_tag); 560 561 if (*tag) 562 { 563 if (start_or_end == START) 564 add_word_args (format ? "<%s %s>" : "<%s>", tag, formatted_attribs); 565 else if (!rollback_empty_tag (tag)) 566 /* Insert close tag only if we didn't rollback, 567 in which case the opening tag is removed. */ 568 add_word_args ("</%s>", tag); 569 } 570 571 if ((start_or_end != START) && old_tag && *old_tag) 572 add_word_args (strlen (old_attribs) > 0 ? "<%s %s>" : "<%s>", 573 old_tag, old_attribs); 574 575 in_html_elt--; 576 } 577 578 void 579 insert_html_tag (int start_or_end, char *tag) 580 { 581 insert_html_tag_with_attribute (start_or_end, tag, NULL); 582 } 583 584 /* Output an HTML <link> to the filename for NODE, including the 586 other string as extra attributes. */ 587 void 588 add_link (char *nodename, char *attributes) 589 { 590 if (nodename) 591 { 592 add_html_elt ("<link "); 593 add_word_args ("%s", attributes); 594 add_word_args (" href=\""); 595 add_anchor_name (nodename, 1); 596 add_word_args ("\" title=\"%s\">\n", nodename); 597 } 598 } 599 600 /* Output NAME with characters escaped as appropriate for an anchor 601 name, i.e., escape URL special characters with our _00hh convention 602 if OLD is zero. (See the manual for details on the new scheme.) 603 604 If OLD is nonzero, generate the node name with the 4.6-and-earlier 605 convention of %hh (and more special characters output as-is, notably 606 - and *). This is only so that external references to old names can 607 still work with HTML generated by the new makeinfo; the gcc folks 608 needed this. Our own HTML does not refer to these names. */ 609 610 void 611 add_escaped_anchor_name (char *name, int old) 612 { 613 canon_white (name); 614 615 if (!old && !strchr ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 616 *name)) 617 { /* XHTML does not allow anything but an ASCII letter to start an 618 identifier. Therefore kludge in this constant string if we 619 have a nonletter. */ 620 add_word ("g_t"); 621 } 622 623 for (; *name; name++) 624 { 625 if (cr_or_whitespace (*name)) 626 add_char ('-'); 627 628 else if (!old && !URL_SAFE_CHAR (*name)) 629 /* Cast so characters with the high bit set are treated as >128, 630 for example o-umlaut should be 246, not -10. */ 631 add_word_args ("_00%x", (unsigned char) *name); 632 633 else if (old && !URL_SAFE_CHAR (*name) && !OLD_URL_SAFE_CHAR (*name)) 634 /* Different output convention, but still cast as above. */ 635 add_word_args ("%%%x", (unsigned char) *name); 636 637 else 638 add_char (*name); 639 } 640 } 641 642 /* Insert the text for the name of a reference in an HTML anchor 643 appropriate for NODENAME. 644 645 If HREF is zero, generate text for name= in the new node name 646 conversion convention. 647 If HREF is negative, generate text for name= in the old convention. 648 If HREF is positive, generate the name for an href= attribute, i.e., 649 including the `#' if it's an internal reference. */ 650 void 651 add_anchor_name (char *nodename, int href) 652 { 653 if (href > 0) 654 { 655 if (splitting) 656 add_url_name (nodename, href); 657 add_char ('#'); 658 } 659 /* Always add NODENAME, so that the reference would pinpoint the 660 exact node on its file. This is so several nodes could share the 661 same file, in case of file-name clashes, but also for more 662 accurate browser positioning. */ 663 if (strcasecmp (nodename, "(dir)") == 0) 664 /* Strip the parens, but keep the original letter-case. */ 665 add_word_args ("%.3s", nodename + 1); 666 else if (strcasecmp (nodename, "top") == 0) 667 add_word ("Top"); 668 else 669 add_escaped_anchor_name (nodename, href < 0); 670 } 671 672 /* Insert the text for the name of a reference in an HTML url, aprropriate 673 for NODENAME */ 674 void 675 add_url_name (char *nodename, int href) 676 { 677 add_nodename_to_filename (nodename, href); 678 } 679 680 /* Convert non [A-Za-z0-9] to _00xx, where xx means the hexadecimal 681 representation of the ASCII character. Also convert spaces and 682 newlines to dashes. */ 683 static void 684 fix_filename (char *filename) 685 { 686 int i; 687 int len = strlen (filename); 688 char *oldname = xstrdup (filename); 689 690 *filename = '\0'; 691 692 for (i = 0; i < len; i++) 693 { 694 if (cr_or_whitespace (oldname[i])) 695 strcat (filename, "-"); 696 else if (URL_SAFE_CHAR (oldname[i])) 697 strncat (filename, (char *) oldname + i, 1); 698 else 699 { 700 char *hexchar = xmalloc (6 * sizeof (char)); 701 sprintf (hexchar, "_00%x", (unsigned char) oldname[i]); 702 strcat (filename, hexchar); 703 free (hexchar); 704 } 705 706 /* Check if we are nearing boundaries. */ 707 if (strlen (filename) >= PATH_MAX - 20) 708 break; 709 } 710 711 free (oldname); 712 } 713 714 /* As we can't look-up a (forward-referenced) nodes' html filename 715 from the tentry, we take the easy way out. We assume that 716 nodenames are unique, and generate the html filename from the 717 nodename, that's always known. */ 718 static char * 719 nodename_to_filename_1 (char *nodename, int href) 720 { 721 char *p; 722 char *filename; 723 char dirname[PATH_MAX]; 724 725 if (strcasecmp (nodename, "Top") == 0) 726 { 727 /* We want to convert references to the Top node into 728 "index.html#Top". */ 729 if (href) 730 filename = xstrdup ("index.html"); /* "#Top" is added by our callers */ 731 else 732 filename = xstrdup ("Top"); 733 } 734 else if (strcasecmp (nodename, "(dir)") == 0) 735 /* We want to convert references to the (dir) node into 736 "../index.html". */ 737 filename = xstrdup ("../index.html"); 738 else 739 { 740 filename = xmalloc (PATH_MAX); 741 dirname[0] = '\0'; 742 *filename = '\0'; 743 744 /* Check for external reference: ``(info-document)node-name'' 745 Assume this node lives at: ``../info-document/node-name.html'' 746 747 We need to handle the special case (sigh): ``(info-document)'', 748 ie, an external top-node, which should translate to: 749 ``../info-document/info-document.html'' */ 750 751 p = nodename; 752 if (*nodename == '(') 753 { 754 int length; 755 756 p = strchr (nodename, ')'); 757 if (p == NULL) 758 { 759 line_error (_("[unexpected] invalid node name: `%s'"), nodename); 760 xexit (1); 761 } 762 763 length = p - nodename - 1; 764 if (length > 5 && 765 FILENAME_CMPN (p - 5, ".info", 5) == 0) 766 length -= 5; 767 /* This is for DOS, and also for Windows and GNU/Linux 768 systems that might have Info files copied from a DOS 8+3 769 filesystem. */ 770 if (length > 4 && 771 FILENAME_CMPN (p - 4, ".inf", 4) == 0) 772 length -= 4; 773 strcpy (filename, "../"); 774 strncpy (dirname, nodename + 1, length); 775 *(dirname + length) = '\0'; 776 fix_filename (dirname); 777 strcat (filename, dirname); 778 strcat (filename, "/"); 779 p++; 780 } 781 782 /* In the case of just (info-document), there will be nothing 783 remaining, and we will refer to ../info-document/, which will 784 work fine. */ 785 strcat (filename, p); 786 if (*p) 787 { 788 /* Hmm */ 789 fix_filename (filename + strlen (filename) - strlen (p)); 790 strcat (filename, ".html"); 791 } 792 } 793 794 /* Produce a file name suitable for the underlying filesystem. */ 795 normalize_filename (filename); 796 797 #if 0 798 /* We add ``#Nodified-filename'' anchor to external references to be 799 prepared for non-split HTML support. Maybe drop this. */ 800 if (href && *dirname) 801 { 802 strcat (filename, "#"); 803 strcat (filename, p); 804 /* Hmm, again */ 805 fix_filename (filename + strlen (filename) - strlen (p)); 806 } 807 #endif 808 809 return filename; 810 } 811 812 /* If necessary, ie, if current filename != filename of node, output 813 the node name. */ 814 void 815 add_nodename_to_filename (char *nodename, int href) 816 { 817 /* for now, don't check: always output filename */ 818 char *filename = nodename_to_filename_1 (nodename, href); 819 add_word (filename); 820 free (filename); 821 } 822 823 char * 824 nodename_to_filename (char *nodename) 825 { 826 /* The callers of nodename_to_filename use the result to produce 827 <a href=, so call nodename_to_filename_1 with last arg non-zero. */ 828 return nodename_to_filename_1 (nodename, 1); 829 } 830