1 /*
2  * Output a table of content of all the headers in all the files
3  * given on the command line.
4  *
5  * Headers with class "no-toc" will not be listed in the ToC.
6  *
7  * The ToC links to elements with ID attributes as well as with
8  * empty <A NAME> elements.
9  *
10  * Tags for a <SPAN> with class "index" are assumed to be used by
11  * a cross-reference generator and will not be copied to the ToC.
12  *
13  * howcome 2005-01-16: class attributes from header tags are now
14  * copied to generated LI tags
15  *
16  * Copyright © 1997-2005 World Wide Web Consortium
17  * See http://www.w3.org/Consortium/Legal/copyright-software
18  *
19  * Bert Bos <bert@w3.org>
20  * Created Sep 1997
21  * $Id: hxmultitoc.c,v 1.6 2017/11/24 09:50:25 bbos Exp $
22  */
23 #include "config.h"
24 #include <assert.h>
25 #include <ctype.h>
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <time.h>
29 #include <stdbool.h>
30 #if STDC_HEADERS
31 # include <string.h>
32 #else
33 # ifndef HAVE_STRCHR
34 #  define strchr index
35 #  define strrchr rindex
36 # endif
37 # ifndef HAVE_STRSTR
38 #  include "strstr.e"
39 # endif
40 #endif
41 #include "export.h"
42 #include "types.e"
43 #include "html.e"
44 #include "scan.e"
45 #include "dict.e"
46 #include "openurl.e"
47 #include "class.e"
48 
49 #define NO_TOC "no-toc"				/* CLASS="... no-toc..." */
50 #define INDEX "index"				/* CLASS="... index..." */
51 
52 #define MAXLINELEN 1024				/* In configfile */
53 
54 #define EXPAND true
55 #define NO_EXPAND false
56 #define KEEP_ANCHORS true
57 #define REMOVE_ANCHORS false
58 
59 static int toc_low = 1, toc_high = 6;		/* Which headers to include */
60 static bool xml = false;			/* Use <empty /> convention */
61 static bool copying = false;			/* Start by not copying */
62 static int curlevel = 0;			/* Level of previous heading */
63 static string base = NULL;			/* URL of each file */
64 static string endtext = "";			/* Text to insert at end */
65 
66 
67 /* handle_error -- called when a parse error occurred */
handle_error(void * clientdata,const string s,int lineno)68 static void handle_error(void *clientdata, const string s, int lineno)
69 {
70   fprintf(stderr, "%d: %s\n", lineno, s);
71 }
72 
73 /* start -- called before the first event is reported */
start(void)74 static void* start(void) {return NULL;}
75 
76 /* end -- called after the last event is reported */
end(void * clientdata)77 static void end(void *clientdata) {}
78 
79 /* handle_comment -- called after a comment is parsed */
handle_comment(void * clientdata,const string commenttext)80 static void handle_comment(void *clientdata, const string commenttext) {}
81 
82 /* handle_text -- called after a text chunk is parsed */
handle_text(void * clientdata,const string text)83 static void handle_text(void *clientdata, const string text)
84 {
85   if (copying) fputs(text, stdout);
86 }
87 
88 /* handle_declaration -- called after a declaration is parsed */
handle_decl(void * clientdata,const string gi,const string fpi,const string url)89 static void handle_decl(void *clientdata, const string gi,
90 			const string fpi, const string url) {}
91 
92 /* handle_proc_instr -- called after a PI is parsed */
handle_pi(void * clientdata,const string pi_text)93 static void handle_pi(void *clientdata, const string pi_text) {}
94 
95 /* handle_header -- handle a H? start tag */
handle_header(int level,pairlist attribs)96 static void handle_header(int level, pairlist attribs)
97 {
98   conststring id, class;
99 
100   if (has_class(attribs, NO_TOC)) return;
101   if (level < toc_low || level > toc_high) return;
102   for (; curlevel > level; curlevel--) printf("</ul>\n");
103   for (; curlevel < level - 1; curlevel++) printf("<li><ul class=\"toc\">\n");
104   if (curlevel == level - 1) {printf("<ul class=\"toc\">\n"); curlevel++;}
105   id = pairlist_get(attribs, "id");
106   class = pairlist_get(attribs, "class");
107   if (class) {
108     printf("<li class=\"%s\"><a href=\"%s#%s\">", class, base, id ? id : (string) "");
109   } else {
110     printf("<li><a href=\"%s#%s\">", base, id ? id : (string) "");
111   }
112   copying = true;
113 }
114 
115 /* handle_span -- print a <span> starttag but without class=index */
handle_span(pairlist attribs)116 static void handle_span(pairlist attribs)
117 {
118   pairlist a;
119   conststring t, h;
120 
121   printf("<span");
122   for (a = attribs; a != NULL; a = a->next) {
123     printf(" %s", a->name);
124     if (strcasecmp(a->name, "class") == 0 && (t = contains(a->value, INDEX))) {
125       /* Print value excluding INDEX */
126       printf("=\"");
127       for (h = a->value; h != t; h++) putchar(*h);
128       printf("%s\"", t + sizeof(INDEX) - 1);
129     } else {
130       if (a->value) printf("=\"%s\"", a->value);
131     }
132   }
133   printf(">");
134 }
135 
136 /* finalize -- close any open lists */
finalize(void)137 static void finalize(void)
138 {
139   for (; curlevel >= toc_low; curlevel--) printf("</ul>\n");
140 }
141 
142 /* handle_starttag -- called after a start tag is parsed */
handle_starttag(void * clientdata,const string name,pairlist attribs)143 static void handle_starttag(void *clientdata, const string name,
144 			    pairlist attribs)
145 {
146   pairlist a;
147 
148   if (eq(name, "h1") || eq(name, "H1")) handle_header(1, attribs);
149   else if (eq(name, "h2") || eq(name, "H2")) handle_header(2, attribs);
150   else if (eq(name, "h3") || eq(name, "H3")) handle_header(3, attribs);
151   else if (eq(name, "h4") || eq(name, "H4")) handle_header(4, attribs);
152   else if (eq(name, "h5") || eq(name, "H5")) handle_header(5, attribs);
153   else if (eq(name, "h6") || eq(name, "H6")) handle_header(6, attribs);
154   else if (eq(name, "a") || eq(name, "A")) ;	/* Skip anchors */
155   else if (copying && !strcasecmp(name, "span")) handle_span(attribs);
156   else if (copying) {				/* Copy the tag */
157     printf("<%s", name);
158     for (a = attribs; a != NULL; a = a->next) {
159       printf(" %s", a->name);
160       if (a->value != NULL) printf("=\"%s\"", a->value);
161     }
162     printf(">");
163   }
164 }
165 
166 /* handle_emptytag -- called after an empty tag is parsed */
handle_emptytag(void * clientdata,const string name,pairlist attribs)167 static void handle_emptytag(void *clientdata, const string name,
168 			    pairlist attribs)
169 {
170   pairlist a;
171 
172   if (copying && !eq(name, "a") && !eq(name, "A")) { /* Copy the tag */
173     printf("<%s", name);
174     for (a = attribs; a != NULL; a = a->next) {
175       printf(" %s", a->name);
176       if (a->value != NULL) printf("=\"%s\"", a->value);
177     }
178     printf(xml ? " />" : ">");
179   }
180 }
181 
182 /* handle_endtag -- called after an endtag is parsed (name may be "") */
handle_endtag(void * clientdata,const string name)183 static void handle_endtag(void *clientdata, const string name)
184 {
185   if (copying) {
186     if (eq(name, "h1") || eq(name, "H1") || eq(name, "h2")
187 	|| eq(name, "H2") || eq(name, "h3") || eq(name, "H3")
188 	|| eq(name, "h4") || eq(name, "H4") || eq(name, "h5")
189 	|| eq(name, "H5") || eq(name, "h6") || eq(name, "H6")) {
190       printf("</a>\n");
191       copying = false;
192     } else if (eq(name, "a") || eq(name, "A")) {
193       /* skip anchors */
194     } else {
195       printf("</%s>", name);
196     }
197   }
198 }
199 
200 /* process_configfile -- read @chapter lines from config file */
process_configfile(const string configfile)201 static void process_configfile(const string configfile)
202 {
203   char line[MAXLINELEN], chapter[MAXLINELEN];
204   FILE *f;
205 
206   if (! (f = fopenurl(configfile, "r", NULL))) {perror(configfile); exit(2);}
207 
208   /* ToDo: accept quoted file names with spaces in their name */
209   while (fgets(line, sizeof(line), f)) {
210     if (sscanf(line, " @chapter %s", chapter) == 1) {
211       if (!base) base = chapter;
212       yyin = fopenurl(chapter, "r", NULL);
213       if (yyin == NULL) {perror(chapter); exit(2);}
214       if (yyparse() != 0) exit(3);
215       fclose(yyin);
216       base = NULL;
217     }
218   }
219 
220   fclose(f);
221 }
222 
223 /* usage -- print usage message and exit */
usage(const string name)224 static void usage(const string name)
225 {
226   fprintf(stderr, "Version %s\n\
227 Usage: %s [-x] [-s text ] [-e text ] [-l low | -h high | -b base | html-file \
228 | -c configfile]+\n",
229 	  VERSION, name);
230   exit(1);
231 }
232 
main(int argc,char * argv[])233 int main(int argc, char *argv[])
234 {
235   int i;
236 
237   /* Bind the parser callback routines to our handlers */
238   set_error_handler(handle_error);
239   set_start_handler(start);
240   set_end_handler(end);
241   set_comment_handler(handle_comment);
242   set_text_handler(handle_text);
243   set_decl_handler(handle_decl);
244   set_pi_handler(handle_pi);
245   set_starttag_handler(handle_starttag);
246   set_emptytag_handler(handle_emptytag);
247   set_endtag_handler(handle_endtag);
248 
249   /* Loop over arguments; options may be in between file names */
250   for (i = 1; i < argc; i++) {
251     if (eq(argv[i], "-l")) {
252       if (i >= argc - 1) usage(argv[0]);
253       toc_low = atoi(argv[++i]);
254       curlevel = toc_low - 1;
255       if (toc_low < 1) toc_low = 1;
256     } else if (eq(argv[i], "-h")) {
257       if (i >= argc - 1) usage(argv[0]);
258       toc_high = atoi(argv[++i]);
259       if (toc_high > 6) toc_high = 6;
260     } else if (eq(argv[i], "-x")) {		/* XML format */
261       xml = true;
262     } else if (eq(argv[i], "-s")) {		/* Insert text at start */
263       printf("%s", argv[++i]);
264     } else if (eq(argv[i], "-e")) {		/* Insert text at end */
265       endtext = argv[++i];
266     } else if (eq(argv[i], "-b")) {
267       base = argv[++i];
268     } else if (eq(argv[i], "-c")) {		/* Config file */
269       process_configfile(argv[++i]);
270     } else if (eq(argv[i], "-")) {
271       if (!base) base = "";
272       yyin = stdin;
273       if (yyparse() != 0) exit(3);
274       base = NULL;				/* Reset base */
275     } else {
276       if (!base) base = argv[i];
277       yyin = fopenurl(argv[i], "r", NULL);
278       if (yyin == NULL) {perror(argv[1]); exit(2);}
279       if (yyparse() != 0) exit(3);
280       fclose(yyin);
281       base = NULL;
282     }
283   }
284   finalize();
285   printf("%s", endtext);			/* Insert text at end */
286   return 0;
287 }
288