1 /**********************************************************************************************
2     Copyright (C) 2014 Oliver Eichler <oliver.eichler@gmx.de>
3     Copyright (C) 2019 Henri Hornburg <hrnbg@t-online.de>
4 
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 
18 **********************************************************************************************/
19 
20 #include "device/CDeviceGarmin.h"
21 #include "gis/ovl/CGisItemOvlArea.h"
22 #include "gis/prj/IGisProject.h"
23 #include "gis/proj_x.h"
24 #include "gis/rte/CGisItemRte.h"
25 #include "gis/trk/CGisItemTrk.h"
26 #include "gis/trk/CKnownExtension.h"
27 #include "gis/wpt/CGisItemWpt.h"
28 #include "helpers/CWptIconManager.h"
29 #include "version.h"
30 
31 #include <QtXml>
32 
33 const QString IGisProject::gpx_ns = "http://www.topografix.com/GPX/1/1";
34 const QString IGisProject::xsi_ns = "http://www.w3.org/2001/XMLSchema-instance";
35 const QString IGisProject::gpxx_ns = "http://www.garmin.com/xmlschemas/GpxExtensions/v3";
36 const QString IGisProject::gpxtpx_ns = "http://www.garmin.com/xmlschemas/TrackPointExtension/v1";
37 const QString IGisProject::wptx1_ns = "http://www.garmin.com/xmlschemas/WaypointExtension/v1";
38 const QString IGisProject::rmc_ns = "urn:net:trekbuddy:1.0:nmea:rmc";
39 const QString IGisProject::ql_ns = "http://www.qlandkarte.org/xmlschemas/v1.1";
40 const QString IGisProject::gs_ns = "http://www.groundspeak.com/cache/1/0";
41 const QString IGisProject::tp1_ns = "http://www.garmin.com/xmlschemas/TrackPointExtension/v1";
42 const QString IGisProject::gpxdata_ns = "http://www.cluetrust.com/XML/GPXDATA/1/0";
43 
44 
readXml(const QDomNode & xml,const QString & tag,qint32 & value)45 static void readXml(const QDomNode& xml, const QString& tag, qint32& value)
46 {
47     if(xml.namedItem(tag).isElement())
48     {
49         bool ok = false;
50         qint32 tmp = xml.namedItem(tag).toElement().text().toInt(&ok);
51         if(!ok)
52         {
53             tmp = qRound(xml.namedItem(tag).toElement().text().toDouble(&ok));
54         }
55         if(ok)
56         {
57             value = tmp;
58         }
59     }
60 }
61 
readXml(const QDomNode & xml,const QString & tag,trkact_t & value)62 static void readXml(const QDomNode& xml, const QString& tag, trkact_t& value)
63 {
64     if(xml.namedItem(tag).isElement())
65     {
66         bool ok = false;
67         qint32 tmp = xml.namedItem(tag).toElement().text().toInt(&ok);
68         if(!ok)
69         {
70             value = CTrackData::trkpt_t::eAct20None;
71         }
72         else
73         {
74             value = trkact_t(tmp);
75         }
76     }
77 }
78 
79 
80 template<typename T>
readXml(const QDomNode & xml,const QString & tag,T & value)81 static void readXml(const QDomNode& xml, const QString& tag, T& value)
82 {
83     if(xml.namedItem(tag).isElement())
84     {
85         bool ok = false;
86         T tmp;
87 
88         if(std::is_same<T, quint32>::value)
89         {
90             tmp = xml.namedItem(tag).toElement().text().toUInt(&ok);
91         }
92         else if(std::is_same<T, quint64>::value)
93         {
94             tmp = xml.namedItem(tag).toElement().text().toULongLong(&ok);
95         }
96         else if(std::is_same<T, qreal>::value)
97         {
98             tmp = xml.namedItem(tag).toElement().text().toDouble(&ok);
99         }
100         else if(std::is_same<T, bool>::value)
101         {
102             tmp = xml.namedItem(tag).toElement().text().toInt(&ok);
103         }
104 
105         if(ok)
106         {
107             value = tmp;
108         }
109     }
110 }
111 
readXml(const QDomNode & xml,const QString & tag,QString & value)112 static void readXml(const QDomNode& xml, const QString& tag, QString& value)
113 {
114     if(xml.namedItem(tag).isElement())
115     {
116         value = xml.namedItem(tag).toElement().text();
117     }
118 }
119 
readXml(const QDomNode & xml,const QString & tag,QString & value,bool & isHtml)120 static void readXml(const QDomNode& xml, const QString& tag, QString& value, bool& isHtml)
121 {
122     if(xml.namedItem(tag).isElement())
123     {
124         const QDomNamedNodeMap& attr = xml.namedItem(tag).toElement().attributes();
125         isHtml = (attr.namedItem("html").nodeValue().toLocal8Bit().toLower() == "true");
126         value = xml.namedItem(tag).toElement().text();
127     }
128 }
129 
readXml(const QDomNode & xml,const QString & tag,QDateTime & value)130 static void readXml(const QDomNode& xml, const QString& tag, QDateTime& value)
131 {
132     if(xml.namedItem(tag).isElement())
133     {
134         QString time = xml.namedItem(tag).toElement().text();
135         IUnit::parseTimestamp(time, value);
136     }
137 }
138 
readXml(const QDomNode & xml,const QString & tag,QList<IGisItem::link_t> & l)139 static void readXml(const QDomNode& xml, const QString& tag, QList<IGisItem::link_t>& l)
140 {
141     if(xml.namedItem(tag).isElement())
142     {
143         const QDomNodeList& links = xml.toElement().elementsByTagName(tag);
144         int N = links.count();
145         for(int n = 0; n < N; ++n)
146         {
147             const QDomNode& link = links.item(n);
148 
149             if(xml != link.parentNode())
150             {
151                 continue;
152             }
153 
154             IGisItem::link_t tmp;
155             tmp.uri.setUrl(link.attributes().namedItem("href").nodeValue());
156             readXml(link, "text", tmp.text);
157             readXml(link, "type", tmp.type);
158 
159             l << tmp;
160         }
161     }
162 }
163 
readXml(const QDomNode & xml,IGisItem::history_t & history)164 static void readXml(const QDomNode& xml, IGisItem::history_t& history)
165 {
166     if(xml.namedItem("ql:history").isElement())
167     {
168         const QDomElement& xmlHistory = xml.namedItem("ql:history").toElement();
169 
170         const QDomNodeList& xmlEntries = xmlHistory.elementsByTagName("ql:event");
171         for(int n = 0; n < xmlEntries.count(); ++n)
172         {
173             const QDomNode& xmlEntry = xmlEntries.item(n);
174             IGisItem::history_event_t entry;
175             readXml(xmlEntry, "ql:icon", entry.icon);
176             readXml(xmlEntry, "ql:time", entry.time);
177             readXml(xmlEntry, "ql:comment", entry.comment);
178 
179             history.events << entry;
180         }
181 
182         history.histIdxInitial = history.events.size() - 1;
183         history.histIdxCurrent = history.histIdxInitial;
184     }
185 }
186 
readXml(const QDomNode & xml,const QString & tag,QPoint & offsetBubble,quint32 & widthBubble)187 static void readXml(const QDomNode& xml, const QString& tag, QPoint& offsetBubble, quint32& widthBubble)
188 {
189     if(xml.namedItem(tag).isElement())
190     {
191         const QDomElement& xmlBubble = xml.namedItem(tag).toElement();
192         int x = xmlBubble.attributes().namedItem("xoff").nodeValue().toInt();
193         int y = xmlBubble.attributes().namedItem("yoff").nodeValue().toInt();
194         offsetBubble = QPoint(x, y);
195         widthBubble = xmlBubble.attributes().namedItem("width").nodeValue().toInt();
196     }
197 }
198 
199 
writeXml(QDomNode & xml,const QString & tag,qint32 val)200 static void writeXml(QDomNode& xml, const QString& tag, qint32 val)
201 {
202     if(val != NOINT)
203     {
204         QDomElement elem = xml.ownerDocument().createElement(tag);
205         xml.appendChild(elem);
206         QDomText text = xml.ownerDocument().createTextNode(QString::number(val));
207         elem.appendChild(text);
208     }
209 }
210 
writeXml(QDomNode & xml,const QString & tag,quint32 val)211 static void writeXml(QDomNode& xml, const QString& tag, quint32 val)
212 {
213     if(val != NOINT)
214     {
215         QDomElement elem = xml.ownerDocument().createElement(tag);
216         xml.appendChild(elem);
217         QDomText text = xml.ownerDocument().createTextNode(QString::number(val));
218         elem.appendChild(text);
219     }
220 }
221 
writeXml(QDomNode & xml,const QString & tag,quint64 val)222 static void writeXml(QDomNode& xml, const QString& tag, quint64 val)
223 {
224     if(val != 0)
225     {
226         QDomElement elem = xml.ownerDocument().createElement(tag);
227         xml.appendChild(elem);
228         QDomText text = xml.ownerDocument().createTextNode(QString::number(val));
229         elem.appendChild(text);
230     }
231 }
232 
writeXml(QDomNode & xml,const QString & tag,const QString & val)233 static void writeXml(QDomNode& xml, const QString& tag, const QString& val)
234 {
235     if(!val.isEmpty())
236     {
237         QDomElement elem = xml.ownerDocument().createElement(tag);
238         xml.appendChild(elem);
239         QDomText text = xml.ownerDocument().createTextNode(val);
240         elem.appendChild(text);
241     }
242 }
243 
writeXml(QDomNode & xml,const QString & tag,qreal val)244 static void writeXml(QDomNode& xml, const QString& tag, qreal val)
245 {
246     if(val != NOFLOAT)
247     {
248         QDomElement elem = xml.ownerDocument().createElement(tag);
249         xml.appendChild(elem);
250         QDomText text = xml.ownerDocument().createTextNode(QString("%1").arg(val, 0, 'f', 8));
251         elem.appendChild(text);
252     }
253 }
254 
writeXml(QDomNode & xml,const QString & tag,const QString & val,bool isHtml)255 static void writeXml(QDomNode& xml, const QString& tag, const QString& val, bool isHtml)
256 {
257     if(!val.isEmpty())
258     {
259         QDomElement elem = xml.ownerDocument().createElement(tag);
260         xml.appendChild(elem);
261         QDomText text = xml.ownerDocument().createCDATASection(val);
262         elem.appendChild(text);
263         elem.setAttribute("html", isHtml ? "True" : "False");
264     }
265 }
266 
writeXml(QDomNode & xml,const QString & tag,const QDateTime & time)267 static void writeXml(QDomNode& xml, const QString& tag, const QDateTime& time)
268 {
269     if(time.isValid())
270     {
271         QDomElement elem = xml.ownerDocument().createElement(tag);
272         xml.appendChild(elem);
273         QDomText text = xml.ownerDocument().createTextNode(time.toString("yyyy-MM-dd'T'hh:mm:ss.zzz'Z'"));
274         elem.appendChild(text);
275     }
276 }
277 
278 
writeXml(QDomNode & xml,const QString & tag,const QList<IGisItem::link_t> & links)279 static void writeXml(QDomNode& xml, const QString& tag, const QList<IGisItem::link_t>& links)
280 {
281     if(!links.isEmpty())
282     {
283         for(const IGisItem::link_t& link : links)
284         {
285             QDomElement elem = xml.ownerDocument().createElement(tag);
286             xml.appendChild(elem);
287 
288             elem.setAttribute("href", link.uri.toString());
289             writeXml(elem, "text", link.text);
290             writeXml(elem, "type", link.type);
291         }
292     }
293 }
294 
writeXml(QDomNode & xml,const IGisItem::history_t & history)295 static void writeXml(QDomNode& xml, const IGisItem::history_t& history)
296 {
297     if(history.events.size() > 1)
298     {
299         QDomElement xmlHistory = xml.ownerDocument().createElement("ql:history");
300         xml.appendChild(xmlHistory);
301         for(int i = 0; i <= history.histIdxCurrent; i++)
302         {
303             const IGisItem::history_event_t& event = history.events[i];
304             QDomElement xmlEvent = xml.ownerDocument().createElement("ql:event");
305             xmlHistory.appendChild(xmlEvent);
306             writeXml(xmlEvent, "ql:icon", event.icon);
307             writeXml(xmlEvent, "ql:time", event.time);
308             writeXml(xmlEvent, "ql:comment", event.comment);
309         }
310     }
311 }
312 
writeXml(QDomNode & xml,const QString & tag,const QPoint & offsetBubble,quint32 widthBubble)313 static void writeXml(QDomNode& xml, const QString& tag, const QPoint& offsetBubble, quint32 widthBubble)
314 {
315     QDomElement elem = xml.ownerDocument().createElement(tag);
316     xml.appendChild(elem);
317 
318     elem.setAttribute("xoff", offsetBubble.x());
319     elem.setAttribute("yoff", offsetBubble.y());
320     elem.setAttribute("width", widthBubble);
321 }
322 
323 
readXml(const QDomNode & node,const QString & parentTags,QHash<QString,QVariant> & extensions)324 static void readXml(const QDomNode& node, const QString& parentTags, QHash<QString, QVariant>& extensions)
325 {
326     QString tag = node.nodeName();
327     if((tag.left(8) == "ql:flags") || (tag.left(11) == "ql:activity"))
328     {
329         return;
330     }
331 
332     QString tags = parentTags.isEmpty() ? tag : parentTags + "|" + tag;
333     const QDomNode& next = node.firstChild();
334     if(next.isText())
335     {
336         extensions[tags] = node.toElement().text();
337     }
338     else
339     {
340         const QDomNodeList& list = node.childNodes();
341         for(int i = 0; i < list.size(); i++)
342         {
343             readXml(list.at(i), tags, extensions);
344         }
345     }
346 }
347 
readXml(const QDomNode & ext,QHash<QString,QVariant> & extensions)348 static void readXml(const QDomNode& ext, QHash<QString, QVariant>& extensions)
349 {
350     const QDomNodeList& list = ext.childNodes();
351     for(int i = 0; i < list.size(); i++)
352     {
353         readXml(list.at(i), "", extensions);
354     }
355 
356     extensions.squeeze();
357 }
358 
writeXml(QDomNode & ext,const QHash<QString,QVariant> & extensions)359 static void writeXml(QDomNode& ext, const QHash<QString, QVariant>& extensions)
360 {
361     if(extensions.isEmpty())
362     {
363         return;
364     }
365 
366     QDomDocument doc = ext.ownerDocument();
367 
368     QStringList keys = extensions.keys();
369     qSort(keys.begin(), keys.end(), [] (const QString& k1, const QString& k2) {
370         return CKnownExtension::get(k1).order < CKnownExtension::get(k2).order;
371     });
372 
373     for(const QString& key : qAsConst(keys))
374     {
375         QStringList tags = key.split('|', QString::SkipEmptyParts);
376 
377         if(tags.size() == 1)
378         {
379             QDomElement elem = doc.createElement(tags.first());
380             ext.appendChild(elem);
381             QDomText text = doc.createTextNode(extensions[key].toString());
382             elem.appendChild(text);
383         }
384         else
385         {
386             QDomNode node = ext;
387 
388             QString lastTag = tags.last();
389             tags.pop_back();
390             for(const QString& tag : qAsConst(tags))
391             {
392                 QDomNode child = node.firstChildElement(tag);
393                 if(child.isNull())
394                 {
395                     QDomElement elem = doc.createElement(tags.first());
396                     node.appendChild(elem);
397                     node = elem;
398                 }
399                 else
400                 {
401                     node = child;
402                 }
403             }
404             QDomElement elem = doc.createElement(lastTag);
405             node.appendChild(elem);
406 
407             QDomText text = doc.createTextNode(extensions[key].toString());
408             elem.appendChild(text);
409         }
410     }
411 }
412 
readMetadata(const QDomNode & xml,metadata_t & metadata)413 void IGisProject::readMetadata(const QDomNode& xml, metadata_t& metadata)
414 {
415     readXml(xml, "name", metadata.name);
416     readXml(xml, "desc", metadata.desc);
417 
418     const QDomNode& xmlAuthor = xml.namedItem("author");
419     if(xmlAuthor.isElement())
420     {
421         readXml(xml, "name", metadata.author.name);
422 
423         const QDomNode& xmlEmail = xmlAuthor.namedItem("email");
424         if(xmlEmail.isElement())
425         {
426             const QDomNamedNodeMap& attr = xmlEmail.attributes();
427             metadata.author.id = attr.namedItem("id").nodeValue();
428             metadata.author.domain = attr.namedItem("domain").nodeValue();
429         }
430 
431         const QDomNode& xmlLink = xmlAuthor.namedItem("link");
432         if(xmlLink.isElement())
433         {
434             metadata.author.link.uri.setUrl(xmlLink.attributes().namedItem("href").nodeValue());
435             readXml(xmlLink, "text", metadata.author.link.text);
436             readXml(xmlLink, "type", metadata.author.link.type);
437         }
438     }
439 
440     const QDomNode& xmlCopyright = xml.namedItem("copyright");
441     if(xmlCopyright.isElement())
442     {
443         metadata.copyright.author = xmlCopyright.attributes().namedItem("author").nodeValue();
444         readXml(xmlCopyright, "year", metadata.copyright.year);
445         readXml(xmlCopyright, "license", metadata.copyright.license);
446     }
447 
448     readXml(xml, "link", metadata.links);
449     readXml(xml, "time", metadata.time);
450     readXml(xml, "keywords", metadata.keywords);
451 
452     const QDomNode& xmlBounds = xml.namedItem("bounds");
453     if(xmlBounds.isElement())
454     {
455         const QDomNamedNodeMap& attr = xmlBounds.attributes();
456         metadata.bounds.setLeft(  attr.namedItem("minlon").nodeValue().toDouble());
457         metadata.bounds.setTop(   attr.namedItem("maxlat").nodeValue().toDouble());
458         metadata.bounds.setRight( attr.namedItem("maxlon").nodeValue().toDouble());
459         metadata.bounds.setBottom(attr.namedItem("minlat").nodeValue().toDouble());
460     }
461 }
462 
writeMetadata(QDomDocument & doc,bool strictGpx11)463 QDomNode IGisProject::writeMetadata(QDomDocument& doc, bool strictGpx11)
464 {
465     QDomElement gpx = doc.createElement("gpx");
466     doc.appendChild(gpx);
467 
468     gpx.setAttribute("version", "1.1");
469     gpx.setAttribute("creator", "QMapShack " VER_STR " http://www.qlandkarte.org/");
470     gpx.setAttribute("xmlns", gpx_ns);
471     gpx.setAttribute("xmlns:xsi", xsi_ns);
472 
473     QString schemaLocation;
474     if(!strictGpx11)
475     {
476         gpx.setAttribute("xmlns:gpxx", gpxx_ns);
477         gpx.setAttribute("xmlns:gpxtpx", gpxtpx_ns);
478         gpx.setAttribute("xmlns:wptx1", wptx1_ns);
479         gpx.setAttribute("xmlns:rmc", rmc_ns);
480         gpx.setAttribute("xmlns:ql", ql_ns);
481         gpx.setAttribute("xmlns:tp1", tp1_ns);
482         gpx.setAttribute("xmlns:gpxdata", gpxdata_ns);
483 
484 
485 
486         schemaLocation = QString()
487                          + gpx_ns + " http://www.topografix.com/GPX/1/1/gpx.xsd "
488                          + gpxx_ns + " http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd "
489                          + gpxtpx_ns + " http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd "
490                          + wptx1_ns + " http://www.garmin.com/xmlschemas/WaypointExtensionv1.xsd "
491                          + ql_ns + " http://www.qlandkarte.org/xmlschemas/v1.1/ql-extensions.xsd "
492                          + gpxdata_ns + " http://www.cluetrust.com/Schemas/gpxdata10.xsd";
493     }
494     else
495     {
496         schemaLocation = QString()
497                          + gpx_ns + " http://www.topografix.com/GPX/1/1/gpx.xsd ";
498     }
499 
500 
501     gpx.setAttribute("xsi:schemaLocation", schemaLocation);
502 
503     QDomElement xmlMetadata = doc.createElement("metadata");
504     gpx.appendChild(xmlMetadata);
505 
506     writeXml(xmlMetadata, "name", metadata.name);
507     writeXml(xmlMetadata, "desc", html2Dev(metadata.desc));
508 
509     if(!metadata.author.name.isEmpty())
510     {
511         QDomElement xmlAuthor = doc.createElement("author");
512         xmlMetadata.appendChild(xmlAuthor);
513 
514         writeXml(xmlAuthor, "name", metadata.author.name);
515 
516         if(!metadata.author.id.isEmpty() && !metadata.author.domain.isEmpty())
517         {
518             QDomElement xmlEmail = doc.createElement("email");
519             xmlAuthor.appendChild(xmlEmail);
520             xmlEmail.setAttribute("id", metadata.author.id);
521             xmlEmail.setAttribute("domain", metadata.author.domain);
522         }
523 
524         if(metadata.author.link.uri.isValid())
525         {
526             QDomElement xmlLink = doc.createElement("link");
527             xmlAuthor.appendChild(xmlLink);
528 
529             xmlLink.setAttribute("href", metadata.author.link.uri.toString());
530             writeXml(xmlLink, "text", metadata.author.link.text);
531             writeXml(xmlLink, "type", metadata.author.link.type);
532         }
533     }
534 
535     if(!metadata.copyright.author.isEmpty())
536     {
537         QDomElement xmlCopyright = doc.createElement("copyright");
538         xmlMetadata.appendChild(xmlCopyright);
539 
540         xmlCopyright.setAttribute("author", metadata.copyright.author);
541         writeXml(xmlCopyright, "year", metadata.copyright.year);
542         writeXml(xmlCopyright, "license", metadata.copyright.license);
543     }
544     writeXml(xmlMetadata, "link", metadata.links);
545     writeXml(xmlMetadata, "time", metadata.time);
546     writeXml(xmlMetadata, "keywords", metadata.keywords);
547 
548     if(metadata.bounds.isValid())
549     {
550         QDomElement xmlBounds = doc.createElement("bounds");
551         xmlMetadata.appendChild(xmlBounds);
552 
553         xmlBounds.setAttribute("minlat", metadata.bounds.bottom());
554         xmlBounds.setAttribute("minlon", metadata.bounds.left());
555         xmlBounds.setAttribute("maxlat", metadata.bounds.top());
556         xmlBounds.setAttribute("maxlon", metadata.bounds.right());
557     }
558 
559     return gpx;
560 }
561 
readGpx(const QDomNode & xml)562 void CGisItemWpt::readGpx(const QDomNode& xml)
563 {
564     readWpt(xml, wpt);
565     // decode some well known extensions
566     if(xml.namedItem("extensions").isElement())
567     {
568         const QDomNode& ext = xml.namedItem("extensions");
569         readXml(ext, "ql:key", key.item);
570         readXml(ext, "ql:flags", flags);
571         readXml(ext, "ql:bubble", offsetBubble, widthBubble);
572         readXml(ext, history);
573 
574         const QDomNode& wptx1 = ext.namedItem("wptx1:WaypointExtension");
575         readXml(wptx1, "wptx1:Proximity", proximity);
576 
577         const QDomNode& xmlCache = ext.namedItem("cache");
578         if(!xmlCache.isNull())
579         {
580             // read OC cache extensions
581         }
582     }
583 
584     const QDomNode& xmlCache = xml.namedItem("groundspeak:cache");
585     if(!xmlCache.isNull() && !geocache.hasData)
586     {
587         readGcExt(xmlCache);
588     }
589 }
590 
save(QDomNode & gpx,bool strictGpx11)591 void CGisItemWpt::save(QDomNode& gpx, bool strictGpx11)
592 {
593     QDomDocument doc = gpx.ownerDocument();
594 
595     QDomElement xmlWpt = doc.createElement("wpt");
596     gpx.appendChild(xmlWpt);
597     writeWpt(xmlWpt, wpt, strictGpx11);
598 
599     if(!strictGpx11)
600     {
601         // write the key as extension tag
602         QDomElement xmlExt = doc.createElement("extensions");
603         xmlWpt.appendChild(xmlExt);
604         writeXml(xmlExt, "ql:key", key.item);
605         writeXml(xmlExt, "ql:flags", flags);
606         writeXml(xmlExt, "ql:bubble", offsetBubble, widthBubble);
607         writeXml(xmlExt, history);
608 
609         // write other well known extensions
610         QDomElement wptx1 = doc.createElement("wptx1:WaypointExtension");
611         xmlExt.appendChild(wptx1);
612         writeXml(wptx1, "wptx1:Proximity", proximity);
613 
614         if(geocache.hasData /*&& geocache.service == eGC*/)
615         {
616             QDomElement xmlCache = doc.createElement("groundspeak:cache");
617             writeGcExt(xmlCache);
618             xmlWpt.appendChild(xmlCache);
619         }
620     }
621 }
622 
readGcExt(const QDomNode & xmlCache)623 void CGisItemWpt::readGcExt(const QDomNode& xmlCache)
624 {
625     //Geocaches only have one link
626     if(wpt.links.isEmpty())
627     {
628         geocache.service = eUnknown;
629     }
630     else
631     {
632         if(wpt.links.first().uri.url(QUrl::RemovePath).contains("geocaching.com"))
633         {
634             geocache.service = eGcCom;
635         }
636         else if(wpt.links.first().uri.url(QUrl::RemovePath).contains("opencaching"))
637         {
638             geocache.service = eOc;
639         }
640         else if(wpt.links.first().uri.url(QUrl::RemovePath).contains("geocaching.su"))
641         {
642             geocache.service = eGcSu;
643         }
644         else
645         {
646             geocache.service = eUnknown;
647         }
648     }
649 
650     const QDomNamedNodeMap& attr = xmlCache.attributes();
651     geocache.id = attr.namedItem("id").nodeValue().toInt();
652 
653     QDomNode geocacheAttributes = xmlCache.namedItem("groundspeak:attributes");
654 
655     geocache.archived = attr.namedItem("archived").nodeValue().toLocal8Bit().toLower() == "true";
656     geocache.available = attr.namedItem("available").nodeValue().toLocal8Bit().toLower() == "true";
657 
658     for(QDomNode xmlAttribute = geocacheAttributes.firstChild(); !xmlAttribute.isNull(); xmlAttribute = xmlAttribute.nextSibling())
659     {
660         quint8 id = xmlAttribute.attributes().namedItem("id").nodeValue().toUInt();
661         if(id >= geocache.attributeMeanings.size())
662         {
663             qWarning() << "CGisItemWpt::readGcExt(): Ignore unknown attribute ID " << id;
664             continue;
665         }
666 
667         qint8 intvalue = xmlAttribute.attributes().namedItem("inc").nodeValue().toUInt();
668         geocache.attributes[id] = (intvalue == 1);
669         if(id == 42) //42 is the code for 'Needs maintenance' and it only appears, when there attribute is set
670         {
671             geocache.needsMaintenance = true;
672         }
673     }
674 
675     readXml(xmlCache, "groundspeak:name", geocache.name);
676     readXml(xmlCache, "groundspeak:placed_by", geocache.owner);
677     readXml(xmlCache, "groundspeak:type", geocache.type);
678     readXml(xmlCache, "groundspeak:container", geocache.container);
679     readXml(xmlCache, "groundspeak:difficulty", geocache.difficulty);
680     readXml(xmlCache, "groundspeak:terrain", geocache.terrain);
681     readXml(xmlCache, "groundspeak:short_description", geocache.shortDesc, geocache.shortDescIsHtml);
682     readXml(xmlCache, "groundspeak:long_description", geocache.longDesc, geocache.longDescIsHtml);
683     readXml(xmlCache, "groundspeak:encoded_hints", geocache.hint);
684     readXml(xmlCache, "groundspeak:country", geocache.country);
685     readXml(xmlCache, "groundspeak:state", geocache.state);
686 
687     const QDomNodeList& logs = xmlCache.toElement().elementsByTagName("groundspeak:log");
688     uint N = logs.count();
689 
690     for(uint n = 0; n < N; ++n)
691     {
692         const QDomNode& xmlLog = logs.item(n);
693         const QDomNamedNodeMap& attr = xmlLog.attributes();
694 
695         geocachelog_t log;
696         log.id = attr.namedItem("id").nodeValue().toUInt();
697         readXml(xmlLog, "groundspeak:date", log.date);
698         readXml(xmlLog, "groundspeak:type", log.type);
699         if(xmlLog.namedItem("groundspeak:finder").isElement())
700         {
701             const QDomNamedNodeMap& attr = xmlLog.namedItem("groundspeak:finder").attributes();
702             log.finderId = attr.namedItem("id").nodeValue();
703         }
704 
705         readXml(xmlLog, "groundspeak:finder", log.finder);
706         readXml(xmlLog, "groundspeak:text", log.text, log.textIsHtml);
707 
708         geocache.logs << log;
709     }
710     geocache.hasData = true;
711 }
712 
713 
714 
writeGcExt(QDomNode & xmlCache)715 void CGisItemWpt::writeGcExt(QDomNode& xmlCache)
716 {
717     QString str;
718     xmlCache.toElement().setAttribute("xmlns:groundspeak", "http://www.groundspeak.com/cache/1/0");
719     xmlCache.toElement().setAttribute("id", geocache.id);
720     xmlCache.toElement().setAttribute("archived", geocache.archived ? "True" : "False");
721     xmlCache.toElement().setAttribute("available", geocache.available ? "True" : "False");
722 
723     writeXml(xmlCache, "groundspeak:name", geocache.name);
724     writeXml(xmlCache, "groundspeak:placed_by", geocache.owner);
725     writeXml(xmlCache, "groundspeak:type", geocache.type);
726     writeXml(xmlCache, "groundspeak:container", geocache.container);
727 
728     QDomElement xmlAttributes = xmlCache.ownerDocument().createElement("groundspeak:attributes");
729     const QList<quint8>& attributes = geocache.attributes.keys();
730     for(quint8 attribute : attributes)
731     {
732         QDomElement xmlAttribute = xmlCache.ownerDocument().createElement("groundspeak:attribute");
733         xmlAttribute.setAttribute("id", attribute);
734         qint8 inc = geocache.attributes[attribute] ? 1 : 0;
735         xmlAttribute.setAttribute("inc", inc);
736         QDomText text;
737         if(attribute < geocache.attributeMeanings.size())
738         {
739             text = xmlCache.ownerDocument().createTextNode(geocache.attributeMeanings[attribute]);
740         }
741         xmlAttribute.appendChild(text);
742         xmlAttributes.appendChild(xmlAttribute);
743         xmlCache.appendChild(xmlAttributes);
744     }
745 
746     if(geocache.difficulty == int(geocache.difficulty))
747     {
748         str.sprintf("%1.0f", geocache.difficulty);
749     }
750     else
751     {
752         str.sprintf("%1.1f", geocache.difficulty);
753     }
754     writeXml(xmlCache, "groundspeak:difficulty", str);
755 
756     if(geocache.terrain == int(geocache.terrain))
757     {
758         str.sprintf("%1.0f", geocache.terrain);
759     }
760     else
761     {
762         str.sprintf("%1.1f", geocache.terrain);
763     }
764     writeXml(xmlCache, "groundspeak:terrain", str);
765     writeXml(xmlCache, "groundspeak:short_description", geocache.shortDesc, geocache.shortDescIsHtml);
766     writeXml(xmlCache, "groundspeak:long_description", geocache.longDesc, geocache.longDescIsHtml);
767     writeXml(xmlCache, "groundspeak:encoded_hints", geocache.hint);
768 
769     if(!geocache.logs.isEmpty())
770     {
771         QDomElement xmlLogs = xmlCache.ownerDocument().createElement("groundspeak:logs");
772         xmlCache.appendChild(xmlLogs);
773 
774         for(const geocachelog_t& log : qAsConst(geocache.logs))
775         {
776             QDomElement xmlLog = xmlCache.ownerDocument().createElement("groundspeak:log");
777             xmlLogs.appendChild(xmlLog);
778 
779             xmlLog.setAttribute("id", log.id);
780             writeXml(xmlLog, "groundspeak:date", log.date);
781             writeXml(xmlLog, "groundspeak:type", log.type);
782 
783             QDomElement xmlFinder = xmlCache.ownerDocument().createElement("groundspeak:finder");
784             xmlLog.appendChild(xmlFinder);
785 
786             QDomText _finder_ = xmlCache.ownerDocument().createCDATASection(log.finder);
787             xmlFinder.appendChild(_finder_);
788             xmlFinder.setAttribute("id", log.finderId);
789 
790             writeXml(xmlLog, "groundspeak:text", log.text, log.textIsHtml);
791         }
792     }
793 }
794 
795 
readTrk(const QDomNode & xml,CTrackData & trk)796 void CGisItemTrk::readTrk(const QDomNode& xml, CTrackData& trk)
797 {
798     readXml(xml, "name", trk.name);
799     readXml(xml, "cmt", trk.cmt);
800     readXml(xml, "desc", trk.desc);
801     readXml(xml, "src", trk.src);
802     readXml(xml, "link", trk.links);
803     readXml(xml, "number", trk.number);
804     readXml(xml, "type", trk.type);
805 
806     const QDomNodeList& trksegs = xml.toElement().elementsByTagName("trkseg");
807     int N = trksegs.count();
808     trk.segs.resize(N);
809     for(int n = 0; n < N; ++n)
810     {
811         const QDomNode& trkseg = trksegs.item(n);
812         CTrackData::trkseg_t& seg = trk.segs[n];
813 
814         const QDomNodeList& xmlTrkpts = trkseg.toElement().elementsByTagName("trkpt");
815         int M = xmlTrkpts.count();
816         seg.pts.resize(M);
817         for(int m = 0; m < M; ++m)
818         {
819             CTrackData::trkpt_t& trkpt = seg.pts[m];
820             const QDomNode& xmlTrkpt = xmlTrkpts.item(m);
821             readWpt(xmlTrkpt, trkpt);
822 
823             const QDomNode& ext = xmlTrkpt.namedItem("extensions");
824             if(ext.isElement())
825             {
826                 readXml(ext, "ql:flags", trkpt.flags);
827                 readXml(ext, "ql:activity", trkpt.activity);
828                 trkpt.sanitizeFlags();
829                 readXml(ext, trkpt.extensions);
830             }
831         }
832     }
833 
834     // decode some well known extensions
835     const QDomNode& ext = xml.namedItem("extensions");
836     if(ext.isElement())
837     {
838         readXml(ext, "ql:key", key.item);
839         readXml(ext, "ql:flags", flags);
840         readXml(ext, history);
841 
842         const QDomNode& gpxx = ext.namedItem("gpxx:TrackExtension");
843         readXml(gpxx, "gpxx:DisplayColor", trk.color);
844         setColor(str2color(trk.color));
845     }
846 
847     deriveSecondaryData();
848 }
849 
850 
851 
save(QDomNode & gpx,bool strictGpx11)852 void CGisItemTrk::save(QDomNode& gpx, bool strictGpx11)
853 {
854     QDomDocument doc = gpx.ownerDocument();
855 
856     QDomElement xmlTrk = doc.createElement("trk");
857     gpx.appendChild(xmlTrk);
858 
859     writeXml(xmlTrk, "name", trk.name);
860     writeXml(xmlTrk, "cmt", html2Dev(trk.cmt, strictGpx11));
861     writeXml(xmlTrk, "desc", html2Dev(trk.desc, strictGpx11));
862     writeXml(xmlTrk, "src", trk.src);
863     writeXml(xmlTrk, "link", trk.links);
864     writeXml(xmlTrk, "number", trk.number);
865     writeXml(xmlTrk, "type", trk.type);
866 
867     if(!strictGpx11)
868     {
869         // write the key as extension tag
870         QDomElement xmlExt = doc.createElement("extensions");
871         xmlTrk.appendChild(xmlExt);
872         writeXml(xmlExt, "ql:key", key.item);
873         writeXml(xmlExt, "ql:flags", flags);
874         writeXml(xmlExt, history);
875 
876         // write other well known extensions
877         QDomElement gpxx = doc.createElement("gpxx:TrackExtension");
878         xmlExt.appendChild(gpxx);
879         writeXml(gpxx, "gpxx:DisplayColor", trk.color);
880     }
881 
882     for(const CTrackData::trkseg_t& seg : qAsConst(trk.segs))
883     {
884         QDomElement xmlTrkseg = doc.createElement("trkseg");
885         xmlTrk.appendChild(xmlTrkseg);
886 
887         for(const CTrackData::trkpt_t& pt : seg.pts)
888         {
889             QDomElement xmlTrkpt = doc.createElement("trkpt");
890             xmlTrkseg.appendChild(xmlTrkpt);
891             writeWpt(xmlTrkpt, pt, strictGpx11);
892 
893             if(!strictGpx11)
894             {
895                 QDomElement xmlExt = doc.createElement("extensions");
896                 xmlTrkpt.appendChild(xmlExt);
897                 writeXml(xmlExt, "ql:flags", pt.flags);
898                 writeXml(xmlExt, "ql:activity", pt.activity);
899                 writeXml(xmlExt, pt.extensions);
900             }
901         }
902     }
903 }
904 
readRte(const QDomNode & xml,rte_t & rte)905 void CGisItemRte::readRte(const QDomNode& xml, rte_t& rte)
906 {
907     readXml(xml, "name", rte.name);
908     readXml(xml, "cmt", rte.cmt);
909     readXml(xml, "desc", rte.desc);
910     readXml(xml, "src", rte.src);
911     readXml(xml, "link", rte.links);
912     readXml(xml, "number", rte.number);
913     readXml(xml, "type", rte.type);
914 
915     const QDomNodeList& xmlRtepts = xml.toElement().elementsByTagName("rtept");
916     int M = xmlRtepts.count();
917     rte.pts.resize(M);
918     for(int m = 0; m < M; ++m)
919     {
920         rtept_t& rtept = rte.pts[m];
921         const QDomNode& xmlRtept = xmlRtepts.item(m);
922         readWpt(xmlRtept, rtept);
923         rtept.icon = CWptIconManager::self().getWptIconByName(rtept.sym, rtept.focus);
924     }
925 
926     // decode some well known extensions
927     if(xml.namedItem("extensions").isElement())
928     {
929         const QDomNode& ext = xml.namedItem("extensions");
930         readXml(ext, "ql:key", key.item);
931     }
932 }
933 
934 
save(QDomNode & gpx,bool strictGpx11)935 void CGisItemRte::save(QDomNode& gpx, bool strictGpx11)
936 {
937     QDomDocument doc = gpx.ownerDocument();
938 
939     QDomElement xmlRte = doc.createElement("rte");
940     gpx.appendChild(xmlRte);
941 
942     writeXml(xmlRte, "name", rte.name);
943     writeXml(xmlRte, "cmt", html2Dev(rte.cmt, strictGpx11));
944     writeXml(xmlRte, "desc", html2Dev(rte.desc, strictGpx11));
945     writeXml(xmlRte, "src", rte.src);
946     writeXml(xmlRte, "link", rte.links);
947     writeXml(xmlRte, "number", rte.number);
948     writeXml(xmlRte, "type", rte.type);
949 
950     if(!strictGpx11)
951     {
952         // write the key as extension tag
953         QDomElement xmlExt = doc.createElement("extensions");
954         xmlRte.appendChild(xmlExt);
955         writeXml(xmlExt, "ql:key", key.item);
956     }
957 
958     for(const rtept_t& pt : qAsConst(rte.pts))
959     {
960         QDomElement xmlRtept = doc.createElement("rtept");
961         xmlRte.appendChild(xmlRtept);
962         writeWpt(xmlRtept, pt, strictGpx11);
963     }
964 }
965 
readArea(const QDomNode & xml,area_t & area)966 void CGisItemOvlArea::readArea(const QDomNode& xml, area_t& area)
967 {
968     readXml(xml, "ql:name", area.name);
969     readXml(xml, "ql:cmt", area.cmt);
970     readXml(xml, "ql:desc", area.desc);
971     readXml(xml, "ql:src", area.src);
972     readXml(xml, "ql:link", area.links);
973     readXml(xml, "ql:number", area.number);
974     readXml(xml, "ql:type", area.type);
975     readXml(xml, "ql:color", area.color);
976     readXml(xml, "ql:width", area.width);
977     readXml(xml, "ql:style", area.style);
978     readXml(xml, "ql:opacity", area.opacity);
979     readXml(xml, "ql:key", key.item);
980     readXml(xml, "ql:flags", flags);
981     readXml(xml, history);
982 
983     const QDomNodeList& xmlPts = xml.toElement().elementsByTagName("ql:point");
984     int M = xmlPts.count();
985     area.pts.resize(M);
986     for(int m = 0; m < M; ++m)
987     {
988         pt_t& pt = area.pts[m];
989         const QDomNode& xmlPt = xmlPts.item(m);
990         readWpt(xmlPt, pt);
991     }
992 
993     setColor(str2color(area.color));
994 
995     deriveSecondaryData();
996 }
997 
save(QDomNode & gpx,bool strictGpx11)998 void CGisItemOvlArea::save(QDomNode& gpx, bool strictGpx11)
999 {
1000     QDomDocument doc = gpx.ownerDocument();
1001 
1002     QDomElement xmlArea = doc.createElement("ql:area");
1003     gpx.appendChild(xmlArea);
1004 
1005     writeXml(xmlArea, "ql:name", area.name);
1006     writeXml(xmlArea, "ql:cmt", area.cmt);
1007     writeXml(xmlArea, "ql:desc", area.desc);
1008     writeXml(xmlArea, "ql:src", area.src);
1009     writeXml(xmlArea, "ql:link", area.links);
1010     writeXml(xmlArea, "ql:number", area.number);
1011     writeXml(xmlArea, "ql:type", area.type);
1012     writeXml(xmlArea, "ql:color", area.color);
1013     writeXml(xmlArea, "ql:width", area.width);
1014     writeXml(xmlArea, "ql:style", area.style);
1015     writeXml(xmlArea, "ql:opacity", area.opacity);
1016     writeXml(xmlArea, "ql:key", key.item);
1017     writeXml(xmlArea, "ql:flags", flags);
1018     writeXml(xmlArea, history);
1019 
1020     for(const pt_t& pt : qAsConst(area.pts))
1021     {
1022         QDomElement xmlPt = doc.createElement("ql:point");
1023         xmlArea.appendChild(xmlPt);
1024         writeWpt(xmlPt, pt, strictGpx11);
1025     }
1026 }
1027 
readWpt(const QDomNode & xml,wpt_t & wpt)1028 void IGisItem::readWpt(const QDomNode& xml, wpt_t& wpt)
1029 {
1030     const QDomNamedNodeMap& attr = xml.attributes();
1031     wpt.lat = attr.namedItem("lat").nodeValue().toDouble();
1032     wpt.lon = attr.namedItem("lon").nodeValue().toDouble();
1033 
1034     readXml(xml, "ele", wpt.ele);
1035     readXml(xml, "time", wpt.time);
1036     readXml(xml, "magvar", wpt.magvar);
1037     readXml(xml, "geoidheight", wpt.geoidheight);
1038     readXml(xml, "name", wpt.name);
1039     readXml(xml, "cmt", wpt.cmt);
1040     readXml(xml, "desc", wpt.desc);
1041     readXml(xml, "src", wpt.src);
1042     readXml(xml, "link", wpt.links);
1043     readXml(xml, "sym", wpt.sym);
1044     readXml(xml, "type", wpt.type);
1045     readXml(xml, "fix", wpt.fix);
1046     readXml(xml, "sat", wpt.sat);
1047     readXml(xml, "hdop", wpt.hdop);
1048     readXml(xml, "vdop", wpt.vdop);
1049     readXml(xml, "pdop", wpt.pdop);
1050     readXml(xml, "ageofdgpsdata", wpt.ageofdgpsdata);
1051     readXml(xml, "dgpsid", wpt.dgpsid);
1052 
1053     // some GPX 1.0 backward compatibility
1054     QString url;
1055     readXml(xml, "url", url);
1056     if(!url.isEmpty())
1057     {
1058         link_t link;
1059         link.uri.setUrl(url);
1060         readXml(xml, "urlname", link.text);
1061 
1062         wpt.links << link;
1063     }
1064 }
1065 
1066 
writeWpt(QDomElement & xml,const wpt_t & wpt,bool strictGpx11)1067 void IGisItem::writeWpt(QDomElement& xml, const wpt_t& wpt, bool strictGpx11)
1068 {
1069     QString str;
1070 
1071     str.sprintf("%1.8f", wpt.lat);
1072     xml.setAttribute("lat", str);
1073     str.sprintf("%1.8f", wpt.lon);
1074     xml.setAttribute("lon", str);
1075 
1076     writeXml(xml, "ele", wpt.ele);
1077     writeXml(xml, "time", wpt.time);
1078     writeXml(xml, "magvar", wpt.magvar);
1079     writeXml(xml, "geoidheight", wpt.geoidheight);
1080     writeXml(xml, "name", wpt.name);
1081     writeXml(xml, "cmt", html2Dev(wpt.cmt, strictGpx11));
1082     writeXml(xml, "desc", html2Dev(wpt.desc, strictGpx11));
1083     if(isOnDevice() != IDevice::eTypeGarmin)
1084     {
1085         writeXml(xml, "src", wpt.src);
1086     }
1087     writeXml(xml, "link", wpt.links);
1088     writeXml(xml, "sym", wpt.sym);
1089     writeXml(xml, "type", wpt.type);
1090     writeXml(xml, "fix", wpt.fix);
1091     writeXml(xml, "sat", wpt.sat);
1092     writeXml(xml, "hdop", wpt.hdop);
1093     writeXml(xml, "vdop", wpt.vdop);
1094     writeXml(xml, "pdop", wpt.pdop);
1095     writeXml(xml, "ageofdgpsdata", wpt.ageofdgpsdata);
1096     writeXml(xml, "dgpsid", wpt.dgpsid);
1097 }
1098 
1099 
createAdventureFromProject(IGisProject * project,const QString & gpxFilename)1100 void CDeviceGarmin::createAdventureFromProject(IGisProject* project, const QString& gpxFilename)
1101 {
1102     if(pathAdventures.isEmpty())
1103     {
1104         return;
1105     }
1106 
1107     QDomDocument doc;
1108 
1109     QDomElement adventure = doc.createElement("Adventure");
1110     doc.appendChild(adventure);
1111     adventure.setAttribute("xmlns", "http://www.garmin.com/xmlschemas/GarminAdventure/v1");
1112 
1113     writeXml(adventure, "GlobalId", project->getKey());
1114     writeXml(adventure, "Name", project->getName());
1115 
1116     QDomElement item = doc.createElement("Item");
1117     adventure.appendChild(item);
1118     writeXml(item, "DataType", "GPSData");
1119     writeXml(item, "Location", gpxFilename);
1120 
1121     writeXml(adventure, "Description", IGisItem::removeHtml(project->getDescription()));
1122 
1123     const int N = project->childCount();
1124     for(int i = 0; i < N; i++)
1125     {
1126         CGisItemTrk* track = dynamic_cast<CGisItemTrk*>(project->child(i));
1127         if(track != nullptr)
1128         {
1129             const CTrackData& trk = track->getTrackData();
1130             if(trk.segs.isEmpty())
1131             {
1132                 continue;
1133             }
1134 
1135             if(trk.segs.first().pts.isEmpty())
1136             {
1137                 continue;
1138             }
1139 
1140             const CTrackData::trkpt_t& origin = trk.segs.first().pts.first();
1141 
1142             QDomElement startPosition = doc.createElement("StartPosition");
1143             adventure.appendChild(startPosition);
1144             writeXml(startPosition, "Lat", origin.lat);
1145             writeXml(startPosition, "Lon", origin.lon);
1146 
1147             writeXml(adventure, "Activity", tr("Unknown"));
1148             writeXml(adventure, "Distance", track->getTotalDistance());
1149             writeXml(adventure, "Duration", track->getTotalElapsedSecondsMoving());
1150             writeXml(adventure, "Ascent", track->getTotalAscent());
1151             writeXml(adventure, "Descent", track->getTotalDescent());
1152             writeXml(adventure, "Difficulty", 1);
1153             writeXml(adventure, "NumRatings", 0);
1154             writeXml(adventure, "MainTrackId", track->getName());
1155 
1156             QDomElement waypointOrder = doc.createElement("WaypointOrder");
1157             adventure.appendChild(waypointOrder);
1158 
1159             for(const CTrackData::trkpt_t& trkpt : trk)
1160             {
1161                 if(trkpt.keyWpt.item.isEmpty())
1162                 {
1163                     continue;
1164                 }
1165 
1166                 const CGisItemWpt* wpt = dynamic_cast<CGisItemWpt*>(project->getItemByKey(trkpt.keyWpt));
1167                 if(wpt == nullptr)
1168                 {
1169                     continue;
1170                 }
1171 
1172                 QDomElement waypoints = doc.createElement("Waypoints");
1173                 waypointOrder.appendChild(waypoints);
1174 
1175                 writeXml(waypoints, "ID", wpt->getName());
1176                 writeXml(waypoints, "DistanceFromOrigin", trkpt.distance);
1177             }
1178 
1179             break;
1180         }
1181     }
1182 
1183 
1184     const QDir dirAdventures(dir.absoluteFilePath(pathAdventures));
1185     QString filename = dirAdventures.absoluteFilePath(project->getKey() + ".adv");
1186     QFile file(filename);
1187 
1188     CDeviceMountLock mountLock(*this);
1189 
1190     file.open(QIODevice::WriteOnly);
1191     QTextStream out(&file);
1192     out.setCodec("UTF-8");
1193     out << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>" << endl;
1194     out << doc.toString();
1195     file.close();
1196 }
1197