From 7743fd1feb81e1659584491ca88d50bd629cccef Mon Sep 17 00:00:00 2001
From: sanine <sanine.not@pm.me>
Date: Tue, 7 Feb 2023 19:47:49 -0600
Subject: add KAI_FILL_ARRAY_FROM_TAGS

---
 src/util/util.h      |  49 +++++++++++++++++++++++++
 src/util/util.test.c | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 151 insertions(+)

diff --git a/src/util/util.h b/src/util/util.h
index 67d3922..71cae4e 100644
--- a/src/util/util.h
+++ b/src/util/util.h
@@ -2,6 +2,7 @@
 #define KALMIA_UTIL_H
 
 #include <kalmia.h>
+#include "xml/xml.h"
 
 
 /** @brief add a new element to an array
@@ -39,4 +40,52 @@ size_t kai_text_to_uints(unsigned int *dest, const char *str, size_t count);
 
 void *kai_alloc(size_t size, const char *purpose);
 
+
+#define KAI_FOR_CHILD_OF_TYPE(parent, child, type) \
+for ( \
+	child = kai_tag_get_first_child_with_type(parent, type); \
+	child != NULL; \
+	child = kai_tag_get_next_sibling_with_type(child, type) \
+)
+
+
+#define KAI_FILL_ARRAY_FROM_TAGS(result, dest, count, type, parent, tag_type, read, release) \
+do { \
+	unsigned int count_internal = 0; \
+	bool error = false; \
+	struct kai_tag_t *tag_internal; \
+	KAI_FOR_CHILD_OF_TYPE(parent, tag_internal, tag_type) { \
+		count_internal += 1; \
+	} \
+	 \
+	if (count_internal == 0) { \
+		dest = NULL; \
+	} \
+	else { \
+		dest = malloc(count_internal * sizeof(type)); \
+		int i = 0; \
+		KAI_FOR_CHILD_OF_TYPE(parent, tag_internal, tag_type) { \
+			if (error) break; \
+			int result_internal = read(dest + i, tag_internal); \
+			if (result_internal != 0) { \
+				int j; \
+				for (j=0; j<i; j++) { \
+					release(dest[i]); \
+				} \
+				free(dest); \
+				dest = NULL; \
+				result = -1; \
+				count = 0; \
+				error = true; \
+			} \
+			i += 1; \
+		} \
+	} \
+ \
+ 	if (!error) { \
+		result = 0; \
+		count = count_internal; \
+	} \
+} while (0)
+
 #endif
diff --git a/src/util/util.test.c b/src/util/util.test.c
index 6288039..53bb8b2 100644
--- a/src/util/util.test.c
+++ b/src/util/util.test.c
@@ -127,6 +127,108 @@ LILY_TEST("convert string to int array with fewer conversions than expected")
 
 
 
+int f_read_int(int *dest, struct kai_tag_t *src)
+{
+	long num = kai_tag_attr_to_long(src, "num", -1);
+	if (num == -1) { return -1; }
+	*dest = num;
+	return 0;
+}
+
+int release_calls;
+void f_release_int(int i) { release_calls += 1; }
+
+
+LILY_TEST("KAI_FILL_ARRAY_FROM_TAGS produces NULL when no tags are found")
+{
+	int result, count;
+	int *buf;
+	release_calls = 0;
+
+	struct kai_tag_t *t = kai_parse_string(
+		"<tag>"
+		"	<xxx />"
+		"	<yyy />"
+		"	<zzz />"
+		"</tag>"
+	);
+
+	KAI_FILL_ARRAY_FROM_TAGS(result, buf, count, int, t, "num", f_read_int, f_release_int);
+	kai_tag_destroy(t);
+
+	CHECK_EQ(result, 0, "%d");
+	CHECK_EQ(release_calls, 0, "%d");
+	CHECK_EQ(buf, NULL, "%p");
+	CHECK_EQ(count, 0, "%d");
+}
+#include LILY_PUSH_TEST()
+
+
+LILY_TEST("KAI_FILL_ARRAY_FROM_TAGS properly fills array")
+{
+	int result, count;
+	int *buf;
+	release_calls = 0;
+
+	struct kai_tag_t *t = kai_parse_string(
+		"<tag>"
+		"	<xxx />"
+		"	<num num=\"5\" />"
+		"	<yyy />"
+		"	<num num=\"4\" />"
+		"	<num num=\"3\" />"
+		"	<zzz />"
+		"	<num num=\"2\" />"
+		"</tag>"
+	);
+
+	KAI_FILL_ARRAY_FROM_TAGS(result, buf, count, int, t, "num", f_read_int, f_release_int);
+	kai_tag_destroy(t);
+
+	REQUIRE_EQ(result, 0, "%d");
+	CHECK_EQ(release_calls, 0, "%d");
+	REQUIRE_EQ(count, 4, "%d");
+	CHECK_EQ(buf[0], 5, "%d");
+	CHECK_EQ(buf[1], 4, "%d");
+	CHECK_EQ(buf[2], 3, "%d");
+	CHECK_EQ(buf[3], 2, "%d");
+
+	free(buf);
+}
+#include LILY_PUSH_TEST()
+
+
+
+LILY_TEST("KAI_FILL_ARRAY_FROM_TAGS properly releases on error")
+{
+	int result, count;
+	int *buf;
+	release_calls = 0;
+
+	struct kai_tag_t *t = kai_parse_string(
+		"<tag>"
+		"	<xxx />"
+		"	<num num=\"5\" />"
+		"	<yyy />"
+		"	<num num=\"4\" />"
+		"	<num nm=\"3\" />"
+		"	<zzz />"
+		"	<num num=\"2\" />"
+		"</tag>"
+	);
+
+	KAI_FILL_ARRAY_FROM_TAGS(result, buf, count, int, t, "num", f_read_int, f_release_int);
+	kai_tag_destroy(t);
+
+	CHECK_EQ(result, -1, "%d");
+	CHECK_EQ(release_calls, 2, "%d");
+	CHECK_EQ(count, 0, "%d");
+	CHECK_EQ(buf, NULL, "%p");
+}
+#include LILY_PUSH_TEST()
+
+
+
 
 #define LILY_FILE_END
 #include LILY_REGISTER_TESTS()
-- 
cgit v1.2.1