/* * ifmetaxml.c * ZoomCocoa * * Created by Andrew Hunter on 04/04/2006. * Copyright 2006 Andrew Hunter. All rights reserved. * */ #include #include #include #include #include #include "ifmetaxml.h" /* == Reading ifiction records == */ #ifndef XMLCALL /* Not always defined? */ # define XMLCALL #endif /* The XML parser function declarations */ static XMLCALL void StartElement(void *userData, const XML_Char *name, const XML_Char **atts); static XMLCALL void EndElement (void *userData, const XML_Char *name); static XMLCALL void CharData (void *userData, const XML_Char *s, int len); /* The parser state structure */ typedef struct IFXmlTag { IFChar* value; XML_Char* name; int failed; char* path; struct IFXmlTag* parent; } IFXmlTag; typedef struct IFXmlState { XML_Parser parser; IFMetabase meta; int failed; /* Set to 1 if the parsing suffers a fatal error */ int version; /* 090 or 100, or 0 if no tag has been encountered */ IFXmlTag* tag; /* The current topmost tag */ IFID storyId; /* The identification chunks to attach to the current story, once we've finished building it */ IFStory story; /* The story that we're building */ IFMetabase tempMetabase; /* A temporary metabase that we put half-built stories into */ IFID tempId; /* The ID of a temporary story */ int release; /* For 0.9 zcode IDs: the release # */ char serial[6]; /* For 0.9 zcode IDs: the serial # */ int checksum; /* For 0.9 zcode IDs: the checksum */ } IFXmlState; /* Load the records contained in the specified */ void IF_ReadIfiction(IFMetabase meta, const unsigned char* xml, size_t size) { XML_Parser theParser; IFXmlState* currentState; /* Construct the parser state structure */ currentState = malloc(sizeof(IFXmlState)); currentState->meta = meta; currentState->failed = 0; currentState->version = 0; currentState->story = NULL; currentState->storyId = NULL; currentState->story = NULL; currentState->tag = NULL; currentState->tempMetabase = IFMB_Create(); currentState->tempId = IFMB_GlulxIdNotInform(0, 0); /* Begin parsing */ theParser = XML_ParserCreate(NULL); currentState->parser = theParser; XML_SetElementHandler(theParser, StartElement, EndElement); XML_SetCharacterDataHandler(theParser, CharData); XML_SetUserData(theParser, currentState); /* Ready? Go! */ XML_Parse(theParser, (const char*)xml, size, 1); /* Clear up any temp stuff we may have created */ if (currentState->storyId) IFMB_FreeId(currentState->storyId); IFMB_FreeId(currentState->tempId); IFMB_Free(currentState->tempMetabase); free(currentState); XML_ParserFree(theParser); } /* Some string utility functions */ static unsigned char bytesFromUTF8[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5}; /* Compare an XML string against a C string */ static int XCstrcmp(const XML_Char* a, const char* b) { int x; for (x=0; a[x] != 0 && b[x] != 0; x++) { if (a[x] < (unsigned char)b[x]) return -1; if (a[x] > (unsigned char)b[x]) return 1; } if (a[x] < (unsigned char)b[x]) return -1; if (a[x] > (unsigned char)b[x]) return 1; return 0; } /* Converts s to IFChars. Result needs to be freed */ static int Xstrlen(const XML_Char* a) { int x; if (a == NULL) return 0; for (x=0; a[x] != 0; x++); return x; } /* Converts 's' to simple ASCII. The return value must be freed */ static char* Xascii(const IFChar* s) { char* res; int x; int len = IFMB_StrLen(s); res = malloc(sizeof(char)*(len+1)); for (x=0; x= 32 && s[x] < 127) res[x] = s[x]; else res[x] = '?'; } res[x] = 0; return res; } static IFChar* Xmdchar(const XML_Char* s, int len) { int x, pos; IFChar* res; if (len < 0) len = Xstrlen(s); res = malloc(sizeof(IFChar)*(len+1)); pos = 0; for (x=0; x= len) break; /* Read+check the characters that make up this char */ errorFlag = 0; for (y=0; y<=bytes; y++) { chrs[y] = (unsigned char)s[x+y]; if (chrs[y] < 127) errorFlag = 1; } if (errorFlag) continue; /* Ignore this character (error) */ /* Get the UCS-4 character */ switch (bytes) { case 1: chr = ((chrs[0]&~0xc0)<<6)|(chrs[1]&~0x80); break; case 2: chr = ((chrs[0]&~0xe0)<<12)|((chrs[1]&~0x80)<<6)|(chrs[2]&~0x80); break; case 3: chr = ((chrs[0]&~0xf0)<<18)|((chrs[1]&~0x80)<<12)|((chrs[2]&~0x80)<<6)|(chrs[3]&~0x80); break; case 4: chr = ((chrs[0]&~0xf8)<<24)|((chrs[1]&~0x80)<<18)|((chrs[2]&~0x80)<<12)|((chrs[3]&~0x80)<<6)|(chrs[4]&~0x80); break; case 5: chr = ((chrs[0]&~0xfc)<<28)|((chrs[1]&~0x80)<<24)|((chrs[2]&~0x80)<<18)|((chrs[3]&~0x80)<<12)|((chrs[4]&~0x80)<<6)|(chrs[5]&~0x80); break; } x += bytes; res[pos++] = chr; } } res[pos++] = 0; return res; } /* Error handling/reporting */ char* IF_StringForError(IFXmlError errorCode) { switch (errorCode) { case IFXmlNotIfiction: return "IFXmlNotIfiction"; case IFXmlNoVersionSupplied: return "IFXmlNoVersionSupplied"; case IFXmlVersionIsTooRecent: return "IFXmlVersionIsTooRecent"; case IFXmlMismatchedTags: return "IFXmlMismatchedTags"; case IFXmlUnrecognisedTag: return "IFXmlUnrecognisedTag"; case IFXmlBadId: return "IFXmlBadId"; case IFXmlBadZcodeSection: return "IFXmlBadZcodeSection"; case IFXmlStoryWithNoId: return "IFXmlStoryWithNoId"; } return "IFXMLUnknownError"; } static void Error(IFXmlState* state, IFXmlError errorType, void* errorData) { int line, column; line = XML_GetCurrentLineNumber(state->parser); column = XML_GetCurrentColumnNumber(state->parser); printf("**** Ifiction ERROR: %s <%i> (line=%i, column=%i)\n", IF_StringForError(errorType), errorType, line, column); } /* The XML parser itself */ static XMLCALL void StartElement(void *userData, const XML_Char *name, const XML_Char **atts) { IFXmlState* state; int x; IFXmlTag* tag; XML_Char* parent; /* Get the state */ state = (IFXmlState*)userData; if (state->tag) { parent = state->tag->name; } else { parent = ""; } /* Put this tag onto the tag stack */ tag = malloc(sizeof(IFXmlTag)); tag->value = malloc(sizeof(IFChar)); tag->value[0] = 0; tag->name = malloc(sizeof(char)*(strlen(name)+1)); tag->path = NULL; strcpy(tag->name, name); tag->parent = state->tag; if (tag->parent) { tag->failed = tag->parent->failed; } else { tag->failed = 0; } state->tag = tag; /* Do nothing else if the parse has failed fatally for some reason */ if (state->failed) return; /* The action we take depends on the current state and the next token */ if (state->version == 0) { if (XCstrcmp(name, "ifindex") != 0) { Error(state, IFXmlNotIfiction, NULL); state->failed = 1; return; } /* How the file is parsed depends on the version number that's supplied */ for (x=0; atts[x] != NULL; x+=2) { if (XCstrcmp(atts[x], "version") == 0) { double version; version = strtod(atts[x+1], NULL); state->version = version * 100; } } if (state->version == 0) { Error(state, IFXmlNoVersionSupplied, NULL); state->failed = 1; } else if (state->version > 100) { Error(state, IFXmlVersionIsTooRecent, NULL); state->failed = 1; } } else if (XCstrcmp(name, "br") == 0) { /* 'br' tags are allowed anywhere and just add a newline to the tag value */ if (state->tag->parent) { int valueLen = IFMB_StrLen(state->tag->parent->value); state->tag->parent->value = realloc(state->tag->parent->value, sizeof(IFChar)*(valueLen+2)); state->tag->parent->value[valueLen] = '\n'; state->tag->parent->value[valueLen+1] = 0; } } else if (state->version == 90) { /* == Version 0.9 identification and data format == */ if (state->tag->failed) { /* Just ignore 'failed' tags */ } else if (XCstrcmp(parent, "ifindex") == 0) { /* Tags under the 'ifindex' root tag */ if (XCstrcmp(name, "story") == 0) { /* Start of a story */ if (state->storyId != NULL) { IFMB_FreeId(state->storyId); state->storyId = NULL; } IFMB_RemoveStoryWithId(state->tempMetabase, state->tempId); state->story = IFMB_GetStoryWithId(state->tempMetabase, state->tempId); } else { /* Unknown tag */ Error(state, IFXmlUnrecognisedTag, NULL); state->tag->failed = 1; } } else if (XCstrcmp(parent, "story") == 0) { /* Tags under the 'story' tag */ if (XCstrcmp(name, "identification") == 0 || XCstrcmp(name, "id") == 0) { /* Start of an identification chunk */ } else if (XCstrcmp(name, "title") == 0) { } else if (XCstrcmp(name, "headline") == 0) { } else if (XCstrcmp(name, "author") == 0) { } else if (XCstrcmp(name, "genre") == 0) { } else if (XCstrcmp(name, "year") == 0) { } else if (XCstrcmp(name, "group") == 0) { } else if (XCstrcmp(name, "zarfian") == 0) { } else if (XCstrcmp(name, "teaser") == 0) { } else if (XCstrcmp(name, "comment") == 0) { } else if (XCstrcmp(name, "rating") == 0) { } else if (XCstrcmp(name, "description") == 0) { } else if (XCstrcmp(name, "coverpicture") == 0) { } else if (XCstrcmp(name, "auxiliary") == 0) { } else { /* Unknown tag */ Error(state, IFXmlUnrecognisedTag, NULL); state->tag->failed = 1; } } else if (XCstrcmp(parent, "identification") == 0 || XCstrcmp(parent, "id") == 0) { if (XCstrcmp(name, "zcode") == 0) { int x; for (x=0; x<6; x++) state->serial[x] = '-'; state->release = -1; state->checksum = -1; } else if (XCstrcmp(name, "format") == 0) { } else if (XCstrcmp(name, "uuid") == 0) { } else { Error(state, IFXmlUnrecognisedTag, NULL); state->tag->failed = 1; } } else if (XCstrcmp(parent, "zcode") == 0) { if (XCstrcmp(name, "release") == 0) { } else if (XCstrcmp(name, "serial") == 0) { } else if (XCstrcmp(name, "checksum") == 0) { } else { Error(state, IFXmlUnrecognisedTag, NULL); } } else { Error(state, IFXmlUnrecognisedTag, NULL); } } else { /* == Version 1.0 identification and data format == */ if (XCstrcmp(parent, "ifindex") == 0 && XCstrcmp(name, "story") == 0) { /* Start of a story tag */ if (state->storyId != NULL) { IFMB_FreeId(state->storyId); state->storyId = NULL; } IFMB_RemoveStoryWithId(state->tempMetabase, state->tempId); state->story = IFMB_GetStoryWithId(state->tempMetabase, state->tempId); } else if (XCstrcmp(name, "identification") == 0 || XCstrcmp(name, "id") == 0) { /* Version 1.0 identification section (handled when closing the tag) */ } else if (XCstrcmp(parent, "identification") == 0 || XCstrcmp(parent, "id") == 0) { /* Tag within a version 1.0 identification section (handled when closing the tag) */ } else if (state->story != NULL) { char* lastPath; char* fullPath; /* Tag within a story */ /* Concatenate the path with the path of the previous tag */ if (state->tag->parent->path == NULL) { lastPath = ""; } else { lastPath = state->tag->parent->path; } fullPath = malloc(sizeof(XML_Char)*(strlen(lastPath) + 2 + strlen(name))); if (lastPath[0] != 0) { strcpy(fullPath, lastPath); strcat(fullPath, "."); strcat(fullPath, name); } else { strcpy(fullPath, name); } state->tag->path = fullPath; /* Add a key for this path */ IFMB_AddValue(state->story, fullPath); /* Add any attributes that were set */ for (x=0; atts[x] != NULL; x += 2) { char* attributePath; IFChar* attributeValue; /* Get the path for this attribute */ attributePath = malloc(strlen(fullPath)+strlen(atts[x])+2); strcpy(attributePath, fullPath); strcat(attributePath, "@"); strcat(attributePath, atts[x]); attributeValue = Xmdchar(atts[x+1], strlen(atts[x+1])); /* Set the value */ IFMB_SetValue(state->story, attributePath, attributeValue); /* Tidy up */ free(attributePath); } } } } static XMLCALL void EndElement(void *userData, const XML_Char *name) { IFXmlState* state; IFXmlTag* tag; int pos, whitePos; XML_Char* parent; IFChar* value; /* Get the state */ state = (IFXmlState*)userData; if (state->failed) return; if (state->tag == NULL) return; if (XCstrcmp(state->tag->name, name) != 0) { /* Mismatched tags */ Error(state, IFXmlMismatchedTags, NULL); state->tag->failed = 1; } if (state->tag->parent) { parent = state->tag->parent->name; } else { parent = ""; } /* Trim out whitespace for the current tag */ pos = whitePos = 0; while (state->tag->value[whitePos] == ' ') whitePos++; while (state->tag->value[whitePos] != 0) { state->tag->value[pos++] = state->tag->value[whitePos]; if (state->tag->value[whitePos] == ' ' || state->tag->value[whitePos] == '\n') { whitePos++; while (state->tag->value[whitePos] == ' ') whitePos++; } else { whitePos++; } } if (pos > 0 && state->tag->value[pos-1] == ' ') pos--; state->tag->value[pos] = 0; value = state->tag->value; /* Perform an action on this tag */ if (!state->tag->failed) { if (XCstrcmp(name, "br") == 0) { /* br tags are supported anywhere */ } else if (state->version == 90) { /* == Handle version 0.90 tags == */ if (XCstrcmp(parent, "identification") == 0 || XCstrcmp(parent, "id") == 0) { /* An identification section */ if (XCstrcmp(name, "zcode") == 0) { /* End of a ZCode ID */ if (state->release < 0) { Error(state, IFXmlBadZcodeSection, NULL); } else { IFID newId; newId = IFMB_ZcodeId(state->release, state->serial, state->checksum); if (state->storyId == NULL) { state->storyId = newId; } else { IFID ids[2]; IFID compoundId; ids[0] = newId; ids[1] = state->storyId; compoundId = IFMB_CompoundId(2, ids); IFMB_FreeId(newId); IFMB_FreeId(state->storyId); state->storyId = compoundId; } } } } else if (XCstrcmp(parent, "zcode") == 0) { /* zcode identification section */ if (XCstrcmp(name, "serial") == 0) { int x; for (x=0; x<6 && value[x] != 0; x++) { state->serial[x] = value[x]; } } else if (XCstrcmp(name, "release") == 0) { char* release = Xascii(value); state->release = atoi(release); free(release); } else if (XCstrcmp(name, "checksum") == 0) { char* checksum = Xascii(value); int x, val; val = 0; for (x=0; x<4 && checksum[x] != 0; x++) { int hex = 0; val <<= 4; if (checksum[x] >= '0' && checksum[x] <= '9') hex = checksum[x]-'0'; else if (checksum[x] >= 'A' && checksum[x] <= 'F') hex = checksum[x]-'A'+10; else if (checksum[x] >= 'a' && checksum[x] <= 'f') hex = checksum[x]-'a'+10; else break; val |= hex; } state->checksum = val; free(checksum); } } else if (XCstrcmp(parent, "uuid") == 0) { /* UUID identification section */ if (XCstrcmp(name, "uuid") == 0) { unsigned char uuid[16]; int uuidPos; int x; IFID newId; for (x=0; x<16; x++) uuid[x] = 0; /* Work out the UUID data */ uuidPos = 0; for (x=0; value[x] != 0; x++) { int digitVal = -1; if (value[x] == '\n' || value[x] == ' ' || value[x] == '-') continue; if (value[x] >= '0' && value[x] <= '9') { digitVal = value[x] - '0'; } else if (value[x] >= 'a' && value[x] <= 'f') { digitVal = value[x] - 'a' + 10; } else if (value[x] >= 'A' && value[x] <= 'F') { digitVal = value[x] - 'A' + 10; } uuid[uuidPos>>1] |= digitVal << ((1-(uuidPos&1))*4); uuidPos++; if (uuidPos >= 32) break; } /* Merge with the currently generated IDs */ newId = IFMB_UUID(uuid); if (state->storyId == NULL) { state->storyId = newId; } else { IFID ids[2]; IFID compoundId; ids[0] = newId; ids[1] = state->storyId; compoundId = IFMB_CompoundId(2, ids); IFMB_FreeId(newId); IFMB_FreeId(state->storyId); state->storyId = compoundId; } } } else if (XCstrcmp(parent, "story") == 0) { /* Story data (or identification, which we ignore) */ char* key = NULL; if (XCstrcmp(name, "title") == 0) { key = "bibliographic.title"; } else if (XCstrcmp(name, "headline") == 0) { key = "bibliographic.headline"; } else if (XCstrcmp(name, "author") == 0) { key = "bibliographic.author"; } else if (XCstrcmp(name, "genre") == 0) { key = "bibliographic.genre"; } else if (XCstrcmp(name, "year") == 0) { key = "bibliographic.firstpublished"; } else if (XCstrcmp(name, "group") == 0) { key = "bibliographic.group"; } else if (XCstrcmp(name, "zarfian") == 0) { key = "bibliographic.forgiveness"; } else if (XCstrcmp(name, "teaser") == 0) { key = "zoom.teaser"; } else if (XCstrcmp(name, "comment") == 0) { key = "zoom.comment"; } else if (XCstrcmp(name, "rating") == 0) { key = "zoom.rating"; } else if (XCstrcmp(name, "description") == 0) { key = "bibliographic.description"; } else if (XCstrcmp(name, "coverpicture") == 0) { key = "zcode.coverpicture"; } else if (XCstrcmp(name, "auxiliary") == 0) { key = "resources.auxiliary"; } if (key != NULL && state->story != NULL) { IFMB_SetValue(state->story, key, value); } } else if (XCstrcmp(parent, "ifindex") == 0) { if (XCstrcmp(name, "story") == 0) { /* End of story: copy it into the main metabase */ if (state->storyId != NULL) { IFMB_CopyStory(state->meta, state->story, state->storyId); IFMB_FreeId(state->storyId); state->storyId = NULL; } else { Error(state, IFXmlStoryWithNoId, NULL); } } } } else { /* == Handle version 1.00 tags == */ if (XCstrcmp(parent, "identification") == 0 || XCstrcmp(parent, "id") == 0) { if (XCstrcmp(name, "ifid") == 0) { IFID newId; char* idValue; int x; /* Construct the IFID used here */ idValue = malloc(sizeof(char)*(IFMB_StrLen(value)+1)); for (x=0; value[x] != 0; x++) { idValue[x] = value[x]; } idValue[x] = 0; newId = IFMB_IdFromString(idValue); free(idValue); if (newId != NULL) { /* Merge with the story ID that we're building at the moment */ if (state->storyId == NULL) { state->storyId = newId; } else { IFID ids[2]; IFID compoundId; ids[0] = newId; ids[1] = state->storyId; compoundId = IFMB_CompoundId(2, ids); IFMB_FreeId(newId); IFMB_FreeId(state->storyId); state->storyId = compoundId; } } else { /* This ID is invalid */ Error(state, IFXmlBadId, NULL); } } } else if (state->tag->path != NULL) { /* Set the value for the current tag */ IFMB_SetValue(state->story, state->tag->path, value); } else if (XCstrcmp(name, "story") == 0) { /* Store the story we've just finished building */ if (state->storyId != NULL) { IFMB_CopyStory(state->meta, state->story, state->storyId); IFMB_FreeId(state->storyId); state->storyId = NULL; state->story = NULL; } else { Error(state, IFXmlStoryWithNoId, NULL); } } } } /* Pop this tag from the stack */ tag = state->tag; state->tag = tag->parent; if (tag->path != NULL) free(tag->path); free(tag->value); free(tag->name); free(tag); } static XMLCALL void CharData(void *userData, const XML_Char *s, int len) { IFXmlState* state; int valueLen, charDataLen, x; IFChar* charData; /* Get the state */ state = (IFXmlState*)userData; if (state->failed) return; if (state->tag == NULL) return; if (state->tag->failed) return; /* Append the character data for the current tag */ charData = Xmdchar(s, len); charDataLen = IFMB_StrLen(charData); valueLen = IFMB_StrLen(state->tag->value); state->tag->value = realloc(state->tag->value, sizeof(IFChar)*(valueLen+charDataLen+1)); for (x=0; xtag->value[valueLen+x] = ' '; } else { state->tag->value[valueLen+x] = charData[x]; } } state->tag->value[valueLen+charDataLen] = 0; /* Tidy up after ourselves */ free(charData); } /* == Writing ifiction records == */ static unsigned char* MakeUtf8Xml(IFChar* string, int allowNewlines) { unsigned char* res = NULL; int len = 0; int pos = 0; int x; #define add(c) if (len <= pos) { len += 256; res = realloc(res, sizeof(unsigned char)*len); } res[pos++] = c for (x=0; string[x] != 0; x++) { IFChar chr = string[x]; switch (chr) { case '<': add('&'); add('l'); add('t'); add(';'); break; case '>': add('&'); add('g'); add('t'); add(';'); break; case '&': add('&'); add('a'); add('m'); add('p'); add(';'); break; case '\'': add('&'); add('a'); add('p'); add('o'); add('s'); add(';'); break; case '\"': add('&'); add('q'); add('u'); add('o'); add('t'); add(';'); break; case '\n': if (allowNewlines) { add('<'); add('b'); add('r'); add('/'); add('>'); } break; default: if (chr < 0x20) { /* These are for the most part invalid */ /* Actually, according to the XML spec, they are fine, but expat complains and pain often results. This *will* prevent certain broken game files from indexing properly, and will generally result in duplicate entries in these cases. */ } else if (chr < 0x80) { add(chr); } else if (chr < 0x800) { add(0xc0 | (chr>>6)); add(0x80 | (chr&0x3f)); } else if (chr < 0x10000) { add(0xe0 | (chr>>12)); add(0x80 | ((chr>>6)&0x3f)); add(0x80 | (chr&0x3f)); } else if (chr < 0x200000) { add(0xf0 | (chr>>18)); add(0x80 | ((chr>>12)&0x3f)); add(0x80 | ((chr>>6)&0x3f)); add(0x80 | (chr&0x3f)); } else { /* These characters can't be represented by unicode anyway */ } } } add(0); return res; } /* Stack of iterators that are being processed */ typedef struct ValueStackItem { IFValueIterator iterator; struct ValueStackItem* previous; } ValueStackItem; void IF_WriteIfiction(IFMetabase meta, int(*writeFunction)(const char* bytes, int length, void* userData), void* userData) { #define w(s) { unsigned char* res = s; writeFunction(res, strlen(res), userData); } #define wu(s) { unsigned char* res; res = MakeUtf8Xml(s, 1); writeFunction(res, strlen(res), userData); free(res); } #define wun(s) { unsigned char* res; res = MakeUtf8Xml(s, 0); writeFunction(res, strlen(res), userData); free(res); } IFStoryIterator stories; ValueStackItem* values; IFStory story; int x; /* Write out the header */ w("\n"); w("\n"); /* Iterate through the stories */ stories = IFMB_GetStoryIterator(meta); while (story = IFMB_NextStory(stories)) { int idCount; IFID singleId[1]; IFID* storyIds; w(" \n"); /* Write out the IDs for the story */ /* TODO: , , etc */ /* Get the IDs that apply to this story */ storyIds = IFMB_SplitId(IFMB_IdForStory(story), &idCount); if (storyIds == NULL) { singleId[0] = IFMB_IdForStory(story); storyIds = singleId; idCount = 1; } /* Write them out in identification sections */ for (x=0; x"); w(idString); w("\n"); free(idString); } /* Iterate through the values for this story */ values = malloc(sizeof(ValueStackItem)); values->iterator = IFMB_GetValueIterator(story); values->previous = NULL; while (values != NULL) { if (IFMB_NextValue(values->iterator)) { IFValueIterator subValues; char* key; IFChar* value; key = IFMB_SubkeyFromIterator(values->iterator); value = IFMB_ValueFromIterator(values->iterator); subValues = IFMB_ChildrenFromIterator(values->iterator); /* Ignore attribute keys */ if (key[0] == '@') { if (subValues != NULL) IFMB_FreeValueIterator(subValues); continue; } /* Ignore empty keys */ if (value == NULL && subValues == NULL) continue; /* Open the tag for this value */ w(" <"); w(key); /* Write out any attributes that this tag might have */ while (subValues != NULL && IFMB_NextValue(subValues)) { char* subKey; subKey = IFMB_SubkeyFromIterator(subValues); if (subKey[0] == '@') { w(" "); w(subKey+1); w("=\""); wun(IFMB_ValueFromIterator(subValues)); w("\""); } } if (subValues != NULL) IFMB_FreeValueIterator(subValues); /* End of the opening tag */ w(">\n"); /* Write the value itself */ if (value != NULL) { w(" "); wu(IFMB_ValueFromIterator(values->iterator)); w("\n"); } /* Get an iterator for any values underneath this one */ subValues = IFMB_ChildrenFromIterator(values->iterator); if (subValues == NULL) { /* No child values */ w(" iterator)); w(">\n"); } else { /* Push this iterator onto the stack */ ValueStackItem* newValues; newValues = malloc(sizeof(ValueStackItem)); newValues->iterator = subValues; newValues->previous = values; values = newValues; } } else { ValueStackItem* previousValues; /* Finished this iterator, move back to the last one */ previousValues = values->previous; IFMB_FreeValueIterator(values->iterator); free(values); values = previousValues; /* Close the tag that we were writing */ if (values != NULL) { w(" iterator)); w(">\n"); } } } w(" \n"); } IFMB_FreeStoryIterator(stories); /* Write out the footer */ w("\n"); }