/*
 * This file is part of Office 2007 Filters for Calligra
 *
 * SPDX-FileCopyrightText: 2009-2010 Nokia Corporation and /or its subsidiary(-ies).
 *
 * Contact: Suresh Chande suresh.chande@nokia.com
 *
 * SPDX-License-Identifier: LGPL-2.1-only
 *
 */

#ifndef MSOOXMLREADER_P_H
#define MSOOXMLREADER_P_H

#include <KLocalizedString>

#ifndef MSOOXML_CURRENT_CLASS
#error Please include MsooXmlReader_p.h after defining MSOOXML_CURRENT_CLASS and MSOOXML_CURRENT_NS!
#endif

#define PASTE2(a, b) a##b
#define PASTE(a, b) PASTE2(a, b) // indirection needed because only function-like macro parameters can be pasted

#define PASTE3_(a, b, c) a##b##c
#define PASTE3(a, b, c) PASTE3_(a, b, c) // indirection needed because only function-like macro parameters can be pasted

#define JOIN2(a, b) a #b
#define JOIN(a, b) JOIN2(a, b) // indirection needed because only function-like macro parameters can be pasted

#define STRINGIFY(s) JOIN("", s)

//! Used to pass context: creates enum value {el}_{CURRENT_EL}
#define PASS_CONTEXT(el) PASTE3(el, _, CURRENT_EL)

#define TRY_READ_WITH_ARGS_INTERNAL(name, args, context) args RETURN_IF_ERROR(read_##name(context))

#define TRY_READ_WITH_ARGS(name, args) TRY_READ_WITH_ARGS_INTERNAL(name, m_read_##name##_args = args, )

#define TRY_READ_WITH_ARGS_IN_CONTEXT(name, args) TRY_READ_WITH_ARGS_INTERNAL(name, m_read_##name##_args = args, PASS_CONTEXT(name))

#define TRY_READ(name) TRY_READ_WITH_ARGS_INTERNAL(name, , )

#define TRY_READ_IN_CONTEXT(name) TRY_READ_WITH_ARGS_INTERNAL(name, , PASS_CONTEXT(name))

#ifdef MSOOXML_CURRENT_NS
#define QUALIFIED_NAME(name) JOIN(MSOOXML_CURRENT_NS ":", name)
#else
#define QUALIFIED_NAME(name) STRINGIFY(name)
#endif

#ifdef NDEBUG
#define PUSH_NAME_INTERNAL
#define POP_NAME_INTERNAL
#else // DEBUG
//! returns caller name at the current scope or "top level"
#define CALL_STACK_TOP_NAME (m_callsNamesDebug.isEmpty() ? QByteArray("top level") : m_callsNamesDebug.top()).constData()
#define PUSH_NAME
//! put at beginning of each read_*() method on call stack, only in debug mode
#define PUSH_NAME_INTERNAL                                                                                                                                     \
    /*debugMsooXml << CALL_STACK_TOP_NAME << "==>" << QUALIFIED_NAME(CURRENT_EL); */                                                                           \
    m_callsNamesDebug.push(STRINGIFY(CURRENT_EL));
//! put at the end of each read_*() method on call stack, only in debug mode
#define POP_NAME_INTERNAL                                                                                                                                      \
    m_callsNamesDebug.pop();                                                                                                                                   \
    /*debugMsooXml << CALL_STACK_TOP_NAME << "<==" << QUALIFIED_NAME(CURRENT_EL); */
#endif

#define READ_PROLOGUE2(method)                                                                                                                                 \
    if (!expectEl(QUALIFIED_NAME(CURRENT_EL))) {                                                                                                               \
        return KoFilter::WrongFormat;                                                                                                                          \
    }                                                                                                                                                          \
    PUSH_NAME_INTERNAL

#define READ_PROLOGUE READ_PROLOGUE2(CURRENT_EL)

#define READ_PROLOGUE_IF_NS(ns)                                                                                                                                \
    if (!expectEl(JOIN(STRINGIFY(ns) ":", CURRENT_EL))) {                                                                                                      \
        return KoFilter::WrongFormat;                                                                                                                          \
    }                                                                                                                                                          \
    PUSH_NAME_INTERNAL

#define READ_EPILOGUE_WITHOUT_RETURN                                                                                                                           \
    POP_NAME_INTERNAL                                                                                                                                          \
    if (!expectElEnd(QUALIFIED_NAME(CURRENT_EL))) {                                                                                                            \
        /*debugMsooXml << "READ_EPILOGUE:" << QUALIFIED_NAME(CURRENT_EL) << "not found!"; */                                                                   \
        return KoFilter::WrongFormat;                                                                                                                          \
    }                                                                                                                                                          \
    /*debugMsooXml << "/READ_EPILOGUE_WITHOUT_RETURN";*/

