summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorP. J. McDermott <pjm@nac.net>2013-02-16 21:35:48 (EST)
committer P. J. McDermott <pjm@nac.net>2013-02-16 21:35:48 (EST)
commit1e4407da677dd7a192c817de16583bb7199ceeb0 (patch)
treeb97d3e7e746ef91ffebb86cb654e6fc8499241e5 /src
parent63349a6230304a7f1aa34a4c96e4873f86bf39c5 (diff)
downloadoverworld-rpg-1e4407da677dd7a192c817de16583bb7199ceeb0.zip
overworld-rpg-1e4407da677dd7a192c817de16583bb7199ceeb0.tar.gz
overworld-rpg-1e4407da677dd7a192c817de16583bb7199ceeb0.tar.bz2
TMX parser rewrite number one.
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am1
-rw-r--r--src/map.h18
-rw-r--r--src/resources/Makefile.am4
-rw-r--r--src/resources/image.h1
-rw-r--r--src/resources/layer.h14
-rw-r--r--src/resources/map.c423
-rw-r--r--src/resources/map.h27
-rw-r--r--src/resources/tileset.c161
-rw-r--r--src/resources/tileset.h23
-rw-r--r--src/xml.c62
-rw-r--r--src/xml.h16
11 files changed, 731 insertions, 19 deletions
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 <SDL_stdinc.h>
-#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 <SDL.h>
#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 <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <expat.h>
+#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("</%s> (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 <tileset> can contain:
+ * * Zero or one <properties>,
+ * * Zero or one <image>s, and
+ * * Zero or more <tile>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("</%s> (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("</%s> (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("</%s> (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("</%s> (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 <SDL_stdinc.h>
+#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 <expat.h>
+#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("</%s> (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 <expat.h>
+#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 <string.h>
+#include <stdio.h>
+#include <expat.h>
+#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 <expat.h>
+
+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