From 1e4407da677dd7a192c817de16583bb7199ceeb0 Mon Sep 17 00:00:00 2001 From: P. J. McDermott Date: Sat, 16 Feb 2013 21:35:48 -0500 Subject: TMX parser rewrite number one. --- (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index ac515cc..2ddbe94 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,6 +4,7 @@ src_SOURCES = \ src/main.c \ src/init.c \ src/logging.c \ + src/xml.c \ src/base64.c \ src/compression.c \ src/tmx.c \ diff --git a/src/map.h b/src/map.h deleted file mode 100644 index 925bdd2..0000000 --- a/src/map.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef MAP_H -#define MAP_H - -#include -#include "tileset.h" -#include "layer.h" - -struct map { - int width; - int height; - int tilewidth; - int tileheight; - struct tileset *tilesets; - struct layer *layers; - Uint8 *collision; -}; - -#endif diff --git a/src/resources/Makefile.am b/src/resources/Makefile.am index 71ddc4b..1829942 100644 --- a/src/resources/Makefile.am +++ b/src/resources/Makefile.am @@ -1,3 +1,5 @@ src_resources_SOURCES = \ src/resources/resource.c \ - src/resources/image.c + src/resources/image.c \ + src/resources/tileset.c \ + src/resources/map.c diff --git a/src/resources/image.h b/src/resources/image.h index 92ef040..b79f0ce 100644 --- a/src/resources/image.h +++ b/src/resources/image.h @@ -1,6 +1,7 @@ #ifndef IMAGE_H #define IMAGE_H +#include #include "resource.h" struct image { diff --git a/src/resources/layer.h b/src/resources/layer.h new file mode 100644 index 0000000..82504de --- /dev/null +++ b/src/resources/layer.h @@ -0,0 +1,14 @@ +#ifndef RESOURCE_LAYER_H +#define RESOURCE_LAYER_H + +#include "resource.h" + +struct layer { + struct resource res; + char *name; + char *encoding; + char *compression; + struct layer *next; +}; + +#endif diff --git a/src/resources/map.c b/src/resources/map.c new file mode 100644 index 0000000..b703629 --- /dev/null +++ b/src/resources/map.c @@ -0,0 +1,423 @@ +#include +#include +#include +#include +#include "map.h" +#include "resource.h" +#include "tileset.h" +#include "../xml.h" +#include "../logging.h" + +struct tmx { + XML_Parser p; + struct map *map; + char *layer_data; +}; + +struct resource_table map_res; + +static void XMLCALL xml_map_start(void *data, const char *el, + const char **attr); +static void XMLCALL xml_map_end(void *data, const char *el); +static void XMLCALL xml_mapelem_start(void *data, const char *el, + const char **attr); +static void XMLCALL xml_mapelem_end(void *data, const char *el); +static void XMLCALL xml_image_start(void *data, const char *el, + const char **attr); +static void XMLCALL xml_image_end(void *data, const char *el); +static void XMLCALL xml_data_start(void *data, const char *el, + const char **attr); +static void XMLCALL xml_data_data(void *data, const char *s, int len); +static void XMLCALL xml_data_end(void *data, const char *el); +static void XMLCALL xml_object_start(void *data, const char *el, + const char **attr); +static void XMLCALL xml_object_end(void *data, const char *el); + +void map_add_layer(struct map *m, struct layer *l); +void map_add_tileset(struct map *m, struct tileset *t); + +static void XMLCALL +xml_map_start(void *data, const char *el, const char **attr) +{ + struct tmx *tmx; + + debug("<%s> (type \"map\")", el); + + tmx = (struct tmx *) data; + + if (xml_check_tag(el, "map")) { + if (tmx->map != NULL) { + warn("Found multiple maps in TMX file"); + XML_StopParser(tmx->p, XML_FALSE); + } + tmx->map = malloc(sizeof(*tmx->map)); + if (tmx->map == NULL) { + warn("Failed to allocate map"); + XML_StopParser(tmx->p, XML_FALSE); + } + memset(tmx->map, 0, sizeof(*tmx->map)); + xml_get_int_attr(tmx->p, attr, "width", + &tmx->map->width, 1); + xml_get_int_attr(tmx->p, attr, "width", + &tmx->map->width, 1); + xml_get_int_attr(tmx->p, attr, "tilewidth", + &tmx->map->tilewidth, 1); + xml_get_int_attr(tmx->p, attr, "tileheight", + &tmx->map->tileheight, 1); + XML_SetStartElementHandler(tmx->p, xml_mapelem_start); + XML_SetEndElementHandler(tmx->p, xml_map_end); + } else { + xml_unexpected_start_tag(tmx->p, el, + "map"); + } +} + +static void XMLCALL +xml_map_end(void *data, const char *el) +{ + struct tmx *tmx; + + debug(" (type \"map\")", el); + + tmx = (struct tmx *) data; + + if (xml_check_tag(el, "map")) { + XML_SetStartElementHandler(tmx->p, NULL); + XML_SetEndElementHandler(tmx->p, NULL); + } else { + xml_unexpected_end_tag(tmx->p, el, + "map"); + } +} + +static void XMLCALL +xml_mapelem_start(void *data, const char *el, const char **attr) +{ + struct tmx *tmx; + char *source; + struct tileset *ts; + struct layer *ly; + + debug("<%s> (type \"mapelem\")", el); + + tmx = (struct tmx *) data; + + if (xml_check_tag(el, "tileset")) { + source = NULL; + xml_get_string_attr(tmx->p, attr, "source", + &source, 0); + if (source != NULL) { + ts = tileset_get(source); + free(source); + XML_SetStartElementHandler(tmx->p, NULL); + XML_SetEndElementHandler(tmx->p, xml_tileset_end); + } else { + ts = resource_alloc("", sizeof(*ts)); + xml_get_int_attr(tmx->p, attr, "tilewidth", + &ts->tilewidth, 1); + xml_get_int_attr(tmx->p, attr, "tileheight", + &ts->tileheight, 1); + /* TODO: Change xml_image_start to xml_tselem_start. + * A can contain: + * * Zero or one , + * * Zero or one s, and + * * Zero or more s. */ + XML_SetStartElementHandler(tmx->p, xml_image_start); + XML_SetEndElementHandler(tmx->p, xml_tileset_end); + } + xml_get_int_attr(tmx->p, attr, "firstgid", + &ts->firstgid, 1); + xml_get_string_attr(tmx->p, attr, "name", + &ts->name, 1); + map_add_tileset(tmx->map, ts); + } else if (xml_check_tag(el, "layer")) { + ly = resource_alloc("", sizeof(*ly)); + xml_get_string_attr(tmx->p, attr, "name", + &ly->name, 1); + map_add_layer(tmx->map, ly); + XML_SetStartElementHandler(tmx->p, xml_data_start); + XML_SetEndElementHandler(tmx->p, xml_mapelem_end); + } else if (xml_check_tag(el, "objectgroup")) { + XML_SetStartElementHandler(tmx->p, xml_object_start); + XML_SetEndElementHandler(tmx->p, xml_mapelem_end); + } else { + xml_unexpected_start_tag(tmx->p, el, + "tileset, layer, or objectgroup"); + } +} + +static void XMLCALL +xml_mapelem_end(void *data, const char *el) +{ + struct tmx *tmx; + + debug(" (type \"mapelem\")", el); + + tmx = (struct tmx *) data; + + if (xml_check_tag(el, "tileset")) { + XML_SetStartElementHandler(tmx->p, xml_mapelem_start); + XML_SetEndElementHandler(tmx->p, xml_map_end); + } else if (xml_check_tag(el, "layer")) { + XML_SetStartElementHandler(tmx->p, xml_mapelem_start); + XML_SetEndElementHandler(tmx->p, xml_map_end); + } else if (xml_check_tag(el, "objectgroup")) { + XML_SetStartElementHandler(tmx->p, xml_mapelem_start); + XML_SetEndElementHandler(tmx->p, xml_map_end); + } else { + xml_unexpected_end_tag(tmx->p, el, + "tileset, layer, or objectgroup"); + } +} + +static void XMLCALL +xml_image_start(void *data, const char *el, const char **attr) +{ + struct tmx *tmx; + struct tileset *ts; + char *source; + + debug("<%s> (type \"image\")", el); + + tmx = (struct tmx *) data; + + if (xml_check_tag(el, "image")) { + ts = tmx->map->tilesets_tail; + xml_get_string_attr(tmx->p, attr, "source", + &source, 1); + ts->image = img_png_get(source); + free(source); + XML_SetStartElementHandler(tmx->p, NULL); + XML_SetEndElementHandler(tmx->p, xml_image_end); + } else { + xml_unexpected_end_tag(tmx->p, el, + "image"); + } +} + +static void XMLCALL +xml_image_end(void *data, const char *el) +{ + struct tmx *tmx; + + tmx = (struct tmx *) data; + + debug(" (type \"image\")", el); + + if (xml_check_tag(el, "image")) { + XML_SetStartElementHandler(tmx->p, NULL); + XML_SetEndElementHandler(tmx->p, xml_mapelem_end); + } else { + xml_unexpected_end_tag(tmx->p, el, + "image"); + } +} + +static void XMLCALL +xml_data_start(void *data, const char *el, const char **attr) +{ + struct tmx *tmx; + struct layer *ly; + + debug("<%s> (type \"data\")", el); + + tmx = (struct tmx *) data; + + if (xml_check_tag(el, "data")) { + ly = tmx->map->layers_tail; + xml_get_string_attr(tmx->p, attr, "encoding", + &ly->encoding, 0); + xml_get_string_attr(tmx->p, attr, "compression", + &ly->compression, 0); + XML_SetStartElementHandler(tmx->p, NULL); + XML_SetEndElementHandler(tmx->p, xml_data_end); + XML_SetCharacterDataHandler(tmx->p, xml_data_data); + } else { + xml_unexpected_start_tag(tmx->p, el, + "data"); + } +} + +static void XMLCALL +xml_data_data(void *data, const char *s, int len) +{ + struct tmx *tmx; + char *s_z; + char *s_z_trimmed, *s_z_trimmed_end; + + debug("...LAYER DATA..."); + + tmx = (struct tmx *) data; + + s_z = s_z_trimmed = strndup(s, len); + + while(isspace(*s_z_trimmed)) { + ++s_z_trimmed; + --len; + } + if (*s_z_trimmed == '\0') { + return; + } + s_z_trimmed_end = s_z_trimmed + len - 1; + while (s_z_trimmed_end > s_z_trimmed && isspace(*s_z_trimmed_end)) { + --s_z_trimmed_end; + } + *(s_z_trimmed_end + 1) = '\0'; + + tmx->layer_data = strdup(s_z_trimmed); + + free(s_z); +} + +static void XMLCALL +xml_data_end(void *data, const char *el) +{ + struct tmx *tmx; + + debug(" (type \"data\")", el); + + tmx = (struct tmx *) data; + + if (xml_check_tag(el, "data")) { + XML_SetStartElementHandler(tmx->p, NULL); + XML_SetEndElementHandler(tmx->p, xml_mapelem_end); + XML_SetCharacterDataHandler(tmx->p, NULL); + } else { + xml_unexpected_end_tag(tmx->p, el, + "data"); + } +} + +static void XMLCALL +xml_object_start(void *data, const char *el, + const char **attr) +{ + struct tmx *tmx; + char *type; + + debug("<%s> (type \"object\")", el); + + tmx = (struct tmx *) data; + + if (xml_check_tag(el, "object")) { + /* TODO: Handle objects. */ + xml_get_string_attr(tmx->p, attr, "type", + &type, 1); + if (strcmp(type, "exit") == 0) { + } else if (strcmp(type, "exit") == 0) { + } + XML_SetStartElementHandler(tmx->p, NULL); + XML_SetEndElementHandler(tmx->p, xml_object_end); + } else { + xml_unexpected_start_tag(tmx->p, el, + "object"); + } +} + +static void XMLCALL +xml_object_end(void *data, const char *el) +{ + struct tmx *tmx; + + debug(" (type \"object\")", el); + + tmx = (struct tmx *) data; + + if (xml_check_tag(el, "object")) { + XML_SetStartElementHandler(tmx->p, xml_object_start); + XML_SetEndElementHandler(tmx->p, xml_mapelem_end); + } else { + xml_unexpected_end_tag(tmx->p, el, + "object"); + } +} + +struct map * +map_get(const char *path) +{ + struct tmx tmx; + FILE *tmx_fp; + void *tmx_buf; + size_t len; + enum XML_Status status; + + memset(&tmx, 0, sizeof(tmx)); + + tmx.map = (struct map *) resource_get(&map_res, path); + if (tmx.map != NULL) { + resource_use((struct resource *) tmx.map); + return tmx.map; + } + + tmx.map = resource_alloc(path, sizeof(*tmx.map)); + + tmx.p = XML_ParserCreate(NULL); + if (tmx.p == NULL) { + warn("Failed to create TMX parser"); + return NULL; + } + + XML_SetStartElementHandler(tmx.p, xml_map_start); + XML_SetEndElementHandler(tmx.p, NULL); + XML_SetCharacterDataHandler(tmx.p, NULL); + + XML_SetUserData(tmx.p, &tmx); + + tmx_fp = fopen(path, "rb"); + if (tmx_fp == NULL) { + warn("Failed to open TMX file"); + XML_ParserFree(tmx.p); + return NULL; + } + + tmx_buf = XML_GetBuffer(tmx.p, 8192); + if (tmx_buf == NULL) { + warn("Failed to create TMX parse buffer"); + XML_ParserFree(tmx.p); + fclose(tmx_fp); + return NULL; + } + + while (!feof(tmx_fp)) { + len = fread(tmx_buf, 1, 8192, tmx_fp); + status = XML_ParseBuffer(tmx.p, len, feof(tmx_fp)); + if (status == XML_STATUS_OK) { + continue; + } + warn("Failed to parse TMX file (%s)", + XML_ErrorString(XML_GetErrorCode(tmx.p))); + XML_ParserFree(tmx.p); + fclose(tmx_fp); + return NULL; + } + + XML_ParserFree(tmx.p); + fclose(tmx_fp); + + resource_use((struct resource *) tmx.map); + resource_add(&map_res, path, (struct resource *) tmx.map); + + return tmx.map; +} + +void +map_add_layer(struct map *m, struct layer *l) +{ + if (m->layers_head == NULL) { + m->layers_head = l; + } else { + m->layers_tail->next = l; + } + m->layers_tail = l; +} + +void +map_add_tileset(struct map *m, struct tileset *t) +{ + if (m->tilesets_head == NULL) { + m->tilesets_head = t; + } else { + m->tilesets_tail->next = t; + } + m->tilesets_tail = t; +} diff --git a/src/resources/map.h b/src/resources/map.h new file mode 100644 index 0000000..04fcf27 --- /dev/null +++ b/src/resources/map.h @@ -0,0 +1,27 @@ +#ifndef RESOURCE_MAP_H +#define RESOURCE_MAP_H + +#include +#include "resource.h" +#include "tileset.h" +#include "layer.h" + +struct map { + struct resource res; + int width; + int height; + int tilewidth; + int tileheight; + struct tileset *tilesets_head; + struct tileset *tilesets_tail; + struct layer *layers_head; + struct layer *layers_tail; + Uint8 *collision; +}; + +struct map *map_get(const char *path); +void map_free(struct map *map); +void map_add_layer(struct map *m, struct layer *l); +void map_add_tileset(struct map *m, struct tileset *t); + +#endif diff --git a/src/resources/tileset.c b/src/resources/tileset.c new file mode 100644 index 0000000..e15224a --- /dev/null +++ b/src/resources/tileset.c @@ -0,0 +1,161 @@ +#include +#include "tileset.h" +#include "../xml.h" +#include "../logging.h" + +/* + * Problem: + * xml_tselem_start(), xml_tileset_end(), etc. may be called with one of two + * types of user data. + * Possible solution: + * 1. Create a base (struct xml_doc) in src/xml.h: + * struct xml_doc { + * enum { + * XML_DOC_TMX, + * XML_DOC_TSX + * } type; + * XML_Parser p; + * }; + * 2. Make (struct tmx) and (struct tmx) "extend" it, e.g.: + * struct tsx { + * struct xml_doc doc; + * struct tileset *ts; + * }; + * 3. Make xml_tselem_start(), xml_tileset_end(), etc. check the doc.type + * member of the user data and find the (struct tileset *) accordingly. + * Wait, what?! + * Alternative solution: + * 1. Declare (struct tmx) in src/resources/map.h. + * 2. Add to (struct tsx) the following member: + * struct tmx *parent_tmx; + * 3. In xml_mapelem_start() (src/resources/map.c): + * 1. Allocate a (struct tsx). + * 2. Set the parent_tmx member of the (struct tsx) to tmx. + * 3. Set the ts member of the (struct tsx) to ts. + * 4. Set the (struct tsx) as the user data. + * 4. In xml_tileset_end() (src/resources/tileset.c), add the following code: + * if (tsx->parent_tmx != NULL) { + * XML_SetUserData(tsx->p, tsx->parent_tmx); + * free(tsx); + * XML_SetStartElementHandler(tmx->p, xml_mapelem_start); + * XML_SetEndElementHandler(tmx->p, xml_map_end); + * } + * Alternative alternative solution: + * Something that doesn't suck. + */ +struct tsx { + XML_Parser p; + struct tileset *ts; +}; + +struct resource_table ts_res; + +void XMLCALL +xml_tileset_start(void *data, const char *el, const char **attr) +{ + struct tsx *tsx; + + debug("<%s> (type \"tileset\")", el); + + tsx = (struct tsx *) data; + + if (xml_check_tag(el, "tileset")) { + xml_get_string_attr(tsx->p, attr, "name", + &tsx->ts->name, 1); + xml_get_int_attr(tsx->p, attr, "tilewidth", + &tsx->ts->tilewidth, 1); + xml_get_int_attr(tsx->p, attr, "tileheight", + &tsx->ts->tileheight, 1); +/* XML_SetStartElementHandler(tsx->p, xml_tselem_start);*/ + XML_SetEndElementHandler(tsx->p, xml_tileset_end); + } else { + xml_unexpected_start_tag(tsx->p, el, + "tileset"); + } +} + +void XMLCALL +xml_tileset_end(void *data, const char *el) +{ + struct tsx *tsx; + + debug(" (type \"tileset\")", el); + + tsx = (struct tsx *) data; + + if (xml_check_tag(el, "tileset")) { + XML_SetStartElementHandler(tsx->p, NULL); + XML_SetEndElementHandler(tsx->p, NULL); + } else { + xml_unexpected_end_tag(tsx->p, el, + "tileset"); + } +} + +struct tileset * +tileset_get(const char *path) +{ + struct tsx tsx; + FILE *tsx_fp; + void *tsx_buf; + size_t len; + enum XML_Status status; + + memset(&tsx, 0, sizeof(tsx)); + + tsx.ts = (struct tileset *) resource_get(&ts_res, path); + if (tsx.ts != NULL) { + resource_use((struct resource *) tsx.ts); + return tsx.ts; + } + + tsx.ts = resource_alloc(path, sizeof(*tsx.ts)); + + tsx.p = XML_ParserCreate(NULL); + if (tsx.p == NULL) { + warn("Failed to create TSX parser"); + return NULL; + } + + XML_SetStartElementHandler(tsx.p, xml_tileset_start); + XML_SetEndElementHandler(tsx.p, NULL); + XML_SetCharacterDataHandler(tsx.p, NULL); + + XML_SetUserData(tsx.p, &tsx); + + tsx_fp = fopen(path, "rb"); + if (tsx_fp == NULL) { + warn("Failed to open TSX file"); + XML_ParserFree(tsx.p); + return NULL; + } + + tsx_buf = XML_GetBuffer(tsx.p, 8192); + if (tsx_buf == NULL) { + warn("Failed to create TSX parse buffer"); + XML_ParserFree(tsx.p); + fclose(tsx_fp); + return NULL; + } + + while (!feof(tsx_fp)) { + len = fread(tsx_buf, 1, 8192, tsx_fp); + status = XML_ParseBuffer(tsx.p, len, feof(tsx_fp)); + if (status == XML_STATUS_OK) { + continue; + } + warn("Failed to parse TSX file (%s)", + XML_ErrorString(XML_GetErrorCode(tsx.p))); + XML_ParserFree(tsx.p); + fclose(tsx_fp); + return NULL; + } + + XML_ParserFree(tsx.p); + fclose(tsx_fp); + + resource_use((struct resource *) tsx.ts); + resource_add(&ts_res, path, (struct resource *) tsx.ts); + + return tsx.ts; +} diff --git a/src/resources/tileset.h b/src/resources/tileset.h new file mode 100644 index 0000000..45de237 --- /dev/null +++ b/src/resources/tileset.h @@ -0,0 +1,23 @@ +#ifndef RESOURCE_TILESET_H +#define RESOURCE_TILESET_H + +#include +#include "resource.h" +#include "image.h" + +struct tileset { + struct resource res; + int firstgid; + char *name; + int tilewidth; + int tileheight; + struct image *image; + struct tileset *next; +}; + +struct tileset *tileset_get(const char *path); +void XMLCALL xml_tileset_start(void *data, const char *el, + const char **attr); +void XMLCALL xml_tileset_end(void *data, const char *el); + +#endif diff --git a/src/xml.c b/src/xml.c new file mode 100644 index 0000000..68a72ed --- /dev/null +++ b/src/xml.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include "xml.h" +#include "logging.h" + +inline int +xml_check_tag(const char *found, const char *expected) +{ + return strcmp(found, expected) != 0; +} + +inline void +xml_unexpected_start_tag(XML_Parser p, const char *found, const char *expected) +{ + warn("Found \"%s\" start tag where expected one of \"%s\" in map", + found, expected); + XML_StopParser(p, XML_FALSE); +} + +inline void +xml_unexpected_end_tag(XML_Parser p, const char *found, const char *expected) +{ + warn("Found \"%s\" end tag where expected one of \"%s\" in map", + found, expected); + XML_StopParser(p, XML_FALSE); +} + +void +xml_get_int_attr(XML_Parser p, const char **attr, const char *name, + int *dest, int req) +{ + for (; attr[0] != NULL; attr += 2) { + if (strcmp(attr[0], name) == 0) { + if (sscanf(attr[1], "%d", dest) == 1) { + return; + } else if (req) { + warn("Invalid \"%s\" attribute value", name); + XML_StopParser(p, XML_FALSE); + } + } + } + if (req) { + warn("Required attribute \"%s\" not found", name); + XML_StopParser(p, XML_FALSE); + } +} + +void +xml_get_string_attr(XML_Parser p, const char **attr, const char *name, + char **dest, int req) +{ + for (; attr[0] != NULL; attr += 2) { + if (strcmp(attr[0], name) == 0) { + *dest = strdup(attr[1]); + } + } + if (req) { + warn("Required attribute \"%s\" not found", name); + XML_StopParser(p, XML_FALSE); + } +} diff --git a/src/xml.h b/src/xml.h new file mode 100644 index 0000000..07f9c80 --- /dev/null +++ b/src/xml.h @@ -0,0 +1,16 @@ +#ifndef XML_H +#define XML_H + +#include + +extern inline int xml_check_tag(const char *found, const char *expected); +extern inline void xml_unexpected_start_tag(XML_Parser p, const char *found, + const char *expected); +extern inline void xml_unexpected_end_tag(XML_Parser p, const char *found, + const char *expected); +void xml_get_int_attr(XML_Parser p, const char **attr, const char *name, + int *dest, int req); +void xml_get_string_attr(XML_Parser p, const char **attr, const char *name, + char **dest, int req); + +#endif -- cgit v0.9.1