html.c revision 1.2 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