1/* xref.c -- cross references for Texinfo.
2   $Id: xref.c,v 1.4 2004/12/21 17:28:35 karl Exp $
3
4   Copyright (C) 2004 Free Software Foundation, Inc.
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 2, or (at your option)
9   any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with this program; if not, write to the Free Software Foundation,
18   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
19
20#include "system.h"
21#include "cmds.h"
22#include "float.h"
23#include "html.h"
24#include "index.h"
25#include "macro.h"
26#include "makeinfo.h"
27#include "node.h"
28#include "xml.h"
29#include "xref.h"
30
31/* Flags which control initial output string for xrefs. */
32int px_ref_flag = 0;
33int ref_flag = 0;
34
35/* Called in the multiple-argument case to make sure we generate a valid
36   Info reference.  In the single-argument case, the :: we output
37   suffices for the Info readers to find the end of the reference.  */
38static void
39add_xref_punctuation (void)
40{
41  if (px_ref_flag || ref_flag)  /* user inserts punct after @xref */
42    {
43      /* Check if there's already punctuation.  */
44      int next_char = next_nonwhitespace_character ();
45
46      if (next_char == -1)
47        /* EOF while looking for punctuation, let's
48           insert a period instead of crying.  */
49        add_char ('.');
50      else if (next_char != ',' && next_char != '.')
51        /* period and comma terminate xrefs, and nothing else.  Instead
52           of generating an Info reference that can't be followed,
53           though, just insert a period.  Not pretty, but functional.  */
54        add_char ('.');
55    }
56}
57
58/* Return next comma-delimited argument, but do not cross a close-brace
59   boundary.  Clean up whitespace, too.  If EXPAND is nonzero, replace
60   the entire brace-delimited argument list with its expansion before
61   looking for the next comma.  */
62char *
63get_xref_token (int expand)
64{
65  char *string = 0;
66
67  if (docbook)
68    xml_in_xref_token = 1;
69
70  if (expand)
71    {
72      int old_offset = input_text_offset;
73      int old_lineno = line_number;
74
75      get_until_in_braces ("}", &string);
76      if (curchar () == '}')    /* as opposed to end of text */
77        input_text_offset++;
78      if (input_text_offset > old_offset)
79        {
80          int limit = input_text_offset;
81
82          input_text_offset = old_offset;
83          line_number = old_lineno;
84          only_macro_expansion++;
85          replace_with_expansion (input_text_offset, &limit);
86          only_macro_expansion--;
87        }
88      free (string);
89    }
90
91  get_until_in_braces (",", &string);
92  if (curchar () == ',')
93    input_text_offset++;
94  fix_whitespace (string);
95
96  if (docbook)
97    xml_in_xref_token = 0;
98
99  return string;
100}
101
102
103/* NOTE: If you wonder why the HTML output is produced with such a
104   peculiar mix of calls to add_word and execute_string, here's the
105   reason.  get_xref_token (1) expands all macros in a reference, but
106   any other commands, like @value, @@, etc., are left intact.  To
107   expand them, we need to run the arguments through execute_string.
108   However, characters like <, &, > and others cannot be let into
109   execute_string, because they will be escaped.  See the mess?  */
110
111/* Make a cross reference. */
112void
113cm_xref (int arg)
114{
115  if (arg == START)
116    {
117      char *arg1 = get_xref_token (1); /* expands all macros in xref */
118      char *arg2 = get_xref_token (0);
119      char *arg3 = get_xref_token (0);
120      char *arg4 = get_xref_token (0);
121      char *arg5 = get_xref_token (0);
122      char *tem;
123
124      /* "@xref{,Foo,, Bar, Baz} is not valid usage of @xref.  The
125         first argument must never be blank." --rms.
126         We hereby comply by disallowing such constructs.  */
127      if (!*arg1)
128        line_error (_("First argument to cross-reference may not be empty"));
129
130      if (docbook)
131        {
132          if (!ref_flag)
133            add_word (px_ref_flag || printing_index
134                ? (char *) _("see ") : (char *) _("See "));
135
136          if (!*arg4 && !*arg5)
137            {
138              char *arg1_id = xml_id (arg1);
139
140              if (*arg2 || *arg3)
141                {
142                  xml_insert_element_with_attribute (XREFNODENAME, START,
143                                                     "linkend=\"%s\"", arg1_id);
144                  free (arg1_id);
145                  execute_string ("%s", *arg3 ? arg3 : arg2);
146                  xml_insert_element (XREFNODENAME, END);
147                }
148              else
149                {
150                  xml_insert_element_with_attribute (XREF, START,
151                                                     "linkend=\"%s\"", arg1_id);
152                  xml_insert_element (XREF, END);
153                  free (arg1_id);
154                }
155            }
156          else if (*arg5)
157            {
158              add_word_args (_("See section ``%s'' in "), *arg3 ? arg3 : arg1);
159              xml_insert_element (CITE, START);
160              add_word (arg5);
161              xml_insert_element (CITE, END);
162            }
163          else if (*arg4)
164            {
165              /* Very sad, we are losing xrefs made to ``info only'' books.  */
166            }
167        }
168      else if (xml)
169        {
170          if (!ref_flag)
171            add_word_args ("%s", px_ref_flag ? _("see ") : _("See "));
172
173          xml_insert_element (XREF, START);
174          xml_insert_element (XREFNODENAME, START);
175          execute_string ("%s", arg1);
176          xml_insert_element (XREFNODENAME, END);
177          if (*arg2)
178            {
179              xml_insert_element (XREFINFONAME, START);
180              execute_string ("%s", arg2);
181              xml_insert_element (XREFINFONAME, END);
182            }
183          if (*arg3)
184            {
185              xml_insert_element (XREFPRINTEDDESC, START);
186              execute_string ("%s", arg3);
187              xml_insert_element (XREFPRINTEDDESC, END);
188            }
189          if (*arg4)
190            {
191              xml_insert_element (XREFINFOFILE, START);
192              execute_string ("%s", arg4);
193              xml_insert_element (XREFINFOFILE, END);
194            }
195          if (*arg5)
196            {
197              xml_insert_element (XREFPRINTEDNAME, START);
198              execute_string ("%s", arg5);
199              xml_insert_element (XREFPRINTEDNAME, END);
200            }
201          xml_insert_element (XREF, END);
202        }
203      else if (html)
204        {
205          if (!ref_flag)
206            add_word_args ("%s", px_ref_flag ? _("see ") : _("See "));
207        }
208      else
209        add_word_args ("%s", px_ref_flag ? "*note " : "*Note ");
210
211      if (!xml)
212        {
213          if (*arg5 || *arg4)
214            {
215              /* arg1 - node name
216                 arg2 - reference name
217                 arg3 - title or topic (and reference name if arg2 is NULL)
218                 arg4 - info file name
219                 arg5 - printed manual title  */
220              char *ref_name;
221
222              if (!*arg2)
223                {
224                  if (*arg3)
225                    ref_name = arg3;
226                  else
227                    ref_name = arg1;
228                }
229              else
230                ref_name = arg2;
231
232              if (html)
233                { /* More to do eventually, down to Unicode
234                     Normalization Form C.  See the HTML Xref nodes in
235                     the manual.  */
236                  char *file_arg = arg4;
237                  add_html_elt ("<a href=");
238
239                  {
240                    /* If there's a directory part, ignore it.  */
241                    char *p = strrchr (file_arg, '/');
242                    if (p)
243                      file_arg = p + 1;
244
245                  /* If there's a dot, make it a NULL terminator, so the
246                     extension does not get into the way.  */
247                    p = strrchr (file_arg , '.');
248                    if (p != NULL)
249                      *p = 0;
250                  }
251
252                  if (! *file_arg)
253                warning (_("Empty file name for HTML cross reference in `%s'"),
254                           arg4);
255
256                  /* Note that if we are splitting, and the referenced
257                     tag is an anchor rather than a node, we will
258                     produce a reference to a file whose name is
259                     derived from the anchor name.  However, only
260                     nodes create files, so we are referencing a
261                     non-existent file.  cm_anchor, which see, deals
262                     with that problem.  */
263                  if (splitting)
264                    execute_string ("\"../%s/", file_arg);
265                  else
266                    execute_string ("\"%s.html", file_arg);
267                  /* Do not collapse -- to -, etc., in references.  */
268                  in_fixed_width_font++;
269                  tem = expansion (arg1, 0); /* expand @-commands in node */
270                  in_fixed_width_font--;
271                  add_anchor_name (tem, 1);
272                  free (tem);
273                  add_word ("\">");
274                  execute_string ("%s",ref_name);
275                  add_word ("</a>");
276                }
277              else
278                {
279                  execute_string ("%s:", ref_name);
280                  in_fixed_width_font++;
281                  execute_string (" (%s)%s", arg4, arg1);
282                  add_xref_punctuation ();
283                  in_fixed_width_font--;
284                }
285
286              /* Free all of the arguments found. */
287              if (arg1) free (arg1);
288              if (arg2) free (arg2);
289              if (arg3) free (arg3);
290              if (arg4) free (arg4);
291              if (arg5) free (arg5);
292              return;
293            }
294          else
295            remember_node_reference (arg1, line_number, followed_reference);
296
297          if (*arg3)
298            {
299              if (html)
300                {
301                  add_html_elt ("<a href=\"");
302                  in_fixed_width_font++;
303                  tem = expansion (arg1, 0);
304                  in_fixed_width_font--;
305                  add_anchor_name (tem, 1);
306                  free (tem);
307                  add_word ("\">");
308                  execute_string ("%s", *arg2 ? arg2 : arg3);
309                  add_word ("</a>");
310                }
311              else
312                {
313                  execute_string ("%s:", *arg2 ? arg2 : arg3);
314                  in_fixed_width_font++;
315                  execute_string (" %s", arg1);
316                  add_xref_punctuation ();
317                  in_fixed_width_font--;
318                }
319            }
320          else
321            {
322              if (html)
323                {
324                  add_html_elt ("<a href=\"");
325                  in_fixed_width_font++;
326                  tem = expansion (arg1, 0);
327                  in_fixed_width_font--;
328                  add_anchor_name (tem, 1);
329                  free (tem);
330                  add_word ("\">");
331                  if (*arg2)
332                    execute_string ("%s", arg2);
333                  else
334                    {
335                      char *fref = get_float_ref (arg1);
336                      execute_string ("%s", fref ? fref : arg1);
337                      free (fref);
338                    }
339                  add_word ("</a>");
340                }
341              else
342                {
343                  if (*arg2)
344                    {
345                      execute_string ("%s:", arg2);
346                      in_fixed_width_font++;
347                      execute_string (" %s", arg1);
348                      add_xref_punctuation ();
349                      in_fixed_width_font--;
350                    }
351                  else
352                    {
353                      char *fref = get_float_ref (arg1);
354                      if (fref)
355                        { /* Reference is being made to a float.  */
356                          execute_string ("%s:", fref);
357                          in_fixed_width_font++;
358                          execute_string (" %s", arg1);
359                          add_xref_punctuation ();
360                          in_fixed_width_font--;
361                        }
362                      else
363                        {
364                          in_fixed_width_font++;
365                          execute_string ("%s::", arg1);
366                          in_fixed_width_font--;
367                        }
368                    }
369                }
370            }
371        }
372      /* Free all of the arguments found. */
373      if (arg1) free (arg1);
374      if (arg2) free (arg2);
375      if (arg3) free (arg3);
376      if (arg4) free (arg4);
377      if (arg5) free (arg5);
378    }
379  else
380    { /* Check that the next non-whitespace character is valid to follow
381         an xref (so Info readers can find the node names).
382         `input_text_offset' is pointing at the "}" which ended the xref
383         command.  This is not used for @pxref or @ref, since we insert
384         the necessary punctuation above, if needed.  */
385      int temp = next_nonwhitespace_character ();
386
387      if (temp == -1)
388        warning (_("End of file reached while looking for `.' or `,'"));
389      else if (temp != '.' && temp != ',')
390        warning (_("`.' or `,' must follow @%s, not `%c'"), command, temp);
391    }
392}
393
394void
395cm_pxref (int arg)
396{
397  if (arg == START)
398    {
399      px_ref_flag++;
400      cm_xref (arg);
401      px_ref_flag--;
402    }
403  /* cm_xref isn't called with arg == END, which disables the code near
404     the end of cm_xref that checks for `.' or `,' after the
405     cross-reference.  This is because cm_xref generates the required
406     character itself (when needed) if px_ref_flag is set.  */
407}
408
409void
410cm_ref (int arg)
411{
412  /* See the comments in cm_pxref about the checks for punctuation.  */
413  if (arg == START)
414    {
415      ref_flag++;
416      cm_xref (arg);
417      ref_flag--;
418    }
419}
420
421void
422cm_inforef (int arg)
423{
424  if (arg == START)
425    {
426      char *node = get_xref_token (1); /* expands all macros in inforef */
427      char *pname = get_xref_token (0);
428      char *file = get_xref_token (0);
429
430      /* (see comments at cm_xref).  */
431      if (!*node)
432        line_error (_("First argument to @inforef may not be empty"));
433
434      if (xml && !docbook)
435        {
436          xml_insert_element (INFOREF, START);
437          xml_insert_element (INFOREFNODENAME, START);
438          execute_string ("%s", node);
439          xml_insert_element (INFOREFNODENAME, END);
440          if (*pname)
441            {
442              xml_insert_element (INFOREFREFNAME, START);
443              execute_string ("%s", pname);
444              xml_insert_element (INFOREFREFNAME, END);
445            }
446          xml_insert_element (INFOREFINFONAME, START);
447          execute_string ("%s", file);
448          xml_insert_element (INFOREFINFONAME, END);
449
450          xml_insert_element (INFOREF, END);
451        }
452      else if (html)
453        {
454          char *tem;
455
456          add_word ((char *) _("see "));
457          /* html fixxme: revisit this */
458          add_html_elt ("<a href=");
459          if (splitting)
460            execute_string ("\"../%s/", file);
461          else
462            execute_string ("\"%s.html", file);
463          tem = expansion (node, 0);
464          add_anchor_name (tem, 1);
465          add_word ("\">");
466          execute_string ("%s", *pname ? pname : tem);
467          add_word ("</a>");
468          free (tem);
469        }
470      else
471        {
472          if (*pname)
473            execute_string ("*note %s: (%s)%s", pname, file, node);
474          else
475            execute_string ("*note (%s)%s::", file, node);
476        }
477
478      free (node);
479      free (pname);
480      free (file);
481    }
482}
483
484/* A URL reference.  */
485void
486cm_uref (int arg)
487{
488  if (arg == START)
489    {
490      extern int printing_index;
491      char *url  = get_xref_token (1); /* expands all macros in uref */
492      char *desc = get_xref_token (0);
493      char *replacement = get_xref_token (0);
494
495      if (docbook)
496        {
497          xml_insert_element_with_attribute (UREF, START, "url=\"%s\"",
498              text_expansion (url));
499          if (*replacement)
500            execute_string ("%s", replacement);
501          else if (*desc)
502            execute_string ("%s", desc);
503          else
504            execute_string ("%s", url);
505          xml_insert_element (UREF, END);
506        }
507      else if (xml)
508        {
509          xml_insert_element (UREF, START);
510          xml_insert_element (UREFURL, START);
511          execute_string ("%s", url);
512          xml_insert_element (UREFURL, END);
513          if (*desc)
514            {
515              xml_insert_element (UREFDESC, START);
516              execute_string ("%s", desc);
517              xml_insert_element (UREFDESC, END);
518            }
519          if (*replacement)
520            {
521              xml_insert_element (UREFREPLACEMENT, START);
522              execute_string ("%s", replacement);
523              xml_insert_element (UREFREPLACEMENT, END);
524            }
525          xml_insert_element (UREF, END);
526        }
527      else if (html)
528        { /* never need to show the url */
529          add_html_elt ("<a href=");
530          /* don't collapse `--' etc. in the url */
531          in_fixed_width_font++;
532          execute_string ("\"%s\"", url);
533          in_fixed_width_font--;
534          add_word (">");
535          execute_string ("%s", *replacement ? replacement
536                                : (*desc ? desc : url));
537          add_word ("</a>");
538        }
539      else if (*replacement) /* do not show the url */
540        execute_string ("%s", replacement);
541      else if (*desc)        /* show both text and url */
542        {
543          execute_string ("%s ", desc);
544          in_fixed_width_font++;
545          execute_string ("(%s)", url);
546          in_fixed_width_font--;
547        }
548      else /* no text at all, so have the url to show */
549        {
550          in_fixed_width_font++;
551          execute_string ("%s%s%s",
552                          printing_index ? "" : "`",
553                          url,
554                          printing_index ? "" : "'");
555          in_fixed_width_font--;
556        }
557      if (url)
558        free (url);
559      if (desc)
560        free (desc);
561      if (replacement)
562        free (replacement);
563    }
564}
565
566/* An email reference.  */
567void
568cm_email (int arg)
569{
570  if (arg == START)
571    {
572      char *addr = get_xref_token (1); /* expands all macros in email */
573      char *name = get_xref_token (0);
574
575      if (xml && docbook)
576        {
577          xml_insert_element_with_attribute (EMAIL, START, "url=\"mailto:%s\"", addr);
578          if (*name)
579              execute_string ("%s", name);
580          xml_insert_element (EMAIL, END);
581        }
582      else if (xml)
583        {
584          xml_insert_element (EMAIL, START);
585          xml_insert_element (EMAILADDRESS, START);
586          execute_string ("%s", addr);
587          xml_insert_element (EMAILADDRESS, END);
588          if (*name)
589            {
590              xml_insert_element (EMAILNAME, START);
591              execute_string ("%s", name);
592              xml_insert_element (EMAILNAME, END);
593            }
594          xml_insert_element (EMAIL, END);
595        }
596      else if (html)
597        {
598          add_html_elt ("<a href=");
599          /* don't collapse `--' etc. in the address */
600          in_fixed_width_font++;
601          execute_string ("\"mailto:%s\"", addr);
602          in_fixed_width_font--;
603          add_word (">");
604          execute_string ("%s", *name ? name : addr);
605          add_word ("</a>");
606        }
607      else
608        {
609          execute_string ("%s%s", name, *name ? " "  : "");
610          in_fixed_width_font++;
611          execute_string ("<%s>", addr);
612          in_fixed_width_font--;
613        }
614
615      if (addr)
616        free (addr);
617      if (name)
618        free (name);
619    }
620}
621