source: trunk/tools/assistant/lib/qhelpprojectdata.cpp@ 846

Last change on this file since 846 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 14.4 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the Qt Assistant of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qhelpprojectdata_p.h"
43
44#include <QtCore/QCoreApplication>
45#include <QtCore/QDir>
46#include <QtCore/QFileInfo>
47#include <QtCore/QStack>
48#include <QtCore/QMap>
49#include <QtCore/QRegExp>
50#include <QtCore/QUrl>
51#include <QtCore/QVariant>
52#include <QtXml/QXmlStreamReader>
53
54QT_BEGIN_NAMESPACE
55
56class QHelpProjectDataPrivate : public QXmlStreamReader
57{
58public:
59 void readData(const QByteArray &contents);
60
61 QString virtualFolder;
62 QString namespaceName;
63 QString rootPath;
64
65 QStringList fileList;
66 QList<QHelpDataCustomFilter> customFilterList;
67 QList<QHelpDataFilterSection> filterSectionList;
68 QMap<QString, QVariant> metaData;
69
70 QString errorMsg;
71
72private:
73 void readProject();
74 void readCustomFilter();
75 void readFilterSection();
76 void readTOC();
77 void readKeywords();
78 void readFiles();
79 void raiseUnknownTokenError();
80 void addMatchingFiles(const QString &pattern);
81 bool hasValidSyntax(const QString &nameSpace, const QString &vFolder) const;
82
83 QMap<QString, QStringList> dirEntriesCache;
84};
85
86void QHelpProjectDataPrivate::raiseUnknownTokenError()
87{
88 raiseError(QCoreApplication::translate("QHelpProject", "Unknown token."));
89}
90
91void QHelpProjectDataPrivate::readData(const QByteArray &contents)
92{
93 addData(contents);
94 while (!atEnd()) {
95 readNext();
96 if (isStartElement()) {
97 if (name() == QLatin1String("QtHelpProject")
98 && attributes().value(QLatin1String("version")) == QLatin1String("1.0"))
99 readProject();
100 else
101 raiseError(QCoreApplication::translate("QHelpProject",
102 "Unknown token. Expected \"QtHelpProject\"!"));
103 }
104 }
105
106 if (hasError()) {
107 raiseError(QCoreApplication::translate("QHelpProject",
108 "Error in line %1: %2").arg(lineNumber())
109 .arg(errorString()));
110 }
111}
112
113void QHelpProjectDataPrivate::readProject()
114{
115 while (!atEnd()) {
116 readNext();
117 if (isStartElement()) {
118 if (name() == QLatin1String("virtualFolder")) {
119 virtualFolder = readElementText();
120 if (!hasValidSyntax(QLatin1String("test"), virtualFolder))
121 raiseError(QCoreApplication::translate("QHelpProject",
122 "Virtual folder has invalid syntax."));
123 } else if (name() == QLatin1String("namespace")) {
124 namespaceName = readElementText();
125 if (!hasValidSyntax(namespaceName, QLatin1String("test")))
126 raiseError(QCoreApplication::translate("QHelpProject",
127 "Namespace has invalid syntax."));
128 } else if (name() == QLatin1String("customFilter")) {
129 readCustomFilter();
130 } else if (name() == QLatin1String("filterSection")) {
131 readFilterSection();
132 } else if (name() == QLatin1String("metaData")) {
133 QString n = attributes().value(QLatin1String("name")).toString();
134 if (!metaData.contains(n))
135 metaData[n]
136 = attributes().value(QLatin1String("value")).toString();
137 else
138 metaData.insert(n, attributes().
139 value(QLatin1String("value")).toString());
140 } else {
141 raiseUnknownTokenError();
142 }
143 } else if (isEndElement() && name() == QLatin1String("QtHelpProject")) {
144 if (namespaceName.isEmpty())
145 raiseError(QCoreApplication::translate("QHelpProject",
146 "Missing namespace in QtHelpProject."));
147 else if (virtualFolder.isEmpty())
148 raiseError(QCoreApplication::translate("QHelpProject",
149 "Missing virtual folder in QtHelpProject"));
150 break;
151 }
152 }
153}
154
155void QHelpProjectDataPrivate::readCustomFilter()
156{
157 QHelpDataCustomFilter filter;
158 filter.name = attributes().value(QLatin1String("name")).toString();
159 while (!atEnd()) {
160 readNext();
161 if (isStartElement()) {
162 if (name() == QLatin1String("filterAttribute"))
163 filter.filterAttributes.append(readElementText());
164 else
165 raiseUnknownTokenError();
166 } else if (isEndElement() && name() == QLatin1String("customFilter")) {
167 break;
168 }
169 }
170 customFilterList.append(filter);
171}
172
173void QHelpProjectDataPrivate::readFilterSection()
174{
175 filterSectionList.append(QHelpDataFilterSection());
176 while (!atEnd()) {
177 readNext();
178 if (isStartElement()) {
179 if (name() == QLatin1String("filterAttribute"))
180 filterSectionList.last().addFilterAttribute(readElementText());
181 else if (name() == QLatin1String("toc"))
182 readTOC();
183 else if (name() == QLatin1String("keywords"))
184 readKeywords();
185 else if (name() == QLatin1String("files"))
186 readFiles();
187 else
188 raiseUnknownTokenError();
189 } else if (isEndElement() && name() == QLatin1String("filterSection")) {
190 break;
191 }
192 }
193}
194
195void QHelpProjectDataPrivate::readTOC()
196{
197 QStack<QHelpDataContentItem*> contentStack;
198 QHelpDataContentItem *itm = 0;
199 while (!atEnd()) {
200 readNext();
201 if (isStartElement()) {
202 if (name() == QLatin1String("section")) {
203 QString title = attributes().value(QLatin1String("title")).toString();
204 QString ref = attributes().value(QLatin1String("ref")).toString();
205 if (contentStack.isEmpty()) {
206 itm = new QHelpDataContentItem(0, title, ref);
207 filterSectionList.last().addContent(itm);
208 } else {
209 itm = new QHelpDataContentItem(contentStack.top(), title, ref);
210 }
211 contentStack.push(itm);
212 } else {
213 raiseUnknownTokenError();
214 }
215 } else if (isEndElement()) {
216 if (name() == QLatin1String("section")) {
217 contentStack.pop();
218 continue;
219 } else if (name() == QLatin1String("toc") && contentStack.isEmpty()) {
220 break;
221 } else {
222 raiseUnknownTokenError();
223 }
224 }
225 }
226}
227
228void QHelpProjectDataPrivate::readKeywords()
229{
230 while (!atEnd()) {
231 readNext();
232 if (isStartElement()) {
233 if (name() == QLatin1String("keyword")) {
234 if (attributes().value(QLatin1String("ref")).toString().isEmpty()
235 || (attributes().value(QLatin1String("name")).toString().isEmpty()
236 && attributes().value(QLatin1String("id")).toString().isEmpty()))
237 raiseError(QCoreApplication::translate("QHelpProject",
238 "Missing attribute in keyword at line %1.")
239 .arg(lineNumber()));
240 filterSectionList.last()
241 .addIndex(QHelpDataIndexItem(attributes().
242 value(QLatin1String("name")).toString(),
243 attributes().value(QLatin1String("id")).toString(),
244 attributes().value(QLatin1String("ref")).toString()));
245 } else {
246 raiseUnknownTokenError();
247 }
248 } else if (isEndElement()) {
249 if (name() == QLatin1String("keyword"))
250 continue;
251 else if (name() == QLatin1String("keywords"))
252 break;
253 else
254 raiseUnknownTokenError();
255 }
256 }
257}
258
259void QHelpProjectDataPrivate::readFiles()
260{
261 while (!atEnd()) {
262 readNext();
263 if (isStartElement()) {
264 if (name() == QLatin1String("file"))
265 addMatchingFiles(readElementText());
266 else
267 raiseUnknownTokenError();
268 } else if (isEndElement()) {
269 if (name() == QLatin1String("file"))
270 continue;
271 else if (name() == QLatin1String("files"))
272 break;
273 else
274 raiseUnknownTokenError();
275 }
276 }
277}
278
279// Expand file pattern and add matches into list. If the pattern does not match
280// any files, insert the pattern itself so the QHelpGenerator will emit a
281// meaningful warning later.
282void QHelpProjectDataPrivate::addMatchingFiles(const QString &pattern)
283{
284 // The pattern matching is expensive, so we skip it if no
285 // wildcard symbols occur in the string.
286 if (!pattern.contains('?') && !pattern.contains('*')
287 && !pattern.contains('[') && !pattern.contains(']')) {
288 filterSectionList.last().addFile(pattern);
289 return;
290 }
291
292 QFileInfo fileInfo(rootPath + '/' + pattern);
293 const QDir &dir = fileInfo.dir();
294 const QString &path = dir.canonicalPath();
295
296 // QDir::entryList() is expensive, so we cache the results.
297 QMap<QString, QStringList>::ConstIterator it = dirEntriesCache.find(path);
298 const QStringList &entries = it != dirEntriesCache.constEnd() ?
299 it.value() : dir.entryList(QDir::Files);
300 if (it == dirEntriesCache.constEnd())
301 dirEntriesCache.insert(path, entries);
302
303 bool matchFound = false;
304#ifdef Q_OS_WIN
305 Qt::CaseSensitivity cs = Qt::CaseInsensitive;
306#else
307 Qt::CaseSensitivity cs = Qt::CaseSensitive;
308#endif
309 QRegExp regExp(fileInfo.fileName(), cs, QRegExp::Wildcard);
310 foreach (const QString &file, entries) {
311 if (regExp.exactMatch(file)) {
312 matchFound = true;
313 filterSectionList.last().
314 addFile(QFileInfo(pattern).dir().path() + '/' + file);
315 }
316 }
317 if (!matchFound)
318 filterSectionList.last().addFile(pattern);
319}
320
321bool QHelpProjectDataPrivate::hasValidSyntax(const QString &nameSpace,
322 const QString &vFolder) const
323{
324 const QLatin1Char slash('/');
325 if (nameSpace.contains(slash) || vFolder.contains(slash))
326 return false;
327 QUrl url;
328 const QLatin1String scheme("qthelp");
329 url.setScheme(scheme);
330 const QString canonicalNamespace = nameSpace.toLower();
331 url.setHost(canonicalNamespace);
332 url.setPath(vFolder);
333
334 const QString expectedUrl(scheme + QLatin1String("://")
335 + canonicalNamespace + slash + vFolder);
336 return url.isValid() && url.toString() == expectedUrl;
337}
338
339/*!
340 \internal
341 \class QHelpProjectData
342 \since 4.4
343 \brief The QHelpProjectData class stores all information found
344 in a Qt help project file.
345
346 The structure is filled with data by calling readData(). The
347 specified file has to have the Qt help project file format in
348 order to be read successfully. Possible reading errors can be
349 retrieved by calling errorMessage().
350*/
351
352/*!
353 Constructs a Qt help project data structure.
354*/
355QHelpProjectData::QHelpProjectData()
356{
357 d = new QHelpProjectDataPrivate;
358}
359
360/*!
361 Destroys the help project data.
362*/
363QHelpProjectData::~QHelpProjectData()
364{
365 delete d;
366}
367
368/*!
369 Reads the file \a fileName and stores the help data. The file has to
370 have the Qt help project file format. Returns true if the file
371 was successfully read, otherwise false.
372
373 \sa errorMessage()
374*/
375bool QHelpProjectData::readData(const QString &fileName)
376{
377 d->rootPath = QFileInfo(fileName).absolutePath();
378 QFile file(fileName);
379 if (!file.open(QIODevice::ReadOnly)) {
380 d->errorMsg = QCoreApplication::translate("QHelpProject",
381 "The input file %1 could not be opened!").arg(fileName);
382 return false;
383 }
384
385 d->readData(file.readAll());
386 return !d->hasError();
387}
388
389/*!
390 Returns an error message if the reading of the Qt help project
391 file failed. Otherwise, an empty QString is returned.
392
393 \sa readData()
394*/
395QString QHelpProjectData::errorMessage() const
396{
397 if (d->hasError())
398 return d->errorString();
399 return d->errorMsg;
400}
401
402/*!
403 \internal
404*/
405QString QHelpProjectData::namespaceName() const
406{
407 return d->namespaceName;
408}
409
410/*!
411 \internal
412*/
413QString QHelpProjectData::virtualFolder() const
414{
415 return d->virtualFolder;
416}
417
418/*!
419 \internal
420*/
421QList<QHelpDataCustomFilter> QHelpProjectData::customFilters() const
422{
423 return d->customFilterList;
424}
425
426/*!
427 \internal
428*/
429QList<QHelpDataFilterSection> QHelpProjectData::filterSections() const
430{
431 return d->filterSectionList;
432}
433
434/*!
435 \internal
436*/
437QMap<QString, QVariant> QHelpProjectData::metaData() const
438{
439 return d->metaData;
440}
441
442/*!
443 \internal
444*/
445QString QHelpProjectData::rootPath() const
446{
447 return d->rootPath;
448}
449
450QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.