Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qhelpenginecore.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
9#include "qhelplink.h"
10
11#if QT_CONFIG(future)
12#include <QtConcurrent/qtconcurrentrun.h>
13#include <QtCore/qpromise.h>
14#endif
15
16#include <QtCore/qdir.h>
17#include <QtCore/qfileinfo.h>
18
20
21using namespace Qt::StringLiterals;
22
24{
25public:
26 QHelpEngineCorePrivate(const QString &collectionFile, QHelpEngineCore *helpEngineCore);
27
28 void init(const QString &collectionFile);
29 bool setup();
30
32 QHelpFilterEngine *filterEngine = nullptr;
35 bool needsSetup = true;
36 bool autoSaveFilter = true;
37 bool usesFilterEngine = false;
38 bool readOnly = true;
39
40 QHelpEngineCore *q;
41};
42
43QHelpEngineCorePrivate::QHelpEngineCorePrivate(const QString &collectionFile,
44 QHelpEngineCore *helpEngineCore)
45{
46 q = helpEngineCore;
47 filterEngine = new QHelpFilterEngine(q);
48 init(collectionFile);
49}
50
51void QHelpEngineCorePrivate::init(const QString &collectionFile)
52{
53 collectionHandler.reset(new QHelpCollectionHandler(collectionFile, q));
54 QObject::connect(collectionHandler.get(), &QHelpCollectionHandler::error, q,
55 [this](const QString &msg) { error = msg; });
56 filterEngine->setCollectionHandler(collectionHandler.get());
57 needsSetup = true;
58}
59
61{
62 error.clear();
63 if (!needsSetup)
64 return true;
65
66 needsSetup = false;
67 emit q->setupStarted();
68
69 collectionHandler->setReadOnly(q->isReadOnly());
70 const bool opened = collectionHandler->openCollectionFile();
71 if (opened)
72 q->currentFilter();
73
74 emit q->setupFinished();
75 return opened;
76}
77
78/*!
79 \class QHelpEngineCore
80 \since 4.4
81 \inmodule QtHelp
82 \brief The QHelpEngineCore class provides the core functionality
83 of the help system.
84
85 Before the help engine can be used, it must be initialized by
86 calling setupData(). At the beginning of the setup process the
87 signal setupStarted() is emitted. From this point on until
88 the signal setupFinished() is emitted, is the help data in an
89 undefined meaning unusable state.
90
91 The core help engine can be used to perform different tasks.
92 By calling documentsForIdentifier() the engine returns
93 URLs specifying the file locations inside the help system. The
94 actual file data can then be retrieved by calling fileData().
95
96 The help engine can contain any number of custom filters.
97 The management of the filters, including adding new filters,
98 changing filter definitions, or removing existing filters,
99 is done through the QHelpFilterEngine class, which can be accessed
100 by the filterEngine() method.
101
102 \note QHelpFilterEngine replaces the older filter API that is
103 deprecated since Qt 5.13. Call setUsesFilterEngine() with \c true to
104 enable the new functionality.
105
106 The core help engine has two modes:
107 \list
108 \li Read-only mode, where the help collection file is not changed
109 unless explicitly requested. This also works if the
110 collection file is in a read-only location,
111 and is the default.
112 \li Fully writable mode, which requires the help collection
113 file to be writable.
114 \endlist
115 The mode can be changed by calling setReadOnly() method, prior to
116 calling setupData().
117
118 The help engine also offers the possibility to set and read values
119 in a persistent way comparable to ini files or Windows registry
120 entries. For more information see \l setCustomValue() or \l customValue().
121
122 This class does not offer any GUI components or functionality for
123 indices or contents. If you need one of those use QHelpEngine
124 instead.
125*/
126
127/*!
128 \fn void QHelpEngineCore::setupStarted()
129
130 This signal is emitted when setup is started.
131*/
132
133/*!
134 \fn void QHelpEngineCore::setupFinished()
135
136 This signal is emitted when the setup is complete.
137*/
138
139/*!
140 \fn void QHelpEngineCore::readersAboutToBeInvalidated()
141 \deprecated
142*/
143
144/*!
145 \fn void QHelpEngineCore::currentFilterChanged(const QString &newFilter)
146 \deprecated
147
148 QHelpFilterEngine::filterActivated() should be used instead.
149
150 This signal is emitted when the current filter is changed to
151 \a newFilter.
152*/
153
154/*!
155 \fn void QHelpEngineCore::warning(const QString &msg)
156
157 This signal is emitted when a non critical error occurs.
158 The warning message is stored in \a msg.
159*/
160
161/*!
162 Constructs a new core help engine with a \a parent. The help engine
163 uses the information stored in the \a collectionFile to provide help.
164 If the collection file does not exist yet, it'll be created.
165*/
166QHelpEngineCore::QHelpEngineCore(const QString &collectionFile, QObject *parent)
167 : QObject(parent)
168 , d(new QHelpEngineCorePrivate(collectionFile, this))
169{}
170
171/*!
172 \internal
173*/
174#if QT_DEPRECATED_SINCE(6, 8)
175QHelpEngineCore::QHelpEngineCore(QHelpEngineCorePrivate *helpEngineCorePrivate, QObject *parent)
176 : QObject(parent)
177 , d(helpEngineCorePrivate)
178{}
179#endif
180
181/*!
182 Destructs the help engine.
183*/
184QHelpEngineCore::~QHelpEngineCore()
185{
186 delete d;
187}
188
189/*!
190 \property QHelpEngineCore::collectionFile
191 \brief the absolute file name of the collection file currently used.
192 \since 4.5
193
194 Setting this property leaves the help engine in an invalid state. It is
195 important to invoke setupData() or any getter function in order to setup
196 the help engine again.
197*/
198QString QHelpEngineCore::collectionFile() const
199{
200 return d->collectionHandler->collectionFile();
201}
202
203void QHelpEngineCore::setCollectionFile(const QString &fileName)
204{
205 if (fileName != collectionFile())
206 d->init(fileName);
207}
208
209/*!
210 \property QHelpEngineCore::readOnly
211 \brief whether the help engine is read-only.
212 \since 6.0
213
214 In read-only mode, the user can use the help engine
215 with a collection file installed in a read-only location.
216 In this case, some functionality won't be accessible,
217 like registering additional documentation, filter editing,
218 or any action that would require changes to the
219 collection file. Setting it to \c false enables the full
220 functionality of the help engine.
221
222 By default, this property is \c true.
223*/
224bool QHelpEngineCore::isReadOnly() const
225{
226 return d->readOnly;
227}
228
229void QHelpEngineCore::setReadOnly(bool enable)
230{
231 if (d->readOnly == enable)
232 return;
233
234 d->readOnly = enable;
235 d->init(collectionFile());
236}
237
238/*!
239 \since 5.13
240
241 Returns the filter engine associated with this help engine.
242 The filter engine allows for adding, changing, and removing existing
243 filters for this help engine. To use the engine you also have to call
244 \l setUsesFilterEngine() set to \c true.
245*/
246QHelpFilterEngine *QHelpEngineCore::filterEngine() const
247{
248 return d->filterEngine;
249}
250
251/*!
252 Sets up the help engine by processing the information found
253 in the collection file and returns true if successful; otherwise
254 returns false.
255
256 By calling the function, the help
257 engine is forced to initialize itself immediately. Most of
258 the times, this function does not have to be called
259 explicitly because getter functions which depend on a correctly
260 set up help engine do that themselves.
261
262 In read-only mode, the collection file must already exist and be
263 non-empty, since it is never created or modified. Otherwise, this
264 function fails and error() returns a description of the problem.
265
266 \note \c{qsqlite4.dll} needs to be deployed with the application as the
267 help system uses the sqlite driver when loading help collections.
268*/
269bool QHelpEngineCore::setupData()
270{
271 d->needsSetup = true;
272 return d->setup();
273}
274
275/*!
276 Creates the file \a fileName and copies all contents from
277 the current collection file into the newly created file,
278 and returns true if successful; otherwise returns false.
279
280 The copying process makes sure that file references to Qt
281 Collection files (\c{.qch}) files are updated accordingly.
282*/
283bool QHelpEngineCore::copyCollectionFile(const QString &fileName)
284{
285 if (!d->setup())
286 return false;
287 return d->collectionHandler->copyCollectionFile(fileName);
288}
289
290/*!
291 Returns the namespace name defined for the Qt compressed help file (.qch)
292 specified by its \a documentationFileName. If the file is not valid, an
293 empty string is returned.
294
295 \sa documentationFileName()
296*/
297QString QHelpEngineCore::namespaceName(const QString &documentationFileName)
298{
299 void *pointer = const_cast<QString *>(&documentationFileName);
300 QHelpDBReader reader(documentationFileName, QHelpGlobal::uniquifyConnectionName(
301 "GetNamespaceName"_L1, pointer), nullptr);
302 if (reader.init())
303 return reader.namespaceName();
304 return {};
305}
306
307/*!
308 Registers the Qt compressed help file (.qch) contained in the file
309 \a documentationFileName. One compressed help file, uniquely
310 identified by its namespace can only be registered once.
311 True is returned if the registration was successful, otherwise
312 false.
313
314 \sa unregisterDocumentation(), error()
315*/
316bool QHelpEngineCore::registerDocumentation(const QString &documentationFileName)
317{
318 d->error.clear();
319 d->needsSetup = true;
320 return d->collectionHandler->registerDocumentation(documentationFileName);
321}
322
323/*!
324 Unregisters the Qt compressed help file (.qch) identified by its
325 \a namespaceName from the help collection. Returns true
326 on success, otherwise false.
327
328 \sa registerDocumentation(), error()
329*/
330bool QHelpEngineCore::unregisterDocumentation(const QString &namespaceName)
331{
332 d->error.clear();
333 d->needsSetup = true;
334 return d->collectionHandler->unregisterDocumentation(namespaceName);
335}
336
337/*!
338 Returns the absolute file name of the Qt compressed help file (.qch)
339 identified by the \a namespaceName. If there is no Qt compressed help file
340 with the specified namespace registered, an empty string is returned.
341
342 \sa namespaceName()
343*/
344QString QHelpEngineCore::documentationFileName(const QString &namespaceName)
345{
346 if (!d->setup())
347 return {};
348
349 const QHelpCollectionHandler::FileInfo fileInfo =
350 d->collectionHandler->registeredDocumentation(namespaceName);
351
352 if (fileInfo.namespaceName.isEmpty())
353 return {};
354
355 if (QDir::isAbsolutePath(fileInfo.fileName))
356 return fileInfo.fileName;
357
358 return QFileInfo(QFileInfo(d->collectionHandler->collectionFile()).absolutePath()
359 + u'/' + fileInfo.fileName).absoluteFilePath();
360}
361
362/*!
363 Returns a list of all registered Qt compressed help files of the current collection file.
364 The returned names are the namespaces of the registered Qt compressed help files (.qch).
365*/
366QStringList QHelpEngineCore::registeredDocumentations() const
367{
368 if (!d->setup())
369 return {};
370 const auto &docList = d->collectionHandler->registeredDocumentations();
371 QStringList list;
372 for (const QHelpCollectionHandler::FileInfo &info : docList)
373 list.append(info.namespaceName);
374 return list;
375}
376
377/*!
378 \deprecated
379
380 QHelpFilterEngine::filters() should be used instead.
381
382 Returns a list of custom filters.
383
384 \sa addCustomFilter(), removeCustomFilter()
385*/
386QStringList QHelpEngineCore::customFilters() const
387{
388 if (!d->setup())
389 return {};
390 return d->collectionHandler->customFilters();
391}
392
393/*!
394 \deprecated
395
396 QHelpFilterEngine::setFilterData() should be used instead.
397
398 Adds the new custom filter \a filterName. The filter attributes
399 are specified by \a attributes. If the filter already exists,
400 its attribute set is replaced. The function returns true if
401 the operation succeeded, otherwise it returns false.
402
403 \sa customFilters(), removeCustomFilter()
404*/
405bool QHelpEngineCore::addCustomFilter(const QString &filterName,
406 const QStringList &attributes)
407{
408 d->error.clear();
409 d->needsSetup = true;
410 return d->collectionHandler->addCustomFilter(filterName, attributes);
411}
412
413/*!
414 \deprecated
415
416 QHelpFilterEngine::removeFilter() should be used instead.
417
418 Returns true if the filter \a filterName was removed successfully,
419 otherwise false.
420
421 \sa addCustomFilter(), customFilters()
422*/
423bool QHelpEngineCore::removeCustomFilter(const QString &filterName)
424{
425 d->error.clear();
426 d->needsSetup = true;
427 return d->collectionHandler->removeCustomFilter(filterName);
428}
429
430/*!
431 \deprecated
432
433 QHelpFilterEngine::availableComponents() should be used instead.
434
435 Returns a list of all defined filter attributes.
436*/
437QStringList QHelpEngineCore::filterAttributes() const
438{
439 if (!d->setup())
440 return {};
441 return d->collectionHandler->filterAttributes();
442}
443
444/*!
445 \deprecated
446
447 QHelpFilterEngine::filterData() should be used instead.
448
449 Returns a list of filter attributes used by the custom
450 filter \a filterName.
451*/
452QStringList QHelpEngineCore::filterAttributes(const QString &filterName) const
453{
454 if (!d->setup())
455 return {};
456 return d->collectionHandler->filterAttributes(filterName);
457}
458
459/*!
460 \deprecated
461 \property QHelpEngineCore::currentFilter
462 \brief the name of the custom filter currently applied.
463 \since 4.5
464
465 QHelpFilterEngine::activeFilter() should be used instead.
466
467 Setting this property will save the new custom filter permanently in the
468 help collection file. To set a custom filter without saving it
469 permanently, disable the auto save filter mode.
470
471 \sa autoSaveFilter()
472*/
473QString QHelpEngineCore::currentFilter() const
474{
475 if (!d->setup())
476 return {};
477
478 if (d->currentFilter.isEmpty()) {
479 const QString &filter =
480 d->collectionHandler->customValue("CurrentFilter"_L1, QString()).toString();
481 if (!filter.isEmpty() && d->collectionHandler->customFilters().contains(filter))
482 d->currentFilter = filter;
483 }
484 return d->currentFilter;
485}
486
487void QHelpEngineCore::setCurrentFilter(const QString &filterName)
488{
489 if (!d->setup() || filterName == d->currentFilter)
490 return;
491 d->currentFilter = filterName;
492 if (d->autoSaveFilter)
493 d->collectionHandler->setCustomValue("CurrentFilter"_L1, d->currentFilter);
494 emit currentFilterChanged(d->currentFilter);
495}
496
497/*!
498 \deprecated
499
500 QHelpFilterEngine::filterData() should be used instead.
501
502 Returns a list of filter attributes for the different filter sections
503 defined in the Qt compressed help file with the given namespace
504 \a namespaceName.
505*/
506QList<QStringList> QHelpEngineCore::filterAttributeSets(const QString &namespaceName) const
507{
508 if (!d->setup())
509 return {};
510 return d->collectionHandler->filterAttributeSets(namespaceName);
511}
512
513/*!
514 \deprecated
515
516 files() should be used instead.
517
518 Returns a list of files contained in the Qt compressed help file \a
519 namespaceName. The files can be filtered by \a filterAttributes as
520 well as by their extension \a extensionFilter (e.g. 'html').
521*/
522QList<QUrl> QHelpEngineCore::files(const QString namespaceName,
523 const QStringList &filterAttributes,
524 const QString &extensionFilter)
525{
526 QList<QUrl> res;
527 if (!d->setup())
528 return res;
529
530 QUrl url;
531 url.setScheme("qthelp"_L1);
532 url.setAuthority(namespaceName);
533
534 const QStringList &files = d->collectionHandler->files(
535 namespaceName, filterAttributes, extensionFilter);
536 for (const QString &file : files) {
537 url.setPath("/"_L1 + file);
538 res.append(url);
539 }
540 return res;
541}
542
543/*!
544 Returns a list of files contained in the Qt compressed help file
545 for \a namespaceName. The files can be filtered by \a filterName as
546 well as by their extension \a extensionFilter (for example, 'html').
547*/
548QList<QUrl> QHelpEngineCore::files(const QString namespaceName,
549 const QString &filterName,
550 const QString &extensionFilter)
551{
552 QList<QUrl> res;
553 if (!d->setup())
554 return res;
555
556 QUrl url;
557 url.setScheme("qthelp"_L1);
558 url.setAuthority(namespaceName);
559
560 const QStringList &files = d->collectionHandler->files(
561 namespaceName, filterName, extensionFilter);
562 for (const QString &file : files) {
563 url.setPath("/"_L1 + file);
564 res.append(url);
565 }
566 return res;
567}
568
569/*!
570 Returns the corrected URL for the \a url that may refer to
571 a different namespace defined by the virtual folder defined
572 as a part of the \a url. If the virtual folder matches the namespace
573 of the \a url, the method just checks if the file exists and returns
574 the same \a url. When the virtual folder doesn't match the namespace
575 of the \a url, it tries to find the best matching namespace according
576 to the active filter. When the namespace is found, it returns the
577 corrected URL if the file exists, otherwise it returns an invalid URL.
578*/
579QUrl QHelpEngineCore::findFile(const QUrl &url) const
580{
581 if (!d->setup())
582 return url;
583
584 QUrl result = d->usesFilterEngine
585 ? d->collectionHandler->findFile(url, d->filterEngine->activeFilter())
586 : d->collectionHandler->findFile(url, filterAttributes(currentFilter())); // obsolete
587 if (!result.isEmpty())
588 return result;
589
590 result = d->usesFilterEngine
591 ? d->collectionHandler->findFile(url, QString())
592 : d->collectionHandler->findFile(url, QStringList()); // obsolete
593 if (!result.isEmpty())
594 return result;
595
596 return url;
597}
598
599/*!
600 Returns the data of the file specified by \a url. If the
601 file does not exist, an empty QByteArray is returned.
602
603 The data is returned exactly as it is stored in the help file, without
604 any filtering. Treat it as untrusted content: the caller is responsible
605 for displaying it in a suitably restricted environment. For more
606 information, see \l{qthelp-security-considerations}{Security Considerations}.
607
608 \sa findFile()
609*/
610QByteArray QHelpEngineCore::fileData(const QUrl &url) const
611{
612 if (!d->setup())
613 return {};
614 return d->collectionHandler->fileData(url);
615}
616
617/*!
618 \since 5.15
619
620 Returns a list of all the document links found for the \a id.
621 The returned list contents depend on the current filter, and therefore only the keywords
622 registered for the current filter will be returned.
623*/
624QList<QHelpLink> QHelpEngineCore::documentsForIdentifier(const QString &id) const
625{
626 return documentsForIdentifier(
627 id, d->usesFilterEngine ? d->filterEngine->activeFilter() : d->currentFilter);
628}
629
630/*!
631 \since 5.15
632
633 Returns a list of the document links found for the \a id, filtered by \a filterName.
634 The returned list contents depend on the passed filter, and therefore only the keywords
635 registered for this filter will be returned. If you want to get all results unfiltered,
636 pass empty string as \a filterName.
637*/
638QList<QHelpLink> QHelpEngineCore::documentsForIdentifier(const QString &id, const QString &filterName) const
639{
640 if (!d->setup())
641 return {};
642
643 if (d->usesFilterEngine)
644 return d->collectionHandler->documentsForIdentifier(id, filterName);
645 return d->collectionHandler->documentsForIdentifier(id, filterAttributes(filterName));
646}
647
648/*!
649 \since 5.15
650
651 Returns a list of all the document links found for the \a keyword.
652 The returned list contents depend on the current filter, and therefore only the keywords
653 registered for the current filter will be returned.
654*/
655QList<QHelpLink> QHelpEngineCore::documentsForKeyword(const QString &keyword) const
656{
657 return documentsForKeyword(
658 keyword, d->usesFilterEngine ? d->filterEngine->activeFilter() : d->currentFilter);
659}
660
661/*!
662 \since 5.15
663
664 Returns a list of the document links found for the \a keyword, filtered by \a filterName.
665 The returned list contents depend on the passed filter, and therefore only the keywords
666 registered for this filter will be returned. If you want to get all results unfiltered,
667 pass empty string as \a filterName.
668*/
669QList<QHelpLink> QHelpEngineCore::documentsForKeyword(const QString &keyword, const QString &filterName) const
670{
671 if (!d->setup())
672 return {};
673
674 if (d->usesFilterEngine)
675 return d->collectionHandler->documentsForKeyword(keyword, filterName);
676 return d->collectionHandler->documentsForKeyword(keyword, filterAttributes(filterName));
677}
678
679/*!
680 Removes the \a key from the settings section in the
681 collection file. Returns true if the value was removed
682 successfully, otherwise false.
683
684 \sa customValue(), setCustomValue()
685*/
686bool QHelpEngineCore::removeCustomValue(const QString &key)
687{
688 d->error.clear();
689 return d->collectionHandler->removeCustomValue(key);
690}
691
692/*!
693 Returns the value assigned to the \a key. If the requested
694 key does not exist, the specified \a defaultValue is
695 returned.
696
697 \sa setCustomValue(), removeCustomValue()
698*/
699QVariant QHelpEngineCore::customValue(const QString &key, const QVariant &defaultValue) const
700{
701 if (!d->setup())
702 return {};
703 return d->collectionHandler->customValue(key, defaultValue);
704}
705
706/*!
707 Save the \a value under the \a key. If the key already exist,
708 the value will be overwritten. Returns true if the value was
709 saved successfully, otherwise false.
710
711 \sa customValue(), removeCustomValue()
712*/
713bool QHelpEngineCore::setCustomValue(const QString &key, const QVariant &value)
714{
715 d->error.clear();
716 return d->collectionHandler->setCustomValue(key, value);
717}
718
719/*!
720 Returns the meta data for the Qt compressed help file \a
721 documentationFileName. If there is no data available for
722 \a name, an invalid QVariant() is returned. The meta
723 data is defined when creating the Qt compressed help file and
724 cannot be modified later. Common meta data includes e.g.
725 the author of the documentation.
726*/
727QVariant QHelpEngineCore::metaData(const QString &documentationFileName,
728 const QString &name)
729{
730 QHelpDBReader reader(documentationFileName, "GetMetaData"_L1, nullptr);
731
732 if (reader.init())
733 return reader.metaData(name);
734 return {};
735}
736
737/*!
738 Returns a description of the last error that occurred.
739*/
740QString QHelpEngineCore::error() const
741{
742 return d->error;
743}
744
745/*!
746 \property QHelpEngineCore::autoSaveFilter
747 \brief whether QHelpEngineCore is in auto save filter mode or not.
748 \since 4.5
749
750 If QHelpEngineCore is in auto save filter mode, the current filter is
751 automatically saved when it is changed by the QHelpFilterEngine::setActiveFilter()
752 function. The filter is saved persistently in the help collection file.
753
754 By default, this mode is on.
755*/
756void QHelpEngineCore::setAutoSaveFilter(bool save)
757{
758 d->autoSaveFilter = save;
759}
760
761bool QHelpEngineCore::autoSaveFilter() const
762{
763 return d->autoSaveFilter;
764}
765
766/*!
767 \since 5.13
768
769 Enables or disables the new filter engine functionality
770 inside the help engine, according to the passed \a uses parameter.
771
772 \sa filterEngine()
773*/
774void QHelpEngineCore::setUsesFilterEngine(bool uses)
775{
776 d->usesFilterEngine = uses;
777}
778
779/*!
780 \since 5.13
781
782 Returns whether the help engine uses the new filter functionality.
783
784 \sa filterEngine()
785*/
786bool QHelpEngineCore::usesFilterEngine() const
787{
788 return d->usesFilterEngine;
789}
790
791#if QT_CONFIG(future)
792static QUrl constructUrl(const QString &namespaceName, const QString &folderName,
793 const QString &relativePath)
794{
795 const int idx = relativePath.indexOf(u'#');
796 const QString &rp = idx < 0 ? relativePath : relativePath.left(idx);
797 const QString anchor = idx < 0 ? QString() : relativePath.mid(idx + 1);
798 return QHelpCollectionHandler::buildQUrl(namespaceName, folderName, rp, anchor);
799}
800
801using ContentProviderResult = QList<QHelpCollectionHandler::ContentsData>;
802using ContentProvider = std::function<ContentProviderResult(const QString &)>;
803using ContentResult = std::shared_ptr<QHelpContentItem>;
804
805// This trick is needed because the c'tor of QHelpContentItem is private.
806QHelpContentItem *createContentItem(const QString &name = {}, const QUrl &link = {},
807 QHelpContentItem *parent = {})
808{
809 return new QHelpContentItem(name, link, parent);
810}
811
812static void requestContentHelper(QPromise<ContentResult> &promise, const ContentProvider &provider,
813 const QString &collectionFile)
814{
815 ContentResult rootItem(createContentItem());
816 const ContentProviderResult result = provider(collectionFile);
817 for (const auto &contentsData : result) {
818 const QString namespaceName = contentsData.namespaceName;
819 const QString folderName = contentsData.folderName;
820 for (const QByteArray &contents : contentsData.contentsList) {
821 if (promise.isCanceled())
822 return;
823
824 if (contents.isEmpty())
825 continue;
826
827 // Holds the chain of the current item's ancestors together with their depths.
828 // Depths are strictly increasing, but not necessarily contiguous.
829 QList<std::pair<int, QHelpContentItem *>> stack;
830 QDataStream s(contents);
831 while (true) {
832 int depth = 0;
833 QString link, title;
834 s >> depth;
835 s >> link;
836 s >> title;
837 if (title.isEmpty())
838 break;
839
840// The example input (depth, link, title):
841//
842// 0 "graphicaleffects5.html" "Qt 5 Compatibility APIs: Qt Graphical Effects"
843// 1 "qtgraphicaleffects5-index.html" "QML Types"
844// 2 "qml-qt5compat-graphicaleffects-blend.html" "Blend Type Reference"
845// 3 "qml-qt5compat-graphicaleffects-blend-members.html" "List of all members"
846// 2 "qml-qt5compat-graphicaleffects-brightnesscontrast.html" "BrightnessContrast Type Reference"
847//
848// Thus, the valid order of depths is:
849// 1. Whenever the item's depth is < 0, we insert the item as its depth is 0.
850// 2. The first item's depth must be 0, otherwise we insert the item as its depth is 0.
851// 3. When the previous depth was N, the next depth must be in range [0, N+1] inclusively.
852// If next item's depth is M > N+1, we insert the item as its depth is N+1.
853
854 // The parent is the last item on the stack with a depth lower than the item's
855 // depth (or the root). When depths are broken, e.g. 0, 2, 2, 1, the gaps are
856 // implicitly filled with that parent. In this case, the 1st item is a root,
857 // and 2nd - 4th are all direct children of the 1st.
858 // Don't materialize the gaps, as the depth comes from untrusted data.
859 depth = qMax(depth, 0);
860 while (!stack.isEmpty() && stack.constLast().first >= depth)
861 stack.removeLast();
862
863 const QUrl url = constructUrl(namespaceName, folderName, link);
864 QHelpContentItem *parent = stack.isEmpty() ? rootItem.get()
865 : stack.constLast().second;
866 stack.push_back({depth, createContentItem(title, url, parent)});
867 }
868 }
869 }
870 promise.addResult(rootItem);
871}
872
873static ContentProvider contentProviderFromFilterEngine(const QString &filter)
874{
875 return [filter](const QString &collectionFile) -> ContentProviderResult {
876 QHelpCollectionHandler collectionHandler(collectionFile);
877 if (!collectionHandler.openCollectionFile())
878 return {};
879 return collectionHandler.contentsForFilter(filter);
880 };
881}
882
883static ContentProvider contentProviderFromAttributes(const QStringList &attributes)
884{
885 return [attributes](const QString &collectionFile) -> ContentProviderResult {
886 QHelpCollectionHandler collectionHandler(collectionFile);
887 if (!collectionHandler.openCollectionFile())
888 return {};
889 return collectionHandler.contentsForFilter(attributes);
890 };
891}
892
893QFuture<ContentResult> QHelpEngineCore::requestContentForCurrentFilter() const
894{
895 const ContentProvider provider = usesFilterEngine()
896 ? contentProviderFromFilterEngine(filterEngine()->activeFilter())
897 : contentProviderFromAttributes(filterAttributes(d->currentFilter));
898 return QtConcurrent::run(requestContentHelper, provider, collectionFile());
899}
900
901QFuture<ContentResult> QHelpEngineCore::requestContent(const QString &filter) const
902{
903 const ContentProvider provider = usesFilterEngine()
904 ? contentProviderFromFilterEngine(filter)
905 : contentProviderFromAttributes(filterAttributes(filter));
906 return QtConcurrent::run(requestContentHelper, provider, collectionFile());
907}
908
909using IndexProvider = std::function<QStringList(const QString &)>;
910using IndexResult = QStringList;
911
912static IndexProvider indexProviderFromFilterEngine(const QString &filter)
913{
914 return [filter](const QString &collectionFile) -> IndexResult {
915 QHelpCollectionHandler collectionHandler(collectionFile);
916 if (!collectionHandler.openCollectionFile())
917 return {};
918 return collectionHandler.indicesForFilter(filter);
919 };
920}
921
922static IndexProvider indexProviderFromAttributes(const QStringList &attributes)
923{
924 return [attributes](const QString &collectionFile) -> IndexResult {
925 QHelpCollectionHandler collectionHandler(collectionFile);
926 if (!collectionHandler.openCollectionFile())
927 return {};
928 return collectionHandler.indicesForFilter(attributes);
929 };
930}
931
932QFuture<IndexResult> QHelpEngineCore::requestIndexForCurrentFilter() const
933{
934 const IndexProvider provider = usesFilterEngine()
935 ? indexProviderFromFilterEngine(filterEngine()->activeFilter())
936 : indexProviderFromAttributes(filterAttributes(d->currentFilter));
937 return QtConcurrent::run(std::move(provider), collectionFile());
938}
939
940QFuture<IndexResult> QHelpEngineCore::requestIndex(const QString &filter) const
941{
942 const IndexProvider provider = usesFilterEngine()
943 ? indexProviderFromFilterEngine(filter)
944 : indexProviderFromAttributes(filterAttributes(filter));
945 return QtConcurrent::run(std::move(provider), collectionFile());
946}
947#endif
948
949QT_END_NAMESPACE
std::unique_ptr< QHelpCollectionHandler > collectionHandler
QHelpFilterEngine * filterEngine
QHelpEngineCorePrivate(const QString &collectionFile, QHelpEngineCore *helpEngineCore)
void init(const QString &collectionFile)
Combined button and popup list for selecting options.