Merge branch 'master' into browser
[projects/chimara/chimara.git] / babel / ifiction.c
diff --git a/babel/ifiction.c b/babel/ifiction.c
new file mode 100644 (file)
index 0000000..cb620e7
--- /dev/null
@@ -0,0 +1,534 @@
+/* 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,&gt,ifiction_null_eh,NULL);\r
+ if (gt.target){ if (gt.output) free(gt.output); return NULL; }\r
+ return gt.output;\r
+}\r