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 never changed.
109 Functions that would modify it fail and return \c false.
110 This also works if the collection file is in a read-only
111 location, 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. The collection file is opened in read-only
220 mode, and functions that would change it, such as
221 registerDocumentation(), addCustomFilter() or setCustomValue(),
222 fail and return \c false. The active filter is kept in memory
223 only and is not saved in the collection file.
224 Setting it to \c false enables the full
225 functionality of the help engine.
226
227 By default, this property is \c true.
228*/
229bool QHelpEngineCore::isReadOnly() const
230{
231 return d->readOnly;
232}
233
234void QHelpEngineCore::setReadOnly(bool enable)
235{
236 if (d->readOnly == enable)
237 return;
238
239 d->readOnly = enable;
240 d->init(collectionFile());
241}
242
243/*!
244 \since 5.13
245
246 Returns the filter engine associated with this help engine.
247 The filter engine allows for adding, changing, and removing existing
248 filters for this help engine. To use the engine you also have to call
249 \l setUsesFilterEngine() set to \c true.
250*/
251QHelpFilterEngine *QHelpEngineCore::filterEngine() const
252{
253 return d->filterEngine;
254}
255
256/*!
257 Sets up the help engine by processing the information found
258 in the collection file and returns true if successful; otherwise
259 returns false.
260
261 By calling the function, the help
262 engine is forced to initialize itself immediately. Most of
263 the times, this function does not have to be called
264 explicitly because getter functions which depend on a correctly
265 set up help engine do that themselves.
266
267 In read-only mode, the collection file must already exist and be
268 non-empty, since it is never created or modified. Otherwise, this
269 function fails and error() returns a description of the problem.
270
271 \note \c{qsqlite4.dll} needs to be deployed with the application as the
272 help system uses the sqlite driver when loading help collections.
273*/
274bool QHelpEngineCore::setupData()
275{
276 d->needsSetup = true;
277 return d->setup();
278}
279
280/*!
281 Creates the file \a fileName and copies all contents from
282 the current collection file into the newly created file,
283 and returns true if successful; otherwise returns false.
284
285 The copying process makes sure that file references to Qt
286 Collection files (\c{.qch}) files are updated accordingly.
287*/
288bool QHelpEngineCore::copyCollectionFile(const QString &fileName)
289{
290 if (!d->setup())
291 return false;
292 return d->collectionHandler->copyCollectionFile(fileName);
293}
294
295/*!
296 Returns the namespace name defined for the Qt compressed help file (.qch)
297 specified by its \a documentationFileName. If the file is not valid, an
298 empty string is returned.
299
300 \sa documentationFileName()
301*/
302QString QHelpEngineCore::namespaceName(const QString &documentationFileName)
303{
304 void *pointer = const_cast<QString *>(&documentationFileName);
305 QHelpDBReader reader(documentationFileName, QHelpGlobal::uniquifyConnectionName(
306 "GetNamespaceName"_L1, pointer), nullptr);
307 if (reader.init())
308 return reader.namespaceName();
309 return {};
310}
311
312/*!
313 Registers the Qt compressed help file (.qch) contained in the file
314 \a documentationFileName. One compressed help file, uniquely
315 identified by its namespace can only be registered once.
316 True is returned if the registration was successful, otherwise
317 false.
318
319 \sa unregisterDocumentation(), error()
320*/
321bool QHelpEngineCore::registerDocumentation(const QString &documentationFileName)
322{
323 d->error.clear();
324 d->needsSetup = true;
325 return d->collectionHandler->registerDocumentation(documentationFileName);
326}
327
328/*!
329 Unregisters the Qt compressed help file (.qch) identified by its
330 \a namespaceName from the help collection. Returns true
331 on success, otherwise false.
332
333 \sa registerDocumentation(), error()
334*/
335bool QHelpEngineCore::unregisterDocumentation(const QString &namespaceName)
336{
337 d->error.clear();
338 d->needsSetup = true;
339 return d->collectionHandler->unregisterDocumentation(namespaceName);
340}
341
342/*!
343 Returns the absolute file name of the Qt compressed help file (.qch)
344 identified by the \a namespaceName. If there is no Qt compressed help file
345 with the specified namespace registered, an empty string is returned.
346
347 \sa namespaceName()
348*/
349QString QHelpEngineCore::documentationFileName(const QString &namespaceName)
350{
351 if (!d->setup())
352 return {};
353
354 const QHelpCollectionHandler::FileInfo fileInfo =
355 d->collectionHandler->registeredDocumentation(namespaceName);
356
357 if (fileInfo.namespaceName.isEmpty())
358 return {};
359
360 if (QDir::isAbsolutePath(fileInfo.fileName))
361 return fileInfo.fileName;
362
363 return QFileInfo(QFileInfo(d->collectionHandler->collectionFile()).absolutePath()
364 + u'/' + fileInfo.fileName).absoluteFilePath();
365}
366
367/*!
368 Returns a list of all registered Qt compressed help files of the current collection file.
369 The returned names are the namespaces of the registered Qt compressed help files (.qch).
370*/
371QStringList QHelpEngineCore::registeredDocumentations() const
372{
373 if (!d->setup())
374 return {};
375 const auto &docList = d->collectionHandler->registeredDocumentations();
376 QStringList list;
377 for (const QHelpCollectionHandler::FileInfo &info : docList)
378 list.append(info.namespaceName);
379 return list;
380}
381
382/*!
383 \deprecated
384
385 QHelpFilterEngine::filters() should be used instead.
386
387 Returns a list of custom filters.
388
389 \sa addCustomFilter(), removeCustomFilter()
390*/
391QStringList QHelpEngineCore::customFilters() const
392{
393 if (!d->setup())
394 return {};
395 return d->collectionHandler->customFilters();
396}
397
398/*!
399 \deprecated
400
401 QHelpFilterEngine::setFilterData() should be used instead.
402
403 Adds the new custom filter \a filterName. The filter attributes
404 are specified by \a attributes. If the filter already exists,
405 its attribute set is replaced. The function returns true if
406 the operation succeeded, otherwise it returns false.
407
408 \sa customFilters(), removeCustomFilter()
409*/
410bool QHelpEngineCore::addCustomFilter(const QString &filterName,
411 const QStringList &attributes)
412{
413 d->error.clear();
414 d->needsSetup = true;
415 return d->collectionHandler->addCustomFilter(filterName, attributes);
416}
417
418/*!
419 \deprecated
420
421 QHelpFilterEngine::removeFilter() should be used instead.
422
423 Returns true if the filter \a filterName was removed successfully,
424 otherwise false.
425
426 \sa addCustomFilter(), customFilters()
427*/
428bool QHelpEngineCore::removeCustomFilter(const QString &filterName)
429{
430 d->error.clear();
431 d->needsSetup = true;
432 return d->collectionHandler->removeCustomFilter(filterName);
433}
434
435/*!
436 \deprecated
437
438 QHelpFilterEngine::availableComponents() should be used instead.
439
440 Returns a list of all defined filter attributes.
441*/
442QStringList QHelpEngineCore::filterAttributes() const
443{
444 if (!d->setup())
445 return {};
446 return d->collectionHandler->filterAttributes();
447}
448
449/*!
450 \deprecated
451
452 QHelpFilterEngine::filterData() should be used instead.
453
454 Returns a list of filter attributes used by the custom
455 filter \a filterName.
456*/
457QStringList QHelpEngineCore::filterAttributes(const QString &filterName) const
458{
459 if (!d->setup())
460 return {};
461 return d->collectionHandler->filterAttributes(filterName);
462}
463
464/*!
465 \deprecated
466 \property QHelpEngineCore::currentFilter
467 \brief the name of the custom filter currently applied.
468 \since 4.5
469
470 QHelpFilterEngine::activeFilter() should be used instead.
471
472 Setting this property will save the new custom filter permanently in the
473 help collection file. To set a custom filter without saving it
474 permanently, disable the auto save filter mode. In \l readOnly
475 mode, the filter is never saved.
476
477 \sa autoSaveFilter()
478*/
479QString QHelpEngineCore::currentFilter() const
480{
481 if (!d->setup())
482 return {};
483
484 if (d->currentFilter.isEmpty()) {
485 const QString &filter =
486 d->collectionHandler->customValue("CurrentFilter"_L1, QString()).toString();
487 if (!filter.isEmpty() && d->collectionHandler->customFilters().contains(filter))
488 d->currentFilter = filter;
489 }
490 return d->currentFilter;
491}
492
493void QHelpEngineCore::setCurrentFilter(const QString &filterName)
494{
495 if (!d->setup() || filterName == d->currentFilter)
496 return;
497 d->currentFilter = filterName;
498 if (d->autoSaveFilter && !d->readOnly)
499 d->collectionHandler->setCustomValue("CurrentFilter"_L1, d->currentFilter);
500 emit currentFilterChanged(d->currentFilter);
501}
502
503/*!
504 \deprecated
505
506 QHelpFilterEngine::filterData() should be used instead.
507
508 Returns a list of filter attributes for the different filter sections
509 defined in the Qt compressed help file with the given namespace
510 \a namespaceName.
511*/
512QList<QStringList> QHelpEngineCore::filterAttributeSets(const QString &namespaceName) const
513{
514 if (!d->setup())
515 return {};
516 return d->collectionHandler->filterAttributeSets(namespaceName);
517}
518
519/*!
520 \deprecated
521
522 files() should be used instead.
523
524 Returns a list of files contained in the Qt compressed help file \a
525 namespaceName. The files can be filtered by \a filterAttributes as
526 well as by their extension \a extensionFilter (e.g. 'html').
527*/
528QList<QUrl> QHelpEngineCore::files(const QString namespaceName,
529 const QStringList &filterAttributes,
530 const QString &extensionFilter)
531{
532 QList<QUrl> res;
533 if (!d->setup())
534 return res;
535
536 QUrl url;
537 url.setScheme("qthelp"_L1);
538 url.setAuthority(namespaceName);
539
540 const QStringList &files = d->collectionHandler->files(
541 namespaceName, filterAttributes, extensionFilter);
542 for (const QString &file : files) {
543 url.setPath("/"_L1 + file);
544 res.append(url);
545 }
546 return res;
547}
548
549/*!
550 Returns a list of files contained in the Qt compressed help file
551 for \a namespaceName. The files can be filtered by \a filterName as
552 well as by their extension \a extensionFilter (for example, 'html').
553*/
554QList<QUrl> QHelpEngineCore::files(const QString namespaceName,
555 const QString &filterName,
556 const QString &extensionFilter)
557{
558 QList<QUrl> res;
559 if (!d->setup())
560 return res;
561
562 QUrl url;
563 url.setScheme("qthelp"_L1);
564 url.setAuthority(namespaceName);
565
566 const QStringList &files = d->collectionHandler->files(
567 namespaceName, filterName, extensionFilter);
568 for (const QString &file : files) {
569 url.setPath("/"_L1 + file);
570 res.append(url);
571 }
572 return res;
573}
574
575/*!
576 Returns the corrected URL for the \a url that may refer to
577 a different namespace defined by the virtual folder defined
578 as a part of the \a url. If the virtual folder matches the namespace
579 of the \a url, the method just checks if the file exists and returns
580 the same \a url. When the virtual folder doesn't match the namespace
581 of the \a url, it tries to find the best matching namespace according
582 to the active filter. When the namespace is found, it returns the
583 corrected URL if the file exists, otherwise it returns an invalid URL.
584*/
585QUrl QHelpEngineCore::findFile(const QUrl &url) const
586{
587 if (!d->setup())
588 return url;
589
590 QUrl result = d->usesFilterEngine
591 ? d->collectionHandler->findFile(url, d->filterEngine->activeFilter())
592 : d->collectionHandler->findFile(url, filterAttributes(currentFilter())); // obsolete
593 if (!result.isEmpty())
594 return result;
595
596 result = d->usesFilterEngine
597 ? d->collectionHandler->findFile(url, QString())
598 : d->collectionHandler->findFile(url, QStringList()); // obsolete
599 if (!result.isEmpty())
600 return result;
601
602 return url;
603}
604
605/*!
606 Returns the data of the file specified by \a url. If the
607 file does not exist, an empty QByteArray is returned.
608
609 The data is returned exactly as it is stored in the help file, without
610 any filtering. Treat it as untrusted content: the caller is responsible
611 for displaying it in a suitably restricted environment. For more
612 information, see \l{qthelp-security-considerations}{Security Considerations}.
613
614 \sa findFile()
615*/
616QByteArray QHelpEngineCore::fileData(const QUrl &url) const
617{
618 if (!d->setup())
619 return {};
620 return d->collectionHandler->fileData(url);
621}
622
623/*!
624 \since 5.15
625
626 Returns a list of all the document links found for the \a id.
627 The returned list contents depend on the current filter, and therefore only the keywords
628 registered for the current filter will be returned.
629*/
630QList<QHelpLink> QHelpEngineCore::documentsForIdentifier(const QString &id) const
631{
632 return documentsForIdentifier(
633 id, d->usesFilterEngine ? d->filterEngine->activeFilter() : d->currentFilter);
634}
635
636/*!
637 \since 5.15
638
639 Returns a list of the document links found for the \a id, filtered by \a filterName.
640 The returned list contents depend on the passed filter, and therefore only the keywords
641 registered for this filter will be returned. If you want to get all results unfiltered,
642 pass empty string as \a filterName.
643*/
644QList<QHelpLink> QHelpEngineCore::documentsForIdentifier(const QString &id, const QString &filterName) const
645{
646 if (!d->setup())
647 return {};
648
649 if (d->usesFilterEngine)
650 return d->collectionHandler->documentsForIdentifier(id, filterName);
651 return d->collectionHandler->documentsForIdentifier(id, filterAttributes(filterName));
652}
653
654/*!
655 \since 5.15
656
657 Returns a list of all the document links found for the \a keyword.
658 The returned list contents depend on the current filter, and therefore only the keywords
659 registered for the current filter will be returned.
660*/
661QList<QHelpLink> QHelpEngineCore::documentsForKeyword(const QString &keyword) const
662{
663 return documentsForKeyword(
664 keyword, d->usesFilterEngine ? d->filterEngine->activeFilter() : d->currentFilter);
665}
666
667/*!
668 \since 5.15
669
670 Returns a list of the document links found for the \a keyword, filtered by \a filterName.
671 The returned list contents depend on the passed filter, and therefore only the keywords
672 registered for this filter will be returned. If you want to get all results unfiltered,
673 pass empty string as \a filterName.
674*/
675QList<QHelpLink> QHelpEngineCore::documentsForKeyword(const QString &keyword, const QString &filterName) const
676{
677 if (!d->setup())
678 return {};
679
680 if (d->usesFilterEngine)
681 return d->collectionHandler->documentsForKeyword(keyword, filterName);
682 return d->collectionHandler->documentsForKeyword(keyword, filterAttributes(filterName));
683}
684
685/*!
686 Removes the \a key from the settings section in the
687 collection file. Returns true if the value was removed
688 successfully, otherwise false.
689
690 \sa customValue(), setCustomValue()
691*/
692bool QHelpEngineCore::removeCustomValue(const QString &key)
693{
694 d->error.clear();
695 return d->collectionHandler->removeCustomValue(key);
696}
697
698/*!
699 Returns the value assigned to the \a key. If the requested
700 key does not exist, the specified \a defaultValue is
701 returned.
702
703 \sa setCustomValue(), removeCustomValue()
704*/
705QVariant QHelpEngineCore::customValue(const QString &key, const QVariant &defaultValue) const
706{
707 if (!d->setup())
708 return {};
709 return d->collectionHandler->customValue(key, defaultValue);
710}
711
712/*!
713 Save the \a value under the \a key. If the key already exist,
714 the value will be overwritten. Returns true if the value was
715 saved successfully, otherwise false.
716
717 \sa customValue(), removeCustomValue()
718*/
719bool QHelpEngineCore::setCustomValue(const QString &key, const QVariant &value)
720{
721 d->error.clear();
722 return d->collectionHandler->setCustomValue(key, value);
723}
724
725/*!
726 Returns the meta data for the Qt compressed help file \a
727 documentationFileName. If there is no data available for
728 \a name, an invalid QVariant() is returned. The meta
729 data is defined when creating the Qt compressed help file and
730 cannot be modified later. Common meta data includes e.g.
731 the author of the documentation.
732*/
733QVariant QHelpEngineCore::metaData(const QString &documentationFileName,
734 const QString &name)
735{
736 QHelpDBReader reader(documentationFileName, "GetMetaData"_L1, nullptr);
737
738 if (reader.init())
739 return reader.metaData(name);
740 return {};
741}
742
743/*!
744 Returns a description of the last error that occurred.
745*/
746QString QHelpEngineCore::error() const
747{
748 return d->error;
749}
750
751/*!
752 \property QHelpEngineCore::autoSaveFilter
753 \brief whether QHelpEngineCore is in auto save filter mode or not.
754 \since 4.5
755
756 If QHelpEngineCore is in auto save filter mode, the current filter is
757 automatically saved when it is changed by the QHelpFilterEngine::setActiveFilter()
758 function. The filter is saved persistently in the help collection file.
759 In \l readOnly mode, the filter is never saved.
760
761 By default, this mode is on.
762*/
763void QHelpEngineCore::setAutoSaveFilter(bool save)
764{
765 d->autoSaveFilter = save;
766}
767
768bool QHelpEngineCore::autoSaveFilter() const
769{
770 return d->autoSaveFilter;
771}
772
773/*!
774 \since 5.13
775
776 Enables or disables the new filter engine functionality
777 inside the help engine, according to the passed \a uses parameter.
778
779 \sa filterEngine()
780*/
781void QHelpEngineCore::setUsesFilterEngine(bool uses)
782{
783 d->usesFilterEngine = uses;
784}
785
786/*!
787 \since 5.13
788
789 Returns whether the help engine uses the new filter functionality.
790
791 \sa filterEngine()
792*/
793bool QHelpEngineCore::usesFilterEngine() const
794{
795 return d->usesFilterEngine;
796}
797
798#if QT_CONFIG(future)
799static QUrl constructUrl(const QString &namespaceName, const QString &folderName,
800 const QString &relativePath)
801{
802 const int idx = relativePath.indexOf(u'#');
803 const QString &rp = idx < 0 ? relativePath : relativePath.left(idx);
804 const QString anchor = idx < 0 ? QString() : relativePath.mid(idx + 1);
805 return QHelpCollectionHandler::buildQUrl(namespaceName, folderName, rp, anchor);
806}
807
808using ContentProviderResult = QList<QHelpCollectionHandler::ContentsData>;
809using ContentProvider = std::function<ContentProviderResult(const QString &)>;
810using ContentResult = std::shared_ptr<QHelpContentItem>;
811
812// This trick is needed because the c'tor of QHelpContentItem is private.
813QHelpContentItem *createContentItem(const QString &name = {}, const QUrl &link = {},
814 QHelpContentItem *parent = {})
815{
816 return new QHelpContentItem(name, link, parent);
817}
818
819static void requestContentHelper(QPromise<ContentResult> &promise, const ContentProvider &provider,
820 const QString &collectionFile)
821{
822 ContentResult rootItem(createContentItem());
823 const ContentProviderResult result = provider(collectionFile);
824 for (const auto &contentsData : result) {
825 const QString namespaceName = contentsData.namespaceName;
826 const QString folderName = contentsData.folderName;
827 for (const QByteArray &contents : contentsData.contentsList) {
828 if (promise.isCanceled())
829 return;
830
831 if (contents.isEmpty())
832 continue;
833
834 // Holds the chain of the current item's ancestors together with their depths.
835 // Depths are strictly increasing, but not necessarily contiguous.
836 QList<std::pair<int, QHelpContentItem *>> stack;
837 QDataStream s(contents);
838 while (true) {
839 int depth = 0;
840 QString link, title;
841 s >> depth;
842 s >> link;
843 s >> title;
844 if (title.isEmpty())
845 break;
846
847// The example input (depth, link, title):
848//
849// 0 "graphicaleffects5.html" "Qt 5 Compatibility APIs: Qt Graphical Effects"
850// 1 "qtgraphicaleffects5-index.html" "QML Types"
851// 2 "qml-qt5compat-graphicaleffects-blend.html" "Blend Type Reference"
852// 3 "qml-qt5compat-graphicaleffects-blend-members.html" "List of all members"
853// 2 "qml-qt5compat-graphicaleffects-brightnesscontrast.html" "BrightnessContrast Type Reference"
854//
855// Thus, the valid order of depths is:
856// 1. Whenever the item's depth is < 0, we insert the item as its depth is 0.
857// 2. The first item's depth must be 0, otherwise we insert the item as its depth is 0.
858// 3. When the previous depth was N, the next depth must be in range [0, N+1] inclusively.
859// If next item's depth is M > N+1, we insert the item as its depth is N+1.
860
861 // The parent is the last item on the stack with a depth lower than the item's
862 // depth (or the root). When depths are broken, e.g. 0, 2, 2, 1, the gaps are
863 // implicitly filled with that parent. In this case, the 1st item is a root,
864 // and 2nd - 4th are all direct children of the 1st.
865 // Don't materialize the gaps, as the depth comes from untrusted data.
866 depth = qMax(depth, 0);
867 while (!stack.isEmpty() && stack.constLast().first >= depth)
868 stack.removeLast();
869
870 const QUrl url = constructUrl(namespaceName, folderName, link);
871 QHelpContentItem *parent = stack.isEmpty() ? rootItem.get()
872 : stack.constLast().second;
873 stack.push_back({depth, createContentItem(title, url, parent)});
874 }
875 }
876 }
877 promise.addResult(rootItem);
878}
879
880static ContentProvider contentProviderFromFilterEngine(const QString &filter)
881{
882 return [filter](const QString &collectionFile) -> ContentProviderResult {
883 QHelpCollectionHandler collectionHandler(collectionFile);
884 if (!collectionHandler.openCollectionFile())
885 return {};
886 return collectionHandler.contentsForFilter(filter);
887 };
888}
889
890static ContentProvider contentProviderFromAttributes(const QStringList &attributes)
891{
892 return [attributes](const QString &collectionFile) -> ContentProviderResult {
893 QHelpCollectionHandler collectionHandler(collectionFile);
894 if (!collectionHandler.openCollectionFile())
895 return {};
896 return collectionHandler.contentsForFilter(attributes);
897 };
898}
899
900QFuture<ContentResult> QHelpEngineCore::requestContentForCurrentFilter() const
901{
902 const ContentProvider provider = usesFilterEngine()
903 ? contentProviderFromFilterEngine(filterEngine()->activeFilter())
904 : contentProviderFromAttributes(filterAttributes(d->currentFilter));
905 return QtConcurrent::run(requestContentHelper, provider, collectionFile());
906}
907
908QFuture<ContentResult> QHelpEngineCore::requestContent(const QString &filter) const
909{
910 const ContentProvider provider = usesFilterEngine()
911 ? contentProviderFromFilterEngine(filter)
912 : contentProviderFromAttributes(filterAttributes(filter));
913 return QtConcurrent::run(requestContentHelper, provider, collectionFile());
914}
915
916using IndexProvider = std::function<QStringList(const QString &)>;
917using IndexResult = QStringList;
918
919static IndexProvider indexProviderFromFilterEngine(const QString &filter)
920{
921 return [filter](const QString &collectionFile) -> IndexResult {
922 QHelpCollectionHandler collectionHandler(collectionFile);
923 if (!collectionHandler.openCollectionFile())
924 return {};
925 return collectionHandler.indicesForFilter(filter);
926 };
927}
928
929static IndexProvider indexProviderFromAttributes(const QStringList &attributes)
930{
931 return [attributes](const QString &collectionFile) -> IndexResult {
932 QHelpCollectionHandler collectionHandler(collectionFile);
933 if (!collectionHandler.openCollectionFile())
934 return {};
935 return collectionHandler.indicesForFilter(attributes);
936 };
937}
938
939QFuture<IndexResult> QHelpEngineCore::requestIndexForCurrentFilter() const
940{
941 const IndexProvider provider = usesFilterEngine()
942 ? indexProviderFromFilterEngine(filterEngine()->activeFilter())
943 : indexProviderFromAttributes(filterAttributes(d->currentFilter));
944 return QtConcurrent::run(std::move(provider), collectionFile());
945}
946
947QFuture<IndexResult> QHelpEngineCore::requestIndex(const QString &filter) const
948{
949 const IndexProvider provider = usesFilterEngine()
950 ? indexProviderFromFilterEngine(filter)
951 : indexProviderFromAttributes(filterAttributes(filter));
952 return QtConcurrent::run(std::move(provider), collectionFile());
953}
954#endif
955
956QT_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.