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