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