view tools/molko-map.c @ 16:621c815c9509

tools: implement basic molko-map stub, continue #2448
author David Demelier <markand@malikania.fr>
date Tue, 07 Jan 2020 21:45:03 +0100
parents
children 0d5ecefcccd3
line wrap: on
line source

/*
 * molko-map.c -- convert tiled .tmx into custom files
 *
 * Copyright (c) 2020 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <assert.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <expat.h>

enum state {
	STATE_ROOT,             /* no tag read yet */
	STATE_MAP,              /* top <map> */
	STATE_MAP_PROPERTIES,   /* <map><properties> */
	STATE_TILESET,          /* <tileset> */
	STATE_TILESET_IMAGE,    /* <tileset><image> */
	STATE_LAYER,            /* <layer> */
	STATE_LAYER_DATA,       /* <layer><data> */
};

enum layer {
	LAYER_NONE,
	LAYER_BACKGROUND,
	LAYER_FOREGROUND
};

struct context {
	enum state state;
	enum layer layer;
};

static const int layer_indices[] = {
	[LAYER_NONE] = -1,
	[LAYER_BACKGROUND] = 0,
	[LAYER_FOREGROUND] = 1
};

static void on_data(struct context *, const char *, const char **);
static void on_image(struct context *, const char *, const char **);
static void on_layer(struct context *, const char *, const char **);
static void on_map(struct context *, const char *, const char **);
static void on_properties(struct context *, const char *, const char **);
static void on_property(struct context *, const char *, const char **);
static void on_tile(struct context *, const char *, const char **);
static void on_tileset(struct context *, const char *, const char **);
static void die(const char *, ...);

static const struct {
	const char *name;
	void (*begin)(struct context *, const char *, const char **);
	void (*end)(struct context *, const char *);
} handlers[] = {
	{ "data",       on_data,        NULL    },
	{ "image",      on_image,       NULL    },
	{ "layer",      on_layer,       NULL    },
	{ "map",        on_map,         NULL    },
	{ "properties", on_properties,  NULL    },
	{ "property",   on_property,    NULL    },
	{ "tile",       on_tile,        NULL    },
	{ "tileset",    on_tileset,     NULL    },
	{ NULL,         NULL,           NULL    },
};

static bool
is_known_map_property(const char *name)
{
	static const char *known[] = {
		"title",
		NULL
	};

	for (size_t i = 0; known[i]; ++i)
		if (strcmp(known[i], name) == 0)
			return true;

	return false;
}

static void
write_map_property(const char **attrs)
{
	/*
	 * This is how <map><properties> are defined:
	 *
	 *             0         1          2          3
	 * <property name="PROPERTY NAME" value="PROPERTY VALUE">
	 */
	for (size_t i = 0; attrs[i]; i += 4) {
		if (is_known_map_property(attrs[i + 1]))
			printf("%s|%s\n", attrs[i + 1], attrs[i + 3]);
		else
			die("unsupported map property: %s\n", attrs[i + 1]);
	}
}

static const char *
find_attr(const char **attrs, const char *key)
{
	for (size_t i = 0; attrs[i]; i += 2)
		if (strcmp(attrs[i], key) == 0)
			return attrs[i + 1];

	return NULL;
}

static void
on_map(struct context *ctx, const char *name, const char **attrs)
{
	(void)ctx;
	(void)name;
	(void)attrs;

	ctx->state = STATE_MAP;
}

static void
on_properties(struct context *ctx, const char *name, const char **attrs)
{
	(void)name;
	(void)attrs;

	/*
	 * <properties> exist in different parent tags
	 */
	switch (ctx->state) {
	case STATE_MAP:
		ctx->state = STATE_MAP_PROPERTIES;
		break;
	default:
		fprintf(stderr, "warning: unsupported <properties>\n");
		break;
	}
}

static void
on_property(struct context *ctx, const char *name, const char **attrs)
{
	(void)name;
	(void)attrs;

	switch (ctx->state) {
	case STATE_MAP_PROPERTIES:
		write_map_property(attrs);
		break;
	default:
		break;
	}
}

