/*
* Copyright 2013-2019 Kai Pastor
*
* This file is part of OpenOrienteering.
*
* OpenOrienteering is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenOrienteering is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenOrienteering. If not, see .
*/
#ifndef OPENORIENTEERING_XML_STREAM_UTIL_H
#define OPENORIENTEERING_XML_STREAM_UTIL_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "core/map_coord.h"
class QRectF;
class QSizeF;
class QXmlStreamReader;
class QXmlStreamWriter;
namespace OpenOrienteering {
/**
* Writes a line break to the XML stream unless auto formatting is active.
*/
void writeLineBreak(QXmlStreamWriter& xml);
/**
* Returns the number of characters which are significant for input/output.
*/
QString numberToString(double value, int precision);
/**
* This class provides recovery from invalid characters in an XML stream.
*
* Some characters are not allowed in well-formed XML 1.0 (cf.
* https://www.w3.org/TR/2008/REC-xml-20081126/#NT-Char). While QXmlStreamWriter
* will not complain when writing such characters, QXmlStreamReader will raise
* a NotWellFormedError. This class will remove offending characters from the
* input and reset the stream reader to the state it had when the helper object
* was initialized.
*
* In a single recovery attempt, the utility tries to handle all offending
* characters from the element for which the tool was constructed. For each
* offending character, the whole XML data is parsed again from the start.
* That's why multiple corrections may take a long time to run.
*
* The XML stream must be based on a QIODevice which supports QIODevice::seek.
*
* Synopsis:
*
* XmlRecoveryHelper recovery(xml);
* auto text = xml.readElementText();
* if (xml.hasError() && recovery())
* {
* addWarning(tr("Some invalid characters had to be removed.");
* text = xml.readElementText();
* }
*/
class XmlRecoveryHelper
{
public:
/**
* Constructs a new recovery helper for the given xml stream.
*
* Captures the current position in the XML stream (QXmlStreamReader::characterOffset()).
*/
XmlRecoveryHelper(QXmlStreamReader& xml) : xml (xml), recovery_start {xml.characterOffset()} {}
/**
* Checks the stream for an error which this utility can handle,
* applies corrections, and resets the stream.
*
* If this operator returns false if either there was a different type of
* error, or if recovery failed. If it returns true, the stream was modified
* in order to fix the errors which are handled by this utility, and a new
* attempt can be made to parse the remainder of the stream.
*/
bool operator() ();
private:
QXmlStreamReader& xml;
const qint64 recovery_start;
};
/**
* The XmlElementWriter helps to construct a single element in an XML document.
*
* It starts a new element on a QXmlStreamWriter when it is constructed,
* and it writes the end tag when it is destructed. After construction, but
* before (child) elements are created on the QXmlStreamWriter, it offers
* convenient functions for writing named attributes of common types.
*
* Typical use:
*
* \code
{
// Construction, begins with start tag
XmlElementWriter coord(xml_writer, QLatin1String("coord"));
coord.writeAttribute(QLatin1String("x"), 34);
coord.writeAttribute(QLatin1String("y"), 3.4);
// Don't use coord once you wrote other data to the stream
writeChildElements(xml_writer);
} // coord goes out of scope here, destructor called, end tag written
* \endcode
*/
class XmlElementWriter
{
public:
/**
* Begins a new element with the given name on the XML writer.
*/
XmlElementWriter(QXmlStreamWriter& xml, const QLatin1String& element_name);
XmlElementWriter(const XmlElementWriter&) = delete;
XmlElementWriter(XmlElementWriter&&) = delete;
/**
* Writes the end tag of the element.
*/
~XmlElementWriter();
XmlElementWriter& operator=(const XmlElementWriter&) = delete;
XmlElementWriter& operator=(XmlElementWriter&&) = delete;
/**
* Writes an attribute with the given name and value.
*/
void writeAttribute(const QLatin1String& qualifiedName, const char* value);
/**
* Writes an attribute with the given name and value.
*/
void writeAttribute(const QLatin1String& qualifiedName, const QString& value);
/**
* Writes an attribute with the given name and value.
* This methods uses Qt's default QString::number(double) implementation.
*/
void writeAttribute(const QLatin1String& qualifiedName, const double value);
/**
* Writes an attribute with the given name and value.
* The precision represents the number of digits after the decimal point.
*/
void writeAttribute(const QLatin1String& qualifiedName, const double value, int precision);
/**
* Writes an attribute with the given name and value.
* This methods uses Qt's default QString::number(float) implementation.
*/
void writeAttribute(const QLatin1String& qualifiedName, const float value);
/**
* Writes an attribute with the given name and value.
* The precision represents the number of digits after the decimal point.
*/
void writeAttribute(const QLatin1String& qualifiedName, const float value, int precision);
/**
* Writes an attribute with the given name and value.
*/
void writeAttribute(const QLatin1String& qualifiedName, const qint64 value);
/**
* Writes an attribute with the given name and value.
*/
void writeAttribute(const QLatin1String& qualifiedName, const int value);
/**
* Writes an attribute with the given name and value.
*/
void writeAttribute(const QLatin1String& qualifiedName, const unsigned int value);
/**
* Writes an attribute with the given name and value.
*/
void writeAttribute(const QLatin1String& qualifiedName, const long unsigned int value);
/**
* Writes an attribute with the given name and value.
*/
void writeAttribute(const QLatin1String& qualifiedName, const quint64 value);
/**
* Writes an attribute with the given name and value.
*/
void writeAttribute(const QLatin1String& qualifiedName, bool value);
/**
* Writes attributes named left, top, width and height,
* representing the given area.
* This methods uses Qt's default QString::number(qreal) implementation.
*/
void write(const QRectF& area);
/**
* Writes attributes named left, top, width and height,
* representing the given area.
*/
void write(const QRectF& area, int precision);
/**
* Writes attributes named width and height, representing the given size.
* This methods uses Qt's default QString::number(qreal) implementation.
*/
void write(const QSizeF& size);
/**
* Writes attributes named width and height, representing the given size.
*/
void write(const QSizeF& size, int precision);
/**
* Writes the coordinates vector as a simple text format.
* This is much more efficient than saving each coordinate as rich XML.
*/
void write(const MapCoordVector& coords);
/**
* Writes tags.
*/
void write(const QHash& tags);
private:
QXmlStreamWriter& xml;
};
/**
* The XmlElementReader helps to read a single element in an XML document.
*
* It assumes to be on a QXmlStreamReader::StartElement when constructed,
* and it reads until the end of the current element, skipping any child nodes,
* when it is destructed. After construction, it offers convenient functions
* for reading named attributes of common types.
*
* Typical use:
*
* \code
while (xml_reader.readNextStartElement())
{
// Construction, begins with start tag
XmlElementReader coord(xml_reader);
int x = coord.attribute(QLatin1String("x"));
double y = coord.attribute(QLatin1String("y"));
FlagEnum flags = coord.attribute(QLatin1String("flags"));
readChildData(xml_reader);
} // coord goes out of scope here, destructor called, reads until end of element
* \endcode
*/
class XmlElementReader
{
public:
/**
* Constructs a new element reader on the given XML reader.
*
* It assumes to be on a QXmlStreamReader::StartElement.
*/
XmlElementReader(QXmlStreamReader& xml);
XmlElementReader(const XmlElementReader&) = delete;
XmlElementReader(XmlElementReader&&) = delete;
/**
* Destructor.
*
* Reads until the end of the current element, skipping any child nodes.
*/
~XmlElementReader();
XmlElementReader& operator=(const XmlElementReader&) = delete;
XmlElementReader& operator=(XmlElementReader&&) = delete;
/**
* Tests whether the element has an attribute with the given name.
*/
bool hasAttribute(const QLatin1String& qualifiedName) const;
/**
* Tests whether the element has an attribute with the given name.
*/
bool hasAttribute(const QString& qualifiedName) const;
/**
* Returns the value of an attribute of type T.
*
* Apart from a number of specializations for common types,
* it has a general implementation which read the attribute as int
* and does static_cast to the actual type. This is useful for enumerations,
* but might also be a cause of buildtime or runtime errors.
*/
template< typename T >
T attribute(const QLatin1String& qualifiedName) const;
/**
* Reads attributes named left, top, width, height into the given area object.
* Counterpart for XmlElementWriter::write(const QRectF&, int).
*/
void read(QRectF& area);
/**
* Reads attributes named width and height into the given size object.
* Counterpart for XmlElementWriter::write(const QSizeF&, int).
*/
void read(QSizeF& size);
/**
* Reads the coordinates vector from a simple text format.
* This is much more efficient than loading each coordinate from rich XML.
*/
void read(MapCoordVector& coords);
/**
* Reads the coordinates vector for a text object.
*
* This is either a single anchor, or an anchor and a size, packed as a
* coordinates vector. Regular coordinate bounds checking is not applied
* to the size.
*
* \todo Make box size explicit data.
*
* \see read(MapCoordVector&)
*/
void readForText(MapCoordVector& coords);
/**
* Read tags.
*/
void read(QHash& tags);
private:
QXmlStreamReader& xml;
const QXmlStreamAttributes attributes; // implicitly shared QVector
};
/**
* @namespace literal
* @brief Namespace for \c QLatin1String constants
*
* It's current main use is in connection with XMLFileFormat.
* XMLFileFormat is built on \c QXmlStreamReader/\c QXmlStreamWriter which
* expect \c QLatin1String arguments in many places.
* In addition, a \c QLatin1String can be compared to a \c QStringRef without
* implicit conversion.
*
* The namespace \c literal cannot be used directly in header files because it
* would easily lead to name conflicts in including files.
* However, custom namespaces in header files can be aliased to \c literal
* locally in method definitions:
*
* \code
* void someFuntion()
* {
* namespace literal = XmlStreamLiteral;
* writeAttribute(literal::left, 37.0);
* }
* \endcode
*
* @sa MapCoordLiteral, XmlStreamLiteral
*/
/**
* @brief Local namespace for \c QLatin1String constants
*
* This namespace collects various \c QLatin1String constants in xml_stream_util.h.
* The namespace \link literal \endlink cannot be used directly in the
* xml_stream_util.h header because it would easily lead to name conflicts
* in including files. However, XmlStreamLiteral can be aliased to \c literal
* locally in method definitions:
*
* \code
* void someFuntion()
* {
* namespace literal = XmlStreamLiteral;
* writeAttribute(literal::left, 37.0);
* }
* \endcode
*
* @sa literal
*/
namespace XmlStreamLiteral
{
static const QLatin1String string_true("true");
static const QLatin1String left("left");
static const QLatin1String top("top");
static const QLatin1String width("width");
static const QLatin1String height("height");
static const QLatin1String count("count");
static const QLatin1String object("object");
static const QLatin1String tags("tags");
static const QLatin1String tag("tag"); ///< @deprecated
static const QLatin1String key("key"); ///< @deprecated
static const QLatin1String t("t");
static const QLatin1String k("k");
static const QLatin1String coord("coord");
}
//### XmlElementWriter inline implemenentation ###
inline
XmlElementWriter::XmlElementWriter(QXmlStreamWriter& xml, const QLatin1String& element_name)
: xml(xml)
{
xml.writeStartElement(element_name);
}
inline
XmlElementWriter::~XmlElementWriter()
{
xml.writeEndElement();
}
inline
void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const char* value)
{
xml.writeAttribute(qualifiedName, QString::fromUtf8(value));
}
inline
void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const QString& value)
{
xml.writeAttribute(qualifiedName, value);
}
inline
void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const double value)
{
xml.writeAttribute(qualifiedName, QString::number(value));
}
inline
void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const double value, int precision)
{
xml.writeAttribute(qualifiedName, numberToString(value, precision));
}
inline
void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const float value)
{
writeAttribute(qualifiedName, double(value));
}
inline
void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const float value, int precision)
{
writeAttribute(qualifiedName, double(value), precision);
}
inline
void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const qint64 value)
{
xml.writeAttribute(qualifiedName, QString::number(value));
}
inline
void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const int value)
{
xml.writeAttribute(qualifiedName, QString::number(value));
}
inline
void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const unsigned int value)
{
xml.writeAttribute(qualifiedName, QString::number(value));
}
inline
void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const long unsigned int value)
{
xml.writeAttribute(qualifiedName, QString::number(value));
}
inline
void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, const quint64 value)
{
xml.writeAttribute(qualifiedName, QString::number(value));
}
inline
void XmlElementWriter::writeAttribute(const QLatin1String& qualifiedName, bool value)
{
namespace literal = XmlStreamLiteral;
if (value)
xml.writeAttribute(qualifiedName, literal::string_true);
}
inline
void XmlElementWriter::write(const QRectF& area)
{
namespace literal = XmlStreamLiteral;
writeAttribute( literal::left, area.left());
writeAttribute( literal::top, area.top());
writeAttribute( literal::width, area.width());
writeAttribute( literal::height, area.height());
}
inline
void XmlElementWriter::write(const QRectF& area, int precision)
{
namespace literal = XmlStreamLiteral;
writeAttribute( literal::left, area.left(), precision );
writeAttribute( literal::top, area.top(), precision );
writeAttribute( literal::width, area.width(), precision );
writeAttribute( literal::height, area.height(), precision );
}
inline
void XmlElementWriter::write(const QSizeF& size)
{
namespace literal = XmlStreamLiteral;
writeAttribute( literal::width, size.width());
writeAttribute( literal::height, size.height());
}
inline
void XmlElementWriter::write(const QSizeF& size, int precision)
{
namespace literal = XmlStreamLiteral;
writeAttribute( literal::width, size.width(), precision );
writeAttribute( literal::height, size.height(), precision );
}
inline
void XmlElementWriter::write(const QHash &tags)
{
namespace literal = XmlStreamLiteral;
typedef QHash Tags;
for (Tags::const_iterator tag = tags.constBegin(), end = tags.constEnd(); tag != end; ++tag)
{
XmlElementWriter tag_element(xml, literal::t);
tag_element.writeAttribute(literal::k, tag.key());
xml.writeCharacters(tag.value());
}
}
//### XmlElementReader inline implemenentation ###
inline
XmlElementReader::XmlElementReader(QXmlStreamReader& xml)
: xml(xml),
attributes(xml.attributes())
{
}
inline
XmlElementReader::~XmlElementReader()
{
if (!xml.isEndElement())
xml.skipCurrentElement();
}
inline
bool XmlElementReader::hasAttribute(const QLatin1String& qualifiedName) const
{
return attributes.hasAttribute(qualifiedName);
}
inline
bool XmlElementReader::hasAttribute(const QString& qualifiedName) const
{
return attributes.hasAttribute(qualifiedName);
}
template< >
inline
QString XmlElementReader::attribute(const QLatin1String& qualifiedName) const
{
return attributes.value(qualifiedName).toString();
}
template< >
inline
qint64 XmlElementReader::attribute(const QLatin1String& qualifiedName) const
{
qint64 value = 0;
const QStringRef ref = attributes.value(qualifiedName);
if (ref.size())
value = QString::fromRawData(ref.data(), ref.size()).toLongLong();
return value;
}
template< >
inline
int XmlElementReader::attribute(const QLatin1String& qualifiedName) const
{
int value = 0;
const QStringRef ref = attributes.value(qualifiedName);
if (ref.size())
value = QString::fromRawData(ref.data(), ref.size()).toInt();
return value;
}
template< >
inline
unsigned int XmlElementReader::attribute(const QLatin1String& qualifiedName) const
{
unsigned int value = 0;
const QStringRef ref = attributes.value(qualifiedName);
if (ref.size())
value = QString::fromRawData(ref.data(), ref.size()).toUInt();
return value;
}
template< >
inline
long unsigned int XmlElementReader::attribute(const QLatin1String& qualifiedName) const
{
unsigned int value = 0;
const QStringRef ref = attributes.value(qualifiedName);
if (ref.size())
value = QString::fromRawData(ref.data(), ref.size()).toUInt();
return value;
}
template< >
inline
double XmlElementReader::attribute(const QLatin1String& qualifiedName) const
{
double value = 0;
const QStringRef ref = attributes.value(qualifiedName);
if (ref.size())
value = QString::fromRawData(ref.data(), ref.size()).toDouble();
return value;
}
template< >
inline
float XmlElementReader::attribute(const QLatin1String& qualifiedName) const
{
float value = 0;
const QStringRef ref = attributes.value(qualifiedName);
if (ref.size())
value = QString::fromRawData(ref.data(), ref.size()).toFloat();
return value;
}
template< >
inline
bool XmlElementReader::attribute(const QLatin1String& qualifiedName) const
{
namespace literal = XmlStreamLiteral;
bool value = (attributes.value(qualifiedName) == literal::string_true);
return value;
}
template< >
inline
QStringRef XmlElementReader::attribute(const QLatin1String& qualifiedName) const
{
return attributes.value(qualifiedName);
}
template< typename T >
inline
T XmlElementReader::attribute(const QLatin1String& qualifiedName) const
{
T value = static_cast(0);
const QStringRef ref = attributes.value(qualifiedName);
if (ref.size())
value = static_cast(QString::fromRawData(ref.data(), ref.size()).toInt());
return value;
}
inline
void XmlElementReader::read(QRectF& area)
{
namespace literal = XmlStreamLiteral;
QStringRef ref = attributes.value(literal::left);
area.setLeft(QString::fromRawData(ref.data(), ref.size()).toDouble());
ref = attributes.value(literal::top);
area.setTop(QString::fromRawData(ref.data(), ref.size()).toDouble());
ref = attributes.value(literal::width);
area.setWidth(QString::fromRawData(ref.data(), ref.size()).toDouble());
ref = attributes.value(literal::height);
area.setHeight(QString::fromRawData(ref.data(), ref.size()).toDouble());
}
inline
void XmlElementReader::read(QSizeF& size)
{
namespace literal = XmlStreamLiteral;
QStringRef ref = attributes.value(literal::width);
size.setWidth(QString::fromRawData(ref.data(), ref.size()).toDouble());
ref = attributes.value(literal::height);
size.setHeight(QString::fromRawData(ref.data(), ref.size()).toDouble());
}
inline
void XmlElementReader::read(QHash &tags)
{
namespace literal = XmlStreamLiteral;
tags.clear();
while (xml.readNextStartElement())
{
if (xml.name() == literal::t)
{
const QString key(xml.attributes().value(literal::k).toString());
tags.insert(key, xml.readElementText());
}
else if (xml.name() == literal::tag)
{
// Full keywords were used in pre-0.6.0 master branch
// TODO Remove after Mapper 0.6.x releases
const QString key(xml.attributes().value(literal::key).toString());
tags.insert(key, xml.readElementText());
}
else if (xml.name() == literal::tags)
{
// Fix for broken Object::save in pre-0.6.0 master branch
// TODO Remove after Mapper 0.6.x releases
const QString key(xml.attributes().value(literal::key).toString());
tags.insert(key, xml.readElementText());
}
else
xml.skipCurrentElement();
}
}
} // namespace OpenOrienteering
#endif