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> </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