core: port tag-list to C++

Also adds a new test, which tests merging of two tag-lists.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Berthold Stoeger 2024-05-29 17:57:48 +02:00 committed by bstoeger
parent 640ecb345b
commit f18acf6fb9
25 changed files with 195 additions and 227 deletions

View file

@ -187,7 +187,7 @@ void export_TeX(const char *filename, bool selected_only, bool plain, ExportCall
dive->maxdepth.mm ? put_format(&buf, "\\def\\%smaximumdepth{%.1f\\%sdepthunit}\n", ssrf, get_depth_units(dive->maxdepth.mm, NULL, &unit), ssrf) : put_format(&buf, "\\def\\%smaximumdepth{}\n", ssrf);
dive->meandepth.mm ? put_format(&buf, "\\def\\%smeandepth{%.1f\\%sdepthunit}\n", ssrf, get_depth_units(dive->meandepth.mm, NULL, &unit), ssrf) : put_format(&buf, "\\def\\%smeandepth{}\n", ssrf);
std::string tags = taglist_get_tagstring(dive->tag_list);
std::string tags = taglist_get_tagstring(dive->tags);
put_format(&buf, "\\def\\%stype{%s}\n", ssrf, tags.c_str());
put_format(&buf, "\\def\\%sviz{%s}\n", ssrf, qPrintable(viz));
put_format(&buf, "\\def\\%srating{%s}\n", ssrf, qPrintable(rating));

View file

@ -564,18 +564,17 @@ void EditTagsBase::redo()
QStringList EditTags::data(struct dive *d) const
{
QStringList res;
for (const struct tag_entry *tag = d->tag_list; tag; tag = tag->next)
res.push_back(QString::fromStdString(tag->tag->name));
for (const divetag *tag: d->tags)
res.push_back(QString::fromStdString(tag->name));
return res;
}
void EditTags::set(struct dive *d, const QStringList &v) const
{
taglist_free(d->tag_list);
d->tag_list = NULL;
d->tags.clear();
for (const QString &tag: v)
taglist_add_tag(&d->tag_list, qPrintable(tag));
taglist_cleanup(&d->tag_list);
taglist_add_tag(d->tags, tag.toStdString());
taglist_cleanup(d->tags);
}
QString EditTags::fieldName() const
@ -627,8 +626,7 @@ static void swapCandQString(QString &q, char *&c)
q = std::move(tmp);
}
PasteState::PasteState(dive *dIn, const dive *data, dive_components what) : d(dIn),
tags(nullptr)
PasteState::PasteState(dive *dIn, const dive *data, dive_components what) : d(dIn)
{
if (what.notes)
notes = data->notes;
@ -653,7 +651,7 @@ PasteState::PasteState(dive *dIn, const dive *data, dive_components what) : d(dI
if (what.divesite)
divesite = data->dive_site;
if (what.tags)
tags = taglist_copy(data->tag_list);
tags = data->tags;
if (what.cylinders) {
cylinders = data->cylinders;
// Paste cylinders is "special":
@ -695,7 +693,6 @@ PasteState::PasteState(dive *dIn, const dive *data, dive_components what) : d(dI
PasteState::~PasteState()
{
taglist_free(tags);
}
void PasteState::swap(dive_components what)
@ -723,7 +720,7 @@ void PasteState::swap(dive_components what)
if (what.divesite)
std::swap(divesite, d->dive_site);
if (what.tags)
std::swap(tags, d->tag_list);
std::swap(tags, d->tags);
if (what.cylinders)
std::swap(cylinders, d->cylinders);
if (what.weights)
@ -1397,7 +1394,7 @@ EditDive::EditDive(dive *oldDiveIn, dive *newDiveIn, dive_site *createDs, dive_s
changedFields |= DiveField::CHILL;
if (!same_string(oldDive->suit, newDive->suit))
changedFields |= DiveField::SUIT;
if (taglist_get_tagstring(oldDive->tag_list) != taglist_get_tagstring(newDive->tag_list)) // This is cheating. Do we have a taglist comparison function?
if (taglist_get_tagstring(oldDive->tags) != taglist_get_tagstring(newDive->tags)) // This is cheating. Do we have a taglist comparison function?
changedFields |= DiveField::TAGS;
if (oldDive->dcs[0].divemode != newDive->dcs[0].divemode)
changedFields |= DiveField::MODE;

View file

@ -299,7 +299,7 @@ struct PasteState {
int current;
int surge;
int chill;
tag_entry *tags;
tag_list tags;
cylinder_table cylinders;
weightsystem_table weightsystems;
int number;

View file

@ -265,26 +265,26 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
* Weather, values table, 0 to 6
* Subsurface don't have this record but we can use tags
*/
dt_dive->tag_list = NULL;
dt_dive->tags.clear();
read_bytes(1);
switch (tmp_1byte) {
case 1:
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "clear")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "clear"));
break;
case 2:
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "misty")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "misty"));
break;
case 3:
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "fog")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "fog"));
break;
case 4:
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "rain")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "rain"));
break;
case 5:
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "storm")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "storm"));
break;
case 6:
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "snow")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "snow"));
break;
default:
// unknown, do nothing
@ -304,22 +304,22 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
read_bytes(1);
switch (tmp_1byte) {
case 1:
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "No suit"));
dt_dive->suit = strdup(translate("gettextFromC", "No suit"));
break;
case 2:
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Shorty"));
dt_dive->suit = strdup(translate("gettextFromC", "Shorty"));
break;
case 3:
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Combi"));
dt_dive->suit = strdup(translate("gettextFromC", "Combi"));
break;
case 4:
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Wet suit"));
dt_dive->suit = strdup(translate("gettextFromC", "Wet suit"));
break;
case 5:
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Semidry suit"));
dt_dive->suit = strdup(translate("gettextFromC", "Semidry suit"));
break;
case 6:
dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Dry suit"));
dt_dive->suit = strdup(translate("gettextFromC", "Dry suit"));
break;
default:
// unknown, do nothing
@ -380,28 +380,28 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
*/
read_bytes(1);
if (bit_set(tmp_1byte, 2))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "no stop")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "no stop"));
if (bit_set(tmp_1byte, 3))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "deco")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "deco"));
if (bit_set(tmp_1byte, 4))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "single ascent")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "single ascent"));
if (bit_set(tmp_1byte, 5))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "multiple ascent")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "multiple ascent"));
if (bit_set(tmp_1byte, 6))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "fresh water")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "fresh water"));
if (bit_set(tmp_1byte, 7))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "salt water")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "salt water"));
/*
* Dive Type 2 - Bit table, use tags again
*/
read_bytes(1);
if (bit_set(tmp_1byte, 0)) {
taglist_add_tag(&dt_dive->tag_list, strdup("nitrox"));
taglist_add_tag(dt_dive->tags, "nitrox");
is_nitrox = 1;
}
if (bit_set(tmp_1byte, 1)) {
taglist_add_tag(&dt_dive->tag_list, strdup("rebreather"));
taglist_add_tag(dt_dive->tags, "rebreather");
is_SCR = 1;
dt_dive->dcs[0].divemode = PSCR;
}
@ -411,36 +411,36 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
*/
read_bytes(1);
if (bit_set(tmp_1byte, 0))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "sight seeing")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "sight seeing"));
if (bit_set(tmp_1byte, 1))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "club dive")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "club dive"));
if (bit_set(tmp_1byte, 2))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "instructor")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "instructor"));
if (bit_set(tmp_1byte, 3))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "instruction")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "instruction"));
if (bit_set(tmp_1byte, 4))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "night")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "night"));
if (bit_set(tmp_1byte, 5))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "cave")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "cave"));
if (bit_set(tmp_1byte, 6))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "ice")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "ice"));
if (bit_set(tmp_1byte, 7))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "search")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "search"));
/*
* Dive Activity 2 - Bit table, use tags again
*/
read_bytes(1);
if (bit_set(tmp_1byte, 0))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "wreck")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "wreck"));
if (bit_set(tmp_1byte, 1))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "river")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "river"));
if (bit_set(tmp_1byte, 2))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "drift")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "drift"));
if (bit_set(tmp_1byte, 3))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "photo")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "photo"));
if (bit_set(tmp_1byte, 4))
taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "other")));
taglist_add_tag(dt_dive->tags, translate("gettextFromC", "other"));
/*
* Other activities - String 1st byte = long
@ -450,7 +450,7 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
if (tmp_1byte != 0) {
read_string(tmp_string1);
snprintf(buffer, sizeof(buffer), "%s: %s\n",
QT_TRANSLATE_NOOP("gettextFromC", "Other activities"),
translate("gettextFromC", "Other activities"),
tmp_string1);
tmp_notes_str = strdup(buffer);
free(tmp_string1);
@ -474,7 +474,7 @@ static char *dt_dive_parser(unsigned char *runner, struct dive *dt_dive, struct
read_string(tmp_string1);
int len = snprintf(buffer, sizeof(buffer), "%s%s:\n%s",
tmp_notes_str ? tmp_notes_str : "",
QT_TRANSLATE_NOOP("gettextFromC", "Datatrak/Wlog notes"),
translate("gettextFromC", "Datatrak/Wlog notes"),
tmp_string1);
dt_dive->notes = (char *)calloc((len +1), 1);
memcpy(dt_dive->notes, buffer, len);
@ -630,7 +630,7 @@ static void wlog_compl_parser(std::string &wl_mem, struct dive *dt_dive, int dco
*/
tmp = (int) two_bytes_to_int(runner[pos_weight + 1], runner[pos_weight]);
if (tmp != 0x7fff) {
weightsystem_t ws = { {tmp * 10}, QT_TRANSLATE_NOOP("gettextFromC", "unknown"), false };
weightsystem_t ws = { {tmp * 10}, translate("gettextFromC", "unknown"), false };
dt_dive->weightsystems.push_back(std::move(ws));
}

