| /* |
| * Xcode documentation set generator. |
| * |
| * Copyright 2007-2012 by Apple Inc. |
| * Copyright 1997-2007 by Easy Software Products. |
| * |
| * Licensed under Apache License v2.0. See the file "LICENSE" for more information. |
| * |
| * Usage: |
| * |
| * makedocset directory *.tokens |
| */ |
| |
| /* |
| * Include necessary headers... |
| */ |
| |
| #include "cgi-private.h" |
| #include <errno.h> |
| |
| |
| /* |
| * Local structures... |
| */ |
| |
| typedef struct _cups_html_s /**** Help file ****/ |
| { |
| char *path; /* Path to help file */ |
| char *title; /* Title of help file */ |
| } _cups_html_t; |
| |
| typedef struct _cups_section_s /**** Help section ****/ |
| { |
| char *name; /* Section name */ |
| cups_array_t *files; /* Files in this section */ |
| } _cups_section_t; |
| |
| |
| /* |
| * Local functions... |
| */ |
| |
| static int compare_html(_cups_html_t *a, _cups_html_t *b); |
| static int compare_sections(_cups_section_t *a, _cups_section_t *b); |
| static int compare_sections_files(_cups_section_t *a, _cups_section_t *b); |
| static void write_index(const char *path, help_index_t *hi); |
| static void write_info(const char *path, const char *revision); |
| static void write_nodes(const char *path, help_index_t *hi); |
| |
| |
| /* |
| * 'main()' - Test the help index code. |
| */ |
| |
| int /* O - Exit status */ |
| main(int argc, /* I - Number of command-line args */ |
| char *argv[]) /* I - Command-line arguments */ |
| { |
| int i; /* Looping var */ |
| char path[1024], /* Path to documentation */ |
| line[1024]; /* Line from file */ |
| help_index_t *hi; /* Help index */ |
| cups_file_t *tokens, /* Tokens.xml file */ |
| *fp; /* Current file */ |
| |
| |
| if (argc < 4) |
| { |
| puts("Usage: makedocset directory revision *.tokens"); |
| return (1); |
| } |
| |
| /* |
| * Index the help documents... |
| */ |
| |
| snprintf(path, sizeof(path), "%s/Contents/Resources/Documentation", argv[1]); |
| if ((hi = helpLoadIndex(NULL, path)) == NULL) |
| { |
| fputs("makedocset: Unable to index help files!\n", stderr); |
| return (1); |
| } |
| |
| snprintf(path, sizeof(path), "%s/Contents/Resources/Documentation/index.html", |
| argv[1]); |
| write_index(path, hi); |
| |
| snprintf(path, sizeof(path), "%s/Contents/Resources/Nodes.xml", argv[1]); |
| write_nodes(path, hi); |
| |
| /* |
| * Write the Info.plist file... |
| */ |
| |
| snprintf(path, sizeof(path), "%s/Contents/Info.plist", argv[1]); |
| write_info(path, argv[2]); |
| |
| /* |
| * Merge the Tokens.xml files... |
| */ |
| |
| snprintf(path, sizeof(path), "%s/Contents/Resources/Tokens.xml", argv[1]); |
| if ((tokens = cupsFileOpen(path, "w")) == NULL) |
| { |
| fprintf(stderr, "makedocset: Unable to create \"%s\": %s\n", path, |
| strerror(errno)); |
| return (1); |
| } |
| |
| cupsFilePuts(tokens, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); |
| cupsFilePuts(tokens, "<Tokens version=\"1.0\">\n"); |
| |
| for (i = 3; i < argc; i ++) |
| { |
| if ((fp = cupsFileOpen(argv[i], "r")) == NULL) |
| { |
| fprintf(stderr, "makedocset: Unable to open \"%s\": %s\n", argv[i], |
| strerror(errno)); |
| return (1); |
| } |
| |
| if (!cupsFileGets(fp, line, sizeof(line)) || strncmp(line, "<?xml ", 6) || |
| !cupsFileGets(fp, line, sizeof(line)) || strncmp(line, "<Tokens ", 8)) |
| { |
| fprintf(stderr, "makedocset: Bad Tokens.xml file \"%s\"!\n", argv[i]); |
| return (1); |
| } |
| |
| while (cupsFileGets(fp, line, sizeof(line))) |
| { |
| if (strcmp(line, "</Tokens>")) |
| cupsFilePrintf(tokens, "%s\n", line); |
| } |
| |
| cupsFileClose(fp); |
| } |
| |
| cupsFilePuts(tokens, "</Tokens>\n"); |
| |
| cupsFileClose(tokens); |
| |
| /* |
| * Return with no errors... |
| */ |
| |
| return (0); |
| } |
| |
| |
| /* |
| * 'compare_html()' - Compare the titles of two HTML files. |
| */ |
| |
| static int /* O - Result of comparison */ |
| compare_html(_cups_html_t *a, /* I - First file */ |
| _cups_html_t *b) /* I - Second file */ |
| { |
| return (_cups_strcasecmp(a->title, b->title)); |
| } |
| |
| |
| /* |
| * 'compare_sections()' - Compare the names of two help sections. |
| */ |
| |
| static int /* O - Result of comparison */ |
| compare_sections(_cups_section_t *a, /* I - First section */ |
| _cups_section_t *b) /* I - Second section */ |
| { |
| return (_cups_strcasecmp(a->name, b->name)); |
| } |
| |
| |
| /* |
| * 'compare_sections_files()' - Compare the number of files and section names. |
| */ |
| |
| static int /* O - Result of comparison */ |
| compare_sections_files( |
| _cups_section_t *a, /* I - First section */ |
| _cups_section_t *b) /* I - Second section */ |
| { |
| int ret = cupsArrayCount(b->files) - cupsArrayCount(a->files); |
| |
| if (ret) |
| return (ret); |
| else |
| return (_cups_strcasecmp(a->name, b->name)); |
| } |
| |
| |
| /* |
| * 'write_index()' - Write an index file for the CUPS help. |
| */ |
| |
| static void |
| write_index(const char *path, /* I - File to write */ |
| help_index_t *hi) /* I - Index of files */ |
| { |
| cups_file_t *fp; /* Output file */ |
| help_node_t *node; /* Current help node */ |
| _cups_section_t *section, /* Current section */ |
| key; /* Section search key */ |
| _cups_html_t *html; /* Current HTML file */ |
| cups_array_t *sections, /* Sections in index */ |
| *sections_files,/* Sections sorted by size */ |
| *columns[3]; /* Columns in final HTML file */ |
| int column, /* Current column */ |
| lines[3], /* Number of lines in each column */ |
| min_column, /* Smallest column */ |
| min_lines; /* Smallest number of lines */ |
| |
| |
| /* |
| * Build an array of sections and their files. |
| */ |
| |
| sections = cupsArrayNew((cups_array_func_t)compare_sections, NULL); |
| |
| for (node = (help_node_t *)cupsArrayFirst(hi->nodes); |
| node; |
| node = (help_node_t *)cupsArrayNext(hi->nodes)) |
| { |
| if (node->anchor) |
| continue; |
| |
| key.name = node->section ? node->section : "Miscellaneous"; |
| if ((section = (_cups_section_t *)cupsArrayFind(sections, &key)) == NULL) |
| { |
| section = (_cups_section_t *)calloc(1, sizeof(_cups_section_t)); |
| section->name = key.name; |
| section->files = cupsArrayNew((cups_array_func_t)compare_html, NULL); |
| |
| cupsArrayAdd(sections, section); |
| } |
| |
| html = (_cups_html_t *)calloc(1, sizeof(_cups_html_t)); |
| html->path = node->filename; |
| html->title = node->text; |
| |
| cupsArrayAdd(section->files, html); |
| } |
| |
| /* |
| * Build a sorted list of sections based on the number of files in each section |
| * and the section name... |
| */ |
| |
| sections_files = cupsArrayNew((cups_array_func_t)compare_sections_files, |
| NULL); |
| for (section = (_cups_section_t *)cupsArrayFirst(sections); |
| section; |
| section = (_cups_section_t *)cupsArrayNext(sections)) |
| cupsArrayAdd(sections_files, section); |
| |
| /* |
| * Then build three columns to hold everything, trying to balance the number of |
| * lines in each column... |
| */ |
| |
| for (column = 0; column < 3; column ++) |
| { |
| columns[column] = cupsArrayNew((cups_array_func_t)compare_sections, NULL); |
| lines[column] = 0; |
| } |
| |
| for (section = (_cups_section_t *)cupsArrayFirst(sections_files); |
| section; |
| section = (_cups_section_t *)cupsArrayNext(sections_files)) |
| { |
| for (min_column = 0, min_lines = lines[0], column = 1; |
| column < 3; |
| column ++) |
| { |
| if (lines[column] < min_lines) |
| { |
| min_column = column; |
| min_lines = lines[column]; |
| } |
| } |
| |
| cupsArrayAdd(columns[min_column], section); |
| lines[min_column] += cupsArrayCount(section->files) + 2; |
| } |
| |
| /* |
| * Write the HTML file... |
| */ |
| |
| if ((fp = cupsFileOpen(path, "w")) == NULL) |
| { |
| fprintf(stderr, "makedocset: Unable to create %s: %s\n", path, |
| strerror(errno)); |
| exit(1); |
| } |
| |
| cupsFilePuts(fp, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 " |
| "Transitional//EN\" " |
| "\"http://www.w3.org/TR/html4/loose.dtd\">\n" |
| "<html>\n" |
| "<head>\n" |
| "<title>CUPS Documentation</title>\n" |
| "<link rel='stylesheet' type='text/css' " |
| "href='cups-printable.css'>\n" |
| "</head>\n" |
| "<body>\n" |
| "<h1 class='title'>CUPS Documentation</h1>\n" |
| "<table width='100%' summary=''>\n" |
| "<tr>\n"); |
| |
| for (column = 0; column < 3; column ++) |
| { |
| if (column) |
| cupsFilePuts(fp, "<td> </td>\n"); |
| |
| cupsFilePuts(fp, "<td valign='top' width='33%'>"); |
| for (section = (_cups_section_t *)cupsArrayFirst(columns[column]); |
| section; |
| section = (_cups_section_t *)cupsArrayNext(columns[column])) |
| { |
| cupsFilePrintf(fp, "<h2 class='title'>%s</h2>\n", section->name); |
| for (html = (_cups_html_t *)cupsArrayFirst(section->files); |
| html; |
| html = (_cups_html_t *)cupsArrayNext(section->files)) |
| cupsFilePrintf(fp, "<p class='compact'><a href='%s'>%s</a></p>\n", |
| html->path, html->title); |
| } |
| cupsFilePuts(fp, "</td>\n"); |
| } |
| cupsFilePuts(fp, "</tr>\n" |
| "</table>\n" |
| "</body>\n" |
| "</html>\n"); |
| cupsFileClose(fp); |
| } |
| |
| |
| /* |
| * 'write_info()' - Write the Info.plist file. |
| */ |
| |
| static void |
| write_info(const char *path, /* I - File to write */ |
| const char *revision) /* I - Subversion revision number */ |
| { |
| cups_file_t *fp; /* File */ |
| |
| |
| if ((fp = cupsFileOpen(path, "w")) == NULL) |
| { |
| fprintf(stderr, "makedocset: Unable to create %s: %s\n", path, |
| strerror(errno)); |
| exit(1); |
| } |
| |
| cupsFilePrintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" |
| "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" " |
| "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" |
| "<plist version=\"1.0\">\n" |
| "<dict>\n" |
| "\t<key>CFBundleIdentifier</key>\n" |
| "\t<string>org.cups.docset</string>\n" |
| "\t<key>CFBundleName</key>\n" |
| "\t<string>CUPS Documentation</string>\n" |
| "\t<key>CFBundleVersion</key>\n" |
| "\t<string>%d.%d.%s</string>\n" |
| "\t<key>CFBundleShortVersionString</key>\n" |
| "\t<string>%d.%d.%d</string>\n" |
| "\t<key>DocSetFeedName</key>\n" |
| "\t<string>cups.org</string>\n" |
| "\t<key>DocSetFeedURL</key>\n" |
| "\t<string>http://www.cups.org/org.cups.docset.atom" |
| "</string>\n" |
| "\t<key>DocSetPublisherIdentifier</key>\n" |
| "\t<string>org.cups</string>\n" |
| "\t<key>DocSetPublisherName</key>\n" |
| "\t<string>CUPS</string>\n" |
| "</dict>\n" |
| "</plist>\n", |
| CUPS_VERSION_MAJOR, CUPS_VERSION_MINOR, revision, |
| CUPS_VERSION_MAJOR, CUPS_VERSION_MINOR, CUPS_VERSION_PATCH); |
| |
| cupsFileClose(fp); |
| } |
| |
| |
| /* |
| * 'write_nodes()' - Write the Nodes.xml file. |
| */ |
| |
| static void |
| write_nodes(const char *path, /* I - File to write */ |
| help_index_t *hi) /* I - Index of files */ |
| { |
| cups_file_t *fp; /* Output file */ |
| int id; /* Current node ID */ |
| help_node_t *node; /* Current help node */ |
| int subnodes; /* Currently in Subnodes for file? */ |
| int needclose; /* Need to close the current node? */ |
| |
| |
| if ((fp = cupsFileOpen(path, "w")) == NULL) |
| { |
| fprintf(stderr, "makedocset: Unable to create %s: %s\n", path, |
| strerror(errno)); |
| exit(1); |
| } |
| |
| cupsFilePuts(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" |
| "<DocSetNodes version=\"1.0\">\n" |
| "<TOC>\n" |
| "<Node id=\"0\">\n" |
| "<Name>CUPS Documentation</Name>\n" |
| "<Path>Documentation/index.html</Path>\n" |
| "</Node>\n"); |
| |
| for (node = (help_node_t *)cupsArrayFirst(hi->nodes), id = 1, subnodes = 0, |
| needclose = 0; |
| node; |
| node = (help_node_t *)cupsArrayNext(hi->nodes), id ++) |
| { |
| if (node->anchor) |
| { |
| if (!subnodes) |
| { |
| cupsFilePuts(fp, "<Subnodes>\n"); |
| subnodes = 1; |
| } |
| |
| cupsFilePrintf(fp, "<Node id=\"%d\">\n" |
| "<Path>Documentation/%s</Path>\n" |
| "<Anchor>%s</Anchor>\n" |
| "<Name>%s</Name>\n" |
| "</Node>\n", id, node->filename, node->anchor, |
| node->text); |
| } |
| else |
| { |
| if (subnodes) |
| { |
| cupsFilePuts(fp, "</Subnodes>\n"); |
| subnodes = 0; |
| } |
| |
| if (needclose) |
| cupsFilePuts(fp, "</Node>\n"); |
| |
| cupsFilePrintf(fp, "<Node id=\"%d\">\n" |
| "<Path>Documentation/%s</Path>\n" |
| "<Name>%s</Name>\n", id, node->filename, node->text); |
| needclose = 1; |
| } |
| } |
| |
| if (subnodes) |
| cupsFilePuts(fp, "</Subnodes>\n"); |
| |
| if (needclose) |
| cupsFilePuts(fp, "</Node>\n"); |
| |
| cupsFilePuts(fp, "</TOC>\n" |
| "</DocSetNodes>\n"); |
| |
| cupsFileClose(fp); |
| } |