summaryrefslogtreecommitdiffstats
path: root/tests/auto/corelib/serialization/qxmlstream/qc14n.h
blob: 5ae87f1a7ac44abd34071cc6a36190d8b702302d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include <QtCore/QDebug>
#include <QtCore/QFlags>
#include <QtCore/QXmlStreamReader>

#include <algorithm>

class QC14N
{
public:
    static bool isEqual(QIODevice *const firstDocument,
                        QIODevice *const secondDocument,
                        QString *const message = nullptr);

private:
    static bool isDifferent(const QXmlStreamReader &r1,
                            const QXmlStreamReader &r2,
                            QString *const message);
    static bool isAttributesEqual(const QXmlStreamReader &r1,
                                  const QXmlStreamReader &r2,
                                  QString *const message);
};

#include <QXmlStreamReader>

/*! \internal

  \a firstDocument and \a secondDocument must be pointers to opened devices.
 */
bool QC14N::isEqual(QIODevice *const firstDocument,
                    QIODevice *const secondDocument,
                    QString *const message)
{
    qDebug() << Q_FUNC_INFO;
    if (!firstDocument)
        qFatal("%s: A valid firstDocument QIODevice pointer must be supplied", Q_FUNC_INFO);
    if (!secondDocument)
        qFatal("%s: A valid secondDocument QIODevice pointer must be supplied", Q_FUNC_INFO);
    if (!firstDocument->isReadable())
        qFatal("%s: The firstDocument device must be readable.", Q_FUNC_INFO);
    if (!secondDocument->isReadable())
        qFatal("%s: The secondDocument device must be readable.", Q_FUNC_INFO);

    QXmlStreamReader r1(firstDocument);
    QXmlStreamReader r2(secondDocument);

    while(!r1.atEnd())
    {
        if(r1.error())
        {
            if(message)
                *message = r1.errorString();

            return false;
        }
        else if(r2.error())
        {
            if(message)
                *message = r1.errorString();

            return false;
        }
        else
        {
            if(isDifferent(r1, r2, message))
                return true;
        }

        r1.readNext();
        r2.readNext();
    }

    if(!r2.atEnd())
    {
        if(message)
            *message = QLatin1String("Reached the end of the first document, while there was still content left in the second");

        return false;
    }

    /* And they lived happily ever after. */
    return true;
}

/*! \internal
 */
bool QC14N::isAttributesEqual(const QXmlStreamReader &r1,
                              const QXmlStreamReader &r2,
                              QString *const message)
{
    Q_UNUSED(message);

    const QXmlStreamAttributes &attrs1 = r1.attributes();
    const QXmlStreamAttributes &attrs2 = r2.attributes();
    if (attrs1.size() != attrs2.size())
        return false;

    auto existsInOtherList = [&attrs2](const auto &attr) { return attrs2.contains(attr); };
    return std::all_of(attrs1.cbegin(), attrs1.cend(), existsInOtherList);
}

bool QC14N::isDifferent(const QXmlStreamReader &r1,
                        const QXmlStreamReader &r2,
                        QString *const message)
{
    // TODO error reporting can be a lot better here.
    if(r1.tokenType() != r2.tokenType())
        return false;

    switch(r1.tokenType())
    {
        case QXmlStreamReader::NoToken:
        /* Fallthrough. */
        case QXmlStreamReader::StartDocument:
        /* Fallthrough. */
        case QXmlStreamReader::EndDocument:
        /* Fallthrough. */
        case QXmlStreamReader::DTD:
            return true;
        case QXmlStreamReader::Invalid:
            return false;
        case QXmlStreamReader::StartElement:
        {
            return r1.qualifiedName() == r2.qualifiedName()
                   /* Yes, the namespace test below should be redundant, but with it we
                    * trap namespace bugs in QXmlStreamReader, if any. */
                   && r1.namespaceUri() == r2.namespaceUri()
                   && isAttributesEqual(r1, r2, message);

        }
        case QXmlStreamReader::EndElement:
        {
            return r1.qualifiedName() == r2.qualifiedName()
                   && r1.namespaceUri() == r2.namespaceUri()
                   && r1.name() == r2.name();
        }
        case QXmlStreamReader::Characters:
        /* Fallthrough. */
        case QXmlStreamReader::Comment:
            return r1.text() == r2.text();
        case QXmlStreamReader::EntityReference:
        case QXmlStreamReader::ProcessingInstruction:
        {
            return r1.processingInstructionTarget() == r2.processingInstructionTarget() &&
                   r2.processingInstructionData() == r2.processingInstructionData();

        }
        default:
            qFatal("%s: Unknown tokenType: %d", Q_FUNC_INFO, static_cast<int>(r1.tokenType()));
            return false;
    }
}