~ubuntu-branches/ubuntu/maverick/texinfo/maverick

« back to all changes in this revision

Viewing changes to makeinfo/html.c

  • Committer: Bazaar Package Importer
  • Author(s): Norbert Preining
  • Date: 2005-10-28 15:10:30 UTC
  • mto: (2.1.1 dapper) (3.1.4 hardy)
  • mto: This revision was merged to the branch mainline in revision 3.
  • Revision ID: james.westby@ubuntu.com-20051028151030-9nsf2s2k2z3fktjt
Tags: upstream-4.8
ImportĀ upstreamĀ versionĀ 4.8

Show diffs side-by-side

added added

removed removed

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