static void
on_layer(struct context *ctx, const char *name, const char **attrs)
{
	(void)name;

	/*
	 * The layer is defined like this:
	 *
	 * Values of attrs:
	 *          0       1       IGNORED
	 * <layer name="background" width="10" height="10">
	 */
	const char *layername = find_attr(attrs, "name");

	if (!layername)
		die("layer is missing 'name' attribute\n");

	if (strcmp(layername, "background") == 0)
		ctx->layer = LAYER_BACKGROUND;
	else
		die("invalid '%s' value for 'name' property\n", layername);

	ctx->state = STATE_LAYER;
}

static void
on_data(struct context *ctx, const char *name, const char **attrs)
{
	(void)name;
	(void)attrs;

	if (ctx->state != STATE_LAYER)
		die("reading <data> outside a <layer> node\n");

	ctx->state = STATE_LAYER_DATA;
}

static void
on_tile(struct context *ctx, const char *name, const char **attrs)
{
	(void)name;

	/* TODO: add more layer */
	if (ctx->state != STATE_LAYER_DATA)
		die("reading <tile> outside a <data> node\n");

	/*
	 * The tile is defined as following:
	 *
	 * Values of attrs:
	 *        0   1
	 * <tile gid="1" />
	 */
	const char *gid = find_attr(attrs, "gid");

	if (!gid)
		die("missing 'gid' attribute in <tile> node\n");

	printf("tile|%d|%s\n", layer_indices[ctx->layer], gid);
}

static void
on_tileset(struct context *ctx, const char *name, const char **attrs)
{
	(void)ctx;
	(void)name;
	(void)attrs;

	ctx->state = STATE_TILESET;
}

static void
on_image(struct context *ctx, const char *name, const char **attrs)
{
	(void)name;

	if (ctx->state != STATE_TILESET)
		die("reading <image> outside a <tileset> node\n");

	/**
	 * The tile is defined as following:
	 *
	 * Values of attrs:
	 *        0   1
	 * <image source="name.png" ... />
	 */
	const char *src = find_attr(attrs, "source");

	if (!src)
		die("missing 'source' attribute in <image> node\n");

	printf("tileset|%s\n", src);
}

static void
on_start_element(struct context *ctx, const char *name, const char **attrs)
{
	assert(ctx);
	assert(name);
	assert(attrs);

	size_t i;

	for (i = 0; handlers[i].name; ++i) {
		if (strcmp(name, handlers[i].name) == 0) {
			handlers[i].begin(ctx, name, attrs);
			break;
		}
	}

	if (!handlers[i].name)
		fprintf(stderr, "unsupported <%s> tag\n", name);
}

static void
on_end_element(void *data, const char *name)
{
	(void)data;
	(void)name;
#if 0
	size_t i;

	for (i = 0; handlers[i].name; ++i) {
		if (strcmp(name, handlers[i].name) == 0) {
			handlers[i].end(ctx, name, attrs);
			break;
		}
	}

	if (!handlers[i].name)
		fprintf(stderr, "unsupported <%s> tag\n", name);
#endif
}

static void
run(XML_Parser parser)
{
	char buffer[BUFSIZ];
	size_t nbread;
	struct context ctx = {0};

	XML_SetUserData(parser, &ctx);

	while ((nbread = fread(buffer, 1, sizeof (buffer), stdin)) > 0) {
		bool done = nbread < sizeof (buffer);

		if (XML_Parse(parser, buffer, nbread, done) == XML_STATUS_ERROR) {
			fprintf(stderr, "%lu: %s\n",
			    XML_GetCurrentLineNumber(parser),
			    XML_ErrorString(XML_GetErrorCode(parser)));
			exit(1);
		}
	}
}

static void
die(const char *fmt, ...)
{
	assert(fmt);

	va_list ap;

	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
	exit(1);
}

int
main(void)
{
	XML_Parser parser;

	parser = XML_ParserCreate(NULL);
	XML_SetElementHandler(parser,
		(XML_StartElementHandler)on_start_element, on_end_element);

	run(parser);

	XML_ParserFree(parser);
}