1
#include "deepfiletree.h"
2
#include <QUrl>
3
#include <QFileInfo>
4
#include <QDir>
5
#include <QVector>
6
#include <QVariant>
7
#include <QXmlQuery>
8
#include <QDomElement>
9
#include "pole.h"
10
#include <QDebug>
11
12
using namespace std;
13
14
/** This XML model spans the file system, OLE streams in OLE files and data structures in some
15
 *  OLE streams.
16
 **/
17
18
namespace {
19
class ElementNode {
20
public:
21
    // if parent, firstSibling, next or previous are -1, they corresponding node does not exist
22
    // if the value is -2, the corresponding node has not been searched yet
23
    int parentId;
24
    int firstSiblingId;
25
    int nextId;
26
    int previousId;
27
28
    ElementNode() :parentId(-2), firstSiblingId(-2), nextId(-2), previousId(-2) {}
29
};
30
31
class OleStreamElementNode : public ElementNode {
32
public:
33
    string streamname;
34
    int size;
35
    bool isdir;
36
};
37
38
class XmlElementNode : public ElementNode {
39
public:
40
    QDomElement element;
41
};
42
43
enum Type {
44
    File,
45
    OLEStream,
46
    XmlElement
47
};
48
49
const int typeSize = 3;
50
const int indexSize = sizeof(int)*8 - typeSize;
51
52
Type getType(int id) {
53
    return (Type)(id >> indexSize);
54
}
55
// last bits are used for the type
56
int getIndex(int id) {
57
    return (id << typeSize) >> typeSize;
58
}
59
int getId(Type t, int index) {
60
    return (t << indexSize) | index;
61
}
62
63
enum Kind {
64
    Element,
65
    AttributeName,
66
    AttributeSize
67
};
68
69
}
70
71
class DeepFileTree::Private {
72
public:
73
    QXmlNamePool namepool;
74
    QList<ElementNode> fileinfonodes;
75
    QList<QFileInfo> fileinfos;
76
    QList<OleStreamElementNode> olestreams;
77
    QList<XmlElementNode> xmlelements;
78
    QDomDocument doc;
79
    const QXmlName directory;
80
    const QXmlName file;
81
    const QXmlName olestream;
82
    const QXmlName olestreamdir;
83
    const QXmlName name;
84
    const QXmlName size;
85
86
    Private(const QXmlNamePool& pool) :namepool(pool),
87
            directory(QXmlName(namepool, QLatin1String("directory"))),
88
            file(QXmlName(namepool, QLatin1String("file"))),
89
            olestream(QXmlName(namepool, QLatin1String("olestream"))),
90
            olestreamdir(QXmlName(namepool, QLatin1String("olestreamdir"))),
91
            name(QXmlName(namepool, QLatin1String("name"))),
92
            size(QXmlName(namepool, QLatin1String("size"))) {
93
    }
94
95
    ElementNode& getElementNode(int id) {
96
        int index = getIndex(id);
97
        switch (getType(id)) {
98
            case File: return fileinfonodes[index];
99
            case OLEStream: return olestreams[index];
100
            case XmlElement:
101
            default:
102
            return xmlelements[index];
103
        }
104
    }
105
    QFileInfo* getFileInfo(int id) {
106
        return (getType(id) == File) ?&fileinfos[getIndex(id)] :0;
107
    }
108
109
    int getFileIndex(const QFileInfo& fileinfo) {
110
        // find the parent
111
        QDir dir = fileinfo.dir();
112
        QFileInfo parentInfo(dir.path());
113
        if (fileinfo == parentInfo) { // parent does not exist
114
            ElementNode node;
115
            node.parentId = node.nextId = node.previousId = -1;
116
            fileinfos.append(fileinfo);
117
            fileinfonodes.append(node);
118
            return fileinfonodes.count() - 1;
119
        }
120
121
        int parentIndex = getFileIndex(parentInfo);
122
        if (parentIndex == -1) return -1;
123
        ElementNode& parent = fileinfonodes[parentIndex];
124
        if (parent.firstSiblingId != -2) return -1;
125
126
        // the parent has not initialized its siblings yet
127
        const int indexOf = fileinfos.indexOf(parentInfo);
128
        if (indexOf != -1) return indexOf;
129
130
        int offset = fileinfos.count();
131
        loadFileSiblings(dir, parentIndex, parent);
132
        return fileinfos.indexOf(fileinfo, offset);
133
    }
134
135
    void loadFileSiblings(const QDir& dir, int parentIndex, ElementNode& parent) {
136
        QFileInfoList list = dir.entryInfoList(QStringList(),
137
            QDir::AllEntries|QDir::AllDirs|QDir::NoDotAndDotDot|QDir::Hidden, QDir::Name);
138
        int offset = fileinfos.count();
139
        fileinfos.append(list);
140
        parent.firstSiblingId = (list.count()) ?getId(File, offset) :-1;
141
        for (int i=0; i<list.size(); ++i) {
142
            ElementNode n;
143
            n.parentId = getId(File, parentIndex);
144
            n.previousId = (i) ?getId(File, offset + i - 1) :-1;
145
            n.nextId = (i == list.size()-1) ?-1 :getId(File, offset + i + 1);
146
            fileinfonodes.append(n);
147
        }
148
    }
149
150
    void loadStreamSiblings(const QFileInfo& info, int parentIndex, ElementNode& parent) {
151
//        if (info.suffix().toLower() != "ppt") return;
152
        POLE::Storage storage(info.absoluteFilePath().toLocal8Bit());
153
        if (!storage.open()) return;
154
        int offset = olestreams.count();
155
        int count = 0;
156
        list<string> entries = storage.entries();
157
        for (list<string>::const_iterator i=entries.begin(); i!=entries.end(); ++i) {
158
            if (!storage.isDirectory(*i)) {
159
                POLE::Stream stream(&storage, "/"+*i);
160
                OleStreamElementNode n;
161
                n.streamname = *i;
162
                n.size = stream.size();
163
                n.isdir = false;
164
                n.parentId = getId(File, parentIndex);
165
                n.previousId = (count) ?getId(OLEStream, offset + count - 1) :-1;
166
                n.nextId = getId(OLEStream, offset + count + 1);
167
                olestreams.append(n);
168
                count++;
169
            }
170
        }
171
        if (count) {
172
            parent.firstSiblingId = getId(OLEStream, offset);
173
            olestreams[olestreams.count()-1].nextId = -1;
174
        }
175
    }
176
177
    QDomElement createTestElement() {
178
        QDomElement e(doc.createElement("TESTING"));
179
        e.setAttribute("e", "E");
180
        QDomElement a(doc.createElement("suba"));
181
        a.setAttribute("a", "A");
182
        QDomElement b(doc.createElement("subb"));
183
        b.setAttribute("b", "B");
184
        b.setAttribute("c", "C");
185
        e.appendChild(a);
186
        e.appendChild(b);
187
        return e;
188
    }
189
190
    void loadStreamAsXmlElements(int parentId, ElementNode& parent) {
191
        parent.firstSiblingId = getId(XmlElement, xmlelements.count());
192
        QDomElement e = createTestElement();
193
        XmlElementNode& en = addXmlElement(e);
194
        en.parentId = parentId;
195
        en.nextId = -1;
196
        en.previousId = -1;
197
    }
198
199
    XmlElementNode& addXmlElement(const QDomElement& e) {
200
        int eindex = xmlelements.count();
201
        xmlelements.append(XmlElementNode());
202
        XmlElementNode& xe = xmlelements[eindex];
203
        xe.element = e;
204
205
        int eid = getId(XmlElement, eindex);
206
        int count = 0;
207
        QDomElement ce = e.firstChildElement();
208
        while (!ce.isNull()) {
209
            count++;
210
            XmlElementNode& cen = addXmlElement(ce);
211
            cen.parentId = eid; 
212
            cen.previousId = (count) ?eid + count - 1:-1;
213
            ce = ce.nextSiblingElement();
214
            cen.nextId = (ce.isNull()) ?-1 :eid + count + 1;
215
        }
216
        xe.firstSiblingId = (count > 0) ?eid + 1 :-1;
217
        return xe;
218
    }
219
};
220
221
DeepFileTree::DeepFileTree(const QXmlNamePool& pool) :d(new Private(pool)) {
222
}
223
DeepFileTree::~DeepFileTree() {
224
    delete d;
225
}
226
227
QXmlNodeModelIndex
228
DeepFileTree::toNodeIndex(const QFileInfo& fileinfo) const {
229
    int indexOf = d->fileinfos.indexOf(fileinfo);
230
    if (indexOf == -1) {
231
        indexOf = d->getFileIndex(fileinfo);
232
        if (indexOf == -1) return QXmlNodeModelIndex();
233
    }
234
    return createIndex(indexOf, Element);
235
}
236
237
QVector<QXmlNodeModelIndex>
238
DeepFileTree::attributes(const QXmlNodeModelIndex& element) const {
239
    QVector<QXmlNodeModelIndex> v;
240
    Type type = getType(element.data());
241
    switch (type) {
242
    case File: {
243
        v.append(createIndex(element.data(), AttributeName));
244
        const QFileInfo* info = d->getFileInfo(element.data());
245
        if (info->isFile()) {
246
            v.append(createIndex(element.data(), AttributeSize));
247
        }
248
        break;
249
    }
250
    case OLEStream: {
251
        v.append(createIndex(element.data(), AttributeName));
252
        const OleStreamElementNode& e = (const OleStreamElementNode&)d->getElementNode(element.data());
253
        if (!e.isdir) {
254
            v.append(createIndex(element.data(), AttributeSize));
255
        }
256
        break;
257
    }
258
    case XmlElement: {
259
        QDomElement e = d->xmlelements[getIndex(element.data())].element;
260
        QDomNamedNodeMap attributes = e.attributes();
261
        for (uint i=0; i<attributes.length(); ++i) {
262
             v.append(createIndex(element.data(), i+1));
263
        }
264
    }
265
    }
266
    return v;
267
}
268
269
QXmlNodeModelIndex
270
DeepFileTree::nextFromSimpleAxis(SimpleAxis axis, const QXmlNodeModelIndex& origin) const {
271
    const QFileInfo* info = d->getFileInfo(origin.data());
272
    ElementNode& node = d->getElementNode(origin.data());
273
274
    int data = -1;
275
    switch (axis) {
276
        case Parent:
277
            data = node.parentId; break;
278
279
        case FirstChild:
280
            if (node.firstSiblingId == -2) {
281
                if (info && info->isDir()) {
282
                    d->loadFileSiblings(info->filePath(), origin.data(), node);
283
                } else if (info && info->isFile()) {
284
                    d->loadStreamSiblings(info->filePath(), origin.data(), node);
285
                } else if (getType(origin.data()) == OLEStream) {
286
                    d->loadStreamAsXmlElements(origin.data(), node);
287
                }
288
                if (node.firstSiblingId == -2) {
289
                    node.firstSiblingId = -1;
290
                }
291
            }
292
            data = node.firstSiblingId; break;
293
 
294
        case PreviousSibling:
295
            data = node.previousId; break;
296
297
        case NextSibling:
298
            data = node.nextId; break;
299
    };
300
    return (data == -1) ?QXmlNodeModelIndex() :createIndex(data, 0);
301
}
302
303
QUrl
304
DeepFileTree::baseUri(const QXmlNodeModelIndex& n) const {
305
    return documentUri(n);
306
}
307
QXmlNodeModelIndex::DocumentOrder
308
DeepFileTree::compareOrder(const QXmlNodeModelIndex& ni1, const QXmlNodeModelIndex& ni2) const {
309
    if (ni1.data() < ni2.data()) return QXmlNodeModelIndex::Precedes;
310
    return (ni1.data() == ni2.data()) ?QXmlNodeModelIndex::Is :QXmlNodeModelIndex::Follows;
311
}
312
QUrl
313
DeepFileTree::documentUri(const QXmlNodeModelIndex& n) const {
314
    // TODO: find root and get uri from that
315
    const QFileInfo& fileinfo = d->fileinfos[n.data()];
316
    return QUrl::fromLocalFile(fileinfo.filePath());
317
}
318
QXmlNodeModelIndex
319
DeepFileTree::elementById(const QXmlName& id) const {
320
    return QXmlNodeModelIndex();
321
}
322
QXmlNodeModelIndex::NodeKind
323
DeepFileTree::kind(const QXmlNodeModelIndex& ni) const {
324
    return ni.additionalData() == Element ? QXmlNodeModelIndex::Element : QXmlNodeModelIndex::Attribute;
325
}
326
QXmlName
327
DeepFileTree::name(const QXmlNodeModelIndex& ni) const {
328
    QVector<QXmlNodeModelIndex> v;
329
    Type type = getType(ni.data());
330
    switch (type) {
331
        case File:
332
            switch ((Kind)ni.additionalData()) {
333
                case Element: return d->getFileInfo(ni.data())->isFile() ?d->file :d->directory;
334
                case AttributeName: return d->name;
335
                case AttributeSize: return d->size;
336
            }
337
        case OLEStream:
338
            switch ((Kind)ni.additionalData()) {
339
                case Element: {
340
                    const OleStreamElementNode& e
341
                         = (const OleStreamElementNode&)d->getElementNode(ni.data());
342
                    return (e.isdir) ?d->olestreamdir :d->olestream;
343
                }
344
                case AttributeName: return d->name;
345
                case AttributeSize: return d->size;
346
            }
347
        case XmlElement:
348
        default: {
349
            QDomElement e = d->xmlelements[getIndex(ni.data())].element;
350
            if (ni.additionalData() == 0) {
351
                return QXmlName(d->namepool, e.nodeName());
352
            } else {
353
                return QXmlName(d->namepool, e.attributes().item(ni.additionalData()-1).nodeName());
354
            }
355
        }
356
    }
357
}
358
QVector<QXmlName>
359
DeepFileTree::namespaceBindings(const QXmlNodeModelIndex& n) const {
360
    return QVector<QXmlName>();
361
}
362
QVector<QXmlNodeModelIndex>
363
DeepFileTree::nodesByIdref(const QXmlName& idref) const {
364
    return QVector<QXmlNodeModelIndex>();
365
}
366
QXmlNodeModelIndex
367
DeepFileTree::root(const QXmlNodeModelIndex& n) const {
368
    int index = n.data();
369
    ElementNode& e = d->getElementNode(n.data());
370
    while (e.parentId != -1) {
371
         index = e.parentId;
372
         e = d->getElementNode(index);
373
    }
374
    return createIndex(index, 0);
375
}
376
QString
377
DeepFileTree::stringValue(const QXmlNodeModelIndex& n) const {
378
    return typedValue(n).toString();
379
}
380
QVariant
381
DeepFileTree::typedValue(const QXmlNodeModelIndex& n) const {
382
    Type type = getType(n.data());
383
    switch (type) {
384
        case File: {
385
            const QFileInfo* info = d->getFileInfo(n.data());
386
            switch ((Kind)n.additionalData()) {
387
                case AttributeName: return info->fileName();
388
                case AttributeSize: return info->size();
389
                default:;
390
            }
391
        }
392
        case OLEStream: {
393
            const OleStreamElementNode& e
394
                = (const OleStreamElementNode&)d->getElementNode(n.data());
395
            switch ((Kind)n.additionalData()) {
396
                case AttributeName: return e.streamname.c_str();
397
                case AttributeSize: return e.size;
398
                default:;
399
            }
400
        }
401
        case XmlElement: {
402
            if (n.additionalData() > 0) {
403
                QDomElement e = d->xmlelements[getIndex(n.data())].element;
404
                return e.attributes().item(n.additionalData()-1).nodeValue();
405
            }
406
        }
407
    }
408
    return QVariant();
409
}