// Copyright (C) 2016 Jochen Becher // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #pragma once #include "archivebasics.h" #include "tag.h" #include "baseclass.h" #include "attribute.h" #include "reference.h" #include "impl/objectid.h" #include "impl/loadingrefmap.h" #include #include #include #include #include namespace qark { class QXmlInArchive : public ArchiveBasics { public: class FileFormatException : public std::exception { }; class UnexpectedForwardReference : public std::exception { }; static const bool inArchive = true; static const bool outArchive = false; private: class XmlTag; class Node { public: typedef QList ChildrenType; virtual ~Node() { qDeleteAll(m_children); } const ChildrenType &children() const { return m_children; } virtual QString qualifiedName() const = 0; virtual void accept(QXmlInArchive &visitor, const XmlTag &tag) { visitor.visit(this, tag); } void append(Node *node) { m_children.append(node); } private: ChildrenType m_children; }; class TagNode : public Node { public: explicit TagNode(const Tag &tag) : m_tag(tag) { } const Tag &tag() const { return m_tag; } QString qualifiedName() const override { return m_tag.qualifiedName(); } void accept(QXmlInArchive &visitor, const XmlTag &tag) override { visitor.visit(this, tag); } private: Tag m_tag; }; template class ObjectNode : public Node { public: explicit ObjectNode(const Object &object) : m_object(object) { } QString qualifiedName() const override { return m_object.qualifiedName(); } Object &object() { return m_object; } void accept(QXmlInArchive &visitor, const XmlTag &tag) override { visitor.visit(this, tag); } private: Object m_object; }; template class BaseNode : public Node { public: explicit BaseNode(const Base &base) : m_base(base) { } QString qualifiedName() const override { return m_base.qualifiedName(); } Base &base() { return m_base; } void accept(QXmlInArchive &visitor, const XmlTag &tag) override { visitor.visit(this, tag); } private: Base m_base; }; template class AttrNode : public Node { public: explicit AttrNode(const Attr &attr) : m_attr(attr) { } QString qualifiedName() const override { return m_attr.qualifiedName(); } void accept(QXmlInArchive &visitor, const XmlTag &tag) override { visitor.visit(this, tag); } Attr &attribute() { return m_attr; } private: Attr m_attr; }; template class SetterAttrNode : public Node { public: explicit SetterAttrNode(const SetterAttr &attr) : m_attr(attr) { } QString qualifiedName() const override { return m_attr.qualifiedName(); } void accept(QXmlInArchive &visitor, const XmlTag &tag) override { visitor.visit(this, tag); } SetterAttr &attribute() { return m_attr; } private: SetterAttr m_attr; }; template class GetterSetterAttrNode : public Node { public: explicit GetterSetterAttrNode(const GetterSetterAttr &attr) : m_attr(attr) { } QString qualifiedName() const override { return m_attr.qualifiedName(); } void accept(QXmlInArchive &visitor, const XmlTag &tag) override { visitor.visit(this, tag); } GetterSetterAttr &attribute() { return m_attr; } private: GetterSetterAttr m_attr; }; template class SetFuncAttrNode : public Node { public: explicit SetFuncAttrNode(const SetFuncAttr &attr) : m_attr(attr) { } QString qualifiedName() const override { return m_attr.qualifiedName(); } void accept(QXmlInArchive &visitor, const XmlTag &tag) override { visitor.visit(this, tag); } SetFuncAttr &attribute() { return m_attr; } private: SetFuncAttr m_attr; }; template class GetSetFuncAttrNode : public Node { public: explicit GetSetFuncAttrNode(const GetSetFuncAttr &attr) : m_attr(attr) { } QString qualifiedName() const override { return m_attr.qualifiedName(); } void accept(QXmlInArchive &visitor, const XmlTag &tag) override { visitor.visit(this, tag); } GetSetFuncAttr &attribute() { return m_attr; } private: GetSetFuncAttr m_attr; }; template class RefNode : public Node { public: explicit RefNode(const Ref &ref) : m_ref(ref) { } QString qualifiedName() const override { return m_ref.qualifiedName(); } void accept(QXmlInArchive &visitor, const XmlTag &tag) override { visitor.visit(this, tag); } Ref &reference() { return m_ref; } private: Ref m_ref; }; template class SetterRefNode : public Node { public: explicit SetterRefNode(const SetterRef &ref) : m_ref(ref) { } QString qualifiedName() const override { return m_ref.qualifiedName(); } void accept(QXmlInArchive &visitor, const XmlTag &tag) override { visitor.visit(this, tag); } SetterRef &reference() { return m_ref; } private: SetterRef m_ref; }; template class GetterSetterRefNode : public Node { public: explicit GetterSetterRefNode(const GetterSetterRef &ref) : m_ref(ref) { } QString qualifiedName() const override { return m_ref.qualifiedName(); } void accept(QXmlInArchive &visitor, const XmlTag &tag) override { visitor.visit(this, tag); } GetterSetterRef &reference() { return m_ref; } private: GetterSetterRef m_ref; }; template class SetFuncRefNode : public Node { public: explicit SetFuncRefNode(const SetFuncRef &ref) : m_ref(ref) { } QString qualifiedName() const override { return m_ref.qualifiedName(); } void accept(QXmlInArchive &visitor, const XmlTag &tag) override { visitor.visit(this, tag); } SetFuncRef &reference() { return m_ref; } private: SetFuncRef m_ref; }; template class GetSetFuncRefNode : public Node { public: explicit GetSetFuncRefNode(const GetSetFuncRef &ref) : m_ref(ref) { } QString qualifiedName() const override { return m_ref.qualifiedName(); } void accept(QXmlInArchive &visitor, const XmlTag &tag) override { visitor.visit(this, tag); } GetSetFuncRef &reference() { return m_ref; } private: GetSetFuncRef m_ref; }; public: explicit QXmlInArchive(QXmlStreamReader &stream) : m_stream(stream) { } ~QXmlInArchive() { } void beginDocument() { while (!m_stream.atEnd()) { switch (m_stream.readNext()) { case QXmlStreamReader::StartDocument: return; case QXmlStreamReader::Comment: break; default: throw FileFormatException(); } } throw FileFormatException(); } void endDocument() { if (m_endTagWasRead) { if (m_stream.tokenType() != QXmlStreamReader::EndDocument) throw FileFormatException(); } else { while (!m_stream.atEnd()) { switch (m_stream.readNext()) { case QXmlStreamReader::EndDocument: return; case QXmlStreamReader::Comment: break; default: throw FileFormatException(); } } throw FileFormatException(); } } void append(const Tag &tag) { auto node = new TagNode(tag); if (!m_nodeStack.empty()) m_nodeStack.top()->append(node); m_nodeStack.push(node); } template void append(const Object &object) { auto node = new ObjectNode(object); if (!m_nodeStack.empty()) m_nodeStack.top()->append(node); m_nodeStack.push(node); } void append(const End &) { Node *node = m_nodeStack.pop(); if (m_nodeStack.empty()) { XmlTag xmlTag = readTag(); if (xmlTag.m_tagName != node->qualifiedName() || xmlTag.m_isEndTag) throw FileFormatException(); node->accept(*this, xmlTag); delete node; } } template void append(const Base &base) { m_nodeStack.top()->append(new BaseNode(base)); } template void append(const Attr &attr) { m_nodeStack.top()->append(new AttrNode(attr)); } template void append(const SetterAttr &attr) { m_nodeStack.top()->append(new SetterAttrNode(attr)); } template void append(const GetterSetterAttr &attr) { m_nodeStack.top()->append(new GetterSetterAttrNode(attr)); } template void append(const SetFuncAttr &attr) { m_nodeStack.top()->append(new SetFuncAttrNode(attr)); } template void append(const GetSetFuncAttr &attr) { m_nodeStack.top()->append(new GetSetFuncAttrNode(attr)); } template void append(const Ref &ref) { m_nodeStack.top()->append(new RefNode(ref)); } template void append(const SetterRef &ref) { m_nodeStack.top()->append(new SetterRefNode(ref)); } template void append(const GetterSetterRef &ref) { m_nodeStack.top()->append(new GetterSetterRefNode(ref)); } template void append(const SetFuncRef &ref) { m_nodeStack.top()->append(new SetFuncRefNode(ref)); } template void append(const GetSetFuncRef &ref) { m_nodeStack.top()->append(new GetSetFuncRefNode(ref)); } #define QARK_READNUMBER(T, M) \ void read(T *i) \ { \ QString s = m_stream.readElementText(); \ m_endTagWasRead = true; \ bool ok = false; \ *i = s.M(&ok); \ if (!ok) \ throw FileFormatException(); \ } QARK_READNUMBER(char, toInt) QARK_READNUMBER(signed char, toInt) QARK_READNUMBER(unsigned char, toUInt) QARK_READNUMBER(short int, toShort) QARK_READNUMBER(unsigned short int, toUShort) QARK_READNUMBER(int, toInt) QARK_READNUMBER(unsigned int, toUInt) QARK_READNUMBER(long int, toLong) QARK_READNUMBER(unsigned long int, toULong) QARK_READNUMBER(long long int, toLongLong) QARK_READNUMBER(unsigned long long int, toULongLong) QARK_READNUMBER(float, toFloat) QARK_READNUMBER(double, toDouble) #undef QARK_READNUMBER void read(bool *b) { QString s = m_stream.readElementText(); m_endTagWasRead = true; if (s == QLatin1String("true")) *b = true; else if (s == QLatin1String("false")) *b = false; else throw FileFormatException(); } void read(QString *s) { *s = m_stream.readElementText(); m_endTagWasRead = true; } enum ReferenceKind { Nullpointer, Pointer, Instance }; class ReferenceTag { public: explicit ReferenceTag(ReferenceKind k = Nullpointer, const QString &string = QString()) : kind(k), typeName(string) { } ReferenceKind kind; QString typeName; }; ReferenceTag readReferenceTag() { XmlTag tag = readTag(); if (tag.m_tagName == QLatin1String("null")) return ReferenceTag(Nullpointer); else if (tag.m_tagName == QLatin1String("reference")) return ReferenceTag(Pointer); else if (tag.m_tagName == QLatin1String("instance")) return ReferenceTag(Instance, tag.m_attributes.value(QLatin1String("type"))); else throw FileFormatException(); } void readReferenceEndTag(ReferenceKind kind) { XmlTag tag = readTag(); if (!tag.m_isEndTag) throw FileFormatException(); else if (tag.m_tagName == QLatin1String("null") && kind != Nullpointer) throw FileFormatException(); else if (tag.m_tagName == QLatin1String("reference") && kind != Pointer) throw FileFormatException(); else if (tag.m_tagName == QLatin1String("instance") && kind != Instance) throw FileFormatException(); } template void read(T *&p) { impl::ObjectId id; int i; read(&i); id.set(i); if (m_loadingRefMap.hasObject(id)) p = m_loadingRefMap.object(id); else throw UnexpectedForwardReference(); } private: class XmlTag { public: QString m_tagName; bool m_isEndTag = false; impl::ObjectId m_id; QHash m_attributes; }; void readChildren(Node *node) { for (;;) { XmlTag xmlTag = readTag(); if (xmlTag.m_isEndTag) { if (xmlTag.m_tagName != node->qualifiedName()) throw FileFormatException(); return; } else { bool foundTag = false; for (auto it = node->children().begin(); it != node->children().end(); ++it) { if ((*it)->qualifiedName() == xmlTag.m_tagName) { foundTag = true; (*it)->accept(*this, xmlTag); } } if (!foundTag) skipUntilEndOfTag(xmlTag); } } } void visit(Node *, const XmlTag &) { throw FileFormatException(); } void visit(TagNode *node, const XmlTag &) { readChildren(node); } template void visit(ObjectNode *node, const XmlTag &tag) { if (tag.m_id.isValid() && node->object().object() != nullptr) m_loadingRefMap.addObject(tag.m_id, node->object().object()); readChildren(node); } template void visit(BaseNode *node, const XmlTag &) { load(*this, node->base().base(), node->base().parameters()); XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->base().qualifiedName()) throw FileFormatException(); } template void visit(AttrNode *node, const XmlTag &) { load(*this, *node->attribute().value(), node->attribute().parameters()); XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->attribute().qualifiedName()) throw FileFormatException(); } template void visit(SetterAttrNode *node, const XmlTag &) { T value; load(*this, value, node->attribute().parameters()); (node->attribute().object().*(node->attribute().setter()))(value); XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->attribute().qualifiedName()) throw FileFormatException(); } template void visit(SetterAttrNode *node, const XmlTag &) { T value; load(*this, value, node->attribute().parameters()); (node->attribute().object().*(node->attribute().setter()))(value); XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->attribute().qualifiedName()) throw FileFormatException(); } template void visit(GetterSetterAttrNode *node, const XmlTag &) { V value; load(*this, value, node->attribute().parameters()); (node->attribute().object().*(node->attribute().setter()))(value); XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->attribute().qualifiedName()) throw FileFormatException(); } template void visit(GetterSetterAttrNode *node, const XmlTag &) { V value; load(*this, value, node->attribute().parameters()); (node->attribute().object().*(node->attribute().setter()))(value); XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->attribute().qualifiedName()) throw FileFormatException(); } template void visit(SetFuncAttrNode *node, const XmlTag &) { T value; load(*this, value, node->attribute().parameters()); (node->attribute().setterFunc())(node->attribute().object(), value); XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->attribute().qualifiedName()) throw FileFormatException(); } template void visit(SetFuncAttrNode *node, const XmlTag &) { T value; load(*this, value, node->attribute().parameters()); (node->attribute().setterFunc())(node->attribute().object(), value); XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->attribute().qualifiedName()) throw FileFormatException(); } template void visit(GetSetFuncAttrNode *node, const XmlTag &) { V value; load(*this, value, node->attribute().parameters()); (node->attribute().setterFunc())(node->attribute().object(), value); XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->attribute().qualifiedName()) throw FileFormatException(); } template void visit(GetSetFuncAttrNode *node, const XmlTag &) { V value; load(*this, value, node->attribute().parameters()); (node->attribute().setterFunc())(node->attribute().object(), value); XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->attribute().qualifiedName()) throw FileFormatException(); } template void visit(RefNode *node, const XmlTag &) { m_currentRefNode = node; T value = T(); load(*this, value, node->reference().parameters()); if (m_currentRefNode) { // ref node was not consumed by forward reference *node->reference().value() = value; m_currentRefNode = nullptr; } XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->reference().qualifiedName()) throw FileFormatException(); } template void visit(SetterRefNode *node, const XmlTag &) { m_currentRefNode = node; T value; load(*this, value, node->reference().parameters()); if (m_currentRefNode) { // ref node was not consumed by forward reference (node->reference().object().*(node->reference().setter()))(value); m_currentRefNode = nullptr; } XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->reference().qualifiedName()) throw FileFormatException(); } template void visit(SetterRefNode *node, const XmlTag &) { m_currentRefNode = node; T value; load(*this, value, node->reference().parameters()); if (m_currentRefNode) { // ref node was not consumed by forward reference (node->reference().object().*(node->reference().setter()))(value); m_currentRefNode = nullptr; } XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->reference().qualifiedName()) throw FileFormatException(); } template void visit(GetterSetterRefNode *node, const XmlTag &) { m_currentRefNode = node; V value; load(*this, value, node->reference().parameters()); if (m_currentRefNode) { // ref node was not consumed by forward reference (node->reference().object().*(node->reference().setter()))(value); m_currentRefNode = nullptr; } XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->reference().qualifiedName()) throw FileFormatException(); } template void visit(GetterSetterRefNode *node, const XmlTag &) { m_currentRefNode = node; V value; load(*this, value, node->reference().parameters()); if (m_currentRefNode) { // ref node was not consumed by forward reference (node->reference().object().*(node->reference().setter()))(value); m_currentRefNode = nullptr; } XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->reference().qualifiedName()) throw FileFormatException(); } template void visit(SetFuncRefNode *node, const XmlTag &) { m_currentRefNode = node; T value; load(*this, value, node->reference().parameters()); if (m_currentRefNode) { // ref node was not consumed by forward reference (node->reference().setterFunc())(node->reference().object(), value); m_currentRefNode = nullptr; } XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->reference().qualifiedName()) throw FileFormatException(); } template void visit(SetFuncRefNode *node, const XmlTag &) { m_currentRefNode = node; T value; load(*this, value, node->reference().parameters()); if (m_currentRefNode) { // ref node was not consumed by forward reference (node->reference().setterFunc())(node->reference().object(), value); m_currentRefNode = nullptr; } XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->reference().qualifiedName()) throw FileFormatException(); } template void visit(GetSetFuncRefNode *node, const XmlTag &) { m_currentRefNode = node; V value; load(*this, value, node->reference().parameters()); if (m_currentRefNode) { // ref node was not consumed by forward reference (node->reference().setterFunc())(node->reference().object(), value); m_currentRefNode = nullptr; } XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->reference().qualifiedName()) throw FileFormatException(); } template void visit(GetSetFuncRefNode *node, const XmlTag &) { m_currentRefNode = node; V value; load(*this, value, node->reference().parameters()); if (m_currentRefNode) { // ref node was not consumed by forward reference (node->reference().setterFunc())(node->reference().object(), value); m_currentRefNode = nullptr; } XmlTag xmlTag = readTag(); if (!xmlTag.m_isEndTag || xmlTag.m_tagName != node->reference().qualifiedName()) throw FileFormatException(); } inline XmlTag readTag(); inline void skipUntilEndOfTag(const XmlTag &xmlTag); QXmlStreamReader &m_stream; bool m_endTagWasRead = false; QStack m_nodeStack; impl::LoadingRefMap m_loadingRefMap; Node *m_currentRefNode = nullptr; }; QXmlInArchive::XmlTag QXmlInArchive::readTag() { XmlTag xmlTag; if (m_endTagWasRead) { if (m_stream.tokenType() != QXmlStreamReader::EndElement) throw FileFormatException(); xmlTag.m_tagName = m_stream.name().toString(); xmlTag.m_isEndTag = true; m_endTagWasRead = false; return xmlTag; } while (!m_stream.atEnd()) { switch (m_stream.readNext()) { case QXmlStreamReader::StartElement: { xmlTag.m_tagName = m_stream.name().toString(); const QXmlStreamAttributes attrList = m_stream.attributes(); for (const QXmlStreamAttribute &attribute : attrList) { if (attribute.name() == QLatin1String("id")) { bool ok = false; int id = attribute.value().toString().toInt(&ok); if (!ok) throw FileFormatException(); xmlTag.m_id = impl::ObjectId(id); } else { xmlTag.m_attributes.insert(attribute.name().toString(), attribute.value().toString()); } } return xmlTag; } case QXmlStreamReader::EndElement: xmlTag.m_tagName = m_stream.name().toString(); xmlTag.m_isEndTag = true; return xmlTag; case QXmlStreamReader::Comment: // intentionally left blank break; case QXmlStreamReader::Characters: if (!m_stream.isWhitespace()) throw FileFormatException(); break; default: throw FileFormatException(); } } throw FileFormatException(); } void QXmlInArchive::skipUntilEndOfTag(const XmlTag &xmlTag) { if (m_endTagWasRead) throw FileFormatException(); int depth = 1; while (!m_stream.atEnd()) { switch (m_stream.readNext()) { case QXmlStreamReader::StartElement: ++depth; break; case QXmlStreamReader::EndElement: --depth; if (depth == 0) { if (m_stream.name().toString() != xmlTag.m_tagName) throw FileFormatException(); return; } break; case QXmlStreamReader::Comment: // intentionally left blank break; case QXmlStreamReader::Characters: // intentionally left blank break; default: throw FileFormatException(); } } throw FileFormatException(); } } // namespace qark