/* * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include typedef struct usage { char *flag; char *arg; char *desc; char *functype; char *funcname; struct usage *funcargs; int optional; struct usage *next; } *usage_t; #define MAXCOMMANDS 100 usage_t usage_head[MAXCOMMANDS], usage_tail[MAXCOMMANDS]; char commandnames[MAXCOMMANDS][MAXNAMLEN]; int seen_name = 0; int multi_command_syntax = 0; char *striplines(char *line); #define MAX(a, b) ((ab) ? b : a) void xml2man(xmlNode *root, char *output_filename, int append_section_number); void parseUsage(xmlNode *node, int pos); char *xs(int count); void strip_dotmxml(char *filename) { char *last = &filename[strlen(filename)-5]; if (!strcmp(last, ".mxml")) *last = '\0'; } int main(int argc, char *argv[]) { xmlDocPtr dp; xmlNode *root; char output_filename[MAXNAMLEN]; int append_section_number; bzero(usage_head, (sizeof(usage_t) * MAXCOMMANDS)); bzero(usage_tail, (sizeof(usage_t) * MAXCOMMANDS)); if (argc < 1) { fprintf(stderr, "xml2man: No arguments given.\n"); exit(-1); } LIBXML_TEST_VERSION; if (argc >= 2) { if (!(dp = xmlParseFile(argv[1]))) { perror(argv[0]); fprintf(stderr, "xml2man: could not parse XML file\n"); exit(-1); } } else { char *buf = malloc(1024 * sizeof(char)); int bufpos = 0; int bufsize = 1024; while (1) { char line[1026]; int len; if (fgets(line, 1024, stdin) == NULL) break; len = strlen(line); while ((bufpos + len + 2) >= bufsize) { bufsize *= 2; buf = realloc(buf, bufsize); } strcat(&buf[bufpos], line); bufpos += len; } xmlParseMemory(buf, bufpos+1); } root = xmlDocGetRootElement(dp); /* Walk the tree and convert to mdoc */ if (argc >= 3) { int len = MAX(strlen(argv[2]), MAXNAMLEN-1); strncpy(output_filename, argv[2], len); output_filename[len] = '\0'; append_section_number = 0; } else if (argc >= 2) { int len = MAX(strlen(argv[1]), MAXNAMLEN-4); strncpy(output_filename, argv[1], len); output_filename[len] = '\0'; /* We'll append ".1" or whatever later. */ strip_dotmxml(output_filename); append_section_number = 1; } else { /* We'll dump to stdout at the right time */ output_filename[0] = 0; append_section_number = 0; } xml2man(root, output_filename, append_section_number); /* Clean up just to be polite. */ xmlFreeDoc(dp); xmlCleanupParser(); return 0; } char *textmatching(char *name, xmlNode *cur, int missing_ok); xmlNode *nodematching(char *name, xmlNode *cur); void writeData(FILE *fp, xmlNode *node); void writeUsage(FILE *fp); void xml2man(xmlNode *root, char *output_filename, int append_section_number) { int section; xmlNode *names, *usage, *retvals, *env, *files, *examples, *diags, *errs; xmlNode *seeAlso, *conformingTo, *history, *bugs; char *docdate = "January 1, 9999"; char *doctitle = "UNKNOWN MANPAGE"; char *os = ""; char *temp; FILE *fp; temp = textmatching("section", root->children, 0); if (temp) section = atoi(temp); else { fprintf(stderr, "Assuming section 1.\n"); section = 1; } temp = textmatching("docdate", root->children, 0); if (temp) docdate = temp; temp = textmatching("doctitle", root->children, 0); if (temp) doctitle = temp; temp = textmatching("os", root->children, 0); if (temp) os = temp; names = nodematching("names", root->children); usage = nodematching("usage", root->children); retvals = nodematching("returnvalues", root->children); env = nodematching("environment", root->children); files = nodematching("files", root->children); examples = nodematching("examples", root->children); diags = nodematching("diagnostics", root->children); errs = nodematching("errors", root->children); seeAlso = nodematching("seealso", root->children); conformingTo = nodematching("conformingto", root->children); history = nodematching("history", root->children); bugs = nodematching("bugs", root->children); if (usage) { parseUsage(usage->children, 0); } // printf("section %d\n", section); // printf("nodes: names = 0x%x, usage = 0x%x, retvals = 0x%x, env = 0x%x,\nfiles = 0x%x, examples = 0x%x, diags = 0x%x, errs = 0x%x,\nseeAlso = 0x%x, conformingTo = 0x%x, history = 0x%x, bugs = 0x%x\n", names, usage, retvals, env, files, examples, diags, errs, seeAlso, conformingTo, history, bugs); /* Write everything to stdout for now */ if (!strlen(output_filename)) { fp = stdout; } else { if (append_section_number) { sprintf(output_filename, "%s.%d", output_filename, section); } if ((fp = fopen(output_filename, "r"))) { fprintf(stderr, "error: file %s exists.\n", output_filename); exit(-1); } else { if (!(fp = fopen(output_filename, "w"))) { fprintf(stderr, "error: could not create file %s\n", output_filename); exit(-1); } } } /* write preamble */ fprintf(fp, ".\\\" Automatically generated from mdocxml\n"); fprintf(fp, ".Dd %s\n", docdate); fprintf(fp, ".Dt \"%s\" %d\n", doctitle, section); fprintf(fp, ".Os %s\n", os); /* write rest of contents */ writeData(fp, names); writeUsage(fp); writeData(fp, retvals); writeData(fp, env); writeData(fp, files); writeData(fp, examples); writeData(fp, diags); writeData(fp, errs); writeData(fp, seeAlso); writeData(fp, conformingTo); writeData(fp, history); writeData(fp, bugs); if (strlen(output_filename)) { fclose(fp); } } xmlNode *nodematching(char *name, xmlNode *cur) { while (cur) { if (!cur->name) break; if (!strcmp(cur->name, name)) break; cur = cur->next; } return cur; } char *textmatching(char *name, xmlNode *node, int missing_ok) { xmlNode *cur = nodematching(name, node); char *ret = NULL; if (!cur) { if (!missing_ok) { fprintf(stderr, "Invalid or missing contents for %s.\n", name); } } else if (cur && cur->children && cur->children->content) { ret = cur->children->content; } else if (!strcmp(name, "text")) { ret = cur->content; } else { fprintf(stderr, "Missing/invalid contents for %s.\n", name); } return ret; } enum states { kGeneral = 0, kNames = 1, kRetval = 2, kMan = 3, kLast = 256 }; void writeData_sub(FILE *fp, xmlNode *node, int state, int textcontainer, int next, int seendd); void writeData(FILE *fp, xmlNode *node) { writeData_sub(fp, node, 0, 0, 0, 0); } void writeData_sub(FILE *fp, xmlNode *node, int state, int textcontainer, int next, int seendd) { int oldtextcontainer = textcontainer; int oldstate = state; int drop_children = 0; char *tail = NULL; if (!node) return; if (!strcmp(node->name, "docdate")) { /* silently ignore */ writeData_sub(fp, node->next, state, 0, 1, seendd); return; } else if (!strcmp(node->name, "doctitle")) { /* silently ignore */ writeData_sub(fp, node->next, state, 0, 1, seendd); return; } else if (!strcmp(node->name, "section")) { if (state == kMan) { fprintf(fp, " "); tail = " "; } else { /* silently ignore */ writeData_sub(fp, node->next, state, 0, 1, seendd); return; } } else if (!strcmp(node->name, "desc")) { if (state == kNames && node->children) { fprintf(fp, ".Nd "); } // if (!node->children) tail = "\n"; } else if (!strcmp(node->name, "names")) { state = kNames; fprintf(fp, ".Sh NAME\n"); } else if (!strcmp(node->name, "name")) { if (state == kNames) { if (seen_name) { fprintf(fp, ".Pp\n"); } fprintf(fp, ".Nm "); textcontainer = 1; seen_name = 1; } else { char *tmp = textmatching("text", node->children, 0); fprintf(fp, ".Nm%s%s\n", (tmp ? " " : ""), (tmp ? tmp : "")); if (tmp) { textcontainer = 0; } } } else if (!strcmp(node->name, "usage")) { textcontainer = 0; } else if (!strcmp(node->name, "flag")) { } else if (!strcmp(node->name, "arg")) { } else if (!strcmp(node->name, "returnvalues")) { state = kRetval; textcontainer = 1; fprintf(fp, ".Sh RETURN VALUES\n"); } else if (!strcmp(node->name, "environment")) { state = kRetval; textcontainer = 1; fprintf(fp, ".Sh ENVIRONMENT\n"); } else if (!strcmp(node->name, "files")) { textcontainer = 0; fprintf(fp, ".Sh FILES\n"); fprintf(fp, ".Bl -tag -width indent\n"); tail = ".El\n"; } else if (!strcmp(node->name, "file")) { textcontainer = 1; fprintf(fp, ".It Pa "); } else if (!strcmp(node->name, "examples")) { state = kRetval; textcontainer = 1; fprintf(fp, ".Sh EXAMPLES\n"); } else if (!strcmp(node->name, "diagnostics")) { state = kRetval; textcontainer = 1; fprintf(fp, ".Sh DIAGNOSTICS\n"); } else if (!strcmp(node->name, "errors")) { state = kRetval; textcontainer = 1; fprintf(fp, ".Sh ERRORS\n"); } else if (!strcmp(node->name, "seealso")) { state = kRetval; textcontainer = 1; fprintf(fp, ".Sh SEE ALSO\n"); } else if (!strcmp(node->name, "conformingto")) { state = kRetval; textcontainer = 1; fprintf(fp, ".Sh CONFORMING TO\n"); } else if (!strcmp(node->name, "history")) { state = kRetval; textcontainer = 1; fprintf(fp, ".Sh HISTORY\n"); } else if (!strcmp(node->name, "bugs")) { state = kRetval; textcontainer = 1; fprintf(fp, ".Sh BUGS\n"); } else if (!strcmp(node->name, "p")) { tail = ".Pp\n"; } else if (!strcmp(node->name, "blockquote")) { fprintf(fp, ".Bl -tag -width indent\n"); tail = ".El\n"; } else if (!strcmp(node->name, "dl")) { int minwidth = 6; xmlNode *ddnode = node->children; textcontainer = 0; for ( ; ddnode ; ddnode = ddnode->next) { char *childtext; if (strcmp(ddnode->name, "dd")) continue; childtext = textmatching("text", ddnode->children, 0); if (childtext) { minwidth = MIN(minwidth, strlen(childtext)); } } fprintf(fp, ".Bl -tag -width %s\n", xs(minwidth-1)); tail = ".El\n"; } else if (!strcmp(node->name, "dd")) { char *guts = textmatching("text", node->children, 0); textcontainer = 0; drop_children = 1; seendd = 1; fprintf(fp, ".It %s", guts ? guts : ""); tail = "\n"; } else if (!strcmp(node->name, "dt")) { if (!seendd) { fprintf(fp, ".It\n"); } textcontainer = 1; tail = "\n"; } else if (!strcmp(node->name, "tt")) { fprintf(fp, ".Dl "); } else if (!strcmp(node->name, "ul")) { fprintf(fp, ".Bl -bullet\n"); tail = ".El\n"; } else if (!strcmp(node->name, "ol")) { fprintf(fp, ".Bl -enum\n"); tail = ".El\n"; } else if (!strcmp(node->name, "li")) { fprintf(fp, ".It\n"); } else if (!strcmp(node->name, "code")) { fprintf(fp, ".Li "); } else if (!strcmp(node->name, "path")) { fprintf(fp, ".Pa "); } else if (!strcmp(node->name, "function")) { fprintf(fp, ".Fn "); } else if (!strcmp(node->name, "command")) { /* @@@ Is this right? @@@ */ fprintf(fp, ".Nm "); } else if (!strcmp(node->name, "manpage")) { /* Cross-reference */ fprintf(fp, ".Xr "); state = kMan; tail = "\n"; textcontainer = 1; } else if (!strcmp(node->name, "text")) { if (textcontainer) { char *stripped_text = striplines(node->content); if (strlen(stripped_text)) { fprintf(fp, "%s%s", stripped_text, (state == kMan ? "" : "\n")); } } } else { fprintf(stderr, "unknown field %s\n", node->name); } if (!drop_children) { writeData_sub(fp, node->children, state, textcontainer, 1, seendd); } textcontainer = oldtextcontainer; state = oldstate; if (tail) { fprintf(fp, "%s", tail); } if (next) { writeData_sub(fp, node->next, state, textcontainer, 1, seendd); } } void write_funcargs(FILE *fp, usage_t cur) { for (; cur; cur = cur->next) { fprintf(fp, ".It Ar \"%s\"", (cur->arg ? cur->arg : "")); fprintf(fp, "\n%s%s", (cur->desc ? cur->desc : ""), (cur->desc ? "\n" : "")); } } char *xs(int count) { static char *buffer = NULL; if (buffer) free(buffer); buffer = malloc((count+1) * sizeof(char)); if (buffer) { int i; for (i=0; inext) { int len; len = 0; if (cur->flag) len += strlen(cur->flag); if (cur->arg) len += strlen(cur->arg); if (len > lwc) lwc = len; } lwc += 4; first = 1; for (cur = usage_head[pos]; cur; cur = cur->next) { if (cur->flag) { if (first) { fprintf(fp, ".Nm%s%s\n", (name_or_empty ? " " : ""), (name_or_empty ? name_or_empty : "")); first = 0; } fprintf(fp, ".%sFl %s", (cur->optional?"Op ":""), cur->flag); } if (cur->arg) { if (first) { fprintf(fp, ".Nm%s%s\n", (name_or_empty ? " " : ""), (name_or_empty ? name_or_empty : "")); first = 0; } fprintf(fp, "%s%sAr %s", (cur->flag?" ":"."), (cur->optional?"Op ":""), cur->arg); } if (cur->functype) { usage_t arg; fprintf(fp, ".Ft %s\n", cur->functype); fprintf(fp, ".Fn \"%s\" ", cur->funcname); for (arg = cur->funcargs; arg; arg = arg->next) { fprintf(fp, "\"%s\" ", arg->arg); } function = 1; } else if (cur->funcargs) { usage_t arg; for (arg = cur->funcargs; arg; arg = arg->next) { fprintf(fp, " %sAr %s", (arg->optional?"Op ":""), arg->arg); } } fprintf(fp, "\n"); } } /* Write OPTIONS section */ topfirst = 1; for (pos = 0; pos < (multi_command_syntax ? multi_command_syntax : 1); pos++) { first=1; for (cur = usage_head[pos]; cur; cur = cur->next) { if (cur->funcargs && !cur->flag) { if (first) { if (topfirst) { fprintf(fp, ".Sh OPTIONS\n"); } first = 0; topfirst = 0; fprintf(fp, ".Bl -tag -width %s\n", xs(lwc)); } write_funcargs(fp, cur->funcargs); continue; } // if (!cur->flag) continue; if (!cur->desc) continue; if (first) { if (topfirst) { fprintf(fp, ".Sh OPTIONS\n"); } fprintf(fp, "The available options %s%s%sare as follows:\n", (name_or_empty ? "for " : ""), (name_or_empty ? name_or_empty : ""), (name_or_empty ? " " : "")); fprintf(fp, ".Bl -tag -width %s\n", xs(lwc)); first = 0; topfirst = 0; } fprintf(fp, ".It"); if (cur->flag) { fprintf(fp, " Fl %s", cur->flag); } if (cur->arg) { fprintf(fp, " Ar \"%s\"", cur->arg); } fprintf(fp, "\n%s\n", cur->desc); } if (!first) { fprintf(fp, ".El\n"); } } } char *propstring(char *name, struct _xmlAttr *prop) { for (; prop; prop=prop->next) { if (!strcmp(prop->name, name)) { if (prop->children && prop->children->content) { return prop->children->content; } } } return NULL; } int propval(char *name, struct _xmlAttr *prop) { char *ps = propstring(name, prop); if (!ps) { /* Assume 0 if property not found */ return 0; } return atoi(ps); } usage_t getflagargs(xmlNode *node) { usage_t head = NULL, tail = NULL; usage_t newnode; while (node) { if (strcmp(node->name, "arg")) { node = node->next; continue; } if (!(newnode = malloc(sizeof(struct usage)))) return NULL; newnode->flag = NULL; newnode->arg = textmatching("text", node->children, 0); newnode->desc = NULL; newnode->optional = propval("optional", node->properties); newnode->functype = NULL; newnode->funcname = NULL; newnode->funcargs = NULL; newnode->next = NULL; if (!head) { head = newnode; tail = newnode; } else { tail->next = newnode; tail = newnode; } node = node->next; } return head; } void parseUsage(xmlNode *node, int pos) { usage_t flag_or_arg; if (!node) return; if (!strcmp(node->name, "text") || !strcmp(node->name, "type") || !strcmp(node->name, "name")) { parseUsage(node->next, pos); return; } if (!strcmp(node->name, "command")) { int i = 0; while (node) { // printf("MCS\n"); if (strcmp(node->name, "command")) { node = node->next; continue; } char *name = propstring("name", node->properties); if (name) { strcpy(commandnames[i], name); } else { fprintf(stderr, "WARNING: command has no name\n"); } // fprintf(stderr, "CMDNAMES[%d] = %s\n", i, commandnames[i]); parseUsage(node->children, i); multi_command_syntax = i+1; node = node->next; if ((++i >= MAXCOMMANDS) && node) { fprintf(stderr, "MAXCOMMANDS reached.\n"); break; } } return; } flag_or_arg = (usage_t)malloc(sizeof(struct usage)); if (!flag_or_arg) return; if (!usage_head[pos]) { usage_head[pos] = flag_or_arg; usage_tail[pos] = flag_or_arg; } else { usage_tail[pos]->next = flag_or_arg; usage_tail[pos] = flag_or_arg; } if (!strcmp(node->name, "arg")) { flag_or_arg->flag = NULL; flag_or_arg->arg = textmatching("text", node->children, 0); flag_or_arg->desc = textmatching("desc", node->children, 0); flag_or_arg->optional = propval("optional", node->properties); flag_or_arg->functype = NULL; flag_or_arg->funcname = NULL; flag_or_arg->funcargs = NULL; flag_or_arg->next = NULL; } else if (!strcmp(node->name, "flag")) { flag_or_arg->flag = textmatching("text", node->children, 0); flag_or_arg->arg = NULL; flag_or_arg->desc = textmatching("desc", node->children, 0); flag_or_arg->optional = propval("optional", node->properties); flag_or_arg->functype = NULL; flag_or_arg->funcname = NULL; flag_or_arg->funcargs = getflagargs(node->children); flag_or_arg->next = NULL; } else if (!strcmp(node->name, "func")) { /* "func" */ flag_or_arg->flag = NULL; flag_or_arg->arg = NULL; flag_or_arg->desc = NULL; flag_or_arg->optional = 0; flag_or_arg->functype = textmatching("type", node->children, 0); flag_or_arg->funcname = textmatching("name", node->children, 0); flag_or_arg->next = NULL; // printf("RECURSE\n"); parseUsage(node->children, pos); // printf("RECURSEOUT\n"); flag_or_arg->funcargs = flag_or_arg->next; usage_tail[pos] = flag_or_arg; flag_or_arg->next = NULL; } else { fprintf(stderr, "UNKNOWN NODE NAME: %s\n", node->name); } parseUsage(node->next, pos); } enum stripstate { kSOL = 1, kText = 2 }; char *striplines(char *line) { static char *ptr = NULL; char *pos; char *linepos; int state = 0; if (!line) return ""; linepos = line; if (ptr) free(ptr); ptr = malloc(strlen(line) * sizeof(char)); state = kSOL; pos = ptr; for (pos=ptr; (*linepos); linepos++,pos++) { switch(state) { case kSOL: if (*linepos == ' ' || *linepos == '\n' || *linepos == '\r' || *linepos == '\t') { pos--; continue; } case kText: if (*linepos == '\n' || *linepos == '\r') { state = kSOL; *pos = ' '; } else { state = kText; *pos = *linepos; } } } *pos = '\0'; // printf("LINE \"%s\" changed to \"%s\"\n", line, ptr); return ptr; }