View file

@ -174,7 +174,7 @@ static void free_dive_structures(struct dive *d)
free(d->notes);
free(d->suit);
/* free tags, additional dive computers, and pictures */
taglist_free(d->tag_list);
d->tags.clear();
d->cylinders.clear();
d->weightsystems.clear();
clear_picture_table(&d->pictures);
@ -211,7 +211,6 @@ void copy_dive(const struct dive *s, struct dive *d)
d->notes = copy_string(s->notes);
d->suit = copy_string(s->suit);
copy_pictures(&s->pictures, &d->pictures);
d->tag_list = taglist_copy(s->tag_list);
}
static void copy_dive_onedc(const struct dive *s, const struct divecomputer &sdc, struct dive *d)
@ -253,7 +252,7 @@ void selective_copy_dive(const struct dive *s, struct dive *d, struct dive_compo
s->dive_site->add_dive(d);
}
if (what.tags)
d->tag_list = taglist_copy(s->tag_list);
d->tags = s->tags;
if (what.cylinders)
copy_cylinder_types(s, d);
if (what.weights)
@ -2349,7 +2348,7 @@ struct dive *merge_dives(const struct dive *a, const struct dive *b, int offset,
MERGE_NONZERO(res, a, b, surge);
MERGE_NONZERO(res, a, b, chill);
copy_pictures(a->pictures.nr ? &a->pictures : &b->pictures, &res->pictures);
taglist_merge(&res->tag_list, a->tag_list, b->tag_list);
res->tags = taglist_merge(a->tags, b->tags);
/* if we get dives without any gas / cylinder information in an import, make sure
* that there is at leatst one entry in the cylinder map for that dive */
auto cylinders_map_a = std::make_unique<int[]>(std::max(size_t(1), a->cylinders.size()));

View file

@ -8,6 +8,7 @@
#include "divecomputer.h"
#include "equipment.h"
#include "picture.h" // TODO: remove
#include "tag.h"
#include <string>
#include <vector>
@ -46,7 +47,7 @@ struct dive {
int salinity = 0; // kg per 10000 l
int user_salinity = 0; // water density reflecting a user-specified type
struct tag_entry *tag_list = nullptr;
tag_list tags;
std::vector<divecomputer> dcs; // Attn: pointers to divecomputers are not stable!
int id = 0; // unique ID for this dive
struct picture_table pictures = { };

View file

@ -818,8 +818,8 @@ static bool check(const filter_constraint &c, const QStringList &list)
static bool has_tags(const filter_constraint &c, const struct dive *d)
{
QStringList dive_tags;
for (const tag_entry *tag = d->tag_list; tag; tag = tag->next)
dive_tags.push_back(QString::fromStdString(tag->tag->name).trimmed());
for (const divetag *tag: d->tags)
dive_tags.push_back(QString::fromStdString(tag->name).trimmed());
return check(c, dive_tags);
}

View file

@ -123,8 +123,8 @@ static std::vector<QString> getWords(const dive *d)
tokenize(QString(d->diveguide), res);
tokenize(QString(d->buddy), res);
tokenize(QString(d->suit), res);
for (const tag_entry *tag = d->tag_list; tag; tag = tag->next)
tokenize(QString::fromStdString(tag->tag->name), res);
for (const divetag *tag: d->tags)
tokenize(QString::fromStdString(tag->name), res);
for (auto &cyl: d->cylinders)
tokenize(QString::fromStdString(cyl.type.description), res);
for (auto &ws: d->weightsystems)

View file

@ -155,7 +155,7 @@ static int dm4_tags(void *param, int, char **data, char **)
struct parser_state *state = (struct parser_state *)param;
if (data[0])
taglist_add_tag(&state->cur_dive->tag_list, data[0]);
taglist_add_tag(state->cur_dive->tags, data[0]);
return 0;
}

View file

@ -261,7 +261,7 @@ static void parse_dive_tags(char *, struct git_parser_state *state)
{
for (const std::string &tag: state->converted_strings) {
if (!tag.empty())
taglist_add_tag(&state->active_dive->tag_list, tag.c_str());
taglist_add_tag(state->active_dive->tags, tag.c_str());
}
}

View file

@ -101,7 +101,7 @@ enum ParseState {
FINDSTART,
FINDEND
};
static void divetags(const char *buffer, struct tag_entry **tags)
static void divetags(const char *buffer, tag_list *tags)
{
int i = 0, start = 0, end = 0;
enum ParseState state = FINDEND;
@ -116,7 +116,7 @@ static void divetags(const char *buffer, struct tag_entry **tags)
if (i > 0 && buffer[i - 1] != '\\') {
std::string s(buffer + start, i - start);
state = FINDSTART;
taglist_add_tag(tags, s.c_str());
taglist_add_tag(*tags, s.c_str());
} else {
state = FINDSTART;
}
@ -139,7 +139,7 @@ static void divetags(const char *buffer, struct tag_entry **tags)
end = len - 1;
if (len > 0) {
std::string s(buffer + start, i - start);
taglist_add_tag(tags, buffer + start);
taglist_add_tag(*tags, buffer + start);
}
}
}
@ -1254,7 +1254,7 @@ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf, str
return;
if (MATCH("number", get_index, &dive->number))
return;
if (MATCH("tags", divetags, &dive->tag_list))
if (MATCH("tags", divetags, &dive->tags))
return;
if (MATCH("tripflag", get_notrip, &dive->notrip))
return;

