| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the test suite of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU | 
| 19 | ** General Public License version 3 as published by the Free Software | 
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT | 
| 21 | ** included in the packaging of this file. Please review the following | 
| 22 | ** information to ensure the GNU General Public License requirements will | 
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. | 
| 24 | ** | 
| 25 | ** $QT_END_LICENSE$ | 
| 26 | ** | 
| 27 | ****************************************************************************/ | 
| 28 |  | 
| 29 | #include <QFile> | 
| 30 | #include <QStack> | 
| 31 |  | 
| 32 | #include <QXmlNamePool> | 
| 33 | #include <QXmlStreamReader> | 
| 34 | #include <QtDebug> | 
| 35 | #include <QTest> | 
| 36 |  | 
| 37 | #include "LoadingModel.h" | 
| 38 | LoadingModel::LoadingModel(const Node::Vector &content, | 
| 39 |                            const QXmlNamePool &np) : QSimpleXmlNodeModel(np) | 
| 40 |                                                    , m_nodes(content) | 
| 41 | { | 
| 42 |     /* | 
| 43 |     for (const Node *n : content) | 
| 44 |         qDebug() << "this:" << n | 
| 45 |                  << "kind:" << n->kind | 
| 46 |                  << "parent: " << n->parent | 
| 47 |                  << "preceding: " << n->precedingSibling | 
| 48 |                  << "following: " << n->followingSibling | 
| 49 |                  << "firstChild: " << n->firstChild | 
| 50 |                  << "value: " << n->value; | 
| 51 |                  */ | 
| 52 | } | 
| 53 |  | 
| 54 | LoadingModel::~LoadingModel() | 
| 55 | { | 
| 56 |      qDeleteAll(c: m_nodes); | 
| 57 | } | 
| 58 |  | 
| 59 | const LoadingModel::Node *LoadingModel::toInternal(const QXmlNodeModelIndex &ni) const | 
| 60 | { | 
| 61 |     return static_cast<const Node *>(ni.internalPointer()); | 
| 62 | } | 
| 63 |  | 
| 64 | QXmlNodeModelIndex LoadingModel::createIndex(const Node *const internal) const | 
| 65 | { | 
| 66 |     if (!internal) | 
| 67 |         qFatal(msg: "%s: cannot construct a model index from a null pointer" , Q_FUNC_INFO); | 
| 68 |     return QAbstractXmlNodeModel::createIndex(pointer: const_cast<Node *>(internal)); | 
| 69 | } | 
| 70 |  | 
| 71 | QUrl LoadingModel::documentUri(const QXmlNodeModelIndex &) const | 
| 72 | { | 
| 73 |     qFatal(msg: "%s: This method should not be called during the test" , Q_FUNC_INFO); | 
| 74 |     return QUrl(); | 
| 75 | } | 
| 76 |  | 
| 77 | QXmlNodeModelIndex::NodeKind LoadingModel::kind(const QXmlNodeModelIndex &ni) const | 
| 78 | { | 
| 79 |     if (ni.isNull()) | 
| 80 |         qFatal(msg: "%s: node model index should not be null" , Q_FUNC_INFO); | 
| 81 |     return toInternal(ni)->kind; | 
| 82 | } | 
| 83 |  | 
| 84 | QXmlNodeModelIndex::DocumentOrder LoadingModel::compareOrder(const QXmlNodeModelIndex &n1, const QXmlNodeModelIndex &n2) const | 
| 85 | { | 
| 86 |     const Node *const in1 = toInternal(ni: n1); | 
| 87 |     const Node *const in2 = toInternal(ni: n2); | 
| 88 |     if (m_nodes.indexOf(t: in1) == -1) | 
| 89 |         qFatal(msg: "%s: node n1 is not in internal node list" , Q_FUNC_INFO); | 
| 90 |     if (m_nodes.indexOf(t: in2) == -1) | 
| 91 |         qFatal(msg: "%s: node n2 is not in internal node list" , Q_FUNC_INFO); | 
| 92 |  | 
| 93 |     if(in1 == in2) | 
| 94 |         return QXmlNodeModelIndex::Is; | 
| 95 |     else if(m_nodes.indexOf(t: in1) < m_nodes.indexOf(t: in2)) | 
| 96 |         return QXmlNodeModelIndex::Precedes; | 
| 97 |     else | 
| 98 |         return QXmlNodeModelIndex::Follows; | 
| 99 | } | 
| 100 |  | 
| 101 | QXmlNodeModelIndex LoadingModel::root(const QXmlNodeModelIndex &) const | 
| 102 | { | 
| 103 |     if (kind(ni: createIndex(internal: m_nodes.first())) != QXmlNodeModelIndex::Document) { | 
| 104 |         qWarning(msg: "%s: first node must be a Document node" , Q_FUNC_INFO); | 
| 105 |         return QXmlNodeModelIndex(); | 
| 106 |     } | 
| 107 |     return createIndex(internal: m_nodes.first()); | 
| 108 | } | 
| 109 |  | 
| 110 | QXmlName LoadingModel::name(const QXmlNodeModelIndex &ni) const | 
| 111 | { | 
| 112 |     return toInternal(ni)->name; | 
| 113 | } | 
| 114 |  | 
| 115 | QVariant LoadingModel::typedValue(const QXmlNodeModelIndex &ni) const | 
| 116 | { | 
| 117 |     const Node *const internal = toInternal(ni); | 
| 118 |  | 
| 119 |     if (internal->kind != QXmlNodeModelIndex::Attribute | 
| 120 |         && internal->kind != QXmlNodeModelIndex::Element) { | 
| 121 |         qWarning(msg: "%s: node must be an attribute or element" , Q_FUNC_INFO); | 
| 122 |         return QVariant(); | 
| 123 |     } | 
| 124 |  | 
| 125 |     return internal->value; | 
| 126 | } | 
| 127 |  | 
| 128 | QString LoadingModel::stringValue(const QXmlNodeModelIndex &ni) const | 
| 129 | { | 
| 130 |     const Node *const internal = toInternal(ni); | 
| 131 |  | 
| 132 |     switch(internal->kind) | 
| 133 |     { | 
| 134 |         case QXmlNodeModelIndex::Text: | 
| 135 |         /* Fallthrough. */ | 
| 136 |         case QXmlNodeModelIndex::ProcessingInstruction: | 
| 137 |         /* Fallthrough. */ | 
| 138 |         case QXmlNodeModelIndex::Comment: | 
| 139 |         /* Fallthrough. */ | 
| 140 |         case QXmlNodeModelIndex::Attribute: | 
| 141 |             return internal->value; | 
| 142 |         default: | 
| 143 |             return QString(); | 
| 144 |     } | 
| 145 | } | 
| 146 |  | 
| 147 | QXmlNodeModelIndex LoadingModel::nextFromSimpleAxis(QAbstractXmlNodeModel::SimpleAxis axis, | 
| 148 |                                                     const QXmlNodeModelIndex &ni) const | 
| 149 | { | 
| 150 |     const Node *const internal = toInternal(ni); | 
| 151 |  | 
| 152 |     /* Note that a QXmlNodeModelIndex containing a null pointer is not a null node. */ | 
| 153 |     switch(axis) | 
| 154 |     { | 
| 155 |         case Parent: | 
| 156 |             return internal->parent ? createIndex(internal: internal->parent) : QXmlNodeModelIndex(); | 
| 157 |         case FirstChild: | 
| 158 |             return internal->firstChild ? createIndex(internal: internal->firstChild) : QXmlNodeModelIndex(); | 
| 159 |         case PreviousSibling: | 
| 160 |             return internal->precedingSibling ? createIndex(internal: internal->precedingSibling) : QXmlNodeModelIndex(); | 
| 161 |         case NextSibling: | 
| 162 |             return internal->followingSibling ? createIndex(internal: internal->followingSibling) : QXmlNodeModelIndex(); | 
| 163 |         default: | 
| 164 |             qWarning(msg: "%s: unknown axis enum value %d" , Q_FUNC_INFO, static_cast<int>(axis)); | 
| 165 |             return QXmlNodeModelIndex(); | 
| 166 |     } | 
| 167 | } | 
| 168 |  | 
| 169 | QVector<QXmlNodeModelIndex> LoadingModel::attributes(const QXmlNodeModelIndex &ni) const | 
| 170 | { | 
| 171 |     QVector<QXmlNodeModelIndex> retval; | 
| 172 |     for (const Node *n : toInternal(ni)->attributes) | 
| 173 |         retval.append(t: createIndex(internal: n)); | 
| 174 |  | 
| 175 |     return retval; | 
| 176 | } | 
| 177 |  | 
| 178 | class Loader | 
| 179 | { | 
| 180 | public: | 
| 181 |     inline Loader(const QXmlNamePool &namePool) : m_namePool(namePool) | 
| 182 |                                                 , m_currentNode(0) | 
| 183 |     { | 
| 184 |         m_parentStack.push(t: 0); | 
| 185 |     } | 
| 186 |  | 
| 187 | private: | 
| 188 |     inline void adjustSiblings(LoadingModel::Node *const justBorn); | 
| 189 |     friend class LoadingModel; | 
| 190 |     Q_DISABLE_COPY(Loader); | 
| 191 |  | 
| 192 |     void load(); | 
| 193 |  | 
| 194 |     QXmlNamePool                        m_namePool; | 
| 195 |     QXmlStreamReader                    m_reader; | 
| 196 |     LoadingModel::Node::Vector          m_result; | 
| 197 |     LoadingModel::Node *                m_currentNode; | 
| 198 |     QStack<LoadingModel::Node *>        m_parentStack; | 
| 199 | }; | 
| 200 |  | 
| 201 | inline void Loader::adjustSiblings(LoadingModel::Node *const justBorn) | 
| 202 | { | 
| 203 |     if(m_currentNode) | 
| 204 |     { | 
| 205 |         if(m_currentNode->parent == justBorn->parent) | 
| 206 |             justBorn->precedingSibling = m_currentNode; | 
| 207 |  | 
| 208 |         m_currentNode->followingSibling = justBorn; | 
| 209 |     } | 
| 210 |  | 
| 211 |     m_currentNode = justBorn; | 
| 212 |  | 
| 213 |     /* Otherwise we're the first child, and our precedingSibling should remain null. */ | 
| 214 |  | 
| 215 |     if(m_parentStack.top() && !m_parentStack.top()->firstChild) | 
| 216 |         m_parentStack.top()->firstChild = justBorn; | 
| 217 | } | 
| 218 |  | 
| 219 | void Loader::load() | 
| 220 | { | 
| 221 |     QFile in(QLatin1String("tree.xml" )); | 
| 222 |  | 
| 223 |     /* LoadingModel::m_result will be null, signalling failure. */ | 
| 224 |     if(!in.open(flags: QIODevice::ReadOnly)) | 
| 225 |         return; | 
| 226 |  | 
| 227 |     QXmlStreamReader reader(&in); | 
| 228 |     while(!reader.atEnd()) | 
| 229 |     { | 
| 230 |         reader.readNext(); | 
| 231 |  | 
| 232 |         switch(reader.tokenType()) | 
| 233 |         { | 
| 234 |             case QXmlStreamReader::StartDocument: | 
| 235 |             /* Fallthrough. */ | 
| 236 |             case QXmlStreamReader::StartElement: | 
| 237 |             { | 
| 238 |                 QXmlName name; | 
| 239 |                 if(reader.tokenType() == QXmlStreamReader::StartElement) | 
| 240 |                 { | 
| 241 |                     name = QXmlName(m_namePool, | 
| 242 |                                     reader.name().toString(), | 
| 243 |                                     reader.namespaceUri().toString(), | 
| 244 |                                     reader.prefix().toString()); | 
| 245 |                 } | 
| 246 |                 /* Else, the name is null. */ | 
| 247 |  | 
| 248 |                 LoadingModel::Node *const tmp = new LoadingModel::Node(reader.tokenType() == QXmlStreamReader::StartElement | 
| 249 |                                                                        ? QXmlNodeModelIndex::Element | 
| 250 |                                                                        : QXmlNodeModelIndex::Document, | 
| 251 |                                                                        m_parentStack.top(), | 
| 252 |                                                                        QString(), | 
| 253 |                                                                        name); | 
| 254 |                 m_result.append(t: tmp); | 
| 255 |  | 
| 256 |                 if(m_currentNode) | 
| 257 |                 { | 
| 258 |                     if(m_currentNode->parent == m_parentStack.top()) | 
| 259 |                         m_currentNode->followingSibling = tmp; | 
| 260 |                 } | 
| 261 |  | 
| 262 |                 const QXmlStreamAttributes attributes(reader.attributes()); | 
| 263 |                 const int len = attributes.count(); | 
| 264 |  | 
| 265 |                 for(int i = 0; i < len; ++i) | 
| 266 |                 { | 
| 267 |                     const QXmlStreamAttribute &attr = attributes.at(i); | 
| 268 |                     const LoadingModel::Node *const a = new LoadingModel::Node(QXmlNodeModelIndex::Attribute, | 
| 269 |                                                                                m_parentStack.top(), | 
| 270 |                                                                                attr.value().toString(), | 
| 271 |                                                                                QXmlName(m_namePool, | 
| 272 |                                                                                        attr.name().toString(), | 
| 273 |                                                                                        attr.namespaceUri().toString(), | 
| 274 |                                                                                        attr.prefix().toString())); | 
| 275 |                     /* We add it also to m_result such that compareOrder() is correct | 
| 276 |                      * for attributes. m_result owns a. */ | 
| 277 |                     tmp->attributes.append(t: a); | 
| 278 |                     m_result.append(t: a); | 
| 279 |                 } | 
| 280 |  | 
| 281 |                 adjustSiblings(justBorn: tmp); | 
| 282 |                 m_parentStack.push(t: m_currentNode); | 
| 283 |                 break; | 
| 284 |             } | 
| 285 |             case QXmlStreamReader::EndDocument: | 
| 286 |             /* Fallthrough. */ | 
| 287 |             case QXmlStreamReader::EndElement: | 
| 288 |             { | 
| 289 |                 m_currentNode->followingSibling = 0; | 
| 290 |                 m_currentNode = m_parentStack.pop(); | 
| 291 |  | 
| 292 |                 if(reader.tokenType() == QXmlStreamReader::EndDocument) | 
| 293 |                     const_cast<LoadingModel::Node *>(m_result.first())->followingSibling = 0; | 
| 294 |  | 
| 295 |                 break; | 
| 296 |             } | 
| 297 |             case QXmlStreamReader::Characters: | 
| 298 |             { | 
| 299 |                 LoadingModel::Node *const tmp = new LoadingModel::Node(QXmlNodeModelIndex::Text, m_parentStack.top(), reader.text().toString()); | 
| 300 |                 m_result.append(t: tmp); | 
| 301 |                 adjustSiblings(justBorn: tmp); | 
| 302 |                 break; | 
| 303 |             } | 
| 304 |             case QXmlStreamReader::ProcessingInstruction: | 
| 305 |             { | 
| 306 |                 LoadingModel::Node *const tmp = new LoadingModel::Node(QXmlNodeModelIndex::ProcessingInstruction, | 
| 307 |                                                                        m_parentStack.top(), | 
| 308 |                                                                        reader.processingInstructionData().toString(), | 
| 309 |                                                                        QXmlName(m_namePool, reader.processingInstructionTarget().toString())); | 
| 310 |                 m_result.append(t: tmp); | 
| 311 |                 adjustSiblings(justBorn: tmp); | 
| 312 |                 break; | 
| 313 |             } | 
| 314 |             case QXmlStreamReader::Comment: | 
| 315 |             { | 
| 316 |                 LoadingModel::Node *const tmp = new LoadingModel::Node(QXmlNodeModelIndex::Comment, m_parentStack.top(), reader.text().toString()); | 
| 317 |                 m_result.append(t: tmp); | 
| 318 |                 adjustSiblings(justBorn: tmp); | 
| 319 |                 break; | 
| 320 |             } | 
| 321 |             case QXmlStreamReader::DTD: | 
| 322 |                 qFatal(msg: "%s: QXmlStreamReader::DTD token is not supported" , Q_FUNC_INFO); | 
| 323 |                 break; | 
| 324 |             case QXmlStreamReader::EntityReference: | 
| 325 |                 qFatal(msg: "%s: QXmlStreamReader::EntityReference token is not supported" , Q_FUNC_INFO); | 
| 326 |                 break; | 
| 327 |             case QXmlStreamReader::NoToken: | 
| 328 |             /* Fallthrough. */ | 
| 329 |             case QXmlStreamReader::Invalid: | 
| 330 |             { | 
| 331 |                 qWarning(msg: "%s" , qPrintable(reader.errorString())); | 
| 332 |                 m_result.clear(); | 
| 333 |                 return; | 
| 334 |             } | 
| 335 |         } | 
| 336 |     } | 
| 337 |  | 
| 338 |     if(reader.hasError()) | 
| 339 |     { | 
| 340 |         qWarning(msg: "%s" , qPrintable(reader.errorString())); | 
| 341 |         m_result.clear(); | 
| 342 |     } | 
| 343 | } | 
| 344 |  | 
| 345 | QAbstractXmlNodeModel::Ptr LoadingModel::create(const QXmlNamePool &np) | 
| 346 | { | 
| 347 |     Loader loader(np); | 
| 348 |     loader.load(); | 
| 349 |     if (loader.m_result.isEmpty()) { | 
| 350 |         qWarning(msg: "%s: attempt to create model with no content" , Q_FUNC_INFO); | 
| 351 |         return Ptr(0); | 
| 352 |     } | 
| 353 |  | 
| 354 |     return Ptr(new LoadingModel(loader.m_result, np)); | 
| 355 | } | 
| 356 |  |