#define READ_EPILOGUE                                                                                                                                          \
    /*debugMsooXml << "READ_EPILOGUE";*/                                                                                                                       \
    READ_EPILOGUE_WITHOUT_RETURN                                                                                                                               \
    return KoFilter::OK;

#define READ_EPILOGUE_IF_NS(ns)                                                                                                                                \
    POP_NAME_INTERNAL                                                                                                                                          \
    if (!expectElEnd((JOIN(STRINGIFY(ns) ":", CURRENT_EL)))) {                                                                                                 \
        return KoFilter::WrongFormat;                                                                                                                          \
    }                                                                                                                                                          \
    return KoFilter::OK;

#define BREAK_IF_END_OF_QSTRING(name)                                                                                                                          \
    /*debugMsooXml << "BREAK_IF_END_OF" << name << "found:" << qualifiedName();*/                                                                              \
    if (isEndElement() && qualifiedName() == name) {                                                                                                           \
        break;                                                                                                                                                 \
    }

#define BREAK_IF_END_OF(name) BREAK_IF_END_OF_QSTRING(QLatin1String(QUALIFIED_NAME(name)))

#define BREAK_IF_END_OF_WITH_NS(ns, name) BREAK_IF_END_OF_QSTRING(QLatin1String(JOIN(STRINGIFY(ns) ":", name)))

// inline bool aaaa(const char * aa) { debugMsooXml << "aa" << aa; return true; }

#define QUALIFIED_NAME_IS(name) (qualifiedName() == QLatin1String(QUALIFIED_NAME(name)))

#define TRY_READ_IF_INTERNAL(name, qname, context)                                                                                                             \
    if (qualifiedName() == QLatin1String(qname)) {                                                                                                             \
        if (!isStartElement()) { /* sanity check */                                                                                                            \
            raiseError(i18n("Start element \"%1\" expected, found \"%2\"", QLatin1String(STRINGIFY(name)), tokenString()));                                    \
            return KoFilter::WrongFormat;                                                                                                                      \
        }                                                                                                                                                      \
        /*debugMsooXml << "TRY_READ_IF " STRINGIFY(name) " started";*/                                                                                         \
        TRY_READ_WITH_ARGS_INTERNAL(name, , context)                                                                                                           \
        /*debugMsooXml << "TRY_READ_IF " STRINGIFY(name) " finished";*/                                                                                        \
    }

#define TRY_READ_IF_IN_CONTEXT_INTERNAL(name, context) TRY_READ_IF_INTERNAL(name, QUALIFIED_NAME(name), context)

#define TRY_READ_IF_NS_IN_CONTEXT_INTERNAL(ns, name, context) TRY_READ_IF_INTERNAL(name, JOIN(STRINGIFY(ns) ":", name), context)

//! Tries to read element @a name by entering into read_{name} function.
//! Must be enclosed within if (isStartElement()), otherwise error will be returned.
#define TRY_READ_IF_IN_CONTEXT(name) TRY_READ_IF_IN_CONTEXT_INTERNAL(name, PASS_CONTEXT(name))

#define TRY_READ_IF_NS_IN_CONTEXT(ns, name) TRY_READ_IF_NS_IN_CONTEXT_INTERNAL(ns, name, PASS_CONTEXT(name))

#define TRY_READ_IF(name) TRY_READ_IF_IN_CONTEXT_INTERNAL(name, )

#define ELSE_TRY_READ_IF(name) else TRY_READ_IF_IN_CONTEXT_INTERNAL(name, )

#define ELSE_TRY_READ_IF_IN_CONTEXT(name) else TRY_READ_IF_IN_CONTEXT_INTERNAL(name, PASS_CONTEXT(name))

#define ELSE_TRY_READ_IF_NS_IN_CONTEXT(ns, name) else TRY_READ_IF_NS_IN_CONTEXT_INTERNAL(ns, name, PASS_CONTEXT(name))

#define TRY_READ_IF_NS_INTERNAL(ns, name)                                                                                                                      \
    if (qualifiedName() == QLatin1String(JOIN(STRINGIFY(ns) ":", name))) {                                                                                     \
        /*debugMsooXml << "TRY_READ_IF_NS " JOIN(STRINGIFY(ns) ":", name) " started";*/                                                                        \
        TRY_READ(name);                                                                                                                                        \
        /*debugMsooXml << "TRY_READ_IF_NS " JOIN(STRINGIFY(ns) ":", name) " finished";*/                                                                       \
    }

