summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/CMakeLists.txt2
-rw-r--r--core/datatrak.c1
-rw-r--r--core/dive.c223
-rw-r--r--core/dive.h46
-rw-r--r--core/import-suunto.c1
-rw-r--r--core/load-git.c1
-rw-r--r--core/parse-xml.c1
-rw-r--r--core/qthelper.cpp1
-rw-r--r--core/save-git.c1
-rw-r--r--core/save-html.c1
-rw-r--r--core/save-xml.c1
-rw-r--r--core/structured_list.h30
-rw-r--r--core/tag.c206
-rw-r--r--core/tag.h62
-rw-r--r--core/uemis-downloader.c1
-rw-r--r--desktop-widgets/command_edit.cpp1
-rw-r--r--desktop-widgets/divelogexportdialog.cpp1
-rw-r--r--desktop-widgets/simplewidgets.cpp1
-rw-r--r--packaging/ios/Subsurface-mobile.pro1
-rw-r--r--qt-models/completionmodels.cpp1
-rw-r--r--qt-models/divetripmodel.cpp1
-rw-r--r--smtk-import/smartrak.c1
-rw-r--r--subsurface-desktop-main.cpp1
-rw-r--r--subsurface-mobile-main.cpp1
-rw-r--r--tests/testtaglist.cpp1
25 files changed, 321 insertions, 267 deletions
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 17a2bd0c7..9d3f3e493 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -144,6 +144,8 @@ set(SUBSURFACE_CORE_LIB_SRCS
subsurfacestartup.h
subsurfacesysinfo.cpp
subsurfacesysinfo.h
+ tag.c
+ tag.h
taxonomy.c
taxonomy.h
time.c
diff --git a/core/datatrak.c b/core/datatrak.c
index 5e806f08f..43c7346cd 100644
--- a/core/datatrak.c
+++ b/core/datatrak.c
@@ -17,6 +17,7 @@
#include "file.h"
#include "divesite.h"
#include "ssrf.h"
+#include "tag.h"
static unsigned int two_bytes_to_int(unsigned char x, unsigned char y)
{
diff --git a/core/dive.c b/core/dive.c
index 907ad40e7..5191402a5 100644
--- a/core/dive.c
+++ b/core/dive.c
@@ -14,24 +14,14 @@
#include "qthelper.h"
#include "metadata.h"
#include "membuffer.h"
+#include "tag.h"
+#include "structured_list.h"
/* one could argue about the best place to have this variable -
* it's used in the UI, but it seems to make the most sense to have it
* here */
struct dive displayed_dive;
-struct tag_entry *g_tag_list = NULL;
-
-static const char *default_tags[] = {
- QT_TRANSLATE_NOOP("gettextFromC", "boat"), QT_TRANSLATE_NOOP("gettextFromC", "shore"), QT_TRANSLATE_NOOP("gettextFromC", "drift"),
- QT_TRANSLATE_NOOP("gettextFromC", "deep"), QT_TRANSLATE_NOOP("gettextFromC", "cavern"), QT_TRANSLATE_NOOP("gettextFromC", "ice"),
- QT_TRANSLATE_NOOP("gettextFromC", "wreck"), QT_TRANSLATE_NOOP("gettextFromC", "cave"), QT_TRANSLATE_NOOP("gettextFromC", "altitude"),
- QT_TRANSLATE_NOOP("gettextFromC", "pool"), QT_TRANSLATE_NOOP("gettextFromC", "lake"), QT_TRANSLATE_NOOP("gettextFromC", "river"),
- QT_TRANSLATE_NOOP("gettextFromC", "night"), QT_TRANSLATE_NOOP("gettextFromC", "fresh"), QT_TRANSLATE_NOOP("gettextFromC", "student"),
- QT_TRANSLATE_NOOP("gettextFromC", "instructor"), QT_TRANSLATE_NOOP("gettextFromC", "photo"), QT_TRANSLATE_NOOP("gettextFromC", "video"),
- QT_TRANSLATE_NOOP("gettextFromC", "deco")
-};
-
const char *cylinderuse_text[] = {
QT_TRANSLATE_NOOP("gettextFromC", "OC-gas"), QT_TRANSLATE_NOOP("gettextFromC", "diluent"), QT_TRANSLATE_NOOP("gettextFromC", "oxygen"), QT_TRANSLATE_NOOP("gettextFromC", "not used")
};
@@ -474,31 +464,6 @@ struct dive *alloc_dive(void)
return dive;
}
-/* Clear everything but the first element;
- * this works for taglist, picturelist, even dive computers */
-#define STRUCTURED_LIST_FREE(_type, _start, _free) \
- { \
- _type *_ptr = _start; \
- while (_ptr) { \
- _type *_next = _ptr->next; \
- _free(_ptr); \
- _ptr = _next; \
- } \
- }
-
-#define STRUCTURED_LIST_COPY(_type, _first, _dest, _cpy) \
- { \
- _type *_sptr = _first; \
- _type **_dptr = &_dest; \
- while (_sptr) { \
- *_dptr = malloc(sizeof(_type)); \
- _cpy(_sptr, *_dptr); \
- _sptr = _sptr->next; \
- _dptr = &(*_dptr)->next; \
- } \
- *_dptr = 0; \
- }
-
static void free_dc(struct divecomputer *dc);
static void free_dc_contents(struct divecomputer *dc);
@@ -548,14 +513,6 @@ static void copy_pl(struct picture *sp, struct picture *dp)
dp->filename = copy_string(sp->filename);
}
-/* copy an element in a list of tags */
-static void copy_tl(struct tag_entry *st, struct tag_entry *dt)
-{
- dt->tag = malloc(sizeof(struct divetag));
- dt->tag->name = copy_string(st->tag->name);
- dt->tag->source = copy_string(st->tag->source);
-}
-
static void free_dive_structures(struct dive *d)
{
if (!d)
@@ -3253,182 +3210,6 @@ static void join_dive_computers(struct dive *d, struct divecomputer *res,
remove_redundant_dc(res, prefer_downloaded);
}
-static bool tag_seen_before(struct tag_entry *start, struct tag_entry *before)
-{
- while (start && start != before) {
- if (same_string(start->tag->name, before->tag->name))
- return true;
- start = start->next;
- }
- return false;
-}
-
-/* remove duplicates and empty nodes */
-void taglist_cleanup(struct tag_entry **tag_list)
-{
- struct tag_entry **tl = tag_list;
- while (*tl) {
- /* skip tags that are empty or that we have seen before */
- if (empty_string((*tl)->tag->name) || tag_seen_before(*tag_list, *tl)) {
- *tl = (*tl)->next;
- continue;
- }
- tl = &(*tl)->next;
- }
-}
-
-char *taglist_get_tagstring(struct tag_entry *tag_list)
-{
- bool first_tag = true;
- struct membuffer b = { 0 };
- struct tag_entry *tmp = tag_list;
- while (tmp != NULL) {
- if (!empty_string(tmp->tag->name)) {
- if (first_tag) {
- put_format(&b, "%s", tmp->tag->name);
- first_tag = false;
- } else {
- put_format(&b, ", %s", tmp->tag->name);
- }
- }
- tmp = tmp->next;
- }
- /* Ensures we do return null terminated empty string for:
- * - empty tag list
- * - tag list with empty tag only
- */
- mb_cstring(&b);
- return detach_buffer(&b);
-}
-
-static inline void taglist_free_divetag(struct divetag *tag)
-{
- if (tag->name != NULL)
- free(tag->name);
- if (tag->source != NULL)
- free(tag->source);
- free(tag);
-}
-
-/* Add a tag to the tag_list, keep the list sorted */
-static struct divetag *taglist_add_divetag(struct tag_entry **tag_list, struct divetag *tag)
-{
- struct tag_entry *next, *entry;
-
- while ((next = *tag_list) != NULL) {
- int cmp = strcmp(next->tag->name, tag->name);
-
- /* Already have it? */
- if (!cmp)
- return next->tag;
- /* Is the entry larger? If so, insert here */
- if (cmp > 0)
- break;
- /* Continue traversing the list */
- tag_list = &next->next;
- }
-
- /* Insert in front of it */
- entry = malloc(sizeof(struct tag_entry));
- entry->next = next;
- entry->tag = tag;
- *tag_list = entry;
- return tag;
-}
-
-struct divetag *taglist_add_tag(struct tag_entry **tag_list, const char *tag)
-{
- size_t i = 0;
- int is_default_tag = 0;
- struct divetag *ret_tag, *new_tag;
- const char *translation;
- new_tag = malloc(sizeof(struct divetag));
-
- for (i = 0; i < sizeof(default_tags) / sizeof(char *); i++) {
- if (strcmp(default_tags[i], tag) == 0) {
- is_default_tag = 1;
- break;
- }
- }
- /* Only translate default tags */
- if (is_default_tag) {
- translation = translate("gettextFromC", tag);
- new_tag->name = malloc(strlen(translation) + 1);
- memcpy(new_tag->name, translation, strlen(translation) + 1);
- new_tag->source = malloc(strlen(tag) + 1);
- memcpy(new_tag->source, tag, strlen(tag) + 1);
- } else {
- new_tag->source = NULL;
- new_tag->name = malloc(strlen(tag) + 1);
- memcpy(new_tag->name, tag, strlen(tag) + 1);
- }
- /* Try to insert new_tag into g_tag_list if we are not operating on it */
- if (tag_list != &g_tag_list) {
- ret_tag = taglist_add_divetag(&g_tag_list, new_tag);
- /* g_tag_list already contains new_tag, free the duplicate */
- if (ret_tag != new_tag)
- taglist_free_divetag(new_tag);
- ret_tag = taglist_add_divetag(tag_list, ret_tag);
- } else {
- ret_tag = taglist_add_divetag(tag_list, new_tag);
- if (ret_tag != new_tag)
- taglist_free_divetag(new_tag);
- }
- return ret_tag;
-}
-
-void taglist_free(struct tag_entry *entry)
-{
- STRUCTURED_LIST_FREE(struct tag_entry, entry, free)
-}
-
-struct tag_entry *taglist_copy(struct tag_entry *s)
-{
- struct tag_entry *res;
- STRUCTURED_LIST_COPY(struct tag_entry, s, res, copy_tl);
- return res;
-}
-
-/* Merge src1 and src2, write to *dst */
-static void taglist_merge(struct tag_entry **dst, struct tag_entry *src1, struct tag_entry *src2)
-{
- struct tag_entry *entry;
-
- for (entry = src1; entry; entry = entry->next)
- taglist_add_divetag(dst, entry->tag);
- for (entry = src2; entry; entry = entry->next)
- taglist_add_divetag(dst, entry->tag);
-}
-
-void taglist_init_global()
-{
- size_t i;
-
- for (i = 0; i < sizeof(default_tags) / sizeof(char *); i++)
- taglist_add_tag(&g_tag_list, default_tags[i]);
-}
-
-bool taglist_contains(struct tag_entry *tag_list, const char *tag)
-{
- while (tag_list) {
- if (same_string(tag_list->tag->name, tag))
- return true;
- tag_list = tag_list->next;
- }
- return false;
-}
-
-struct tag_entry *taglist_added(struct tag_entry *original_list, struct tag_entry *new_list)
-{
- struct tag_entry *added_list = NULL;
- while (new_list) {
- if (!taglist_contains(original_list, new_list->tag->name))
- taglist_add_tag(&added_list, new_list->tag->name);
- new_list = new_list->next;
- }
- return added_list;
-}
-
bool is_dc_planner(const struct divecomputer *dc)
{
return same_string(dc->model, "planned dive");
diff --git a/core/dive.h b/core/dive.h
index c115f5c0f..a68336494 100644
--- a/core/dive.h
+++ b/core/dive.h
@@ -177,52 +177,6 @@ struct sample // BASE TYPE BYTES UNITS RANGE
// not calculated when planning a dive
}; // Total size of structure: 57 bytes, excluding padding at end
-struct divetag {
- /*
- * The name of the divetag. If a translation is available, name contains
- * the translated tag
- */
- char *name;
- /*
- * If a translation is available, we write the original tag to source.
- * This enables us to write a non-localized tag to the xml file.
- */
- char *source;
-};
-
-struct tag_entry {
- struct divetag *tag;
- struct tag_entry *next;
-};
-
-/*
- * divetags are only stored once, each dive only contains
- * a list of tag_entries which then point to the divetags
- * in the global g_tag_list
- */
-
-extern struct tag_entry *g_tag_list;
-
-struct divetag *taglist_add_tag(struct tag_entry **tag_list, const char *tag);
-struct tag_entry *taglist_added(struct tag_entry *original_list, struct tag_entry *new_list);
-
-/*
- * Writes all divetags form tag_list into internally allocated buffer
- * Function returns pointer to allocated buffer
- * Buffer contains comma separated list of tags names or null terminated string
- *
- * NOTE! The returned buffer must be freed once used.
- */
-char *taglist_get_tagstring(struct tag_entry *tag_list);
-
-/* cleans up a list: removes empty tags and duplicates */
-void taglist_cleanup(struct tag_entry **tag_list);
-
-void taglist_init_global();
-void taglist_free(struct tag_entry *tag_list);
-struct tag_entry *taglist_copy(struct tag_entry *s);
-bool taglist_contains(struct tag_entry *tag_list, const char *tag);
-
struct extra_data {
const char *key;
const char *value;
diff --git a/core/import-suunto.c b/core/import-suunto.c
index 078593700..060ed113b 100644
--- a/core/import-suunto.c
+++ b/core/import-suunto.c
@@ -12,6 +12,7 @@
#include "device.h"
#include "membuffer.h"
#include "gettext.h"
+#include "tag.h"
static int dm4_events(void *param, int columns, char **data, char **column)
{
diff --git a/core/load-git.c b/core/load-git.c
index a0baedecd..91d8f8cd1 100644
--- a/core/load-git.c
+++ b/core/load-git.c
@@ -22,6 +22,7 @@
#include "membuffer.h"
#include "git-access.h"
#include "qthelper.h"
+#include "tag.h"
const char *saved_git_id = NULL;
diff --git a/core/parse-xml.c b/core/parse-xml.c
index 43d0049a7..f4a7d60a9 100644
--- a/core/parse-xml.c
+++ b/core/parse-xml.c
@@ -29,6 +29,7 @@
#include "device.h"
#include "membuffer.h"
#include "qthelper.h"
+#include "tag.h"
int verbose, quit, force_root;
int last_xml_version = -1;
diff --git a/core/qthelper.cpp b/core/qthelper.cpp
index 65c55414d..e9bab9ebd 100644
--- a/core/qthelper.cpp
+++ b/core/qthelper.cpp
@@ -17,6 +17,7 @@
#include <sys/time.h>
#include "exif.h"
#include "file.h"
+#include "tag.h"
#include "imagedownloader.h"
#include <QFile>
#include <QRegExp>
diff --git a/core/save-git.c b/core/save-git.c
index a5f6f0dde..bbf70c373 100644
--- a/core/save-git.c
+++ b/core/save-git.c
@@ -26,6 +26,7 @@
#include "version.h"
#include "qthelper.h"
#include "gettext.h"
+#include "tag.h"
#define VA_BUF(b, fmt) do { va_list args; va_start(args, fmt); put_vformat(b, fmt, args); va_end(args); } while (0)
diff --git a/core/save-html.c b/core/save-html.c
index f9d7049a9..3c54efd6f 100644
--- a/core/save-html.c
+++ b/core/save-html.c
@@ -8,6 +8,7 @@
#include "qthelper.h"
#include "gettext.h"
#include "divesite.h"
+#include "tag.h"
#include <stdio.h>
void write_attribute(struct membuffer *b, const char *att_name, const char *value, const char *separator)
diff --git a/core/save-xml.c b/core/save-xml.c
index f043ab6d8..658f05e1b 100644
--- a/core/save-xml.c
+++ b/core/save-xml.c
@@ -22,6 +22,7 @@
#include "git-access.h"
#include "qthelper.h"
#include "gettext.h"
+#include "tag.h"
/*
* We're outputting utf8 in xml.
diff --git a/core/structured_list.h b/core/structured_list.h
new file mode 100644
index 000000000..232e4ee34
--- /dev/null
+++ b/core/structured_list.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef STRUCTURED_LIST_H
+#define STRUCTURED_LIST_H
+
+/* Clear everything but the first element;
+ * this works for taglist, picturelist, even dive computers */
+#define STRUCTURED_LIST_FREE(_type, _start, _free) \
+ { \
+ _type *_ptr = _start; \
+ while (_ptr) { \
+ _type *_next = _ptr->next; \
+ _free(_ptr); \
+ _ptr = _next; \
+ } \
+ }
+
+#define STRUCTURED_LIST_COPY(_type, _first, _dest, _cpy) \
+ { \
+ _type *_sptr = _first; \
+ _type **_dptr = &_dest; \
+ while (_sptr) { \
+ *_dptr = malloc(sizeof(_type)); \
+ _cpy(_sptr, *_dptr); \
+ _sptr = _sptr->next; \
+ _dptr = &(*_dptr)->next; \
+ } \
+ *_dptr = 0; \
+ }
+
+#endif
diff --git a/core/tag.c b/core/tag.c
new file mode 100644
index 000000000..ba88ab879
--- /dev/null
+++ b/core/tag.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "tag.h"
+#include "structured_list.h"
+#include "subsurface-string.h"
+#include "membuffer.h"
+#include "gettext.h"
+
+#include <stdlib.h>
+
+struct tag_entry *g_tag_list = NULL;
+
+static const char *default_tags[] = {
+ QT_TRANSLATE_NOOP("gettextFromC", "boat"), QT_TRANSLATE_NOOP("gettextFromC", "shore"), QT_TRANSLATE_NOOP("gettextFromC", "drift"),
+ QT_TRANSLATE_NOOP("gettextFromC", "deep"), QT_TRANSLATE_NOOP("gettextFromC", "cavern"), QT_TRANSLATE_NOOP("gettextFromC", "ice"),
+ QT_TRANSLATE_NOOP("gettextFromC", "wreck"), QT_TRANSLATE_NOOP("gettextFromC", "cave"), QT_TRANSLATE_NOOP("gettextFromC", "altitude"),
+ QT_TRANSLATE_NOOP("gettextFromC", "pool"), QT_TRANSLATE_NOOP("gettextFromC", "lake"), QT_TRANSLATE_NOOP("gettextFromC", "river"),
+ QT_TRANSLATE_NOOP("gettextFromC", "night"), QT_TRANSLATE_NOOP("gettextFromC", "fresh"), QT_TRANSLATE_NOOP("gettextFromC", "student"),
+ QT_TRANSLATE_NOOP("gettextFromC", "instructor"), QT_TRANSLATE_NOOP("gettextFromC", "photo"), QT_TRANSLATE_NOOP("gettextFromC", "video"),
+ QT_TRANSLATE_NOOP("gettextFromC", "deco")
+};
+
+/* copy an element in a list of tags */
+static void copy_tl(struct tag_entry *st, struct tag_entry *dt)
+{
+ dt->tag = malloc(sizeof(struct divetag));
+ dt->tag->name = copy_string(st->tag->name);
+ dt->tag->source = copy_string(st->tag->source);
+}
+
+static bool tag_seen_before(struct tag_entry *start, struct tag_entry *before)
+{
+ while (start && start != before) {
+ if (same_string(start->tag->name, before->tag->name))
+ return true;
+ start = start->next;
+ }
+ return false;
+}
+
+/* remove duplicates and empty nodes */
+void taglist_cleanup(struct tag_entry **tag_list)
+{
+ struct tag_entry **tl = tag_list;
+ while (*tl) {
+ /* skip tags that are empty or that we have seen before */
+ if (empty_string((*tl)->tag->name) || tag_seen_before(*tag_list, *tl)) {
+ *tl = (*tl)->next;
+ continue;
+ }
+ tl = &(*tl)->next;
+ }
+}
+
+char *taglist_get_tagstring(struct tag_entry *tag_list)
+{
+ bool first_tag = true;
+ struct membuffer b = { 0 };
+ struct tag_entry *tmp = tag_list;
+ while (tmp != NULL) {
+ if (!empty_string(tmp->tag->name)) {
+ if (first_tag) {
+ put_format(&b, "%s", tmp->tag->name);
+ first_tag = false;
+ } else {
+ put_format(&b, ", %s", tmp->tag->name);
+ }
+ }
+ tmp = tmp->next;
+ }
+ /* Ensures we do return null terminated empty string for:
+ * - empty tag list
+ * - tag list with empty tag only
+ */
+ mb_cstring(&b);
+ return detach_buffer(&b);
+}
+
+static inline void taglist_free_divetag(struct divetag *tag)
+{
+ if (tag->name != NULL)
+ free(tag->name);
+ if (tag->source != NULL)
+ free(tag->source);
+ free(tag);
+}
+
+/* Add a tag to the tag_list, keep the list sorted */
+static struct divetag *taglist_add_divetag(struct tag_entry **tag_list, struct divetag *tag)
+{
+ struct tag_entry *next, *entry;
+
+ while ((next = *tag_list) != NULL) {
+ int cmp = strcmp(next->tag->name, tag->name);
+
+ /* Already have it? */
+ if (!cmp)
+ return next->tag;
+ /* Is the entry larger? If so, insert here */
+ if (cmp > 0)
+ break;
+ /* Continue traversing the list */
+ tag_list = &next->next;
+ }
+
+ /* Insert in front of it */
+ entry = malloc(sizeof(struct tag_entry));
+ entry->next = next;
+ entry->tag = tag;
+ *tag_list = entry;
+ return tag;
+}
+
+struct divetag *taglist_add_tag(struct tag_entry **tag_list, const char *tag)
+{
+ size_t i = 0;
+ int is_default_tag = 0;
+ struct divetag *ret_tag, *new_tag;
+ const char *translation;
+ new_tag = malloc(sizeof(struct divetag));
+
+ for (i = 0; i < sizeof(default_tags) / sizeof(char *); i++) {
+ if (strcmp(default_tags[i], tag) == 0) {
+ is_default_tag = 1;
+ break;
+ }
+ }
+ /* Only translate default tags */
+ if (is_default_tag) {
+ translation = translate("gettextFromC", tag);
+ new_tag->name = malloc(strlen(translation) + 1);
+ memcpy(new_tag->name, translation, strlen(translation) + 1);
+ new_tag->source = malloc(strlen(tag) + 1);
+ memcpy(new_tag->source, tag, strlen(tag) + 1);
+ } else {
+ new_tag->source = NULL;
+ new_tag->name = malloc(strlen(tag) + 1);
+ memcpy(new_tag->name, tag, strlen(tag) + 1);
+ }
+ /* Try to insert new_tag into g_tag_list if we are not operating on it */
+ if (tag_list != &g_tag_list) {
+ ret_tag = taglist_add_divetag(&g_tag_list, new_tag);
+ /* g_tag_list already contains new_tag, free the duplicate */
+ if (ret_tag != new_tag)
+ taglist_free_divetag(new_tag);
+ ret_tag = taglist_add_divetag(tag_list, ret_tag);
+ } else {
+ ret_tag = taglist_add_divetag(tag_list, new_tag);
+ if (ret_tag != new_tag)
+ taglist_free_divetag(new_tag);
+ }
+ return ret_tag;
+}
+
+void taglist_free(struct tag_entry *entry)
+{
+ STRUCTURED_LIST_FREE(struct tag_entry, entry, free)
+}
+
+struct tag_entry *taglist_copy(struct tag_entry *s)
+{
+ struct tag_entry *res;
+ STRUCTURED_LIST_COPY(struct tag_entry, s, res, copy_tl);
+ return res;
+}
+
+/* Merge src1 and src2, write to *dst */
+void taglist_merge(struct tag_entry **dst, struct tag_entry *src1, struct tag_entry *src2)
+{
+ struct tag_entry *entry;
+
+ for (entry = src1; entry; entry = entry->next)
+ taglist_add_divetag(dst, entry->tag);
+ for (entry = src2; entry; entry = entry->next)
+ taglist_add_divetag(dst, entry->tag);
+}
+
+void taglist_init_global()
+{
+ size_t i;
+
+ for (i = 0; i < sizeof(default_tags) / sizeof(char *); i++)
+ taglist_add_tag(&g_tag_list, default_tags[i]);
+}
+
+bool taglist_contains(struct tag_entry *tag_list, const char *tag)
+{
+ while (tag_list) {
+ if (same_string(tag_list->tag->name, tag))
+ return true;
+ tag_list = tag_list->next;
+ }
+ return false;
+}
+
+struct tag_entry *taglist_added(struct tag_entry *original_list, struct tag_entry *new_list)
+{
+ struct tag_entry *added_list = NULL;
+ while (new_list) {
+ if (!taglist_contains(original_list, new_list->tag->name))
+ taglist_add_tag(&added_list, new_list->tag->name);
+ new_list = new_list->next;
+ }
+ return added_list;
+}
+
diff --git a/core/tag.h b/core/tag.h
new file mode 100644
index 000000000..1f50dc920
--- /dev/null
+++ b/core/tag.h
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0
+// Dive tag related structures and helpers
+#ifndef TAG_H
+#define TAG_H
+
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct divetag {
+ /*
+ * The name of the divetag. If a translation is available, name contains
+ * the translated tag
+ */
+ char *name;
+ /*
+ * If a translation is available, we write the original tag to source.
+ * This enables us to write a non-localized tag to the xml file.
+ */
+ char *source;
+};
+
+struct tag_entry {
+ struct divetag *tag;
+ struct tag_entry *next;
+};
+
+/*
+ * divetags are only stored once, each dive only contains
+ * a list of tag_entries which then point to the divetags
+ * in the global g_tag_list
+ */
+extern struct tag_entry *g_tag_list;
+
+struct divetag *taglist_add_tag(struct tag_entry **tag_list, const char *tag);
+struct tag_entry *taglist_added(struct tag_entry *original_list, struct tag_entry *new_list);
+
+/*
+ * Writes all divetags form tag_list into internally allocated buffer
+ * Function returns pointer to allocated buffer
+ * Buffer contains comma separated list of tags names or null terminated string
+ *
+ * NOTE! The returned buffer must be freed once used.
+ */
+char *taglist_get_tagstring(struct tag_entry *tag_list);
+
+/* cleans up a list: removes empty tags and duplicates */
+void taglist_cleanup(struct tag_entry **tag_list);
+
+void taglist_init_global();
+void taglist_free(struct tag_entry *tag_list);
+struct tag_entry *taglist_copy(struct tag_entry *s);
+bool taglist_contains(struct tag_entry *tag_list, const char *tag);
+void taglist_merge(struct tag_entry **dst, struct tag_entry *src1, struct tag_entry *src2);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/core/uemis-downloader.c b/core/uemis-downloader.c
index 593b94617..83f37545b 100644
--- a/core/uemis-downloader.c
+++ b/core/uemis-downloader.c
@@ -26,6 +26,7 @@
#include "uemis.h"
#include "divelist.h"
#include "divesite.h"
+#include "tag.h"
#include "core/subsurface-string.h"
#define ERR_FS_ALMOST_FULL QT_TRANSLATE_NOOP("gettextFromC", "Uemis Zurich: the file system is almost full.\nDisconnect/reconnect the dive computer\nand click \'Retry\'")
diff --git a/desktop-widgets/command_edit.cpp b/desktop-widgets/command_edit.cpp
index 2c9d56ba5..89b864f60 100644
--- a/desktop-widgets/command_edit.cpp
+++ b/desktop-widgets/command_edit.cpp
@@ -5,6 +5,7 @@
#include "core/divelist.h"
#include "core/qthelper.h" // for copy_qstring
#include "core/subsurface-string.h"
+#include "core/tag.h"
#include "desktop-widgets/mapwidget.h" // TODO: Replace desktop-dependency by signal
namespace Command {
diff --git a/desktop-widgets/divelogexportdialog.cpp b/desktop-widgets/divelogexportdialog.cpp
index 4fef543a0..1059d9bd0 100644
--- a/desktop-widgets/divelogexportdialog.cpp
+++ b/desktop-widgets/divelogexportdialog.cpp
@@ -17,6 +17,7 @@
#include "profile-widget/profilewidget2.h"
#include "core/save-profiledata.h"
#include "core/divesite.h"
+#include "core/tag.h"
// Retrieves the current unit settings defined in the Subsurface preferences.
#define GET_UNIT(name, field, f, t) \
diff --git a/desktop-widgets/simplewidgets.cpp b/desktop-widgets/simplewidgets.cpp
index 660a8a433..acb66c0c5 100644
--- a/desktop-widgets/simplewidgets.cpp
+++ b/desktop-widgets/simplewidgets.cpp
@@ -22,6 +22,7 @@
#include "profile-widget/profilewidget2.h"
#include "desktop-widgets/command.h"
#include "core/metadata.h"
+#include "core/tag.h"
double MinMaxAvgWidget::average() const
{
diff --git a/packaging/ios/Subsurface-mobile.pro b/packaging/ios/Subsurface-mobile.pro
index ea6a378ea..a0caf4886 100644
--- a/packaging/ios/Subsurface-mobile.pro
+++ b/packaging/ios/Subsurface-mobile.pro
@@ -70,6 +70,7 @@ SOURCES += ../../subsurface-mobile-main.cpp \
../../core/membuffer.c \
../../core/sha1.c \
../../core/strtod.c \
+ ../../core/tag.c \
../../core/taxonomy.c \
../../core/time.c \
../../core/uemis.c \
diff --git a/qt-models/completionmodels.cpp b/qt-models/completionmodels.cpp
index b4e166de3..957b8c7fd 100644
--- a/qt-models/completionmodels.cpp
+++ b/qt-models/completionmodels.cpp
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include "qt-models/completionmodels.h"
#include "core/dive.h"
+#include "core/tag.h"
#include <QSet>
#include <QString>
diff --git a/qt-models/divetripmodel.cpp b/qt-models/divetripmodel.cpp
index 59069164f..7795b3563 100644
--- a/qt-models/divetripmodel.cpp
+++ b/qt-models/divetripmodel.cpp
@@ -6,6 +6,7 @@
#include "core/divelist.h"
#include "core/qthelper.h"
#include "core/subsurface-string.h"
+#include "core/tag.h"
#include <QIcon>
#include <QDebug>
#include <memory>
diff --git a/smtk-import/smartrak.c b/smtk-import/smartrak.c
index 31c7e4dc3..64c6d8e5f 100644
--- a/smtk-import/smartrak.c
+++ b/smtk-import/smartrak.c
@@ -33,6 +33,7 @@
#include "core/libdivecomputer.h"
#include "core/divesite.h"
#include "core/membuffer.h"
+#include "core/tag.h"
/* SmartTrak version, constant for every single file */
int smtk_version;
diff --git a/subsurface-desktop-main.cpp b/subsurface-desktop-main.cpp
index cec1a159b..7922b01e1 100644
--- a/subsurface-desktop-main.cpp
+++ b/subsurface-desktop-main.cpp
@@ -12,6 +12,7 @@
#include "core/qthelper.h"
#include "core/subsurfacestartup.h"
#include "core/settings/qPref.h"
+#include "core/tag.h"
#include "desktop-widgets/diveplanner.h"
#include "desktop-widgets/mainwindow.h"
#include "desktop-widgets/preferences/preferencesdialog.h"
diff --git a/subsurface-mobile-main.cpp b/subsurface-mobile-main.cpp
index a9e4b424d..93c81f5a1 100644
--- a/subsurface-mobile-main.cpp
+++ b/subsurface-mobile-main.cpp
@@ -13,6 +13,7 @@
#include "core/subsurfacestartup.h"
#include "core/settings/qPref.h"
#include "core/settings/qPrefDisplay.h"
+#include "core/tag.h"
#include <QApplication>
#include <QLocale>
diff --git a/tests/testtaglist.cpp b/tests/testtaglist.cpp
index afe5049f1..cd7e59c43 100644
--- a/tests/testtaglist.cpp
+++ b/tests/testtaglist.cpp
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include "testtaglist.h"
#include "core/dive.h"
+#include "core/tag.h"
void TestTagList::initTestCase()
{