1 /*
2 * Copyright 2013-2019 Kai Pastor
3 *
4 * This file is part of OpenOrienteering.
5 *
6 * OpenOrienteering is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * OpenOrienteering is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with OpenOrienteering. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #ifndef OPENORIENTEERING_XML_STREAM_UTIL_H
21 #define OPENORIENTEERING_XML_STREAM_UTIL_H
22
23 #include <QtGlobal>
24 #include <QHash>
25 #include <QLatin1String>
26 #include <QRectF>
27 #include <QSizeF>
28 #include <QString>
29 #include <QStringRef>
30 #include <QXmlStreamAttributes>
31 #include <QXmlStreamReader>
32 #include <QXmlStreamWriter>
33
34 #include "core/map_coord.h"
35
36 class QRectF;
37 class QSizeF;
38 class QXmlStreamReader;
39 class QXmlStreamWriter;
40
41 namespace OpenOrienteering {
42
43
44 /**
45 * Writes a line break to the XML stream unless auto formatting is active.
46 */
47 void writeLineBreak(QXmlStreamWriter& xml);
48
49
50 /**
51 * Returns the number of characters which are significant for input/output.
52 */
53 QString numberToString(double value, int precision);
54
55
56 /**
57 * This class provides recovery from invalid characters in an XML stream.
58 *
59 * Some characters are not allowed in well-formed XML 1.0 (cf.
60 * https://www.w3.org/TR/2008/REC-xml-20081126/#NT-Char). While QXmlStreamWriter
61 * will not complain when writing such characters, QXmlStreamReader will raise
62 * a NotWellFormedError. This class will remove offending characters from the
63 * input and reset the stream reader to the state it had when the helper object
64 * was initialized.
65 *
66 * In a single recovery attempt, the utility tries to handle all offending
67 * characters from the element for which the tool was constructed. For each
68 * offending character, the whole XML data is parsed again from the start.
69 * That's why multiple corrections may take a long time to run.
70 *
71 * The XML stream must be based on a QIODevice which supports QIODevice::seek.
72 *
73 * Synopsis:
74 *
75 * XmlRecoveryHelper recovery(xml);
76 * auto text = xml.readElementText();
77 * if (xml.hasError() && recovery())
78 * {
79 * addWarning(tr("Some invalid characters had to be removed.");
80 * text = xml.readElementText();
81 * }
82 */
83 class XmlRecoveryHelper
84 {
85 public:
86 /**
87 * Constructs a new recovery helper for the given xml stream.
88 *
89 * Captures the current position in the XML stream (QXmlStreamReader::characterOffset()).
90 */
XmlRecoveryHelper(QXmlStreamReader & xml)91 XmlRecoveryHelper(QXmlStreamReader& xml) : xml (xml), recovery_start {xml.characterOffset()} {}
92
93 /**
94 * Checks the stream for an error which this utility can handle,
95 * applies corrections, and resets the stream.
96 *
97 * If this operator returns false if either there was a different type of
98 * error, or if recovery failed. If it returns true, the stream was modified
99 * in order to fix the errors which are handled by this utility, and a new
100 * attempt can be made to parse the remainder of the stream.
101 */
102 bool operator() ();
103
104 private:
105 QXmlStreamReader& xml;
106 const qint64 recovery_start;
107 };
108
109
110
111 /**
112 * The XmlElementWriter helps to construct a single element in an XML document.
113 *
114 * It starts a new element on a QXmlStreamWriter when it is constructed,
115 * and it writes the end tag when it is destructed. After construction, but
116 * before (child) elements are created on the QXmlStreamWriter, it offers
117 * convenient functions for writing named attributes of common types.
118 *
119 * Typical use:
120 *
121 * \code
122 {
123 // Construction, begins with start tag
124 XmlElementWriter coord(xml_writer, QLatin1String("coord"));
125 coord.writeAttribute(QLatin1String("x"), 34);
126 coord.writeAttribute(QLatin1String("y"), 3.4);
127
128 // Don't use coord once you wrote other data to the stream
129 writeChildElements(xml_writer);
130
131 } // coord goes out of scope here, destructor called, end tag written
132 * \endcode
133 */
134 class XmlElementWriter
135 {
136 public:
137 /**
138 * Begins a new element with the given name on the XML writer.
139 */
140 XmlElementWriter(QXmlStreamWriter& xml, const QLatin1String& element_name);
141
142 XmlElementWriter(const XmlElementWriter&) = delete;
143 XmlElementWriter(XmlElementWriter&&) = delete;
144
145 /**
146 * Writes the end tag of the element.
147 */
148 ~XmlElementWriter();
149
150
151 XmlElementWriter& operator=(const XmlElementWriter&) = delete;
152 XmlElementWriter& operator=(XmlElementWriter&&) = delete;
153
154
155 /**
156 * Writes an attribute with the given name and value.
157 */
158 void writeAttribute(const QLatin1String& qualifiedName, const char* value);
159
160 /**
161 * Writes an attribute with the given name and value.
162 */
163 void writeAttribute(const QLatin1String& qualifiedName, const QString& value);
164
165 /**
166 * Writes an attribute with the given name and value.
167 * This methods uses Qt's default QString::number(double) implementation.
168 */
169 void writeAttribute(const QLatin1String& qualifiedName, const double value);
170
171 /**
172 * Writes an attribute with the given name and value.
173 * The precision represents the number of digits after the decimal point.
174 */
175 void writeAttribute(const QLatin1String& qualifiedName, const double value, int precision);
176
177 /**
178 * Writes an attribute with the given name and value.
179 * This methods uses Qt's default QString::number(float) implementation.
180 */
181 void writeAttribute(const QLatin1String& qualifiedName, const float value);
182
183 /**
184 * Writes an attribute with the given name and value.
185 * The precision represents the number of digits after the decimal point.
186 */
187 void writeAttribute(const QLatin1String& qualifiedName, const float value, int precision);
188
189 /**
190 * Writes an attribute with the given name and value.
191 */
192 void writeAttribute(const QLatin1String& qualifiedName, const qint64 value);
193
194 /**
195 * Writes an attribute with the given name and value.
196 */
197 void writeAttribute(const QLatin1String& qualifiedName, const int value);
198
199 /**
200 * Writes an attribute with the given name and value.
201 */
202 void writeAttribute(const QLatin1String& qualifiedName, const unsigned int value);
203
204 /**
205 * Writes an attribute with the given name and value.
206 */
207 void writeAttribute(const QLatin1String& qualifiedName, const long unsigned int value);
208
209 /**
210 * Writes an attribute with the given name and value.
211 */
212 void writeAttribute(const QLatin1String& qualifiedName, const quint64 value);
213
214 /**
215 * Writes an attribute with the given name and value.
216 */
217 void writeAttribute(const QLatin1String& qualifiedName, bool value);
218
219 /**
220 * Writes attributes named left, top, width and height,
221 * representing the given area.
222 * This methods uses Qt's default QString::number(qreal) implementation.
223 */
224 void write(const QRectF& area);
225
226 /**
227 * Writes attributes named left, top, width and height,
228 * representing the given area.
229 */
230 void write(const QRectF& area, int precision);
231
232 /**
233 * Writes attributes named width and height, representing the given size.
234 * This methods uses Qt's default QString::number(qreal) implementation.
235 */
236 void write(const QSizeF& size);
237
238 /**
239 * Writes attributes named width and height, representing the given size.
240 */
241 void write(const QSizeF& size, int precision);
242
243 /**
244 * Writes the coordinates vector as a simple text format.
245 * This is much more efficient than saving each coordinate as rich XML.
246 */
247 void write(const MapCoordVector& coords);
248
249 /**
250 * Writes tags.
251 */
252 void write(const QHash<QString, QString>& tags);
253
254 private:
255 QXmlStreamWriter& xml;
256 };
257
258
259 /**
260 * The XmlElementReader helps to read a single element in an XML document.
261 *
262 * It assumes to be on a QXmlStreamReader::StartElement when constructed,
263 * and it reads until the end of the current element, skipping any child nodes,
264 * when it is destructed. After construction, it offers convenient functions
265 * for reading named attributes of common types.
266 *
267 * Typical use:
268 *
269 * \code
270 while (xml_reader.readNextStartElement())
271 {
272 // Construction, begins with start tag
273 XmlElementReader coord(xml_reader);
274 int x = coord.attribute<int>(QLatin1String("x"));
275 double y = coord.attribute<double>(QLatin1String("y"));
276 FlagEnum flags = coord.attribute<FlagEnum>(QLatin1String("flags"));
277
278 readChildData(xml_reader);
279
280 } // coord goes out of scope here, destructor called, reads until end of element
281 * \endcode
282 */
283 class XmlElementReader
284 {
285 public:
286 /**
287 * Constructs a new element reader on the given XML reader.
288 *
289 * It assumes to be on a QXmlStreamReader::StartElement.
290 */
291 XmlElementReader(QXmlStreamReader& xml);
292
293 XmlElementReader(const XmlElementReader&) = delete;
294 XmlElementReader(XmlElementReader&&) = delete;
295
296 /**
297 * Destructor.
298 *
299 * Reads until the end of the current element, skipping any child nodes.
300 */
301 ~XmlElementReader();
302
303
304 XmlElementReader& operator=(const XmlElementReader&) = delete;
305 XmlElementReader& operator=(XmlElementReader&&) = delete;
306
307
308 /**
309 * Tests whether the element has an attribute with the given name.
310 */
311 bool hasAttribute(const QLatin1String& qualifiedName) const;
312
313 /**
314 * Tests whether the element has an attribute with the given name.
315 */
316 bool hasAttribute(const QString& qualifiedName) const;
317
318 /**
319 * Returns the value of an attribute of type T.
320 *
321 * Apart from a number of specializations for common types,
322 * it has a general implementation which read the attribute as int
323 * and does static_cast to the actual type. This is useful for enumerations,
324 * but might also be a cause of buildtime or runtime errors.
325 */
326 template< typename T >
327 T attribute(const QLatin1String& qualifiedName) const;
328
329 /**
330 * Reads attributes named left, top, width, height into the given area object.
331 * Counterpart for XmlElementWriter::write(const QRectF&, int).
332 */
333 void read(QRectF& area);
334
335 /**
336 * Reads attributes named width and height into the given size object.
337 * Counterpart for XmlElementWriter::write(const QSizeF&, int).
338 */
339 void read(QSizeF& size);
340
341 /**
342 * Reads the coordinates vector from a simple text format.
343 * This is much more efficient than loading each coordinate from rich XML.
344 */
345 void read(MapCoordVector& coords);
346
347 /**
348 * Reads the coordinates vector for a text object.
349 *
350 * This is either a single anchor, or an anchor and a size, packed as a
351 * coordinates vector. Regular coordinate bounds checking is not applied
352 * to the size.
353 *
354 * \todo Make box size explicit data.
355 *
356 * \see read(MapCoordVector&)
357 */
358 void readForText(MapCoordVector& coords);
359
360 /**
361 * Read tags.
362 */
363 void read(QHash<QString, QString>& tags);
364
365 private:
366 QXmlStreamReader& xml;
367 const QXmlStreamAttributes attributes; // implicitly shared QVector
368 };
369
370
371 /**
372 * @namespace literal
373 * @brief Namespace for \c QLatin1String constants
374 *
375 * It's current main use is in connection with XMLFileFormat.
376 * XMLFileFormat is built on \c QXmlStreamReader/\c QXmlStreamWriter which
377 * expect \c QLatin1String arguments in many places.
378 * In addition, a \c QLatin1String can be compared to a \c QStringRef without
379 * implicit conversion.
380 *
381 * The namespace \c literal cannot be used directly in header files because it
382 * would easily lead to name conflicts in including files.
383 * However, custom namespaces in header files can be aliased to \c literal
384 * locally in method definitions:
385 *
386 * \code
387 * void someFuntion()
388 * {
389 * namespace literal = XmlStreamLiteral;
390 * writeAttribute(literal::left, 37.0);
391 * }
392 * \endcode
393 *
394 * @sa MapCoordLiteral, XmlStreamLiteral
395 */
396
397
398 /**
399 * @brief Local namespace for \c QLatin1String constants
400 *
401 * This namespace collects various \c QLatin1String constants in xml_stream_util.h.
402 * The namespace \link literal \endlink cannot be used directly in the
403 * xml_stream_util.h header because it would easily lead to name conflicts
404 * in including files. However, XmlStreamLiteral can be aliased to \c literal
405 * locally in method definitions:
406 *
407 * \code
408 * void someFuntion()
409 * {
410 * namespace literal = XmlStreamLiteral;
411 * writeAttribute(literal::left, 37.0);
412 * }
413 * \endcode
414 *
415 * @sa literal
416 */
417 namespace XmlStreamLiteral
418 {
419 static const QLatin1String string_true("true");
420
421 static const QLatin1String left("left");
422 static const QLatin1String top("top");
423 static const QLatin1String width("width");
424 static const QLatin1String height("height");
425
426 static const QLatin1String count("count");
427
428 static const QLatin1String object("object");
429 static const QLatin1String tags("tags");
430 static const QLatin1String tag("tag"); ///< @deprecated
431 static const QLatin1String key("key"); ///< @deprecated
432 static const QLatin1String t("t");
433 static const QLatin1String k("k");
434
435 static const QLatin1String coord("coord");
436 }
437
438
439
440 //### XmlElementWriter inline implemenentation ###
441
442 inline
XmlElementWriter(QXmlStreamWriter & xml,const QLatin1String & element_name)443 XmlElementWriter::XmlElementWriter(QXmlStreamWriter& xml, const QLatin1String& element_name)
444 : xml(xml)
445 {
446 xml.writeStartElement(element_name);
447 }
448
449 inline
~XmlElementWriter()450 XmlElementWriter::~XmlElementWriter()
451 {
452 xml.writeEndElement();
453 }
454
455 inline
writeAttribute(const QLatin1String & qualifiedName,const char * value)456 void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const char* value)
457 {
458 xml.writeAttribute(qualifiedName, QString::fromUtf8(value));
459 }
460
461 inline
writeAttribute(const QLatin1String & qualifiedName,const QString & value)462 void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const QString& value)
463 {
464 xml.writeAttribute(qualifiedName, value);
465 }
466
467 inline
writeAttribute(const QLatin1String & qualifiedName,const double value)468 void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const double value)
469 {
470 xml.writeAttribute(qualifiedName, QString::number(value));
471 }
472
473 inline
writeAttribute(const QLatin1String & qualifiedName,const double value,int precision)474 void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const double value, int precision)
475 {
476 xml.writeAttribute(qualifiedName, numberToString(value, precision));
477 }
478
479 inline
writeAttribute(const QLatin1String & qualifiedName,const float value)480 void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const float value)
481 {
482 writeAttribute(qualifiedName, double(value));
483 }
484
485 inline
writeAttribute(const QLatin1String & qualifiedName,const float value,int precision)486 void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const float value, int precision)
487 {
488 writeAttribute(qualifiedName, double(value), precision);
489 }
490
491 inline
writeAttribute(const QLatin1String & qualifiedName,const qint64 value)492 void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const qint64 value)
493 {
494 xml.writeAttribute(qualifiedName, QString::number(value));
495 }
496
497 inline
writeAttribute(const QLatin1String & qualifiedName,const int value)498 void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const int value)
499 {
500 xml.writeAttribute(qualifiedName, QString::number(value));
501 }
502
503 inline
writeAttribute(const QLatin1String & qualifiedName,const unsigned int value)504 void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const unsigned int value)
505 {
506 xml.writeAttribute(qualifiedName, QString::number(value));
507 }
508
509 inline
writeAttribute(const QLatin1String & qualifiedName,const long unsigned int value)510 void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const long unsigned int value)
511 {
512 xml.writeAttribute(qualifiedName, QString::number(value));
513 }
514
515 inline
writeAttribute(const QLatin1String & qualifiedName,const quint64 value)516 void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const quint64 value)
517 {
518 xml.writeAttribute(qualifiedName, QString::number(value));
519 }
520
521 inline
writeAttribute(const QLatin1String & qualifiedName,bool value)522 void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, bool value)
523 {
524 namespace literal = XmlStreamLiteral;
525
526 if (value)
527 xml.writeAttribute(qualifiedName, literal::string_true);
528 }
529
530 inline
write(const QRectF & area)531 void XmlElementWriter::write(const QRectF& area)
532 {
533 namespace literal = XmlStreamLiteral;
534
535 writeAttribute( literal::left, area.left());
536 writeAttribute( literal::top, area.top());
537 writeAttribute( literal::width, area.width());
538 writeAttribute( literal::height, area.height());
539 }
540
541 inline
write(const QRectF & area,int precision)542 void XmlElementWriter::write(const QRectF& area, int precision)
543 {
544 namespace literal = XmlStreamLiteral;
545
546 writeAttribute( literal::left, area.left(), precision );
547 writeAttribute( literal::top, area.top(), precision );
548 writeAttribute( literal::width, area.width(), precision );
549 writeAttribute( literal::height, area.height(), precision );
550 }
551
552 inline
write(const QSizeF & size)553 void XmlElementWriter::write(const QSizeF& size)
554 {
555 namespace literal = XmlStreamLiteral;
556
557 writeAttribute( literal::width, size.width());
558 writeAttribute( literal::height, size.height());
559 }
560
561 inline
write(const QSizeF & size,int precision)562 void XmlElementWriter::write(const QSizeF& size, int precision)
563 {
564 namespace literal = XmlStreamLiteral;
565
566 writeAttribute( literal::width, size.width(), precision );
567 writeAttribute( literal::height, size.height(), precision );
568 }
569
570 inline
write(const QHash<QString,QString> & tags)571 void XmlElementWriter::write(const QHash<QString, QString> &tags)
572 {
573 namespace literal = XmlStreamLiteral;
574 typedef QHash<QString, QString> Tags;
575
576 for (Tags::const_iterator tag = tags.constBegin(), end = tags.constEnd(); tag != end; ++tag)
577 {
578 XmlElementWriter tag_element(xml, literal::t);
579 tag_element.writeAttribute(literal::k, tag.key());
580 xml.writeCharacters(tag.value());
581 }
582 }
583
584 //### XmlElementReader inline implemenentation ###
585
586 inline
XmlElementReader(QXmlStreamReader & xml)587 XmlElementReader::XmlElementReader(QXmlStreamReader& xml)
588 : xml(xml),
589 attributes(xml.attributes())
590 {
591 }
592
593 inline
~XmlElementReader()594 XmlElementReader::~XmlElementReader()
595 {
596 if (!xml.isEndElement())
597 xml.skipCurrentElement();
598 }
599
600 inline
hasAttribute(const QLatin1String & qualifiedName)601 bool XmlElementReader::hasAttribute(const QLatin1String& qualifiedName) const
602 {
603 return attributes.hasAttribute(qualifiedName);
604 }
605
606 inline
hasAttribute(const QString & qualifiedName)607 bool XmlElementReader::hasAttribute(const QString& qualifiedName) const
608 {
609 return attributes.hasAttribute(qualifiedName);
610 }
611
612 template< >
613 inline
attribute(const QLatin1String & qualifiedName)614 QString XmlElementReader::attribute(const QLatin1String& qualifiedName) const
615 {
616 return attributes.value(qualifiedName).toString();
617 }
618
619 template< >
620 inline
attribute(const QLatin1String & qualifiedName)621 qint64 XmlElementReader::attribute(const QLatin1String& qualifiedName) const
622 {
623 qint64 value = 0;
624 const QStringRef ref = attributes.value(qualifiedName);
625 if (ref.size())
626 value = QString::fromRawData(ref.data(), ref.size()).toLongLong();
627 return value;
628 }
629
630 template< >
631 inline
attribute(const QLatin1String & qualifiedName)632 int XmlElementReader::attribute(const QLatin1String& qualifiedName) const
633 {
634 int value = 0;
635 const QStringRef ref = attributes.value(qualifiedName);
636 if (ref.size())
637 value = QString::fromRawData(ref.data(), ref.size()).toInt();
638 return value;
639 }
640
641 template< >
642 inline
attribute(const QLatin1String & qualifiedName)643 unsigned int XmlElementReader::attribute(const QLatin1String& qualifiedName) const
644 {
645 unsigned int value = 0;
646 const QStringRef ref = attributes.value(qualifiedName);
647 if (ref.size())
648 value = QString::fromRawData(ref.data(), ref.size()).toUInt();
649 return value;
650 }
651
652 template< >
653 inline
attribute(const QLatin1String & qualifiedName)654 long unsigned int XmlElementReader::attribute(const QLatin1String& qualifiedName) const
655 {
656 unsigned int value = 0;
657 const QStringRef ref = attributes.value(qualifiedName);
658 if (ref.size())
659 value = QString::fromRawData(ref.data(), ref.size()).toUInt();
660 return value;
661 }
662
663 template< >
664 inline
attribute(const QLatin1String & qualifiedName)665 double XmlElementReader::attribute(const QLatin1String& qualifiedName) const
666 {
667 double value = 0;
668 const QStringRef ref = attributes.value(qualifiedName);
669 if (ref.size())
670 value = QString::fromRawData(ref.data(), ref.size()).toDouble();
671 return value;
672 }
673
674 template< >
675 inline
attribute(const QLatin1String & qualifiedName)676 float XmlElementReader::attribute(const QLatin1String& qualifiedName) const
677 {
678 float value = 0;
679 const QStringRef ref = attributes.value(qualifiedName);
680 if (ref.size())
681 value = QString::fromRawData(ref.data(), ref.size()).toFloat();
682 return value;
683 }
684
685 template< >
686 inline
attribute(const QLatin1String & qualifiedName)687 bool XmlElementReader::attribute(const QLatin1String& qualifiedName) const
688 {
689 namespace literal = XmlStreamLiteral;
690
691 bool value = (attributes.value(qualifiedName) == literal::string_true);
692 return value;
693 }
694
695 template< >
696 inline
attribute(const QLatin1String & qualifiedName)697 QStringRef XmlElementReader::attribute(const QLatin1String& qualifiedName) const
698 {
699 return attributes.value(qualifiedName);
700 }
701
702 template< typename T >
703 inline
attribute(const QLatin1String & qualifiedName)704 T XmlElementReader::attribute(const QLatin1String& qualifiedName) const
705 {
706 T value = static_cast<T>(0);
707 const QStringRef ref = attributes.value(qualifiedName);
708 if (ref.size())
709 value = static_cast<T>(QString::fromRawData(ref.data(), ref.size()).toInt());
710 return value;
711 }
712
713 inline
read(QRectF & area)714 void XmlElementReader::read(QRectF& area)
715 {
716 namespace literal = XmlStreamLiteral;
717
718 QStringRef ref = attributes.value(literal::left);
719 area.setLeft(QString::fromRawData(ref.data(), ref.size()).toDouble());
720 ref = attributes.value(literal::top);
721 area.setTop(QString::fromRawData(ref.data(), ref.size()).toDouble());
722 ref = attributes.value(literal::width);
723 area.setWidth(QString::fromRawData(ref.data(), ref.size()).toDouble());
724 ref = attributes.value(literal::height);
725 area.setHeight(QString::fromRawData(ref.data(), ref.size()).toDouble());
726 }
727
728 inline
read(QSizeF & size)729 void XmlElementReader::read(QSizeF& size)
730 {
731 namespace literal = XmlStreamLiteral;
732
733 QStringRef ref = attributes.value(literal::width);
734 size.setWidth(QString::fromRawData(ref.data(), ref.size()).toDouble());
735 ref = attributes.value(literal::height);
736 size.setHeight(QString::fromRawData(ref.data(), ref.size()).toDouble());
737 }
738
739 inline
read(QHash<QString,QString> & tags)740 void XmlElementReader::read(QHash<QString, QString> &tags)
741 {
742 namespace literal = XmlStreamLiteral;
743
744 tags.clear();
745 while (xml.readNextStartElement())
746 {
747 if (xml.name() == literal::t)
748 {
749 const QString key(xml.attributes().value(literal::k).toString());
750 tags.insert(key, xml.readElementText());
751 }
752 else if (xml.name() == literal::tag)
753 {
754 // Full keywords were used in pre-0.6.0 master branch
755 // TODO Remove after Mapper 0.6.x releases
756 const QString key(xml.attributes().value(literal::key).toString());
757 tags.insert(key, xml.readElementText());
758 }
759 else if (xml.name() == literal::tags)
760 {
761 // Fix for broken Object::save in pre-0.6.0 master branch
762 // TODO Remove after Mapper 0.6.x releases
763 const QString key(xml.attributes().value(literal::key).toString());
764 tags.insert(key, xml.readElementText());
765 }
766 else
767 xml.skipCurrentElement();
768 }
769 }
770
771
772 } // namespace OpenOrienteering
773
774 #endif
775