//! Like TRY_READ_IF() but namespace for explicit namespace @a ns.
#define TRY_READ_IF_NS(ns, name)                                                                                                                               \
    if (!isStartElement()) { /* sanity check */                                                                                                                \
        raiseError(i18n("Start element \"%1\" expected, found \"%2\"", QLatin1String(JOIN(STRINGIFY(ns) ":", name)), tokenString()));                          \
        return KoFilter::WrongFormat;                                                                                                                          \
    } else                                                                                                                                                     \
        TRY_READ_IF_NS_INTERNAL(ns, name)

#define ELSE_TRY_READ_IF_NS(ns, name) else TRY_READ_IF_NS_INTERNAL(ns, name)

#define ELSE_WRONG_FORMAT                                                                                                                                      \
    else                                                                                                                                                       \
    {                                                                                                                                                          \
        return KoFilter::WrongFormat;                                                                                                                          \
    }

#define ELSE_WRONG_FORMAT_DEBUG(dbg)                                                                                                                           \
    else                                                                                                                                                       \
    {                                                                                                                                                          \
        debugMsooXml << dbg;                                                                                                                                   \
        return KoFilter::WrongFormat;                                                                                                                          \
    }

//! Reads optional attribute of name @a atrname and allocates variable of the same name.
/*! Requires the following line to be present above:
    @code
    const QXmlStreamAttributes attrs( attributes() );
    @endcode
*/
#define TRY_READ_ATTR(atrname) QString atrname(attrs.value(QUALIFIED_NAME(atrname)).toString());

//! Reads optional attribute of name @a atrname into the variable @a destination.
/*! Requires the following line to be present above:
    @code
    const QXmlStreamAttributes attrs( attributes() );
    @endcode
*/
#define TRY_READ_ATTR_INTO(atrname, destination)                                                                                                               \
    destination = attrs.value(QUALIFIED_NAME(atrname)).toString();                                                                                             \
    /*debugMsooXml << "TRY_READ_ATTR_INTO: " STRINGIFY(destination) << "=" << destination;*/

//! Reads optional attribute of name @a atrname with explicitly specified namespace @a ns.
/*! Creates QString variable with name \<ns\>_\<atrame\>
 */
/*! Requires the following line to be present above:
    @code
    const QXmlStreamAttributes attrs( attributes() );
    @endcode
*/
#define TRY_READ_ATTR_WITH_NS(ns, atrname) QString PASTE3(ns, _, atrname)(attrs.value(JOIN(STRINGIFY(ns) ":", atrname)).toString());

//! Reads optional attribute of name @a atrname with explicitly specified namespace @a ns
//! into the variable @a destination.
/*! Requires the following line to be present above:
    @code
    const QXmlStreamAttributes attrs( attributes() );
    @endcode
*/
#define TRY_READ_ATTR_WITH_NS_INTO(ns, atrname, destination)                                                                                                   \
    destination = attrs.value(JOIN(STRINGIFY(ns) ":", atrname)).toString();                                                                                    \
    /*debugMsooXml << "TRY_READ_ATTR_WITH_NS_INTO: " STRINGIFY(destination) << "=" << destination;*/

inline QString atrToString(const QXmlStreamAttributes &attrs, const char *atrname)
{
    const QStringView v(attrs.value(atrname));
    return v.isNull() ? QString() : v.toString();
}

//! Reads optional attribute of name @a atrname without namespace.
/*! Creates QString variable with name \<atrname\>
 */
/*! Requires the following line to be present above:
    @code
    const QXmlStreamAttributes attrs( attributes() );
    @endcode
*/
#define TRY_READ_ATTR_WITHOUT_NS(atrname) QString atrname(atrToString(attrs, STRINGIFY(atrname)));

//! Reads required attribute of name @a atrname and allocates variable of the same name
//! If there is no such attribute, returns KoFilter::WrongFormat.
/*! Requires the following line to be present above:
    @code
    const QXmlStreamAttributes attrs( attributes() );
    @endcode
*/
#define READ_ATTR(atrname)                                                                                                                                     \
    QString atrname;                                                                                                                                           \
    if (attrs.hasAttribute(QUALIFIED_NAME(atrname))) {                                                                                                         \
        atrname = attrs.value(QUALIFIED_NAME(atrname)).toString();                                                                                             \
    }                                                                                                                                                          \
    ELSE_WRONG_FORMAT_DEBUG("READ_ATTR: " QUALIFIED_NAME(atrname) " not found")

