From c95e1cfc2331f5e22dcae51ee637b70ce9973cb9 Mon Sep 17 00:00:00 2001 From: tsteven4 <13596209+tsteven4@users.noreply.github.com> Date: Fri, 27 Oct 2023 14:07:24 -0600 Subject: [PATCH] support optional start elements in xmlstreamwriter. (#1193) this adds methods to xmlstreamwriter to allow conditional writing of a start element. The conditional element will only be written to the stream if it has children, which is deteremined when the condtional element is ended. This simplifies writing of such elements as the writer does not need to know if it will subsequently write children or not. --- garmin_fs.cc | 148 ++++++++++++++---------------------- garmin_fs.h | 11 ++- gpx.cc | 144 +++++++++++++++++------------------ src/core/xmlstreamwriter.cc | 93 +++++++++++++++++++++- src/core/xmlstreamwriter.h | 81 +++++++++++++++++++- 5 files changed, 300 insertions(+), 177 deletions(-) diff --git a/garmin_fs.cc b/garmin_fs.cc index 1827fe120..c6846efce 100644 --- a/garmin_fs.cc +++ b/garmin_fs.cc @@ -20,17 +20,17 @@ */ -#include // for snprintf, sscanf -#include // for strtod +#include // for snprintf, sscanf +#include // for strtod -#include // for QString, QStringLiteral -#include // for QXmlStreamWriter -#include // for CaseInsensitive +#include // for QString, QStringLiteral +#include // for CaseInsensitive #include "defs.h" #include "garmin_fs.h" -#include "garmin_tables.h" // for gt_switch_display_mode_value, gt_display_mode_symbol, gt_display_mode_symbol_and_comment, gt_display_mode_symbol_and_name -#include "inifile.h" // for inifile_readstr +#include "garmin_tables.h" // for gt_switch_display_mode_value, gt_display_mode_symbol, gt_display_mode_symbol_and_comment, gt_display_mode_symbol_and_name +#include "inifile.h" // for inifile_readstr +#include "src/core/xmlstreamwriter.h" // for XmlStreamWriter #define MYNAME "garmin_fs" @@ -80,113 +80,75 @@ garmin_fs_t::~garmin_fs_t() void garmin_fs_xml_fprint(const Waypoint* waypt, - QXmlStreamWriter* writer) + gpsbabel::XmlStreamWriter* writer) { garmin_fs_t* gmsd = garmin_fs_t::find(waypt); - if (gmsd == nullptr) { - return; - } + writer->stackOptionalStartElement(QStringLiteral("extensions")); + writer->stackOptionalStartElement(QStringLiteral("gpxx:WaypointExtension")); + writer->stackNamespace(QStringLiteral("http://www.garmin.com/xmlschemas/GpxExtensions/v3"), + "gpxx"); - /* Find out if there is at least one field set */ - QString addr = garmin_fs_t::get_addr(gmsd, ""); - if (addr.isEmpty()) { - addr = garmin_fs_t::get_city(gmsd, ""); - } - if (addr.isEmpty()) { - addr = garmin_fs_t::get_country(gmsd, ""); + if (waypt->proximity_has_value()) { + writer->stackTextElement(QStringLiteral("gpxx:Proximity"), QString::number(waypt->proximity_value(), 'f', 6)); } - if (addr.isEmpty()) { - addr = garmin_fs_t::get_postal_code(gmsd, ""); + + if (waypt->temperature_has_value()) { + writer->stackTextElement(QStringLiteral("gpxx:Temperature"), QString::number(waypt->temperature_value(), 'f', 6)); } - if (addr.isEmpty()) { - addr = garmin_fs_t::get_state(gmsd, ""); + + if (waypt->depth_has_value()) { + writer->stackTextElement(QStringLiteral("gpxx:Depth"), QString::number(waypt->depth_value(), 'f', 6)); } - QString phone = garmin_fs_t::get_phone_nr(gmsd, ""); - - if (!addr.isEmpty() || !phone.isEmpty() || - (gmsd->flags.category && gmsd->category) || - waypt->depth_has_value() || - waypt->proximity_has_value() || - waypt->temperature_has_value() || - gmsd->flags.display) { - writer->writeStartElement(QStringLiteral("extensions")); - writer->writeStartElement(QStringLiteral("gpxx:WaypointExtension")); - writer->writeNamespace(QStringLiteral("http://www.garmin.com/xmlschemas/GpxExtensions/v3"), - "gpxx"); - if (waypt->proximity_has_value()) { - writer->writeTextElement(QStringLiteral("gpxx:Proximity"), QString::number(waypt->proximity_value(), 'f', 6)); - } - if (waypt->temperature_has_value()) { - writer->writeTextElement(QStringLiteral("gpxx:Temperature"), QString::number(waypt->temperature_value(), 'f', 6)); - } - if (waypt->depth_has_value()) { - writer->writeTextElement(QStringLiteral("gpxx:Depth"), QString::number(waypt->depth_value(), 'f', 6)); - } - if (gmsd->flags.display) { - const char* cx; - switch (gmsd->display) { - case gt_display_mode_symbol: - cx = "SymbolOnly"; - break; - case gt_display_mode_symbol_and_comment: - cx = "SymbolAndDescription"; - break; - default: - cx = "SymbolAndName"; - break; - } - writer->writeTextElement(QStringLiteral("gpxx:DisplayMode"), cx); + if (garmin_fs_t::has_display(gmsd)) { + const char* cx; + switch (gmsd->display) { + case gt_display_mode_symbol: + cx = "SymbolOnly"; + break; + case gt_display_mode_symbol_and_comment: + cx = "SymbolAndDescription"; + break; + default: + cx = "SymbolAndName"; + break; } - if (gmsd->flags.category && gmsd->category) { - uint16_t cx = gmsd->category; - writer->writeStartElement(QStringLiteral("gpxx:Categories")); - for (int i = 0; i < 16; i++) { - if (cx & 1) { - writer->writeTextElement(QStringLiteral("gpxx:Category"), QStringLiteral("Category %1").arg(i+1)); - } - cx = cx >> 1; - } - writer->writeEndElement(); // gpxx:Categories - } - if (!addr.isEmpty()) { - QString str; - writer->writeStartElement(QStringLiteral("gpxx:Address")); + writer->stackTextElement(QStringLiteral("gpxx:DisplayMode"), cx); + } - if (!(str = garmin_fs_t::get_addr(gmsd, nullptr)).isEmpty()) { - writer->writeTextElement(QStringLiteral("gpxx:StreetAddress"), str); - } - if (!(str = garmin_fs_t::get_city(gmsd, nullptr)).isEmpty()) { - writer->writeTextElement(QStringLiteral("gpxx:City"), str); - } - if (!(str = garmin_fs_t::get_state(gmsd, nullptr)).isEmpty()) { - writer->writeTextElement(QStringLiteral("gpxx:State"), str); - } - if (!(str = garmin_fs_t::get_country(gmsd, nullptr)).isEmpty()) { - writer->writeTextElement(QStringLiteral("gpxx:Country"), str); + if (garmin_fs_t::has_category(gmsd)) { + uint16_t cx = gmsd->category; + writer->stackStartElement(QStringLiteral("gpxx:Categories")); + for (int i = 0; i < 16; i++) { + if (cx & 1) { + writer->stackTextElement(QStringLiteral("gpxx:Category"), QStringLiteral("Category %1").arg(i+1)); } - if (!(str = garmin_fs_t::get_postal_code(gmsd, nullptr)).isEmpty()) { - writer->writeTextElement(QStringLiteral("gpxx:PostalCode"), str); - } - writer->writeEndElement(); // /gpxx::Address + cx = cx >> 1; } + writer->stackEndElement(); // gpxx:Categories + } - if (!phone.isEmpty()) { - writer->writeTextElement(QStringLiteral("gpxx:PhoneNumber"), phone); - } + writer->stackOptionalStartElement(QStringLiteral("gpxx:Address")); + writer->stackOptionalTextElement(QStringLiteral("gpxx:StreetAddress"), garmin_fs_t::get_addr(gmsd, nullptr)); + writer->stackOptionalTextElement(QStringLiteral("gpxx:City"), garmin_fs_t::get_city(gmsd, nullptr)); + writer->stackOptionalTextElement(QStringLiteral("gpxx:State"), garmin_fs_t::get_state(gmsd, nullptr)); + writer->stackOptionalTextElement(QStringLiteral("gpxx:Country"), garmin_fs_t::get_country(gmsd, nullptr)); + writer->stackOptionalTextElement(QStringLiteral("gpxx:PostalCode"), garmin_fs_t::get_postal_code(gmsd, nullptr)); + writer->stackEndElement(); // gpxx:Address + + writer->stackOptionalTextElement(QStringLiteral("gpxx:PhoneNumber"), garmin_fs_t::get_phone_nr(gmsd, nullptr)); - writer->writeEndElement(); // /gpxx::WaypointExtension - writer->writeEndElement(); // /extensions. - } + writer->stackEndElement(); // gpxx:WaypointExtension + writer->stackEndElement(); // extensions } void garmin_fs_xml_convert(const int base_tag, int tag, const QString& qstr, Waypoint* waypt) { // FIXME: eliminate C string copy/use here: - const char *cdatastr = xstrdup(qstr); + const char* cdatastr = xstrdup(qstr); garmin_fs_t* gmsd = garmin_fs_t::find(waypt); if (gmsd == nullptr) { gmsd = garmin_fs_alloc(-1); diff --git a/garmin_fs.h b/garmin_fs.h index 25d3bbb18..d225339dc 100644 --- a/garmin_fs.h +++ b/garmin_fs.h @@ -24,14 +24,13 @@ #ifndef GARMIN_FS_H #define GARMIN_FS_H -#include // for size_t -#include // for int32_t, int16_t, uint16_t +#include // for int32_t, int16_t, uint16_t -#include // for QString -#include // for QXmlStreamWriter +#include // for QString #include "defs.h" -#include "formspec.h" // for FsChainFind, kFsGmsd, FormatSpecificData +#include "formspec.h" // for FsChainFind, kFsGmsd, FormatSpecificData +#include "src/core/xmlstreamwriter.h" // for XmlStreamWriter /* this order is used by most devices */ @@ -222,7 +221,7 @@ void garmin_fs_copy(void** dest, const void* src); /* for GPX */ void garmin_fs_xml_convert(int base_tag, int tag, const QString& qstr, Waypoint* waypt); -void garmin_fs_xml_fprint(const Waypoint* waypt, QXmlStreamWriter*); +void garmin_fs_xml_fprint(const Waypoint* waypt, gpsbabel::XmlStreamWriter*); /* common garmin_fs utilities */ diff --git a/gpx.cc b/gpx.cc index 9a127c7c4..ad5bfcaaa 100644 --- a/gpx.cc +++ b/gpx.cc @@ -21,6 +21,7 @@ #include "gpx.h" +#include // for assert #include // for lround #include // for sscanf #include // for strchr, strncpy @@ -573,10 +574,10 @@ GpxFormat::gpx_end(QStringView /*unused*/) gc_log_date = gpsbabel::DateTime(); break; case tt_cache_favorite_points: - wpt_tmp->AllocGCData()->favorite_points = cdatastr.toInt(); + wpt_tmp->AllocGCData()->favorite_points = cdatastr.toInt(); break; case tt_cache_personal_note: - wpt_tmp->AllocGCData()->personal_note = cdatastr; + wpt_tmp->AllocGCData()->personal_note = cdatastr; break; /* @@ -1047,7 +1048,7 @@ GpxFormat::qualifiedName() const void GpxFormat::read() { - for (bool atEnd = false; !reader->atEnd() && !atEnd;) { + for (bool atEnd = false; !reader->atEnd() && !atEnd;) { reader->readNext(); // do processing switch (reader->tokenType()) { @@ -1097,7 +1098,7 @@ GpxFormat::read() } } - if (reader->hasError()) { + if (reader->hasError()) { fatal(FatalMsg() << MYNAME << "Read error:" << reader->errorString() << "File:" << iqfile->fileName() << "Line:" << reader->lineNumber() @@ -1263,87 +1264,80 @@ GpxFormat::gpx_write_common_position(const Waypoint* waypointp, const gpx_point_ void GpxFormat::gpx_write_common_extensions(const Waypoint* waypointp, const gpx_point_type point_type) const { - // gpx version we are writing is >= 1.1. - garmin_fs_t* gmsd = (opt_garminext) ? garmin_fs_t::find(waypointp) : nullptr; // only needed if garmin extensions selected + assert(gpx_write_version >= gpx_1_1); - if ((opt_humminbirdext && (waypointp->depth_has_value() || waypointp->temperature_has_value())) || - (opt_garminext && gpxpt_route==point_type && gmsd != nullptr && gmsd->ilinks != nullptr) || - (opt_garminext && gpxpt_waypoint==point_type && (waypointp->proximity_has_value() || waypointp->temperature_has_value() || waypointp->depth_has_value())) || - (opt_garminext && gpxpt_track==point_type && (waypointp->temperature_has_value() || waypointp->depth_has_value() || waypointp->heartrate != 0 || waypointp->cadence != 0))) { - writer->writeStartElement(QStringLiteral("extensions")); - if (opt_humminbirdext) { - if (waypointp->depth_has_value()) { - writer->writeTextElement(QStringLiteral("h:depth"), toString(waypointp->depth_value() * 100.0)); + writer->stackOptionalStartElement(QStringLiteral("extensions")); + + if (opt_humminbirdext) { + if (waypointp->depth_has_value()) { + writer->stackTextElement(QStringLiteral("h:depth"), toString(waypointp->depth_value() * 100.0)); + } + if (waypointp->temperature_has_value()) { + writer->stackTextElement(QStringLiteral("h:temperature"), toString(waypointp->temperature_value())); + } + } + + if (opt_garminext) { + // Although not required by the schema we assume that gpxx:WaypointExtension must be a child of gpx:wpt. + // Although not required by the schema we assume that gpxx:RoutePointExtension must be a child of gpx:rtept. + // Although not required by the schema we assume that gpxx:TrackPointExtension must be a child of gpx:trkpt. + // Although not required by the schema we assume that gpxtpx:TrackPointExtension must be a child of gpx:trkpt. + switch (point_type) { + case gpxpt_waypoint: + writer->stackOptionalStartElement(QStringLiteral("gpxx:WaypointExtension")); + if (waypointp->proximity_has_value()) { + writer->stackTextElement(QStringLiteral("gpxx:Proximity"), toString(waypointp->proximity_value())); } if (waypointp->temperature_has_value()) { - writer->writeTextElement(QStringLiteral("h:temperature"), toString(waypointp->temperature_value())); + writer->stackTextElement(QStringLiteral("gpxx:Temperature"), toString(waypointp->temperature_value())); } - } - - if (opt_garminext) { - // Although not required by the schema we assume that gpxx:WaypointExtension must be a child of gpx:wpt. - // Although not required by the schema we assume that gpxx:RoutePointExtension must be a child of gpx:rtept. - // Although not required by the schema we assume that gpxx:TrackPointExtension must be a child of gpx:trkpt. - // Although not required by the schema we assume that gpxtpx:TrackPointExtension must be a child of gpx:trkpt. - switch (point_type) { - case gpxpt_waypoint: - if (waypointp->proximity_has_value() || waypointp->temperature_has_value() || waypointp->depth_has_value()) { - writer->writeStartElement(QStringLiteral("gpxx:WaypointExtension")); - if (waypointp->proximity_has_value()) { - writer->writeTextElement(QStringLiteral("gpxx:Proximity"), toString(waypointp->proximity_value())); - } - if (waypointp->temperature_has_value()) { - writer->writeTextElement(QStringLiteral("gpxx:Temperature"), toString(waypointp->temperature_value())); - } - if (waypointp->depth_has_value()) { - writer->writeTextElement(QStringLiteral("gpxx:Depth"), toString(waypointp->depth_value())); - } - writer->writeEndElement(); // "gpxx:WaypointExtension" - } - break; - case gpxpt_route: - if (gmsd != nullptr && gpxpt_route==point_type && gmsd->ilinks != nullptr) { - writer->writeStartElement(QStringLiteral("gpxx:RoutePointExtension")); - garmin_ilink_t* link = gmsd->ilinks; - garmin_ilink_t* prior = nullptr; // GDB files sometime contain repeated point; omit them - while (link != nullptr) { - if (prior == nullptr || prior->lat != link->lat || prior->lon != link->lon) { - writer->writeStartElement(QStringLiteral("gpxx:rpt")); - writer->writeAttribute(QStringLiteral("lat"), toString(link->lat)); - writer->writeAttribute(QStringLiteral("lon"), toString(link->lon)); - writer->writeEndElement(); // "gpxx:rpt" - } - prior = link; - link = link->next; - } - writer->writeEndElement(); // "gpxx:RoutePointExtension" - } - break; - case gpxpt_track: - if (waypointp->temperature_has_value() || waypointp->depth_has_value() || waypointp->heartrate != 0 || waypointp->cadence != 0) { - // gpxtpx:TrackPointExtension is a replacement for gpxx:TrackPointExtension. - writer->writeStartElement(QStringLiteral("gpxtpx:TrackPointExtension")); - if (waypointp->temperature_has_value()) { - writer->writeTextElement(QStringLiteral("gpxtpx:atemp"), toString(waypointp->temperature_value())); - } - if (waypointp->depth_has_value()) { - writer->writeTextElement(QStringLiteral("gpxtpx:depth"), toString(waypointp->depth_value())); - } - if (waypointp->heartrate != 0) { - writer->writeTextElement(QStringLiteral("gpxtpx:hr"), QString::number(waypointp->heartrate)); - } - if (waypointp->cadence != 0) { - writer->writeTextElement(QStringLiteral("gpxtpx:cad"), QString::number(waypointp->cadence)); + if (waypointp->depth_has_value()) { + writer->stackTextElement(QStringLiteral("gpxx:Depth"), toString(waypointp->depth_value())); + } + writer->stackEndElement(); // gpxx:WaypointExtension + break; + case gpxpt_route: { + garmin_fs_t* gmsd = garmin_fs_t::find(waypointp); + if (gmsd != nullptr && gmsd->ilinks != nullptr) { + writer->stackOptionalStartElement(QStringLiteral("gpxx:RoutePointExtension")); + garmin_ilink_t* link = gmsd->ilinks; + garmin_ilink_t* prior = nullptr; // GDB files sometime contain repeated point; omit them + while (link != nullptr) { + if (prior == nullptr || prior->lat != link->lat || prior->lon != link->lon) { + writer->stackStartElement(QStringLiteral("gpxx:rpt")); + writer->stackAttribute(QStringLiteral("lat"), toString(link->lat)); + writer->stackAttribute(QStringLiteral("lon"), toString(link->lon)); + writer->stackEndElement(); // "gpxx:rpt" } - writer->writeEndElement(); // "gpxtpx:TrackPointExtension" + prior = link; + link = link->next; } - break; + writer->stackEndElement(); // gpxx:RoutePointExtension } } - - writer->writeEndElement(); // "extensions" + break; + case gpxpt_track: + // gpxtpx:TrackPointExtension is a replacement for gpxx:TrackPointExtension. + writer->stackOptionalStartElement(QStringLiteral("gpxtpx:TrackPointExtension")); + if (waypointp->temperature_has_value()) { + writer->stackTextElement(QStringLiteral("gpxtpx:atemp"), toString(waypointp->temperature_value())); + } + if (waypointp->depth_has_value()) { + writer->stackTextElement(QStringLiteral("gpxtpx:depth"), toString(waypointp->depth_value())); + } + if (waypointp->heartrate != 0) { + writer->stackTextElement(QStringLiteral("gpxtpx:hr"), QString::number(waypointp->heartrate)); + } + if (waypointp->cadence != 0) { + writer->stackTextElement(QStringLiteral("gpxtpx:cad"), QString::number(waypointp->cadence)); + } + writer->stackEndElement(); // gpxtpx:TrackPointExtension + break; + } } + + writer->stackEndElement(); // "extensions" } void diff --git a/src/core/xmlstreamwriter.cc b/src/core/xmlstreamwriter.cc index 307dc6ffb..1ce72f1c3 100644 --- a/src/core/xmlstreamwriter.cc +++ b/src/core/xmlstreamwriter.cc @@ -23,6 +23,8 @@ #include // for QXmlStreamWriter #include // for QT_VERSION, QT_VERSION_CHECK +#include "defs.h" + // As this code began in C, we have several hundred places that write // c strings. Add a test that the string contains anything useful // before serializing an empty tag. @@ -41,7 +43,96 @@ namespace gpsbabel { -// Dont emit the element if there's nothing interesting in it. +XmlStreamWriter::xml_stack_list_entry_t& XmlStreamWriter::activeStack() +{ + if (stack_list.isEmpty()) { + fatal("xmlstreamwriter: programming error: the stack* functions are used incorrectly."); + } + return stack_list.last(); +} + +void XmlStreamWriter::stackAttribute(const QString& name, const QString& value) +{ + activeStack().append(xml_command(xml_wrt_cmd_t::attribute, name, value)); +} + +void XmlStreamWriter::stackEndElement() +{ + xml_stack_list_entry_t& active_stack = activeStack(); + active_stack.append(xml_command(xml_wrt_cmd_t::end_element)); + + // Has the active_stack OptionalStartElement been paired with an EndElement? + if (active_stack.element_depth == 0) { // yes + const xml_stack_list_entry_t completed_stack = stack_list.takeLast(); + // Does the completed_stack OptionalStartElement have any content? + if (completed_stack.element_count > 1) { // yes + // Is this the initial OptionalStartElement? + if (!stack_list.isEmpty()) { // no. append stack contents to parent. + stack_list.last().append(completed_stack); + } else { // yes. write the stack contents. + for (const auto& command: completed_stack.stack) { + switch (command.type) { + case xml_wrt_cmd_t::start_element: + QXmlStreamWriter::writeStartElement(command.name); + break; + case xml_wrt_cmd_t::attribute: + QXmlStreamWriter::writeAttribute(command.name, command.value); + break; + case xml_wrt_cmd_t::name_space: + QXmlStreamWriter::writeNamespace(command.name, command.value); + break; + case xml_wrt_cmd_t::text_element: + QXmlStreamWriter::writeTextElement(command.name, command.value); + break; + case xml_wrt_cmd_t::end_element: + QXmlStreamWriter::writeEndElement(); + break; + } + } + } + } // else {no. empty OptionalStartElement is discarded.} + } +} + +void XmlStreamWriter::stackNamespace(const QString& namespaceUri, const QString& prefix) +{ + activeStack().append(xml_command(xml_wrt_cmd_t::name_space, namespaceUri, prefix)); +} + +/* + * Start an element that will be written if and only if it has children. + * + * Usage: + * 1. stackOptionalStartElement must be the first stack*() method called. + * 2. stackOptionalStartElement must be paired with a subsequent + * stackEndElement. + * 3. write*() methods should not be called until the initial optional start + * element has paired with a subsequent stackEndElement. + */ +void XmlStreamWriter::stackOptionalStartElement(const QString& name) +{ + stack_list.append(xml_stack_list_entry_t()); + stackStartElement(name); +} + +void XmlStreamWriter::stackOptionalTextElement(const QString& name, const QString& text) +{ + if (!text.isEmpty()) { + stackTextElement(name, text); + } +} + +void XmlStreamWriter::stackStartElement(const QString& name) +{ + activeStack().append(xml_command(xml_wrt_cmd_t::start_element, name)); +} + +void XmlStreamWriter::stackTextElement(const QString& name, const QString& text) +{ + activeStack().append(xml_command(xml_wrt_cmd_t::text_element, name, text)); +} + +// Don't emit the element if there's nothing interesting in it. void XmlStreamWriter::writeOptionalTextElement(const QString& qualifiedName, const QString& text) { if (!text.isEmpty()) { diff --git a/src/core/xmlstreamwriter.h b/src/core/xmlstreamwriter.h index 24097da6b..87b236e80 100644 --- a/src/core/xmlstreamwriter.h +++ b/src/core/xmlstreamwriter.h @@ -20,8 +20,10 @@ #ifndef XMLSTREAMWRITER_H #define XMLSTREAMWRITER_H -#include // for QString -#include // for QXmlStreamWriter +#include // for QList +#include // for QString +#include // for QXmlStreamWriter +#include namespace gpsbabel { @@ -31,7 +33,82 @@ class XmlStreamWriter : public QXmlStreamWriter public: using QXmlStreamWriter::QXmlStreamWriter; + /* Member Functions */ + + void stackAttribute(const QString& name, const QString& value); + void stackEndElement(); + void stackNamespace(const QString& namespaceUri, const QString& prefix); + void stackOptionalStartElement(const QString& name); + void stackOptionalTextElement(const QString& name, const QString& text); + void stackStartElement(const QString& name); + void stackTextElement(const QString& name, const QString& text); + void writeOptionalTextElement(const QString& qualifiedName, const QString& text); + +private: + /* Types */ + + enum class xml_wrt_cmd_t { + start_element, + attribute, + name_space, + text_element, + end_element + }; + + struct xml_command { + explicit xml_command(xml_wrt_cmd_t t, + QString n = QString(), + QString v = QString()) + : type(t), name(std::move(n)), value(std::move(v)) {}; + + xml_wrt_cmd_t type; + QString name; + QString value; + }; + + using xml_stack_t = QList; + + struct xml_stack_list_entry_t { + void append(const xml_stack_list_entry_t& other) + { + stack.append(other.stack); + element_count += other.element_count; + }; + + void append(const xml_command& cmd) + { + stack.append(cmd); + switch (cmd.type) { + case xml_wrt_cmd_t::start_element: + ++element_count; + ++element_depth; + break; + case xml_wrt_cmd_t::text_element: + ++element_count; + break; + case xml_wrt_cmd_t::end_element: + --element_depth; + break; + case xml_wrt_cmd_t::attribute: + case xml_wrt_cmd_t::name_space: + break; + } + }; + + xml_stack_t stack; + int element_count{0}; + int element_depth{0}; + }; + + /* Member Functions */ + + xml_stack_list_entry_t& activeStack(); + + /* Data Members */ + + QList stack_list; + }; } // namespace gpsbabel