/* autoXml - Generate structures code and parser for XML file from DTD-like spec. */ #include "common.h" #include "linefile.h" #include "hash.h" #include "dystring.h" #include "cheapcgi.h" #include "obscure.h" #include "portable.h" #include "dtdParse.h" /* Variables that can be over-ridden from command line. */ char *textField = "text"; char *fileComment = "autoXml generated file"; boolean picky; /* Generate a parser that is afraid of the unknown. */ boolean makeMain; /* Generate main() routine as test shell. */ boolean positiveOnly; /* Don't write out negative numbers. */ char prefix[128]; /* Added to start of output file and structure names. */ void usage() /* Explain usage and exit. */ { errAbort( "autoXml - Generate structures code and parser for XML file from DTD-like spec\n" "usage:\n" " autoXml file.dtdx root\n" "This will generate root.c, root.h\n" "options:\n" " -textField=xxx what to name text between start/end tags. Default 'text'\n" " -comment=xxx Comment to appear at top of generated code files\n" " -picky Generate parser that rejects stuff it doesn't understand\n" " -main Put in a main routine that's a test harness\n" " -prefix=xxx Prefix to add to structure names. By default same as root\n" " -positive Don't write out optional attributes with negative values\n" ); } char *cAttType(char *type) /* Return C declaration corresponding to type. */ { if (sameWord(type, "INT")) return "int "; else if (sameWord(type, "FLOAT")) return "double "; else return "char *"; } char fAttType(char *type) /* Return printf %code to type. */ { if (sameWord(type, "INT")) return 'd'; else if (sameWord(type, "FLOAT")) return 'f'; else return 's'; } boolean childMatch(struct dtdElChild *children, struct dtdElement *el) /* Return TRUE if any of children are el. */ { struct dtdElChild *ec; for (ec = children; ec != NULL; ec = ec->next) { if (ec->el == el) return TRUE; } return FALSE; } boolean anyParent(struct dtdElement *elList, struct dtdElement *child) /* Return TRUE if anybody in elList could be a parent to child. */ { struct dtdElement *el; for (el = elList; el != NULL; el = el->next) { if (childMatch(el->children, child)) return TRUE; } return FALSE; } void freeFunctionPrototype(struct dtdElement *el, FILE *f, char *addSemi) /* Put up function prototype for elFree function. */ { char *name = el->mixedCaseName; fprintf(f, "void %sFree(struct %s **pObj)%s", name, name, addSemi); fprintf(f, "\n"); fprintf(f, "/* Free up %s. */\n", name); } void freeListFunctionPrototype(struct dtdElement *el, FILE *f, char *addSemi) /* Put up function prototype for elFreeList function. */ { char *name = el->mixedCaseName; fprintf(f, "void %sFreeList(struct %s **pList)%s", name, name, addSemi); fprintf(f, "\n"); fprintf(f, "/* Free up list of %s. */\n", name); } void saveFunctionPrototype(struct dtdElement *el, FILE *f, char *addSemi) /* Put up function prototype for elSave function. */ { char *name = el->mixedCaseName; fprintf(f, "void %sSave(struct %s *obj, int indent, FILE *f)%s", name, name, addSemi); fprintf(f, "\n"); fprintf(f, "/* Save %s to file. */\n", name); } void loadFunctionPrototype(struct dtdElement *el, FILE *f, char *addSemi) /* Put up function prototype for elLoad function. */ { char *name = el->mixedCaseName; fprintf(f, "struct %s *%sLoad(char *fileName)%s\n", name, name, addSemi); fprintf(f, "/* Load %s from XML file where it is root element. */\n", name); } void loadFunctionBody(struct dtdElement *el, FILE *f) /* Write body of elLoad function. */ { fprintf(f, "{\n"); fprintf(f, "struct %s *obj;\n", el->mixedCaseName); fprintf(f, "xapParseAny(fileName, \"%s\", %sStartHandler, %sEndHandler, NULL, &obj);\n", el->name, prefix, prefix); fprintf(f, "return obj;\n"); fprintf(f, "}\n"); fprintf(f, "\n"); } void loadNextFunctionPrototype(struct dtdElement *el, FILE *f, char *addSemi) /* Put up function prototype for elLoad function. */ { char *name = el->mixedCaseName; fprintf(f, "struct %s *%sLoadNext(struct xap *xap)%s\n", name, name, addSemi); fprintf(f, "/* Load next %s element. Use xapOpen to get xap. */\n", name); } void loadNextFunctionBody(struct dtdElement *el, FILE *f) /* Write body of elLoad function. */ { fprintf(f, "{\n"); fprintf(f, "return xapNext(xap, \"%s\");\n", el->name); fprintf(f, "}\n"); fprintf(f, "\n"); } void startHandlerPrototype(FILE *f, char *addSemi) /* Print function prototype for startHandler. */ { fprintf(f, "void *%sStartHandler(struct xap *xp, char *name, char **atts)%s\n", prefix, addSemi); fprintf(f, "/* Called by xap with start tag. Does most of the parsing work. */\n"); } void makeStartHandler(struct dtdElement *elList, FILE *f) /* Create function that gets called by expat at start of tag. */ { struct dtdElement *el; struct dtdAttribute *att; startHandlerPrototype(f, ""); fprintf(f, "{\n"); fprintf(f, "struct xapStack *st = xp->stack+1;\n"); fprintf(f, "int depth = xp->stackDepth;\n"); fprintf(f, "int i;\n"); fprintf(f, "\n"); for (el = elList; el != NULL; el = el->next) { fprintf(f, "%sif (sameString(name, \"%s\"))\n", (el == elList ? "" : "else "), el->name); fprintf(f, " {\n"); fprintf(f, " struct %s *obj;\n", el->mixedCaseName); fprintf(f, " AllocVar(obj);\n"); if (el->attributes != NULL) { boolean first = TRUE; for (att = el->attributes; att != NULL; att = att->next) { if (att->usual != NULL) { char *quote = "\""; if (sameString(att->type, "INT") || sameString(att->type, "FLOAT")) quote = ""; fprintf(f, " obj->%s = %s%s%s;\n", att->mixedCaseName, quote, att->usual, quote); } } fprintf(f, " for (i=0; atts[i] != NULL; i += 2)\n"); fprintf(f, " {\n"); fprintf(f, " char *name = atts[i], *val = atts[i+1];\n"); for (att = el->attributes; att != NULL; att = att->next) { fprintf(f, " %s (sameString(name, \"%s\"))\n", (first ? "if " : "else if"), att->name); if (sameWord(att->type, "INT")) fprintf(f, " obj->%s = atoi(val);\n", att->mixedCaseName); else if (sameWord(att->type, "FLOAT")) fprintf(f, " obj->%s = atof(val);\n", att->mixedCaseName); else fprintf(f, " obj->%s = cloneString(val);\n", att->mixedCaseName); first = FALSE; } if (picky) { fprintf(f, " else\n"); fprintf(f, " xapError(xp, \"Unknown attribute %%s\", name);\n"); } fprintf(f, " }\n"); for (att = el->attributes; att != NULL; att = att->next) { if (att->required) { if (sameWord(att->type, "INT") || sameWord(att->type, "FLOAT")) { /* For the moment can't check these. */ } else { fprintf(f, " if (obj->%s == NULL)\n", att->mixedCaseName); fprintf(f, " xapError(xp, \"missing %s\");\n", att->name); } } } } if (anyParent(elList, el)) { struct dtdElement *parent; boolean first = TRUE; fprintf(f, " if (depth > 1)\n"); fprintf(f, " {\n"); for (parent = elList; parent != NULL; parent = parent->next) { if (childMatch(parent->children, el)) { fprintf(f, " %s (sameString(st->elName, \"%s\"))\n", (first ? "if " : "else if"), parent->name); fprintf(f, " {\n"); fprintf(f, " struct %s *parent = st->object;\n", parent->mixedCaseName); fprintf(f, " slAddHead(&parent->%s, obj);\n", el->mixedCaseName); fprintf(f, " }\n"); first = FALSE; } } if (picky) { fprintf(f, " else\n"); fprintf(f, " xapError(xp, \"%%s misplaced\", name);\n"); } fprintf(f, " }\n"); } fprintf(f, " return obj;\n"); fprintf(f, " }\n"); } fprintf(f, "else\n"); fprintf(f, " {\n"); if (picky) fprintf(f, " xapError(xp, \"Unknown element %%s\", name);\n"); else fprintf(f, " xapSkip(xp);\n"); fprintf(f, " return NULL;\n"); fprintf(f, " }\n"); fprintf(f, "}\n"); fprintf(f, "\n"); } void endHandlerPrototype(FILE *f, char *addSemi) /* Print function prototype for endHandler. */ { fprintf(f, "void %sEndHandler(struct xap *xp, char *name)%s\n", prefix, addSemi); fprintf(f, "/* Called by xap with end tag. Checks all required children are loaded. */\n"); } void makeEndHandler(struct dtdElement *elList, FILE *f) /* Create function that gets called by expat at end of tag. */ { struct dtdElement *el; struct dtdElChild *ec; boolean first = TRUE; endHandlerPrototype(f, ""); fprintf(f, "{\n"); fprintf(f, "struct xapStack *stack = xp->stack;\n"); for (el = elList; el != NULL; el = el->next) { if (el->children || el->textType) { fprintf(f, "%sif (sameString(name, \"%s\"))\n", (first ? "" : "else "), el->name); fprintf(f, " {\n"); fprintf(f, " struct %s *obj = stack->object;\n", el->mixedCaseName); for (ec = el->children; ec != NULL; ec = ec->next) { char *cBIG = ec->el->name; char *cSmall = ec->el->mixedCaseName; if (ec->copyCode == '1') { fprintf(f, " if (obj->%s == NULL)\n", cSmall); fprintf(f, " xapError(xp, \"Missing %s\");\n", cBIG); fprintf(f, " if (obj->%s->next != NULL)\n", cSmall); fprintf(f, " xapError(xp, \"Multiple %s\");\n", cBIG); } else if (ec->copyCode == '+') { if (! ec->isOr) { /* bypassing this is not the Right thing to do -- * really we should make sure that somebody in the Or list * was present */ fprintf(f, " if (obj->%s == NULL)\n", cSmall); fprintf(f, " xapError(xp, \"Missing %s\");\n",cBIG); } fprintf(f, " slReverse(&obj->%s);\n", cSmall); } else if (ec->copyCode == '*') { fprintf(f, " slReverse(&obj->%s);\n", cSmall); } else if (ec->copyCode == '?') { fprintf(f, " if (obj->%s != NULL && obj->%s->next != NULL)\n", cSmall, cSmall); fprintf(f, " xapError(xp, \"Multiple %s\");\n", cBIG); } } if (el->textType) { if (sameString(el->textType, "#INT")) fprintf(f, " obj->%s = atoi(stack->%s->string);\n", textField, textField); else if (sameString(el->textType, "#FLOAT")) fprintf(f, " obj->%s = atof(stack->%s->string);\n", textField, textField); else fprintf(f, " obj->%s = cloneString(stack->%s->string);\n", textField, textField); } fprintf(f, " }\n"); first = FALSE; } } fprintf(f, "}\n"); fprintf(f, "\n"); } void makeH(struct dtdElement *elList, char *fileName) /* Produce C header file. */ { FILE *f = mustOpen(fileName, "w"); struct dtdElement *el; struct dtdAttribute *att; struct dtdElChild *ec; char upcPrefix[128]; fprintf(f, "/* %s.h %s */\n", prefix, fileComment); strcpy(upcPrefix, prefix); touppers(upcPrefix); fprintf(f, "#ifndef %s_H\n", upcPrefix); fprintf(f, "#define %s_H\n", upcPrefix); fprintf(f, "\n"); fprintf(f, "#ifndef XAP_H\n"); fprintf(f, "#include \"xap.h\"\n"); fprintf(f, "#endif\n"); fprintf(f, "\n"); fprintf(f, "/* The start and end handlers here are used with routines defined in xap.h.\n" " * In particular if you want to read just parts of the XML file into memory\n" " * call xapOpen() with these, and then xapNext() with the name of the tag\n" " * you want to load. */\n\n"); startHandlerPrototype(f, ";"); fprintf(f, "\n"); endHandlerPrototype(f, ";"); fprintf(f, "\n"); fprintf(f, "\n"); for (el = elList; el != NULL; el = el->next) { fprintf(f, "struct %s\n", el->mixedCaseName); fprintf(f, " {\n"); fprintf(f, " struct %s *next;\n", el->mixedCaseName); if (el->textType != NULL) { if (sameString(el->textType, "#INT")) fprintf(f, " int %s;\n", textField); else if (sameString(el->textType, "#FLOAT")) fprintf(f, " float %s;\n", textField); else fprintf(f, " char *%s;\n", textField); } for (att = el->attributes; att != NULL; att = att->next) { fprintf(f, " %s%s;", cAttType(att->type), att->mixedCaseName); if (att->required) fprintf(f, "\t/* Required */"); else { if (att->usual != NULL) fprintf(f, "\t/* Defaults to %s */", att->usual); else fprintf(f, "\t/* Optional */"); } fprintf(f, "\n"); } for (ec = el->children; ec != NULL; ec = ec->next) { fprintf(f, " struct %s *%s;", ec->el->mixedCaseName, ec->el->mixedCaseName); if (ec->copyCode == '1') fprintf(f, "\t/** Single instance required. **/"); else if (ec->copyCode == '+') fprintf(f, "\t/** Non-empty list required. **/"); else if (ec->copyCode == '*') fprintf(f, "\t/** Possibly empty list. **/"); else if (ec->copyCode == '?') fprintf(f, "\t/** Optional (may be NULL). **/"); fprintf(f, "\n"); } fprintf(f, " };\n"); fprintf(f, "\n"); freeFunctionPrototype(el, f, ";"); fprintf(f, "\n"); freeListFunctionPrototype(el, f, ";"); fprintf(f, "\n"); saveFunctionPrototype(el, f, ";"); fprintf(f, "\n"); loadFunctionPrototype(el, f, ";"); fprintf(f, "\n"); loadNextFunctionPrototype(el, f, ";"); fprintf(f, "\n"); } fprintf(f, "#endif /* %s_H */\n", upcPrefix); fprintf(f, "\n"); } boolean optionalChildren(struct dtdElement *el) /* Return TRUE if not sure whether you have children. */ { struct dtdElChild *ec; boolean required = FALSE, optional = FALSE; for (ec = el->children; ec != NULL; ec = ec->next) { if (ec->copyCode == '*' || ec->copyCode == '?') optional = TRUE; else required = TRUE; } return !required && optional; } boolean isAtomic(struct dtdElement *el) /* Return TRUE if by definition no children. */ { return el->textType == NULL && el->children == NULL; } void saveFunctionBody(struct dtdElement *el, FILE *f) /* Write out save function body. */ { struct dtdElChild *ec; struct dtdAttribute *att; boolean optKids = optionalChildren(el); boolean isAtom = isAtomic(el); fprintf(f, "{\n"); /* Declare variables to walk through list if need be. */ for (ec = el->children; ec != NULL; ec = ec->next) { if (ec->copyCode == '*' || ec->copyCode == '+') { char *name = ec->el->mixedCaseName; fprintf(f, "struct %s *%s;\n", name, name); } } if (optKids) fprintf(f, "boolean isNode = TRUE;\n"); fprintf(f, "if (obj == NULL) return;\n"); fprintf(f, "xapIndent(indent, f);\n"); if (el->attributes == NULL) { if (isAtom) fprintf(f, "fprintf(f, \" ->\\n\");\n"); else fprintf(f, "fprintf(f, \"<%s>\");\n", el->name); } else { fprintf(f, "fprintf(f, \"<%s\");\n", el->name); for (att = el->attributes; att != NULL; att = att->next) { if (att->required ) fprintf(f, "fprintf(f, \" %s=\\\"%%%c\\\"\", obj->%s);\n", att->name, fAttType(att->type), att->mixedCaseName); else { if (sameString(att->type, "INT") || sameString(att->type, "FLOAT")) { if (positiveOnly) { fprintf(f, "if (obj->%s >= 0)\n ", att->mixedCaseName); } fprintf(f, "fprintf(f, \" %s=\\\"%%%c\\\"\", obj->%s);\n", att->name, fAttType(att->type), att->mixedCaseName); } else { fprintf(f, "if (obj->%s != NULL)\n", att->mixedCaseName); fprintf(f, " fprintf(f, \" %s=\\\"%%%c\\\"\", obj->%s);\n", att->name, fAttType(att->type), att->mixedCaseName); } } } if (isAtom) fprintf(f, "fprintf(f, \"/>\\n\");\n"); else fprintf(f, "fprintf(f, \">\");\n"); } if (el->textType != NULL) { if (sameString(el->textType, "#INT")) fprintf(f, "fprintf(f, \"%%d\", obj->%s);\n", textField); else if (sameString(el->textType, "#FLOAT")) fprintf(f, "fprintf(f, \"%%f\", obj->%s);\n", textField); else fprintf(f, "fprintf(f, \"%%s\", obj->%s);\n", textField); } if (isAtom) { } else if (el->children == NULL) { fprintf(f, "fprintf(f, \"\\n\");\n", el->name); } else { if (!optKids) fprintf(f, "fprintf(f, \"\\n\");\n"); for (ec = el->children; ec != NULL; ec = ec->next) { char *name = ec->el->mixedCaseName; if (ec->copyCode == '*' || ec->copyCode == '+') { fprintf(f, "for (%s = obj->%s; %s != NULL; %s = %s->next)\n", name, name, name, name, name); fprintf(f, " {\n"); if (optKids) { fprintf(f, " if (isNode)\n"); fprintf(f, " {\n"); fprintf(f, " fprintf(f, \"\\n\");\n"); fprintf(f, " isNode = FALSE;\n"); fprintf(f, " }\n"); } fprintf(f, " %sSave(%s, indent+2, f);\n", name, name); fprintf(f, " }\n"); } else { if (optKids) { fprintf(f, "if (obj->%s != NULL)\n", name); fprintf(f, " {\n"); fprintf(f, " if (isNode)\n"); fprintf(f, " {\n"); fprintf(f, " fprintf(f, \"\\n\");\n"); fprintf(f, " isNode = FALSE;\n"); fprintf(f, " }\n"); fprintf(f, " %sSave(obj->%s, indent+2, f);\n", name, name); fprintf(f, " }\n"); } else fprintf(f, "%sSave(obj->%s, indent+2, f);\n", name, name); } } if (optKids) { fprintf(f, "if (!isNode)\n"); fprintf(f, " xapIndent(indent, f);\n"); } else { fprintf(f, "xapIndent(indent, f);\n"); } fprintf(f, "fprintf(f, \"\\n\");\n", el->name); } fprintf(f, "}\n"); fprintf(f, "\n"); } void freeFunctionBody(struct dtdElement *el, FILE *f) /* Write out free function body. */ { struct dtdElChild *ec; struct dtdAttribute *att; fprintf(f, "{\n"); fprintf(f, "struct %s *obj = *pObj;\n", el->mixedCaseName); /* Declare variables to walk through list if need be. */ fprintf(f, "if (obj == NULL) return;\n"); for (att = el->attributes; att != NULL; att = att->next) { if (!sameString(att->type, "INT") && !sameString(att->type, "FLOAT")) fprintf(f, "freeMem(obj->%s);\n", att->mixedCaseName); } if (el->textType != NULL) { if (!sameString(el->textType, "#INT") && !sameString(el->textType, "#FLOAT")) fprintf(f, "freeMem(obj->text);\n"); } for (ec = el->children; ec != NULL; ec = ec->next) { char *name = ec->el->mixedCaseName; if (ec->copyCode == '*' || ec->copyCode == '+') fprintf(f, "%sFreeList(&obj->%s);\n", name, name); else fprintf(f, "%sFree(&obj->%s);\n", name, name); } fprintf(f, "freez(pObj);\n"); fprintf(f, "}\n"); fprintf(f, "\n"); } void freeListFunctionBody(struct dtdElement *el, FILE *f) /* Write out free function body. */ { fprintf(f, "{\n"); fprintf(f, "struct %s *el, *next;\n", el->mixedCaseName); fprintf(f, "for (el = *pList; el != NULL; el = next)\n"); fprintf(f, " {\n"); fprintf(f, " next = el->next;\n"); fprintf(f, " %sFree(&el);\n", el->mixedCaseName); fprintf(f, " el = next;\n"); fprintf(f, " }\n"); fprintf(f, "}\n"); fprintf(f, "\n"); } void makeTestDriver(struct dtdElement *rootEl, FILE *f) /* Make main routine. */ { char *symName = rootEl->mixedCaseName; fprintf(f, "int main(int argc, char *argv[])\n"); fprintf(f, "/* Test driver for %s routines */\n", prefix); fprintf(f, "{\n"); fprintf(f, "struct %s *obj;\n", symName); fprintf(f, "if (argc != 2)\n"); fprintf(f, " errAbort(\"Please run again with a xml filename.\");\n"); fprintf(f, "obj = %sLoad(argv[1]);\n", symName); fprintf(f, "%sSave(obj, 0, stdout);\n", symName); fprintf(f, "%sFree(&obj);\n", symName); fprintf(f, "return 0;\n"); fprintf(f, "}\n"); } void makeC(struct dtdElement *elList, char *fileName, char *incName) /* Produce C code file. */ { FILE *f = mustOpen(fileName, "w"); struct dtdElement *el; char incFile[128], incExt[64]; splitPath(incName, NULL, incFile, incExt); fprintf(f, "/* %s.c %s */\n", prefix, fileComment); fprintf(f, "\n"); fprintf(f, "#include \"common.h\"\n"); fprintf(f, "#include \"xap.h\"\n"); fprintf(f, "#include \"%s%s\"\n", incFile, incExt); fprintf(f, "\n"); fprintf(f, "\n"); for (el = elList; el != NULL; el = el->next) { freeFunctionPrototype(el, f, ""); freeFunctionBody(el, f); freeListFunctionPrototype(el, f, ""); freeListFunctionBody(el, f); saveFunctionPrototype(el, f, ""); saveFunctionBody(el, f); loadFunctionPrototype(el, f, ""); loadFunctionBody(el, f); loadNextFunctionPrototype(el, f, ""); loadNextFunctionBody(el, f); } makeStartHandler(elList, f); makeEndHandler(elList, f); if (makeMain) makeTestDriver(elList, f); } void autoXml(char *dtdxFile, char *outRoot) /* autoXml - Generate structures code and parser for XML file from DTD-like spec. */ { struct dtdElement *elList = NULL; struct hash *elHash = NULL; char hName[512], cName[512]; char outDir[256]; splitPath(outRoot, outDir, prefix, NULL); if (cgiVarExists("prefix")) strcpy(prefix, cgiString("prefix")); if (outDir[0] != 0) makeDir(outDir); dtdParse(dtdxFile, prefix, textField, &elList, &elHash); printf("Parsed %d elements in %s\n", slCount(elList), dtdxFile); sprintf(hName, "%s.h", outRoot); makeH(elList, hName); sprintf(cName, "%s.c", outRoot); makeC(elList, cName, hName); printf("Generated code in %s\n", cName); } int main(int argc, char *argv[]) /* Process command line. */ { cgiSpoof(&argc, argv); textField = cgiUsualString("textField", textField); fileComment = cgiUsualString("comment", fileComment); picky = cgiBoolean("picky"); makeMain = cgiBoolean("main"); positiveOnly = cgiBoolean("positive"); if (argc != 3) usage(); autoXml(argv[1], argv[2]); return 0; }