//! Like @ref READ_ATTR(atrname) but reads the attribute into the variable @a destination.
#define READ_ATTR_INTO(atrname, destination)                                                                                                                   \
    if (attrs.hasAttribute(QUALIFIED_NAME(atrname))) {                                                                                                         \
        destination = attrs.value(QUALIFIED_NAME(atrname)).toString();                                                                                         \
    }                                                                                                                                                          \
    ELSE_WRONG_FORMAT_DEBUG("READ_ATTR_INTO: " QUALIFIED_NAME(atrname) " not found")

//! Reads required attribute of name @a atrname with explicitly specified namespace @a ns
/*! into the variable @a destination.
 */
/*! Requires the following line to be present above:
    @code
    const QXmlStreamAttributes attrs( attributes() );
    @endcode
*/
#define READ_ATTR_WITH_NS_INTO(ns, atrname, destination)                                                                                                       \
    if (attrs.hasAttribute(JOIN(STRINGIFY(ns) ":", atrname))) {                                                                                                \
        destination = attrs.value(JOIN(STRINGIFY(ns) ":", atrname)).toString();                                                                                \
    }                                                                                                                                                          \
    ELSE_WRONG_FORMAT_DEBUG("READ_ATTR_WITH_NS_INTO: " JOIN(STRINGIFY(ns) ":", atrname) " not found")

//! Reads required attribute of name @a atrname with explicitly specified namespace @a ns.
/*! Creates QString variable with name \<ns\>_\<atrame\>
 */
/*! Requires the following line to be present above:
    @code
    const QXmlStreamAttributes attrs( attributes() );
    @endcode
*/
#define READ_ATTR_WITH_NS(ns, atrname)                                                                                                                         \
    QString PASTE3(ns, _, atrname);                                                                                                                            \
    if (attrs.hasAttribute(JOIN(STRINGIFY(ns) ":", atrname))) {                                                                                                \
        PASTE3(ns, _, atrname) = attrs.value(JOIN(STRINGIFY(ns) ":", atrname)).toString();                                                                     \
    }                                                                                                                                                          \
    ELSE_WRONG_FORMAT_DEBUG("READ_ATTR_WITH_NS: " JOIN(STRINGIFY(ns) ":", atrname) " not found")

//! Reads required attribute of name @a atrname without namespace.
/*! Creates QString variable with name \<atrname\>
 */
/*! Requires the following line to be present above:
    @code
    const QXmlStreamAttributes attrs( attributes() );
    @endcode
*/
#define READ_ATTR_WITHOUT_NS(atrname)                                                                                                                          \
    QString atrname;                                                                                                                                           \
    if (attrs.hasAttribute(STRINGIFY(atrname))) {                                                                                                              \
        atrname = attrs.value(STRINGIFY(atrname)).toString();                                                                                                  \
    }                                                                                                                                                          \
    ELSE_WRONG_FORMAT_DEBUG("READ_ATTR_WITHOUT_NS: " STRINGIFY(atrname) " not found")

//! Like @ref READ_ATTR_WITHOUT_NS(atrname) but reads the attribute into the variable @a destination.
#define READ_ATTR_WITHOUT_NS_INTO(atrname, destination)                                                                                                        \
    if (attrs.hasAttribute(STRINGIFY(atrname))) {                                                                                                              \
        destination = attrs.value(STRINGIFY(atrname)).toString();                                                                                              \
    }                                                                                                                                                          \
    ELSE_WRONG_FORMAT_DEBUG("READ_ATTR_WITHOUT_NS_INTO: " STRINGIFY(atrname) " not found")

/*! Requires the following line to be present above:
    @code
    const QXmlStreamAttributes attrs( attributes() );
    @endcode
*/
#define TRY_READ_ATTR_QSTRING(atrname) QString atrname(attrs.value(m_defaultNamespace + atrname).toString());

//! Like @ref TRY_READ_ATTR_WITHOUT_NS(atrname) but reads the attribute into the variable @a destination.
#define TRY_READ_ATTR_WITHOUT_NS_INTO(atrname, destination) destination = attrs.value(STRINGIFY(atrname)).toString();

//! reads boolean attribute "val" prefixed with default namespace, defaults to true
#define READ_BOOLEAN_VAL readBooleanAttr(QUALIFIED_NAME(val), true)

