1 /*
2  * Xcode documentation set generator.
3  *
4  * Copyright 2007-2012 by Apple Inc.
5  * Copyright 1997-2007 by Easy Software Products.
6  *
7  * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
8  *
9  * Usage:
10  *
11  *   makedocset directory *.tokens
12  */
13 
14 /*
15  * Include necessary headers...
16  */
17 
18 #include "cgi-private.h"
19 #include <errno.h>
20 
21 
22 /*
23  * Local structures...
24  */
25 
26 typedef struct _cups_html_s		/**** Help file ****/
27 {
28   char		*path;			/* Path to help file */
29   char		*title;			/* Title of help file */
30 } _cups_html_t;
31 
32 typedef struct _cups_section_s		/**** Help section ****/
33 {
34   char		*name;			/* Section name */
35   cups_array_t	*files;			/* Files in this section */
36 } _cups_section_t;
37 
38 
39 /*
40  * Local functions...
41  */
42 
43 static int	compare_html(_cups_html_t *a, _cups_html_t *b);
44 static int	compare_sections(_cups_section_t *a, _cups_section_t *b);
45 static int	compare_sections_files(_cups_section_t *a, _cups_section_t *b);
46 static void	write_index(const char *path, help_index_t *hi);
47 static void	write_info(const char *path, const char *revision);
48 static void	write_nodes(const char *path, help_index_t *hi);
49 
50 
51 /*
52  * 'main()' - Test the help index code.
53  */
54 
55 int					/* O - Exit status */
main(int argc,char * argv[])56 main(int  argc,				/* I - Number of command-line args */
57      char *argv[])			/* I - Command-line arguments */
58 {
59   int		i;			/* Looping var */
60   char		path[1024],		/* Path to documentation */
61 		line[1024];		/* Line from file */
62   help_index_t	*hi;			/* Help index */
63   cups_file_t	*tokens,		/* Tokens.xml file */
64 		*fp;			/* Current file */
65 
66 
67   if (argc < 4)
68   {
69     puts("Usage: makedocset directory revision *.tokens");
70     return (1);
71   }
72 
73  /*
74   * Index the help documents...
75   */
76 
77   snprintf(path, sizeof(path), "%s/Contents/Resources/Documentation", argv[1]);
78   if ((hi = helpLoadIndex(NULL, path)) == NULL)
79   {
80     fputs("makedocset: Unable to index help files!\n", stderr);
81     return (1);
82   }
83 
84   snprintf(path, sizeof(path), "%s/Contents/Resources/Documentation/index.html",
85            argv[1]);
86   write_index(path, hi);
87 
88   snprintf(path, sizeof(path), "%s/Contents/Resources/Nodes.xml", argv[1]);
89   write_nodes(path, hi);
90 
91  /*
92   * Write the Info.plist file...
93   */
94 
95   snprintf(path, sizeof(path), "%s/Contents/Info.plist", argv[1]);
96   write_info(path, argv[2]);
97 
98  /*
99   * Merge the Tokens.xml files...
100   */
101 
102   snprintf(path, sizeof(path), "%s/Contents/Resources/Tokens.xml", argv[1]);
103   if ((tokens = cupsFileOpen(path, "w")) == NULL)
104   {
105     fprintf(stderr, "makedocset: Unable to create \"%s\": %s\n", path,
106 	    strerror(errno));
107     return (1);
108   }
109 
110   cupsFilePuts(tokens, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
111   cupsFilePuts(tokens, "<Tokens version=\"1.0\">\n");
112 
113   for (i = 3; i < argc; i ++)
114   {
115     if ((fp = cupsFileOpen(argv[i], "r")) == NULL)
116     {
117       fprintf(stderr, "makedocset: Unable to open \"%s\": %s\n", argv[i],
118 	      strerror(errno));
119       return (1);
120     }
121 
122     if (!cupsFileGets(fp, line, sizeof(line)) || strncmp(line, "<?xml ", 6) ||
123         !cupsFileGets(fp, line, sizeof(line)) || strncmp(line, "<Tokens ", 8))
124     {
125       fprintf(stderr, "makedocset: Bad Tokens.xml file \"%s\"!\n", argv[i]);
126       return (1);
127     }
128 
129     while (cupsFileGets(fp, line, sizeof(line)))
130     {
131       if (strcmp(line, "</Tokens>"))
132         cupsFilePrintf(tokens, "%s\n", line);
133     }
134 
135     cupsFileClose(fp);
136   }
137 
138   cupsFilePuts(tokens, "</Tokens>\n");
139 
140   cupsFileClose(tokens);
141 
142  /*
143   * Return with no errors...
144   */
145 
146   return (0);
147 }
148 
149 
150 /*
151  * 'compare_html()' - Compare the titles of two HTML files.
152  */
153 
154 static int				/* O - Result of comparison */
compare_html(_cups_html_t * a,_cups_html_t * b)155 compare_html(_cups_html_t *a,		/* I - First file */
156              _cups_html_t *b)		/* I - Second file */
157 {
158   return (_cups_strcasecmp(a->title, b->title));
159 }
160 
161 
162 /*
163  * 'compare_sections()' - Compare the names of two help sections.
164  */
165 
166 static int				/* O - Result of comparison */
compare_sections(_cups_section_t * a,_cups_section_t * b)167 compare_sections(_cups_section_t *a,	/* I - First section */
168                  _cups_section_t *b)	/* I - Second section */
169 {
170   return (_cups_strcasecmp(a->name, b->name));
171 }
172 
173 
174 /*
175  * 'compare_sections_files()' - Compare the number of files and section names.
176  */
177 
178 static int				/* O - Result of comparison */
compare_sections_files(_cups_section_t * a,_cups_section_t * b)179 compare_sections_files(
180     _cups_section_t *a,			/* I - First section */
181     _cups_section_t *b)			/* I - Second section */
182 {
183   int	ret = cupsArrayCount(b->files) - cupsArrayCount(a->files);
184 
185   if (ret)
186     return (ret);
187   else
188     return (_cups_strcasecmp(a->name, b->name));
189 }
190 
191 
192 /*
193  * 'write_index()' - Write an index file for the CUPS help.
194  */
195 
196 static void
write_index(const char * path,help_index_t * hi)197 write_index(const char   *path,		/* I - File to write */
198             help_index_t *hi)		/* I - Index of files */
199 {
200   cups_file_t		*fp;		/* Output file */
201   help_node_t		*node;		/* Current help node */
202   _cups_section_t	*section,	/* Current section */
203 			key;		/* Section search key */
204   _cups_html_t		*html;		/* Current HTML file */
205   cups_array_t		*sections,	/* Sections in index */
206 			*sections_files,/* Sections sorted by size */
207 			*columns[3];	/* Columns in final HTML file */
208   int			column,		/* Current column */
209 			lines[3],	/* Number of lines in each column */
210 			min_column,	/* Smallest column */
211 			min_lines;	/* Smallest number of lines */
212 
213 
214  /*
215   * Build an array of sections and their files.
216   */
217 
218   sections = cupsArrayNew((cups_array_func_t)compare_sections, NULL);
219 
220   for (node = (help_node_t *)cupsArrayFirst(hi->nodes);
221        node;
222        node = (help_node_t *)cupsArrayNext(hi->nodes))
223   {
224     if (node->anchor)
225       continue;
226 
227     key.name = node->section ? node->section : "Miscellaneous";
228     if ((section = (_cups_section_t *)cupsArrayFind(sections, &key)) == NULL)
229     {
230       section        = (_cups_section_t *)calloc(1, sizeof(_cups_section_t));
231       section->name  = key.name;
232       section->files = cupsArrayNew((cups_array_func_t)compare_html, NULL);
233 
234       cupsArrayAdd(sections, section);
235     }
236 
237     html = (_cups_html_t *)calloc(1, sizeof(_cups_html_t));
238     html->path  = node->filename;
239     html->title = node->text;
240 
241     cupsArrayAdd(section->files, html);
242   }
243 
244  /*
245   * Build a sorted list of sections based on the number of files in each section
246   * and the section name...
247   */
248 
249   sections_files = cupsArrayNew((cups_array_func_t)compare_sections_files,
250                                 NULL);
251   for (section = (_cups_section_t *)cupsArrayFirst(sections);
252        section;
253        section = (_cups_section_t *)cupsArrayNext(sections))
254     cupsArrayAdd(sections_files, section);
255 
256  /*
257   * Then build three columns to hold everything, trying to balance the number of
258   * lines in each column...
259   */
260 
261   for (column = 0; column < 3; column ++)
262   {
263     columns[column] = cupsArrayNew((cups_array_func_t)compare_sections, NULL);
264     lines[column]   = 0;
265   }
266 
267   for (section = (_cups_section_t *)cupsArrayFirst(sections_files);
268        section;
269        section = (_cups_section_t *)cupsArrayNext(sections_files))
270   {
271     for (min_column = 0, min_lines = lines[0], column = 1;
272          column < 3;
273 	 column ++)
274     {
275       if (lines[column] < min_lines)
276       {
277         min_column = column;
278         min_lines  = lines[column];
279       }
280     }
281 
282     cupsArrayAdd(columns[min_column], section);
283     lines[min_column] += cupsArrayCount(section->files) + 2;
284   }
285 
286  /*
287   * Write the HTML file...
288   */
289 
290   if ((fp = cupsFileOpen(path, "w")) == NULL)
291   {
292     fprintf(stderr, "makedocset: Unable to create %s: %s\n", path,
293             strerror(errno));
294     exit(1);
295   }
296 
297   cupsFilePuts(fp, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 "
298                    "Transitional//EN\" "
299 		   "\"http://www.w3.org/TR/html4/loose.dtd\">\n"
300 		   "<html>\n"
301 		   "<head>\n"
302 		   "<title>CUPS Documentation</title>\n"
303 		   "<link rel='stylesheet' type='text/css' "
304 		   "href='cups-printable.css'>\n"
305 		   "</head>\n"
306 		   "<body>\n"
307 		   "<h1 class='title'>CUPS Documentation</h1>\n"
308 		   "<table width='100%' summary=''>\n"
309 		   "<tr>\n");
310 
311   for (column = 0; column < 3; column ++)
312   {
313     if (column)
314       cupsFilePuts(fp, "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>\n");
315 
316     cupsFilePuts(fp, "<td valign='top' width='33%'>");
317     for (section = (_cups_section_t *)cupsArrayFirst(columns[column]);
318          section;
319 	 section = (_cups_section_t *)cupsArrayNext(columns[column]))
320     {
321       cupsFilePrintf(fp, "<h2 class='title'>%s</h2>\n", section->name);
322       for (html = (_cups_html_t *)cupsArrayFirst(section->files);
323            html;
324 	   html = (_cups_html_t *)cupsArrayNext(section->files))
325 	cupsFilePrintf(fp, "<p class='compact'><a href='%s'>%s</a></p>\n",
326 	               html->path, html->title);
327     }
328     cupsFilePuts(fp, "</td>\n");
329   }
330   cupsFilePuts(fp, "</tr>\n"
331                    "</table>\n"
332 		   "</body>\n"
333 		   "</html>\n");
334   cupsFileClose(fp);
335 }
336 
337 
338 /*
339  * 'write_info()' - Write the Info.plist file.
340  */
341 
342 static void
write_info(const char * path,const char * revision)343 write_info(const char *path,		/* I - File to write */
344            const char *revision)	/* I - Subversion revision number */
345 {
346   cups_file_t	*fp;			/* File */
347 
348 
349   if ((fp = cupsFileOpen(path, "w")) == NULL)
350   {
351     fprintf(stderr, "makedocset: Unable to create %s: %s\n", path,
352             strerror(errno));
353     exit(1);
354   }
355 
356   cupsFilePrintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
357 		     "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
358 		     "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
359 		     "<plist version=\"1.0\">\n"
360 		     "<dict>\n"
361 		     "\t<key>CFBundleIdentifier</key>\n"
362 		     "\t<string>org.cups.docset</string>\n"
363 		     "\t<key>CFBundleName</key>\n"
364 		     "\t<string>CUPS Documentation</string>\n"
365 		     "\t<key>CFBundleVersion</key>\n"
366 		     "\t<string>%d.%d.%s</string>\n"
367 		     "\t<key>CFBundleShortVersionString</key>\n"
368 		     "\t<string>%d.%d.%d</string>\n"
369 		     "\t<key>DocSetFeedName</key>\n"
370 		     "\t<string>cups.org</string>\n"
371 		     "\t<key>DocSetFeedURL</key>\n"
372 		     "\t<string>http://www.cups.org/org.cups.docset.atom"
373 		     "</string>\n"
374 		     "\t<key>DocSetPublisherIdentifier</key>\n"
375 		     "\t<string>org.cups</string>\n"
376 		     "\t<key>DocSetPublisherName</key>\n"
377 		     "\t<string>CUPS</string>\n"
378 		     "</dict>\n"
379 		     "</plist>\n",
380 		     CUPS_VERSION_MAJOR, CUPS_VERSION_MINOR, revision,
381 		     CUPS_VERSION_MAJOR, CUPS_VERSION_MINOR, CUPS_VERSION_PATCH);
382 
383   cupsFileClose(fp);
384 }
385 
386 
387 /*
388  * 'write_nodes()' - Write the Nodes.xml file.
389  */
390 
391 static void
write_nodes(const char * path,help_index_t * hi)392 write_nodes(const char   *path,		/* I - File to write */
393             help_index_t *hi)		/* I - Index of files */
394 {
395   cups_file_t	*fp;			/* Output file */
396   int		id;			/* Current node ID */
397   help_node_t	*node;			/* Current help node */
398   int		subnodes;		/* Currently in Subnodes for file? */
399   int		needclose;		/* Need to close the current node? */
400 
401 
402   if ((fp = cupsFileOpen(path, "w")) == NULL)
403   {
404     fprintf(stderr, "makedocset: Unable to create %s: %s\n", path,
405             strerror(errno));
406     exit(1);
407   }
408 
409   cupsFilePuts(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
410 		   "<DocSetNodes version=\"1.0\">\n"
411 		   "<TOC>\n"
412 		   "<Node id=\"0\">\n"
413 		   "<Name>CUPS Documentation</Name>\n"
414 		   "<Path>Documentation/index.html</Path>\n"
415 		   "</Node>\n");
416 
417   for (node = (help_node_t *)cupsArrayFirst(hi->nodes), id = 1, subnodes = 0,
418            needclose = 0;
419        node;
420        node = (help_node_t *)cupsArrayNext(hi->nodes), id ++)
421   {
422     if (node->anchor)
423     {
424       if (!subnodes)
425       {
426         cupsFilePuts(fp, "<Subnodes>\n");
427 	subnodes = 1;
428       }
429 
430       cupsFilePrintf(fp, "<Node id=\"%d\">\n"
431                          "<Path>Documentation/%s</Path>\n"
432 			 "<Anchor>%s</Anchor>\n"
433 			 "<Name>%s</Name>\n"
434 			 "</Node>\n", id, node->filename, node->anchor,
435 		     node->text);
436     }
437     else
438     {
439       if (subnodes)
440       {
441         cupsFilePuts(fp, "</Subnodes>\n");
442 	subnodes = 0;
443       }
444 
445       if (needclose)
446         cupsFilePuts(fp, "</Node>\n");
447 
448       cupsFilePrintf(fp, "<Node id=\"%d\">\n"
449                          "<Path>Documentation/%s</Path>\n"
450 			 "<Name>%s</Name>\n", id, node->filename, node->text);
451       needclose = 1;
452     }
453   }
454 
455   if (subnodes)
456     cupsFilePuts(fp, "</Subnodes>\n");
457 
458   if (needclose)
459     cupsFilePuts(fp, "</Node>\n");
460 
461   cupsFilePuts(fp, "</TOC>\n"
462 		   "</DocSetNodes>\n");
463 
464   cupsFileClose(fp);
465 }
466