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