//! Converts @a string into integer @a destination; returns KoFilter::WrongFormat on failure.
//! @warning @a destination is left unchanged if @a string is empty, so it is up to developer to initialize it.
#define STRING_TO_INT(string, destination, debugElement)                                                                                                       \
    if (string.isEmpty()) {                                                                                                                                    \
    } else {                                                                                                                                                   \
        bool ok;                                                                                                                                               \
        const int val_tmp = string.toInt(&ok);                                                                                                                 \
        if (!ok) {                                                                                                                                             \
            debugMsooXml << "STRING_TO_INT: error converting" << string << "to int (attribute" << debugElement << ")";                                         \
            return KoFilter::WrongFormat;                                                                                                                      \
        }                                                                                                                                                      \
        destination = val_tmp;                                                                                                                                 \
    }

//! Converts @a string into longlong @a destination; returns KoFilter::WrongFormat on failure.
//! @warning @a destination is left unchanged if @a string is empty, so it is up to developer to initialize it.
#define STRING_TO_LONGLONG(string, destination, debugElement)                                                                                                  \
    if (string.isEmpty()) {                                                                                                                                    \
    } else {                                                                                                                                                   \
        bool ok;                                                                                                                                               \
        const quint64 val_tmp = string.toLongLong(&ok);                                                                                                        \
        if (!ok) {                                                                                                                                             \
            debugMsooXml << "STRING_TO_LONGLONG: error converting" << string << "to LONGLONG (attribute" << debugElement << ")";                               \
            return KoFilter::WrongFormat;                                                                                                                      \
        }                                                                                                                                                      \
        destination = val_tmp;                                                                                                                                 \
    }

//! Converts @a string into a qreal value in @a destination; returns KoFilter::WrongFormat on failure.
//! @warning @a destination is left unchanged if @a string is empty, so it is up to developer to initialize it.
#define STRING_TO_QREAL(string, destination, debugElement)                                                                                                     \
    if (string.isEmpty()) {                                                                                                                                    \
    } else {                                                                                                                                                   \
        bool ok;                                                                                                                                               \
        const qreal val_tmp = string.toDouble(&ok);                                                                                                            \
        if (!ok) {                                                                                                                                             \
            debugMsooXml << "STRING_TO_DOUBLE: error converting" << string << "to qreal (attribute" << debugElement << ")";                                    \
            return KoFilter::WrongFormat;                                                                                                                      \
        }                                                                                                                                                      \
        destination = val_tmp;                                                                                                                                 \
    }

//! Skips everything until end of CURRENT_EL is pulled
#define SKIP_EVERYTHING                                                                                                                                        \
    /*debugMsooXml << "Skipping everything in element" << qualifiedName() << "...";*/                                                                          \
    const QString qn(qualifiedName().toString());                                                                                                              \
    /*debugMsooXml << *this; */                                                                                                                                \
    while (true) {                                                                                                                                             \
        readNext();                                                                                                                                            \
        if (atEnd())                                                                                                                                           \
            break;                                                                                                                                             \
        if (isEndElement() && qualifiedName() == qn) {                                                                                                         \
            break;                                                                                                                                             \
        }                                                                                                                                                      \
    }

//! Skips elements, which cannot be interpreted at this time in order to avoid them
// being read somewhere else
#define SKIP_UNKNOWN                                                                                                                                           \
    else                                                                                                                                                       \
    {                                                                                                                                                          \
        skipCurrentElement();                                                                                                                                  \
    }

#define SKIP_EVERYTHING_AND_RETURN                                                                                                                             \
    SKIP_EVERYTHING                                                                                                                                            \
    return KoFilter::OK;

#define BIND_READ_METHOD(name, method) m_readMethods.insert(QLatin1String(name), &MSOOXML_CURRENT_CLASS::read_##method);

#define BIND_READ(name) BIND_READ_METHOD(STRINGIFY(name), name)

#define BIND_READ_SKIP(name) BIND_READ_METHOD(STRINGIFY(name), SKIP)

#define BIND_READ_METHOD_HASH(hash, name, method) hash.insert(QLatin1String(name), &MSOOXML_CURRENT_CLASS::read_##method);

#define BIND_READ_HASH(hash, name) BIND_READ_METHOD_HASH(hash, STRINGIFY(name), name)

#define BIND_READ_HASH_SKIP(hash, name) BIND_READ_METHOD_HASH(hash, STRINGIFY(name), SKIP)

#endif
