+++ /dev/null
-/* ifiction.c common babel interface for processing ifiction metadata\r
- * (c) 2006 By L. Ross Raszewski\r
- *\r
- * This code is freely usable for all purposes.\r
- *\r
- * This work is licensed under the Creative Commons Attribution2.5 License.\r
- * To view a copy of this license, visit\r
- * http://creativecommons.org/licenses/by/2.5/ or send a letter to\r
- * Creative Commons,\r
- * 543 Howard Street, 5th Floor,\r
- * San Francisco, California, 94105, USA.\r
- *\r
- * This file depends on treaty.h\r
- *\r
- * This file contains common routines for handling ifiction metadata strings\r
- *\r
- * int32 ifiction_get_IFID(char *metadata, char *output, int32 output_extent)\r
- * does what the babel treaty function GET_STORY_FILE_IFID_SEL would do for ifiction\r
- *\r
- * void ifiction_parse(char *md, IFCloseTag close_tag, void *close_ctx,\r
- * IFErrorHandler error_handler, void *error_ctx)\r
- * parses the given iFiction metadata. close_tag(struct XMLtag xtg, close_ctx)\r
- * is called for each tag as it is closed, error_handler(char *error, error_ctx)\r
- * is called each time a structural or logical error is found in the iFiction\r
- * This is a very simple XML parser, and probably not as good as any "real"\r
- * XML parser. Its only two benefits are that (1) it's really small, and (2)\r
- * it strictly checks the ifiction record against the Treaty of Babel\r
- * requirements\r
- *\r
- */\r
-\r
-#include "ifiction.h"\r
-#include <string.h>\r
-#include <stdio.h>\r
-#include <stdlib.h>\r
-#include <ctype.h>\r
-\r
-void *my_malloc(int, char *);\r
-extern char *format_registry[];\r
-\r
-\r
-static int32 llp;\r
-static char *lnlst;\r
-\r
-static char utfeol[3] = { 0xe2, 0x80, 0xa8 };\r
-static int32 getln(char *endp)\r
-{\r
- for(;lnlst<endp;lnlst++) if (*lnlst=='\n' || memcmp(lnlst,utfeol,3)==0) llp++;\r
- return llp;\r
-}\r
-\r
-\r
-static int32 ifiction_get_first_IFID(char *metadata, char *output, int32 output_extent)\r
-{\r
- char *ifid_begin, *ifid_end;\r
- \r
- ifid_begin=strstr(metadata,"<ifid>");\r
- if (!ifid_begin) return NO_REPLY_RV;\r
- ifid_begin+=6;\r
-\r
- ifid_end=strstr(ifid_begin,"</ifid>");\r
- if (!ifid_end) return NO_REPLY_RV;\r
- if (output_extent<=(ifid_end-ifid_begin)) return INVALID_USAGE_RV;\r
-\r
- memcpy(output,ifid_begin,ifid_end-ifid_begin);\r
-\r
- output[ifid_end-ifid_begin]=0;\r
-\r
- return ifid_end-metadata+7;\r
-}\r
-\r
-\r
-int32 ifiction_get_IFID(char *metadata, char *output, int32 output_extent)\r
-{\r
- int32 j=0, k;\r
-\r
- while(*metadata)\r
- {\r
- if ((k=ifiction_get_first_IFID(metadata,output,output_extent)) <= 0) break;\r
- j++;\r
- metadata+=k;\r
- output_extent-=strlen(output)+1;\r
- output+=strlen(output);\r
- *output=',';\r
- output++;\r
- }\r
- if (*(output-1)==',') *(output-1)=0;\r
- return j;\r
-}\r
-\r
-\r
-static char *leaf_tags[] = { "ifid",\r
- "format",\r
- "bafn",\r
- "title",\r
- "author",\r
- "headline",\r
- "firstpublished",\r
- "genre",\r
- "group",\r
- "description",\r
- "leafname",\r
- "url",\r
- "authoremail",\r
- "height",\r
- "width",\r
-\r
- NULL\r
- };\r
-static char *one_per[] = { "identification",\r
- "bibliographic",\r
- "format",\r
- "title",\r
- "author",\r
- "headline",\r
- "firstpublished",\r
- "genre",\r
- "group",\r
- "description",\r
- "leafname",\r
- "height",\r
- "width",\r
- "forgiveness",\r
- "colophon",\r
- NULL\r
- };\r
-\r
-static char *required[] = {\r
- "cover", "height",\r
- "cover", "width",\r
- "cover", "format",\r
- "resources", "auxiliary",\r
- "auxiliary", "leafname",\r
- "auxiliary", "description",\r
- "ifiction", "story",\r
- "story", "identification",\r
- "story", "bibliographic",\r
- "identification", "ifid",\r
- "identification", "format",\r
- "bibliographic", "title",\r
- "bibliographic", "author",\r
- "colophon", "generator",\r
- "colophon", "originated",\r
- NULL, NULL\r
- };\r
-static char *zarfian[] = {\r
- "Merciful",\r
- "Polite",\r
- "Tough",\r
- "Nasty",\r
- "Cruel",\r
- NULL\r
- };\r
-\r
-struct ifiction_info {\r
- int32 width;\r
- int32 height;\r
- int format;\r
- };\r
-static void ifiction_validate_tag(struct XMLTag *xtg, struct ifiction_info *xti, IFErrorHandler err_h, void *ectx)\r
-{\r
- int i;\r
- char ebuf[512];\r
- struct XMLTag *parent=xtg->next;\r
- if (parent)\r
- {\r
- for(i=0;leaf_tags[i];i++)\r
- if (strcmp(parent->tag,leaf_tags[i])==0)\r
- {\r
- sprintf(ebuf, "Error: (line %d) Tag <%s> is not permitted within tag <%s>",\r
- xtg->beginl,xtg->tag,parent->tag);\r
- err_h(ebuf,ectx);\r
- }\r
- for(i=0;required[i];i+=2)\r
- if (strcmp(required[i],parent->tag)==0 && strcmp(required[i+1],xtg->tag)==0)\r
- parent->rocurrences[i]=1;\r
- for(i=0;one_per[i];i++)\r
- if (strcmp(one_per[i],xtg->tag)==0)\r
- if (parent->occurences[i]) { \r
- sprintf(ebuf,"Error: (line %d) Found more than one <%s> within <%s>",xtg->beginl,xtg->tag,\r
- parent->tag);\r
- err_h(ebuf,ectx);\r
- }\r
- else parent->occurences[i]=1;\r
- }\r
- for(i=0;required[i];i+=2)\r
- if (strcmp(required[i],xtg->tag)==0 && !xtg->rocurrences[i])\r
- {\r
- sprintf(ebuf,"Error: (line %d) Tag <%s> is required within <%s>",xtg->beginl, required[i+1],xtg->tag);\r
- err_h(ebuf,ectx);\r
- }\r
- if (parent && strcmp(parent->tag,"identification")==0)\r
- {\r
- if (strcmp(xtg->tag,"format")==0)\r
- {\r
- int i;\r
- for(i=0;format_registry[i];i++) if (memcmp(xtg->begin,format_registry[i],strlen(format_registry[i]))==0) break;\r
- if (format_registry[i]) xti->format=i;\r
- else\r
- {\r
- char bf[256];\r
- memcpy(bf,xtg->begin,xtg->end-xtg->begin);\r
- bf[xtg->end-xtg->begin]=0;\r
- xti->format=-1;\r
- sprintf(ebuf,"Warning: (line %d) Unknown format %s.",xtg->beginl,bf);\r
- err_h(ebuf,ectx);\r
- }\r
- }\r
- }\r
- if (parent && strcmp(parent->tag,"cover")==0)\r
- {\r
- if (strcmp(xtg->tag,"width")==0)\r
- {\r
- int i;\r
- sscanf(xtg->begin,"%d",&i);\r
- if (i<120)\r
- {\r
- sprintf(ebuf,"Warning: (line %d) Cover art width should not be less than 120.",xtg->beginl);\r
- err_h(ebuf,ectx);\r
- }\r
- if (i>1200)\r
- {\r
- sprintf(ebuf,"Warning: (line %d) Cover art width should not exceed 1200.",xtg->beginl);\r
- err_h(ebuf,ectx);\r
- }\r
- if (!xti->width) xti->width=i;\r
- if (xti->height && (xti->width> 2 * xti->height || xti->height > 2 * xti->width))\r
- {\r
- sprintf(ebuf,"Warning: (line %d) Cover art aspect ratio exceeds 2:1.",xtg->beginl);\r
- err_h(ebuf,ectx);\r
- }\r
-\r
- }\r
- if (strcmp(xtg->tag,"height")==0)\r
- {\r
- int i;\r
- sscanf(xtg->begin,"%d",&i);\r
- if (i<120)\r
- {\r
- sprintf(ebuf,"Warning: (line %d) Cover art height should not be less than 120.",xtg->beginl);\r
- err_h(ebuf,ectx);\r
- }\r
- if (i>1200)\r
- {\r
- sprintf(ebuf,"Warning: (line %d) Cover art height should not exceed 1200.",xtg->beginl);\r
- err_h(ebuf,ectx);\r
- }\r
- if (!xti->height) xti->height=i;\r
- if (xti->width && (xti->width> 2 * xti->height || xti->height > 2 * xti->width))\r
- {\r
- sprintf(ebuf,"Warning: (line %d) Cover art aspect ratio exceeds 2:1.",xtg->beginl);\r
- err_h(ebuf,ectx);\r
- }\r
-\r
- }\r
- if (strcmp(xtg->tag,"format")==0 && memcmp(xtg->begin,"jpg",3) && memcmp(xtg->begin,"png",3))\r
- {\r
- sprintf(ebuf,"Warning: (line %d) <format> should be one of: png, jpg.",xtg->beginl);\r
- err_h(ebuf,ectx);\r
- }\r
- }\r
- if (parent && strcmp(parent->tag,"bibliographic")==0)\r
- {\r
- char *p;\r
- if (isspace(*xtg->begin)|| isspace(*(xtg->end-1)))\r
- {\r
- sprintf(ebuf,"Warning: (line %d) Extraneous spaces at beginning or end of tag <%s>.",xtg->beginl,xtg->tag);\r
- err_h(ebuf,ectx);\r
- }\r
- for(p=xtg->begin;p<xtg->end-1;p++)\r
-/* Obsoleted by Revision 6\r
- if (isspace(*p) && isspace(*(p+1)))\r
- {\r
- sprintf(ebuf,"Warning: (line %d) Extraneous spaces found in tag <%s>.",xtg->beginl, xtg->tag);\r
- err_h(ebuf,ectx);\r
- }\r
- else if (isspace(*p) && *p!=' ')\r
- {\r
- sprintf(ebuf,"Warning: (line %d) Improper whitespace character found in tag <%s>.",xtg->beginl, xtg->tag);\r
- err_h(ebuf,ectx);\r
-\r
- }\r
-*/\r
- if (strcmp(xtg->tag, "description") && xtg->end-xtg->begin > 240)\r
- { \r
- sprintf(ebuf,"Warning: (line %d) Tag <%s> length exceeds treaty guidelines",xtg->beginl, xtg->tag);\r
- err_h(ebuf,ectx);\r
- }\r
- if (strcmp(xtg->tag, "description")==0 && xtg->end-xtg->begin > 2400)\r
- {\r
- sprintf(ebuf,"Warning: (line %d) Tag <%s> length exceeds treaty guidelines",xtg->beginl, xtg->tag);\r
- err_h(ebuf,ectx);\r
- }\r
- if (strcmp(xtg->tag,"firstpublished")==0)\r
- {\r
- int l=xtg->end-xtg->begin;\r
- if ((l!=4 && l!=10) ||\r
- (!isdigit(xtg->begin[0]) ||\r
- !isdigit(xtg->begin[1]) ||\r
- !isdigit(xtg->begin[2]) ||\r
- !isdigit(xtg->begin[3])) ||\r
- (l==10 && ( xtg->begin[4]!='-' ||\r
- xtg->begin[7]!='-' ||\r
- !isdigit(xtg->begin[5]) ||\r
- !isdigit(xtg->begin[6]) ||\r
- !(xtg->begin[5]=='0' || xtg->begin[5]=='1') ||\r
- !(xtg->begin[5]=='0' || xtg->begin[6]<='2') ||\r
- !isdigit(xtg->begin[8]) ||\r
- !isdigit(xtg->begin[9]))))\r
- {\r
- sprintf(ebuf,"Warning: (line %d) Tag <%s> should be format YYYY or YYYY-MM-DD",xtg->beginl, xtg->tag);\r
- err_h(ebuf,ectx);\r
- }\r
- }\r
- if (strcmp(xtg->tag,"seriesnumber")==0)\r
- {\r
- char *l;\r
- if (*xtg->begin=='0' && xtg->end!=xtg->begin+1)\r
- {\r
- sprintf(ebuf,"Warning: (line %d) Tag <%s> should not use leading zeroes",xtg->beginl, xtg->tag);\r
- err_h(ebuf,ectx);\r
- }\r
-\r
- for(l=xtg->begin;l<xtg->end;l++) if (!isdigit(*l))\r
- {\r
- sprintf(ebuf,"Warning: (line %d) Tag <%s> should be a positive number",xtg->beginl, xtg->tag);\r
- err_h(ebuf,ectx);\r
- }\r
- }\r
- if (strcmp(xtg->tag,"forgiveness")==0)\r
- {\r
- int l;\r
- for(l=0;zarfian[l];l++) if (memcmp(xtg->begin,zarfian[l],strlen(zarfian[l]))==0) break;\r
- if (!zarfian[l])\r
- {\r
- sprintf(ebuf,"Warning: (line %d) <forgiveness> should be one of: Merciful, Polite, Tough, Cruel",xtg->beginl);\r
- err_h(ebuf,ectx);\r
- }\r
- }\r
- }\r
- if (xti->format>0)\r
- { \r
- for(i=0;format_registry[i];i++) if (strcmp(xtg->tag,format_registry[i])==0) break;\r
- if (format_registry[i] && xti->format !=i)\r
- {\r
- sprintf(ebuf,"Warning: (line %d) Found <%s> tag, but story is identified as %s.",xtg->beginl, xtg->tag, format_registry[xti->format]);\r
- err_h(ebuf,ectx);\r
- }\r
- }\r
- if (strcmp(xtg->tag,"story")==0)\r
- {\r
- xti->format=-1;\r
- xti->width=0;\r
- xti->height=0;\r
- }\r
-\r
-}\r
-\r
-\r
-\r
-void ifiction_parse(char *md, IFCloseTag close_tag, void *close_ctx, IFErrorHandler error_handler, void *error_ctx)\r
-{\r
-char *xml, buffer[2400], *aep, *mda=md, ebuffer[512];\r
-struct XMLTag *parse=NULL, *xtg;\r
-struct ifiction_info xti;\r
-char BOM[3]={ 0xEF, 0xBB, 0xBF};\r
-xti.width=0;\r
-xti.height=0;\r
-xti.format=-1;\r
-llp=1;\r
-lnlst=md;\r
-\r
-while(*mda && isspace(*mda)) mda++;\r
-if (memcmp(mda,BOM,3)==0)\r
-{ mda+=3;\r
- while(*mda && isspace(*mda)) mda++;\r
-}\r
-\r
-\r
-if (strncmp("<?xml version=\"1.0\" encoding=\"UTF-8\"?>",mda,\r
- strlen("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"))\r
- &&\r
- strncmp("<?xml version=\"1.0\" encoding=\"utf-8\"?>",mda,\r
- strlen("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"))\r
- )\r
-{\r
- error_handler("Error: XML header not found.",error_ctx);\r
- return;\r
-}\r
-\r
-xml=strstr(md,"<ifindex");\r
-if (!xml) {\r
- error_handler("Error: <ifindex> not found",error_ctx);\r
- return;\r
- }\r
-while(xml && *xml)\r
-{\r
- char *bp, *ep, *tp;\r
- while(*xml&&*xml!='<') xml++;\r
- if (!*xml) break;\r
- bp=xml;\r
- tp=strchr(bp+1,'<');\r
- ep=strchr(bp+1,'>');\r
- if (!ep) break;\r
- if (tp && tp < ep)\r
- { xml=tp; continue; }\r
- if (!tp) tp=ep+1; \r
- if (bp[1]=='/') /* end tag */\r
- {\r
- strncpy(buffer,bp+2,(ep-bp)-2);\r
- buffer[(ep-bp)-2]=0;\r
- if (parse && strcmp(buffer,parse->tag)==0)\r
- { /* copasetic. Close the tag */\r
- xtg=parse;\r
- parse=xtg->next;\r
- xtg->end=ep-strlen(buffer)-2;\r
- ifiction_validate_tag(xtg,&xti,error_handler, error_ctx);\r
- close_tag(xtg,close_ctx);\r
- free(xtg);\r
- }\r
- else\r
- {\r
- for(xtg=parse;xtg && strcmp(buffer,xtg->tag);xtg=xtg->next);\r
- if (xtg) /* Intervening unclosed tags */\r
- { for(xtg=parse;xtg && strcmp(buffer,parse->tag);xtg=parse)\r
- {\r
- xtg->end=xml-1;\r
- parse=xtg->next;\r
- sprintf(ebuffer,"Error: (line %d) unclosed <%s> tag",xtg->beginl,xtg->tag);\r
- error_handler(ebuffer,error_ctx);\r
- ifiction_validate_tag(xtg,&xti,error_handler, error_ctx);\r
- close_tag(xtg,close_ctx);\r
- free(xtg);\r
- }\r
- xtg=parse;\r
- if (xtg)\r
- {\r
- xtg->end=xml-1;\r
- parse=xtg->next;\r
- ifiction_validate_tag(xtg,&xti, error_handler, error_ctx);\r
- close_tag(xtg,close_ctx);\r
- free(xtg);\r
- }\r
- }\r
- else\r
- { \r
- sprintf(ebuffer,"Error: (line %d) saw </%s> without <%s>",getln(xml), buffer,buffer);\r
- error_handler(ebuffer,error_ctx);\r
- }\r
- }\r
-\r
- }\r
- else if(*(ep-1)=='/' || bp[1]=='!') /* unterminated tag */\r
- {\r
- /* Do nothing */\r
- }\r
- else /* Terminated tag beginning */\r
- {\r
- int i;\r
- xtg=(struct XMLTag *)my_malloc(sizeof(struct XMLTag),"XML Tag");\r
- xtg->next=parse;\r
- xtg->beginl=getln(bp);\r
- for(i=0;bp[i+1]=='_' || bp[i+1]=='-' || isalnum(bp[i+1]);i++)\r
- xtg->tag[i]=bp[i+1];\r
- if (i==0)\r
- { xml=tp;\r
- free(xtg);\r
- continue;\r
- }\r
- parse=xtg;\r
- parse->tag[i]=0;\r
- strncpy(parse->fulltag,bp+1,ep-bp-1);\r
- parse->fulltag[ep-bp-1]=0;\r
- parse->begin=ep+1;\r
- }\r
- xml=tp;\r
-}\r
- while (parse)\r
- {\r
- xtg=parse;\r
- xtg->end=aep-1;\r
- parse=xtg->next;\r
- sprintf(ebuffer,"Error: (line %d) Unclosed tag <%s>",xtg->beginl,xtg->tag);\r
- ifiction_validate_tag(xtg,&xti,error_handler, error_ctx);\r
- close_tag(xtg,close_ctx);\r
- free(xtg);\r
- }\r
-}\r
-\r
-struct get_tag\r
-{\r
- char *tag;\r
- char *parent;\r
- char *output;\r
- char *target;\r
-};\r
-\r
-static void ifiction_null_eh(char *e, void *c)\r
-{\r
- if (e || c) { }\r
-\r
-}\r
-\r
-static void ifiction_find_value(struct XMLTag *xtg, void *xti)\r
-{\r
- struct get_tag *gt=(struct get_tag *)xti;\r
-\r
- if (gt->output && !gt->target) return;\r
- if (gt->target && gt->output && strcmp(gt->output,gt->target)==0) { gt->target=NULL; free(gt->output); gt->output=NULL; }\r
- if (((!xtg->next && !gt->parent) || (xtg->next && gt->parent && strcmp(xtg->next->tag,gt->parent)==0)) &&\r
- strcmp(xtg->tag,gt->tag)==0)\r
- {\r
- int32 l = xtg->end-xtg->begin;\r
-\r
- if (gt->output) free(gt->output);\r
- gt->output=(char *)my_malloc(l+1, "ifiction tag buffer");\r
- memcpy(gt->output, xtg->begin, l);\r
- gt->output[l]=0;\r
-\r
- }\r
-}\r
-\r
-\r
-char *ifiction_get_tag(char *md, char *p, char *t, char *from)\r
-{\r
- struct get_tag gt;\r
- gt.output=NULL;\r
- gt.parent=p;\r
- gt.tag=t;\r
- gt.target=from;\r
- ifiction_parse(md,ifiction_find_value,>,ifiction_null_eh,NULL);\r
- if (gt.target){ if (gt.output) free(gt.output); return NULL; }\r
- return gt.output;\r
-}\r