View file

@ -104,17 +104,16 @@ static void save_overview(struct membuffer *b, struct dive *dive)
show_utf8(b, "notes ", dive->notes, "\n");
}
static void save_tags(struct membuffer *b, struct tag_entry *tags)
static void save_tags(struct membuffer *b, const tag_list &tags)
{
const char *sep = " ";
if (!tags)
if (tags.empty())
return;
put_string(b, "tags");
while (tags) {
show_utf8(b, sep, tags->tag->source.empty() ? tags->tag->name.c_str() : tags->tag->source.c_str(), "");
for (const divetag *tag: tags) {
show_utf8(b, sep, tag->source.empty() ? tag->name.c_str() : tag->source.c_str(), "");
sep = ", ";
tags = tags->next;
}
put_string(b, "\n");
}
@ -449,7 +448,7 @@ static void create_dive_buffer(struct dive *dive, struct membuffer *b)
SAVE("airpressure", surface_pressure.mbar);
cond_put_format(dive->notrip, b, "notrip\n");
cond_put_format(dive->invalid, b, "invalid\n");
save_tags(b, dive->tag_list);
save_tags(b, dive->tags);
if (dive->dive_site)
put_format(b, "divesiteid %08x\n", dive->dive_site->uuid);
if (verbose && dive->dive_site)

View file

@ -311,18 +311,16 @@ void put_HTML_watertemp(struct membuffer *b, const struct dive *dive, const char
static void put_HTML_tags(struct membuffer *b, const struct dive *dive, const char *pre, const char *post)
{
put_string(b, pre);
struct tag_entry *tag = dive->tag_list;
if (!tag)
if (dive->tags.empty())
put_string(b, "[\"--\"");
const char *separator = "[";
while (tag) {
for (const divetag *tag: dive->tags) {
put_format(b, "%s\"", separator);
separator = ", ";
put_HTML_quoted(b, tag->tag->name.c_str());
put_HTML_quoted(b, tag->name.c_str());
put_string(b, "\"");
tag = tag->next;
}
put_string(b, "]");
put_string(b, post);

View file

@ -367,17 +367,16 @@ static void save_events(struct membuffer *b, struct dive *dive, const struct div
save_one_event(b, dive, ev);
}
static void save_tags(struct membuffer *b, struct tag_entry *entry)
static void save_tags(struct membuffer *b, const tag_list &tags)
{
if (entry) {
if (!tags.empty()) {
const char *sep = " tags='";
do {
const struct divetag *tag = entry->tag;
for (const divetag *tag: tags) {
put_string(b, sep);
/* If the tag has been translated, write the source to the xml file */
quote(b, tag->source.empty() ? tag->name.c_str() : tag->source.c_str(), 1);
sep = ", ";
} while ((entry = entry->next) != NULL);
}
put_string(b, "'");
}
}
@ -509,7 +508,7 @@ void save_one_dive_to_mb(struct membuffer *b, struct dive *dive, bool anonymize)
if (dive->maxcns)
put_format(b, " cns='%d%%'", dive->maxcns);
save_tags(b, dive->tag_list);
save_tags(b, dive->tags);
if (dive->dive_site)
put_format(b, " divesiteid='%8x'", dive->dive_site->uuid);
if (dive->user_salinity)

View file

@ -22,124 +22,87 @@ static const char *default_tags[] = {
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)
divetag::divetag(std::string name, std::string source) :
name(std::move(name)), source(std::move(source))
{
dt->tag = st->tag;
}
static bool tag_seen_before(struct tag_entry *start, struct tag_entry *before)
/* remove duplicates and empty tags */
void taglist_cleanup(tag_list &list)
{
while (start && start != before) {
if (start->tag->name == before->tag->name)
return true;
start = start->next;
}
return false;
// Remove empty tags
list.erase(std::remove_if(list.begin(), list.end(), [](const divetag *tag) { return tag->name.empty(); }),
list.end());
// Sort (should be a NOP, because we add in a sorted way, but let's make sure)
std::sort(list.begin(), list.end());
// Remove duplicates
list.erase(std::unique(list.begin(), list.end(),
[](const divetag *tag1, const divetag *tag2) { return tag1->name == tag2->name; }),
list.end());
}
/* remove duplicates and empty nodes */
void taglist_cleanup(struct tag_entry **tag_list)
std::string taglist_get_tagstring(const tag_list &list)
{
struct tag_entry **tl = tag_list;
while (*tl) {
/* skip tags that are empty or that we have seen before */
if ((*tl)->tag->name.empty() || tag_seen_before(*tag_list, *tl)) {
*tl = (*tl)->next;
continue;
}
tl = &(*tl)->next;
}
}
std::string taglist_get_tagstring(struct tag_entry *tag_list)
{
bool first_tag = true;
std::string res;
for (struct tag_entry *tmp = tag_list; tmp != NULL; tmp = tmp->next) {
if (tmp->tag->name.empty())
for (const divetag *tag: list) {
if (tag->name.empty())
continue;
if (!first_tag)
if (!res.empty())
res += ", ";
res += tmp->tag->name;
first_tag = false;
res += tag->name;
}
return res;
}
/* Add a tag to the tag_list, keep the list sorted */
static void taglist_add_divetag(struct tag_entry **tag_list, const struct divetag *tag)
static void taglist_add_divetag(tag_list &list, const struct divetag *tag)
{
struct tag_entry *next, *entry;
while ((next = *tag_list) != NULL) {
int cmp = next->tag->name.compare(tag->name);
/* Already have it? */
if (!cmp)
return;
/* 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 = (tag_entry *)malloc(sizeof(struct tag_entry));
entry->next = next;
entry->tag = tag;
*tag_list = entry;
// Use binary search to enter at sorted position
auto it = std::lower_bound(list.begin(), list.end(), tag,
[](const struct divetag *tag1, const struct divetag *tag2)
{ return tag1->name < tag2->name; });
// Don't add if it already exists
if (it == list.end() || (*it)->name != tag->name)
list.insert(it, tag);
}
static const divetag *register_tag(const char *s, const char *source)
static const divetag *register_tag(std::string s, std::string source)
{
// binary search
auto it = std::lower_bound(g_tag_list.begin(), g_tag_list.end(), s,
[](const std::unique_ptr<divetag> &tag, const char *s)
[](const std::unique_ptr<divetag> &tag, const std::string &s)
{ return tag->name < s; });
if (it == g_tag_list.end() || (*it)->name != s) {
std::string source_s = empty_string(source) ? std::string() : std::string(source);
it = g_tag_list.insert(it, std::make_unique<divetag>(s, source));
}
if (it == g_tag_list.end() || (*it)->name != s)
it = g_tag_list.insert(it, std::make_unique<divetag>(std::move(s), std::move(source)));
return it->get();
}
void taglist_add_tag(struct tag_entry **tag_list, const char *tag)
void taglist_add_tag(tag_list &list, const std::string &tag)
{
bool is_default_tag = std::find_if(std::begin(default_tags), std::end(default_tags),
[&tag] (const char *default_tag) { return tag == default_tag; });
/* Only translate default tags */
/* TODO: Do we really want to translate user-supplied tags if they happen to be known!? */
const char *translation = is_default_tag ? translate("gettextFromC", tag) : tag;
const char *source = is_default_tag ? tag : nullptr;
const struct divetag *d_tag = register_tag(translation, source);
std::string translation = is_default_tag ? translate("gettextFromC", tag.c_str()) : tag;
std::string source = is_default_tag ? tag : std::string();
const struct divetag *d_tag = register_tag(std::move(translation), std::move(source));
taglist_add_divetag(tag_list, d_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;
taglist_add_divetag(list, d_tag);
}
/* Merge src1 and src2, write to *dst */
void taglist_merge(struct tag_entry **dst, struct tag_entry *src1, struct tag_entry *src2)
tag_list taglist_merge(const tag_list &src1, const tag_list &src2)
{
struct tag_entry *entry;
tag_list dst;
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);
for (const divetag *t: src1)
taglist_add_divetag(dst, t);
for (const divetag *t: src2)
taglist_add_divetag(dst, t);
return dst;
}
void taglist_init_global()

View file

@ -19,25 +19,18 @@ struct divetag {
* This enables us to write a non-localized tag to the xml file.
*/
std::string source;
divetag(const char *n, const char *s) : name(n), source(s)
{
}
divetag(std::string name, std::string source);
};
struct tag_entry {
const struct divetag *tag;
struct tag_entry *next;
};
using tag_list = std::vector<const divetag *>;
void taglist_add_tag(struct tag_entry **tag_list, const char *tag);
void taglist_add_tag(tag_list &list, const std::string &tag);
/* cleans up a list: removes empty tags and duplicates */
void taglist_cleanup(struct tag_entry **tag_list);
void taglist_cleanup(tag_list &list);
void taglist_init_global();
void taglist_free(struct tag_entry *tag_list);
struct tag_entry *taglist_copy(struct tag_entry *s);
void taglist_merge(struct tag_entry **dst, struct tag_entry *src1, struct tag_entry *src2);
tag_list taglist_merge(const tag_list &src1, const tag_list &src2);
/*
* divetags are only stored once, each dive only contains
@ -46,14 +39,7 @@ void taglist_merge(struct tag_entry **dst, struct tag_entry *src1, struct tag_en
*/
extern std::vector<std::unique_ptr<divetag>> g_tag_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
*/
extern std::string taglist_get_tagstring(struct tag_entry *tag_list);
/* Comma separated list of tags names or null terminated string */
std::string taglist_get_tagstring(struct tag_entry *tag_list);
/* Comma separated list of tags names or empty string */
std::string taglist_get_tagstring(const tag_list &tags);
#endif

View file

@ -341,11 +341,8 @@ void DiveComponentSelection::buttonClicked(QAbstractButton *button)
text << tr("Suit: ") << current_dive->suit << "\n";
if (what-> tags) {
text << tr("Tags: ");
tag_entry *entry = current_dive->tag_list;
while (entry) {
text << entry->tag->name.c_str() << " ";
entry = entry->next;
}
for (const divetag *tag: current_dive->tags)
text << tag->name.c_str() << " ";
text << "\n";
}
if (what->cylinders) {

View file

@ -118,7 +118,7 @@ void TabDiveNotes::divesChanged(const QVector<dive *> &dives, DiveField field)
if (field.divesite)
updateDiveSite(currentDive);
if (field.tags)
ui.tagWidget->setText(QString::fromStdString(taglist_get_tagstring(currentDive->tag_list)));
ui.tagWidget->setText(QString::fromStdString(taglist_get_tagstring(currentDive->tags)));
if (field.buddy)
ui.buddy->setText(currentDive->buddy);
if (field.diveguide)
@ -253,7 +253,7 @@ void TabDiveNotes::updateData(const std::vector<dive *> &, dive *currentDive, in
// reset labels in case we last displayed trip notes
ui.LocationLabel->setText(tr("Location"));
ui.NotesLabel->setText(tr("Notes"));
ui.tagWidget->setText(QString::fromStdString(taglist_get_tagstring(currentDive->tag_list)));
ui.tagWidget->setText(QString::fromStdString(taglist_get_tagstring(currentDive->tags)));
bool isManual = is_dc_manually_added_dive(&currentDive->dcs[0]);
ui.depth->setVisible(isManual);
ui.depthLabel->setVisible(isManual);

View file

@ -552,7 +552,7 @@ QVariant TemplateLayout::getValue(QString list, QString property, const State &s
} else if (property == "notes") {
return formatNotes(d);
} else if (property == "tags") {
return QString::fromStdString(taglist_get_tagstring(d->tag_list));
return QString::fromStdString(taglist_get_tagstring(d->tags));
} else if (property == "gas") {
return formatGas(d);
} else if (property == "sac") {

View file

@ -1324,7 +1324,7 @@ void QMLManager::commitChanges(QString diveId, QString number, QString date, QSt
}
// normalize the tag list we have and the one we get from the UI
// try hard to deal with accidental white space issues
QStringList existingTagList = QString::fromStdString(taglist_get_tagstring(d->tag_list)).split(",", SKIP_EMPTY);
QStringList existingTagList = QString::fromStdString(taglist_get_tagstring(d->tags)).split(",", SKIP_EMPTY);
QStringList newTagList = tags.split(",", SKIP_EMPTY);
QStringList newCleanTagList;
for (QString s: newTagList) {
@ -1335,10 +1335,9 @@ void QMLManager::commitChanges(QString diveId, QString number, QString date, QSt
existingTagList.sort();
if (newCleanTagList.join(",") != existingTagList.join(",")) {
diveChanged = true;
taglist_free(d->tag_list);
d->tag_list = nullptr;
d->tags.clear();
for (QString tag: newCleanTagList)
taglist_add_tag(&d->tag_list, qPrintable(tag));
taglist_add_tag(d->tags, qPrintable(tag));
}
if (d->rating != rating) {
diveChanged = true;

View file

@ -294,7 +294,7 @@ QVariant DiveTripModelBase::diveData(const struct dive *d, int column, int role)
case MobileListModel::SumWeightRole: return formatSumWeight(d);
case MobileListModel::DiveGuideRole: return QString(d->diveguide);
case MobileListModel::BuddyRole: return QString(d->buddy);
case MobileListModel::TagsRole: return QString::fromStdString(taglist_get_tagstring(d->tag_list));
case MobileListModel::TagsRole: return QString::fromStdString(taglist_get_tagstring(d->tags));
case MobileListModel::NotesRole: return formatNotes(d);
case MobileListModel::GpsRole: return formatDiveGPS(d);
case MobileListModel::GpsDecimalRole: return format_gps_decimal(d);
@ -347,7 +347,7 @@ QVariant DiveTripModelBase::diveData(const struct dive *d, int column, int role)
else
return d->maxcns;
case TAGS:
return QString::fromStdString(taglist_get_tagstring(d->tag_list));
return QString::fromStdString(taglist_get_tagstring(d->tags));
case PHOTOS:
break;
case COUNTRY:
@ -1767,8 +1767,8 @@ bool DiveTripModelList::lessThan(const QModelIndex &i1, const QModelIndex &i2) c
case MAXCNS:
return lessThanHelper(d1->maxcns - d2->maxcns, row_diff);
case TAGS: {
std::string s1 = taglist_get_tagstring(d1->tag_list);
std::string s2 = taglist_get_tagstring(d2->tag_list);
std::string s1 = taglist_get_tagstring(d1->tags);
std::string s2 = taglist_get_tagstring(d2->tags);
int diff = strCmp(s1, s2);
return lessThanHelper(diff, row_diff);
}

View file

@ -691,7 +691,7 @@ static void smtk_parse_relations(MdbHandle *mdb, struct dive *dive, char *dive_i
if (str.empty())
continue;
if (tag)
taglist_add_tag(&dive->tag_list, str.c_str());
taglist_add_tag(dive->tags, str);
else
concat(tmp, ", ", str);
if (str.find("SCR") != std::string::npos)
@ -717,7 +717,7 @@ static void smtk_parse_other(struct dive *dive, const std::vector<std::string> &
const std::string &str = list[i];
if (!str.empty()) {
if (tag)
taglist_add_tag(&dive->tag_list, str.c_str());
taglist_add_tag(dive->tags, str);
else
concat(&dive->notes, "\n", format_string_std("Smartrak %s: %s", data_name, str.c_str()));
}

View file

@ -1501,8 +1501,8 @@ struct DiveGuideVariable : public StatsVariableTemplate<StatsVariable::Type::Dis
struct TagBinner : public StringBinner<TagBinner, StringBin> {
std::vector<QString> to_bin_values(const dive *d) const {
std::vector<QString> tags;
for (const tag_entry *tag = d->tag_list; tag; tag = tag->next)
tags.push_back(QString::fromStdString(tag->tag->name).trimmed());
for (const divetag *tag: d->tags)
tags.push_back(QString::fromStdString(tag->name).trimmed());
return tags;
}
};
@ -1513,7 +1513,7 @@ struct TagVariable : public StatsVariableTemplate<StatsVariable::Type::Discrete>
return StatsTranslations::tr("Tags");
}
QString diveCategories(const dive *d) const override {
return QString::fromStdString(taglist_get_tagstring(d->tag_list));
return QString::fromStdString(taglist_get_tagstring(d->tags));
}
std::vector<const StatsBinner *> binners() const override {
return { &tag_binner };

View file

@ -15,29 +15,29 @@ void TestTagList::cleanupTestCase()
void TestTagList::testGetTagstringNoTags()
{
struct tag_entry *tag_list = NULL;
std::string tagstring = taglist_get_tagstring(tag_list);
tag_list tags;
std::string tagstring = taglist_get_tagstring(tags);
QVERIFY(tagstring.empty());
}
void TestTagList::testGetTagstringSingleTag()
{
struct tag_entry *tag_list = NULL;
taglist_add_tag(&tag_list, "A new tag");
std::string tagstring = taglist_get_tagstring(tag_list);
tag_list tags;
taglist_add_tag(tags, "A new tag");
std::string tagstring = taglist_get_tagstring(tags);
QCOMPARE(QString::fromStdString(tagstring), QString::fromUtf8("A new tag"));
}
void TestTagList::testGetTagstringMultipleTags()
{
struct tag_entry *tag_list = NULL;
taglist_add_tag(&tag_list, "A new tag");
taglist_add_tag(&tag_list, "A new tag 1");
taglist_add_tag(&tag_list, "A new tag 2");
taglist_add_tag(&tag_list, "A new tag 3");
taglist_add_tag(&tag_list, "A new tag 4");
taglist_add_tag(&tag_list, "A new tag 5");
std::string tagstring = taglist_get_tagstring(tag_list);
tag_list tags;
taglist_add_tag(tags, "A new tag");
taglist_add_tag(tags, "A new tag 1");
taglist_add_tag(tags, "A new tag 2");
taglist_add_tag(tags, "A new tag 3");
taglist_add_tag(tags, "A new tag 4");
taglist_add_tag(tags, "A new tag 5");
std::string tagstring = taglist_get_tagstring(tags);
QCOMPARE(QString::fromStdString(tagstring),
QString::fromUtf8(
"A new tag, "
@ -50,11 +50,11 @@ void TestTagList::testGetTagstringMultipleTags()
void TestTagList::testGetTagstringWithAnEmptyTag()
{
struct tag_entry *tag_list = NULL;
taglist_add_tag(&tag_list, "A new tag");
taglist_add_tag(&tag_list, "A new tag 1");
taglist_add_tag(&tag_list, "");
std::string tagstring = taglist_get_tagstring(tag_list);
tag_list tags;
taglist_add_tag(tags, "A new tag");
taglist_add_tag(tags, "A new tag 1");
taglist_add_tag(tags, "");
std::string tagstring = taglist_get_tagstring(tags);
QCOMPARE(QString::fromStdString(tagstring),
QString::fromUtf8(
"A new tag, "
@ -63,11 +63,40 @@ void TestTagList::testGetTagstringWithAnEmptyTag()
void TestTagList::testGetTagstringEmptyTagOnly()
{
struct tag_entry *tag_list = NULL;
taglist_add_tag(&tag_list, "");
std::string tagstring = taglist_get_tagstring(tag_list);
tag_list tags;
taglist_add_tag(tags, "");
std::string tagstring = taglist_get_tagstring(tags);
QCOMPARE(QString::fromStdString(tagstring),
QString::fromUtf8(""));
}
void TestTagList::testMergeTags()
{
tag_list tags1, tags2;
taglist_add_tag(tags1, "A new tag");
taglist_add_tag(tags1, "A new tag 6");
taglist_add_tag(tags1, "A new tag 1");
taglist_add_tag(tags1, "A new tag 2");
taglist_add_tag(tags1, "");
taglist_add_tag(tags1, "A new tag 2");
taglist_add_tag(tags1, "A new tag 3");
taglist_add_tag(tags1, "A new tag");
taglist_add_tag(tags2, "");
taglist_add_tag(tags2, "A new tag 1");
taglist_add_tag(tags2, "A new tag 4");
taglist_add_tag(tags2, "A new tag 2");
taglist_add_tag(tags2, "A new tag 5");
tag_list tags3 = taglist_merge(tags1, tags2);
std::string tagstring = taglist_get_tagstring(tags3);
QCOMPARE(QString::fromStdString(tagstring),
QString::fromUtf8(
"A new tag, "
"A new tag 1, "
"A new tag 2, "
"A new tag 3, "
"A new tag 4, "
"A new tag 5, "
"A new tag 6"));
}
QTEST_GUILESS_MAIN(TestTagList)

View file

@ -15,6 +15,7 @@ private slots:
void testGetTagstringMultipleTags();
void testGetTagstringWithAnEmptyTag();
void testGetTagstringEmptyTagOnly();
void testMergeTags();
};
#endif