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