summaryrefslogtreecommitdiffstats
path: root/src/qdoc/qdoc
diff options
context:
space:
mode:
Diffstat (limited to 'src/qdoc/qdoc')
-rw-r--r--src/qdoc/qdoc/CMakeLists.txt117
-rw-r--r--src/qdoc/qdoc/doc/config/qdoc.qdocconf61
-rw-r--r--src/qdoc/qdoc/doc/corefeatures.qdoc11
-rw-r--r--src/qdoc/qdoc/doc/examples/cpp.qdoc.sample102
-rw-r--r--src/qdoc/qdoc/doc/examples/layoutmanagement.qdocinc13
-rw-r--r--src/qdoc/qdoc/doc/examples/main.cpp16
-rw-r--r--src/qdoc/qdoc/doc/examples/mainwindow.cpp213
-rw-r--r--src/qdoc/qdoc/doc/examples/minimum.qdocconf46
-rw-r--r--src/qdoc/qdoc/doc/examples/objectmodel.qdocinc11
-rw-r--r--src/qdoc/qdoc/doc/examples/qml.qdoc.sample90
-rw-r--r--src/qdoc/qdoc/doc/examples/samples.qdocinc85
-rw-r--r--src/qdoc/qdoc/doc/examples/signalandslots.qdocinc9
-rw-r--r--src/qdoc/qdoc/doc/files/basicqt.qdoc.sample64
-rw-r--r--src/qdoc/qdoc/doc/files/compat.qdocconf10
-rw-r--r--src/qdoc/qdoc/doc/files/qtgui.qdocconf45
-rw-r--r--src/qdoc/qdoc/doc/images/happyguy.jpgbin0 -> 53442 bytes
-rw-r--r--src/qdoc/qdoc/doc/images/link-to-qquickitem.pngbin0 -> 46571 bytes
-rw-r--r--src/qdoc/qdoc/doc/images/links-to-broken-links.pngbin0 -> 16569 bytes
-rw-r--r--src/qdoc/qdoc/doc/images/links-to-links.pngbin0 -> 10042 bytes
-rw-r--r--src/qdoc/qdoc/doc/images/qt-logo.pngbin0 -> 1008 bytes
-rw-r--r--src/qdoc/qdoc/doc/images/training.jpgbin0 -> 8368 bytes
-rw-r--r--src/qdoc/qdoc/doc/images/windows-pushbutton.pngbin0 -> 722 bytes
-rw-r--r--src/qdoc/qdoc/doc/images/windows-toolbutton.pngbin0 -> 771 bytes
-rw-r--r--src/qdoc/qdoc/doc/qdoc-guide/qdoc-guide.qdoc670
-rw-r--r--src/qdoc/qdoc/doc/qdoc-guide/qtwritingstyle-cpp.qdoc163
-rw-r--r--src/qdoc/qdoc/doc/qdoc-guide/qtwritingstyle-qml.qdoc142
-rw-r--r--src/qdoc/qdoc/doc/qdoc-manual-cmdindex.qdoc143
-rw-r--r--src/qdoc/qdoc/doc/qdoc-manual-contextcmds.qdoc1002
-rw-r--r--src/qdoc/qdoc/doc/qdoc-manual-intro.qdoc299
-rw-r--r--src/qdoc/qdoc/doc/qdoc-manual-macros.qdoc497
-rw-r--r--src/qdoc/qdoc/doc/qdoc-manual-markupcmds.qdoc3158
-rw-r--r--src/qdoc/qdoc/doc/qdoc-manual-qdocconf.qdoc1992
-rw-r--r--src/qdoc/qdoc/doc/qdoc-manual-topiccmds.qdoc1049
-rw-r--r--src/qdoc/qdoc/doc/qdoc-manual.qdoc59
-rw-r--r--src/qdoc/qdoc/doc/qdoc-warnings.qdoc832
-rw-r--r--src/qdoc/qdoc/doc/qtgui-qdocconf.qdoc260
-rw-r--r--src/qdoc/qdoc/src/qdoc/access.h15
-rw-r--r--src/qdoc/qdoc/src/qdoc/aggregate.cpp762
-rw-r--r--src/qdoc/qdoc/src/qdoc/aggregate.h118
-rw-r--r--src/qdoc/qdoc/src/qdoc/atom.cpp458
-rw-r--r--src/qdoc/qdoc/src/qdoc/atom.h223
-rw-r--r--src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.cpp126
-rw-r--r--src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.h17
-rw-r--r--src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.cpp125
-rw-r--r--src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.h17
-rw-r--r--src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.cpp96
-rw-r--r--src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.h20
-rw-r--r--src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef.h207
-rw-r--r--src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef_members.qdocinc162
-rw-r--r--src/qdoc/qdoc/src/qdoc/clang/AST/LLVM_LICENSE.txt279
-rw-r--r--src/qdoc/qdoc/src/qdoc/clang/AST/QualTypeNames.h491
-rw-r--r--src/qdoc/qdoc/src/qdoc/clang/AST/qt_attribution.json20
-rw-r--r--src/qdoc/qdoc/src/qdoc/clangcodeparser.cpp1904
-rw-r--r--src/qdoc/qdoc/src/qdoc/clangcodeparser.h94
-rw-r--r--src/qdoc/qdoc/src/qdoc/classnode.cpp260
-rw-r--r--src/qdoc/qdoc/src/qdoc/classnode.h69
-rw-r--r--src/qdoc/qdoc/src/qdoc/codechunk.cpp104
-rw-r--r--src/qdoc/qdoc/src/qdoc/codechunk.h74
-rw-r--r--src/qdoc/qdoc/src/qdoc/codemarker.cpp448
-rw-r--r--src/qdoc/qdoc/src/qdoc/codemarker.h67
-rw-r--r--src/qdoc/qdoc/src/qdoc/codeparser.cpp139
-rw-r--r--src/qdoc/qdoc/src/qdoc/codeparser.h142
-rw-r--r--src/qdoc/qdoc/src/qdoc/collectionnode.cpp104
-rw-r--r--src/qdoc/qdoc/src/qdoc/collectionnode.h126
-rw-r--r--src/qdoc/qdoc/src/qdoc/comparisoncategory.cpp28
-rw-r--r--src/qdoc/qdoc/src/qdoc/comparisoncategory.h54
-rw-r--r--src/qdoc/qdoc/src/qdoc/config.cpp1439
-rw-r--r--src/qdoc/qdoc/src/qdoc/config.h409
-rw-r--r--src/qdoc/qdoc/src/qdoc/cppcodemarker.cpp594
-rw-r--r--src/qdoc/qdoc/src/qdoc/cppcodemarker.h35
-rw-r--r--src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp1021
-rw-r--r--src/qdoc/qdoc/src/qdoc/cppcodeparser.h111
-rw-r--r--src/qdoc/qdoc/src/qdoc/doc.cpp420
-rw-r--r--src/qdoc/qdoc/src/qdoc/doc.h92
-rw-r--r--src/qdoc/qdoc/src/qdoc/docbookgenerator.cpp4765
-rw-r--r--src/qdoc/qdoc/src/qdoc/docbookgenerator.h168
-rw-r--r--src/qdoc/qdoc/src/qdoc/docparser.cpp2836
-rw-r--r--src/qdoc/qdoc/src/qdoc/docparser.h176
-rw-r--r--src/qdoc/qdoc/src/qdoc/docprivate.cpp30
-rw-r--r--src/qdoc/qdoc/src/qdoc/docprivate.h76
-rw-r--r--src/qdoc/qdoc/src/qdoc/docutilities.h28
-rw-r--r--src/qdoc/qdoc/src/qdoc/editdistance.cpp68
-rw-r--r--src/qdoc/qdoc/src/qdoc/editdistance.h17
-rw-r--r--src/qdoc/qdoc/src/qdoc/enumitem.h36
-rw-r--r--src/qdoc/qdoc/src/qdoc/enumnode.cpp82
-rw-r--r--src/qdoc/qdoc/src/qdoc/enumnode.h49
-rw-r--r--src/qdoc/qdoc/src/qdoc/examplenode.h42
-rw-r--r--src/qdoc/qdoc/src/qdoc/externalpagenode.cpp28
-rw-r--r--src/qdoc/qdoc/src/qdoc/externalpagenode.h25
-rw-r--r--src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.cpp161
-rw-r--r--src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.h24
-rw-r--r--src/qdoc/qdoc/src/qdoc/functionnode.cpp473
-rw-r--r--src/qdoc/qdoc/src/qdoc/functionnode.h202
-rw-r--r--src/qdoc/qdoc/src/qdoc/generator.cpp2216
-rw-r--r--src/qdoc/qdoc/src/qdoc/generator.h207
-rw-r--r--src/qdoc/qdoc/src/qdoc/headernode.cpp43
-rw-r--r--src/qdoc/qdoc/src/qdoc/headernode.h46
-rw-r--r--src/qdoc/qdoc/src/qdoc/helpprojectwriter.cpp769
-rw-r--r--src/qdoc/qdoc/src/qdoc/helpprojectwriter.h106
-rw-r--r--src/qdoc/qdoc/src/qdoc/htmlgenerator.cpp3722
-rw-r--r--src/qdoc/qdoc/src/qdoc/htmlgenerator.h179
-rw-r--r--src/qdoc/qdoc/src/qdoc/importrec.h33
-rw-r--r--src/qdoc/qdoc/src/qdoc/location.cpp423
-rw-r--r--src/qdoc/qdoc/src/qdoc/location.h92
-rw-r--r--src/qdoc/qdoc/src/qdoc/macro.h27
-rw-r--r--src/qdoc/qdoc/src/qdoc/main.cpp720
-rw-r--r--src/qdoc/qdoc/src/qdoc/manifestwriter.cpp405
-rw-r--r--src/qdoc/qdoc/src/qdoc/manifestwriter.h46
-rw-r--r--src/qdoc/qdoc/src/qdoc/namespacenode.cpp190
-rw-r--r--src/qdoc/qdoc/src/qdoc/namespacenode.h48
-rw-r--r--src/qdoc/qdoc/src/qdoc/node.cpp1383
-rw-r--r--src/qdoc/qdoc/src/qdoc/node.h343
-rw-r--r--src/qdoc/qdoc/src/qdoc/openedlist.cpp172
-rw-r--r--src/qdoc/qdoc/src/qdoc/openedlist.h47
-rw-r--r--src/qdoc/qdoc/src/qdoc/pagenode.cpp117
-rw-r--r--src/qdoc/qdoc/src/qdoc/pagenode.h82
-rw-r--r--src/qdoc/qdoc/src/qdoc/parameters.cpp542
-rw-r--r--src/qdoc/qdoc/src/qdoc/parameters.h113
-rw-r--r--src/qdoc/qdoc/src/qdoc/parsererror.cpp90
-rw-r--r--src/qdoc/qdoc/src/qdoc/parsererror.h26
-rw-r--r--src/qdoc/qdoc/src/qdoc/propertynode.cpp135
-rw-r--r--src/qdoc/qdoc/src/qdoc/propertynode.h93
-rw-r--r--src/qdoc/qdoc/src/qdoc/proxynode.cpp54
-rw-r--r--src/qdoc/qdoc/src/qdoc/proxynode.h23
-rw-r--r--src/qdoc/qdoc/src/qdoc/puredocparser.cpp74
-rw-r--r--src/qdoc/qdoc/src/qdoc/puredocparser.h31
-rw-r--r--src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.cpp177
-rw-r--r--src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.h28
-rw-r--r--src/qdoc/qdoc/src/qdoc/qdocdatabase.cpp1685
-rw-r--r--src/qdoc/qdoc/src/qdoc/qdocdatabase.h395
-rw-r--r--src/qdoc/qdoc/src/qdoc/qdocindexfiles.cpp1449
-rw-r--r--src/qdoc/qdoc/src/qdoc/qdocindexfiles.h73
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlcodemarker.cpp175
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlcodemarker.h38
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlcodeparser.cpp143
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlcodeparser.h38
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.cpp794
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.h139
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlpropertynode.cpp175
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlpropertynode.h72
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmltypenode.cpp153
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmltypenode.h65
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlvisitor.cpp729
-rw-r--r--src/qdoc/qdoc/src/qdoc/qmlvisitor.h93
-rw-r--r--src/qdoc/qdoc/src/qdoc/quoter.cpp338
-rw-r--r--src/qdoc/qdoc/src/qdoc/quoter.h45
-rw-r--r--src/qdoc/qdoc/src/qdoc/relatedclass.cpp46
-rw-r--r--src/qdoc/qdoc/src/qdoc/relatedclass.h34
-rw-r--r--src/qdoc/qdoc/src/qdoc/sections.cpp992
-rw-r--r--src/qdoc/qdoc/src/qdoc/sections.h206
-rw-r--r--src/qdoc/qdoc/src/qdoc/sharedcommentnode.cpp56
-rw-r--r--src/qdoc/qdoc/src/qdoc/sharedcommentnode.h51
-rw-r--r--src/qdoc/qdoc/src/qdoc/singleton.h32
-rw-r--r--src/qdoc/qdoc/src/qdoc/sourcefileparser.h82
-rw-r--r--src/qdoc/qdoc/src/qdoc/tagfilewriter.cpp306
-rw-r--r--src/qdoc/qdoc/src/qdoc/tagfilewriter.h33
-rw-r--r--src/qdoc/qdoc/src/qdoc/template_declaration.h502
-rw-r--r--src/qdoc/qdoc/src/qdoc/text.cpp341
-rw-r--r--src/qdoc/qdoc/src/qdoc/text.h87
-rw-r--r--src/qdoc/qdoc/src/qdoc/tokenizer.cpp788
-rw-r--r--src/qdoc/qdoc/src/qdoc/tokenizer.h179
-rw-r--r--src/qdoc/qdoc/src/qdoc/topic.h29
-rw-r--r--src/qdoc/qdoc/src/qdoc/tree.cpp1357
-rw-r--r--src/qdoc/qdoc/src/qdoc/tree.h183
-rw-r--r--src/qdoc/qdoc/src/qdoc/typedefnode.cpp55
-rw-r--r--src/qdoc/qdoc/src/qdoc/typedefnode.h54
-rw-r--r--src/qdoc/qdoc/src/qdoc/utilities.cpp254
-rw-r--r--src/qdoc/qdoc/src/qdoc/utilities.h28
-rw-r--r--src/qdoc/qdoc/src/qdoc/variablenode.cpp23
-rw-r--r--src/qdoc/qdoc/src/qdoc/variablenode.h44
-rw-r--r--src/qdoc/qdoc/src/qdoc/webxmlgenerator.cpp903
-rw-r--r--src/qdoc/qdoc/src/qdoc/webxmlgenerator.h60
-rw-r--r--src/qdoc/qdoc/src/qdoc/xmlgenerator.cpp487
-rw-r--r--src/qdoc/qdoc/src/qdoc/xmlgenerator.h54
-rw-r--r--src/qdoc/qdoc/tests/CMakeLists.txt6
-rw-r--r--src/qdoc/qdoc/tests/config/CMakeLists.txt24
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/configs/exampletest.qdocconf2
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/configs/expandvars.qdocconf13
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/configs/includepaths.qdocconf2
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/configs/includes/test.qdoc1
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/configs/paths.qdocconf5
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/configs/vars.qdocconf17
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/empty/test.pro1
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example1/example1.pro1
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example2/example2.qmlproject1
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example3/example3.pyproject1
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example4/CMakeLists.txt1
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example4/example4.pro1
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/includepaths/include/framework/ignore.h1
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/includepaths/include/more/ignore.h1
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/includepaths/include/purpose.h1
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/includepaths/include/system/ignore.h1
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/includepaths/includepaths.qdocconf16
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/paths/includes/test.qdoc1
-rw-r--r--src/qdoc/qdoc/tests/config/testdata/paths/paths.qdocconf2
-rw-r--r--src/qdoc/qdoc/tests/config/tst_config.cpp180
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/CMakeLists.txt46
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/autolinking.html36
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/cpptypes.html25
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmodule/all-namespaces.html20
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmodule/testtype-members.html37
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmodule/testtype.html67
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmoduleref-sub-crossmodule.html30
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/testtype-members.html37
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/testtype.html67
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/includefromexampledirs/index.html27
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/includefromexampledirs/qml-qdoc-test-abstractparent-members.html20
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/includefromexampledirs/qml-qdoc-test-abstractparent.html68
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/index-linking.html37
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist-docbook/qdoc-test-qmlmodule.xml16
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist-docbook/test-componentset-example.xml37
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist-docbook/testcpp-module.xml49
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist/qdoc-test-qmlmodule.html19
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist/test-componentset-example.html47
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist/testcpp-module.html45
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/obsolete-classes.html32
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/qml-linkmodule-grandchild-members.html26
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/qmlpropertygroups-docbook/qml-qdoc-test-parent.xml45
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/qmlpropertygroups/qml-qdoc-test-anotherchild-members.html28
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/qmlpropertygroups/qml-qdoc-test-parent.html61
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp-module.html57
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp.index79
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/crossmoduleref.html48
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testcpp-module.html57
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testqdoc-test-members.html30
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testqdoc-test.html162
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testqdoc.html64
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-test-members.html30
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-test-obsolete.html45
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-test.html162
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-testderived-members.html33
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-testderived-obsolete.html26
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-testderived.html81
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc.html64
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/configs/noautolist.qdocconf64
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/configs/testcpp.qdocconf31
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/configs/testcpp_singleexec.qdocconf32
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/CrossModule2
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/crossmodule.qdocconf26
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/crossmodule_singleexec.qdocconf6
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/namespaces.qdoc9
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/testtype.cpp45
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/testtype.h16
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/dontdocument/TestCPP2
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/dontdocument/dontdocument.qdocconf46
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/demo.cpp11
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/demo.pro2
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/doc/src/demo.qdoc11
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/dontxclude/CMakeLists.txt2
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/excludes/CMakeLists.txt2
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/hidden/doc/src/hidden.qdoc11
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/hidden/hidden.pro2
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/images/01.pngbin0 -> 1142 bytes
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/images/leonardo-da-vinci.pngbin0 -> 15076 bytes
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/excludes/anotherindex.qdoc14
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/excludes/parentinclude.qdoc28
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/includefromexampledirs.qdocconf64
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/src/includefromparent.qdoc41
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/src/parent.qdocinc1
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/indexlinking/indexlinking.qdocconf29
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/indexlinking/linking.qdoc41
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/qml/DocTest.qml86
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/qml/cmaketest/CMakeLists.txt2
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/qml/cmaketest/doc/src/cmaketest.qdoc9
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/qml/cmaketest/main.cpp1
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/ProgressBar.qml98
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/Switch.qml105
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/TabWidget.qml146
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/componentset.pro5
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/componentset.qml7
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/examples.qdoc82
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/uicomponents.qdoc.sample14
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/qml/doctest/DocTest.qml11
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/qml/modules.qdoc19
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/qml/parent.qdoc87
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/qml/type.cpp127
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/qmlpropertygroups/parent.qdoc37
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/qmlpropertygroups/qmlpropertygroups.qdocconf63
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/singleexec/singleexec.qdocconf4
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/TestCPP5
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/classlists.qdoc51
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/properties.qdoc55
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/snippets/snippet_testcpp.cpp3
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/testcpp.cpp418
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/testcpp.h137
-rw-r--r--src/qdoc/qdoc/tests/generatedoutput/tst_generatedoutput.cpp283
-rw-r--r--src/qdoc/qdoc/tests/qdoc/CMakeLists.txt28
-rw-r--r--src/qdoc/qdoc/tests/qdoc/boundaries/filesystem/catch_directorypath.cpp195
-rw-r--r--src/qdoc/qdoc/tests/qdoc/boundaries/filesystem/catch_filepath.cpp136
-rw-r--r--src/qdoc/qdoc/tests/qdoc/filesystem/catch_fileresolver.cpp307
-rw-r--r--src/qdoc/qdoc/tests/qdoc/main.cpp12
-rw-r--r--src/qdoc/qdoc/tests/qdoccommandlineparser/CMakeLists.txt41
-rw-r--r--src/qdoc/qdoc/tests/qdoccommandlineparser/tst_arguments.txt22
-rw-r--r--src/qdoc/qdoc/tests/qdoccommandlineparser/tst_qdoccommandlineparser.cpp161
-rw-r--r--src/qdoc/qdoc/tests/utilities/CMakeLists.txt21
-rw-r--r--src/qdoc/qdoc/tests/utilities/tst_utilities.cpp134
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/CMakeLists.txt44
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/README.md82
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/bug80259.qdocconf31
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/first-members.html17
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/first-nested.html27
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/first.html37
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/index.html21
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/second.html29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/testmodule.index13
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/third.html29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/first-nested.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/first.webxml15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/index.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/second.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/testmodule.index13
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/third.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/TestModule.h6
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/aaa.h7
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/bbb.h8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/ccc.h7
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/main.cpp29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/qdoc/index.qdoc10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/cmakedocumentation.qdocconf29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/car.xml42
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/engine.xml42
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/testcar-module.xml23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/testcarprivate-module.xml23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/car.html36
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/cmakedocumentation.index15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/engine.html36
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/testcar-module.html29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/testcarprivate-module.html29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/car.webxml15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/cmakedocumentation.index15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/engine.webxml20
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/testcar-module.webxml4
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/testcarprivate-module.webxml4
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/src/car.cpp46
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/src/car.h17
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/compiler_generated_member_functions.qdocconf23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/docbook/qdoctests-compilergeneratedmemberfunctions.xml39
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/docbook/qdoctests-module.xml38
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/docbook/qdoctests.xml30
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/compiler-generated-member-functions.index18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests-compilergeneratedmemberfunctions-members.html20
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests-compilergeneratedmemberfunctions.html59
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests-module.html38
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests.html43
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/compiler-generated-member-functions.index18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/qdoctests-compilergeneratedmemberfunctions.webxml28
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/qdoctests-module.webxml4
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/qdoctests.webxml34
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/src/compilergeneratedmemberfunctions.cpp45
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/src/compilergeneratedmemberfunctions.h20
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/comprehensiveproject.qdocconf105
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/autolinking.xml32
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/classes.xml30
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/cpptypes.xml33
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/crossmoduleref.xml59
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/images/leonardo-da-vinci.pngbin0 -> 15076 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/obsolete-classes.xml31
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qdoc-test-qmlmodule.xml54
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-int.xml36
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-abstractparent.xml79
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-child.xml79
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-doctest.xml117
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-oldtype.xml37
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-type.xml237
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-yetanotherchild.xml44
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-test-nover-doctest.xml35
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-test-nover-typenoversion.xml35
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-themodule-thetype.xml43
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-uicomponents-progressbar.xml104
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-uicomponents-switch.xml47
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-uicomponents-tabwidget.xml77
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qmlmodules.xml103
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/seenclass.xml42
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-cmaketest-cmakelists-txt.xml14
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-cmaketest-example.xml27
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-cmaketest-main-cpp.xml13
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-componentset-pro.xml18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-componentset-qml.xml20
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-example.xml55
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-progressbar-qml.xml111
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-switch-qml.xml116
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-tabwidget-qml.xml158
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-demo-cpp.xml21
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-demo-pro.xml14
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-dontxclude-cmakelists-txt.xml14
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-example.xml32
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-hidden-example.xml12
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-empty-qmlmodule.xml12
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-nover-qmlmodule.xml30
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testcpp-module.xml91
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testqdoc-test.xml376
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testqdoc-testderived.xml308
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testqdoc.xml73
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/themodule-qmlmodule.xml16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/uicomponents-qmlmodule.xml34
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/autolinking.html36
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/classes.html21
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/cpptypes.html25
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/crossmoduleref.html47
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/images/leonardo-da-vinci.pngbin0 -> 15076 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/obsolete-classes.html32
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qdoc-test-qmlmodule.html27
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-int.html45
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-abstractparent-members.html23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-abstractparent.html98
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-child-members.html23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-child.html98
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-doctest-members.html25
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-doctest.html139
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-oldtype-members.html16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-oldtype.html33
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-type-members.html37
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-type-obsolete.html33
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-type.html209
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-yetanotherchild-members.html19
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-yetanotherchild.html48
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-doctest-members.html17
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-doctest.html33
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-typenoversion-members.html17
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-typenoversion.html33
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-themodule-thetype-members.html19
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-themodule-thetype.html45
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-progressbar-members.html24
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-progressbar.html98
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-switch-members.html21
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-switch.html68
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-tabwidget-members.html21
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-tabwidget.html84
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qmlmodules.html49
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/seenclass.html37
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-cmaketest-cmakelists-txt.html11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-cmaketest-example.html21
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-cmaketest-main-cpp.html10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-componentset-pro.html15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-componentset-qml.html17
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-example.html55
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-progressbar-qml.html108
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-switch-qml.html113
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-tabwidget-qml.html155
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-demo-cpp.html18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-demo-pro.html11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-dontxclude-cmakelists-txt.html11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-example.html24
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-hidden-example.html21
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-empty-qmlmodule.html17
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-nover-qmlmodule.html23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test.index238
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test.qhp260
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testcpp-module.html60
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-test-members.html30
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-test-obsolete.html45
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-test.html165
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-testderived-members.html50
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-testderived-obsolete.html26
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-testderived.html173
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc.html65
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testtagfile.tags500
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/themodule-qmlmodule.html18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/uicomponents-qmlmodule.html24
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/autolinking.webxml36
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/classes.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/cpptypes.webxml22
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/crossmoduleref.webxml15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/images/leonardo-da-vinci.pngbin0 -> 15076 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/obsolete-classes.webxml18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/qdoc-test-qmlmodule.webxml4
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/qmlmodules.webxml21
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/seenclass.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-cmaketest-cmakelists-txt.webxml11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-cmaketest-example.webxml23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-cmaketest-main-cpp.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-componentset-pro.webxml14
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-componentset-qml.webxml16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-example.webxml67
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-progressbar-qml.webxml107
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-switch-qml.webxml112
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-tabwidget-qml.webxml154
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-demo-cpp.webxml18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-demo-pro.webxml11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-dontxclude-cmakelists-txt.webxml11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-example.webxml30
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-hidden-example.webxml11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-empty-qmlmodule.webxml4
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-nover-qmlmodule.webxml4
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test.index226
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testcpp-module.webxml4
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testqdoc-test.webxml121
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testqdoc-testderived.webxml124
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testqdoc.webxml253
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testtagfile.tags500
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/themodule-qmlmodule.webxml4
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/uicomponents-qmlmodule.webxml4
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/TestCPP5
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/classlists.qdoc51
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/dont.cpp22
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/dont.h16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/demo.cpp11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/demo.pro2
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/doc/src/demo.qdoc11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/dontxclude/CMakeLists.txt2
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/excludes/CMakeLists.txt2
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/hidden/doc/src/hidden.qdoc11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/hidden/hidden.pro2
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/images/01.pngbin0 -> 1142 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/images/leonardo-da-vinci.pngbin0 -> 15076 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/properties.qdoc55
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/DocTest.qml86
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/cmaketest/CMakeLists.txt2
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/cmaketest/doc/src/cmaketest.qdoc9
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/cmaketest/main.cpp1
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/ProgressBar.qml98
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/Switch.qml105
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/TabWidget.qml146
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/componentset.pro5
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/componentset.qml7
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/examples.qdoc82
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/uicomponents.qdoc.sample14
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/doctest/DocTest.qml11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/modules.qdoc19
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/parent.qdoc87
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/type.cpp133
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/snippets/snippet_testcpp.cpp3
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/testcpp.cpp418
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/testcpp.h140
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/unseenclass.qdoc11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/cxx20.qdocconf32
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/bar.xml21
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/baz.xml21
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywithoneclassandpartiallywithanother.xml27
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywiththreeclasses.xml22
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywiththreeclassesacrossmultiplelines.xml22
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywithtwoclasses.xml26
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/equalitycomparableclass.xml22
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/foo.xml21
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/partiallyorderedclass.xml22
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/stronglyorderedclass.xml22
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/weaklyorderedclass.xml22
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/bar.html28
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/baz.html28
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywithoneclassandpartiallywithanother.html37
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywiththreeclasses.html29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywiththreeclassesacrossmultiplelines.html29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywithtwoclasses.html33
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/cxx20.index18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/equalitycomparableclass.html29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/foo.html28
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/partiallyorderedclass.html29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/stronglyorderedclass.html29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/weaklyorderedclass.html29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/bar.webxml8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/baz.webxml8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywithoneclassandpartiallywithanother.webxml11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywiththreeclasses.webxml8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywiththreeclassesacrossmultiplelines.webxml8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywithtwoclasses.webxml8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/cxx20.index18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/equalitycomparableclass.webxml8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/foo.webxml8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/partiallyorderedclass.webxml8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/stronglyorderedclass.webxml8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/weaklyorderedclass.webxml8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/src/classes_with_various_ordering.cpp89
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/src/classes_with_various_ordering.h41
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/duplicate_section_titles_have_unique_anchors.qdocconf25
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/docbook/page-one.xml27
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/html/duplicate-section-titles-have-unique-anchors.index12
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/html/page-one.html35
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/webxml/duplicate-section-titles-have-unique-anchors.index12
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/webxml/page-one.webxml30
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/src/page_one.qdoc21
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/expected/html/globals.html45
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/expected/html/testglobals-module.html29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/expected/html/testglobals.index23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/globalfunc.qdocconf23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/src/TestGlobals1
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/src/global.h8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/src/global.qdoc25
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/docbook/headers.xml19
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/docbook/testheader.xml68
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/docbook/tests.xml19
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/headerfile.index23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/headers.html19
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/testheader.html69
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/tests.html19
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/headerfile.index23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/headers.webxml21
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/testheader.webxml34
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/tests.webxml21
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/headerfile.qdocconf25
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/src/testheader.cpp43
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/src/testheader.h8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/another-page-with-comments-in-the-brief.html18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/brief-adventures.html27
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/illformatted-examples.html31
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/illformatteddocumentation-someexample-example.html16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/illformatteddocumentation.index22
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/page-with-an-image-at-the-top.html19
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/page-with-comment-after-brief.html18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/page-with-comment-in-brief.html18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/another-page-with-comments-in-the-brief.webxml11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/brief-adventures.webxml17
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/illformatted-examples.webxml31
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/illformatteddocumentation-someexample-example.webxml8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/illformatteddocumentation.index22
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/page-with-an-image-at-the-top.webxml12
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/page-with-comment-after-brief.webxml11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/page-with-comment-in-brief.webxml11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/illformatted_documentation.qdocconf29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/src/brief_adventures.qdoc75
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/src/illformatted-examples.qdoc17
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/src/some_example.qdoc8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/a-page-with-a-line-comment-in-the-see-also-command.xml20
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/another-page-with-an-image-at-the-top.xml19
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/images/leonardo-da-vinci.pngbin0 -> 15076 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/line-comment-adventures.xml16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/a-page-with-a-line-comment-in-the-see-also-command.html18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/another-page-with-an-image-at-the-top.html19
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/images/leonardo-da-vinci.pngbin0 -> 15076 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/line-comment-adventures.html27
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/linecomment.index11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/a-page-with-a-line-comment-in-the-see-also-command.webxml14
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/another-page-with-an-image-at-the-top.webxml13
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/images/leonardo-da-vinci.pngbin0 -> 15076 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/line-comment-adventures.webxml16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/linecomment.index11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/line_comments.qdocconf27
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/src/images/leonardo-da-vinci.pngbin0 -> 15076 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/src/line_comment_adventures.qdoc39
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/expected/a-minimal-qdoc-configuration.index7
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/expected/readme.html17
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/minimal_configuration.qdocconf12
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/src/README.qdoc9
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/cppmodule-module-suffix.xml26
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/group.xml11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/modifiedoutputfilenames-test-example.xml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/page.xml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/prefix-header-suffix.xml21
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/prefix-namespace-class-suffix.xml22
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/prefix-namespace-suffix.xml27
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/qml-qmlmodule-suffix-type.xml21
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/qmlmodule-qmlmodule-suffix.xml16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/cppmodule-module-suffix.html32
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/group.html15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/modifiedoutputfilenames-test-example.html15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/modifiedoutputfilenames.index16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/page.html15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/prefix-header-suffix.html24
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/prefix-namespace-class-suffix.html29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/prefix-namespace-suffix.html35
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/qml-qmlmodule-suffix-type-members.html14
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/qml-qmlmodule-suffix-type.html27
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/qmlmodule-qmlmodule-suffix.html18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/cppmodule-module-suffix.webxml4
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/group.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/modifiedoutputfilenames-test-example.webxml8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/modifiedoutputfilenames.index16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/page.webxml8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/prefix-header-suffix.webxml8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/prefix-namespace-class-suffix.webxml8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/prefix-namespace-suffix.webxml11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/qmlmodule-qmlmodule-suffix.webxml4
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/modifiedoutputfilenames.qdocconf22
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/src/example/test/CMakeLists.txt1
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/src/test.cpp24
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/src/test.h12
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/docbook/boringclass.xml28
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/docbook/excitingclass.xml32
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/docbook/moduleinstate-module.xml23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/boringclass.html31
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/excitingclass.html34
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/moduleinstate-module.html30
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/modulestate.index11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/boringclass.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/excitingclass.webxml15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/moduleinstate-module.webxml4
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/modulestate.index11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/modulestate.qdocconf29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/src/classes_in_stateful_module.h15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/src/module_in_a_state.qdoc26
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/8b5c72eb.html16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/adventures-with-non-ascii-characters.html46
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/e85685de.html16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/8b5c72eb.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/adventures-with-non-ascii-characters.webxml45
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/e85685de.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/mozzarella-7c883eff.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/nonasciicharacterinput.index19
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/santa-14209312.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/seite-mit-ausschlie-lich-gro-buchstaben-im-titel-berschrift-htm-bfa91582.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/mozzarella-7c883eff.html16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/nonasciicharacterinput.index19
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/santa-14209312.html16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/seite-mit-ausschlie-lich-gro-buchstaben-im-titel-berschrift-htm-bfa91582.html16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/non_ascii_character_input.qdocconf30
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/src/adventures_with_non_ascii_characters.qdoc89
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/crash.xml12
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/images/01.pngbin0 -> 1142 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/images/leonardo-da-vinci.pngbin0 -> 15076 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocfileoutput-exhaustive.xml129
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocfileoutput-linking.xml19
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocfileoutput.xml80
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocmanuallikefileoutput.xml27
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/toc.xml23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/crash.html16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/images/01.pngbin0 -> 1142 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/images/leonardo-da-vinci.pngbin0 -> 15076 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/outputfromqdocfiles.index33
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocfileoutput-exhaustive.html78
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocfileoutput-linking.html37
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocfileoutput.html70
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocmanuallikefileoutput.html63
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/toc.html29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/crash.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/images/01.pngbin0 -> 1142 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/images/leonardo-da-vinci.pngbin0 -> 15076 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/outputfromqdocfiles.index33
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocfileoutput-exhaustive.webxml90
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocfileoutput-linking.webxml20
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocfileoutput.webxml95
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocmanuallikefileoutput.webxml57
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/toc.webxml24
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/outputfromqdocfiles.qdocconf43
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/images/01.pngbin0 -> 1142 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/images/leonardo-da-vinci.pngbin0 -> 15076 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/qdoctests-outputfromqdocfiles.qdoc241
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/qdoctests-outputfromqdocmanuallikefiles.qdoc59
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/snippets/main.cpp10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/expected/docbook/stdpair-proxypage-proxy.xml16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/expected/html/proxypage.index10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/expected/html/stdpair-proxypage-proxy.html20
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/proxypage.qdocconf24
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/src/proxy.h11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/src/proxy.qdoc9
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/cppcar.xml28
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/qml-qmlnativetypesynopsis-car.xml66
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/qml-qmlnativetypesynopsis-truck.xml66
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/qmlnativetypesynopsis-qmlmodule.xml25
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/cppcar.html33
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-nativetype-synopsis-test.index20
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-car-members.html15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-car.html46
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-truck-members.html15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-truck.html46
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qmlnativetypesynopsis-qmlmodule.html19
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/webxml/cppcar.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/webxml/qml-nativetype-synopsis-test.index20
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/webxml/qmlnativetypesynopsis-qmlmodule.webxml4
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/qml_nativetype_synopsis.qdocconf23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/src/cppcar.cpp68
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/src/cppcar.h10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/class.xml136
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/module-module.xml18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/qml-qmlmodule-type.xml103
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/qmlmodule-qmlmodule.xml16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/class-members.html18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/class.html59
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/module-module.html28
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qml-qmlmodule-type-members.html18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qml-qmlmodule-type.html62
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qmlenumvaluesfromcpp.index23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qmlmodule-qmlmodule.html18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/class.webxml54
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/module-module.webxml4
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/qmlenumvaluesfromcpp.index23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/qmlmodule-qmlmodule.webxml4
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/qmlenumvaluesfromcpp.qdocconf22
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/src/class.cpp36
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/src/class.h10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/src/qmltype.qdoc23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/a.cpp11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/a.h5
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/b.cpp12
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/bar.html35
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/module-module.html22
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/relatesordering.index11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/relatesordering.qdocconf7
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/autolinking.xml32
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/cpptypes.xml30
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/crossmoduleref.xml49
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/obsolete-classes.xml31
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/scoped-enum-linking.xml12
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testcpp-module.xml77
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testqdoc-test.xml253
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testqdoc-testderived.xml83
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testqdoc.xml63
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/whatsnew.xml11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/autolinking.html36
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/cpptypes.html24
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/crossmoduleref.html47
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/obsolete-classes.html32
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/scoped-enum-linking.html18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testcpp-module.html55
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testcpp.index91
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-test-members.html30
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-test-obsolete.html45
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-test.html173
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-testderived-members.html38
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-testderived-obsolete.html26
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-testderived.html81
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc.html64
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/whatsnew.html37
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/autolinking.webxml36
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/cpptypes.webxml22
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/crossmoduleref.webxml15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/obsolete-classes.webxml18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/scoped-enum-linking.webxml11
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testcpp-module.webxml4
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testcpp.index91
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testqdoc-test.webxml160
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testqdoc-testderived.webxml33
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testqdoc.webxml201
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/whatsnew.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/scopedenum.qdocconf37
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/classlists.qdoc51
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/scopedenum.qdoc43
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/snippets/snippet_testcpp.cpp3
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/testcpp.cpp402
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/testcpp.h139
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/docbok/tableaftervalue.xml60
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/html/tableaftervalue-members.html18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/html/tableaftervalue.html56
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/html/tableaftervalue.index13
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/webxml/tableaftervalue.index13
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/webxml/tableaftervalue.webxml35
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/src/table-after-value.cpp28
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/src/table-after-value.h8
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/tableaftervalue.qdocconf29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/docbook/templated-callables-h.xml65
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/docbook/templatedclass.xml66
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templated-callables-h.html81
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templatedcallables.index77
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templatedclass-members.html26
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templatedclass.html90
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/webxml/templated-callables-h.webxml66
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/webxml/templatedcallables.index77
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/webxml/templatedclass.webxml66
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/src/templated_callables.cpp149
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/src/templated_callables.h73
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/templatedcallables.qdocconf29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/autolinking.xml32
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/bar.xml43
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/baz.xml42
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/cpptypes.xml30
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/crossmoduleref.xml49
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/foo.xml43
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/obsolete-classes.xml31
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testcpp-module.xml107
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-test-struct.xml42
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-test.xml189
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-testderived.xml83
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-vec.xml43
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc.xml67
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/autolinking.html36
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/bar.html38
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/baz.html38
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/cpptypes.html24
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/crossmoduleref.html47
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/foo.html38
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/obsolete-classes.html32
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testcpp-module.html60
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test-members.html31
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test-obsolete.html45
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test-struct.html32
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test.html167
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-testderived-members.html39
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-testderived-obsolete.html26
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-testderived.html81
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-vec.html38
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc.html68
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testtemplate.index87
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/autolinking.webxml36
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/bar.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/baz.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/cpptypes.webxml22
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/crossmoduleref.webxml15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/foo.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/obsolete-classes.webxml18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testcpp-module.webxml4
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-test-struct.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-test.webxml130
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-testderived.webxml33
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-vec.webxml10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc.webxml176
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testtemplate.index87
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/classlists.qdoc51
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/snippets/snippet_testcpp.cpp3
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testcpp.cpp402
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testcpp.h137
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testtemplate.cpp23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testtemplate.h28
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/testtemplate.qdocconf38
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/crash.xml14
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/images/01.pngbin0 -> 1142 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/images/leonardo-da-vinci.pngbin0 -> 15076 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocfileoutput-exhaustive.xml131
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocfileoutput-linking.xml19
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocfileoutput.xml80
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocmanuallikefileoutput.xml27
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/toc-test.xml40
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/toc.xml23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/crash.html26
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/images/01.pngbin0 -> 1142 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/images/leonardo-da-vinci.pngbin0 -> 15076 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/outputfromqdocfiles.index34
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocfileoutput-exhaustive.html91
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocfileoutput-linking.html39
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocfileoutput.html72
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocmanuallikefileoutput.html63
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/toc-test.html32
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/toc.html31
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/crash.webxml12
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/images/01.pngbin0 -> 1142 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/images/leonardo-da-vinci.pngbin0 -> 15076 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/outputfromqdocfiles.index34
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocfileoutput-exhaustive.webxml92
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocfileoutput-linking.webxml20
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocfileoutput.webxml95
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocmanuallikefileoutput.webxml57
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/toc-test.webxml44
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/toc.webxml24
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/images/01.pngbin0 -> 1142 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/images/leonardo-da-vinci.pngbin0 -> 15076 bytes
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/qdoctests-outputfromqdocfiles.qdoc241
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/qdoctests-outputfromqdocmanuallikefiles.qdoc59
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/snippets/main.cpp10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/toc.qdoc23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/tocnavigation.qdocconf48
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/docbook/trademark-test.xml15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/docbook/trademarks.xml19
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/html/trademark-test.html26
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/html/trademarkcommand.index12
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/html/trademarks.html23
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/webxml/trademark-test.webxml15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/webxml/trademarkcommand.index12
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/webxml/trademarks.webxml20
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/src/test.qdoc32
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/trademark_command.qdocconf20
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/docbook/struct.xml57
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/html/struct-members.html19
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/html/struct.html53
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/html/trailingbackslashes.index18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/webxml/struct.webxml32
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/webxml/trailingbackslashes.index18
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/src/trailing_backslashes.cpp32
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/src/trailing_backslashes.h14
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/trailing_backslashes.qdocconf29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/docbook/space.xml29
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/html/space.html43
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/html/usingdirective.index15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/webxml/space.webxml16
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/webxml/usingdirective.index15
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/UsingDirective2
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/alias.h10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/space.cpp21
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/space.h10
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/usingdirective.qdocconf27
-rw-r--r--src/qdoc/qdoc/tests/validateqdocoutputfiles/tst_validateqdocoutputfiles.cpp182
957 files changed, 90710 insertions, 0 deletions
diff --git a/src/qdoc/qdoc/CMakeLists.txt b/src/qdoc/qdoc/CMakeLists.txt
new file mode 100644
index 000000000..a1d31765e
--- /dev/null
+++ b/src/qdoc/qdoc/CMakeLists.txt
@@ -0,0 +1,117 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+if(CMAKE_VERSION VERSION_LESS "3.19" AND MSVC AND CMAKE_GENERATOR STREQUAL "Ninja Multi-Config")
+ message(WARNING "qdoc will not be built in this configuration.")
+ return()
+endif()
+
+if (MINGW)
+ set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" PROPERTY _qt_skip_separate_debug_info ON)
+endif()
+
+
+#####################################################################
+## qdoc Tool:
+#####################################################################
+
+qt_get_tool_target_name(target_name qdoc)
+qt_internal_add_tool(${target_name}
+ TARGET_DESCRIPTION "Qt Documentation Compiler"
+ TOOLS_TARGET Tools
+ USER_FACING
+ SOURCES
+ src/qdoc/aggregate.cpp
+ src/qdoc/atom.cpp
+ src/qdoc/boundaries/filesystem/directorypath.cpp
+ src/qdoc/boundaries/filesystem/filepath.cpp
+ src/qdoc/boundaries/filesystem/resolvedfile.cpp
+ src/qdoc/clangcodeparser.cpp
+ src/qdoc/classnode.cpp
+ src/qdoc/codechunk.cpp
+ src/qdoc/codemarker.cpp
+ src/qdoc/codeparser.cpp
+ src/qdoc/collectionnode.cpp
+ src/qdoc/comparisoncategory.h
+ src/qdoc/config.cpp
+ src/qdoc/cppcodemarker.cpp
+ src/qdoc/cppcodeparser.cpp
+ src/qdoc/doc.cpp
+ src/qdoc/docbookgenerator.cpp
+ src/qdoc/docparser.cpp
+ src/qdoc/docprivate.cpp
+ src/qdoc/editdistance.cpp
+ src/qdoc/enumnode.cpp
+ src/qdoc/externalpagenode.cpp
+ src/qdoc/filesystem/fileresolver.cpp
+ src/qdoc/functionnode.cpp
+ src/qdoc/generator.cpp
+ src/qdoc/headernode.cpp
+ src/qdoc/helpprojectwriter.cpp
+ src/qdoc/htmlgenerator.cpp
+ src/qdoc/location.cpp
+ src/qdoc/main.cpp
+ src/qdoc/manifestwriter.cpp
+ src/qdoc/namespacenode.cpp
+ src/qdoc/node.cpp
+ src/qdoc/openedlist.cpp
+ src/qdoc/pagenode.cpp
+ src/qdoc/parameters.cpp
+ src/qdoc/parsererror.cpp
+ src/qdoc/propertynode.cpp
+ src/qdoc/proxynode.cpp
+ src/qdoc/puredocparser.cpp
+ src/qdoc/qdoccommandlineparser.cpp
+ src/qdoc/qdocdatabase.cpp
+ src/qdoc/qdocindexfiles.cpp
+ src/qdoc/qmlcodemarker.cpp
+ src/qdoc/qmlcodeparser.cpp
+ src/qdoc/qmlmarkupvisitor.cpp
+ src/qdoc/qmlpropertynode.cpp
+ src/qdoc/qmltypenode.cpp
+ src/qdoc/qmlvisitor.cpp
+ src/qdoc/quoter.cpp
+ src/qdoc/relatedclass.cpp
+ src/qdoc/sections.cpp
+ src/qdoc/sharedcommentnode.cpp
+ src/qdoc/tagfilewriter.cpp
+ src/qdoc/text.cpp
+ src/qdoc/tokenizer.cpp
+ src/qdoc/tree.cpp
+ src/qdoc/typedefnode.cpp
+ src/qdoc/utilities.cpp
+ src/qdoc/variablenode.cpp
+ src/qdoc/webxmlgenerator.cpp
+ src/qdoc/xmlgenerator.cpp
+ NO_UNITY_BUILD_SOURCES
+ src/qdoc/qmlmarkupvisitor.cpp # redefinition of 'samp'/'slt' (from codemarker.cpp)
+ INCLUDE_DIRECTORIES
+ ${CMAKE_CURRENT_LIST_DIR}/src
+ LIBRARIES
+ Qt::QmlPrivate
+ WrapLibClang::WrapLibClang
+ DEFINES
+ #(CLANG_RESOURCE_DIR=\"/clang//include\") # special case remove
+ CLANG_RESOURCE_DIR=${QT_LIBCLANG_RESOURCE_DIR}
+ # To provide the ability to workaround version-specific Clang issues.
+ # A re-export of (LLVM|CLANG)_VERSION_MAJOR done in WrapLibClang.cmake
+ LIBCLANG_VERSION_MAJOR=${QT_LIB_CLANG_VERSION_MAJOR}
+ QDOC2_COMPAT
+)
+qt_internal_return_unless_building_tools()
+
+# If libclangTooling.a is not built with -fPIE enabled we cannot link it to qdoc.
+# TODO: Re-enable PIE once clang is built with PIE in provisioning.
+set_target_properties(${target_name} PROPERTIES POSITION_INDEPENDENT_CODE FALSE)
+
+qt_internal_extend_target(${target_name} CONDITION (WIN32 AND ICC) OR MSVC
+ LINK_OPTIONS
+ "/STACK:4194304"
+)
+qt_internal_add_docs(${target_name}
+ doc/config/qdoc.qdocconf
+)
+
+if(QT_BUILD_TESTS)
+ add_subdirectory(tests)
+endif()
diff --git a/src/qdoc/qdoc/doc/config/qdoc.qdocconf b/src/qdoc/qdoc/doc/config/qdoc.qdocconf
new file mode 100644
index 000000000..d886ff75b
--- /dev/null
+++ b/src/qdoc/qdoc/doc/config/qdoc.qdocconf
@@ -0,0 +1,61 @@
+include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf)
+include($QT_INSTALL_DOCS/config/exampleurl-qttools.qdocconf)
+
+project = QDoc
+description = QDoc Manual
+version = $QT_VERSION
+moduleheader =
+
+sourcedirs = ..
+
+exampledirs = .. \
+ ../examples
+
+imagedirs = ../images
+
+tagfile = ../html/qdoc.tags
+
+examples.fileextensions = "*.cpp *.h *.js *.xq *.svg *.xml *.ui *.qhp *.qhcp *.qml *.css *.qdoc *.qdocinc *.sample"
+
+qhp.projects = QDoc
+
+qhp.QDoc.file = qdoc.qhp
+qhp.QDoc.namespace = org.qt-project.qdoc.$QT_VERSION_TAG
+qhp.QDoc.virtualFolder = qdoc
+qhp.QDoc.indexTitle = QDoc Manual
+qhp.QDoc.indexRoot =
+
+qhp.QDoc.subprojects = overviews
+qhp.QDoc.subprojects.overviews.title = Overviews
+qhp.QDoc.subprojects.overviews.indexTitle = QDoc Manual
+qhp.QDoc.subprojects.overviews.selectors = doc:page
+
+depends += \
+ qtconcurrent \
+ qtcore \
+ qtdbus \
+ qtdesigner \
+ qtdoc \
+ qthelp \
+ qtgui \
+ qtlinguist \
+ qtnetwork \
+ qtopengl \
+ qtprintsupport \
+ qtqml \
+ qtquick \
+ qtquickcontrols \
+ qtsql \
+ qtsvg \
+ qttestlib \
+ qtuitools \
+ qtwidgets \
+ qtxml
+
+ignorewords += QDoc
+
+navigation.landingpage = "QDoc Manual"
+
+# Convenience macros for creating links to QDoc commands & configuration variables
+macro.qdoccmd = \\l{\1-command}{\\c{\\\1}}
+macro.qdocvar = \\l{\1-variable}{\\c{\1}}
diff --git a/src/qdoc/qdoc/doc/corefeatures.qdoc b/src/qdoc/qdoc/doc/corefeatures.qdoc
new file mode 100644
index 000000000..e1edc3a92
--- /dev/null
+++ b/src/qdoc/qdoc/doc/corefeatures.qdoc
@@ -0,0 +1,11 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page corefeatures.html
+ \title Core Features
+
+ \include examples/signalandslots.qdocinc
+ \include examples/objectmodel.qdocinc
+ \include examples/layoutmanagement.qdocinc
+*/
diff --git a/src/qdoc/qdoc/doc/examples/cpp.qdoc.sample b/src/qdoc/qdoc/doc/examples/cpp.qdoc.sample
new file mode 100644
index 000000000..39c77f63c
--- /dev/null
+++ b/src/qdoc/qdoc/doc/examples/cpp.qdoc.sample
@@ -0,0 +1,102 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+//![class]
+/*!
+ \class QCache
+ \brief The QCache class is a template class that provides a cache.
+
+ \ingroup tools
+ \ingroup shared
+
+ \reentrant
+
+ QCache\<Key, T\> defines a cache that stores objects of type T
+ associated with keys of type Key. For example, here's the
+ definition of a cache that stores objects of type Employee
+ associated with an integer key:
+
+ \snippet code/doc_src_qcache.cpp 0
+
+ Here's how to insert an object in the cache:
+
+ \snippet code/doc_src_qcache.cpp 1
+
+ ... detailed description omitted
+
+ \sa QPixmapCache, QHash, QMap
+*/
+//![class]
+
+//![function]
+/*!
+ \fn QString &QString::remove(int position, int n)
+
+ Removes \a n characters from the string, starting at the given \a
+ position index, and returns a reference to the string.
+
+ If the specified \a position index is within the string, but \a
+ position + \a n is beyond the end of the string, the string is
+ truncated at the specified \a position.
+
+ \snippet qstring/main.cpp 37
+
+ \sa insert(), replace()
+*/
+QString &QString::remove(int pos, int len)
+//! [function]
+
+//! [return]
+/*!
+ Returns \c true if a QScroller object was already created for \a target; \c false otherwise.
+
+ \sa scroller()
+*/
+bool QScroller::hasScroller(QObject *target)
+//! [return]
+
+//! [property]
+/*!
+ \property QVariantAnimation::duration
+ \brief the duration of the animation
+
+ This property describes the duration in milliseconds of the
+ animation. The default duration is 250 milliseconds.
+
+ \sa QAbstractAnimation::duration()
+ */
+int QVariantAnimation::duration() const
+//! [property]
+
+//! [signals]
+/*!
+ \fn QAbstractTransition::triggered()
+
+ This signal is emitted when the transition has been triggered (after
+ onTransition() has been called).
+*/
+//! [signals]
+
+//! [enums]
+/*!
+ \enum QSql::TableType
+
+ This enum type describes types of SQL tables.
+
+ \value Tables All the tables visible to the user.
+ \value SystemTables Internal tables used by the database.
+ \value Views All the views visible to the user.
+ \value AllTables All of the above.
+*/
+//! [enums]
+
+//! [overloaded notifier]
+/*!
+\property QSpinBox::value
+\brief the value of the spin box
+
+setValue() will emit valueChanged() if the new value is different
+from the old one. The \l{QSpinBox::}{value} property has a second notifier
+signal which includes the spin box's prefix and suffix.
+*/
+//! [overloaded notifier]
diff --git a/src/qdoc/qdoc/doc/examples/layoutmanagement.qdocinc b/src/qdoc/qdoc/doc/examples/layoutmanagement.qdocinc
new file mode 100644
index 000000000..780b03c8f
--- /dev/null
+++ b/src/qdoc/qdoc/doc/examples/layoutmanagement.qdocinc
@@ -0,0 +1,13 @@
+\section1 Layout Classes
+
+The Qt layout system provides a simple and powerful way of specifying
+the layout of child widgets.
+
+By specifying the logical layout once, you get the following benefits:
+
+\list
+ \li Positioning of child widgets.
+ \li Sensible default sizes for windows.
+ \li Sensible minimum sizes for windows.
+ \li ...
+\endlist
diff --git a/src/qdoc/qdoc/doc/examples/main.cpp b/src/qdoc/qdoc/doc/examples/main.cpp
new file mode 100644
index 000000000..0898fa5f7
--- /dev/null
+++ b/src/qdoc/qdoc/doc/examples/main.cpp
@@ -0,0 +1,16 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QApplication>
+#include <QPushButton>
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+
+ QPushButton hello("Hello world!");
+ hello.resize(100, 30);
+
+ hello.show();
+ return app.exec();
+}
diff --git a/src/qdoc/qdoc/doc/examples/mainwindow.cpp b/src/qdoc/qdoc/doc/examples/mainwindow.cpp
new file mode 100644
index 000000000..4e67f0a79
--- /dev/null
+++ b/src/qdoc/qdoc/doc/examples/mainwindow.cpp
@@ -0,0 +1,213 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QtWidgets>
+
+#include "mainwindow.h"
+#include "scribblearea.h"
+
+//! [0]
+MainWindow::MainWindow()
+{
+ scribbleArea = new ScribbleArea;
+ setCentralWidget(scribbleArea);
+
+ createActions();
+ createMenus();
+
+ setWindowTitle(tr("Scribble"));
+ resize(500, 500);
+}
+//! [0]
+
+//! [1]
+void MainWindow::closeEvent(QCloseEvent *event)
+//! [1] //! [2]
+{
+ if (maybeSave()) {
+ event->accept();
+ } else {
+ event->ignore();
+ }
+}
+//! [2]
+
+//! [3]
+void MainWindow::open()
+//! [3] //! [4]
+{
+ if (maybeSave()) {
+ QString fileName = QFileDialog::getOpenFileName(this,
+ tr("Open File"), QDir::currentPath());
+ if (!fileName.isEmpty())
+ scribbleArea->openImage(fileName);
+ }
+}
+//! [4]
+
+//! [5]
+void MainWindow::save()
+//! [5] //! [6]
+{
+ QAction *action = qobject_cast<QAction *>(sender());
+ QByteArray fileFormat = action->data().toByteArray();
+ saveFile(fileFormat);
+}
+//! [6]
+
+//! [7]
+void MainWindow::penColor()
+//! [7] //! [8]
+{
+ QColor newColor = QColorDialog::getColor(scribbleArea->penColor());
+ if (newColor.isValid())
+ scribbleArea->setPenColor(newColor);
+}
+//! [8]
+
+//! [9]
+void MainWindow::penWidth()
+//! [9] //! [10]
+{
+ bool ok;
+ int newWidth = QInputDialog::getInteger(this, tr("Scribble"),
+ tr("Select pen width:"),
+ scribbleArea->penWidth(),
+ 1, 50, 1, &ok);
+ if (ok)
+ scribbleArea->setPenWidth(newWidth);
+}
+//! [10]
+
+//! [11]
+void MainWindow::about()
+//! [11] //! [12]
+{
+ QMessageBox::about(this, tr("About Scribble"),
+ tr("<p>The <b>Scribble</b> example shows how to use QMainWindow as the "
+ "base widget for an application, and how to reimplement some of "
+ "QWidget's event handlers to receive the events generated for "
+ "the application's widgets:</p><p> We reimplement the mouse event "
+ "handlers to facilitate drawing, the paint event handler to "
+ "update the application and the resize event handler to optimize "
+ "the application's appearance. In addition we reimplement the "
+ "close event handler to intercept the close events before "
+ "terminating the application.</p><p> The example also demonstrates "
+ "how to use QPainter to draw an image in real time, as well as "
+ "to repaint widgets.</p>"));
+}
+//! [12]
+
+//! [13]
+void MainWindow::createActions()
+//! [13] //! [14]
+{
+ openAct = new QAction(tr("&Open..."), this);
+ openAct->setShortcuts(QKeySequence::Open);
+ connect(openAct, SIGNAL(triggered()), this, SLOT(open()));
+
+ const QByteArrayList formats = QImageWriter::supportedImageFormats();
+ for (const QByteArray &format : formats) {
+ QString text = tr("%1...").arg(QString(format).toUpper());
+
+ QAction *action = new QAction(text, this);
+ action->setData(format);
+ connect(action, SIGNAL(triggered()), this, SLOT(save()));
+ saveAsActs.append(action);
+ }
+
+ printAct = new QAction(tr("&Print..."), this);
+ connect(printAct, SIGNAL(triggered()), scribbleArea, SLOT(print()));
+
+ exitAct = new QAction(tr("E&xit"), this);
+ exitAct->setShortcuts(QKeySequence::Quit);
+ connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
+
+ penColorAct = new QAction(tr("&Pen Color..."), this);
+ connect(penColorAct, SIGNAL(triggered()), this, SLOT(penColor()));
+
+ penWidthAct = new QAction(tr("Pen &Width..."), this);
+ connect(penWidthAct, SIGNAL(triggered()), this, SLOT(penWidth()));
+
+ clearScreenAct = new QAction(tr("&Clear Screen"), this);
+ clearScreenAct->setShortcut(tr("Ctrl+L"));
+ connect(clearScreenAct, SIGNAL(triggered()),
+ scribbleArea, SLOT(clearImage()));
+
+ aboutAct = new QAction(tr("&About"), this);
+ connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));
+
+ aboutQtAct = new QAction(tr("About &Qt"), this);
+ connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
+}
+//! [14]
+
+//! [15]
+void MainWindow::createMenus()
+//! [15] //! [16]
+{
+ saveAsMenu = new QMenu(tr("&Save As"), this);
+ saveAsMenu->addActions(saveAsActs);
+
+ fileMenu = new QMenu(tr("&File"), this);
+ fileMenu->addAction(openAct);
+ fileMenu->addMenu(saveAsMenu);
+ fileMenu->addAction(printAct);
+ fileMenu->addSeparator();
+ fileMenu->addAction(exitAct);
+
+ optionMenu = new QMenu(tr("&Options"), this);
+ optionMenu->addAction(penColorAct);
+ optionMenu->addAction(penWidthAct);
+ optionMenu->addSeparator();
+ optionMenu->addAction(clearScreenAct);
+
+ helpMenu = new QMenu(tr("&Help"), this);
+ helpMenu->addAction(aboutAct);
+ helpMenu->addAction(aboutQtAct);
+
+ menuBar()->addMenu(fileMenu);
+ menuBar()->addMenu(optionMenu);
+ menuBar()->addMenu(helpMenu);
+}
+//! [16]
+
+//! [17]
+bool MainWindow::maybeSave()
+//! [17] //! [18]
+{
+ if (scribbleArea->isModified()) {
+ QMessageBox::StandardButton ret;
+ ret = QMessageBox::warning(this, tr("Scribble"),
+ tr("The image has been modified.\n"
+ "Do you want to save your changes?"),
+ QMessageBox::Save | QMessageBox::Discard
+ | QMessageBox::Cancel);
+ if (ret == QMessageBox::Save) {
+ return saveFile("png");
+ } else if (ret == QMessageBox::Cancel) {
+ return false;
+ }
+ }
+ return true;
+}
+//! [18]
+
+//! [19]
+bool MainWindow::saveFile(const QByteArray &fileFormat)
+//! [19] //! [20]
+{
+ QString initialPath = QDir::currentPath() + "/untitled." + fileFormat;
+
+ QString fileName = QFileDialog::getSaveFileName(this, tr("Save As"),
+ initialPath,
+ tr("%1 Files (*.%2);;All Files (*)")
+ .arg(QString(fileFormat.toUpper()))
+ .arg(QString(fileFormat)));
+ if (fileName.isEmpty()) {
+ return false;
+ } else {
+ return scribbleArea->saveImage(fileName, fileFormat);
+ }
+}
+//! [20]
diff --git a/src/qdoc/qdoc/doc/examples/minimum.qdocconf b/src/qdoc/qdoc/doc/examples/minimum.qdocconf
new file mode 100644
index 000000000..fca0cd757
--- /dev/null
+++ b/src/qdoc/qdoc/doc/examples/minimum.qdocconf
@@ -0,0 +1,46 @@
+# QDoc is a tool that constantly evolves and there may be compatibility issues
+# between old and new practices. For that reason, QDoc configuration files in
+# the Qt Project includes compat.qdocconf:
+#include(compat.qdocconf)
+
+# Give the documentation project a title:
+project = My documentation project
+
+# Pass additional include paths to QDoc when parsing C++ code for documentation
+# comments.
+#includepaths += -I/some/path
+
+# QDoc needs a lists of file extensions to know which files to process in
+# different situations. Uncomment the following include statement to get
+# a pre-defined list of file extensions.
+#include(fileextensions.qdocconf)
+
+# You can also specify file extensions manually.
+headers.fileextensions = "*.h *.hpp"
+sources.fileextensions = "*.cpp *.qml *.qdoc"
+
+# The outputdir variable specifies the directory where QDoc places the generated
+# documentation.
+outputdir = public
+
+# The headerdirs variable specifies the directories that contain the header
+# files associated with the .cpp source files used in the documentation.
+headerdirs = .
+
+# The sourcedirs variable specifies the directories that contain the .cpp or
+# .qdoc files used in the documentation.
+sourcedirs = .
+
+# The exampledirs variable specifies the directories that contain the source
+# code of the example files.
+exampledirs = ./examples
+
+# The imagedirs variable specifies the directories that contain images used in
+# the documentation.
+imagedirs = ./images
+
+# Set a warning limit. QDoc will exit with a non-zero exit code if it generates
+# documentation warnings during the documentation build. Useful for tracking
+# down documentation issues.
+#warninglimit = 0
+#warninglimit.enabled = true
diff --git a/src/qdoc/qdoc/doc/examples/objectmodel.qdocinc b/src/qdoc/qdoc/doc/examples/objectmodel.qdocinc
new file mode 100644
index 000000000..02b5991c4
--- /dev/null
+++ b/src/qdoc/qdoc/doc/examples/objectmodel.qdocinc
@@ -0,0 +1,11 @@
+\section1 Qt Object Model
+
+The standard C++ object model provides very efficient runtime support
+for the object paradigm. But its static nature is inflexibile in
+certain problem domains. Graphical user interface programming is a
+domain that requires both runtime efficiency and a high level of
+flexibility. Qt provides this, by combining the speed of C++ with the
+flexibility of the Qt Object Model.
+
+...
+
diff --git a/src/qdoc/qdoc/doc/examples/qml.qdoc.sample b/src/qdoc/qdoc/doc/examples/qml.qdoc.sample
new file mode 100644
index 000000000..e2c1277d2
--- /dev/null
+++ b/src/qdoc/qdoc/doc/examples/qml.qdoc.sample
@@ -0,0 +1,90 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+//![qmltype]
+ \qmltype TextEdit
+ \nativetype QQuickTextEdit
+ \inqmlmodule QtQuick
+ \ingroup qtquick-visual
+ \ingroup qtquick-input
+ \inherits Item
+ \brief Displays multiple lines of editable formatted text
+
+ The TextEdit item displays a block of editable, formatted text.
+
+ It can display both plain and rich text. For example:
+
+ \qml
+ TextEdit {
+ width: 240
+ text: "<b>Hello</b> <i>World!</i>"
+ font.family: "Helvetica"
+ font.pointSize: 20
+ color: "blue"
+ focus: true
+ }
+ \endqml
+
+ \image declarative-textedit.gif
+
+ ... omitted detailed description
+
+ \sa Text, TextInput, {examples/quick/text/textselection}{Text Selection example}
+//![qmltype]
+
+//![function]
+/*
+ \qmlmethod QtQuick2::ListModel::remove(int index, int count = 1)
+
+ Deletes the content at \a index from the model.
+
+ \sa clear()
+*/
+void QQuickListModel::remove(QQmlV8Function *args)
+//! [function]
+
+//! [return]
+/*
+ Returns \c true if a QScroller object was already created for \a target; \c false otherwise.
+
+ \sa scroller()
+*/
+bool QScroller::hasScroller(QObject *target)
+//! [return]
+
+//! [property]
+/*
+ \property QVariantAnimation::duration
+ \brief the duration of the animation
+
+ This property describes the duration in milliseconds of the
+ animation. The default duration is 250 milliseconds.
+
+ \sa QAbstractAnimation::duration()
+ */
+int QVariantAnimation::duration() const
+//! [property]
+
+//! [signals]
+/*
+ This signal is emitted when the user clicks the button. A click is defined
+ as a press followed by a release. The corresponding handler is
+ \c onClicked.
+*/
+signal clicked()
+//! [signals]
+
+//! [enums]
+/*!
+\qmlproperty enumeration QtQuick2::Text::font.weight
+
+Sets the font's weight.
+
+The weight can be one of:
+\value Font.Light
+\value Font.Normal The default
+\value Font.DemiBold
+\value Font.Bold
+\value Font.Black
+
+//! [enums]
diff --git a/src/qdoc/qdoc/doc/examples/samples.qdocinc b/src/qdoc/qdoc/doc/examples/samples.qdocinc
new file mode 100644
index 000000000..ea257852f
--- /dev/null
+++ b/src/qdoc/qdoc/doc/examples/samples.qdocinc
@@ -0,0 +1,85 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+//! [qvector3d-class]
+/*!
+ \class QVector3D
+ \brief The QVector3D class represents a vector or vertex in 3D space.
+ \since 4.6
+ \ingroup painting-3D
+
+ Vectors are one of the main building blocks of 3D representation and
+ drawing. They consist of three coordinates, traditionally called
+ x, y, and z.
+
+ The QVector3D class can also be used to represent vertices in 3D space.
+ We therefore do not need to provide a separate vertex class.
+
+ \note By design values in the QVector3D instance are stored as \c float.
+ This means that on platforms where the \c qreal arguments to QVector3D
+ functions are represented by \c double values, it is possible to
+ lose precision.
+
+ \sa QVector2D, QVector4D, QQuaternion
+*/
+//! [qvector3d-class]
+
+//! [qvector3d-function]
+/*!
+ \fn QVector3D::QVector3D(const QPoint& point)
+
+ Constructs a vector with x and y coordinates from a 2D \a point, and a
+ z coordinate of 0.
+*/
+//! [qvector3d-function]
+
+//! [sample-page]
+/*!
+ \page generic-guide.html
+ \title Generic QDoc Guide
+ \nextpage Creating QDoc Configuration Files
+ There are three essential materials for generating documentation with QDoc:
+
+ \list
+ \li \c QDoc binary (\c {qdoc})
+ \li \c qdocconf configuration files
+ \li \c Documentation in \c C++, \c QML, and \c .qdoc files
+ \endlist
+*/
+//! [sample-page]
+
+//! [sample-faq]
+/*!
+ \page altruism-faq.html
+ \title Altruism Frequently Asked Questions
+
+ \brief All the questions about altruism, answered.
+
+ ...
+*/
+//! [sample-faq]
+
+//! [sample-example]
+/*!
+ \title UI Components: Tab Widget Example
+ \example declarative/ui-components/tabwidget
+
+ This example shows how to create a tab widget. It also demonstrates how
+ \l {Property aliases}{property aliases} and
+ \l {Introduction to the QML Language#Default Properties}{default properties} can be used to collect and
+ assemble the child items declared within an \l Item.
+
+ \image qml-tabwidget-example.png
+*/
+//! [sample-example]
+
+//! [sample-overview]
+/*!
+ \page overview-qt-technology.html
+ \title Overview of a Qt Technology
+
+ \brief provides a technology never seen before.
+
+*/
+//! [sample-overview]
+
diff --git a/src/qdoc/qdoc/doc/examples/signalandslots.qdocinc b/src/qdoc/qdoc/doc/examples/signalandslots.qdocinc
new file mode 100644
index 000000000..e14ede144
--- /dev/null
+++ b/src/qdoc/qdoc/doc/examples/signalandslots.qdocinc
@@ -0,0 +1,9 @@
+\section1 Signals and Slots
+
+Signals and slots are used for communication between objects. The signals and
+slots mechanism is a central feature of Qt and probably the part that differs
+most from the features provided by other frameworks.
+
+\section2 Introduction
+
+In GUI programming, when we ...
diff --git a/src/qdoc/qdoc/doc/files/basicqt.qdoc.sample b/src/qdoc/qdoc/doc/files/basicqt.qdoc.sample
new file mode 100644
index 000000000..1243387b2
--- /dev/null
+++ b/src/qdoc/qdoc/doc/files/basicqt.qdoc.sample
@@ -0,0 +1,64 @@
+ /*!
+ \page basicqt.html
+ \nextpage Getting Started
+
+ \indexpage Index
+ \startpage Basic Qt
+
+ \title Basic Qt
+
+ The Qt toolkit is a C++ class library and a set of tools for
+ building multiplatform GUI programs using a "write once,
+ compile anywhere approach".
+
+ Table of contents:
+
+ \list
+ \li \l {Getting Started}
+ \li \l {Creating Dialogs}
+ \li \l {Creating Main Windows}
+ \endlist
+ */
+
+ /*!
+ \page gettingstarted.html
+ \previouspage Basic Qt
+ \nextpage Creating Dialogs
+
+ \indexpage Index
+ \startpage Basic Qt
+
+ \title Getting Started
+
+ This chapter shows how to combine basic C++ with the
+ functionality provided by Qt to create a few small graphical
+ interface (GUI) applications.
+*/
+
+/ *!
+ \page creatingdialogs.html
+ \previouspage Getting Started
+
+ \indexpage Index
+ \startpage Basic Qt
+
+ \title Creating Dialogs
+
+ This chapter will teach you how to create dialog boxes using Qt.
+*/
+
+/*!
+ \page index.html
+
+ \indexpage Index
+ \startpage Basic Qt
+
+ \title Index
+
+ \list
+ \li \l {Basic Qt}
+ \li \l {Creating Dialogs}
+ \li \l {Getting Started}
+ \endlist
+*/
+
diff --git a/src/qdoc/qdoc/doc/files/compat.qdocconf b/src/qdoc/qdoc/doc/files/compat.qdocconf
new file mode 100644
index 000000000..94e2ffd7d
--- /dev/null
+++ b/src/qdoc/qdoc/doc/files/compat.qdocconf
@@ -0,0 +1,10 @@
+macro.0 = "\\\\0"
+macro.b = "\\\\b"
+macro.n = "\\\\n"
+macro.r = "\\\\r"
+macro.img = "\\image"
+macro.endquote = "\\endquotation"
+macro.relatesto = "\\relates"
+
+spurious = "Missing comma in .*" \
+ "Missing pattern .*"
diff --git a/src/qdoc/qdoc/doc/files/qtgui.qdocconf b/src/qdoc/qdoc/doc/files/qtgui.qdocconf
new file mode 100644
index 000000000..ae873e83d
--- /dev/null
+++ b/src/qdoc/qdoc/doc/files/qtgui.qdocconf
@@ -0,0 +1,45 @@
+include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf)
+
+project = QtGui
+description = Qt GUI Reference Documentation
+version = $QT_VERSION
+
+examplesinstallpath = gui
+
+qhp.projects = QtGui
+
+qhp.QtGui.file = qtgui.qhp
+qhp.QtGui.namespace = org.qt-project.qtgui.$QT_VERSION_TAG
+qhp.QtGui.virtualFolder = qtgui
+qhp.QtGui.indexTitle = Qt GUI
+qhp.QtGui.indexRoot =
+
+qhp.QtGui.subprojects = classes
+qhp.QtGui.subprojects.classes.title = C++ Classes
+qhp.QtGui.subprojects.classes.indexTitle = Qt GUI C++ Classes
+qhp.QtGui.subprojects.classes.selectors = class fake:headerfile
+qhp.QtGui.subprojects.classes.sortPages = true
+
+tagfile = ../../../doc/qtgui/qtgui.tags
+
+depends += \
+ qtcore \
+ qtnetwork \
+ qtopengl \
+ qtsvg \
+ qtqml \
+ qtquick \
+ qtwidgets \
+ qtdoc
+
+headerdirs += ..
+
+sourcedirs += .. \
+ ../../../examples/gui/doc/src
+
+exampledirs += ../../../examples/gui \
+ snippets
+
+imagedirs += images \
+ ../../../examples/gui/doc/images \
+ ../../../doc/src/images \
diff --git a/src/qdoc/qdoc/doc/images/happyguy.jpg b/src/qdoc/qdoc/doc/images/happyguy.jpg
new file mode 100644
index 000000000..e8604793c
--- /dev/null
+++ b/src/qdoc/qdoc/doc/images/happyguy.jpg
Binary files differ
diff --git a/src/qdoc/qdoc/doc/images/link-to-qquickitem.png b/src/qdoc/qdoc/doc/images/link-to-qquickitem.png
new file mode 100644
index 000000000..00e03c371
--- /dev/null
+++ b/src/qdoc/qdoc/doc/images/link-to-qquickitem.png
Binary files differ
diff --git a/src/qdoc/qdoc/doc/images/links-to-broken-links.png b/src/qdoc/qdoc/doc/images/links-to-broken-links.png
new file mode 100644
index 000000000..775143bd4
--- /dev/null
+++ b/src/qdoc/qdoc/doc/images/links-to-broken-links.png
Binary files differ
diff --git a/src/qdoc/qdoc/doc/images/links-to-links.png b/src/qdoc/qdoc/doc/images/links-to-links.png
new file mode 100644
index 000000000..9d2cc2fae
--- /dev/null
+++ b/src/qdoc/qdoc/doc/images/links-to-links.png
Binary files differ
diff --git a/src/qdoc/qdoc/doc/images/qt-logo.png b/src/qdoc/qdoc/doc/images/qt-logo.png
new file mode 100644
index 000000000..835f5a3fe
--- /dev/null
+++ b/src/qdoc/qdoc/doc/images/qt-logo.png
Binary files differ
diff --git a/src/qdoc/qdoc/doc/images/training.jpg b/src/qdoc/qdoc/doc/images/training.jpg
new file mode 100644
index 000000000..c2ce5c3b2
--- /dev/null
+++ b/src/qdoc/qdoc/doc/images/training.jpg
Binary files differ
diff --git a/src/qdoc/qdoc/doc/images/windows-pushbutton.png b/src/qdoc/qdoc/doc/images/windows-pushbutton.png
new file mode 100644
index 000000000..14528d680
--- /dev/null
+++ b/src/qdoc/qdoc/doc/images/windows-pushbutton.png
Binary files differ
diff --git a/src/qdoc/qdoc/doc/images/windows-toolbutton.png b/src/qdoc/qdoc/doc/images/windows-toolbutton.png
new file mode 100644
index 000000000..9ceb846ed
--- /dev/null
+++ b/src/qdoc/qdoc/doc/images/windows-toolbutton.png
Binary files differ
diff --git a/src/qdoc/qdoc/doc/qdoc-guide/qdoc-guide.qdoc b/src/qdoc/qdoc/doc/qdoc-guide/qdoc-guide.qdoc
new file mode 100644
index 000000000..ff9ac808c
--- /dev/null
+++ b/src/qdoc/qdoc/doc/qdoc-guide/qdoc-guide.qdoc
@@ -0,0 +1,670 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+/*!
+ \page qdoc-guide.html
+ \title Getting Started with QDoc
+ \nextpage Installing Clang for QDoc
+
+ Qt uses QDoc to generate its documentation set into HTML and DocBook XML
+ formats. QDoc uses a set of configuration files to generate documentation
+ from QDoc comments. The comments have types called
+ \l{writing-topic-commands}{topics} that determine whether a comment is a
+ class documentation or a property documentation. A comment may also have
+ \l{writing-markup}{mark up} to enhance the layout and formatting of the
+ final output.
+
+ There are three essential materials for generating documentation with QDoc:
+ \list
+ \li \c QDoc binary
+ \li \c qdocconf configuration files
+ \li \c Documentation in \c C++, \c QML, and \c .qdoc files
+ \endlist
+
+ \note \l{QDoc Manual}{QDoc} requires \l{http://clang.llvm.org}{Clang}
+ for parsing C++ header and source files, and for parsing the function
+ signatures in \l {fn-command} {\\fn} commands. See \l {Installing Clang for QDoc}
+ for details.
+
+ This section intends to cover the basic necessities for creating a
+ documentation set. Additionally, the guide presents special considerations
+ and options to documenting non-C++ API documentation as well as QML
+ documentation. Finally, the guide will provide a sample project
+ documentation and an example of a QML type documentation.
+
+ For specific QDoc information, consult the
+ \l{QDoc Manual}.
+ \section1 Chapters
+
+ \list 1
+ \li \l{Installing Clang for QDoc}
+ \li \l{Creating QDoc Configuration Files}
+ \li \l{Writing Documentation}
+ \li \l{Categories of Documentation}
+ \list
+ \li \l{C++ Documentation Style}
+ \li \l{QML Documentation Style}
+ \endlist
+ \li \l{How to Resolve QDoc Warnings}
+ \endlist
+
+*/
+
+/*!
+ \page qdoc-guide-conf.html
+ \title Creating QDoc Configuration Files
+ \previouspage Installing Clang for QDoc
+ \nextpage Writing Documentation
+ To generate documentation, QDoc uses configuration files, with the
+ \c qdocconf extension, to store configuration settings.
+
+ The \l{The QDoc Configuration File} article covers the various configuration
+ variables in greater detail.
+
+ \section1 QDoc Configuration Files
+ QDoc's configuration settings can reside in a single \e qdocconf file, but
+ can also be in other qdocconf files. The \c {include(<filepath>)} command
+ allows configuration files to include other configuration files.
+
+ QDoc has two outputs, HTML documentation and documentation in DocBook XML
+ format. The main distinction between the two outputs is that HTML
+ documentation needs to have its HTML styling information in the
+ configuration files. DocBook documentation does not, and a separate process
+ can style the documentation in DocBook at a later time. DocBook therefore
+ more flexible in allowing different styles to apply to the same information.
+
+ To run QDoc, the project configuration file is supplied as an argument.
+ \code
+ qdoc project.qdocconf
+ \endcode
+
+ The project configuration contains information that QDoc uses to create the
+ documentation.
+
+ \section2 Project Information
+
+ QDoc uses the \c project information to generate the documentation.
+ \code
+ project = QDoc Project
+ description = Sample QDoc project
+ \endcode
+
+ \target qdoc-input-output-dir
+ \section2 Input and Output Directories
+
+ Specifying the path to the source directories allow QDoc to find sources and
+ generate documentation.
+
+ \badcode
+ sourcedirs = <path to source code>
+ exampledirs = <path to examples directory>
+ imagedirs = <path to image directory>
+
+ sources.fileextensions = "*.cpp *.qdoc *.mm *.qml"
+ headers.fileextensions = "*.h *.ch *.h++ *.hh *.hpp *.hxx"
+ examples.fileextensions = "*.cpp *.h *.js *.xq *.svg *.xml *.ui *.qhp *.qhcp *.qml"
+ examples.imageextensions = "*.png *.jpeg *.jpg *.gif *.mng"
+ \endcode
+
+ QDoc will process headers and sources from the ones specified in the
+ \c fileextensions variable.
+
+ Likewise, QDoc needs the path to the output directory. The \c outputformats
+ variable determines the type of documentation. These variables should be
+ in separate configuration files to modularize the documentation build.
+ \badcode
+ outputdir = $SAMPLE_PROJECT/doc/html
+ outputformats = HTML
+ \endcode
+
+ QDoc can resolve the paths relative to the qdocconf file as well as
+ environment variables.
+
+ \note During each QDoc run, the output directory is deleted.
+ \section2 Extra Files
+
+ QDoc will output generated documentation into the directory specified in
+ the \l{Input and Output Directories}{output} directory. It is also possible
+ to specify extra files that QDoc should export.
+
+ \badcode
+ HTML.extraimages = extraImage.png \
+ extraImage2.png
+ \endcode
+
+ The \c extraImage.png and the \c extraImage2.png files will be copied to the
+ HTML output directory.
+
+ \section2 Qt Help Framework Configuration
+
+ QDoc will also export a \e {Qt Help Project} file, in a \c qhp file.
+ The qhp file is then used by the \c qhelpgenerator to package the
+ documentation into a \c qch file. Qt Creator and Qt Assistant reads the qch
+ file to display the documentation.
+
+ The \l {Creating Help Project Files} article covers the configuration
+ options.
+
+ \section2 HTML Configuration
+
+ QDoc has an HTML generator that will export a set of documentation into
+ HTML files using various configuration settings. QDoc will place the
+ generated documentation into the directory specified by the \c outputdir
+ variable.
+
+ \badcode
+ outputformats = HTML
+ outputdir = <path to output directory>
+ \endcode
+
+ QDoc needs to know where the styles and templates for generating HTML
+ are located. Typically, the templates directory contains a \c scripts,
+ \c images, and a \c style directory, containing scripts and CSS files.
+
+ The main configuration variables are:
+ \badcode
+ HTML.postheader
+ HTML.postpostheader
+ HTML.postheader
+ HTML.footer
+
+ HTML.headerstyles
+ HTML.stylesheets = template/style/style.css \
+ template/style/style1.css
+
+ HTML.scripts = template/scripts/script.js
+ \endcode
+
+ The \c{HTML.headerstyles} variable inserts the style information into the
+ HTML file and the \c{HTML.stylesheets} specifies which files QDoc should
+ copy into the output directory. In addition, QDoc will embed the string
+ in the \c postheader, \c footer, and related variables into each HTML file.
+
+ The \l {Format-specific Configuration Variables} article outlines the usage
+ of each variable.
+
+ \section2 QDoc Index Files
+
+ Documentation projects can link to targets in other projects by specifying
+ a set of dependencies, or a set of direct paths to index file(s) this
+ project depends on. When QDoc generates documentation for a project,
+ it will also generate an \c .index file containing URLs to each linkable
+ entity in the project. Other projects can then define a dependency to the
+ index file in order to link to the documentation within that project.
+
+ \b {See also}: \l {depends-variable}{depends} and
+ \l {indexes-variable}{indexes}.
+
+ \section1 Macros and Other Configurations
+
+ Macros for substituting HTML characters exist and are helpful for generating
+ specific HTML-valid characters.
+
+ \badcode
+ macro.pi.HTML = "&Pi;"
+ \endcode
+ The snippet code will replace any instances of \c{\\pi} with \c &Pi; in the
+ HTML file, which will appear as the Greek \pi symbol when viewed in
+ browsers.
+
+ \b {See also:} \l {macro-variable}{macro}.
+
+ \section2 QML Additions
+
+ QDoc is able to parse QML files for QDoc comments. QDoc will parse files
+ with the QML extension, \c{.qml}, if the extension type is included in the
+ \l{Input and Output Directories}{fileextensions} variable.
+
+ Also, the generated HTML files can have a prefix and a suffix following the
+ QML module name, specified in the QDoc configuration file.
+ \badcode
+ outputprefixes = QML
+ outputprefixes.QML = uicomponents-
+ outputsuffixes = QML
+ outputsuffixes.QML = -tp
+ \endcode
+
+ \b {See also}: \l {outputprefixes-variable}{outputprefixes},
+ \l {outputsuffixes-variable}{outputsuffixes}.
+
+*/
+
+/*!
+ \page qdoc-guide-writing.html
+ \title Writing Documentation
+ \previouspage Creating QDoc Configuration Files
+ \nextpage Categories of Documentation
+
+ \section1 QDoc Comments
+
+ Documentation is contained within QDoc \e comments, delimited by
+ \beginqdoc and \endqdoc comments. Note that these are valid comments
+ in C++ and QML.
+
+ Within a QDoc comment, \c {//!} is used as a single-line documentation
+ comment; the comment itself and anything after it, until a newline,
+ is omitted from the generated output.
+
+ QDoc will parse C++ and QML files to look for QDoc comments. To explicitly
+ omit a certain file type, omit it from the
+ \l{Input and Output Directories}{configuration} file.
+
+ \section1 QDoc Commands
+
+ QDoc uses \e commands to retrieve information about the documentation. \c
+ Topic commands determine the type of documentation element, the \c context
+ commands provide hints and information about a topic, and \c markup commands
+ provide information on how QDoc should format a piece of documentation.
+
+ \target writing-topic-commands
+ \section2 QDoc Topics
+ Each QDoc comment must have a \e topic type. A topic distinguishes it from
+ other topics. To specify a topic type, use one of the several
+ \l{Topic Commands}{topic commands}.
+
+ QDoc will collect similar topics and create a page for each one. For
+ example, all the enumerations, properties, functions, and class description
+ of a particular C++ class will reside in one page. A generic page is
+ specified using the \l{page-command}{\\page} command and the filename is the
+ argument.
+
+ Example of topic commands:
+ \list
+ \li \l{enum-command}{\\enum} - for enumeration documentation
+ \li \l{class-command}{\\class} - for C++ class documentation
+ \li \l{qmltype-command}{\\qmltype} - for QML type documentation
+ \li \l{page-command}{\\page} - for creating a page.
+ \endlist
+
+ A QDoc comment can contain multiple topic commands in the same category, with
+ some restrictions. This way, it's possible to write a single comment that
+ documents all overloads of a function (using multiple \l {fn-command}{\\fn}
+ commands), or all properties in a QML property group (using
+ \l {qmlproperty-command}{\\qmlproperty} commands) in one go.
+
+ If a QDoc comment contains multiple topic commands, it's possible to
+ provide additional \e {context commands} for individual topics in
+ follow-up comments:
+
+ \badcode *
+ /\1!
+ \qmlproperty string Type::element.name
+ \qmlproperty int Type::element.id
+
+ \brief Holds the element name and id.
+ \1/
+
+ /\1!
+ \qmlproperty int Type::element.id
+ \readonly
+ \1/
+ \endcode
+
+ Here, the follow-up comment marks the \e element.id property as read-only,
+ while \e element.name remains writable.
+
+ \note A follow-up comment cannot contain any additional text, only
+ \l {writing-context}{context commands} that document the context of
+ the item.
+
+ The \l{page-command}{\\page} command is for creating articles that are not
+ part of source documentation. The command can also accept two arguments: the
+ file name of the article and the documentation type. The possible types are:
+ \list
+ \li \c howto
+ \li \c overview
+ \li \c tutorial
+ \li \c faq
+ \li \c attribution - used for documenting license attributions
+ \li \c article - \e default when there is no type
+ \endlist
+
+ \snippet examples/samples.qdocinc sample-faq
+
+ The \l{Topic Commands} page has information on all of the available topic
+ commands.
+
+ \target writing-context
+ \section2 Topic Contexts
+
+ Context commands give QDoc a hint about the \e context of the topic. For
+ example, if a C++ function is deprecated, then it should be marked as such
+ with the \l{deprecated-command}{\\deprecated} command. Likewise,
+ \l{nextpage-command}{page navigation} and \l{title-command}{page title}
+ give extra page information to QDoc.
+
+ QDoc will create additional links or pages for these contexts. For example,
+ a group is created using the \l{group-command}{\\group} command and the
+ members have the \l{ingroup-command}{\\ingroup} command. The group name is
+ supplied as an argument.
+
+ The \l{Context Commands} page has a listing of all the available context
+ commands.
+
+ \target writing-markup
+ \section2 Documentation Markup
+
+ QDoc can do \e markup of text similar to other markup or
+ documentation tools. QDoc can mark a section of text in \b{bold},
+ when the text is marked up with the \l{b-command}{\\b} command.
+
+ \code
+ \b{This} text will be in \b{bold}.
+ \endcode
+
+ The \l{Markup Commands} page has a full listing of the available markup
+ commands.
+
+ \section1 Anatomy of Documentation
+
+ Essentially, for QDoc to create a page, there must be some essential
+ ingredients present.
+
+ \list
+ \li Assign a topic to a QDoc comment - A comment could be a page, a
+ property documentation, a class documentation, or any of the available
+ \l{Topic Commands}{topic commands}.
+
+ \li Give the topic a context - QDoc can associate certain topics to other
+ pages such as associating deprecated functions when the documentation is
+ marked with \l{deprecated-command}{\\deprecated}.
+
+ \li Mark sections of the document with
+ \l{Markup Commands}{markup commands} - QDoc can create layouts and
+ format the documentation for the documentation.
+ \endlist
+
+ In Qt, the \l{QVector3D} class was documented with the following QDoc
+ comment:
+ \snippet examples/samples.qdocinc qvector3d-class
+
+ It has a constructor, \l{QVector3D::QVector3D()}, which was documented with
+ the following QDoc comment:
+ \snippet examples/samples.qdocinc qvector3d-function
+
+ The different comments may reside in different files and QDoc will collect
+ them depending on their topic and their context. The resulting documentation
+ from the snippets are generated into the \l{QVector3D} class documentation.
+
+ Note that if the documentation immediately precedes the function or class
+ in the source code, then it does not need to have a topic. QDoc will assume
+ that the documentation above the code is the documentation for that code.
+
+ An article is created using \l{page-command}{\\page} command. The first
+ argument is the HTML file that QDoc will create. The topic is supplemented
+ with context commands, the \l{title-command}{\\title} and
+ \l{nextpage-command}{\\nextpage} commands. There are several other
+ QDoc commands such as the \l{list-command}{\\list} command.
+ \snippet examples/samples.qdocinc sample-page
+
+ The section on \l{QDoc Topics}{topic commands} gives an overview on several
+ other topic types.
+
+
+*/
+
+/*!
+ \page qdoc-categories.html
+ \title Categories of Documentation
+ \previouspage Writing Documentation
+ \nextpage How to Resolve QDoc Warnings
+ \brief Describes the different types such as How-To's, Tutorials, Overviews,
+ Examples, and Class Documentation.
+
+ There are several types of predefined documentation \e categories or
+ \e types:
+ \list
+ \li How-To's
+ \li Tutorial
+ \li Overview
+ \li Article
+ \li FAQ (Frequently Asked Questions)
+ \li C++ API Documentation
+ \li QML Type Documentation
+ \li Code Example
+ \endlist
+
+ QDoc has the ability to format a page depending on the type. Further,
+ stylesheets can provide additional control on the display of each category.
+
+ \section1 API Documentation
+ QDoc excels in the creation of API documentation given a set of source code
+ and documentation in QDoc comments. Specifically, QDoc is aware of Qt's
+ architecture and can validate the existence of Qt C++ class, function, or
+ property documentation. QDoc gives warnings and errors if it cannot
+ associate a documentation with a code entity or if a code entity does not
+ have documentation.
+
+ In general, every Qt code entity such as properties, classes, methods,
+ signals, and enumerations have a corresponding
+ \l{qdoc-topics}{topic command}. QDoc will associate the documentation to the
+ source using C++ naming rules.
+
+ QDoc will parse the header files (typically \c .h files) to build a tree of
+ the class structures. Then QDoc will parse the source files and
+ documentation files to attach documentation to the class structure.
+ Afterwards, QDoc will generate a page for the class.
+
+ \note QDoc uses the header files to inform itself about the class and will
+ not properly process QDoc comments in header files.
+
+ \section2 Language Styles
+
+ To produce quality API documentation, the Qt API references follow a
+ particular language guidelines. While the contents of this page demonstrates
+ how to create API documentation, the style guidelines demonstrate how
+ the reference materials follow a consistent use of language.
+
+ \list
+ \li \l{C++ Documentation Style}
+ \li \l{QML Documentation Style}
+ \endlist
+
+ \keyword qml-documentation
+ \section2 Documenting QML Types
+
+ In the world of \l{Qt Quick}{QML}, there are additional entities we need to
+ document such as QML signals, attached properties, and QML methods.
+ Internally, they use Qt technologies, however, QML API documentation
+ requires different layout and naming conventions from the Qt C++ API
+ documentation.
+
+ A list of QML related QDoc commands:
+ \list
+ \li \l{qmlattachedproperty-command}{\\qmlattachedproperty}
+ \li \l{qmlattachedsignal-command}{\\qmlattachedsignal}
+ \li \l{qmlvaluetype-command}{\\qmlvaluetype}
+ \li \l{qmltype-command}{\\qmltype} - creates a QML type documentation
+ \li \l{qmlmethod-command}{\\qmlmethod}
+ \li \l{qmlproperty-command}{\\qmlproperty}
+ \li \l{qmlsignal-command}{\\qmlsignal}
+ \li \l{inherits-command}{\\inherits}
+ \li \l{qmlmodule-command}{\\qmlmodule}
+ \li \l{inqmlmodule-command}{\\inqmlmodule}
+ \li \l{nativetype-command}{\\nativetype}
+
+ \endlist
+
+ \note Remember to enable QML parsing by including the \c{*.qml} filetype in
+ the \l{qdoc-input-output-dir}{fileextension} variable.
+
+ To document a QML type, start by creating a QDoc comment that uses the
+ \l{qmltype-command} {\\qmltype} command as its topic command.
+
+ \section3 QML Parser
+
+ If your QML type is defined in a \e qml file, document it there.
+ If your QML type is represented by a C++ class, document it in the
+ \e cpp file for that C++ class and include an
+ \l{nativetype-command}{\\nativetype} command to specify the
+ name of the C++ class. Don't document a QML type in a \e{cpp} file
+ if the QML type is defined in a \e{qml} file.
+
+ When documenting a QML type in a \e{qml} file, place each QDoc
+ comment directly above the entity to which the comment applies.
+ For example, place the QDoc comment containing the \e{\\qmltype}
+ command (the topic comment) directly above the outer QML type in
+ the \e{qml} file. Place the comment for documenting a QML property
+ directly above the property declaration, and so on for QML signal
+ handlers and QML methods. Note that when documenting QML
+ properties in a \e{qml} file, you don't normally include the
+ \e{\\qmlproperty} command as a topic command (which you must do
+ when documenting QML types in \e{cpp} files), because the QML
+ parser automatically associates each QDoc comment with the next
+ QML declaration it parses. The same is true for QML signal handler
+ and QML method comments. But it is sometimes useful to include one
+ or more \e{\\qmlproperty} commands in the comment, e.g. when the
+ property type is another QML type and you want the user to only
+ use certain properties within that other QML type, but not all of
+ them. But when documenting a property that has an alias, place the
+ QDoc comment for it directly above the alias declaration. In these
+ cases, the QDoc comment \e must contain a \e{\\qmlproperty}
+ command, because that is the only way QDoc can know the type of
+ the aliased property.
+
+ When documenting a QML type in the \e cpp file of its
+ corresponding C++ class (if it has one), you normally place each
+ QDoc comment directly above the entity it documents. However, QDoc
+ does not use the QML parser to parse these files (the C++ parser
+ is used), so these QML QDoc comments can appear anywhere in the
+ \e{cpp} file. Note that QML QDoc comments in \e cpp files \e must
+ use the QML topic commands. i.e., the \l{qmltype-command}
+ {\\qmltype} command \e must appear in the QDoc comment for the
+ QML type, and a \l{qmlproperty-command} {\\qmlproperty} command \e
+ must appear in each QML property QDoc comment.
+
+ \section3 QML Modules
+
+ A QML type belongs to a \e module. The module
+ may include all the related types for a platform or contain a certain
+ version of \l{Qt Quick}. For example, the Qt Quick 2 QML types belong
+ to the Qt Quick 2 module while there is also a Qt Quick 1 module for the older
+ types introduced in Qt 4.
+
+ QML modules allow grouping QML types. The \l{qmltype-command}
+ {\\qmltype} topic command must have an \l{inqmlmodule-command}
+ {\\inqmlmodule} context command to relate the type to a QML
+ module. Similarly, a \l{qmlmodule-command}{\\qmlmodule} topic
+ command must exist in a separate \c{.qdoc} file to create the
+ overview page for the module. The overview page will list the
+ QML types of the QML module.
+
+ The links to the QML types must therefore also contain the module name.
+ For example, if a type called \c TabWidget is in the \c UIComponents
+ module, it must be linked as \c {UIComponents::TabWidget}.
+
+ \section3 Read-only and Internal QML Properties
+
+ QDoc detects QML properties that are marked as \c readonly. Note that the
+ property must be initialized with a value.
+
+ \badcode
+ readonly property int sampleReadOnlyProperty: 0
+ \endcode
+
+ Properties and signals that are not meant for the public interface may
+ be marked with the \l{internal-command}{\\internal} command. QDoc will not
+ publish the documentation in the generated outputs.
+
+ \section1 Articles & Overviews
+
+ Articles and overviews are a style of writing best used for providing
+ summary detail on a topic or concept. It may introduce a technology or
+ discuss how a concept may be applied, but without discussing exact steps
+ in too much detail. However, this type of content could provide the entry
+ point for readers to find instructional and reference materials that do,
+ such as tutorials, examples and class documentation. An example of an
+ overview might be a product page, such as a top level discussion of
+ Qt Quick, individual modules, design principles, or tools.
+
+ To signify that a document is an article, you append the article keyword
+ to the \\page command:
+
+ \snippet examples/samples.qdocinc sample-overview
+
+ The \l{writing-topic-commands}{writing topic commands} section has a listing
+ of the available \\page command arguments.
+
+ \section1 Tutorials, How-To's, FAQ's
+
+ Tutorials, How-To's, and FAQ's are all instructional material, in that they
+ instruct or prescribe to the reader. Tutorials are content designed to guide
+ the reader along a progressive learning path for a concept or technology.
+ How-To's and FAQ's (\e{Frequently Asked Questions}) provide guidance by
+ presenting material in the form of answers to commonly asked topics.
+ How-To's and FAQ's are designed for easy reference and are not necessarily
+ presented in a linear progression.
+
+ To create these types, mark the pages by providing a \c type argument to the
+ \l{page-command}{\\page} command. The \c type argument is the second
+ argument, with the file name being the first.
+ \snippet examples/samples.qdocinc sample-faq
+
+ The \l{writing-topic-commands}{writing topic commands} section has a listing
+ of the available \\page command arguments.
+
+ \section1 Code Examples
+ Examples are an effective way to demonstrate practical usage of a given
+ technology or concept. When it comes to middleware this is usually in the
+ form of an application using simple code and clear explanations of what the
+ code is doing. Any module, API, project, pattern etc. should have at least
+ one good example.
+
+ An example may have an accompanying tutorial. The tutorial instructs and
+ describes the code, while the code example is the code content that users
+ may study. Code examples may have accompanying text that are not in the
+ tutorial.
+
+ QDoc will create a page containing the example code with a description
+ using the \l{example-command}{\\example} command.
+
+ \snippet examples/samples.qdocinc sample-example
+
+ QDoc will use the directory specified in the input
+ \l{Input and Output Directories}{exampledirs} variable to find the Qt
+ Project (\c .pro) file to generate the example files. The generated HTML
+ will have the filename, \c {declarative-ui-components-tabwidget.html}. QDoc
+ will also list all of the example code.
+
+ \note The example's project file must be the same as the
+ directory name.
+*/
+
+
+/*!
+ \page qdoc-guide-clang.html
+ \title Installing Clang for QDoc
+ \previouspage Getting Started with QDoc
+ \nextpage Creating QDoc Configuration Files
+
+ QDoc uses Clang when parsing C++ files as well as function signatures in
+ \l {fn-command} {\\fn} commands. Clang is part of
+ \l {https://llvm.org/}{the LLVM Compiler Infrastructure Project}.
+ If you're going to build QDoc from source, you must install
+ \l{http://clang.llvm.org}{Clang 15.0} or later.
+
+ You can get Clang through various channels:
+
+ \list
+ \li Qt provides the
+ \l{http://download.qt.io/development_releases/prebuilt/libclang/qt}
+ {prebuilt Clang packages} that are used for the Qt binaries in the
+ online installer. These let you link LLVM/Clang libraries statically,
+ but only support Release builds on Windows.
+ \li Linux distributions often provide a package called \e libclang-dev or
+ \e libclang-devel. Qt's build system instructs CMake to look for
+ \e {ClangConfig.cmake}, so make sure you install the package that
+ provides this file if you want to build QDoc. Running QDoc requires
+ only \e libclang.
+ \li On macOS, you can also use Homebrew's
+ \l{https://formulae.brew.sh/formula/llvm}{llvm formula}.
+ \endlist
+
+ \note the prebuilt binaries from \l{http://releases.llvm.org/download.html}
+ cannot be used, as they miss certain components required by QDoc.
+
+ If you install Clang in a custom location you need to tell CMake where
+ to find it. This can be done by specifying your LLVM installation path
+ using the \c LLVM_INSTALL_DIR environment variable when configuring Qt.
+ Alternatively, you can add the installation path to the \c CMAKE_PREFIX_PATH
+ CMake cache variable.
+*/
diff --git a/src/qdoc/qdoc/doc/qdoc-guide/qtwritingstyle-cpp.qdoc b/src/qdoc/qdoc/doc/qdoc-guide/qtwritingstyle-cpp.qdoc
new file mode 100644
index 000000000..43387ca7e
--- /dev/null
+++ b/src/qdoc/qdoc/doc/qdoc-guide/qtwritingstyle-cpp.qdoc
@@ -0,0 +1,163 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+\page qtwritingstyle-cpp.html
+\title C++ Documentation Style
+\brief Style guidelines for C++ documentation
+
+To generate the documentation, QDoc goes through the source code and generates
+documentation for C++ types such as classes. QDoc then associates member
+functions, properties, and other types to the appropriate class.
+
+Note that the documentation must be in the implementation files such as \c .cpp.
+
+\section1 Class Documentation
+
+Class documentation is generated using the \l{class-command}{\\class} command and
+the name of the class as the first argument.
+
+\snippet examples/cpp.qdoc.sample class
+
+\l{Context commands} add information about the class, such as its module or
+which version the class was added.
+
+Some common context commands are:
+\list
+\li \l{brief-command}{\\brief} - the class' brief description \b (mandatory)
+\li \l{since-command}{\\since} - the version to which the class was added \b (mandatory)
+\li \l{internal-command}{\\internal} - marks the class as internal. Internal
+classes do not appear in the public API documentation.
+\endlist
+
+
+\section2 The Brief and Detailed Description
+
+The \e{brief description} is marked with the \l{brief-command}{\\brief} command
+and it is for summarizing the purpose or functionality of the class. For C++
+classes, QDoc will take the class and create annotated information for the
+class. The annotated information appears in lists and tables which display the
+class.
+
+The C++ brief should start with:
+\code
+"The <C++ class name> class"
+\endcode
+
+The \e{detailed description} section starts after the brief description. It
+provides more information about the class. The detailed description may contain
+images, snippet code, or links to other relevant documents. There
+must be an empty line which separates the brief and detailed description.
+
+\section1 Member Functions
+
+Typically, function documentation immediately precedes the implementation of the
+function in the \c .cpp file. For function documentation that is not immediately
+above the implementation, the \l{fn-command}{\\fn} is needed.
+
+\snippet examples/cpp.qdoc.sample function
+
+The function documentation starts with a verb, indicating the operation the
+function performs. This also applies to constructors and destructors.
+
+Some common verbs for function documentation:
+\list
+\li "Constructs..." - for constructors
+\li "Destroys..." - for destructors
+\li "Returns..." - for accessor functions
+\endlist
+
+The function documentation must document:
+\list
+\li the return type
+\li the parameters
+\li the actions of the functions
+\endlist
+
+The \l{a-command}{\\a} command marks the parameter in the documentation.
+The return type documentation should link to the type documentation or be
+marked with the \l{c-command}{\\c} command in the case of boolean values.
+
+\snippet examples/cpp.qdoc.sample return
+
+\section1 Properties
+
+The property documentation resides immediately above the read function's
+implementation. The \l{writing-topic-commands}{topic command} for properties is
+\l{property-command}{\\property}.
+
+\snippet examples/cpp.qdoc.sample property
+
+Property documentation usually starts with "This property...", but these are
+alternate expressions:
+\list
+\li "This property holds..."
+\li "This property describes..."
+\li "This property represents..."
+\li "Returns \c true when... and \c false when..." - for properties that
+are read.
+\li "Sets the..." - for properties that configure a type.
+\endlist
+
+Property documentation must include:
+\list
+\li description and behavior of the property
+\li accepted values for the property
+\li the default value of the property
+\endlist
+Similar to \l{Member Functions}{functions}, the default type may be linked
+or marked with the \c{\c} command.
+
+An example of a value range style is:
+\quotation
+The values range from 0.0 (no blur) to maximumRadius (maximum blur). By default, the property is set to 0.0 (no blur).
+\endquotation
+
+\section1 Signals, Notifiers, and Slots
+The \l{writing-topic-commands}{topic command} for signals, notifiers, and slots
+is \l{fn-command}{\\fn}. Signal documentation state when they are triggered
+or emitted.
+
+\snippet examples/cpp.qdoc.sample signals
+
+Signal documentation typically begin with "This signal is triggered when...".
+Here are alternate styles:
+\list
+\li "This signal is triggered when..."
+\li "Triggered when..."
+\li "Emitted when..."
+\endlist
+
+For slots or notifiers, the condition when they are executed or triggered by
+a signal should be documented.
+\list
+\li "Executed when..."
+\li "This slot is executed when..."
+\endlist
+
+For properties that have overloaded signals, QDoc groups the overloaded
+notifiers together. To refer to a specific version of a notifier or signal,
+simply refer to the property and mention that there are different versions of
+the notifier.
+
+\snippet examples/cpp.qdoc.sample overloaded notifier
+
+\section1 Enums, Namespaces, and Other Types
+
+Enums, namespaces, and macros have a \l{writing-topic-commands}{topic command} for their documentation:
+\list
+\li \l{enum-command}{\\enum}
+\li \l{typedef-command}{\\typedef}
+\li \l{macro-command}{\\macro}
+\endlist
+
+The language style for these types mention that they are an enum or a macro and
+continues with the type description.
+
+For enumerations, the \l{value-command}{\\value} command is for listing the
+values. QDoc creates a table of values for the enum.
+
+\snippet examples/cpp.qdoc.sample enums
+
+*/
+
diff --git a/src/qdoc/qdoc/doc/qdoc-guide/qtwritingstyle-qml.qdoc b/src/qdoc/qdoc/doc/qdoc-guide/qtwritingstyle-qml.qdoc
new file mode 100644
index 000000000..91d2094cb
--- /dev/null
+++ b/src/qdoc/qdoc/doc/qdoc-guide/qtwritingstyle-qml.qdoc
@@ -0,0 +1,142 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+\page qtwritingstyle-qml.html
+\title QML Documentation Style
+\brief Style guidelines for QML documentation
+
+QDoc can process QML types defined as C++ classes and QML types defined in
+\c .qml files. For C++ classes documented as QML types, the QDoc comments are
+in the \c .cpp file while QML types defined in QML are in the \c .qml
+file. The C++ classes must also be documented
+documented with the QML \l{topic-commands}{topic commands}:
+
+\list
+\li \l{qmlattachedproperty-command}{\\qmlattachedproperty}
+\li \l{qmlattachedsignal-command}{\\qmlattachedsignal}
+\li \l{qmlvaluetype-command}{\\qmlvaluetype}
+\li \l{qmltype-command}{\\qmltype}
+\li \l{qmlmethod-command}{\\qmlmethod}
+\li \l{qmlproperty-command}{\\qmlproperty}
+\li \l{qmlsignal-command}{\\qmlsignal}
+\li \l{qmlmodule-command}{\\qmlmodule}
+\li \l{inqmlmodule-command}{\\inqmlmodule}
+\li \l{nativetype-command}{\\nativetype}
+\endlist
+
+For QML types defined in \c .qml files, QDoc will parse the QML and determine
+the properties, signals, and the type within the QML definition. The QDoc
+block then needs to be immediately above the declaration. For QML types
+implemented in C++, QDoc will output warnings if the C++ class documentation
+does not exist. The class documentation may be marked as
+\l{internal-command}{internal} if it is not a public API.
+
+\section1 QML Types
+
+The \l{qmltype-command}{\\qmltype} command is for QML type documentation.
+
+\snippet examples/qml.qdoc.sample qmltype
+
+The \l{nativetype-command}{\\nativetype} command accepts the C++ class which
+implements the QML type as the argument. For types implemented in QML, this
+is not needed.
+
+The \e{brief description} provides a summary for the QML type. The brief does
+not need to be a complete sentence and may start with a verb. QDoc will append
+the brief description onto the QML type in tables and generated lists.
+
+\code
+\qmltype ColorAnimation
+\brief Animates changes in color values
+\endcode
+
+Here are some alternate verbs for the brief statement:
+\list
+\li "Provides..."
+\li "Specifies..."
+\li "Describes..."
+\endlist
+
+The \e{detailed description} follows the brief and may contain images, snippet,
+and link to other documentation.
+
+\section1 Properties
+
+The property description focuses on what the property \e does and may use the
+following style:
+
+Property documentation usually starts with "This property..." but for certain
+properties, these are the common expressions:
+\list
+\li "This property holds..."
+\li "This property describes..."
+\li "This property represents..."
+\li "Returns \c true when... and \c false when..." - for properties that
+are marked \c{read-only}.
+\li "Sets the..." - for properties that configure a type.
+\endlist
+
+\section1 Signals and Handlers Documentation
+
+QML signals are documented either in the QML file or in the C++ implementation
+with the \l{qmlsignal-command}{\\qmlsignal} command. Signal documentation
+must include the condition for emitting the signal, mention the corresponding
+signal handler, and document whether the signal accepts a parameter.
+
+\snippet examples/qml.qdoc.sample signals
+
+These are the possible documentation styles for signals:
+\list
+\li "This signal is triggered when..."
+\li "Triggered when..."
+\li "Emitted when..."
+\endlist
+
+\section1 Methods and QML Functions
+
+Typically, function documentation immediately precedes the implementation of the
+function in the \c .cpp file. The \l{topic-commands}{topic command} for
+functions is \l{fn-command}{\\fn}. For functions in QML, the
+documentation must reside immediately above the function declaration.
+
+The function documentation starts with a verb, indicating the operation the
+function performs.
+
+\snippet examples/qml.qdoc.sample function
+
+Some common verbs for function documentation:
+\list
+\li "Copies..." - for constructors
+\li "Destroys..." - for destructors
+\li "Returns..." - for accessor functions
+\endlist
+
+The function documentation must document:
+\list
+\li the return type
+\li the parameters
+\li the actions of the functions
+\endlist
+
+The \l{a-command}{\\a} command marks the parameter in the documentation.
+The return type documentation should link to the type documentation or be
+marked with the \l{c-command}{\\c} command in the case of boolean values.
+
+\section1 Enumerations
+
+QML enumerations are documented as QML properties with the
+\l{qmlproperty-command}{\\qmlproperty} command. The type of the property
+is \c enumeration. Use the \l{value-command}{\\value} command to document
+the enum values. Add the type name as a prefix to each value, separated by
+a period (.), as QDoc does not do this automatically.
+
+\snippet examples/qml.qdoc.sample enums
+
+The QDoc comment lists the values of the enumeration. If the enumeration is
+implemented in C++, the documentation may link to the corresponding C++
+enumeration. However, the QDoc comment should advise that the enumeration
+is a C++ enumeration.
+
+*/
+
diff --git a/src/qdoc/qdoc/doc/qdoc-manual-cmdindex.qdoc b/src/qdoc/qdoc/doc/qdoc-manual-cmdindex.qdoc
new file mode 100644
index 000000000..773ec1cfb
--- /dev/null
+++ b/src/qdoc/qdoc/doc/qdoc-manual-cmdindex.qdoc
@@ -0,0 +1,143 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page 27-qdoc-commands-alphabetical.html
+ \previouspage Introduction to QDoc
+ \nextpage Topic Commands
+
+ \title Command Index
+
+ This is a complete, alphabetized list of the QDoc commands.
+
+ \list
+
+ \li \l {a-command} {\\a}
+ \li \l {abstract-command} {\\abstract}
+ \li \l {annotatedlist-command} {\\annotatedlist}
+ \li \l {attribution-command} {\\attribution}
+ \li \l {b-command} {\\b}
+ \li \l {badcode-command} {\\badcode}
+ \li \l {b-command} {\\bold} (deprecated, use \\b)
+ \li \l {br-command} {\\br}
+ \li \l {brief-command} {\\brief}
+ \li \l {c-command} {\\c}
+ \li \l {caption-command} {\\caption}
+ \li \l {class-command} {\\class}
+ \li \l {code-command} {\\code}
+ \li \l {codeline-command} {\\codeline}
+ \li \l {compares-command} {\\compares}
+ \li \l {compareswith-command} {\\compareswith}
+ \li \l {deprecated-command} {\\deprecated}
+ \li \l {default-command} {\\default}
+ \li \l {details-command} {\\details}
+ \li \l {div-command} {\\div}
+ \li \l {dots-command} {\\dots}
+ \li \l {e-command} {\\e}
+ \li \l {else-command} {\\else}
+ \li \l {endif-command} {\\endif}
+ \li \l {enum-command} {\\enum}
+ \li \l {example-command} {\\example}
+ \li \l {externalpage-command} {\\externalpage}
+ \li \l {fn-command} {\\fn}
+ \li \l {footnote-command} {\\footnote}
+ \li \l {generatelist-command} {\\generatelist}
+ \li \l {group-command} {\\group}
+ \li \l {header-command} {\\header}
+ \li \l {headerfile-command} {\\headerfile}
+ \li \l {e-command} {\\i} (deprecated, use \\e)
+ \li \l {if-command} {\\if}
+ \li \l {image-command} {\\image}
+ \li \l {include-command} {\\include}
+ \li \l {ingroup-command} {\\ingroup}
+ \li \l {inheaderfile-command}{\\inheaderfile}
+ \li \l {inherits-command}{\\inherits}
+ \li \l {inlineimage-command} {\\inlineimage}
+ \li \l {inmodule-command} {\\inmodule}
+ \li \l {inqmlmodule-command} {\\inqmlmodule}
+ \li \l {instantiates-command} {\\instantiates} (deprecated, use \\nativetype)
+ \li \l {internal-command} {\\internal}
+ \li \l {keyword-command} {\\keyword}
+ \li \l {l-command} {\\l}
+ \li \l {legalese-command} {\\legalese}
+ \li \l {li-command} {\\li}
+ \li \l {list-command} {\\list}
+ \li \l {macro-command} {\\macro}
+ \li \l {meta-command} {\\meta}
+ \li \l {module-command} {\\module}
+ \li \l {modulestate-command} {\\modulestate}
+ \li \l {namespace-command} {\\namespace}
+ \li \l {nativetype-command} {\\nativetype}
+ \li \l {nextpage-command} {\\nextpage}
+ \li \l {noautolist-command} {\\noautolist}
+ \li \l {nonreentrant-command} {\\nonreentrant}
+ \li \l {note-command} {\\note}
+ \li \l {li-command} {\\o} (deprecated, use \\li)
+ \li \l {omit-command} {\\omit}
+ \li \l {omitvalue-command} {\\omitvalue}
+ \li \l {overload-command} {\\overload}
+ \li \l {page-command} {\\page}
+ \li \l {preliminary-command} {\\preliminary}
+ \li \l {previouspage-command} {\\previouspage}
+ \li \l {printline-command} {\\printline}
+ \li \l {printto-command} {\\printto}
+ \li \l {printuntil-command} {\\printuntil}
+ \li \l {property-command} {\\property}
+ \li \l {qml-command} {\\qml}
+ \li \l {qmlabstract-command} {\\qmlabstract}
+ \li \l {qmlattachedproperty-command} {\\qmlattachedproperty}
+ \li \l {qmlattachedsignal-command} {\\qmlattachedsignal}
+ \li \l {qmlvaluetype-command} {\\qmlvaluetype}
+ \li \l {qmlclass-command} {\\qmlclass} (deprecated, use \\qmltype)
+ \li \l {qmldefault-command} {\\qmldefault}
+ \li \qdoccmd qmlenumeratorsfrom
+ \li \l {qmltype-command} {\\qmltype}
+ \li \l {qmlmethod-command} {\\qmlmethod}
+ \li \l {qmlproperty-command} {\\qmlproperty}
+ \li \l {qmlsignal-command} {\\qmlsignal}
+ \li \l {qmlmodule-command} {\\qmlmodule}
+ \li \l {qtcmakepackage-command} {\\qtcmakepackage}
+ \li \l {qtcmaketargetitem-command} {\\qtcmaketargetitem}
+ \li \l {quotation-command} {\\quotation}
+ \li \l {quotefile-command} {\\quotefile}
+ \li \l {quotefromfile-command} {\\quotefromfile}
+ \li \l {raw-command} {\\raw}
+ \li \l {readonly-command} {\\readonly}
+ \li \l {reentrant-command} {\\reentrant}
+ \li \l {reimp-command} {\\reimp}
+ \li \l {relates-command} {\\relates}
+ \li \l {required-command} {\\required}
+ \li \l {row-command} {\\row}
+ \li \l {sa-command} {\\sa}
+ \li \l {sectionOne-command} {\\section1}
+ \li \l {sectionTwo-command} {\\section2}
+ \li \l {sectionThree-command} {\\section3}
+ \li \l {sectionFour-command} {\\section4}
+ \li \l {since-command} {\\since}
+ \li \l {sincelist-command} {\\sincelist}
+ \li \l {skipline-command} {\\skipline}
+ \li \l {skipto-command} {\\skipto}
+ \li \l {skipuntil-command} {\\skipuntil}
+ \li \l {snippet-command} {\\snippet}
+ \li \l {span-command} {\\span}
+ \li \l {startpage-command} {\\startpage}
+ \li \l {sub-command} {\\sub}
+ \li \l {subtitle-command} {\\subtitle}
+ \li \l {sup-command} {\\sup}
+ \li \l {table-command} {\\table}
+ \li \l {tableofcontents-command} {\\tableofcontents}
+ \li \l {target-command} {\\target}
+ \li \l {threadsafe-command} {\\threadsafe}
+ \li \l {title-command} {\\title}
+ \li \qdoccmd tm
+ \li \l {tt-command} {\\tt}
+ \li \l {typealias-command} {\\typealias}
+ \li \l {typedef-command} {\\typedef}
+ \li \l {uicontrol-command} {\\uicontrol}
+ \li \l {underline-command} {\\underline}
+ \li \l {variable-command} {\\variable}
+ \li \l {value-command} {\\value}
+ \li \l {warning-command} {\\warning}
+ \li \l {wrapper-command} {\\wrapper}
+ \endlist
+*/
diff --git a/src/qdoc/qdoc/doc/qdoc-manual-contextcmds.qdoc b/src/qdoc/qdoc/doc/qdoc-manual-contextcmds.qdoc
new file mode 100644
index 000000000..7c62c08bd
--- /dev/null
+++ b/src/qdoc/qdoc/doc/qdoc-manual-contextcmds.qdoc
@@ -0,0 +1,1002 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page 14-qdoc-commands-contextcommands.html
+ \previouspage Topic Commands
+ \nextpage Document Navigation
+
+ \title Context Commands
+
+ The context commands provide information about the element being
+ documented that QDoc can't deduce on its own. For example:
+ \list
+ \li Is this class thread-safe?
+ \li Is this function reentrant?
+ \li Of which module is this class a member?
+ \li Which include statement is needed to use this class?
+ \endlist
+
+ Context commands can appear anywhere in a QDoc comment,
+ but they are normally placed near the top of the comment, just
+ below the \l {Topic Commands} {topic} command.
+
+ \list
+ \li \l {abstract-command} {\\abstract}
+ \li \l {attribution-command} {\\attribution}
+ \li \l {compares-command}{\\compares} (Since QDoc 6.7)
+ \li \l {compareswith-command}{\\compareswith} (Since QDoc 6.7)
+ \li \l {default-command} {\\default}
+ \li \l {deprecated-command}{\\deprecated}
+ \li \l {ingroup-command}{\\ingroup}
+ \li \l {inheaderfile-command}{\\inheaderfile}
+ \li \l {inherits-command}{\\inherits}
+ \li \l {inmodule-command}{\\inmodule}
+ \li \l {internal-command}{\\internal}
+ \li \l {modulestate-command} {\\modulestate} (Since QDoc 6.5)
+ \li \l {nextpage-command}{\\nextpage}
+ \li \l {nonreentrant-command}{\\nonreentrant}
+ \li \l {overload-command}{\\overload}
+ \li \l {preliminary-command}{\\preliminary}
+ \li \l {previouspage-command}{\\previouspage}
+ \li \l {qmlabstract-command} {\\qmlabstract}
+ \li \l {qmldefault-command} {\\qmldefault}
+ \li \qdoccmd qmlenumeratorsfrom (Since QDoc 6.8)
+ \li \l {qtcmakepackage-command} {\\qtcmakepackage}
+ \li \l {qtcmaketargetitem-command} {\\qtcmaketargetitem}
+ \li \l {readonly-command} {\\readonly}
+ \li \l {reentrant-command}{\\reentrant}
+ \li \l {reimp-command}{\\reimp}
+ \li \l {relates-command}{\\relates}
+ \li \l {required-command} {\\required}
+ \li \l {since-command}{\\since}
+ \li \l {startpage-command}{\\startpage}
+ \li \l {subtitle-command}{\\subtitle}
+ \li \l {threadsafe-command}{\\threadsafe}
+ \li \l {title-command}{\\title}
+ \li \l {wrapper-command}{\\wrapper}
+ \endlist
+
+*/
+
+/*!
+ \page 15-qdoc-commands-navigation.html
+ \previouspage Context Commands
+ \nextpage Status
+
+ \title Document Navigation
+
+ The navigation commands are for linking the pages of a document in
+ a meaningful sequence. Below is a sequence of QDoc comments that
+ shows a typical use of the navigation commands.
+
+ \section1 Example
+ \quotefile files/basicqt.qdoc.sample
+
+ The \l {startpage-command} {\\startpage} command creates a link to
+ the page the author wants as the first page of a multipage document.
+
+ The link is included in the generated HTML source code but has no
+ visual effect on the documentation:
+
+ \code
+ <head>
+ ...
+ <link rel="start" href="basicqt.html" />
+ ...
+ </head>
+ \endcode
+
+ \section1 Commands
+
+ \target previouspage-command
+ \section2 \\previouspage
+
+ The \\previouspage command links the current page to the previous
+ page in a sequence. The command has two arguments, each enclosed
+ by curly braces: the first is the link target (the title of
+ the previous page), the second is the link text. If the page's
+ title is equivalent to the link text, the second argument can be
+ omitted.
+
+ The command must stand alone on its own line.
+
+ \target nextpage-command
+ \section2 \\nextpage
+
+ The \\nextpage command links the current page to the next page in
+ a sequence. The command follows the same syntax and argument
+ convention as the \l {previouspage-command} {\\previouspage}
+ command.
+
+ \target startpage-command
+ \section2 \\startpage
+
+ The \\startpage command specifies the first page of a sequence of
+ pages. The command must stand alone on its own line, and its
+ unique argument is the title of the first document.
+
+ QDoc will generate a link to the start page and include it in the
+ generated HTML file, but this has no visual effect on the
+ documentation. The generated link type tells browsers and search
+ engines which document is considered by the author to be the
+ starting point of the collection.
+*/
+
+/*!
+ \page 16-qdoc-commands-status.html
+ \previouspage Document Navigation
+ \nextpage Thread Support
+
+ \title Status
+
+ These commands are for indicating that a documented element has
+ some special status. The element could be marked \e deprecated,
+ that is, it's about to be made obsolete and no longer included
+ in the public interface. The \l {since-command}{\\since} command
+ is for specifying the version number in which a function or class
+ first appeared. The \l {qmlabstract-command} {\\qmlabstract} command
+ is for marking a QML type as an abstract base class.
+
+ \target abstract-command
+ \target qmlabstract-command
+ \section1 \\abstract and \\qmlabstract
+
+ \\abstract is a synonym for the \\qmlabstract command. Add this
+ command to the \l {qmltype-command} {\\qmltype} comment for a QML
+ type when that type is meant to be used \e {only} as an abstract
+ base type. When a QML type is abstract, it means that the QML type
+ that can't be instantiated. Instead, the properties in its public
+ API are included in the public properties list on the reference
+ page for each QML type that inherits the abstract QML type. The
+ properties are documented as if they are properties of the
+ inheriting QML type.
+
+ Normally, when a QML type is marked with \e{\\qmlabstract}, it is
+ also marked with \e{\\internal} so that its reference page is not
+ generated. It the abstract QML type is not marked internal, it
+ will have a reference page in the documentation.
+
+ \target attribution-command
+ \section1 \\attribution
+
+ The \\attribution command marks a documented \qdoccmd page as license
+ attribution documentation.
+
+ The \l {annotatedattributions} {\\generatelist annotatedattributions}
+ command generates an annotated list of all license attribution pages
+ in the documentation project.
+
+ \target default-command
+ \section1 \\default
+
+ The \\default command is used for documenting a default value for
+ a QML property. The command takes a single argument, which is
+ displayed in the documentation as the default value.
+
+ \badcode *
+ /\1!
+ \qmlproperty real Item::x
+ \default 0.0
+ \1/
+ \endcode
+
+ If the default value is a non-empty string, use quotes:
+
+ \badcode *
+ /\1!
+ \qmlproperty string Item::state
+ \default "invalid"
+ \1/
+ \endcode
+
+ \target compares-command
+ \section2 \\compares
+
+ Use the \c {\compares} command to describe the comparison results for the
+ documented C++ type when compared to itself. You must use this command in
+ conjunction with the \l {class-command}{\\class} command.
+
+ \c {\compares} takes one of the following arguments:
+
+ //! [comparison-categories]
+ \list
+ \li \c strong
+ \li \c partial
+ \li \c weak
+ \li \c equality
+ \endlist
+
+ \c {strong}, \c {partial}, and \c {weak} relate to the ordering.
+ \c {equality} means that the type is only compared for equality.
+ //! [comparison-categories]
+
+ This command was introduced to QDoc with Qt 6.7.
+
+ See also \l {compareswith-command}{\\compareswith}.
+
+ \target compareswith-command
+ \section1 \\compareswith
+
+ Use the \c {\compareswith .. \endcompareswith} pair of commands to
+ describe the comparison results for the documented C++ type when
+ compared to other types. \c {\compareswith} takes two or more
+ arguments: a comparison category, followed by a type name, or a
+ space-separated list of type names. Any text lines between
+ \c {\compareswith} and \c {\endcompareswith} commands are
+ considered further details that apply to all types subject
+ to the comparison category argument.
+
+ Types that have one or more space in their name, such as
+ \c{unsigned long}, should be enclosed in braces.
+
+ For example:
+
+ \badcode *
+ /\1!
+ ...
+ \compareswith strong int long {unsigned long} {unsigned int} char
+ ...
+ \endcompareswith
+ ...
+ \1/
+ \endcode
+
+ Argument enclosed in braces have their leading and trailing whitespaces
+ removed.
+ For example, \c{unsigned long} and \c{ unsigned long } are equivalent.
+
+ The comparison category argument must be one of the following:
+ \include qdoc-manual-contextcmds.qdoc comparison-categories
+
+ This command was introduced to QDoc with Qt 6.7.
+
+ See also \l {compares-command}{\\compares}.
+
+ \target qmldefault-command
+ \section1 \\qmldefault
+
+ The \\qmldefault command is for marking a QML property as the
+ \l {default-properties}
+ {default property}. The word \c default is displayed in
+ the documentation of the property.
+
+ \badcode *
+ /\1!
+ \qmlproperty list<Change> State::changes
+ This property holds the changes to apply for this state.
+ \qmldefault
+
+ By default, these changes are applied against the default state. If the state
+ extends another state, then the changes are applied against the state being
+ extended.
+ \1/
+ \endcode
+
+ See how QDoc renders this property on the reference page for the
+ \l {QtQuick::State::changes}{State} type.
+
+ \target qmlenumeratorsfrom-command
+ \section1 \\qmlenenumeratorsfrom
+
+ Use the \\qmlenumeratorsfrom command in a \qdoccmd qmlproperty topic
+ with a property type \e enumeration, to automatically replicate the
+ documentation for enumerators from a C++ \qdoccmd enum topic.
+
+ The command takes a fully qualified C++ enum as an argument,
+ and generates a list of enumerators and their descriptions.
+
+ \note The C++ enum must be documented in the same project; QDoc
+ cannot access its documentation if it's part of an external
+ documentation set that the current project \qdocvar depends
+ on.
+
+ By default, each enumerator is prefixed with the type name the
+ property belongs to, with \c{.} as the separator.
+
+ For example:
+
+ \badcode *
+ /\1!
+ \qmlproperty enumeration QtMultimedia::Camera::error
+ \qmlenumeratorsfrom QCamera::Error
+
+ //! Outputs documentation for 'Camera.NoError', 'Camera.CameraError'
+ \1/
+ \endcode
+
+ If the enumerators are registered to QML under a different type
+ name, this name (prefix) can be specified using the optional
+ argument in square brackets:
+
+ \badcode
+ \qmlenumeratorsfrom [Errors] QCamera::Error
+
+ //! Outputs documentation for 'Errors.NoError', 'Errors.CameraError'
+ \1/
+ \endcode
+
+ This command was introduced in QDoc 6.8.
+
+ See also \qdoccmd {qmlproperty}, \qdoccmd {enum}, and \qdoccmd {value}.
+
+ \target dontdocument-command
+ \section1 \\dontdocument
+
+ The \\dontdocument command is only used in a dontdocument.qdoc file
+ for a particular module. This file specifies publically declared
+ classes or structs that are not meant to be documented. QDoc will
+ not print warnings about missing \\class comments for these classes
+ and structs.
+
+ Below you will find the \\dontdocument command in the
+ dontdocument.qdoc for widgets:
+
+ \badcode *
+ /\1!
+ \dontdocument (QTypeInfo QMetaTypeId)
+ \1/
+ \endcode
+
+ \target inheaderfile-command
+ \section1 \\inheaderfile
+
+ The \\inheaderfile meta-command is used for overriding the include statement
+ generated for a C++ class, namespace, or header file reference documentation.
+
+ By default, QDoc documents a \c {\class SomeClass} to be available with
+ a following include statement:
+
+ \code
+ #include <SomeClass>
+ \endcode
+
+ If the actual include statement differs from the default, this can be
+ documented as
+
+ \badcode
+ \class SomeClass
+ \inheaderfile Tools/SomeClass
+ ...
+ \endcode
+
+ See also \l {class-command}{\\class} and
+ \l {headerfile-command}{\\headerfile}.
+
+
+ \target obsolete-command
+ \section1 \\obsolete
+ The \\obsolete command is superceded by the \\deprecated command.
+
+ This command is kept for backwards compatibility reasons only.
+ It may be removed in a future version of QDoc. Use the \\deprecated
+ command instead.
+
+ See also \l {deprecated-command}{\\deprecated}.
+
+ \target deprecated-command
+ \section1 \\deprecated
+
+ The \\deprecated command is for indicating that a function is being
+ deprecated, and that it should no longer be used in new code. There
+ is no guarantee for how long it will remain in the library.
+
+ The \\deprecated command takes two optional arguments:
+ \list
+ \li A version in square brackets (e.g. [6.2]).
+ \li A string with more information, for example a suggested
+ replacement.
+ \endlist
+
+ When generating the reference documentation for a class, QDoc will
+ create and link to a separate page documenting its deprecated
+ functions. It is good practice to suggest an equivalent function
+ as an alternative.
+
+ \badcode *
+ /\1!
+ \fn MyClass::MyDeprecatedFunction
+ \deprecated [6.2] Use MyNewFunction() instead.
+ \1/
+ \endcode
+
+ \target internal-command
+ \section1 \\internal
+
+ The \\internal command indicates that the referenced
+ function is not part of the public interface.
+
+ The command must stand on its own line.
+
+ QDoc ignores the documentation as well as the documented item,
+ when generating the associated class reference documentation.
+
+ \badcode *
+ /\1!
+ \internal
+
+ Tries to find the decimal separator. If it can't find
+ it and the thousand delimiter is != '.' it will try to
+ find a '.';
+ \1/
+ int QDoubleSpinBoxPrivate::findDelimiter
+ (const QString &str, int index) const
+ {
+ int dotindex = str.indexOf(delimiter, index);
+ if (dotindex == -1 && thousand != dot && delimiter != dot)
+ dotindex = str.indexOf(dot, index);
+ return dotindex;
+ }
+ \endcode
+
+ This function will not be included in the documentation, unless QDoc
+ is called with the \c{-showinternal} command line option or the
+ \c{QDOC_SHOW_INTERNAL} environment variable is set.
+
+ \target modulestate-command
+ \section1 \\modulestate
+
+ The \\modulestate command can be used within a \\module or \\qmlmodule
+ topic to provide a module state description other than \e preliminary or
+ \e deprecated.
+
+ Rest of the line is taken as an argument that describes the module's
+ state. For example:
+
+ \badcode *
+ /*!
+ \module QtFoo
+ \modulestate Technical Preview
+ \1/
+ \endcode
+
+ QDoc will then add this information on the module page:
+
+ \quotation
+ This module is in \e {Technical Preview} state.
+ \endquotation
+
+ In HTML output, this state information appears also in the navigation
+ bar (breadcrumbs) of reference pages for the module's members.
+
+ \target preliminary-command
+ \section1 \\preliminary
+
+ The \\preliminary command is for indicating that a referenced
+ function is still under development.
+
+ The command must stand on its own line.
+
+ The \\preliminary command expands to a notification in the
+ function documentation, and marks the function as preliminary when
+ it appears in lists.
+
+ \badcode *
+ /\1!
+ \preliminary
+
+ Returns information about the joining type attributes of the
+ character (needed for certain languages such as Arabic or
+ Syriac).
+
+ \1/
+ QChar::JoiningType QChar::joiningType() const
+ {
+ return QChar::joiningType(ucs);
+ }
+ \endcode
+
+ \target readonly-command
+ \section1 \\readonly
+
+ The \\readonly command is used in conjunction with a \l {qmlproperty-command}
+ {\\qmlproperty} command to mark the QML property as read-only.
+
+ \target required-command
+ \section1 \\required
+
+ The \\required command is used in conjunction with a \l {qmlproperty-command}
+ {\\qmlproperty} command to mark the QML property as required.
+
+ \b {See also} \l {The Property System}.
+
+ \target since-command
+ \section1 \\since
+
+ The \\since command tells in which minor release
+ the associated functionality was added.
+
+ If the argument passed to \\since contains no spaces, it is assumed to be
+ a version number string for the Qt project, and QDoc will prefix it with
+ 'Qt' in the generated output. The argument can also contain the project
+ name explicitly:
+
+ \badcode
+ \since MyFramework 2.0
+ \endcode
+
+ In this case, the arguments (project and version) are used as is.
+
+ \section2 Inheritance of Since Information
+
+ Since QDoc version 6.5, C++ classes and QML types inherit the \\since statement
+ from their respective \l {module-command}{module} or \l {qmlmodule-command}
+ {QML module}, unless \\since is explicitly used in the type documentation.
+
+ \section2 Since Clause
+
+ The \\value command allows an optional \e {since} clause, enclosed in square
+ brackets, to immediately follow the command string. This is used for
+ marking specific C++ enum values with since information.
+
+ See also \l {value-command}{\\value} and \l {ignoresince}.
+
+ \target wrapper-command
+ \section1 \\wrapper
+
+ The \\wrapper command, when used in a C++ class documentation, marks the
+ class as a \e wrapper that provides access to a non-Qt API. This command
+ is used for suppressing warnings that might otherwise be generated for
+ members of such a class.
+*/
+
+
+/*!
+ \page 17-qdoc-commands-thread.html
+ \previouspage Status
+ \nextpage Relating Things
+
+ \title Thread Support
+
+ The thread support commands are for specifying the level of
+ support for multithreaded programming in a class or function.
+ There are three levels of support: \c threadsafe, \c reentrant and
+ \c nonreentrant.
+
+ The default is \c nonreentrant which means that the associated
+ class or function cannot be called by multiple threads. \c
+ Reentrant and \c threadsafe are levels primarily used for classes.
+
+ \c Reentrant means that all the functions in the referenced class
+ can be called simultaneously by multiple threads, provided that
+ each invocation of the functions reference unique data. While \c
+ threadsafe means that all the functions in the referenced class
+ can be called simultaneously by multiple threads even when each
+ invocation references shared data.
+
+ When a class is marked \l {reentrant-command} {\\reentrant} or \l
+ {threadsafe-command} {\\threadsafe}, functions in that class can
+ be marked \c nonreentrant using the \l {nonreentrant-command}
+ {\\nonreentrant} command.
+
+ \section1 Example
+
+ \target reentrant-example
+ \badcode *
+ /\1!
+ \class QLocale
+ \brief The QLocale class converts between numbers and their
+ string representations in various languages.
+
+ \reentrant
+ \ingroup i18n
+ \ingroup text
+
+ QLocale is initialized with a language/country pair in its
+ constructor and offers number-to-string and string-to-number
+ conversion functions similar to those in QString.
+
+ ...
+
+ \nonreentrant
+
+ Sets the global default locale to \a locale. These values are
+ used when a QLocale object is constructed with no
+ arguments. If this function is not called, the system's locale
+ is used.
+
+ \warning In a multithreaded application, the default locale
+ should be set at application startup, before any non-GUI
+ threads are created.
+
+ \sa system(), c()
+ \1/
+ void QLocale::setDefault(const QLocale &locale)
+ {
+ default_d = locale.d;
+ }
+ \endcode
+
+ QDoc generates a notification when a class is
+ declared reentrant, and lists the exceptions (the declared
+ nonreentrant functions). A link to the general documentation on \l
+ {17-qdoc-commands-thread.html#reentrant} {reentrancy and thread-safety} is
+ included. In addition a warning, "\b Warning: This function is
+ not reentrant.", is generated in the nonreentrant functions'
+ documentation.
+
+ QDoc will generate the same notification and warnings when a class
+ is declared threadsafe.
+
+ For more information see the general documentation on \l
+ {17-qdoc-commands-thread.html#reentrant} {reentrancy and thread-safety}.
+
+ \section1 Commands
+
+ \target threadsafe-command
+ \section2 \\threadsafe
+
+ The \\threadsafe command includes a line in the documentation to
+ indicate that the associated class or function is \e threadsafe
+ and can be called simultaneously by multiple threads, even when
+ separate invocations reference shared data.
+
+ The command must stand on its own line.
+
+ The documentation generated from this command will be similar to
+ the what is generated for the \l {reentrant-command} {\\reentrant}
+ command. See the example above in the \l {reentrant-example}
+ {introduction}.
+
+ See also \l{reentrant-command} {\\reentrant} and
+ \l{nonreentrant-command} {\\nonreentrant}.
+
+ \target reentrant-command
+ \section2 \\reentrant
+
+ The \\reentrant command indicates that the associated class or
+ function can be called simultaneously by multiple threads,
+ provided that each invocation references its own data. See the \l
+ {reentrant-example} {example} above.
+
+ The command must stand on its own line.
+
+ See also \l{nonreentrant-command} {\\nonreentrant} and
+ \l{threadsafe-command} {\\threadsafe}.
+
+ \target nonreentrant-command
+ \section2 \\nonreentrant
+
+ The \\nonreentrant command indicates that the associated class or
+ function cannot be called by multiple threads. Nonreentrant is the
+ default case.
+
+ The command must stand on its own line.
+
+ When a class is marked \l {reentrant-command} {\\reentrant} or \l
+ {threadsafe-command} {\\threadsafe}, functions in that class can
+ be marked \c nonreentrant using this command in the \l{fn-command}
+ {\\fn} comment of the functions to be excluded.
+
+ See also \l{reentrant-command} {\\reentrant} and
+ \l{threadsafe-command} {\\threadsafe}.
+*/
+
+/*!
+ \page 18-qdoc-commands-relating.html
+ \previouspage Thread Support
+ \nextpage Grouping Things
+
+ \title Relating Things
+
+ The relating commands are for specifying how one documented
+ element relates to another documented element. Some examples:
+ \list
+ \li This function is an overload of another function.
+ \li This function is a reimplementation of another function.
+ \li This typedef is \e related to some class or header file.
+ \endlist
+
+ There is also a command for documenting that a QML type inherits
+ some other QML type.
+
+ \section1 Commands
+
+ \target inherits-command
+ \section2 \\inherits
+
+ The \\inherits command is for documenting that one QML type
+ inherits some other QML type. It must be included in the
+ inheriting element's \l{qmltype-command}{\\qmltype} comment.
+ The argument is the name of the inherited QML type.
+
+ \badcode *
+ /\1!
+ \qmltype PauseAnimation
+ \nativetype QDeclarativePauseAnimation
+ \ingroup qml-animation-transition
+ \since 4.7
+ \inherits Animation
+ \brief The PauseAnimation element provides a pause for an animation.
+
+ When used in a SequentialAnimation, PauseAnimation is a step
+ when nothing happens, for a specified duration.
+
+ A 500ms animation sequence, with a 100ms pause between two animations:
+
+ SequentialAnimation {
+ NumberAnimation { ... duration: 200 }
+ PauseAnimation { duration: 100 }
+ NumberAnimation { ... duration: 200 }
+ }
+
+ \sa {QML Animation and Transitions}, {declarative/animation/basics}{Animation basics example}
+ \1/
+ \endcode
+
+ QDoc includes this line on the reference page for the
+ \l [QML] PauseAnimation
+ element:
+
+ \quotation
+ Inherits \l [QML] Animation
+ \endquotation
+
+ \target overload-command
+ \section2 \\overload
+
+ The \\overload command is for indicating that a function is a
+ secondary overload of its name.
+
+ The command must stand on its own line.
+
+ For a function name that is overloaded (except constructors), QDoc
+ expects one primary version of the function, and all the others
+ marked with the \b {\\overload command}. The primary version
+ should be fully documented. Each overload can have whatever extra
+ documentation you want to add for just that overloaded version.
+
+ You can include the function name plus '()' as a parameter to
+ the \b{\\overload} command, which will include a standard
+ \e{This function overloads...} line of text with a link
+ to the documentation for the primary version of the function.
+
+ \badcode *
+ /\1!
+ \overload addAction()
+
+ This convenience function creates a new action with an
+ \a icon and some \a text. The function adds the newly
+ created action to the menu's list of actions, and
+ returns it.
+
+ \sa QWidget::addAction()
+ \1/
+ QAction *QMenu::addAction(const QIcon &icon, const QString &text)
+ {
+ QAction *ret = new QAction(icon, text, this);
+ addAction(ret);
+ return ret;
+ }
+ \endcode
+
+ If you don't include the function name with the \b{\\overload}
+ command, then instead of the "This function overloads..." line
+ with the link to the documentation for the primary version, you
+ get the old standard line:
+
+ \quotation
+ This is an overloaded member function, provided for
+ convenience.
+ \endquotation.
+
+ \target reimp-command
+ \section2 \\reimp
+
+ The \\reimp command is for indicating that a function is a
+ reimplementation of a virtual function.
+
+ The command must stand on its own line.
+
+ QDoc will omit the reimplemented function from the class
+ reference.
+
+ \badcode *
+ /\1!
+ \reimp
+ \1/
+ void QToolButton::nextCheckState()
+ {
+ Q_D(QToolButton);
+ if (!d->defaultAction)
+ QAbstractButton::nextCheckState();
+ else
+ d->defaultAction->trigger();
+ }
+ \endcode
+
+ This function will not be included in the documentation. Instead,
+ a link to the base function QAbstractButton::nextCheckState() will
+ appear in the documentation.
+
+ \target relates-command
+ \section2 \\relates
+
+ The \\relates command is for including the documentation of a
+ global element to some class or header file. The argument is a
+ class name or header file. For template types, use the type name only.
+
+ \badcode *
+ /\1!
+ \relates QChar
+
+ Reads a char from the stream \a in into char \a chr.
+
+ \sa {Format of the QDataStream operators}
+ \1/
+ QDataStream &operator>>(QDataStream &in, QChar &chr)
+ {
+ quint16 u;
+ in >> u;
+ chr.unicode() = ushort(u);
+ return in;
+ }
+ \endcode
+
+ The documentation for this function will be included on the reference page
+ for class QChar.
+*/
+
+/*!
+ \page 19-qdoc-commands-grouping.html
+ \previouspage Relating Things
+ \nextpage Naming Things
+
+ \title Grouping Things
+
+ The grouping commands relate classes to defined groups and
+ modules. The groups are used when generating lists of related
+ classes in the documentation, while the modules are elements of
+ Qt's structure.
+
+ \section1 Commands
+
+ \target ingroup-command
+ \section2 \\ingroup
+
+ The \\ingroup command indicates that the given
+ class, page, or other entity belongs to a certain group of
+ related documentation.
+
+ An entity may belong to multiple groups.
+
+ The \\ingroup command's argument is a group name, but note
+ that the command considers the rest of the line as part of
+ its argument. Make sure that the group name is followed by
+ a linebreak.
+
+ \badcode *
+ /\1!
+ \class QDir
+ \brief The QDir class provides access to directory
+ structures and their contents.
+
+ \ingroup io
+ ...
+ \1/
+ \endcode
+
+ This adds the QDir class to the \c io group. An entry for QDir
+ will then appear on the list created with, for example,
+ the \l {annotatedlist-command} {\\annotatedlist} command with
+ an argument \c io.
+
+ QDoc automatically generates links to associated groups on a
+ C++ class, namespace, or header reference page. For example,
+ given the above documentation for class \QDir and the following
+ \l {group-command}{\\group} page:
+
+ \badcode *
+ /\1
+ \group io
+ \title Input/Output and Networking
+ ...
+ \1/
+ \endcode
+
+ QDoc then outputs a statement on the QDir reference page:
+
+ \quotation
+ \list
+ \li \QDir is part of \l {Input/Output and Networking}.
+ \endlist
+ \endquotation
+
+ For HTML output, QDoc also generates a link to the group
+ page as part of the navigation bar (breadcrumbs). If multiple
+ \\ingroup commands are used, the first one that references
+ a documented \\group is selected.
+
+ See also \l {group-command} {\\group}.
+
+ \target inmodule-command
+ \section2 \\inmodule
+
+ The \\inmodule command relates a class to the module specified by
+ the command's argument.
+
+ For the basic classes in Qt, a class's module is determined by its
+ location, namely its directory. However, for extensions like
+ ActiveQt and \QD, a class must be related to a module
+ explicitly.
+
+ The command's argument is a module name, but note that the command
+ considers the rest of the line as part of its argument. Make sure
+ that the module name is followed by a linebreak.
+
+ \code
+ /*!
+ \class QDesignerTaskMenuExtension
+ \inmodule QtDesigner
+ * /
+ \endcode
+
+ This ensures that the QDesignerTaskMenuExtension class is included
+ in the \QD module, which means, for example, that the
+ class will appear on the list created by calling the \l
+ {generatelist-command} {\\generatelist} command with the \c
+ {{classesbymodule QtDesigner}} argument.
+
+ See also \l {module-command} {\\module} and \l
+ {generatelist-command} {\\generatelist}.
+*/
+
+/*!
+ \page 20-qdoc-commands-namingthings.html
+ \previouspage Grouping Things
+ \nextpage Markup Commands
+
+ \title Naming Things
+
+ In general, a title command considers everything that follows it
+ until the first line break as its argument. If the title is so
+ long it must span multiple lines, end each line (except the last
+ one) with a backslash.
+
+ \section1 Commands
+
+ \target title-command
+ \section2 \\title
+
+ The \\title command sets the title for a documentation page, or
+ allows you to override it.
+
+ \badcode *
+ /\1!
+ \page signalandslots.html
+
+ \title Signals & Slots
+
+ Signals and slots are used for communication between
+ objects. The signals and slots mechanism is a central
+ feature of Qt, and probably the part that differs most
+ from the features provided by other frameworks.
+
+ ...
+ \1/
+ \endcode
+
+ See also \l {subtitle-command} {\\subtitle}.
+
+ \target subtitle-command
+ \section2 \\subtitle
+
+ The \\subtitle command sets a subtitle for a documentation page.
+
+ \badcode *
+ /\1!
+ \page qtopiacore-overview.html
+
+ \title Qtopia Core
+ \subtitle Qt for Embedded Linux
+
+ Qt/Embedded, the embedded Linux port of Qt, is a
+ complete and self-contained C++ GUI and platform
+ development tool for Linux-based embedded development.
+ ...
+ \1/
+ \endcode
+
+ See also \l {title-command} {\\title}.
+
+*/
diff --git a/src/qdoc/qdoc/doc/qdoc-manual-intro.qdoc b/src/qdoc/qdoc/doc/qdoc-manual-intro.qdoc
new file mode 100644
index 000000000..0aeb715c9
--- /dev/null
+++ b/src/qdoc/qdoc/doc/qdoc-manual-intro.qdoc
@@ -0,0 +1,299 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page 01-qdoc-manual.html
+ \previouspage QDoc Manual
+ \nextpage Command Index
+
+ \title Introduction to QDoc
+
+ QDoc is a tool used by Qt Developers to generate documentation for
+ software projects. It works by extracting \e {QDoc comments} from
+ project source files and then formatting these comments as HTML
+ pages or DocBook XML documents. QDoc finds QDoc comments in \c
+ {.cpp} files and in \c {.qdoc} files. QDoc does not look for QDoc
+ comments in \c {.h} files. A QDoc comment always begins with an
+ exclamation mark (\b{!})). For example:
+
+ \badcode *
+ /\1!
+ \class QObject
+ \brief The QObject class is the base class of all Qt objects.
+
+ \ingroup objectmodel
+
+ \reentrant
+
+ QObject is the heart of the Qt \l{Object Model}. The
+ central feature in this model is a very powerful mechanism
+ for seamless object communication called \l{signals and
+ slots}. You can connect a signal to a slot with connect()
+ and destroy the connection with disconnect(). To avoid
+ never ending notification loops you can temporarily block
+ signals with blockSignals(). The protected functions
+ connectNotify() and disconnectNotify() make it possible to
+ track connections.
+
+ QObjects organize themselves in \l {Object Trees &
+ Ownership} {object trees}. When you create a QObject with
+ another object as parent, the object will automatically
+ add itself to the parent's \c children() list. The parent
+ takes ownership of the object. It will automatically
+ delete its children in its destructor. You can look for an
+ object by name and optionally type using findChild() or
+ findChildren().
+
+ Every object has an objectName() and its class name can be
+ found via the corresponding metaObject() (see
+ QMetaObject::className()). You can determine whether the
+ object's class inherits another class in the QObject
+ inheritance hierarchy by using the \c inherits() function.
+
+ ....
+ \1/
+ \endcode
+
+ From the QDoc comment above, QDoc generates the HTML \l {QObject}
+ {QObject class reference} page.
+
+ This manual explains how to use the QDoc commands in QDoc comments
+ to embed good documentation in your source files. It also explains
+ how to make a \l {The QDoc Configuration File} {QDoc configuration
+ file}, which you will pass to QDoc on the command line.
+
+ \section1 Running QDoc
+
+ The name of the QDoc program is \c {qdoc}. To run QDoc from the
+ command line, give it the name of a configuration file:
+
+ \quotation
+ \c {$ ../../bin/qdoc ./config.qdocconf}
+ \endquotation
+
+ QDoc recognizes the \c {.qdocconf} suffix as a \l{The QDoc
+ Configuration File} {QDoc configuration file}. The configuration
+ file is where you tell QDoc where to find the project source
+ files, header files, and \c {.qdoc} files. It is also where you
+ tell QDoc what kind of output to generate (HTML, DocBook XML...),
+ and where to put the generated documentation. The configuration
+ file also contains other information for QDoc.
+
+ See \l{The QDoc Configuration File} for instructions on how to
+ set up a QDoc configuration file.
+
+ \section2 Running QDoc in Single Execution Mode
+
+ Beginning with Qt 5.5, a new way to run QDoc is available that
+ reduces the time it takes to generate the Qt5 documentation by as
+ much as 90%. The new way to run QDoc is \e{single execution} mode.
+ Single execution mode is not currently available in the Qt5 build
+ system, which still uses the \e {standard} mode. Single execution
+ mode is only available when you run QDoc yourself, which you will
+ want to do often as you document your module and integrate your
+ documentation with the other Qt modules.
+
+ To run QDoc in single execution mode, add \c {-single-exec} to the
+ command line and pass QDoc a master \c qdocconf file that is
+ simply a list of file paths for qdocconf files of all the Qt5
+ modules. For example:
+
+ \code
+ /Users/me/qt5/qtbase/bin/qdoc -outputdir /Users/me/qt5/qtbase/doc -installdir /Users/me/qt5/qtbase/doc /Users/me/qt5/master.qdocconf -single-exec
+ \endcode
+
+ The qdocconf file, \c {master.qdocconf}, just lists the qdocconf files for all the Qt5 modules to be processed:
+
+ \badcode
+ /Users/me/qt5/qtbase/src/corelib/doc/qtcore.qdocconf
+ /Users/me/qt5/qtbase/src/network/doc/qtnetwork.qdocconf
+ /Users/me/qt5/qtbase/src/sql/doc/qtsql.qdocconf
+ /Users/me/qt5/qtbase/src/xml/doc/qtxml.qdocconf
+ /Users/me/qt5/qtbase/src/testlib/doc/qttestlib.qdocconf
+ /Users/me/qt5/qtbase/src/concurrent/doc/qtconcurrent.qdocconf
+ /Users/me/qt5/qtbase/src/gui/doc/qtgui.qdocconf
+ /Users/me/qt5/qtbase/src/platformheaders/doc/qtplatformheaders.qdocconf
+ /Users/me/qt5/qtbase/src/widgets/doc/qtwidgets.qdocconf
+ /Users/me/qt5/qtbase/src/opengl/doc/qtopengl.qdocconf
+ /Users/me/qt5/qtbase/src/printsupport/doc/qtprintsupport.qdocconf
+ /Users/me/qt5/qtbase/src/tools/qdoc/doc/config/qdoc.qdocconf
+ /Users/me/qt5/qtbase/qmake/doc/qmake.qdocconf
+ /Users/me/qt5/qtsvg/src/svg/doc/qtsvg.qdocconf
+ /Users/me/qt5/qtxmlpatterns/src/xmlpatterns/doc/qtxmlpatterns.qdocconf
+ /Users/me/qt5/qtdeclarative/src/qml/doc/qtqml.qdocconf
+ /Users/me/qt5/qtdeclarative/src/quick/doc/qtquick.qdocconf
+ /Users/me/qt5/qtquickcontrols/src/controls/doc/qtquickcontrols.qdocconf
+ /Users/me/qt5/qtquickcontrols/src/layouts/doc/qtquicklayouts.qdocconf
+ /Users/me/qt5/qtquickcontrols/src/dialogs/doc/qtquickdialogs.qdocconf
+ /Users/me/qt5/qtmultimedia/src/multimedia/doc/qtmultimedia.qdocconf
+ /Users/me/qt5/qtmultimedia/src/multimediawidgets/doc/qtmultimediawidgets.qdocconf
+ /Users/me/qt5/qtactiveqt/src/activeqt/doc/activeqt.qdocconf
+ /Users/me/qt5/qtsensors/src/sensors/doc/qtsensors.qdocconf
+ /Users/me/qt5/qtwebkit/Source/qtwebkit.qdocconf
+ /Users/me/qt5/qttools/src/assistant/help/doc/qthelp.qdocconf
+ /Users/me/qt5/qttools/src/assistant/assistant/doc/qtassistant.qdocconf
+ /Users/me/qt5/qttools/src/designer/src/uitools/doc/qtuitools.qdocconf
+ /Users/me/qt5/qttools/src/designer/src/designer/doc/qtdesigner.qdocconf
+ /Users/me/qt5/qttools/src/linguist/linguist/doc/qtlinguist.qdocconf
+ /Users/me/qt5/qtwebkit-examples/doc/qtwebkitexamples.qdocconf
+ /Users/me/qt5/qtgraphicaleffects/src/effects/doc/qtgraphicaleffects.qdocconf
+ /Users/me/qt5/qtscript/src/script/doc/qtscript.qdocconf
+ /Users/me/qt5/qtscript/src/scripttools/doc/qtscripttools.qdocconf
+ /Users/me/qt5/qtserialport/src/serialport/doc/qtserialport.qdocconf
+ /Users/me/qt5/qtdoc/doc/config/qtdoc.qdocconf
+ \endcode
+
+ \section3 Why Standard Mode Is Slow
+
+ Currently, the Qt5 build system does not use QDoc's \e {single
+ execution} mode for generating the Qt5 documentation. It runs QDoc
+ in the \e {standard} mode. The standard mode was came about
+ because it was the easiest way to get convert the Qt4 QDoc to
+ handle the modularization of Qt in Qt5. In Qt4, QDoc ran once over
+ all the Qt4 sources to generate the HTML documentation for Qt.
+ While generating the Qt documentation, Qt4 QDoc also generated an
+ \e {index file} for Qt. That index file was meant to be used as
+ input to subsequent QDoc runs for generating HTML documentation
+ for other software libraries/products that were based on Qt. The
+ Qt index file allowed QDoc to link documentation written for those
+ other libraries/products to the Qt4 documentation.
+
+ When Qt5 came along, Qt was divided into modules. Since then,
+ many new modules have been added to Qt. As of version 5.5, there
+ are over 40 separate modules in Qt5, each with its own
+ documentation that links to (depends on) the documentation of
+ other Qt modules.
+
+ In \e {standard mode}, QDoc runs twice for each module. The first
+ QDoc run for a particular Qt module, parses all the module's
+ source files and then uses the information to generate the
+ module's index file. It is called the \e{prepare phase} because
+ it \e prepares the module's index file. The second QDoc run for
+ the module also parses all the module's source files and then
+ generates the module's documentation pages. This is called the \e
+ {generate phase} because it generates the module's documentation.
+
+ The module's documentation will likely contain HTML links to the
+ documentation of one or more of the other Qt modules. For example,
+ most Qt5 modules contain links to documentation in QtCore. When a
+ Qt module contains links into the documentation of other Qt
+ module's, that module is said to depend on those other Qt modules.
+ Hence when QDoc runs the \e {generate phase} for that module, it
+ must also load the index files for those modules so it can create
+ those links.
+
+ Hence, when the Qt build system generates the Qt documentation, it
+ first runs QDoc once for each module to perform the \e {prepare
+ phase} to generate all the index files. Then it runs QDoc once for
+ each module to perform the \e {generate phase}, where it uses the
+ dependent index files to generate the module's documentation,
+ including any cross-module links it finds. Each execution of
+ QDoc, both \e {prepare phase} and \e {generate phase}, parses
+ all the source files that are included in the module, and in the
+ \e {generate phase} also parses the index files for the dependent
+ modules. Nothing is retained or retainable between QDoc runs.
+
+ \section3 Why Single Execution Mode Is Much Faster
+
+ As the name implies, single execution mode uses a single QDoc
+ process to generate all the Qt5 documentation. The single QDoc
+ process still performs a \e{prepare phase} for each module and
+ then a \e{generate phase} for each module, but there are a few
+ differences. It begins by reading the master qdocconf file. Then
+ it reads each qdocconf file in the master list and performs the
+ \e{prepare phase} for each module. During the \e{prepare phase},
+ all the source files for the module are parsed to build a syntax
+ tree for the module. The module's index file is then generated,
+ although QDoc will not re-read the index files in the \e{generate
+ phase}. The important difference here is that the module's syntax
+ tree is retained after the index file is generated, so that after
+ the \e{prepare phase} has been run for all the modules, QDoc still
+ has all the syntax trees it built.
+
+ QDoc then processes each module again for the \e{generate phase}.
+ But now QDoc doesn't need to re-parse each module's source files,
+ because the module's syntax tree is still in memory. Nor does QDoc
+ need to re-read the index files for the dependent modules, again
+ because it still has the syntax trees for those modules in memory.
+ It remains only to traverse each module's syntax tree to generate
+ the documentation pages.
+
+ Hence, QDoc parses each source file once and only once and doesn't
+ need to read index files. This is what makes single execution mode
+ much faster than the standard mode. It is anticipated that the Qt
+ build system will eventually run QDoc in single execution mode.
+ However, changes to the master qdocconf file might be required, so
+ the method described above for running QDoc in single execution
+ mode might have to change, watch this space for updates.
+
+ \section1 How QDoc Works
+
+ QDoc begins by reading the configuration file you specified on the
+ command line. It stores all the variables from the configuration
+ file for later use. One of the first variables it uses is \c
+ {outputformats}. This variable tells QDoc which output generators
+ it will run. The default value is \e {HTML}, so if you don't set
+ \c {outputformats} in your configuration file, QDoc will generate
+ HTML output. That's usually what you will want anyway, but you can
+ also specify \e {DocBook} to get DocBook output instead.
+
+ Next, QDoc uses the values of the
+ \l {headerdirs-variable}
+ {headerdirs} variable and/or the \l
+ {22-qdoc-configuration-generalvariables.html#headers-variable}
+ {headers} variable to find and parse all the header files for your
+ project. QDoc does \e not scan header files for QDoc comments. It
+ parses the header files to build a master tree of all the items
+ that should be documented, in other words, the items that QDoc should find
+ QDoc comments for.
+
+ After parsing all the header files and building the master tree of
+ items to be documented, QDoc uses the value of the \l
+ {22-qdoc-configuration-generalvariables.html#sourcedirs-variable}
+ {sourcedirs} variable and/or the value of the \l
+ {22-qdoc-configuration-generalvariables.html#sources-variable}
+ {sources} variable to find and parse all the \c {.cpp} and \c
+ {.qdoc} files for your project. These are the files QDoc scans for
+ \e {QDoc comments}. Remember that a QDoc comment begins with
+ an exclamation mark: \b {/*!} .
+
+ For each QDoc comment it finds, it searches the master tree for
+ the item where the documentation belongs. Then it interprets the
+ QDoc commands in the comment and stores the interpreted commands
+ and the comment text in the tree node for the item.
+
+ Finally, QDoc traverses the master tree. For each node, if the
+ node has stored documentation, QDoc calls the output generator
+ specified by the \c {outputformats} variable to format and write
+ the documentation in the directory specified in the configuration
+ file in the \l
+ {22-qdoc-configuration-generalvariables.html#outputdir-variable}
+ {outputdir} variable.
+
+ \section1 Command Types
+
+ QDoc interprets three types of commands:
+
+ \list
+ \li \l {Topic Commands}
+ \li \l {Context Commands}
+ \li \l {Markup Commands}
+ \endlist
+
+ Topic commands identify the element you are documenting, for example
+ a C++ class, function, type, or an extra page of text
+ that doesn't map to an underlying C++ element.
+
+ Context commands tell QDoc how the element being documented
+ relates to other documented elements, for example, next and previous page
+ links, inclusion in page groups, or library modules. Context
+ commands can also provide information about the documented element
+ that QDoc can't get from the source files, for example, whether the
+ element is thread-safe, whether it is an overloaded or reimplemented function,
+ or whether it has been deprecated.
+
+ Markup commands tell QDoc how text and image elements in the
+ document should be rendered, or about the document's outline
+ structure.
+*/
+
diff --git a/src/qdoc/qdoc/doc/qdoc-manual-macros.qdoc b/src/qdoc/qdoc/doc/qdoc-manual-macros.qdoc
new file mode 100644
index 000000000..2d65b540b
--- /dev/null
+++ b/src/qdoc/qdoc/doc/qdoc-manual-macros.qdoc
@@ -0,0 +1,497 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page qdoc-macros.html
+ \previouspage 12-0-qdoc-commands-miscellaneous.html
+ \nextpage qdoc-macros-cmake-text-snippets.html
+ \title Macros
+
+ Use the \l {macro-variable}{macro} variable to create your own simple QDoc
+ commands.
+
+ Macros are useful for:
+
+ \list
+ \li Including recurring text, such as formatted text snippets,
+ into documentation.
+ \li Pulling in information, such as version strings, from the build
+ system into .documentation.
+ \li Maintaining a single location for strings that are likely to change
+ in the future, such as product names.
+ \li Making sure that a term or product name is used consistently.
+ \li Styling the generated documentation by using HTML and DocBook
+ elements, such as forced line breaks and special characters.
+ \li Using tabs to show examples of content for different programming
+ languages, operating systems, or tools, for example.
+ \endlist
+
+ \section1 Global Macros
+
+ For a set of predefined global macros that you can use in all documentation
+ projects and their current values, see
+ \l{https://github.com/qt/qtbase/blob/e4315204b1412d74842b3167c3eb9a49dc233355/doc/global/macros.qdocconf}
+ {/qtbase/doc/global/macros.qdocconf} and
+ \l{https://github.com/qt/qtbase/blob/e4315204b1412d74842b3167c3eb9a49dc233355/doc/global/htmltabs.qdocconf}
+ {/qtbase/doc/global/htmltabs.qdocconf}. They have macros that include:
+
+ \list
+ \li \l {CMake Text Snippets}
+ \li \l {HTML and DocBook Formatting}
+ \li \l {Product Names}
+ \li \l {Product Versions}
+ \li \l {Tabbed Content}
+ \li \l {youtube-macro}{Links to YouTube content}
+ \li \l {Miscellaneous Macros}
+ \endlist
+
+ \QOI copies the macros files to your Qt installation folder, so you can use
+ the macros even if you build the documentation using QDoc from an installed
+ Qt (as opposed to building QDoc yourself from sources).
+
+ \note The values of the global macros might change between Qt versions,
+ so the macros might be extended differently depending on the Qt version that
+ you use to build the documentation.
+
+ \section1 Adding Macros
+
+ You can add macros for a particular documentation project in the
+ .qdocconf file of the project or any .qdocconf file that you include into it.
+ If the macro could be useful in more than one documentation project, add it
+ to the global macros file.
+
+ For more information about the macro syntax and options, see
+ \l {macro-variable}{macro}.
+
+ \section1 Using Macros
+
+ You can use macros in the same way as the built-in QDoc commands in:
+
+ \list
+ \li Code source files, in documentation comments (.cpp, .qml)
+ \li Documentation source files (.qdoc, .qdocinc)
+ \endlist
+
+ For example:
+
+ \badcode
+ \QOI copies the macros files to your Qt installation folder.
+ \endcode
+
+ \section1 Obsolete Macros
+
+ The global macros file contains the following obsolete macros for
+ compatibility with older documentation sets.
+
+ \table
+ \header
+ \li Don't Use
+ \li Use
+ \row
+ \li \\gui
+ \li \l{uicontrol-command}{\\uicontrol}
+ \row
+ \li \\menu
+ \li \l{uicontrol-command}{\\uicontrol}
+ \row
+ \li \\param
+ \li \l{a-command}{\\a}
+ \row
+ \li \\return
+ \li Write \e Returns or \e returns.
+ \endtable
+
+ \sa {macro-variable}{macro}, {Unknown macro}
+*/
+
+/*!
+ \page qdoc-macros-cmake-text-snippets.html
+ \previouspage qdoc-macros.html
+ \nextpage qdoc-macros-formatting.html
+
+ \title CMake Text Snippets
+
+ See \l{https://github.com/qt/qtbase/blob/e4315204b1412d74842b3167c3eb9a49dc233355/doc/global/macros.qdocconf}
+ {/qtbase/doc/global/macros.qdocconf} for standard text snippets that you
+ should use when writing \l {Building with CMake}
+ {Qt-specific CMake documentation}:
+
+ \table
+ \header
+ \li Macro
+ \li Use
+ \row
+ \li \\cmakecommandandroidonly
+ \li For Android targets
+ \row
+ \li \\cmakecommandsince
+ \li To indicate which Qt version introduced the command
+ \row
+ \li \\cmakepropertyandroidonly
+ \li For Android targets
+ \row
+ \li \\cmakepropertyiosonly
+ \li For iOS targets
+ \row
+ \li \\cmakepropertysince
+ \li To indicate which Qt version introduced the property
+ \row
+ \li \\cmakepropertywebassemblyonly
+ \li For WebAssembly targets
+ \row
+ \li \\cmakevariableandroidonly
+ \li For Android targets
+ \row
+ \li \\cmakevariableiosonly
+ \li For iOS targets
+ \row
+ \li \\cmakevariablesince
+ \li To indicate which Qt version introduced the variable
+ \row
+ \li \\preliminarycmakecommand
+ \li To indicate that the command may change in future versions
+ \row
+ \li \\preliminarycmakeproperty
+ \li To indicate that the property may change in future versions
+ \row
+ \li \\preliminarycmakevariable
+ \li To indicate that the variable may change in future versions
+ \row
+ \li \\versionlessCMakeCommandsNote
+ \li For all commands, to tell users how to use versioned commands
+ \endtable
+
+ \sa {macro-variable}{macro}, {Macros}
+*/
+
+/*!
+ \page qdoc-macros-formatting.html
+ \previouspage qdoc-macros.html
+ \nextpage qdoc-macros-product-names.html
+
+ \title HTML and DocBook Formatting
+
+ See \l{https://github.com/qt/qtbase/blob/e4315204b1412d74842b3167c3eb9a49dc233355/doc/global/macros.qdocconf}
+ {/qtbase/doc/global/macros.qdocconf} for HTML and DocBook elements and
+ special characters that you can use in documentation source files.
+
+ \sa {Macros and Other Configurations}, {macro-variable}{macro}, {Macros}
+
+*/
+
+/*!
+ \page qdoc-macros-product-names.html
+ \previouspage qdoc-macros-formatting.html
+ \nextpage qdoc-macros-tabbed-content.html
+
+ \title Product Names
+
+ Use the following macros to refer to products:
+
+ \table
+ \header
+ \li Macro
+ \li Expands To
+ \row
+ \li \\B2Q
+ \li Boot to Qt
+ \row
+ \li \\B2QSS
+ \li Boot to Qt Software Stack
+ \row
+ \li \\B2QST
+ \li Boot to Qt Startup Screen
+ \row
+ \li \\IFW
+ \li Qt Installer Framework
+ \row
+ \li \\macos
+ \li macOS
+ \row
+ \li \\QA
+ \li Qt Assistant
+ \row
+ \li \\QB
+ \li Qt Bridge
+ \row
+ \li \\QBF
+ \li Qt Bridge for Figma
+ \row
+ \li \\QBPS
+ \li Qt Bridge for Adobe Photoshop
+ \row
+ \li \\QBSK
+ \li Qt Bridge for Sketch
+ \row
+ \li \\QBXD
+ \li Qt Bridge for Adobe XD
+ \row
+ \li \\QC
+ \li Qt Creator
+ \row
+ \li \\QD
+ \li Qt Widgets Designer
+ \row
+ \li \\QDS
+ \li Qt Design Studio
+ \row
+ \li \\QDV
+ \li Qt Design Viewer
+ \row
+ \li \\QfP
+ \li Qt for Python
+ \row
+ \li \\QL
+ \li Qt Linguist
+ \row
+ \li \\QMCU
+ \li Qt for MCUs
+ \row
+ \li \\QMLLS
+ \li QML Language Server
+ \row
+ \li \\QMLP
+ \li QML Profiler
+ \row
+ \li \\QMT
+ \li Qt Maintenance Tool
+ \row
+ \li \\QOI
+ \li Qt Online Installer
+ \row
+ \li \\QQEM
+ \li Qt Quick Effect Maker
+ \row
+ \li \\QQV
+ \li Qt QML Viewer
+ \row
+ \li \\QtAA
+ \li Qt for Android Automotive
+ \row
+ \li \\QtTAS
+ \li Qt Tools for Android Studio
+ \row
+ \li \\QUL
+ \li Qt Quick Ultralite
+ \endtable
+
+ \sa {macro-variable}{macro}, {Macros}
+*/
+
+/*!
+ \page qdoc-macros-product-versions.html
+ \previouspage qdoc-macros-product-names.html
+ \nextpage qdoc-macros-tabbed-content.html
+
+ \title Product Versions
+
+ Use the following macros to display version and status information about a
+ topic:
+
+ \table
+ \header
+ \li Macro
+ \li Expands To
+ \row
+ \li \\QtMajorVersion
+ \li The major version number based on the $QT_VER environment variable
+ \row
+ \li \\QtMinorVersion
+ \li The minor version number based on the $QT_VER environment variable
+ \row
+ \li \\QtVersion
+ \li The value of the $QT_VERSION environment variable
+ \row
+ \li \\QtVer
+ \li The value of the $QT_VER environment variable
+ \row
+ \li \\techpreview
+ \li The statement that a module or class is preliminary and might change
+ \endtable
+
+ \section1 \\techpreview
+
+ Appends the tech preview link to the brief sentence and adds the topic to
+ the \c tech_preview group.
+
+ Must be placed directly under a \brief command.
+
+ \sa {macro-variable}{macro}, {Macros}
+*/
+
+/*!
+ \page qdoc-macros-tabbed-content.html
+ \previouspage qdoc-macros-formatting.html
+ \nextpage qdoc-macros-youtube-videos.html
+
+ \title Tabbed Content
+
+ Use the following macros to create \e {tab groups} of labeled HTML tabs that
+ have comparable or alternative content. For example, you can show code
+ snippets for different programming languages.
+
+ \list
+ \li \l{tab-macro}{\\tab}
+ \li \l{tabcontent-macro}{\\tabcontent}
+ \li \l{endtabcontent-macro}{\\endtabcontent}
+ \endlist
+
+ \section1 Using Tab Macros
+
+ \badcode
+ \tab {name}{tab-id}{title}{checked}
+ \tabcontent {tab-id}
+ content
+ \endtabcontent
+ \endcode
+
+ \note These macros only work with the online template, so you usually
+ need to use conditional text to get good results also for the offline help.
+
+ For example, to show instructions for the CMake and qmake build systems in
+ tabs:
+
+ \badcode
+ \if defined(onlinedocs)
+ \tab {build-qt-app}{tab-cmake}{CMake}{checked}
+ \tab {build-qt-app}{tab-qmake}{qmake}{}
+ \tabcontent {tab-cmake}
+ \else
+ \section1 Using CMake
+ \endif
+ CMake-specific instructions go here
+ \if defined(onlinedocs)
+ \endtabcontent
+ \tabcontent {tab-qmake}
+ \else
+ \section1 Using qmake
+ \endif
+ qmake-specific instructions go here
+ \if defined(onlinedocs)
+ \endtabcontent
+ \endif
+ \endcode
+
+ \target tab-macro
+ \section1 \\tab
+
+ \badcode
+ \tab {name}{tab-id}{title}{checked}
+ \endcode
+
+ Specifies a tab in a tab group with a name, ID, title, and default state.
+
+ Use a unique \e {name} to specify which tabs belong to a tab group. That is,
+ all tabs in a group have the same name. To separate the tabs from each other,
+ give them each a unique \e {tab-id} within the group. Use this \e {tab-id}
+ as the value of the \\tabcontent macro to add content to the tab.
+
+ The \e {checked} argument selects the tab by default when the HTML page is
+ loaded. For the initially hidden tabs, pass an empty argument {}.
+
+ \target tabcontent-macro
+ \section1 \\tabcontent
+
+ \badcode
+ \tabcontent {tab-id}
+ \endcode
+
+ Adds content to the tab with the \e {tab-id} you specify.
+
+ \target endtabcontent-macro
+ \section1 \\endtabcontent
+
+ Marks the end of the tab content that you begin with \\tabcontent.
+
+ \sa {Macros}
+*/
+
+/*!
+ \page qdoc-macros-youtube-videos.html
+ \previouspage qdoc-macros-tabbed-content.html
+ \nextpage qdoc-macros-misc.html
+
+ \target youtube-macro
+ \title \\youtube
+
+ When generating online documentation, embeds a YouTube video in the HTML.
+ When generating offline documentation (.qch), adds an external link to the
+ video with a thumbnail image. The HTML docs show a thumbnail of the video
+ with a play button. You need to save the thumbnail in
+ \c {\images\extraimages\} in your project folder.
+
+ Use the following URL to open the thumbnail image in a browser:
+ \c {https://img.youtube.com/vi/<ID>/0.jpg}. The \c <ID> is the ID of
+ the video on YouTube. For example, if the URL to the video is
+ \c {https://www.youtube.com/watch?v=dQw4w9WgXcQ&feature=youtu.be},
+ the ID is \c dQw4w9WgXcQ. Save the image file as \c dQw4w9WgXcQ.jpg.
+
+ You must add the filename of the thumbnail file to
+ \c {\images\extraimages\extraimages.qdocconf}. For example:
+
+ \badcode
+ {HTML.extraimages,qhp.qtdesignstudio.extraFiles} += \
+ images/dQw4w9WgXcQ.jpg
+ \endcode
+
+ To add a link to the video in text, write:
+
+ \badcode
+ \youtube dQw4w9WgXcQ
+ \endcode
+
+ \sa {Macros}
+*/
+
+/*!
+ \page qdoc-macros-misc.html
+ \previouspage qdoc-macros-youtube-videos.html
+ \nextpage 21-0-qdoc-configuration.html
+
+ \title Miscellaneous Macros
+
+ You can use the following macros to affect the HTML output and to use
+ standard terminology:
+
+ \list
+ \li \l {borderedimage-macro}{\\borderedimage}
+ \li \l {examplecategory-macro}{\\examplecategory}
+ \li \l {key-macro}{\\key}
+ \li \l {nullptr-macro}{\\nullptr}
+ \li \l {summary-macro}{\\summary}
+ \endlist
+
+ \target borderedimage-macro
+ \section1 \\borderedimage
+
+ Like \l{image-command}{\\image}, but adds a border around the image.
+
+ \target examplecategory-macro
+ \section1 \\examplecategory
+
+ Describes the category an example is sorted to in the Qt Creator
+ \uicontrol Examples tab.
+
+ \target key-macro
+ \section1 \\key
+
+ Formats keyboard key names.
+
+ \target nullptr-macro
+ \section1 \\nullptr
+
+ Expands to the term to use for null pointers.
+
+ \target summary-macro
+ \section1 \\summary
+
+ Like \l{brief-command}{\\brief}, but replicates the sentence also as text.
+
+ Wrap the entire sentence within \c {{}}. For example:
+
+ \badcode
+ \summary {Creates a build target.}
+ \endcode
+
+ \sa {macro-variable}{macro}, {Macros}
+*/
diff --git a/src/qdoc/qdoc/doc/qdoc-manual-markupcmds.qdoc b/src/qdoc/qdoc/doc/qdoc-manual-markupcmds.qdoc
new file mode 100644
index 000000000..636e6f868
--- /dev/null
+++ b/src/qdoc/qdoc/doc/qdoc-manual-markupcmds.qdoc
@@ -0,0 +1,3158 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page 03-qdoc-commands-markup.html
+ \previouspage Naming Things
+ \nextpage Macros
+
+ \title Markup Commands
+
+ The markup commands indicate the generated documentation's visual
+ appearance and logical structure.
+
+ \list
+ \li \l {a-command} {\\a}
+ \li \l {annotatedlist-command} {\\annotatedlist}
+ \li \l {b-command} {\\b}
+ \li \l {badcode-command} {\\badcode}
+ \li \l {b-command} {\\bold} (deprecated, use \\b)
+ \li \l {br-command} {\\br}
+ \li \l {brief-command} {\\brief}
+ \li \l {c-command} {\\c}
+ \li \l {caption-command} {\\caption}
+ \li \l {code-command} {\\code}
+ \li \l {codeline-command} {\\codeline}
+ \li \l {details-command} {\\details}
+ \li \l {div-command} {\\div}
+ \li \l {dots-command} {\\dots}
+ \li \l {e-command} {\\e}
+ \li \l {else-command} {\\else}
+ \li \l {endif-command} {\\endif}
+ \li \l {footnote-command} {\\footnote}
+ \li \l {generatelist-command} {\\generatelist}
+ \li \l {header-command} {\\header}
+ \li \l {e-command} {\\i} (deprecated, use \\e)
+ \li \l {if-command} {\\if}
+ \li \l {image-command} {\\image}
+ \li \l {include-command} {\\include}
+ \li \l {inlineimage-command} {\\inlineimage}
+ \li \l {keyword-command} {\\keyword}
+ \li \l {l-command} {\\l}
+ \li \l {legalese-command} {\\legalese}
+ \li \l {li-command} {\\li}
+ \li \l {list-command} {\\list}
+ \li \l {meta-command} {\\meta}
+ \li \l {noautolist-command} {\\noautolist}
+ \li \l {li-command} {\\o} (deprecated, use \\li)
+ \li \l {note-command} {\\note}
+ \li \l {omit-command} {\\omit}
+ \li \l {printline-command} {\\printline}
+ \li \l {printto-command} {\\printto}
+ \li \l {printuntil-command} {\\printuntil}
+ \li \l {qml-command}{\\qml}
+ \li \l {quotation-command} {\\quotation}
+ \li \l {quotefile-command} {\\quotefile}
+ \li \l {quotefromfile-command} {\\quotefromfile}
+ \li \l {raw-command} {\\raw}
+ \li \l {row-command} {\\row}
+ \li \l {sa-command} {\\sa}
+ \li \l {sectionOne-command} {\\section1}
+ \li \l {sectionTwo-command} {\\section2}
+ \li \l {sectionThree-command} {\\section3}
+ \li \l {sectionFour-command} {\\section4}
+ \li \l {sincelist-command} {\\sincelist}
+ \li \l {skipline-command} {\\skipline}
+ \li \l {skipto-command} {\\skipto}
+ \li \l {skipuntil-command} {\\skipuntil}
+ \li \l {snippet-command} {\\snippet}
+ \li \l {span-command} {\\span}
+ \li \l {sub-command} {\\sub}
+ \li \l {sup-command} {\\sup}
+ \li \l {table-command} {\\table}
+ \li \l {tableofcontents-command} {\\tableofcontents}
+ \li \l {target-command} {\\target}
+ \li \qdoccmd tm
+ \li \l {tt-command} {\\tt}
+ \li \l {uicontrol-command} {\\uicontrol}
+ \li \l {underline-command} {\\underline}
+ \li \l {raw-command} {\\unicode}
+ \li \l {warning-command} {\\warning}
+ \li \l {backslash-sequence} {\\\\}
+ \li \l {endash-sequence} {\--}
+ \li \l {emdash-sequence} {-\--}
+ \endlist
+*/
+
+
+/*!
+ \page 04-qdoc-commands-textmarkup.html
+ \previouspage Markup Commands
+ \nextpage Document Structure
+
+ \title Text Markup
+
+ The text formatting commands indicate how text is to be rendered.
+
+ \target a-command
+ \section1 \\a (parameter marker)
+
+ The \\a command tells QDoc the next word is a formal parameter name.
+
+ A warning is emitted when a formal parameter is not documented or
+ is misspelled, so when you document a function you should mention
+ each formal parameter by name in the function description,
+ preceded by the \\a command. The parameter name is then rendered
+ in italics.
+
+ The formal parameter name may be enclosed between curly brackets,
+ but that isn't required.
+
+ \target c-command
+ \section1 \\c (code font)
+
+ The \\c command is used for rendering variable names, user-defined
+ class names, and C++ keywords (for example, \c int and \c for) in the code
+ font.
+
+ The command renders its argument using a monospace font. If the text to be
+ rendered in the code font contains spaces, enclose the entire text in curly
+ brackets:
+
+ \code
+ \c {QLineEdit::QLineEdit(const QString &contents, QWidget *parent) : QWidget(parent)}
+ \endcode
+
+ The \\c command accepts the special character \c \ within its
+ argument, which renders it as a normal character. So if you want
+ to use nested commands, you must use the \l {tt-command} {teletype
+ (\\tt)} command instead.
+
+ See also \l {tt-command} {\\tt} and \l {code-command} {\\code}.
+
+ \target details-command
+ \section1 \\details (collapsible)
+
+ The \\details and \\enddetails commands generates a collapsible <details>
+ element with a <summary> to control the hidden/visible state.
+
+ When generating HTML output, use the \\details and \\enddetails commands to
+ generate a collapsible \c{<details>} HTML element. The command takes an
+ optional summary string enclosed in curly braces. This optional argument
+ specifies a visible heading for the details.
+
+ For example, with the following input:
+ \badcode *
+ /\1!
+ \details {QDoc details}
+ \note You're looking at detailed information.
+ \enddetails
+ \1/
+ \endcode
+
+ If QDoc is generating HTML, it will translate these commands to:
+
+ \badcode
+ <summary>QDoc details</summary><div class="admonition note"><p><b>Note: </b>You're looking at detailed information.</p></div>
+ \endcode
+
+ QDoc renders this as:
+
+ \details {QDoc details}
+ \note You're looking at detailed information.
+ \enddetails
+
+ For any other output format, QDoc generates the contents as a normal paragraph,
+ ignoring the summary string. This command was introduced to QDoc in Qt6.6.
+
+ \target div-command
+ \section1 \\div
+
+ The \\div and \\enddiv commands delimit a large or small block of
+ text (which may include other QDoc commands) to which special
+ formatting attributes should be applied.
+
+ An argument must be provided in curly braces, as in the QDoc
+ comment shown below. The argument is not interpreted but is used
+ as attribute(s) of the tag that is output by QDoc.
+
+ For example, we might want to render an inline image so that it
+ floats to the right of the current block of text:
+
+ \badcode *
+ /\1!
+ \div {class="float-right"}
+ \inlineimage qml-column.png
+ \enddiv
+ \1/
+ \endcode
+
+ If QDoc is generating HTML, it will translate these commands to:
+
+ \code
+ <div class="float-right"><p><img src="images/qml-column.png" /></p></div>
+ \endcode
+
+ For HTML, the attribute value \e {float-right} then will refer to
+ a clause in the style.css file, which in this case could be:
+
+ \code
+ div.float-right
+ {
+ float: right; margin-left: 2em
+ }
+ \endcode
+
+ \note Note that the \b {\\div} command can be nested.
+
+ Below you can find an example taken from the index.qdoc file used to
+ generate index.html for Qt 4.7:
+
+ \code
+ \div {class="indexbox guide"}
+ \div {class="heading"}
+ Qt Developer Guide
+ \enddiv
+ \div {class="indexboxcont indexboxbar"}
+ \div {class="section indexIcon"} \emptyspan
+ \enddiv
+ \div {class="section"}
+ Qt is a cross-platform application and UI
+ framework. Using Qt, you can write web-enabled
+ applications once and deploy them across desktop,
+ mobile and embedded operating systems without
+ rewriting the source code.
+ \enddiv
+ \div {class="section sectionlist"}
+ \list
+ \li \l{Getting Started}
+ \li \l{Installation} {Installation}
+ \li \l{how-to-learn-qt.html} {How to learn Qt}
+ \li \l{tutorials.html} {Tutorials}
+ \li \l{Qt Examples} {Examples}
+ \li \l{qt4-7-intro.html} {What's new in Qt 4.7}
+ \endlist
+ \enddiv
+ \enddiv
+ \enddiv
+ \endcode
+
+ When all the class attribute values are defined as they are in the
+ style.css file that is used for rendering the Qt documentation,
+ the above example is rendered as:
+
+ \div {class="indexbox guide"}
+ \div {class="heading"}
+ Qt Developer Guide
+ \enddiv
+ \div {class="indexboxcont indexboxbar"}
+ \div {class="section indexIcon"} \emptyspan
+ \enddiv
+ \div {class="section"}
+ Qt is a cross-platform application and UI
+ framework. Using Qt, you can write web-enabled
+ applications once and deploy them across desktop,
+ mobile and embedded operating systems without
+ rewriting the source code.
+ \enddiv
+ \div {class="section sectionlist"}
+ \list
+ \li Getting Started
+ \li Installation
+ \li How to learn Qt
+ \li Tutorials
+ \li Examples
+ \li What's new in Qt 4.7
+ \endlist
+ \enddiv
+ \enddiv
+ \enddiv
+
+ See also \l {span-command} {\\span}.
+
+ \target span-command
+ \section1 \\span
+
+ The \\span command applies special formatting to a small block of text.
+
+ Two arguments must be provided, each argument in curly braces, as
+ shown in the QDoc comment below. The first argument is not
+ interpreted, but specifies the formatting attribute(s) of the tag
+ output by QDoc. The second argument is the text to be rendered with
+ the special formatting attributes.
+
+ For example, we might want to render the first word of each
+ element in a numeric list in blue.
+
+ \badcode *
+ /\1!
+ Global variables with complex types:
+ \list 1
+ \li \span {class="variableName"} {mutableComplex1} in globals.cpp at line 14
+ \li \span {class="variableName"} {mutableComplex2} in globals.cpp at line 15
+ \li \span {class="variableName"} {constComplex1} in globals.cpp at line 16
+ \li \span {class="variableName"} {constComplex2} in globals.cpp at line 17
+ \endlist
+ \1/
+ \endcode
+
+ Class \e {variableName} refers to a clause in your style.css.
+
+ \code
+ .variableName
+ {
+ font-family: courier;
+ color: blue
+ }
+ \endcode
+
+ Using the \e {variableName} clause shown above, the example is rendered as:
+
+ Global variables with complex types:
+ \list 1
+ \li \span {class="variableName"} {mutableComplex1} in globals.cpp at line 14
+ \li \span {class="variableName"} {mutableComplex2} in globals.cpp at line 15
+ \li \span {class="variableName"} {constComplex1} in globals.cpp at line 16
+ \li \span {class="variableName"} {constComplex2} in globals.cpp at line 17
+ \endlist
+
+ \note The \b span command does not cause a new paragraph to be
+ started.
+
+ See also \l {div-command} {\\div}.
+
+ \target tm-command
+ \section1 \\tm (trademark)
+
+ The \\tm command indicates that its argument is a trademark. QDoc appends
+ a trademark symbol `™` to the first occurrence of the argument when
+ generating a page.
+
+ In the project's configuration, the \c navigation.trademarkspage variable
+ is used for defining a title of a page that contains trademark-related
+ documentation.
+
+ \badcode
+ navigation.trademarkspage = Trademarks
+ \endcode
+
+ If set, each occurrence of the trademark symbol also links to the
+ trademarks page.
+
+ \note In section titles, the \\tm command is ignored and its argument
+ rendered as-is.
+
+ See also \l {sectionOne-command}{\\section1} and \qdocvar {navigation}.
+
+ \target tt-command
+ \section1 \\tt (teletype font)
+
+ The \\tt command renders its argument in a monospace font. This
+ command behaves just like the \l {c-command} {\\c} command, except
+ that \\tt allows you to nest QDoc commands within the argument
+ (e.g. \l {e-command} {\\e}, \l {b-command} {\\b} and \l
+ {underline-command} {\\underline}).
+
+ \badcode *
+ /\1!
+ After having populated the main container with
+ child widgets, \c setupUi() scans the main container's list of
+ slots for names with the form
+ \tt{on_\e{objectName}_\e{signalName}().}
+ \1/
+ \endcode
+
+ If the text to be rendered in the code font contains spaces, enclose the
+ entire text in curly brackets.
+
+ \code
+ \tt {QLineEdit::QLineEdit(const QString &contents, QWidget *parent) :QWidget(parent)}
+ \endcode
+
+ See also \l {c-command} {\\c}.
+
+ \target b-command
+ \section1 \\b
+
+ The \\b command renders its argument in bold font. This command used
+ to be called \\bold.
+
+ \badcode *
+ /\1!
+ This is regular text; \b {this text is
+ rendered using the \\b command}.
+ \1/
+ \endcode
+
+ \target br-command
+ \section1 \\br
+
+ The \\br command forces a line break.
+
+ \target e-command
+ \section1 \\e (emphasis, italics)
+
+ The \\e command renders its argument in a special font, normally italics. This
+ command used to be called \\i, which is now deprecated.
+
+ If the argument contains spaces or other punctuation, enclose the
+ argument in curly brackets.
+
+ \badcode *
+ /\1!
+ Here, we render \e {a few words} in italics.
+ \1/
+ \endcode
+
+ If you want to use other QDoc commands within an argument that
+ contains spaces, you always need to enclose the argument in
+ braces. But QDoc is smart enough to count parentheses, so you
+ don't need braces in cases like this:
+
+ \badcode *
+ /\1!
+ An argument can sometimes contain whitespaces,
+ for example: \e QPushButton(tr("A Brand New Button"))
+ \1/
+ \endcode
+
+ Finally, trailing punctuation is not included in an argument,
+ nor is "'s".
+
+ \target sub-command
+ \section1 \\sub
+
+ The \\sub command renders its argument lower than the baseline of
+ the regular text, using a smaller font.
+
+ \badcode *
+ /\1!
+ Definition (Range): Consider the sequence
+ {x\sub n}\sub {n > 1} . The set
+
+ {x\sub 2, x\sub 3, x\sub 4, ...} = {x\sub n ; n = 2, 3, 4, ...}
+
+ is called the range of the sequence.
+ \1/
+ \endcode
+
+ If the argument contains spaces or other punctuation, enclose the
+ argument in curly brackets.
+
+ \target sup-command
+ \section1 \\sup
+
+ The \\sup command renders its argument higher than
+ the baseline of the regular text, using a smaller font.
+
+ \badcode *
+ /\1!
+ The series
+
+ 1 + a + a\sup 2 + a\sup 3 + a\sup 4 + ...
+
+ is called the \i {geometric series}.
+ \1/
+ \endcode
+
+ If the argument contains spaces or other punctuation, enclose the
+ argument in curly brackets.
+
+ \target uicontrol-command
+ \section1 \\uicontrol
+
+ The \\uicontrol command is used to mark content as being used for UI
+ control elements. When using HTML, the output is rendered in bold.
+
+ See also \l {b-command}{\\b}.
+
+ \target underline-command
+ \section1 \\underline
+
+ The \\underline command renders its argument underlined.
+
+ \badcode *
+ /\1!
+ The \underline {F}ile menu gives the users the possibility
+ to edit an existing file, or save a new or modified
+ file, and exit the application.
+ \1/
+ \endcode
+
+ If the argument contains spaces or other punctuation, enclose the
+ argument in curly brackets.
+
+ \target backslash-sequence
+ \section1 \\\\ (double backslash)
+
+ The sequence \\\\ expands to a single backslash.
+
+ QDoc commands always start with a single backslash. To display a
+ single backslash in the text, you must type two backslashes. If
+ you want to display two backslashes, you must type four.
+
+ \badcode *
+ /\1!
+ The \\\\ command is useful if you want a
+ backslash to appear verbatim, for example,
+ writing C:\\windows\\home\\.
+ \1/
+ \endcode
+
+ However, if you want your text to appear in a monospace font as
+ well, you can use the \l {c-command} {\\c} command instead, which
+ accepts and renders the backslash as any other character. For
+ example:
+
+ \badcode *
+ /\1!
+ The \\c command is useful if you want a
+ backslash to appear verbatim, and the word
+ that contains it written in a monospace font,
+ like this: \c {C:\windows\home\}.
+ \1/
+ \endcode
+
+ \target endash-sequence
+ \section1 \-- (en dash)
+ QDoc renders double hyphens as an en dash. QDoc markup commands
+ designed to make their input appear verbatim---such as the \\c
+ command---won't replace the double hyphens with an en dash character.
+ For example:
+
+ \badcode *
+ /\1!
+ The \\c command -- useful if you want text in a monospace font --
+ is well documented.
+ \1/
+ \endcode
+
+ However, other commands may require that the hyphens are escaped to ensure
+ QDoc renders the output as expected. For example;
+
+ \badcode *
+ /\1!
+ This \l {endash-sequence}{link to the -- (endash) sequence}
+ isn't escaped and QDoc therefore renders an endash in the link
+ text. However, the escaped
+ \l {endash-sequence}{link to the \-- (endash) sequence}
+ renders both hyphens as intended.
+ \1/
+ \endcode
+
+ See also \l {emdash-sequence}{\-\-- (em dash)}.
+
+ \target emdash-sequence
+ \section1 -\-- (em dash)
+
+ QDoc renders triple hyphens as an en dash. QDoc markup commands
+ designed to make their input appear verbatim---such as the \\c
+ command---won't replace the triple hyphens with an en dash character.
+ For example:
+
+ \badcode *
+ /\1!
+ The \\c command---useful when you want text to be rendered
+ verbatim---is well documented.
+ \1/
+ \endcode
+
+ However, other commands may require that the hyphens are escaped to ensure
+ QDoc renders the output as expected. For example;
+
+ \badcode *
+ /\1!
+ This \l {emdash-sequence}{link to the --- (emdash) sequence}
+ isn't escaped and QDoc therefore renders an emdash in the link
+ text. However, the escaped
+ \l {emdash-sequence}{link to the -\-- (emdash) sequence}
+ renders both hyphens as intended.
+ \1/
+ \endcode
+
+ \note The escaped control sequence in this example is for the en dash.
+ This avoids a hyphen followed by an en dash in the output.
+
+ See also \l {endash-sequence}{\-- (en dash)}.
+*/
+
+
+/*!
+ \page 05-qdoc-commands-documentstructure.html
+ \previouspage Text Markup
+ \nextpage Including Code Inline
+
+ \title Document Structure
+
+ The document structuring commands are for dividing your document
+ into sections. QDoc supports four levels of section: \c \section1,
+ \c \section2, \c \section3, and \c \section4. The section commands
+ correspond to the traditional section, subsection, etc used in
+ outlining.
+
+ \section1 Section commands
+
+ In general a document structuring command considers everything
+ that follows it until the first line break as its argument. The
+ argument is rendered as the unit's title. If the title needs to be
+ spanned over several lines, make sure that each line (except the
+ last one) is ended with a backslash.
+
+ In total, there are four levels for sections in QDoc: \c \section1,
+ \c \section2, \c \section3 and \c \section4. These correspond to the
+ traditional section, subsection, subsubsection and subsubsubsection.
+
+ There is a strict ordering of the section units:
+
+ \code
+ section1
+ |
+ section2
+ |
+ section3
+ |
+ section4
+ \endcode
+
+ When sections are used, the first section command should be \c section1.
+
+ \badcode *
+ /\1!
+ \section1 Basic Qt
+
+ This is the first section.
+
+
+ \section2 Getting Started
+
+ This is the first subsection.
+
+
+ \section3 Hello Qt
+
+ This is the first subsubsection.
+
+
+ \section3 Making Connections
+
+ This is the second subsubsection.
+
+
+ \section3 Using the Reference Documentation
+
+ This is the third subsubsection.
+
+
+ \section2 Creating Dialogs
+
+ This is the second subsection.
+
+
+ \section3 Subclassing QDialog
+
+ This is the first subsubsection.
+
+ ...
+
+
+ \section1 Intermediate Qt
+
+ This is the second section.
+
+
+ \section2 Layout Management
+
+ This is the second section's first subsection.
+
+
+ \section3 Basic Layouts
+
+ This is the first subsubsection.
+
+ ...
+ \1/
+ \endcode
+
+ Each section is a logical unit in the document. The section
+ heading appears in the automatically generated table of contents
+ that normally appears in the upper right-hand corner of the page.
+
+ \target sectionOne-command
+ \section1 \\section1
+
+ The \\section1 command starts a new section.
+
+ See \l{section-commands} {Section commands} for an explanation of the various
+ section units, command argument, and rendering.
+
+ \target sectionTwo-command
+ \section1 \\section2
+
+ The \\section2 command starts a new section.
+
+ See \l{section-commands} {Section commands} for an explanation of the various
+ section units, command argument, and rendering.
+
+ \target sectionThree-command
+ \section1 \\section3
+
+ The \\section3 command starts a new section.
+
+ See \l{section-commands} {Section commands} for an explanation of the various
+ section units, command argument, and rendering.
+
+ \target sectionFour-command
+ \section1 \\section4
+
+ The \\section4 command starts a new section.
+
+ See \l{section-commands} {Section commands} for an explanation of the various
+ section units, command argument, and rendering.
+
+*/
+
+
+/*!
+ \page 06-qdoc-commands-includecodeinline.html
+ \previouspage Document Structure
+ \nextpage Including External Code
+
+ \title Including Code Inline
+
+ The following commands are used to render source code without
+ formatting. The source code begins on a new line, rendered in the
+ code.
+
+ \note Although most of these commands are for rendering C++
+ code, the
+ \l{07-0-qdoc-commands-includingexternalcode.html#snippet-command}
+ {\\snippet} and
+ \l{07-0-qdoc-commands-includingexternalcode.html#codeline-command}
+ {\\codeline} commands are preferred over the others. These
+ commands allow equivalent code snippets for other Qt language
+ bindings to be substituted for the C++ snippets in the
+ documentation.
+
+ \target code-command
+ \section1 \\code
+
+ The \\code and \\endcode commands enclose a snippet of source code.
+
+ \note The \l {c-command} {\\c} command can be used for short code
+ fragments within a sentence. The \\code command is for longer code
+ snippets. It renders the code verbatim in a separate paragraph in
+ a html <pre> element, and parses the enclosed snippet, creating links
+ to any known types in the code.
+
+ For documenting command-line instructions, shell scripts, or any
+ content that is not in a Qt language recognized by QDoc, use
+ \l {badcode-command}{\\badcode} instead.
+
+ When processing the \\code command, QDoc removes all indentation
+ that is common for the verbatim code blocks within a \c{/}\c{*!}
+ ... \c{*}\c{/} comment before it adds the standard indentation.
+
+ \note This doesn't apply to externally quoted code using the \l
+ {quotefromfile-command} {\\quotefromfile} or \l
+ {quotefile-command} {\\quotefile} command.
+
+ \code \\endcode *
+ /*!
+ \code
+ #include <QApplication>
+ #include <QPushButton>
+
+ int main(int argc, char *argv[])
+ {
+ ...
+ }
+ \1
+ \2/
+ \endcode
+
+ Other QDoc commands are disabled within \\code... \\endcode, and
+ the special character '\\' is accepted and rendered like the rest
+ of the code, unless it is followed by a digit and parameters were
+ passed to \\code.
+
+ \section2 Highlighting and autolinking
+
+ The \\code commands attempts to parse its contents as code of a
+ specific language, as defined in the \l {language-variable}{language}
+ configuration variable. This provides highlighting and automatic
+ linking to types detected in the code.
+
+ As an exception since QDoc version 6.4, when the \\code command
+ is used within a QML-specific \l {Topic Commands}{topic}, QDoc
+ first attempts to recognize the code as QML; for other topics,
+ the language configuration variable takes precedence. To
+ explicitly mark the code snippet as QML, use the \l {qml-command}
+ {\\qml} command instead.
+
+ \section2 Code snippet parameters
+
+ Since QDoc version 5.12, \\code command accepts also optional
+ parameters. Parameters are useful for injecting simple
+ strings into the code snippet. To inject a string to a specific
+ location in the snippet, add a backslash followed by a digit (1..8).
+ The digits correspond with the order of the argument list, where
+ arguments are separated by spaces.
+
+ For example:
+
+ \badcode 1 2 * endcode
+ /\3!
+ \code * hello
+ /\\1 \\2 \\1/
+ \\4
+ \3/
+ \endcode
+
+ For the above snippet, QDoc renders the word \e hello enclosed in
+ a C-style comment.
+
+ \section2 Including code from external files
+
+ To include code snippets from an external file, use the
+ \l{snippet-command}{\\snippet} and \l{codeline-command}
+ {\\codeline} commands.
+
+ See also \l {c-command}{\\c}, \l {qml-command}{\\qml},
+ \l {badcode-command}{\\badcode}, \l {quotefromfile-command}
+ {\\quotefromfile}, and \l {language-variable}{language}.
+
+ \target badcode-command
+ \section1 \\badcode
+
+ Similar to \l {code-command}{\\code}, \\badcode and \\endcode commands
+ enclose content that is rendered verbatim in a separate paragraph, but no
+ parsing or automatic link creation is performed. Instead, the content is
+ treated as plain text.
+
+ Substitute \\code with this command when documenting command-line
+ instructions, shell scripts or any other content that is not in a Qt
+ language, but should still be styled similarly to a \\code paragraph.
+
+ Like \\code, \\badcode accepts also optional parameters.
+
+ \target qml-command
+ \section1 \\qml
+
+ The \\qml and \\endqml commands enclose a snippet of QML source
+ code. Use these for proper syntax highlighting of QML code snippets.
+ The enclosed snippet must be complete as if it was a valid .qml file.
+ If the snippet is incomplete, QDoc will issue a warning and ignore the
+ snippet.
+
+ \badcode *
+ /\1!
+ \qml
+ import QtQuick 2.0
+
+ Row {
+ Rectangle {
+ width: 100; height: 100
+ color: "blue"
+ transform: Translate { y: 20 }
+ }
+ Rectangle {
+ width: 100; height: 100
+ color: "red"
+ transform: Translate { y: -20 }
+ }
+ }
+ \endqml
+ \1/
+ \endcode
+
+ Like the \l{code-command}{\\code} command, \\qml accepts optional
+ parameters.
+*/
+
+
+/*!
+ \page 07-0-qdoc-commands-includingexternalcode.html
+ \previouspage Including Code Inline
+ \nextpage Creating Links
+
+ \title Including External Code
+
+ The following commands enable you to include code snippets from
+ external files. You can make QDoc include the complete contents of
+ a file, or you can quote specific parts of the file and skip
+ others. The typical use of the latter is to quote a file chunk by
+ chunk.
+
+ \note Although all these commands can be used for rendering C++
+ code, the
+ \l{07-0-qdoc-commands-includingexternalcode.html#snippet-command}
+ {\\snippet} and
+ \l{07-0-qdoc-commands-includingexternalcode.html#codeline-command}
+ {\\codeline} commands are preferred over the others. These
+ commands allow equivalent code snippets for other Qt language
+ bindings to be substituted for the C++ snippets in the
+ documentation.
+
+ \target quotefile-command
+ \section1 \\quotefile
+
+ The \\quotefile command expands to the complete contents of the
+ file given as argument.
+
+ The command considers the rest of the line as part of its
+ argument, make sure to follow the file name with a line break.
+
+ The file's contents is rendered in a separate paragraph, using a
+ monospace font and the standard indentation. The code is shown
+ verbatim.
+
+ \badcode *
+ /\1!
+ This is a simple "Hello world" example:
+
+ \quotefile examples/main.cpp
+
+ It contains only the bare minimum you need
+ to get a Qt application up and running.
+ \1/
+ \endcode
+
+ See also \l {quotefromfile-command} {\\quotefromfile} and
+ \l {code-command} {\\code}.
+
+
+ \target quotefromfile-command
+ \section1 \\quotefromfile
+
+ The \\quotefromfile command opens the file given as argument for
+ quoting.
+
+ The command considers the rest of the line as part of its
+ argument, make sure to follow the file name with a line break.
+
+ The command is intended for use when quoting parts from file with
+ the walkthrough commands: \l {printline-command} {\\printline}, \l
+ {printto-command} {\\printto}, \l {printuntil-command}
+ {\\printuntil}, \l {skipline-command} {\\skipline}, \l
+ {skipto-command} {\\skipto}, \l {skipuntil-command}
+ {\\skipuntil}. This enables you to quote specific portions of a
+ file.
+
+ \badcode *
+ /\1!
+ The whole application is contained within
+ the \c main() function:
+
+ \quotefromfile examples/main.cpp
+
+ \skipto main
+ \printuntil app(argc, argv)
+
+ First we create a QApplication object using
+ the \c argc and \c argv parameters.
+
+ \skipto QPushButton
+ \printuntil resize
+
+ Then we create a QPushButton, and give it a reasonable
+ size using the QWidget::resize() function.
+
+ ...
+ \1/
+ \endcode
+
+ QDoc remembers which file it is quoting from, and the current
+ position in that file (see \l {file} {\\printline} for more
+ information). There is no need to "close" the file.
+
+ See also \l {quotefile-command} {\\quotefile}, \l {code-command}
+ {\\code} and \l {dots} {\\dots}.
+
+ \target printline-command
+ \section1 \\printline
+
+ The \\printline command expands to the line from the current
+ position to the next non-blank line of the current source file.
+
+ To ensure that the documentation remains synchronized with the
+ source file, a substring of the line must be specified as an
+ argument to the command. Note that the command considers the rest
+ of the line as part of its argument, make sure to follow the
+ substring with a line break.
+
+ The line from the source file is rendered as a separate paragraph,
+ using a monospace font and the standard indentation. The code is
+ shown verbatim.
+
+ \badcode *
+ /\1!
+ There has to be exactly one QApplication object
+ in every GUI application that uses Qt.
+
+ \quotefromfile examples/main.cpp
+
+ \printline QApplication
+
+ This line includes the QApplication class
+ definition. QApplication manages various
+ application-wide resources, such as the
+ default font and cursor.
+
+ \printline QPushButton
+
+ This line includes the QPushButton class
+ definition. The QPushButton widget provides a command
+ button.
+
+ \printline main
+
+ The main function...
+ \1/
+ \endcode
+
+ \target file
+
+ QDoc reads the file sequentially. To move the current position
+ forward you can use either of the \l {skipline-command}
+ {\\skip...} commands. To move the current position backward, you
+ can use the \l {quotefromfile-command} {\\quotefromfile} command
+ again.
+
+ \target substring
+
+ If the substring argument is surrounded by slashes it is
+ interpreted as a \l {QRegularExpression}{regular expression}.
+
+ \badcode *
+ /\1!
+ \quotefromfile examples/mainwindow.cpp
+
+ \skipto closeEvent
+ \printuntil /^\}/
+
+ Close events are sent to widgets that the users want to
+ close, usually by clicking \c File|Exit or by clicking
+ the \c X title bar button. By reimplementing the event
+ handler, we can intercept attempts to close the
+ application.
+ \1/
+ \endcode
+
+ (\l {widgets/scribble} {The complete example file...})
+
+ The regular expression \c /^\}/ makes QDoc print until the first
+ '}' character occurring at the beginning of the line without
+ indentation. /.../ encloses the regular expression, and '^' means
+ the beginning of the line. The '}' character must be escaped since
+ it is a special character in regular expressions.
+
+ QDoc will emit a warning if the specified substring or regular
+ expression cannot be located, i.e. if the source code has changed.
+
+ See also \l {printto-command} {\\printto} and \l
+ {printuntil-command} {\\printuntil}.
+
+ \target printto-command
+ \section1 \\printto
+
+ The \\printto command expands to all the lines from the current
+ position up to and \e excluding the next line containing a given
+ substring.
+
+ The command considers the rest of the line as part of its
+ argument, make sure to follow the substring with a line break. The
+ command also follows the same conventions for \l {file}
+ {positioning} and \l {substring} {argument} as the \l
+ {printline-command} {\\printline} command.
+
+ The lines from the source file are rendered in a separate
+ paragraph, using a monospace font and the standard
+ indentation. The code is shown verbatim.
+
+ \badcode *
+ /\1!
+ The whole application is contained within the
+ \c main() function:
+
+ \quotefromfile examples/main.cpp
+ \printto hello
+
+ First we create a QApplication object using the \c argc and
+ \c argv parameters...
+ \1/
+ \endcode
+
+ See also \l {printline-command} {\\printline} and \l
+ {printuntil-command} {\\printuntil}.
+
+ \target printuntil-command
+ \section1 \\printuntil
+
+ The \\printuntil command expands to all the lines from the current
+ position up to and \e including the next line containing a given
+ substring.
+
+ The command considers the rest of the line as part of its
+ argument, make sure to follow the substring with a line break. The
+ command also follows the same conventions for \l {file}
+ {positioning} and \l {substring} {argument} as the \l
+ {printline-command} {\\printline} command.
+
+ If \\printuntil is used without an argument, it expands to all the
+ lines from the current position to the end of the quoted file.
+
+ The lines from the source file are rendered in a separate
+ paragraph, using a monospace font and the standard
+ indentation. The code is shown verbatim.
+
+ \badcode *
+ /\1!
+ The whole application is contained within the
+ \c main() function:
+
+ \quotefromfile examples/main.cpp
+ \skipto main
+ \printuntil hello
+
+ First we create a QApplication object using the
+ \c argc and \c argv parameters, then we create
+ a QPushButton.
+ \1/
+ \endcode
+
+ See also \l {printline-command} {\\printline} and \l
+ {printto-command} {\\printto}.
+
+ \target skipline-command
+ \section1 \\skipline
+
+ The \\skipline command ignores the next non-blank line in the
+ current source file.
+
+ Doc reads the file sequentially, and the \\skipline command is
+ used to move the current position (omitting a line of the source
+ file). See the remark about \l {file} {file positioning} above.
+
+ The command considers the rest of the line as part of its
+ argument, make sure to follow the substring with a line break. The
+ command also follows the same conventions for \l {substring}
+ {argument} as the \l {printline-command} {\\printline} command,
+ and it is used in conjunction with the \l {quotefromfile-command}
+ {\\quotefromfile} command.
+
+ \badcode *
+ /\1!
+ QPushButton is a GUI push button that the user
+ can press and release.
+
+ \quotefromfile examples/main.cpp
+ \skipline QApplication
+ \printline QPushButton
+
+ This line includes the QPushButton class
+ definition. For each class that is part of the
+ public Qt API, there exists a header file of
+ the same name that contains its definition.
+ \1/
+ \endcode
+
+
+ See also \l {skipto-command} {\\skipto}, \l {skipuntil-command}
+ {\\skipuntil} and \l {dots} {\\dots}.
+
+ \target skipto-command
+ \section1 \\skipto
+
+ The \\skipto command ignores all the lines from the current
+ position up to and \e excluding the next line containing a given
+ substring.
+
+ QDoc reads the file sequentially, and the \\skipto command is used
+ to move the current position (omitting one or several lines of the
+ source file). See the remark about \l {file} {file positioning}
+ above.
+
+ The command considers the rest of the line as part of its
+ argument, make sure to follow the substring with a line break.
+
+ The command also follows the same conventions for \l {substring}
+ {argument} as the \l {printline-command} {\\printline} command,
+ and it is used in conjunction with the \l {quotefromfile-command}
+ {\\quotefromfile} command.
+
+ \badcode *
+ /\1!
+ The whole application is contained within
+ the \c main() function:
+
+ \quotefromfile examples/main.cpp
+ \skipto main
+ \printuntil }
+
+ First we create a QApplication object. There
+ has to be exactly one such object in
+ every GUI application that uses Qt. Then
+ we create a QPushButton, resize it to a reasonable
+ size ...
+ \1/
+ \endcode
+
+ See also \l {skipline-command} {\\skipline}, \l
+ {skipuntil-command} {\\skipuntil} and \l {dots} {\\dots}.
+
+ \target skipuntil-command
+ \section1 \\skipuntil
+
+ The \\skipuntil command ignores all the lines from the current
+ position up to and \e including the next line containing a given
+ substring.
+
+ QDoc reads the file sequentially, and the \\skipuntil command is
+ used to move the current position (omitting one or several lines
+ of the source file). See the remark about \l {file} {file
+ positioning} above.
+
+ The command considers the rest of the line as part of its
+ argument, make sure to follow the substring with a line break.
+
+ The command also follows the same conventions for \l {substring}
+ {argument} as the \l {printline-command} {\\printline} command,
+ and it is used in conjunction with the \l {quotefromfile-command}
+ {\\quotefromfile} command.
+
+ \badcode *
+ /\1!
+ The first thing we did in the \c main() function
+ was to create a QApplication object \c app.
+
+ \quotefromfile examples/main.cpp
+ \skipuntil show
+ \dots
+ \printuntil }
+
+ In the end we must remember to make \c main() pass the
+ control to Qt. QCoreApplication::exec() will return when
+ the application exits...
+ \1/
+ \endcode
+
+ See also \l {skipline-command} {\\skipline}, \l {skipto-command}
+ {\\skipto} and \l {dots} {\\dots}.
+
+ \target dots-command
+ \section1 \\dots
+
+ The \\dots command indicates that parts of the source file have
+ been omitted when quoting a file.
+
+ The command is used in conjunction with the \l
+ {quotefromfile-command} {\\quotefromfile} command, and should be
+ stated on its own line. The dots are rendered on a new line, using
+ a monospace font.
+
+ \badcode *
+ /\1!
+ \quotefromfile examples/main.cpp
+ \skipto main
+ \printuntil {
+ \dots
+ \skipuntil exec
+ \printline }
+ \1/
+ \endcode
+
+ The default indentation is 4 spaces, but this can be adjusted
+ using the command's optional argument.
+
+ \badcode *
+ /\1!
+ \dots 0
+ \dots
+ \dots 8
+ \dots 12
+ \dots 16
+ \1/
+ \endcode
+
+ See also \l {skipline-command} {\\skipline}, \l {skipto-command}
+ {\\skipto} and \l {skipuntil-command} {\\skipuntil}.
+
+ \target snippet-command
+ \section1 \\snippet
+
+ The \\snippet command causes a code snippet to be included
+ verbatim as preformatted text, which may be syntax highlighted.
+
+ Each code snippet is referenced by the file that holds it and by
+ a unique identifier for that file. Snippet files are typically
+ stored in a \c{snippets} directory inside the documentation
+ directory (for example, \c{$QTDIR/doc/src/snippets}).
+
+ For example, the following documentation references a snippet in a
+ file residing in a subdirectory of the documentation directory:
+
+ \code
+ \snippet snippets/textdocument-resources/main.cpp Adding a resource
+ \endcode
+
+ The text following the file name is the unique identifier for the
+ snippet. This is used to delimit the quoted code in the relevant
+ snippet file, as shown in the following example that corresponds to
+ the above \c{\snippet} command:
+
+ \code
+ ...
+ QImage image(64, 64, QImage::Format_RGB32);
+ image.fill(qRgb(255, 160, 128));
+
+//! [Adding a resource]
+ document->addResource(QTextDocument::ImageResource,
+ QUrl("mydata://image.png"), QVariant(image));
+//! [Adding a resource]
+ ...
+ \endcode
+
+ By default, QDoc looks for \c{//!} as a code snippet marker.
+ For \c{.pro}, \c{.py}, \c{.cmake}, and \c{CMakeLists.txt}
+ files, \c {#!} is detected. Finally, \c{<!--} is accepted in
+ \c{.html}, \c{.qrc}, \c{.ui}, \c{.xml}, and \c{.xq} files.
+
+ \target codeline-command
+ \section1 \\codeline
+
+ The \\codeline command inserts a blank line of preformatted
+ text. It is used to insert gaps between snippets without closing
+ the current preformatted text area and opening a new one.
+
+*/
+
+
+/*!
+ \page 08-qdoc-commands-creatinglinks.html
+ \previouspage Including External Code
+ \nextpage Including Images
+
+ \title Creating Links
+
+ These commands are for creating hyperlinks to classes, functions,
+ examples, and other targets.
+
+ \target l-command
+ \section1 \\l (link)
+
+ The \\l link command is used to create a hyperlink to many
+ different kinds of targets. The command's general syntax is:
+
+ \code
+ \l [ link criteria ] { link target } { link text }
+ \endcode
+
+ ...where the \c {link criteria} in square brackets are optional
+ but may be required when the \c {link target} is ambiguous. See
+ \l {Fixing Ambiguous Links} below.
+
+ Here is an example using the \\l command to link to an external page:
+
+ \badcode *
+ /\1!
+ Read the \l {http://doc.qt.io/qt-6/}
+ {Qt 6 Documentation} carefully.
+ \1/
+ \endcode
+
+ If the link target is equivalent to the link text, the second
+ argument can be omitted.
+
+ For example, if you have documentation like:
+
+ \badcode *
+ /\1!
+ \target assertions
+
+ Assertions make some statement about the text at the
+ point where they occur in the regexp, but they do not
+ match any characters.
+
+ ...
+
+ Regexps are built up from expressions, quantifiers, and
+ \l {assertions} {assertions}.
+ \1/
+ \endcode
+
+ You can simplify this as follows:
+
+ \badcode *
+ /\1!
+ \target assertions
+
+ Assertions make some statement about the text at the
+ point where they occur in the regexp, but they do not
+ match any characters.
+
+ ...
+
+ Regexps are built up from expressions, quantifiers, and
+ \l assertions.
+ \1/
+ \endcode
+
+ For the one-parameter version, the braces can often be omitted.
+ The \\l command supports several ways of linking:
+
+ \list
+
+ \li \c {\l QWidget} - The name of a class documented with the \l
+ {class-command} {\\class} command.
+
+ \li \c {\l QWidget::sizeHint()} - The signature of a function without
+ parameters. If a matching function without parameters can't be found,
+ the link is satisfied with the first matching function found.
+
+ \li \c {\l QWidget::removeAction(QAction* action)} - The signature
+ of a function with parameters. If an exact match is not found, the
+ link is not satisfied and QDoc reports a \e {Can't link to...} error.
+
+ \li \c {\l <QtGlobal>} - The subject of a \l {headerfile-command}
+ {\\headerfile} command.
+
+ \li \c {\l widgets/wiggly} - The relative path used in an \l
+ {example-command} {\\example} command.
+
+ \li \c {\l {QWidget Class Reference}} - The title used in a
+ \l {title-command} {\\title} command.
+
+ \li \c {\l {Introduction to QDoc}}- The text from one of the
+ \l{section-commands} {Section commands}.
+
+ \li \c {\l fontmatching} - The argument of a \l {target-command}
+ {\\target} command.
+
+ \li \c {\l {Shared Classes}} - A keyword named in a \l
+ {keyword-command} {\\keyword} command.
+
+ \li \c {\l http://qt-project.org/} - A URL.
+
+ \endlist
+
+ QDoc also tries to make a link out of any word that doesn't
+ resemble a normal English word, for example, Qt class names or
+ functions, like QWidget or QWidget::sizeHint(). In these cases,
+ the \\l command can actually be omitted, but by using the command,
+ you ensure that QDoc will emit a warning if it cannot find the
+ link target. In addition, if you only want the function name to
+ appear in the link, you can use the following syntax:
+
+ \list
+ \li \c {\l {QWidget::} {sizeHint()}}
+ \endlist
+
+ \section2 Fixing Ambiguous Links
+
+ Because of the modularization of Qt beginning with Qt 5.0, The
+ possibility that QDoc will have to deal with ambiguous links has
+ increased. An ambiguous link is one that has a matching target in
+ more than one Qt module, e.g. the same section title can appear in
+ more than one Qt module, or the name of a C++ class in one module
+ can also be the name of a QML type in another module. A real
+ example in Qt5 is the name Qt itself. Qt is the name of both a C++
+ namespace in QtCore and a QML type in QtQml.
+
+ Suppose we want to link to the \l {Qt} {Qt C++ namespace}. At the
+ time QDoc generated this HTML page, that link was correct. Does
+ it still go to the C++ namespace? Qdoc generated that link from
+ this link command:
+
+ \list
+ \li \c {\l {Qt} {Qt C++ namespace}}
+ \endlist
+
+ Now suppose we want to link to the \l [QML] {Qt} {Qt QML type}.
+ At the time QDoc generated this HTML page, that link was also
+ correct, but we had to use this link command:
+
+ \list
+ \li \c {\l [QML] {Qt} {Qt QML type}}
+ \endlist
+
+ The \e {QML} in \e {square brackets} tells QDoc to accept a
+ matching target only if the target is on a QML page. Qdoc actually
+ finds the C++ namespace target first, but since that target is on
+ a C++ page, QDoc ignores it and keeps looking until it finds the
+ same target on a QML page.
+
+ Without the guidance in the \e{\\l command} in the optional \e
+ {square bracket} argument, QDoc links to the first matching target
+ it finds. QDoc can't warn that the link was ambiguous in such
+ cases because it doesn't know that another matching target exists.
+
+ \section2 What arguments can appear in square brackets?
+
+ A link command with square bracket argument has the following syntax:
+ \list
+ \c {\l [QML|CPP|DOC|QtModuleName] {link target} {link text}}
+ \endlist
+
+ The \e {square bracket} argument is only allowed in the \c {\l
+ (link)} command. The example above shows how \c QML is used as the
+ \e {square brackets} argument to force QDoc to match a QML target.
+ Most often, this will be a QML type, but it can also be a QML
+ member function of property.
+
+ In the example, QDoc didn't need a \e {square bracket} argument to
+ find the Qt C++ namespace page, because that one was the first
+ matching target QDoc found anyway. However, to force QDoc to find
+ a C++ target when a matching QML target gets in the way, \c CPP
+ can be used as the \e {square bracket} argument. For example:
+
+ \list
+ \li \c {\l [CPP] {Qt} {Qt C++ namespace}}
+ \endlist
+
+ ...will force QDoc to ignore the Qt QML type and continue
+ searching until it matches the Qt C++ namespace.
+
+ If the link target is neither a C++ nor a QML entity, \c {DOC} can
+ be used as the \e {square bracket} argument to prevent QDoc from
+ matching either of those. At this writing, there were no cases of
+ ambiguous links where using \c {DOC} was required.
+
+ Often, the documentor knows which Qt module the link target is
+ in. When the module name is known, use the module name as the \e
+ {square bracket} argument. In the example above, if we know that
+ the QML type named Qt is located in the QtQml module, we can write
+ the link command like this:
+
+ \list
+ \li \c {\l [QtQml] {Qt} {Qt QML type}}
+ \endlist
+
+ When a module name is used as the \e {square bracket} argument,
+ QDoc will search for the link target in that module only. This
+ makes searching for link targets more efficient.
+
+ Finally, the module name and entity type arguments can be
+ combined, separated by a blank, so something like this is also
+ allowed:
+
+ \list
+ \li \c {\l [CPP QtQml] {Window} {C++ class Window}}
+ \endlist
+
+ As of this writing, there were no cases where combining the two
+ was required.
+
+ See also \l {sa-command} {\\sa}, \l {target-command} {\\target},
+ and \l {keyword-command} {\\keyword}.
+
+
+ \target sa-command
+ \section1 \\sa (see also)
+
+ The \\sa command defines a list of links that will be rendered in
+ a separate "See also" section at the bottom of the documentation
+ unit.
+
+ The command takes a comma-separated list of links as its
+ argument. If the line ends with a comma, you can continue
+ the list on the next line. The general syntax is:
+
+ \code
+ \sa {the first link}, {the second link},
+ {the third link}, ...
+ \endcode
+
+ QDoc will automatically try to generate "See also" links
+ interconnecting a property's various functions. For example, a
+ setVisible() function will automatically get a link to visible()
+ and vice versa.
+
+ In general, QDoc will generate "See also" links that interconnect
+ the functions that access the same property. It recognizes four
+ different syntax versions:
+
+ \list
+ \li \c property()
+ \li \c setProperty()
+ \li \c isProperty()
+ \li \c hasProperty()
+ \endlist
+
+ The \\sa command supports the same kind of links as the \l
+ {l-command} {\\l} command.
+
+ \badcode *
+ /\1!
+ Appends the actions \a actions to this widget's
+ list of actions.
+
+ \sa removeAction(), QMenu, addAction()
+ \1/
+ void QWidget::addActions(QList<QAction *> actions)
+ {
+ ...
+ }
+ \endcode
+
+ See also \l {l-command} {\\l}, \l {target-command} {\\target} and
+ \l {keyword-command} {\\keyword}.
+
+
+ \target target-command
+ \section1 \\target
+
+ The \\target command names a place in the documentation that you
+ can link to using the \l {l-command} {\\l (link)} and \l
+ {sa-command} {\\sa (see also)} commands.
+
+ \e{The text up to the line break becomes the target name}. Be sure
+ to follow the target name with a line break. Curly brackets are not
+ required around the target name, but they may be required when the
+ target name is used in a link command. See below.
+
+ \badcode *
+ /\1!
+ \target capturing parentheses
+ \section1 Capturing Text
+
+ Parentheses allow us to group elements together so that
+ we can quantify and capture them.
+
+ ...
+ \1/
+ \endcode
+
+ The target name \e{capturing parentheses} can be linked to
+ in the following way:
+
+ \list
+ \li \c {\l {capturing parentheses}}
+ \endlist
+
+ \note The brackets in the link example are required because the
+ target name contains spaces.
+
+ \section2 \\target in a \\table
+ When you use the \\target command in a table, make sure that the
+ \\target command follows a \l {li-command}{\\li}-command (table
+ cell), and that it's either on a separate line, or the last content
+ that occurs in the line it's in. This is due to how the \\target
+ command works; it consumes anything up to the next line break as
+ its parameter. In other words, if you have a table and need a
+ \\target within it, make sure that it follows the following
+ structure:
+
+ \badcode
+ \table
+ \row
+ \li \target my-target
+ My text goes here.
+ \li This is my next table cell.
+ \endtable
+ \endcode
+
+ See also \l {l-command} {\\l}, \l {sa-command} {\\sa} and \l
+ {keyword-command} {\\keyword}.
+
+ \target keyword-command
+ \section1 \\keyword
+
+ The \\keyword command names a place in the documentation that you
+ can link to using the \l {l-command} {\\l (link)} and \l
+ {sa-command} {\\sa (see also)} commands.
+
+ The \\keyword command is like the \l {target-command} {\\target}
+ command, except when linking to keyword the link goes to the top of
+ the QDoc comment where the \\keyword appears in. If you want to
+ create a link target to a \c section unit within a \\page, use
+ \\target instead. A keyword can be linked from anywhere using a
+ simple syntax.
+
+ Keywords must be unique over all the documents processed during
+ the QDoc run. The command uses the rest of the line as its
+ argument. Be sure to follow the keyword with a line break.
+
+
+ \badcode *
+ /\1!
+ \class QRegularExpression
+ \reentrant
+ \brief The QRegularExpression class provides pattern
+ matching using regular expressions.
+ \ingroup tools
+ \ingroup misc
+ \ingroup shared
+
+ \keyword regular expression
+
+ Regular expressions, or "regexps", provide a way to
+ find patterns within text.
+
+ ...
+ \1/
+ \endcode
+
+ The location marked with the keyword can be linked to with:
+
+ \badcode *
+ /\1!
+ When a string is surrounded by slashes, it is
+ interpreted as a \l {regular expression}.
+ \1/
+ \endcode
+
+ If the keyword text contains spaces, the brackets are required.
+
+ See also \l {l-command} {\\l (link)}, \l {sa-command} {\\sa (see
+ also)} and \l {target-command} {\\target}.
+
+*/
+
+
+/*!
+ \page 09-qdoc-commands-includingimages.html
+ \previouspage Creating Links
+ \nextpage Tables and Lists
+
+ \title Including Images
+
+ The graphic commands makes it possible to include images in the
+ documentation. The images can be rendered as separate paragraphs,
+ or within running text.
+
+ \target image-command
+ \section1 \\image
+
+ The \\image command expands to the image specified by its first
+ argument, and renders it centered as a separate paragraph.
+
+ The command takes two arguments. The first argument is the name of
+ the image file. The second argument is optional and is a simple
+ description of the image, equivalent to the HTML alt="" in an image
+ tag. The description is used for tooltips and for browsers that don't
+ support images, like the Lynx text browser.
+
+ The remaining text \e{after} the file name is the optional,
+ description argument. Be sure to follow the file name or the
+ description with a line break. Curly brackets are required if the
+ description argument spans multiple lines.
+
+ \badcode *
+ /\1!
+ Qt is a C++ toolkit for cross-platform GUI application development.
+
+ \image happyguy.jpg "Happy guy"
+
+ Qt provides single-source portability across Microsoft
+ Windows, macOS, Linux, and all major commercial Unix
+ variants. It is also available for embedded devices.
+ \1/
+ \endcode
+
+ See also \l {inlineimage-command} {\\inlineimage} and \l
+ {caption-command} {\\caption}.
+
+ \target inlineimage-command
+ \section1 \\inlineimage
+
+ The \\inlineimage command expands to the image specified by its
+ argument. The image is rendered inline with the rest of the text.
+
+ The command takes two arguments. The first argument is the name of
+ the image file. The second argument is optional and is a simple
+ description of the image within braces {}, equivalent to the HTML
+ alt="" in an image tag. The description is used for tooltips, and
+ for when a browser doesn't support images, like the Lynx text browser.
+
+ The most common use of the \\inlineimage command is in lists and
+ tables. Here is an example of including inline images in a list:
+
+ \badcode *
+ /\1!
+ \list 1
+ \li \inlineimage happy.gif {Oh so happy, I am a caption!}
+ \li \inlineimage happy.gif Oh so happy, but I'm not a caption.
+ \endlist
+ \1/
+ \endcode
+
+ Here is an example of including inline images in a table:
+
+ \badcode *
+ /\1!
+ \table
+ \header
+ \li Qt
+ \li Qt Creator
+ \row
+ \li \inlineimage happy.gif {Oh so happy!}
+ \li \inlineimage happy.gif Oh so happy!
+ \row
+ \li \inlineimage happy.gif Oh so happy!
+ \li \inlineimage happy.gif {Oh so happy!}
+ \endtable
+ \1/
+ \endcode
+
+ The command can also be used to insert an image inline with the
+ text.
+
+ \badcode *
+ /\1!
+ \inlineimage training.jpg {Qt Training} The Qt Programming course is
+ offered as a five day Open Enrollment Course. The classes are open to
+ the public. Although the course is open to anyone who wants to learn,
+ attendees should have significant experience in C++ development to
+ derive maximum benefit from the course.
+ \1/
+ \endcode
+
+ See also \l {image-command} {\\image} and \l {caption-command} {\\caption}.
+
+ \target caption-command
+ \section1 \\caption
+
+ The \\caption command provides a caption for an image.
+
+ The command takes all the text up to the end of the paragraph to
+ be the caption. Experiment until you get the effect you want.
+
+ \badcode *
+ /\1!
+ \table 100%
+ \row
+ \li \image windows-pushbutton.png
+ \caption The QPushButton widget provides a command button.
+ \li \image windows-toolbutton.png
+ \caption The QToolButton class provides a quick-access button to commands
+ or options, usually used inside a QToolBar.
+ \endtable
+ \1/
+ \endcode
+
+ See also \l {image-command} {\\image} and \l {inlineimage-command}
+ {\\inlineimage}
+*/
+
+
+/*!
+ \page 10-qdoc-commands-tablesandlists.html
+ \previouspage Including Images
+ \nextpage Special Content
+
+ \title Tables and Lists
+
+ These commands enable creating lists and tables. A list is
+ rendered left aligned as a separate paragraph. A table is rendered
+ centered as a separate paragraph. The table width depends on the
+ width of its contents.
+
+ \target table-command
+ \section1 \\table
+
+ The \\table and \\endtable commands delimit the contents of a
+ table.
+
+ The command accepts a single argument specifying the table's width
+ as a percentage of the page width:
+
+ \badcode *
+ /\1!
+ \table 100 %
+
+ ...
+
+ \endtable
+ \1/
+ \endcode
+
+ The code above ensures that the table will fill all available
+ space. If the table's width is smaller than 100 %, the table will
+ be centered in the generated documentation.
+
+ A table can contain headers, rows and columns. A row starts with a
+ \l {row-command} {\\row} command and consists of cells, each of which
+ starts with an \l {li-command} {\\li} command. There is also a \l
+ {header-command} {\\header} command which is a special kind of row
+ that has a special format.
+
+ \badcode *
+ /\1!
+ \table
+ \header
+ \li Qt Core Feature
+ \li Brief Description
+ \row
+ \li \l {Signal and Slots}
+ \li Signals and slots are used for communication
+ between objects.
+ \row
+ \li \l {Layout Management}
+ \li The Qt layout system provides a simple
+ and powerful way of specifying the layout
+ of child widgets.
+ \row
+ \li \l {Drag and Drop}
+ \li Drag and drop provides a simple visual
+ mechanism which users can use to transfer
+ information between and within applications.
+ \endtable
+ \1/
+ \endcode
+
+ You can also make cells span several rows and columns. For
+ example:
+
+ \badcode *
+ /\1!
+ \table
+ \header
+ \li {3,1} This header cell spans three columns,
+ but only one row.
+ \row
+ \li {2, 1} This table cell spans two columns,
+ but only one row
+ \li {1, 2} This table cell spans only one column,
+ but two rows.
+ \row
+ \li A regular table cell
+ \li A regular table cell
+ \endtable
+ \1/
+ \endcode
+
+ See also \l {header-command} {\\header}, \l {row-command} {\\row} and \l {li-command} {\\li}.
+
+ \target header-command
+ \section1 \\header
+
+ The \\header command indicates that the following table cells are
+ the current table's column headers.
+
+ The command can only be used within the \l{table-command}
+ {\\table...\\endtable} commands. A header can contain several
+ cells. A cell is created with the \l {li-command} {\\li} command.
+
+ A header cell's text is centered within the table cell and
+ rendered using a bold font.
+
+ \badcode *
+ /\1!
+ \table
+ \header
+ \li Qt Core Feature
+ \li Brief Description
+ \row
+ \li \l {Signal and Slots}
+ \li Signals and slots are used for communication
+ between objects.
+ \endtable
+ \1/
+ \endcode
+
+ See also \l {table-command} {\\table}, \l {row-command} {\\row} and \l {li-command} {\\li}.
+
+ \target row-command
+ \section1 \\row
+
+ The \\row command begins a new row in a table. The \l {li-command}
+ {\\li items} that belong in the new row will immediately follow the
+ \\row.
+
+ The command can only be used within the \l{table-command}
+ {\\table...\\endtable} commands. A row can contain several
+ cells. A cell is created with the \l {li-command} {\\li} command.
+
+ The background cell color of each row alternates between two
+ shades of grey, making it easier to distinguish the rows from each
+ other. The cells' contents is left aligned.
+
+ \badcode *
+ /\1!
+ \table
+ \header
+ \li Qt Core Feature
+ \li Brief Description
+ \row
+ \li \l {Signal and Slots}
+ \li Signals and slots are used for communication
+ between objects.
+ \row
+ \li \l {Layout Management}
+ \li The Qt layout system provides a simple
+ and powerful way of specifying the layout
+ of child widgets.
+ \row
+ \li \l {Drag and Drop}
+ \li Drag and drop provides a simple visual
+ mechanism which users can use to transfer
+ information between and within applications.
+ \endtable
+ \1/
+ \endcode
+
+ See also \l {table-command} {\\table}, \l {header-command}
+ {\\header}, and \l {li-command} {\\li}.
+
+ \target value-command
+ \section1 \\value
+
+ The \\value command starts the documentation of a C++ enum item.
+
+ The command's first argument is the value name. The value name may
+ be preceded by an optional \e since clause enclosed in square
+ brackets. The value description follows the value name. The description
+ ends at the next blank line or \\value. The arguments are rendered in a
+ table.
+
+ \note To include images in the \\value description, use the
+ \l {inlineimage-command}{\\inlineimage} command.
+
+ Without a \e since clause, a \\value command could look like this:
+
+ \code
+ \value QtInfoMsg A message generated by the qInfo() function.
+ \endcode
+
+ The same command with a \e since clause would look like this:
+
+ \code
+ \value [since 5.5] QtInfoMsg A message generated by the qInfo() function.
+ \endcode
+
+ The documentation will be located in the associated class, header
+ file or namespace documentation. See the \l {enum-command}
+ {\\enum} documentation for an example.
+
+ \note Since Qt 5.4, \\value command can also be used outside the
+ \l {enum-command} {\\enum} topic. In this case, QDoc renders a
+ two-column table listing the constant name (taken as-is from the
+ first argument) and its description. This can be used, for
+ example, in \l {qmlproperty-command}{\\qmlproperty} topic for
+ documenting acceptable values for a QML enumeration property.
+
+ See also \l {enum-command} {\\enum} and \l {omitvalue-command} {\\omitvalue}.
+
+ \target omitvalue-command
+ \section1 \\omitvalue
+
+ The \\omitvalue command excludes a C++ enum item from the
+ documentation.
+
+ The command's only mandatory argument is the name of the enum item
+ that will be omitted. If the enum item is followed by a single-line
+ description, that is also omitted.
+
+ See the \l {enum-command} {\\enum} documentation for an example.
+
+ See also \l {enum-command} {\\enum} and \l {value-command}
+ {\\value} \l {since-command}{\\since}
+
+ \target list-command
+ \section1 \\list
+
+ The \\list and \\endlist commands delimit a list of items.
+
+ Create each list item with the \l {li-command} {\\li} command. A
+ list always contains one or more items. Lists can be nested. For
+ example:
+
+ \badcode *
+ /\1!
+ \list
+ \li Qt Reference Documentation: Getting Started
+ \list
+ \li How to Learn Qt
+ \li Installation
+ \list
+ \li Qt/X11
+ \li Qt/Windows
+ \li Qt/Mac
+ \li Qt/Embedded
+ \endlist
+ \li Tutorial and Examples
+ \endlist
+ \endlist
+ \1/
+ \endcode
+
+ The \\list command takes an optional argument providing
+ alternative appearances for the list items.
+
+ \badcode *
+ /\1!
+ \list
+ \li How to Learn Qt
+ \li Installation
+ \li Tutorial and Examples
+ \endlist
+ \1/
+ \endcode
+
+ If you provide 'A' as an argument to the \\list command, the
+ bullets are replaced with characters in alphabetical order:
+
+ \list A
+ \li How to Learn Qt
+ \li Installation
+ \li Tutorial and Examples
+ \endlist
+
+ If you replace 'A' with '1', the list items are numbered in
+ ascending order:
+
+ \list 1
+ \li How to Learn Qt
+ \li Installation
+ \li Tutorial and Examples
+ \endlist
+
+ If you provide 'i' as the argument, the bullets are replaced with
+ roman numerals:
+
+ \list i
+ \li How to Learn Qt
+ \li Installation
+ \li Tutorial and Examples
+ \endlist
+
+ Finally, you can make the list items appear with roman numbers
+ following in ascending order if you provide 'I' as the optional
+ argument:
+
+ \list I
+ \li How to Learn Qt
+ \li Installation
+ \li Tutorial and Examples
+ \endlist
+
+ You can also make the listing start at any character or number by
+ simply provide the number or character you want to start at. For
+ example:
+
+ \badcode *
+ /\1!
+ \list G
+ \li How to Learn Qt
+ \li Installation
+ \li Tutorial and Examples
+ \endlist
+ \1/
+ \endcode
+
+ See also \l {li-command} {\\li}.
+
+ \target li-command
+ \section1 \\li (table cell, list item)
+
+ The \\li command marks a table cell or a list item. This command
+ is only used in \l{table-command} {tables} and \l{list-command}
+ {lists}.
+
+ It considers everything as its argument until the next \\li command, until the
+ next \l {table-command} {\\endtable}, or \l {list-command} {\\endlist}
+ command. See \l {table-command} {\\table} and \l {list-command} {\\list}
+ for examples.
+
+ If the command is used within a table, you can also specify
+ how many rows or columns the item should span.
+
+ \badcode *
+ /\1!
+ \table
+ \header
+ \li {3,1} This header cell spans three columns
+ but only one row.
+ \row
+ \li {2, 1} This table item spans two columns
+ but only one row
+ \li {1, 2} This table item spans only one column,
+ but two rows.
+ \row
+ \li A regular table item
+ \li A regular table item
+ \endtable
+ \1/
+ \endcode
+
+ If not specified, the item will span one column and one row.
+
+ See also \l {table-command} {\\table}, \l {header-command}
+ {\\header}, and \l {list-command} {\\list}.
+
+*/
+
+
+/*!
+ \page 11-qdoc-commands-specialcontent.html
+ \previouspage Tables and Lists
+ \nextpage Miscellaneous
+
+ \title Special Content
+
+ The document contents commands identify parts of the documentation,
+ parts with a special rendering, conceptual meaning or
+ function.
+
+ \target quotation-command
+ \section1 \\quotation
+
+ The \\quotation and \\endquotation commands delimit a long quotation.
+
+ The text in the delimited block is surrounded by
+ \b{<blockquote>} and \b{</blockquote>} in the html output,
+ e.g.:
+
+ \badcode *
+ /\1!
+ Although the prospect of a significantly broader market is
+ good news for Firstlogic, the notion also posed some
+ challenges. Dave Dobson, director of technology for the La
+ Crosse, Wisconsin-based company, said:
+
+ \quotation
+ As our solutions were being adopted into new
+ environments, we saw an escalating need for easier
+ integration with a wider range of enterprise
+ applications.
+ \endquotation
+ \1/
+ \endcode
+
+ The text in the \b{\\quotation} block will appear in the generated HTML as:
+
+ \badcode
+ <blockquote>
+ <p>As our solutions were being adopted into new environments,
+ we saw an escalating need for easier integration with a wider
+ range of enterprise applications.</p>
+ </blockquote>
+ \endcode
+
+ The built-in style sheet for most browsers will render the
+ contents of the <blockquote> tag with left and right
+ indentations. The example above would be rendered as:
+
+ \quotation
+ As our solutions were being adopted into new
+ environments, we saw an escalating need for easier
+ integration with a wider range of enterprise
+ applications.
+ \endquotation
+
+ But you can redefine the \b{<blockquote>} tag in your style.css file.
+
+ \target footnote-command
+ \section1 \\footnote
+
+ The \\footnote and \\endfootnote commands delimit a footnote.
+
+ The footnote is rendered at the bottom of the page.
+
+ \warning The \b{\\footnote} and \b{\\endfootnote} commands
+ have not been implemented. The footnote is rendered as a regular
+ HTML paragraph.
+
+ \target note-command
+ \section1 \\note
+
+ The \\note command defines a new paragraph preceded by "Note:"
+ in bold.
+
+ \target tableofcontents-command
+ \section1 \\tableofcontents
+
+ The \\tableofcontents command has been disabled because QDoc
+ now generates a table of contents automatically.
+
+ The automatically generated table of contents appears in the upper
+ righthand corner of the page.
+
+ \target brief-command
+ \section1 \\brief
+
+ The \\brief command introduces a one-sentence description of
+ any of the \l{Topic Commands}.
+
+ The brief text is used to introduce the documentation of the
+ associated object, and in lists generated using the \l
+ {generatelist-command} {\\generatelist} command and the \l
+ {annotatedlist-command} {\\annotatedlist} command.
+
+ The brief text will be displayed in the documentation
+ for that particular topic.
+
+ For example the boolean QWidget::isWindow property:
+
+ \badcode *
+ /\1!
+ \property QWidget::isActiveWindow
+ \brief Whether this widget's window is the active window.
+
+ The active window is the window that contains the widget that
+ has keyboard focus.
+
+ When popup windows are visible, this property is \c true
+ for both the active window \e and the popup.
+
+ \sa activateWindow(), QApplication::activeWindow()
+ \1/
+ \endcode
+
+ and the QWidget::geometry property
+
+ \badcode *
+ /\1!
+ \property QWidget::geometry
+ \brief The geometry of the widget relative to its parent and
+ excluding the window frame.
+
+ When changing the geometry, the widget, if visible,
+ receives a move event (moveEvent()) and/or a resize
+ event (resizeEvent()) immediately.
+
+ ...
+
+ \sa frameGeometry(), rect(), ...
+ \1/
+ \endcode
+
+ When the \\brief command is used to describe a class, we recommend
+ using a complete sentence like this:
+
+ \badcode
+ The <classname> class is|provides|contains|specifies...
+ \endcode
+
+ \warning Do not repeat your detailed description with the same sentence as
+ the brief statement will be the first paragraph of the detailed
+ description.
+
+ \badcode *
+ /\1!
+ \class PreviewWindow
+ \brief The PreviewWindow class is a custom widget
+ displaying the names of its currently set
+ window flags in a read-only text editor.
+
+ The PreviewWindow class inherits QWidget. The widget
+ displays the names of its window flags set with the
+ setWindowFlags() function. It is also provided with a
+ QPushButton that closes the window.
+
+ ...
+
+ \sa QWidget
+ \1/
+ \endcode
+
+ Using \\brief in a \l{namespace-command}{\\namespace}:
+
+ \badcode *
+ /\1!
+ \namespace Qt
+
+ \brief The Qt namespace contains miscellaneous identifiers
+ used throughout the Qt library.
+ \1/
+ \endcode
+
+ Using \\brief in a \l{headerfile-command}{\\headerfile}:
+
+ \badcode *
+ /\1!
+ \headerfile <QtGlobal>
+ \title Global Qt Declarations
+
+ \brief The <QtGlobal> header file provides basic
+ declarations and is included by all other Qt headers.
+
+ \sa <QtAlgorithms>
+ \1/
+ \endcode
+
+ See also \l{property-command} {\\property}, \l{class-command}
+ {\\class}, \l{namespace-command} {\\namespace} and
+ \l{headerfile-command} {\\headerfile}.
+
+ \target legalese-command
+ \section1 \\legalese
+
+ The \\legalese and \\endlegalese commands delimit a license agreement.
+
+ In the generated HTML, the delimited text is surrounded by a \b
+ {<div class="LegaleseLeft">} and \b {</div>} tags.
+
+ An example of a license agreement enclosed in \\legalese
+ and \\endlegalese:
+
+ \badcode *
+ /\1!
+ \legalese
+ Copyright 1996 Daniel Dardailler.
+
+ Permission to use, copy, modify, distribute, and sell this
+ software for any purpose is hereby granted without fee,
+ provided that the above copyright notice appear in all
+ copies and that both that copyright notice and this
+ permission notice appear in supporting documentation, and
+ that the name of Daniel Dardailler not be used in
+ advertising or publicity pertaining to distribution of the
+ software without specific, written prior permission. Daniel
+ Dardailler makes no representations about the suitability of
+ this software for any purpose. It is provided "as is"
+ without express or implied warranty.
+
+ Modifications Copyright 1999 Matt Koss, under the same
+ license as above.
+ \endlegalese
+ \1/
+ \endcode
+
+ It will appear in the generated HTML as:
+
+ \badcode
+ <div class="LegaleseLeft">
+ <p>Copyright 1996 Daniel Dardailler.</p>
+ <p>Permission to use, copy, modify, distribute, and sell
+ this software for any purpose is hereby granted without fee,
+ provided that the above copyright notice appear in all
+ copies and that both that copyright notice and this
+ permission notice appear in supporting documentation, and
+ that the name of Daniel Dardailler not be used in
+ advertising or publicity pertaining to distribution of the
+ software without specific, written prior permission. Daniel
+ Dardailler makes no representations about the suitability of
+ this software for any purpose. It is provided "as is"
+ without express or implied warranty.</p>
+
+ <p>Modifications Copyright 1999 Matt Koss, under the same
+ license as above.</p>
+ </div>
+ \endcode
+
+ If the \\endlegalese command is omitted, QDoc will process the
+ \\legalese command but considers the rest of the documentation
+ page as the license agreement.
+
+ Ideally, the license text is located with the licensed code.
+
+ Elsewhere, the documentation identified as \e{\\legalese} command
+ can be accumulated using \l {generatelist-command} {\\generatelist}
+ with \c {legalese} as the argument. This is useful for generating
+ an overview of the license agreements associated with the source
+ code.
+
+ \note The output of the \c {\generatelist legalese} command includes
+ the \\legalese texts in the current documentation project only. If
+ the current documentation project depends on other modules, their
+ license texts will not be listed.
+
+ \target warning-command
+ \section1 \\warning
+
+ The \\warning command prepends "Warning:" to the command's
+ argument, in bold font.
+
+ \badcode *
+ /\1!
+ Qt::HANDLE is a platform-specific handle type
+ for system objects. This is equivalent to
+ \c{void *} on Windows and macOS, and to
+ \c{unsigned long} on X11.
+
+ \warning Using this type is not portable.
+ \1/
+ \endcode
+*/
+
+
+/*!
+ \page 12-0-qdoc-commands-miscellaneous.html
+ \previouspage Special Content
+ \nextpage The QDoc Configuration File
+
+ \title Miscellaneous
+
+ These commands provide miscellaneous functions connected to the
+ visual appearance of the documentation, and to the process of
+ generating the documentation.
+
+ \target annotatedlist-command
+ \section1 \\annotatedlist
+
+ The \\annotatedlist command expands to a list of the members of a
+ group, each member listed with its \e {brief} text. Below is an
+ example from the Qt Reference Documentation:
+
+ \badcode *
+ /\1!
+ ...
+ \section1 Drag and Drop Classes
+
+ These classes deal with drag and drop and the necessary mime type
+ encoding and decoding.
+
+ \annotatedlist draganddrop
+ \1/
+ \endcode
+
+ This generates a list of all the C++ classes and/or QML types in
+ the \e{draganddrop} group. A C++ class or QML type in the
+ \e{draganddrop} group will have \e{\\ingroup draganddrop} in its
+ \e{\\class} or \e{\\qmltype} comment.
+
+
+ \target qtcmakepackage-command
+ \section1 \\qtcmakepackage
+
+ Use the \\qtcmakepackage command to add CMake package information to classes
+ and namespaces. This information will then appear in a table at the top of
+ the class or namespace documentation page. For example:
+
+ \badcode *
+ /*!
+ \namespace Foo
+ \inheaderfile Bar
+ \qtcmakepackage Baz
+ \brief A namespace.
+
+ ...
+ \1/
+ \endcode
+
+ QDoc will output this as
+
+ \quotation
+ \raw HTML
+ <h1 class="title">Foo Namespace</h1>
+ <p>A namespace. <a>More...</a></p>
+ <div class="table"><table class="alignedsummary">
+ <tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Bar&gt;</span></td></tr>
+ <tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS Baz)</td></tr>
+ </table></div>
+ \endraw
+ \endquotation
+
+
+ \target qtcmaketargetitem-command
+ \section1 \\qtcmaketargetitem
+
+ Use the \\qtcmaketargetitem command to override the \e {item} part of the
+ CMake \c{target_link_libraries} information that's added to classes and
+ namespaces. The command must be used in conjunction with the
+ \l{module-command}{\\module} and \l{qtcmakepackage}{\\qtcmakepackage}}
+ commands.
+
+ \e{See also} \l{module-command}{\\module} and
+ \l{qtcmakepackage}{\\qtcmakepackage}}
+
+
+ \target generatelist-command
+ \section1 \\generatelist
+
+ The \\generatelist command expands to a list of links to the
+ documentation entities grouped with an \l {ingroup-command}
+ {\\ingroup} command or entities that match one of the arguments
+ listed below. An example from the Qt Reference Documentation:
+
+ \badcode *
+ /\1!
+ \page classes.html
+ \title All Classes
+
+ For a shorter list that only includes the most
+ frequently used classes, see \l{Qt's Main Classes}.
+
+ \generatelist classes Q
+ \1/
+ \endcode
+
+ This generates the \e {All Classes} page. The command accepts the
+ following arguments:
+
+ \section2 \c {<group-name>}
+
+ With a group name as the only argument, QDoc lists all entities that
+ use the \c {\ingroup <group-name>} command.
+
+ \target table example
+ \section2 \c annotatedclasses
+
+ The \c annotatedclasses argument provides a table containing the
+ names of all the classes, and a description of each class. Each
+ class name is a link to the class's reference documentation. For
+ example:
+
+ \table
+ \row
+ \li QDial
+ \li Rounded range control (like a speedometer or potentiometer)
+ \row
+ \li QDialog
+ \li The base class of dialog windows
+ \row
+ \li QDir
+ \li Access to directory structures and their contents
+ \endtable
+
+ A C++ class is documented with the \l {class-command} {\\class}
+ command. The annotation for the class is taken from the argument
+ of the class comment's \l {brief-command} {\\brief} command.
+
+ \section2 \c annotatedexamples
+
+ The \c annotatedexamples argument provides a complete list of all
+ examples as a set of tables containing the titles of all the
+ examples, and a description of each example. Each title is a
+ link to the example's documentation.
+
+ A separate table for each module (that has documented examples)
+ is generated, provided that the module has defined a
+ navigation.landingpage configuration variable. The \e landingpage
+ variable is used as a title for a header that precedes each table.
+
+ \section2 \c annotatedattributions
+
+ The \c annotatedattributions argument provides a complete list of all
+ attributions as a set of tables containing the titles of all the
+ attributions, and a description of each attribution. Each title is a
+ link to the attribution's page.
+
+ A separate table for each module (that has attributions)
+ is generated, provided that the module has defined a
+ navigation.landingpage configuration variable. The \e landingpage
+ variable is used as a title for a header that precedes each table.
+
+ \target list example
+ \section2 \c {classes <prefix>}
+
+ The \c classes argument provides a complete alphabetical list of
+ the classes. The second argument, \c{<prefix>}, is the common
+ prefix for the class names. The class names will be sorted on the
+ character that follows the common prefix. e.g. The common prefix
+ for the Qt classes is \c Q. The common prefix argument is
+ optional. If no common prefix is provided, the class names will
+ be sorted on their first character.
+
+ Each class name becomes a link to the class's reference
+ documentation. This command is used to generate the
+ \e {All Classes} page this way:
+
+ \badcode *
+ /\1!
+ \page classes.html
+ \title All Classes
+ \ingroup classlists
+
+ \brief Alphabetical list of classes.
+
+ This is a list of all Qt classes. For classes that
+ have been deprecated, see the \l{Obsolete Classes}
+ list.
+
+ \generatelist classes Q
+ \1/
+ \endcode
+
+ A C++ class is documented with the \l {class-command} {\\class}
+ command.
+
+ \section2 \c classesbymodule
+
+ When this argument is used, a second argument is required, which
+ specifies the module whose classes are to be listed. QDoc
+ generates a table containing those classes. Each class is listed
+ with the text of its \l{brief-command} {\\brief} command.
+
+ For example, this command can be used on a module page as follows:
+
+ \badcode *
+ /\1!
+ \page phonon-module.html
+ \module Phonon
+ \title Phonon Module
+ \ingroup modules
+
+ \brief Contains namespaces and classes for multimedia functionality.
+
+ \generatelist{classesbymodule Phonon}
+
+ ...
+ \1/
+ \endcode
+
+ Each class that is a member of the specified module must be marked
+ with the \l {inmodule-command} {\\inmodule} command in its \\class
+ comment.
+
+ \section2 \c qmltypesbymodule
+
+ Similar to \c classesbymodule argument, but used for listing the
+ QML types (excluding QML value types) from the QML module specified
+ with the second argument.
+
+ \note Support for this argument was introduced in QDoc 5.6.
+
+ \section2 \c qmlvaluetypesbymodule
+
+ Similar to \c qmltypesbymodule argument, but lists QML value
+ types instead.
+
+ \note Support for this argument was introduced in QDoc 6.7.
+
+ \section2 \c functionindex
+
+ The \c functionindex argument provides a complete alphabetical
+ list of all the documented member functions. It is normally used
+ only to generate the \e {Qt function index} page
+ this way:
+
+ \badcode *
+ /\1!
+ \page functions.html
+ \title All Functions
+ \ingroup funclists
+
+ \brief All documented Qt functions listed alphabetically with a
+ link to where each one is declared.
+
+ This is the list of all documented member functions and global
+ functions in the Qt API. Each function has a link to the
+ class or header file where it is declared and documented.
+
+ \generatelist functionindex
+ \1/
+ \endcode
+
+ \section2 \c legalese
+
+ The \c legalese argument tells QDoc to generate a list of licenses in
+ the current documentation project. Each license is identified using
+ the \l {legalese-command} {\\legalese} command.
+
+ \section2 \c overviews
+
+ The \c overviews argument is used to tell QDoc to generate a list
+ by concatenating the contents of all the \l {group-command}
+ {\\group} pages. Qt uses it to generate the \e {overviews} page
+ this way:
+
+ \badcode *
+ /\1!
+ \page overviews.html
+
+ \title All Overviews and HOWTOs
+
+ \generatelist overviews
+ \1/
+ \endcode
+
+ \section2 \c attributions
+
+ The \c attributions argument is used to tell QDoc to generate a list
+ of attributions in the documentation.
+
+ \section2 \c related
+
+ The \c related argument is used in combination with the \l
+ {group-command} {\\group} and \l {ingroup-command} {\\ingroup}
+ commands to list all the overviews related to a specified
+ group. For example, the page for the \e {Programming with Qt}
+ page is generated this way:
+
+ \badcode *
+ /\1!
+ \group qt-basic-concepts
+ \title Programming with Qt
+
+ \brief The basic architecture of the Qt cross-platform application and UI framework.
+
+ Qt is a cross-platform application and UI framework for
+ writing web-enabled applications for desktop, mobile, and
+ embedded operating systems. This page contains links to
+ articles and overviews explaining key components and
+ techniuqes used in Qt development.
+
+ \generatelist {related}
+ \1/
+ \endcode
+
+ Each page listed on this group page contains the command:
+
+ \code
+ \ingroup qt-basic-concepts
+ \endcode
+
+ \target if-command
+ \section1 \\if
+
+ The \\if command and the corresponding \\endif command
+ enclose parts of a QDoc comment that only will be included if
+ the condition specified by the command's argument is true.
+
+ The command reads the rest of the line and parses it as an C++ #if
+ statement.
+
+ \badcode *
+ /\1!
+ \if defined(opensourceedition)
+
+ \note This edition is for the development of
+ \l{Qt Open Source Edition} {Free and Open Source}
+ software only; see \l{Qt Commercial Editions}.
+
+ \endif
+ \1/
+ \endcode
+
+ This QDoc comment will only be rendered if the \c
+ opensourceedition preprocessor symbol is defined, and specified in
+ the \l {defines-variable} {defines} variable in the configuration
+ file to make QDoc process the code within #ifdef and #endif:
+
+ \badcode
+ defines = opensourceedition
+ \endcode
+
+ You can also define the preprocessor symbol manually on the
+ command line. For more information see the documentation of the \l
+ {defines-variable} {defines} variable.
+
+ See also \l{endif-command} {\\endif}, \l{else-command} {\\else},
+ \l {defines-variable} {defines} and \l {falsehoods-variable}
+ {falsehoods}.
+
+ \target endif-command
+ \section1 \\endif
+
+ The \\endif command and the corresponding \\if command
+ enclose parts of a QDoc comment that will be included if
+ the condition specified by the \l {if-command} {\\if} command's
+ argument is true.
+
+ For more information, see the documentation of the \l {if-command}
+ {\\if} command.
+
+ See also \l{if-command} {\\if}, \l{else-command} {\\else}, \l
+ {defines-variable} {defines} and \l {falsehoods-variable}
+ {falsehoods}.
+
+ \target else-command
+ \section1 \\else
+
+ The \\else command specifies an alternative if the
+ condition in the \l {if-command} {\\if} command is false.
+
+ The \\else command can only be used within \l {if-command}
+ {\\if...\\endif} commands, but is useful when there is only two
+ alternatives.
+
+ \target include-command
+ \section1 \\include
+
+ The \\include command sends all or part of the file specified by
+ its first argument to the QDoc input stream to be processed as a
+ QDoc comment snippet.
+
+ The command is useful when some snippet of commands or text is to
+ be used in multiple places in the documentation. Use the \\include
+ command wherever you want to insert a snippet into the documentation.
+ The file containing the snippet to include, must be located under the
+ path(s) listed in the \l{sourcedirs-variable}{sourcedirs} or
+ \l{exampledirs-variable}{exampledirs} QDoc configuration variable.
+ It can be either any source file parsed by QDoc (or even the same one
+ where \\include command is used), or any other text file. To store
+ snippets in a separate file that is not meant to be parsed by QDoc,
+ use a file extension that is not listed in
+ \l{sources.fileextensions-variable}{sources.fileextensions};
+ for example, \c .qdocinc.
+
+ The command can have one or more arguments. The first
+ argument is always a file name. The contents of the file must be
+ QDoc input, in other words, a sequence of QDoc commands and text, but
+ without the enclosing QDoc comment \c{/}\c{*!} ... \c{*}\c{/} delimiters.
+ If you want to include the entire named file, leave the second argument
+ empty. If you want to include only part of the file, see the
+ \l{2-argument-form}{two argument form} below. Here is an example
+ of the one argument form:
+
+ \badcode *
+ /\1!
+ \page corefeatures.html
+ \title Core Features
+
+ \include examples/signalandslots.qdocinc
+ \include examples/objectmodel.qdocinc
+ \include examples/layoutmanagement.qdocinc
+ \1/
+ \endcode
+
+ QDoc renders this page \l{corefeatures.html} {as shown here}.
+
+ \target 2-argument-form
+ \section2 \\include filename snippet-identifier
+
+ It is a waste of time to make a separate \c .qdocinc file for every
+ QDoc include snippet you want to use in multiple places in the
+ documentation, especially given that you probably have to put the
+ copyright/license notice in every one of these files. If you
+ have multiple snippets to be included, you can put them all in a
+ single file and surround each one with:
+
+ \badcode
+ //! [snippet-id1]
+
+ QDoc commands and text...
+
+ //! [snippet-id1]
+
+ //! [snippet-id2]
+
+ More QDoc commands and text...
+
+ //! [snippet-id2]
+ \endcode
+
+ Then you can use the two-argument form of the command:
+
+ \badcode
+ \include examples/signalandslots.qdocinc snippet-id2
+ \include examples/objectmodel.qdocinc another-snippet-id
+ \endcode
+
+ The sequence of QDoc commands and text found between the two tags
+ with the same name as the second argument is sent to the QDoc input
+ stream. You can even have nested snippets.
+
+ \note Snippet identifiers work also within documentation comment
+ (\beginqdoc .. \endqdoc) blocks, so it's not necessary to use a
+ separate \c .qdocinc file. When processing a comment block, QDoc
+ removes any \c {//!} comment lines from the generated output.
+
+ \section2 Extra arguments
+
+ Since QDoc 6.3, any further arguments passed to the \\include command
+ are used for injecting strings into the included content. To inject a
+ string to a specific location in the content, add a backslash followed
+ by a digit (1..9). The digits correspond with the order of the argument
+ list. Enclose arguments in curly braces to ensure that QDoc renders the
+ entire argument, including possible whitespace characters, as you expect.
+
+ \important Each additional argument (including the snippet ID) must be
+ enclosed in braces. If you want to include the entire file,
+ use an empty snippet ID: \c {{}}.
+
+ For example, given the following snippet in a file \c includes.qdocinc:
+
+ \badcode
+ //! [usage]
+ To enable \e{\1}, select \uicontrol {\2} > \uicontrol Enable.
+ //! [usage]
+ \endcode
+
+ Then, the following \\include line:
+
+ \badcode
+ \include includes.qdocinc {usage} {detailed output} {Verbose}
+ \endcode
+
+ Renders
+ \quotation
+ To enable \e {detailed output}, select \uicontrol {Verbose} >
+ \uicontrol Enable.
+ \endquotation
+
+ \target meta-command
+ \section1 \\meta
+
+ The \\meta command is used for adding metadata to documentation.
+ The command has two arguments: the first argument is the name of the
+ metadata attribute, and the second argument is the value for the attribute.
+ Each argument should be enclosed in curly brackets, as shown in this
+ example:
+
+ \badcode *
+ /\1!
+ \example demos/coffee
+ \title Coffee Machine
+ \brief A Qt Quick application with a state-based custom user interface.
+
+ \meta {tags} {quick,embedded,states,touch}
+ \meta {category} {Application Examples}
+ \1/
+ \endcode
+
+ A number of metadata attributes have a specific purpose:
+
+ \b {Example Metadata}
+
+ Another use for \\meta command is to include metadata (tags) in
+ \l {example-command}{\\example} documentation. By default, QDoc
+ generates example tags based on the example's \l {title-command}{\\title}
+ and module name. These tags are displayed in Qt Creator's Welcome mode,
+ helping users navigate the list of examples.
+
+ Additional tags can be created with \c {\meta {tag} {tag1}}
+ or \c {\meta {tags} {tag1,[tag2,...]}}.
+ For example:
+
+ \badcode *
+ /\1!
+ \example helloworld
+ \title Hello World Example
+ \meta {tags} {tutorial,basic}
+ \1/
+ \endcode
+
+ This would result in the following tags: \e {tutorial,basic,hello,world}.
+ Common words such as \e example are ignored.
+
+ \b {Excluding Examples}
+
+ Marking an example \e broken will exclude it from the generated manifest
+ file, effectively removing it from Qt Creator's Welcome mode.
+
+ \badcode
+ \meta {tag} {broken}
+ \endcode
+
+ \b {Example Install Paths}
+
+ The \\meta command combined with an argument \c installpath specifies the
+ location of an installed example. This value overrides the one that is set
+ using the \c examplesinstallpath configuration variable.
+
+ \badcode *
+ /\1!
+ \example helloworld
+ \title Hello World Example
+ \meta {installpath} {tutorials}
+ \1/
+ \endcode
+
+ See also \l {examplesinstallpath}.
+
+ \b {Status}
+
+ A \c status argument for the \\meta command adds a custom status description
+ for a \l {class-command}{\\class} or a \l {qmltype-command}{\\qmltype}. This
+ description will then appear in a table at the top of the type reference page.
+
+ \badcode *
+ /\1!
+ \class QNativeInterface::QAndroidApplication
+ \meta {status} {Android-specific}
+ \1/
+ \endcode
+
+ See also \l {Status}{status-related commands}.
+
+ \target noautolist-command
+ \section1 \\noautolist
+
+ The \\noautolist command indicates that the annotated list of C++
+ classes or QML types, which is automatically generated at the
+ bottom of the C++ or QML module page should be omitted, because
+ the classes or types have been listed manually. This command can
+ also be used with the \l {group-command}{\\group} command to omit
+ the list of group members, when they are listed manually.
+
+ The command must stand on its own line. See \l {Qt Quick Controls QML Types}
+ for an example. The page is generated from \c {qtquickcontrols2-qmlmodule.qdoc}.
+ There you will find a QDoc comment containing the \c{\qmlmodule} command for
+ the QtQuick.Controls module. The same comment contains a \c {\noautolist}
+ command to disable automatic list generation, and a \l {generatelist-command}
+ {\\generatelist} to list the QML types in a specific section of the document.
+
+ This command was introduced in QDoc 5.6.
+
+ Since Qt 5.10, this command can be applied also to \l{example-command}
+ {\\example} documentation, where it causes the automatically generated
+ list of files and images belonging to an example project to be omitted.
+
+ \target omit-command
+ \section1 \\omit
+
+ The \\omit command and the corresponding \\endomit command
+ delimit parts of the documentation that you want QDoc to skip. For
+ example:
+
+ \badcode *
+ /\1!
+ \table
+ \row
+ \li Basic Widgets
+ \li Basic GUI widgets such as buttons, comboboxes
+ and scrollbars.
+
+ \omit
+ \row
+ \li Component Model
+ \li Interfaces and helper classes for the Qt
+ Component Model.
+ \endomit
+
+ \row
+ \li Database Classes
+ \li Database related classes, e.g. for SQL databases.
+ \endtable
+ \1/
+ \endcode
+
+ \target raw-command
+ \section1 \\raw (avoid!)
+
+ The \\raw command and the corresponding
+ \\endraw command delimit a block of raw mark-up language code.
+
+ \warning Avoid using this command if possible. If you are trying to
+ generate special table or list behavior, try to get the behavior you want
+ using the \l {span-command} {\\span} and \l {div-command} {\\div}
+ commands in your \l {table-command} {\\table} or \l {list-command}
+ {\\list}.
+
+ The command takes an argument specifying the code's format.
+
+ QDoc generates the given code only when generating the format that
+ was specified by the user.
+
+ For example, "\\raw HTML" will only generate code when QDoc
+ generates HTML documentation.
+
+ \note You can often achieve the intended purpose by using QDoc commands,
+ while reducing the chance of mistakes or content becoming unmaintained.
+
+ \target sincelist-command
+ \section1 \\sincelist
+
+ The \\sincelist command expands to a detailed breakdown of new
+ inclusions to the documented API in a specified version. Example
+ usage:
+
+ \badcode * \QtMajorVersion \QtMinorVersion \QtVer
+ /\1!
+ \page newclasses\2\3.html
+ \title New Classes and Functions in \4
+ \brief A comprehensive list of new classes and functions in \4.
+
+ \sincelist \4
+ \1/
+ \endcode
+
+ \\sincelist takes a single argument, a version string. The generated
+ output includes all functionality that is marked with a
+ \l {since-command}{\\since} command or a \l {since clause} matching
+ the version string.
+
+ \target unicode-command
+ \section1 \\unicode
+
+ The \\unicode command allows you to insert an arbitrary Unicode
+ character in the document.
+
+ The command takes an argument specifying the character as an
+ integer. By default, base 10 is assumed, unless a '0x' or '0'
+ prefix is specified (for base 16 and 8, respectively).
+*/
+
diff --git a/src/qdoc/qdoc/doc/qdoc-manual-qdocconf.qdoc b/src/qdoc/qdoc/doc/qdoc-manual-qdocconf.qdoc
new file mode 100644
index 000000000..d6fc3305e
--- /dev/null
+++ b/src/qdoc/qdoc/doc/qdoc-manual-qdocconf.qdoc
@@ -0,0 +1,1992 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page 21-0-qdoc-configuration.html
+ \previouspage Miscellaneous Macros
+ \nextpage Generic Configuration Variables
+
+ \title The QDoc Configuration File
+
+ Before running QDoc, you must create a QDoc configuration file to
+ tell QDoc where to find the source files that contain the QDoc
+ comments. The pathname to your configuration file is passed to
+ QDoc on the command line:
+
+ \quotation
+ \c {/current/dir$ ../../bin/qdoc ./config.qdocconf}
+ \endquotation
+
+ \section1 General Description
+
+ The configuration file is a list of entries of the form \e
+ {"variable = value"}. Using the configuration variables, you can
+ define where QDoc should find the various source files, images and
+ examples, where to put generated documentation etc. The
+ configuration file can also contain directives like \c
+ include. For an example, see \l minimum.qdocconf.
+
+ You can also use configuration variables to get QDoc to support
+ \l{Supporting Derived Projects} {derived projects}, i.e QDoc can
+ generate links in your project's documentation to elements in the
+ Qt online documentation. See the \l {Supporting Derived projects}
+ section.
+
+ A valid configuration variable name can include upper and lower case
+ letters, numbers, and an underscore, '_'.
+
+ The value of a configuration variable can be set using either '='
+ or '+='. The difference is that '=' overrides the previous value,
+ while '+=' adds a new value to the current one.
+
+ Values of some configuration variables are interpreted as a list of
+ strings, for example:
+ \l {sourcedirs-variable}
+ {\c{sourcedirs}}, while others are treated as a single string. Double
+ quotes around a value string are optional, but including them allows
+ you to use special characters like '=' and ' \" ' within the value
+ string, for example:
+
+ \badcode
+ HTML.postheader = "<a href=\"index.html\">Home</a>"
+ \endcode
+
+ If an entry spans many lines, use a backslash at the end of every
+ line but the last:
+
+ \badcode
+ sourcedirs = kernel tools widgets
+ \endcode
+
+ This can be written as:
+ \badcode
+ sourcedirs = kernel \
+ tools \
+ widgets
+ \endcode
+
+ If a value spans multiple lines but is interpreted as a single string,
+ the lines are joined with spaces.
+
+ \section1 Expansion of Configuration Values
+
+ QDoc supports expanding environment variables within configuration files.
+ For example, Qt modules rely on the environment variable QT_INSTALL_DOCS
+ to include definitions related to all Qt module documentation projects:
+
+ \badcode
+ include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf)
+ \endcode
+
+ A variable to expand is prefixed with '$'. To use the literal character
+ '$' within a value string, escape it with a backslash: '\\$'.
+
+ Since QDoc 6.0, values can be expanded also from other configuration
+ variables. In addition to the \c {$variable} syntax, also \c {${variable}}
+ is valid. The latter eliminates the need to separate the variable
+ name with whitespace or non-variable characters. For example:
+
+ \badcode
+ baseurl = https://doc.qt.io/
+ ...
+ url = ${baseurl}qtcreator
+ \endcode
+
+ If the target variable refers to a list of strings, they will be
+ joined using spaces in the expanded value:
+
+ \badcode
+ vars = foo \
+ bar \
+ baz
+
+ items = "Items: $vars" # Expands to "Items: foo bar baz"
+ \endcode
+
+ Controlling which character is used for joining the string list is also
+ possible:
+
+ \badcode
+ items = "Items: ${vars,|}" # Expands to "Items: foo|bar|baz"
+ items = "Items: ${vars,,}" # Expands to "Items: foo,bar,baz"
+ items = "Items: ${vars,}" # Expands to "Items: foobarbaz"
+ \endcode
+
+ As the expansion is performed after reading in all variables, the order
+ in which they are defined does not matter.
+
+ \note Nested variable expansion is not supported.
+
+ \section2 Expanding Environment Variables
+
+ When expanding environment variables, the \c {${variable}} syntax has
+ different behavior compared to \c {$variable}. The former expands the
+ content of the variable in place to be parsed as part of the
+ configuration file, while the latter simply assigns the content as a
+ value for the current configuration variable. This has implications if
+ the environment variable contains a list of elements separated by
+ whitespace, or other formatting recognized by QDoc.
+
+ For example, if the value of an environment variable \c SRCDIRS is
+ \c {"../src/a ../src/b"}, then
+
+ \badcode
+ sourcedirs = $SRCDIRS # Fail - value is interpreted as a single path.
+ sourcedirs = ${SRCDIRS} # Ok - whitespace is used as a delimiter.
+ \endcode
+
+ \section1 Configuration Variables
+
+ \section1 Variable List
+
+ \list
+ \li \l {defines-variable} {defines}
+ \li \l {depends-variable} {depends}
+ \li \l {exampledirs-variable} {exampledirs}
+ \li \l {examples-variable} {examples}
+ \li \l {examplesinstallpath-variable} {examplesinstallpath}
+ \li \l {examples.fileextensions-variable} {examples.fileextensions}
+ \li \l {excludedirs-variable} {excludedirs}
+ \li \l {excludefiles-variable} {excludefiles}
+ \li \l {extraimages-variable} {extraimages}
+ \li \l {falsehoods-variable} {falsehoods}
+ \li \l {headerdirs-variable} {headerdirs}
+ \li \l {headers-variable} {headers}
+ \li \l {headers.fileextensions-variable} {headers.fileextensions}
+ \li \l {HTML.footer-variable} {HTML.footer}
+ \li \l {HTML.postheader-variable} {HTML.postheader}
+ \li \l {HTML.style-variable} {HTML.style}
+ \li \l {includepaths-variable} {includepaths}
+ \li \l {ignorewords-variable} {ignorewords}
+ \li \l {ignoresince-variable} {ignoresince}
+ \li \l {imagedirs-variable} {imagedirs}
+ \li \l {indexes-variable} {indexes}
+ \li \l {language-variable} {language}
+ \li \l {locationinfo-variable} {locationinfo}
+ \li \l {macro-variable} {macro}
+ \li \l {manifestmeta-variable} {manifestmeta}
+ \li \l {moduleheader-variable} {moduleheader}
+ \li \l {navigation-variable} {navigation}
+ \li \l {outputdir-variable} {outputdir}
+ \li \l {outputformats-variable} {outputformats}
+ \li \l {outputprefixes-variable} {outputprefixes}
+ \li \l {outputsuffixes-variable} {outputsuffixes}
+ \li \l {project-variable} {project}
+ \li \l {sourcedirs-variable} {sourcedirs}
+ \li \l {sources-variable} {sources}
+ \li \l {sources.fileextensions-variable} {sources.fileextensions}
+ \li \l {spurious-variable} {spurious}
+ \li \l {tabsize-variable} {tabsize}
+ \li \l {url-variable} {url}
+ \li \l {url.examples-variable} {url.examples}
+ \li \l {version-variable} {version}
+ \li \l {versionsym-variable} {versionsym}
+ \li \l {warninglimit-variable} {warninglimit}
+ \endlist
+
+ \section1 Categories
+
+ \list
+ \li \l {Generic Configuration Variables}
+ \li \l {Format-specific Configuration Variables}
+ \endlist
+
+ \section1 Configuration File Examples
+
+ \list
+ \li A minimum configuration file: \l minimum.qdocconf
+ \li The Qt configuration file: \l qtgui.qdocconf
+ \endlist
+*/
+
+
+/*!
+ \page 22-qdoc-configuration-generalvariables.html
+ \previouspage The QDoc Configuration File
+ \nextpage Creating Help Project Files
+
+ \title Generic Configuration Variables
+
+ With the general QDoc configuration variables, you can define
+ where QDoc will find the various source files it needs to generate
+ the documentation, as well as the directory to put the generated
+ documentation. You can also do some minor manipulation of QDoc
+ itself, controlling its output and processing behavior.
+
+ \target codeindent-variable
+ \section1 codeindent
+
+ The \c codeindent variable specifies the level of indentation that
+ QDoc uses when writing code snippets.
+
+ QDoc originally used a hard-coded value of four spaces for code
+ indentation to ensure that code snippets could be easily
+ distinguished from surrounding text. Since we can use \l{HTML
+ Specific Configuration Variables#HTML.stylesheets} {stylesheets}
+ to adjust the appearance of certain types of HTML elements, this
+ level of indentation is not always required.
+
+ \target codeprefix-variable
+ \target codesuffix-variable
+ \section1 codeprefix, codesuffix
+
+ The \c codeprefix and \c codesuffix variables specify a pair of
+ strings that each code snippet is enclosed in.
+
+ \target defines-variable
+ \section1 defines
+
+ The \c defines variable specifies the C++ preprocessor symbols
+ that QDoc will recognize and respond to.
+
+ When a preprocessor symbol is specified using the \c defines
+ variable, you can also use the \l {if-command} {\\if} command to
+ enclose documentation that only will be included if the
+ preprocessor symbol is defined.
+
+ \badcode
+ defines = QT_GUI_LIB
+ \endcode
+
+ This ensures that QDoc will process the code that requires these
+ symbols to be defined. For example:
+
+ \code
+ #ifdef Q_GUI_LIB
+ void keyClick(QWidget *widget, Qt::Key key, Qt::KeyboardModifiers modifier = Qt::NoModifier, int delay = -1)
+ #endif
+ \endcode
+
+
+ You can also define preprocessor symbols manually on the command
+ line using the -D option. For example:
+
+ \badcode
+ currentdirectory$ qdoc -Dqtforpython qtgui.qdocconf
+ \endcode
+
+ In this case the -D option ensures that the \c qtforpython
+ preprocessor symbol is defined when QDoc processes the source
+ files defined in the qtgui.qdocconf file.
+
+ See also \l {falsehoods-variable} {falsehoods} and \l {if-command} {\\if}.
+
+ \target depends-variable
+ \section1 depends
+
+ The \c depends variable defines a list of other documentation projects
+ that this project depends on for resolving link targets for type
+ inheritance and anything else the documentation needs to link to.
+
+ Like Qt itself, documentation for Qt is distributed across multiple
+ modules. In a multi-module documentation project, the minimum set
+ of dependencies for a single module consists of actual build
+ dependencies. In addition, if there is a documentation project
+ (module) that acts as a top-level entry point for the entire
+ documentation set and provides \l {navigation-variable}{navigation}
+ links, each module documentation should include it as a dependency.
+
+ When QDoc generates documentation for a project, it will also generate
+ an \c .index file containing URLs to each linkable entity in the project.
+ Each dependency is a (lowercase) name of a project. This name must
+ match with the base name of the index file generated for that
+ project.
+
+ \badcode
+ depends = \
+ qtdoc \
+ qtcore \
+ qtquick
+ \endcode
+
+ When invoking QDoc on a project that has dependencies and uses the
+ \c depends variable, one or more \c -indexdir path(s) must be passed
+ as command line option(s). QDoc uses these paths to search for
+ the dependencies' index files.
+
+ \badcode
+ qdoc mydoc.qdocconf -outputdir $PWD/html -indexdir $QT_INSTALL_DOCS
+ \endcode
+
+ With above, QDoc will search for a file
+ \c {$QT_INSTALL_DOCS/qtdoc/qtdoc.index} for a dependency to \c qtdoc.
+ If an index file for a dependency is not found, QDoc will output a
+ warning.
+
+ The \c depends command accepts also a special value of '*'. This
+ instructs QDoc to load all index files found in specified index
+ directories; that is, "depends on everything".
+
+ \badcode
+ depends = *
+ \endcode
+
+ See also \l indexes, \l project, and \l url.
+
+ \target exampledirs-variable
+ \section1 exampledirs
+
+ The \c exampledirs variable specifies the directories containing
+ the source code of the example files.
+
+ The \l {examples-variable} {examples} and \l
+ {exampledirs-variable} {exampledirs} variables are used by the \l
+ {quotefromfile-command} {\\quotefromfile}, \l {quotefile-command}
+ {\\quotefile} and \l {example-command} {\\example} commands. If
+ both the \l {examples-variable} {examples} and \l
+ {exampledirs-variable} {exampledirs} variables are defined, QDoc
+ will search in both, first in \l {examples-variable} {examples}
+ then in \l {exampledirs-variable} {exampledirs}.
+
+ QDoc will search through the directories in the specified order,
+ and accept the first matching file it finds. It will only search
+ in the specified directories, \e not in subdirectories.
+
+ \badcode
+ exampledirs = $QTDIR/doc/src \
+ $QTDIR/examples \
+ $QTDIR \
+ $QTDIR/qmake/examples
+
+ examples = $QTDIR/examples/widgets/analogclock/analogclock.cpp
+ \endcode
+
+ When processing
+
+ \badcode
+ \quotefromfile widgets/calculator/calculator.cpp
+ \endcode
+
+ QDoc will see if there is a file called \c calculator.cpp
+ listed as a value in the \l {examples-variable} {\c examples} variable. If
+ there isn't, it will search in the \c exampledirs variable, and
+ first see if there exists a file called
+
+ \badcode
+ $QTDIR/doc/src/widgets/calculator/calculator.cpp
+ \endcode
+
+ If it doesn't, QDoc will continue looking for a file called
+
+ \badcode
+ $QTDIR/examples/widgets/calculator/calculator.cpp
+ \endcode
+
+ and so forth.
+
+ See also \l {examples-variable}{examples}.
+
+ \target examples-variable
+ \section1 examples
+
+ The \c examples variable allows you to specify individual example
+ files in addition to those located in the directories specified by
+ the \l {exampledirs-variable} {\c exampledirs} variable.
+
+ The \c examples and \l {exampledirs-variable} {\c exampledirs}
+ variables are used by the \l {quotefromfile-command}
+ {\\quotefromfile}, \l {quotefile-command} {\\quotefile} and \l
+ {example-command} {\\example} commands. If both the \c examples and \l
+ {exampledirs-variable} {\c exampledirs} variables are defined,
+ QDoc will search in both, first in \c examples then in \l
+ {exampledirs-variable} {\c exampledirs}.
+
+ QDoc will search through the values listed for the \c examples
+ variable, in the specified order, and accept the first one it
+ finds.
+
+ For an extensive example, see the \l {exampledirs-variable} {\c
+ exampledirs} command. But note that if you know the file is listed
+ in the \c examples variable, you don't need to specify its path:
+
+ \badcode
+ \quotefromfile calculator.cpp
+ \endcode
+
+ See also \l {exampledirs-variable} {exampledirs}.
+
+ \target examplesinstallpath-variable
+ \section1 examplesinstallpath
+
+ The \c examplesinstallpath variable sets the root path for this
+ project's examples under the \e installed example directory.
+
+ Assuming a root install path of \c QT_INSTALL_EXAMPLES for all
+ examples, then the path
+
+ \badcode
+ <QT_INSTALL_EXAMPLES>/<examplesinstallpath>/<example_path>
+ \endcode
+
+ will be used to refer to a path of a single example within this
+ documentation project. These paths are recorded in the
+ \l {Example Manifest Files} {example manifest file}, read by Qt
+ Creator.
+
+ To ensure correct paths, \c examplesinstallpath must match with
+ one of the directories listed in \l {exampledirs-variable}
+ {exampledirs}. The path passed as an argument for each
+ \l {example-command} {\\example} command is relative to
+ the path in \e exampledirs.
+
+ For example:
+
+ \badcode
+ exampledirs = ./snippets \
+ ../../../examples/mymodule
+
+ examplesinstallpath = mymodule
+ \endcode
+
+ And given a following \\example command:
+
+ \badcode *
+ /*!
+ \example basic/hello
+ ...
+ \1/
+ \endcode
+
+ Then, the path \c {mymodule/basic/hello} is recorded in the manifest file
+ for this example.
+
+ \note It is possible to override \c examplesinstallpath for an individual
+ \l {example-command}{\\example} using the \l {meta-command}{\\meta}
+ command.
+
+ \b {See also}: \l {exampledirs}, \l {example-command} {\\example}, and
+ \l {meta-command}{\\meta}.
+
+ \target examples.fileextensions-variable
+ \section1 examples.fileextensions
+
+ The \c examples.fileextensions variable specifies the file
+ extensions that QDoc will look for when collecting example files
+ for display in the documentation.
+
+ The default extensions are *.cpp, *.h, *.js, *.xq, *.svg, *.xml
+ and *.ui.
+
+ The extensions are given as standard wildcard expressions. You
+ can add a file extension to the filter using '+='. For example:
+
+ \badcode
+ examples.fileextensions += *.qrc
+ \endcode
+
+ See also \l{headers.fileextensions}.
+
+ \target excludedirs-variable
+ \section1 excludedirs
+
+ The \c excludedirs variable is for listing directories that should \e{not}
+ be processed by QDoc, even if the same directories are included by the
+ \l {sourcedirs-variable} {sourcedirs} or \l {headerdirs-variable} {headerdirs}
+ variables.
+
+ For example:
+
+ \badcode
+ sourcedirs = src/corelib
+ excludedirs = src/corelib/tmp
+ \endcode
+
+ When executed, QDoc will exclude the listed directories from
+ further consideration. Files in these directories will not be
+ read by QDoc.
+
+ See also \l {excludefiles-variable} {excludefiles}.
+
+ \target excludefiles-variable
+ \section1 excludefiles
+
+ The \c excludefiles variable allows you to specify individual files
+ that should \e{not} be processed by QDoc.
+
+ \badcode
+ excludefiles += $QT_CORE_SOURCES/../../src/widgets/kernel/qwidget.h \
+ $QT_CORE_SOURCES/../../src/widgets/kernel/qwidget.cpp
+ \endcode
+
+ If you include the above in your qdocconf file for qtbase, there
+ will be no class documentation generated for QWidget.
+
+ Since Qt 5.6, also simple wildcards ('*' and '?') are recognized by
+ \c excludefiles. For example, to exclude all private Qt header files
+ from being parsed, define the following:
+
+ \badcode
+ excludefiles += "*_p.h"
+ \endcode
+
+ See also \l {excludedirs-variable} {excludedirs}.
+
+ \target extraimages-variable
+ \section1 extraimages
+
+ The \c extraimages variable tells QDoc to incorporate specific
+ images in the generated documentation.
+
+ QDoc automatically copies an image file from \l imagedirs to the output
+ directory if it's referenced by the \l {image-command} {\c{\image}} or
+ \l {inlineimage-command} {\c{\inlineimage}} command. If you want to copy
+ additional images, you must specify them using the \c extraimages variable.
+
+ The general syntax is \tt {\e{format}.extraimages = \e image}.
+
+ For a contextualized example, refer to the description of the
+ \l {HTML.postheader-variable}{HTML.postheader} variable.
+
+ Example:
+ \badcode
+ HTML.extraimages = images/qt-logo.png
+ \endcode
+
+ See also \l images and \l imagedirs.
+
+ \target falsehoods-variable
+ \section1 falsehoods
+
+ The \c falsehoods variable defines the truth value of specified
+ preprocessor symbols as false.
+
+ The values of the variable are regular expressions (see QRegularExpression
+ for details). If this variable is not set for a preprocessor
+ symbol, QDoc assumes its truth value is true. The exception is
+ '0', which is always false.
+
+ QDoc will recognize, and is able to evaluate, the following
+ preprocessor syntax:
+
+ \code
+ #ifdef NOTYET
+ ...
+ #endif
+
+ #if defined (NOTYET)
+ ...
+ #end if
+ \endcode
+
+ However, faced with unknown syntax like
+
+ \code
+ #if NOTYET
+ ...
+ #endif
+ \endcode
+
+ QDoc will evaluate it as true by default, \e unless the
+ preprocessor symbol is specified within the \c falsehoods variable
+ entry:
+
+ \badcode
+ falsehoods = NOTYET
+ \endcode
+
+ See also \l defines.
+
+ \target generateindex-variable
+ \section1 generateindex
+
+ The \c generateindex variable contains a boolean value that
+ specifies whether to generate an index file when HTML
+ documentation is generated.
+
+ By default, an index file is always generated with HTML
+ documentation, so this variable is typically only used when
+ disabling this feature (by setting the value to \c false) or when
+ enabling index generation for the WebXML output (by setting the
+ value to \c true).
+
+ \target headerdirs-variable
+ \section1 headerdirs
+
+ The \c headerdirs variable specifies the directories containing
+ the header files associated with the \c .cpp source files used in
+ the documentation.
+
+ \badcode
+ headerdirs = $QTDIR/src \
+ $QTDIR/extensions/activeqt \
+ $QTDIR/extensions/motif \
+ $QTDIR/tools/designer/src/lib/extension \
+ $QTDIR/tools/designer/src/lib/sdk \
+ $QTDIR/tools/designer/src/lib/uilib
+ \endcode
+
+ When executed, the first thing QDoc will do is to read through the
+ headers specified in the \l {headers} {\c headers} variable, and
+ the ones located in the directories specified in the \c headerdir
+ variable (including all subdirectories), building an internal
+ structure of the classes and their functions.
+
+ Then it will read through the sources specified in the \l
+ {sources-variable} {\c sources}, and the ones located in the
+ directories specified in the \l {sourcedirs-variable} {\c
+ sourcedirs} varible (including all subdirectories), merging the
+ documentation with the structure it retrieved from the header
+ files.
+
+ If both the \c headers and \c headerdirs variables are defined,
+ QDoc will read through both, first \l {headers} {\c headers} then
+ \c headerdirs.
+
+ In the specified directories, QDoc will only read the files with
+ the \c fileextensions specified in the \l {headers.fileextensions}
+ {\c headers.fileextensions} variable. The files specified by
+ \l {headers} {\c headers} will be read without taking into account
+ their fileextensions.
+
+ See also \l headers and \l headers.fileextensions.
+
+ \target headers-variable
+ \section1 headers
+
+ The \c headers variable allows you to specify individual header
+ files in addition to those located in the directories specified by
+ the \l {headerdirs} {\c headerdirs} variable.
+
+ \badcode
+ headers = $QTDIR/src/gui/widgets/qlineedit.h \
+ $QTDIR/src/gui/widgets/qpushbutton.h
+ \endcode
+
+ When processing the \c headers variable, QDoc behaves in the same
+ way as it does when processing the \l {headerdirs} {\c headerdirs}
+ variable. For more information, see the \l {headerdirs} {\c
+ headerdirs} variable.
+
+ See also \l headerdirs.
+
+ \target headers.fileextensions-variable
+ \section1 headers.fileextensions
+
+ The \c headers.fileextensions variable specify the extension used
+ by the headers.
+
+ When processing the header files specified in the \l {headerdirs}
+ {\c headerdirs} variable, QDoc will only read the files with the
+ fileextensions specified in the \c headers.fileextensions
+ variable. In this way QDoc avoids spending time reading irrelevant
+ files.
+
+ The default extensions are *.ch, *.h, *.h++, *.hh, *.hpp, and
+ *.hxx.
+
+ The extensions are given as standard wildcard expressions. You
+ can add a file extension to the filter using '+='. For example:
+
+ \badcode
+ header.fileextensions += *.H
+ \endcode
+
+ \warning The above assignment may not work as described.
+
+ See also \l headerdirs.
+
+ \target includepaths-variable
+ \section1 includepaths
+
+ The \c includepaths variable is used for passing additional
+ include paths to the Clang parser that QDoc uses for parsing C++
+ code for documentation comments.
+
+ The variable accepts a list of paths, prefixed with \c{-I} (include
+ path), \c {-F} (\macos framework include path), or \c{-isystem}
+ (system include path). If a prefix is omitted, \c{-I} is used by
+ default.
+
+ Paths relative to the current .qdocconf file are resolved into
+ absolute paths. Paths that do not exist in the file system are
+ ignored.
+
+ \note For Qt documentation projects, the build system typically
+ provides the required include paths as command line
+ arguments when invoking QDoc.
+
+ See also \l moduleheader.
+
+ \target ignorewords-variable
+ \section1 ignorewords
+
+ The \c ignorewords variable is used for specifying a list of strings
+ that QDoc will ignore when resolving hyperlink targets.
+
+ QDoc has an auto-linking feature, where linking is attempted for words
+ that resemble C++ or QML entities. Specifically, a string
+ qualifies for auto-linking if it is at least three characters in
+ length, has no whitespace, and it
+
+ \list
+ \li is a \e camelCase word, that is, it contains at least one
+ uppercase character at index greater than zero, or
+ \li contains the substring \c {()} or \c {::}, or
+ \li contains at least one special character, \c {@} or \c {_}.
+ \endlist
+
+ Adding a qualified word to \c ignorewords stops QDoc from linking
+ that word automatically. For example, if the word \e OpenGL is a
+ valid link target (a section, \l{page-command}{\\page}, or
+ \l {externalpage-command}{\\externalpage} title), a hyperlink for
+ each occurrence can be avoided with
+
+ \badcode
+ ignorewords += OpenGL
+ \endcode
+
+ Linking explicitly with \l {l-command}{\\l} continues to work for
+ ignored words.
+
+ The \c ignorewords variable was introduced in QDoc 5.14.
+
+ \target ignoresince-variable
+ \section1 ignoresince
+
+ The \c ignoresince variable is used for setting a cutoff value for
+ versions passed to the \l {since-command}{\\since} command. All
+ \\since commands that define a version lower than the cutoff are
+ ignored and do not generate output.
+
+ The cutoff values are project-specific. The project name can be
+ defined as a subvariable. The default project name is \e Qt. For
+ example:
+
+ \badcode
+ ignoresince = 5.0
+ ignoresince.QDoc = 5.0
+ \endcode
+
+ These will ignore \\since commands where the major version is 4
+ or lower and the project is either \c QDoc or undefined.
+
+ \badcode
+ \since 3.2 # Ignored
+ \since 5.2 # Documented (as 'Qt 5.2')
+ \since QDoc 4.6 # Ignored
+ \since QtQuick 2.5 # Documented
+ \endcode
+
+ The \c ignoresince variable was introduced in QDoc 5.15.
+
+ See also \l {since-command}{\\since}.
+
+ \target imagedirs-variable
+ \section1 imagedirs
+
+ The \c imagedirs variable specifies the directories containing the
+ images used in the documentation.
+
+ The \l {images} {\c images} and \c imagedirs variables are used by
+ the \l {image-command} {\\image} and \l {inlineimage-command}
+ {\\inlineimage} commands. If both the \l {images} {\c images} and
+ \c imagedirs variables are defined, QDoc will search in both. First
+ in \l {images} {\c images}, then in \c imagedirs.
+
+ QDoc will search through the directories in the specified order,
+ and accept the first matching file it finds. It will only search
+ in the specified directories, \e not in subdirectories.
+
+ \badcode
+ imagedirs = $QTDIR/doc/src/images \
+ $QTDIR/examples
+
+ images = $QTDIR/doc/src/images/calculator-example.png
+ \endcode
+
+ When processing
+
+ \badcode
+ \image calculator-example.png
+ \endcode
+
+ QDoc will then see if there is a file called
+ calculator-example.png listed as a value in the \c images
+ variable. If there isn't, it will search in the \c imagedirs
+ variable for:
+
+ \badcode
+ $QTDIR/doc/src/images/calculator-example.png
+ \endcode
+
+ If the file doesn't exist, QDoc will look for a file called
+
+ \badcode
+ $QTDIR/examples/calculator-example.png
+ \endcode
+
+ \target language-variable
+ \section1 language
+
+ The \c language variable specifies the language of the source code
+ that is used in the documentation. Specifically, it defines the
+ default language for parsing source code within \\code .. \\endcode
+ blocks.
+
+ \badcode
+ language = Cpp
+ \endcode
+
+ The default language is C++ (Cpp), and doesn't need to be explicitly
+ specified. If the code snippets in the documentation consist mainly
+ of QML code, set QML as the default:
+
+ \badcode
+ language = QML
+ \endcode
+
+ See also \l {code-command}[\\code}.
+
+ \target locationinfo-variable
+ \section1 locationinfo
+
+ The \c locationinfo boolean variable determines whether detailed
+ location information about each entity is written to
+ \c {.index}-files and \c {.webxml}-files (when using the WebXML
+ output format).
+
+ Location information consists of the full path and line
+ number of either the declaration or documentation comment block
+ in the source code.
+
+ Setting this to \c false turns off location info:
+
+ \badcode
+ locationinfo = false
+ \endcode
+
+ The default value is \c true.
+
+ The \c locationinfo variable was introduced in QDoc 5.15.
+
+ \target macro-variable
+ \section1 macro
+
+ The \c macro variable is used to create your own simple QDoc
+ commands. The syntax is \tt {macro.\e{command} = \e{definition}},
+ where the definition is written using QDoc syntax.
+
+ A macro variable can be restricted for use in one type of output
+ generation. By appending \c {.HTML} to the macro name, for
+ example, the macro is only used when generating HTML output.
+
+ \badcode
+ macro.key = "\\b"
+ macro.raisedaster.HTML = "<sup>*</sup>"
+ \endcode
+
+ The first macro defines the \\key command to render its argument
+ using a bold font. The second macro defines the \\raisedaster
+ command to render a superscript asterisk, but only when generating
+ HTML.
+
+ A macro can also take up to seven parameters:
+
+ \badcode
+ macro.hello = "Hello \1!"
+ \endcode
+
+ Parameters are passed to macros the same way as to other commands:
+
+ \badcode
+ \hello World
+ \endcode
+
+ When using more than one parameter, or when an argument
+ contains whitespace, enclose each argument in braces:
+
+ \badcode
+ macro.verinfo = "\1 (version \2)"
+ \endcode
+
+ \badcode
+ \verinfo {QFooBar} {1.0 beta}
+ \endcode
+
+ A special macro option, \e match, can be added for additional
+ regular expression pattern matching for expanded macros.
+
+ For example,
+
+ \badcode
+ macro.qtminorversion = "$QT_VER"
+ macro.qtminorversion.match = "\\d+\\.(\\d+)"
+ \endcode
+
+ This creates a macro \\qtminorversion that expands to the minor
+ version based on the QT_VER environment variable.
+
+ A macro that defines a match pattern outputs all capture groups
+ (parentheses) concatenated together, or the exact matched string
+ if the pattern does not contain any capture groups.
+
+ For more information about pre-defined macros, see \l {Macros}.
+
+ \target manifestmeta-variable
+ \section1 manifestmeta
+
+ The \c manifestmeta variable specifies additional meta-content
+ for the example manifest files generated by QDoc.
+
+ See the \l{Manifest Meta Content} section for more information.
+
+ \target moduleheader-variable
+ \section1 moduleheader
+
+ The \c moduleheader variable defines the name of the module
+ header of a documented C++ module.
+
+ Projects that document C++ APIs require a module-level header
+ that includes all public classes, namespaces and header files
+ for the module. The Clang parser in QDoc uses this file to
+ build a pre-compiled header (PCH) for the module to increase
+ the speed of parsing source files.
+
+ By default, the \l{project-variable}{project} name is used
+ also as the module header name.
+
+ \badcode
+ project = QtCore
+ \endcode
+
+ With the above project name, QDoc searches a module header
+ \e QtCore in all known include paths; first using the paths
+ passed as command line arguments, then the paths listed in
+ the \l includepaths variable.
+
+ QDoc will issue a warning if the module header is not found.
+ It will then attempt to build an artificial module header
+ based on the headers listed in the \l {headerdirs-variable}
+ {headerdirs} variable.
+
+ For Qt documentation projects, the build system typically
+ provides QDoc with correct include paths to locate the
+ module header, provided that the \c project variable is set
+ correctly. The \c moduleheader variable provides an
+ alternative file name for QDoc to search for.
+
+ If the project contains no C++ documentation, QDoc should be
+ instructed to skip generating a PCH by setting \c moduleheader
+ to an empty string:
+
+ \badcode
+ # No C++ code to document in this project
+ moduleheader =
+ \endcode
+
+ See also \l includepaths and \l project.
+
+ \target naturallanguage-variable
+ \section1 naturallanguage
+
+ The \c naturallanguage variable specifies the natural language
+ used for the documentation generated by QDoc.
+
+ \badcode
+ naturallanguage = zh-Hans
+ \endcode
+
+ By default, the natural language is \c en for compatibility with
+ legacy documentation.
+
+ QDoc will add the natural language information to the HTML it
+ generates, using the \c lang and \c xml:lang attributes.
+
+ See also \l {sourceencoding-variable} {sourceencoding},
+ \l {outputencoding-variable} {outputencoding},
+ \l{http://www.w3.org/TR/xhtml1/#C_7}
+ {C.7. The lang and xml:lang Attributes} and
+ \l{http://www.w3.org/TR/i18n-html-tech-lang/#ri20040429.113217290}
+ {Best Practice 13: Using Hans and Hant codes}.
+
+ \target navigation-variable
+ \section1 navigation
+
+ The \c navigation sub-variables, if defined, set the home page, landing
+ page, C++ classes page, and QML types page that are visible in the
+ generated navigation bar for each page.
+
+ In a project with multiple sub-projects (for example, Qt modules), each
+ sub-project typically defines its own landing page while the same home
+ page is used across all sub-projects.
+
+ \b Sub-variables
+
+ \table
+ \row \li \c navigation.homepage
+ \li Project home page.
+ \row \li \c navigation.hometitle
+ \li (Optional) User-visible title for the home page.
+ Default value is taken from \c homepage.
+ \row \li \c navigation.landingpage
+ \li Sub-project landing page.
+ \row \li \c navigation.landingtitle
+ \li (Optional) User-visible title for the landing page.
+ Defaults value is taken from \c landingpage.
+ \row \li \c navigation.cppclassespage
+ \li Top-level page that lists all C++ classes for this (sub-)project.
+ Typically, the title of a \l {module-command}{\\module} page.
+ \row \li \c navigation.cppclassestitle
+ \li (Optional) User-visible title for the C++ classes page.
+ Default is "C++ Classes".
+ \row \li \c navigation.qmltypespage
+ \li Top-level page that lists all QML types for this (sub-)project.
+ Typically, the title of a \l {qmlmodule-command}{\\qmlmodule} page.
+ \row \li \c navigation.qmltypestitle
+ \li (Optional) User-visible title for the QML types page.
+ Default is "QML Types".
+ \row \li \c navigation.toctitles (Since QDoc 6.0)
+ \li Page title(s) containing a \l {list-command}{\\list} structure that
+ acts as a table of contents (TOC). QDoc generates navigation links
+ for pages listed in the TOC, without the need for
+ \l {nextpage-command}{\\nextpage} and \l {previouspage-command}
+ {\\previouspage} commands, as well as a navigation hierarchy that's
+ visible in the navigation bar (breadcrumbs) for HTML output.
+ \row \li \c navigation.toctitles.inclusive (Since QDoc 6.3)
+ \li If set to \c true, page(s) listed in \c navigation.toctitles
+ will also appear in the navigation bar as a root item.
+ \row \li \c navigation.trademarkspage (Since QDoc 6.8)
+ \li Title of a page that documents trademarks mentioned in the
+ documentation. See also \qdoccmd tm command.
+ \endtable
+
+ For example:
+
+ \badcode
+ # Common configuration
+ navigation.homepage = index.html
+ navigation.hometitle = "Qt $QT_VER"
+
+ # qtquick.qdocconf
+ navigation.landingpage = "Qt Quick"
+ navigation.cppclassespage = "Qt Quick C++ Classes"
+ navigation.qmltypespage = "Qt Quick QML Types"
+ \endcode
+
+ The above configuration produces the following navigation bar for \c Item QML type:
+
+ \badcode
+ Qt 5.10 > Qt Quick > QML Types > Item QML Type
+ \endcode
+
+ \target outputdir-variable
+ \section1 outputdir
+
+ The \c outputdir variable specifies the directory where QDoc will
+ put the generated documentation.
+
+ \badcode
+ outputdir = $QTDIR/doc/html
+ \endcode
+
+ locates the generated Qt reference documentation in
+ $QTDIR/doc/html. For example, the documentation of the QWidget
+ class is located in
+
+ \badcode
+ $QTDIR/doc/html/qwidget.html
+ \endcode
+
+ The associated images will be put in an \c images subdirectory.
+
+ \warning When running QDoc multiple times using the same output
+ directory, all files from the previous run will be lost.
+
+ \target outputencoding-variable
+ \section1 outputencoding
+
+ The \c outputencoding variable specifies the encoding used for the
+ documentation generated by QDoc.
+
+ \badcode
+ outputencoding = UTF-8
+ \endcode
+
+ By default, the output encoding is \c ISO-8859-1 (Latin1) for
+ compatibility with legacy documentation. When generating
+ documentation for some languages, particularly non-European
+ languages, this is not sufficient and an encoding such as UTF-8 is
+ required.
+
+ QDoc will encode HTML using this encoding and generate the correct
+ declarations to indicate to browsers which encoding is being
+ used. The \l naturallanguage configuration variable should also be
+ specified to provide browsers with a complete set of character
+ encoding and language information.
+
+ See also \l outputencoding and \l naturallanguage.
+
+ \target outputformats-variable
+ \section1 outputformats
+
+ The \c outputformats variable specifies the format(s) of
+ the generated documentation.
+
+ Since Qt 5.11, QDoc supports the HTML and WebXML formats; since
+ Qt 5.15, it can also generate the documentation in DocBook. If no
+ \c outputformats are specified, QDoc generates the documentation
+ in HTML (the default format). All output formats can be specified,
+ with dedicated output directories and other settings. For example:
+
+ \badcode
+ outputformats = WebXML HTML
+ WebXML.nosubdirs = true
+ WebXML.outputsubdir = webxml
+ WebXML.quotinginformation = true
+ \endcode
+
+ This generates HTML documentation using the default settings, as well
+ as WebXML documentation into output subdirectory \e webxml.
+
+ \target outputprefixes-variable
+ \section1 outputprefixes
+
+ The \c outputprefixes variable specifies a mapping between types of files
+ and the prefixes to prepend to the output file names in the generated
+ documentation.
+
+ QDoc supports adding an output prefix to the file names of QML type, C++
+ class, namespace, and header file reference pages.
+
+ \badcode
+ outputprefixes = QML CPP
+ outputprefixes.QML = uicomponents-
+ outputprefixes.CPP = components-
+ \endcode
+
+ By default, files containing the API documentation for QML types
+ are prefixed with \c {qml-}. In the above example, the prefix \c
+ {uicomponents-} is used instead.
+
+ Likewise, C++ type documentation pages are prefixed with \c {components-}
+ in the above example. By default, C++ type pages have no prefix.
+
+ \target outputsuffixes-variable
+ \section1 outputsuffixes
+
+ The \c outputsuffixes variable specifies a mapping between types of
+ files and suffixes to apply to the module or type name as they appear
+ in the output file names.
+
+ QDoc supports adding an output suffix to the file names of module pages,
+ QML type, C++ class, namespace, and header file reference pages.
+
+ By default, no suffix is used. The QML output suffix, if defined, is
+ applied as a suffix to the module name as it appears in the file names
+ of QML type and QML module pages.
+
+ File names for C++ types do not include the module name. The CPP
+ output suffix, if defined, is applied as a suffix for the type name.
+
+ \badcode
+ outputsuffixes = QML CPP
+ {outputsuffixes.QML,outputsuffixes.CPP} = -tp
+ \endcode
+
+ With the definitions above, given a QML module name \e FooBar and the default
+ \l {outputprefixes-variable}{output prefix} (\c {qml-}), the name of
+ the generated file for a QML type \e FooWidget is
+ \c qml-foobar-tp-foowidget.html.
+
+ Likewise, for a C++ class \e QFoobar, QDoc generates \c qfoobar-tp.html.
+
+ The \c outputsuffixes variable was introduced in QDoc 5.6.
+
+ \target qhp-variable
+ \section1 qhp
+
+ The \c qhp sub-variables are used to define the information to be
+ written out to Qt Help Project (\c{qhp}) files.
+
+ See the \l{Creating Help Project Files} chapter for information
+ about this process.
+
+ Since QDoc 6.6, setting the base \c qhp variable to \c true means
+ that a valid help project configuration is expected:
+
+ \badcode
+ qhp = true
+ \endcode
+
+ Then, if a project configuration did not define \c {qhp.projects},
+ QDoc issues a warning. This is useful for ensuring that all
+ documentation projects with a shared top-level \e .qdocconf file
+ (as in Qt) are configured correctly.
+
+ To turn off the warning, set the variable to \c false.
+
+ \target sourcedirs-variable
+ \section1 sourcedirs
+
+ The \c sourcedirs variable specifies the directories containing
+ the \c .cpp or \c .qdoc files used in the documentation.
+
+ \badcode
+ sourcedirs += .. \
+ ../../../examples/gui/doc/src
+ \endcode
+
+ When executed, the first thing QDoc will do is to read through the
+ headers specified in the \l {header-command} {\c header} variable,
+ and the ones located in the directories specified in the \c
+ headerdir variable (including all subdirectories), building an
+ internal structure of the classes and their functions.
+
+ Then it will read through the sources specified in the \l
+ {sources} {\c sources}, and the ones located in the directories
+ specified in the \l {sourcedirs} {\c sourcedirs} variable
+ (including all subdirectories), merging the documentation with the
+ structure it retrieved from the header files.
+
+ If both the \c sources and \c sourcedirs variables are defined,
+ QDoc will read through both, first \l {sources} {\c sources} then
+ \c sourcedirs.
+
+ In the specified directories, QDoc will only read the files with
+ the \c fileextensions specified in the \l {sources.fileextensions}
+ {\c sources.fileextensions} variable. The files specified by \l {sources}
+ {\c sources} will be read independent of their fileextensions.
+
+ See also \l {sources-variable} {sources} and
+ \l {sources.fileextensions-variable} {sources.fileextensions}.
+
+ \target sourceencoding-variable
+ \section1 sourceencoding
+
+ The \c sourceencoding variable specifies the encoding used for the
+ source code and documentation.
+
+ \badcode
+ sourceencoding = UTF-8
+ \endcode
+
+ By default, the source encoding is \c ISO-8859-1 (Latin1) for
+ compatibility with legacy documentation. For some languages,
+ particularly non-European languages, this is not sufficient and an
+ encoding such as UTF-8 is required.
+
+ Although QDoc will use the encoding to read source and
+ documentation files, limitations of C++ compilers may prevent you
+ from using non-ASCII characters in source code comments. In cases
+ like these, it is possible to write API documentation completely
+ in documentation files.
+
+ See also \l {naturallanguage-variable} {naturallanguage} and
+ \l {outputencoding-variable} {outputencoding}.
+
+ \target sources-variable
+ \section1 sources
+
+ The \c sources variable allows you to specify individual source
+ files in addition to those located in the directories specified by
+ the \l {sourcedirs-variable} {sourcedirs} variable.
+
+ \badcode
+ sources = $QTDIR/src/gui/widgets/qlineedit.cpp \
+ $QTDIR/src/gui/widgets/qpushbutton.cpp
+ \endcode
+
+ When processing the \c sources variable, QDoc behaves in the same
+ way as it does when processing the \l {sourcedirs-variable}
+ {sourcedirs} variable. For more information, see the \l
+ {sourcedirs-variable} {sourcedirs} variable.
+
+ See also \l {sourcedirs-variable} {sourcedirs}.
+
+ \target sources.fileextensions-variable
+ \section1 sources.fileextensions
+
+ The \c sources.fileextensions variable filters the files within a
+ source directory.
+
+ When processing the source files specified in the \l {sourcedirs}
+ {\c sourcedirs} variable, QDoc will only read the files with the
+ fileextensions specified in the \c sources.fileextensions
+ variable. In this way QDoc avoid spending time reading irrelevant
+ files.
+
+ The default extensions are *.c++, *.cc, *.cpp, *.cxx, *.mm, *.qml
+ and *.qdoc.
+
+ The extensions are given as standard wildcard expressions. You
+ can add a file extension to the filter using '+='. For example:
+
+ \badcode
+ sources.fileextensions += *.CC
+ \endcode
+
+ \warning The above assignment may not work as described.
+
+ See also \l {sourcedirs-variable} {sourcedirs} and \l
+ {sources-variable} {sources}.
+
+
+ \target spurious-variable
+ \section1 spurious
+
+ The \c spurious variable excludes specified QDoc warnings from the
+ output. The warnings are specified using standard wildcard
+ expressions.
+
+ \badcode
+ spurious = "Cannot find .*" \
+ "Missing .*"
+ \endcode
+
+ makes sure that warnings matching either of these expressions,
+ will not be part of the output when running QDoc. For example
+ would the following warning be omitted from the output:
+
+ \badcode
+ src/opengl/qgl_mac.cpp:156: Missing parameter name
+ \endcode
+
+ \target syntaxhighlighting
+ \section1 syntaxhighlighting
+
+ The \c syntaxhighlighting variable specifies whether QDoc should
+ perform syntax highlighting on source code quoted in the
+ documentation it generates.
+
+ \badcode
+ syntaxhighlighting = true
+ \endcode
+
+ will enable syntax highlighting for all supported programming
+ languages.
+
+ \target tabsize-variable
+ \section1 tabsize
+
+ The \c tabsize variable defines the size of a tab character.
+
+ \badcode
+ tabsize = 4
+ \endcode
+
+ will give the tab character the size of 4 spaces. The default
+ value of the variable is 8, and doesn't need to be specified.
+
+ \target tagfile-variable
+ \section1 tagfile
+
+ The \c tagfile variable specifies the Doxygen tag file to be
+ written when HTML is generated.
+
+ \target version-variable
+ \section1 version
+
+ The \c version variable specifies the version number of the
+ documented software.
+
+ \badcode
+ version = 5.6.0
+ \endcode
+
+ When a version number is specified (using the \tt{\l version} or
+ \tt {\l versionsym} variables in a \c .qdocconf file), it is
+ accessible through the corresponding \\version command for use in
+ the documentation.
+
+ \warning The \\version command's functionality is not fully
+ implemented; currently it only works within raw HTML code.
+
+ See also \l versionsym.
+
+ \target versionsym-variable
+ \section1 versionsym
+
+ The \c versionsym variable specifies a C++ preprocessor symbol
+ that defines the version number of the documented software.
+
+ \badcode
+ versionsym = QT_VERSION_STR
+ \endcode
+
+ QT_VERSION_STR is defined in qglobal.h as follows
+
+ \badcode
+ #define QT_VERSION_STR "5.14.1"
+ \endcode
+
+ When a version number is specified (using the \tt{\l version} or
+ \tt {\l versionsym} variables in a \c .qdocconf file), it is
+ accessible through the corresponding \\version command for use in
+ the documentation.
+
+ \warning The \\version command's functionality is not fully
+ implemented. Currently, it only works within raw HTML code.
+
+ See also \l {version} {\\version}.
+
+ \target warninglimit-variable
+ \section1 warninglimit
+
+ The \c warninglimit variable sets the maximum number of documentation
+ warnings allowed. If this limit is exceeded, QDoc continues as normal
+ but exits with the warning count as the error code. If the limit was
+ not exceeded or \c warninglimit was not defined, QDoc process exits
+ with 0, assuming there were no other critical errors.
+
+ Setting the \c warninglimit to \c 0 means failure on any warning.
+
+ \note By default, QDoc does not enforce the warning limit. Enable it
+ with \c {warninglimit.enabled = true} or by defining
+ the \c QDOC_ENABLE_WARNINGLIMIT environment variable.
+
+ For example,
+
+ \badcode
+ # Fail the documentation build if we have more than 100 warnings
+ warninglimit = 100
+ warninglimit.enabled = true
+ \endcode
+
+ The \c warninglimit variable was introduced in Qt 5.11.
+*/
+
+/*!
+ \page 22-creating-help-project-files.html
+ \previouspage Generic Configuration Variables
+ \nextpage Format-specific Configuration Variables
+
+ \title Creating Help Project Files
+
+ \section1 Overview
+
+ Qt Assistant uses a system for managing Qt documentation that requires
+ QDoc to generate inventories of files.
+
+ QDoc allows configuration variables to be used to specify which pages are
+ to be used in each documentation set it generates. These are specified as
+ subvariables of the \c qhp variable with each set declared using a unique
+ identifier as a subvariable.
+
+ For example, the configuration file for the Qt Quick documentation set
+ specifies information about the set as subvariables with the
+ \c{qhp.QtQuick} prefix:
+
+ \badcode
+ qhp.projects = QtQuick
+
+ qhp.QtQuick.file = qtquick.qhp
+ qhp.QtQuick.namespace = org.qt-project.qtquick.$QT_VERSION_TAG
+ qhp.QtQuick.virtualFolder = qtquick
+ qhp.QtQuick.indexTitle = Qt Quick
+ qhp.QtQuick.indexRoot =
+
+ qhp.QtQuick.subprojects = qmltypes classes examples
+
+ qhp.QtQuick.subprojects.qmltypes.title = QML Types
+ qhp.QtQuick.subprojects.qmltypes.indexTitle = Qt Quick QML Types
+ qhp.QtQuick.subprojects.qmltypes.selectors = qmlclass
+ qhp.QtQuick.subprojects.qmltypes.sortPages = true
+
+ qhp.QtQuick.subprojects.classes.title = Classes
+ qhp.QtQuick.subprojects.classes.title = C++ Classes
+ qhp.QtQuick.subprojects.classes.indexTitle = Qt Quick C++ Classes
+ qhp.QtQuick.subprojects.classes.selectors = class fake:headerfile
+ qhp.QtQuick.subprojects.classes.sortPages = true
+
+ qhp.QtQuick.subprojects.examples.title = Examples
+ qhp.QtQuick.subprojects.examples.indexTitle = Qt Quick Examples and Tutorials
+ qhp.QtQuick.subprojects.examples.selectors = fake:example
+ \endcode
+
+ The documentation set may include one or more subprojects, which are added
+ to the table of contents under the name specified by \c title. The page
+ in the documentation referred to by the \c indexTitle acts as the index page
+ for the subproject. The page types to list under the subproject are specified
+ by \c selectors. The entries are alphabetically sorted if \c sortPages is set
+ to \c true.
+
+ \section2 Using Selectors
+
+ The \c selectors property specifies which page types are listed under the
+ table of contents entry for a subproject. Multiple selectors can be listed,
+ separated by whitespace.
+
+ \table
+ \header \li Selector \li Description
+ \row \li \c namespace \li Namespaces
+ \row \li \c class \li Classes
+ \row \li \c qmltype \li QML Types
+ \row \li \c qmlclass \li Alias for \c qmltype.
+ \row \li \c module[:name] \li C++ modules or members of the module
+ with a specified name.
+ \row \li \c qmlmodule[:name] \li QML modules or members of the module
+ with a specified name.
+ \row \li \c doc[:subtype] \li Documentation pages with a specified
+ \c subtype. Multiple subtypes can be
+ listed as a comma-separated list.
+ \row \li \c fake \li Alias for \c doc.
+ \row \li \c group[:groupname] \li Documentation pages for members of a
+ specified group, as added using the
+ \l {ingroup-command}
+ {\\ingroup} groupname command.
+ Multiple group names can be listed as
+ a comma-separated list.
+ (Introduced in QDoc 5.6).
+ \endtable
+
+ Available subtypes for the \c doc selector:
+
+ \table
+ \header \li Subtype \li Description
+ \row \li \c example \li Examples
+ \row \li \c headerfile \li Header files
+ \row \li \c page \li Documentation pages defined with the
+ \l {page-command} {\\page} command.
+ \endtable
+
+ For example, the following configuration would select example pages and
+ pages that include the \c {\ingroup tutorials} command:
+
+ \badcode
+ qhp.QtQuickControls.subprojects = examples
+ qhp.QtQuickControls.subprojects.examples.title = Examples and Tutorials
+ qhp.QtQuickControls.subprojects.examples.indexTitle = Qt Quick Controls Examples
+ qhp.QtQuickControls.subprojects.examples.selectors = doc:example group:tutorials
+ qhp.QtQuickControls.subprojects.examples.sortPages = true
+ \endcode
+
+ \section2 Adding Table of Contents
+
+ To create a table of contents for a manual, create a subproject with
+ a \c{type} property and set it to \c{manual}. The page in the documentation
+ referred to by the \c{indexTitle} property must contain a list of links
+ that acts as a table of contents for the whole manual. QDoc will take the
+ information in this list and create a table of contents for the subproject.
+
+ For example, the configuration file for Qt Creator defines only one
+ subproject for its documentation, including all the documentation in a
+ single manual:
+
+ \badcode
+ qhp.QtCreator.subprojects = manual
+ qhp.QtCreator.subprojects.manual.title = Qt Creator Manual
+ qhp.QtCreator.subprojects.manual.indexTitle = Qt Creator Manual
+ qhp.QtCreator.subprojects.manual.type = manual
+ \endcode
+
+ In this example, the page entitled "Qt Creator Manual" contains a nested
+ list of links to pages in the documentation which is duplicated in
+ Qt Assistant's Contents tab.
+*/
+
+/*!
+ \page 24-qdoc-configuration-htmlvariables.html
+ \previouspage Creating Help Project Files
+ \nextpage Supporting Derived Projects
+
+ \keyword HTML Specific Configuration Variables
+ \title Format-specific Configuration Variables
+
+ The format-specific configuration variables define the generated
+ documentation's style, or define the contents of the
+ documentation's footer or postheader.
+
+ Some of the configuration values are relevant only for the HTML
+ output format and their values contain raw HTML.
+
+ \target HTML.footer-variable
+ \section1 HTML.footer
+
+ The \c HTML.footer variable defines the content of the generated
+ HTML documentation's footer.
+
+ The footer is rendered at the bottom of the generated
+ documentation page.
+
+ The variable's value is given as raw HTML code enclosed by
+ quotation marks. Note that if the value spans several lines, each
+ line needs to be enclosed by quotation marks.
+
+ \badcode
+ HTML.footer = "<p /><address><hr /><div align=\"center\">\n" \
+ ...
+ "</tr></table></div></address>"
+ \endcode
+
+ \target FORMAT.nosubdirs
+ \section1 <FORMAT>.nosubdirs
+
+ A boolean value which, when \c true, enables single-directory output
+ mode; all generated files go to \l {FORMAT.outputsubdir} directory,
+ instead of a subdirectory based on the documentation project name.
+
+ \target FORMAT.outputsubdir
+ \section1 <FORMAT>.outputsubdir
+
+ Defines the subdirectory under \l outputdir where documentation is
+ generated.
+
+ \badcode
+ HTML.nosubdirs = true
+ HTML.outputsubdir = html
+ \endcode
+
+ With above, the output goes to \c {<outputdir>/html}.
+
+ \target HTML.postheader-variable
+ \section1 HTML.postheader
+
+ The \c HTML.postheader variable defines the content of the
+ generated HTML documentation's postheader.
+
+ The header is rendered at the top of the generated documentation
+ page.
+
+ The variable's value is given as raw HTML enclosed by quotation
+ marks. Note that if the value spans several lines, each line needs
+ to be enclosed by quotation marks.
+
+ \badcode
+ HTML.postheader = "<table border=\"0\"..." \
+ ...
+ "<img src=\"images/qt-logo.png\" \
+ "align=\"right\" width=\"203\" height=\"32\""\
+ "border=\"0\" />" \
+ "</td></tr>" \
+ "</table>"
+ \endcode
+
+ The complete variable entry in \l qtgui.qdocconf provides the
+ standard header of the \l {http://doc.qt.io/qt-5/qtgui-index.html}
+ {Qt GUI Documentation}.
+
+ \target FORMAT.quotinginformation
+ \section1 <FORMAT>.quotinginformation
+
+ A boolean value which, when \c true, generates references to
+ quoted content (for example, \l {quotefromfile-command}{\\quotefromfile}
+ and \l {snippet-command}{\\snippet} commands) instead of including
+ the content directly.
+
+ Used currently for the \c WebXML output format.
+
+ \badcode
+ WebXML.quotinginformation = true
+ \endcode
+
+ \target HTML.style-variable
+ \section1 HTML.style
+
+ The HTML.style variable defines the style for
+ the generated HTML documentation.
+
+ The variable's value is given as raw HTML enclosed by quotation
+ marks. Note that if the value spans several lines, each line needs
+ to be enclosed by quotation marks.
+
+ \badcode
+ HTML.style = "h3.fn,span.fn" \
+ "{ margin-left: 1cm; text-indent: -1cm; }\n" \
+ "a:link { color: #004faf; text-decoration: none }\n" \
+ "a:visited" \
+ "{ color: #672967; text-decoration: none }\n" \
+ "td.postheader { font-family: sans-serif }\n" \
+ "tr.address { font-family: sans-serif }\n" \
+ "body { background: #ffffff; color: black; }"
+ \endcode
+
+ \target HTML.stylesheets-variable
+ \section1 HTML.stylesheets
+
+ The HTML.stylesheets variable defines a list of stylesheets
+ to use for the generated HTML documentation.
+
+ Using separate stylesheets for the documentation makes it easier
+ to customize and experiment with the style used once the contents
+ has been generated. Typically, it is only necessary to define a
+ single stylesheet for any set of documentation; for example:
+
+ \badcode
+ HTML.stylesheets = path/to/classic.css
+ \endcode
+
+ QDoc expects to find stylesheets in the directory containing the
+ \l qtgui.qdocconf file, and it will copy those specified to the output
+ directory alongside the HTML pages.
+
+ \target HTML.tocdepth
+ \section1 <FORMAT>.tocdepth
+
+ The \c {<FORMAT>.tocdepth} variable defines how many document sections
+ are printed in the table of contents. Setting tocdepth to \c 0 disables
+ the table of contents while leaving it undefined prints all document
+ sections.
+
+ Currently only has an effect for the HTML format:
+
+ \badcode
+ HTML.tocdepth = 3
+ \endcode
+*/
+
+/*!
+ \page 25-qdoc-configuration-derivedprojects.html
+ \previouspage Format-specific Configuration Variables
+ \nextpage Example Manifest Files
+
+ \title Supporting Derived Projects
+
+ Some configuration variables allow you to use QDoc to support
+ Qt-based projects. They allow your project to contain links to the
+ online Qt documentation, which means that QDoc will be able to
+ create links to the class reference documentation, without any
+ explicit linking command.
+
+ \target description-variable
+ \section1 description
+
+ The description variable holds a short description of the
+ associated project.
+
+ See also \l project.
+
+ \target indexes-variable
+ \section1 indexes
+
+ The \c indexes variable defines a set of paths to index files to load.
+
+ \badcode
+ indexes = \
+ $QT_INSTALL_DOCS/qtcore/qtcore.index \
+ $SOME_OTHER_PROJECT/doc/foo.index
+ \endcode
+
+ The \c indexes variable provides an alternative to \l depends for
+ defining project's dependencies. As direct paths are provided, no
+ \c -indexdir command line option(s) are required when invoking QDoc.
+
+ It is possible to define dependencies using either variable. Qt
+ documentation only uses the \c depends variable.
+
+ See also \l depends, \l project and \l url.
+
+ \target project-variable
+ \section1 project
+
+ The \c project variable provides a name for the project associated
+ with the \c .qdocconf file. This is a mandatory variable that all
+ projects must set.
+
+ The project's name is used to form a file name for the associated
+ project's \e index file.
+
+ \badcode
+ project = QtCreator
+ \endcode
+
+ This will cause an index file called \c qtcreator.index to be
+ created.
+
+ If the project name contains whitespace or special characters,
+ these are replaced with dashes ('-') in the generated index file
+ name.
+
+ See also \l depends, \l indexes, and \l description.
+
+ \target url-variable
+ \section1 url
+
+ The \c url variable holds the base URL for the
+ documentation associated with the current project.
+
+ The URL is stored in the generated index file for the
+ project. When we use the index on its own, QDoc will use this as
+ the base URL when constructing links to classes, functions, and
+ other things listed in the index.
+
+ \badcode
+ project = QtCore
+ description = Qt Core Reference Documentation
+ url = https://doc.qt.io/qt/
+
+ ...
+ \endcode
+
+ This ensures that whenever QDoc generates
+ references to entities in the Qt Core module, the base URL is
+ \c https://doc.qt.io/qt/.
+
+ See also \l depends, \l indexes and \l {url.examples}.
+
+ \target url.examples-variable
+ \section1 url.examples
+
+ The \c url.examples variable holds the base URL for the examples
+ associated with the current project.
+
+ If defined, a link to the example project directory is generated
+ at the end of each example documentation page. The \c url.examples
+ variable refers to the root directory of the examples related to
+ this project; it can be a link to an online repository (starting
+ with \e http:// or \e https://), or to the local file system
+ (\c file://).
+
+ If \c url.examples is not defined, QDoc will output a list of
+ example's files and images instead.
+
+ For example, given these definitions:
+
+ \badcode
+ url.examples = "https://code.qt.io/cgit/qt/qtbase.git/tree/examples/"
+ examplesinstallpath = corelib
+ \endcode
+
+ Then, for the following \l {example-command}{\\example} command:
+
+ \badcode *
+ /\1!
+ \example threads/semaphores
+ ...
+ \1/
+ \endcode
+
+ QDoc generates a link to
+ \c https://code.qt.io/cgit/qt/qtbase.git/tree/examples/corelib/threads/semaphores.
+
+ If the URL contains more components (for example, a query string)
+ after the example path, \\1 can be used as a placeholder for the
+ path:
+
+ \badcode
+ url.examples = "https://code.qt.io/cgit/qt/qtbase.git/tree/examples/\1?h=$QT_VER"
+ examplesinstallpath = corelib
+ \endcode
+
+ Given the same \\example command as above and assuming that
+ \c $QT_VER expands to \c {5.13}, the generated URL is
+ \c https://code.qt.io/cgit/qt/qtbase.git/tree/examples/corelib/threads/semaphores?h=5.13.
+
+ \c {url.examples} variable was introduced in QDoc version 5.13.
+
+ See also \l url, \l examplesinstallpath, and \l {example-command}{\\example}.
+
+ \target howto
+ \section1 How to Support Derived Projects
+
+ This feature makes use of the comprehensive indexes generated by
+ QDoc when it creates the Qt reference documentation.
+
+ For example, \l qtgui.qdocconf (the configuration file for Qt GUI)
+ contains the following variable definitions:
+
+ \badcode
+ project = QtGui
+ description = Qt GUI Reference Documentation
+ url = http://doc.qt.io/qt/
+
+ ...
+ \endcode
+
+ The \l project variable name is used to form a file name for the
+ index file; in this case the \c qtgui.index file is created. The \l
+ url is stored in the index file. Afterwards, QDoc will use this
+ as the base URL when constructing links to classes, functions,
+ and other things listed in the index.
+
+ See also \l depends, \l indexes, \l project, and \l url.
+*/
+
+/*!
+ \page 26-qdoc-configuration-example-manifest-files.html
+ \previouspage Supporting Derived Projects
+
+ \title Example Manifest Files
+
+ QDoc generates example-manifest.xml files that contain information about
+ all documented examples. These files are used by Qt Creator to present a
+ list of examples in its welcome screen and to link to their documentation.
+
+ \section1 Manifest XML Structure
+
+ A manifest file has the following structure:
+
+ \badcode
+ <?xml version="1.0" encoding="UTF-8"?>
+ <instructionals module="QtGui">
+ <examples>
+ <example
+ name="Analog Clock Window"
+ docUrl="qthelp://org.qt-project.qtgui.502/qtgui/analogclock.html"
+ projectPath="gui/analogclock/analogclock.pro"
+ imageUrl="qthelp://org.qt-project.qtgui.502/qtgui/images/analogclock-window.png">
+ <description><![CDATA[The Analog Clock Window example shows how
+ to draw the contents of a custom window.]]></description>
+ <tags>analog,clock,window</tags>
+ <fileToOpen>gui/analogclock/main.cpp</fileToOpen>
+ </example>
+ ...
+ </examples>
+ </instructionals>
+ \endcode
+
+ Each \c {<example>} element contains information about a name,
+ description, the location of the project file and documentation,
+ as well as a list of tags associated with the example.
+
+ \target metacontent
+ \section1 Manifest Meta Content
+
+ It is possible to augment the manifest files with additional
+ meta-content - that is, extra attributes and tags for selected
+ examples, using the \c manifestmeta configuration command.
+
+ One use case for meta-content is highlighting a number of prominent
+ examples. Another is improving search functionality by adding
+ relevant keywords as tags for a certain category of examples.
+
+ The examples for which meta-content is applied to is specified using
+ one or more filters. Matching examples to filters is done based on
+ names, with each example name prefixed with a module name and a
+ slash. Simple wildcard matching is supported; by using \c {*} at the
+ end it's possible to match multiple examples with a single string.
+
+ Example:
+
+ \badcode
+ manifestmeta.filters = highlighted sql webkit global
+
+ manifestmeta.highlighted.names = "QtGui/Analog Clock Window" \
+ "QtWidgets/Analog Clock"
+ manifestmeta.highlighted.attributes = isHighlighted:true
+
+ manifestmeta.sql.names = "QtSql/*"
+ manifestmeta.sql.tags = database,sql
+
+ manifestmeta.webkit.names = "QtWebKitExamples/*"
+ manifestmeta.webkit.tags = webkit
+
+ manifestmeta.global.names = *
+ manifestmeta.global.tags = qt6
+ \endcode
+
+ Above, an \c isHighlighted attribute is added to two examples. If
+ the attribute value is omitted, QDoc uses the string \c {true} by
+ default. Extra tags are added for Qt WebKit and Qt SQL examples, and
+ another tag is applied to all examples by using just \c {*} as the
+ match string.
+*/
+
+/*!
+ \page 21-1-minimum-qdocconf.html
+ \previouspage qtgui.qdocconf
+ \nextpage The QDoc Configuration File
+
+ \title minimum.qdocconf
+
+ \quotefile examples/minimum.qdocconf
+*/
+
+/*!
+ \page 21-2-qtgui-qdocconf.html
+ \previouspage Supporting Derived Projects
+ \nextpage minimum.qdocconf
+
+ \title qtgui.qdocconf
+
+ \quotefile files/qtgui.qdocconf
+*/
diff --git a/src/qdoc/qdoc/doc/qdoc-manual-topiccmds.qdoc b/src/qdoc/qdoc/doc/qdoc-manual-topiccmds.qdoc
new file mode 100644
index 000000000..c1e6c2e5c
--- /dev/null
+++ b/src/qdoc/qdoc/doc/qdoc-manual-topiccmds.qdoc
@@ -0,0 +1,1049 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page 13-qdoc-commands-topics.html
+ \previouspage Command Index
+ \nextpage Context Commands
+
+ \title Topic Commands
+
+ A topic command tells QDoc which source code element is being
+ documented. Some topic commands allow you to create documentation
+ pages that aren't tied to any underlying source code element.
+
+ When QDoc processes a QDoc comment, it tries to connect the
+ comment to an element in the source code by first looking for a
+ topic command that names the source code element. If there is no
+ topic command, QDoc tries to connect the comment to the source
+ code element that immediately follows the comment. If it can't do
+ either of these and if there is no topic command that indicates
+ the comment does not have an underlying source code element (e.g.
+ \l{page-command} {\\page}), then the comment is discarded.
+
+ \target topic argument
+
+ The name of the entity being documented is usually the only
+ argument for a topic command. Use the complete name. Sometimes
+ there can be a second parameter in the argument. See e.g. \l
+ {page-command} {\\page}.
+
+ \code
+ \enum QComboBox::InsertPolicy
+ \endcode
+
+ The \l {fn-command} {\\fn} command is a special case. For the \l
+ {fn-command} {\\fn} command, use the function's signature
+ including the class qualifier.
+
+ \code
+ \fn void QGraphicsWidget::setWindowFlags(Qt::WindowFlags wFlags)
+ \endcode
+
+ A topic command can appear anywhere in a comment but must stand
+ alone on its own line. It is good practice is to let the topic command
+ be the first line of the comment. If the argument spans several
+ lines, make sure that each line (except the last one) is ended
+ with a backslash. Moreover, QDoc counts parentheses, which means
+ that if it encounters a '(' it considers everything until the
+ closing ')' as its argument.
+
+ If a topic command is repeated with different arguments, the
+ same documentation will appear for both the units.
+
+ \badcode *
+ /\1!
+ \fn void PreviewWindow::setWindowFlags()
+ \fn void ControllerWindow::setWindowFlags()
+
+ Sets the widgets flags using the QWidget::setWindowFlags()
+ function.
+
+ Then runs through the available window flags, creating a text
+ that contains the names of the flags that matches the flags
+ parameter, displaying the text in the widgets text editor.
+ \1/
+ \endcode
+
+ The \c PreviewWindow::setWindowFlags() and \c
+ ControllerWindow::setWindowFlags() functions will get the same
+ documentation.
+
+ \section2 Nomenclature for files generated by topic commands
+
+ For many topic commands, such as \l {page-command}{\\page}, QDoc
+ generates a file when processing the documentation.
+
+ QDoc normalizes the name of each file before writing it to disk.
+ The following operations are performed:
+
+ \list
+ \li All sequences of non alphanumeric characters are replaced with a hyphen, '-'.
+ \li All uppercase letters are replaced with their lowercase equivalent.
+ \li All trailing hyphens are removed.
+ \endlist
+
+ For example, the following command generates a file named
+ \c{this-generates-a-file-and-writes-it-to-disk.html}:
+
+ \badcode
+ \page this_generates_a_file_(and_writes_it_to_DISK)-.html
+ \endcode
+
+ As the example shows, the name that is given to the file in the
+ command might differ from the name of the actual file that is
+ written to disk.
+
+ \section3 Prefixes and Suffixes for generated files
+
+ When QDoc generates a file, it may add a prefix, a suffix, or both,
+ depending on the element that the file will document.
+
+ The table below shows what those prefixes and suffixes are for
+ various elements.
+
+ \table
+ \header
+ \li Element
+ \li Prefix
+ \li Suffix
+ \li Command
+ \row
+ \li QML Modules
+ \li None
+ \li "-qmlmodule"
+ \li \l {qmlmodule-command}{\\qmlmodule}
+ \row
+ \li Modules
+ \li None
+ \li "-module"
+ \li \l {module-command}{\\module}
+ \row
+ \li Examples
+ \li The project name, as given by the \l
+ {project-variable}{project configuration variable},
+ followed by a hyphen.
+ \li "-example"
+ \li \l {example-command}{\\example}
+ \row
+ \li QML Types
+ \li The output prefix for QML, as given by the \l
+ {outputprefixes-variable}{outputprefixes configuration
+ variable}.
+
+ If the module that contains this type is known to QDoc,
+ the module name is added as a prefix, followed by the QML
+ output suffix, as defined by the \l
+ {outputsuffixes-variable}{outputsuffixes configuration
+ variable} and a hyphen.
+ \li None
+ \li \l {qmltype-command}{\\qmltype}
+ \endtable
+
+ \target class-command
+ \section1 \\class
+
+ The \\class command is for documenting a C++ \e class, a C/C++
+ \e struct, or a \e union. The argument is the complete, qualified
+ name of the class. The command tells QDoc that a class is part of
+ the public API, and lets you enter a detailed description.
+
+ \badcode *
+ /\1!
+ \class QMap::iterator
+ \inmodule QtCore
+
+ \brief The QMap::iterator class provides an STL-style
+ non-const iterator for QMap and QMultiMap.
+
+ QMap features both \l{STL-style iterators} and
+ \l{Java-style iterators}. The STL-style iterators ...
+ \1/
+ \endcode
+
+ The HTML documentation for the named class is written to a
+ \c{.html} file named from the class name, in lower case, and with
+ the double colon qualifiers replaced with '-'. For example, the
+ documentation for the \c QMap::iterator class is written to \c
+ qmap-iterator.html.
+
+ The file contains the class description from the \\class comment,
+ plus the documentation generated from QDoc comments for all the
+ class members: a list of the class's types, properties,
+ functions, signals, and slots.
+
+ In addition to the detailed description of the class, the \\class
+ comment typically contains an \l {inmodule-command} {\\inmodule}
+ command, as well as a \l {brief-command} {\\brief} description.
+ Here is a very simple example:
+
+ \badcode *
+ /\1!
+ \class PreviewWindow
+ \inmodule CustomWidgets
+ \brief The PreviewWindow class is a custom widget.
+ displaying the names of its currently set
+ window flags in a read-only text editor.
+
+ \ingroup miscellaneous
+
+ The PreviewWindow class inherits QWidget. The widget
+ displays the names of its window flags set with the \l
+ {function} {setWindowFlags()} function. It is also
+ provided with a QPushButton that closes the window.
+
+ ...
+
+ \sa QWidget
+ \1/
+ \endcode
+
+ The way QDoc renders this \\class depends on your \c {style.css}
+ file.
+
+ \target enum-command
+ \section1 \\enum
+
+ The \\enum command is for documenting a C++ enum type. The
+ argument is the full name of the enum type.
+
+ The enum values are documented in the \\enum comment using the \l
+ {value-command} {\\value} command. If an enum value is not
+ documented with \\value, QDoc emits a warning. These warnings can
+ be avoided using the \l {omitvalue-command} {\\omitvalue} command
+ to tell QDoc that an enum value should not be documented. The enum
+ documentation will be included on the class reference page, header
+ file page, or namespace page where the enum type is defined. For
+ example, consider the enum type \c {Corner} in the Qt namespace:
+
+ \code
+ enum Corner {
+ TopLeftCorner = 0x00000,
+ TopRightCorner = 0x00001,
+ BottomLeftCorner = 0x00002,
+ BottomRightCorner = 0x00003
+ #if defined(QT3_SUPPORT) && !defined(Q_MOC_RUN)
+ ,TopLeft = TopLeftCorner,
+ TopRight = TopRightCorner,
+ BottomLeft = BottomLeftCorner,
+ BottomRight = BottomRightCorner
+ #endif
+ };
+ \endcode
+
+ This enum can be cocumented this way:
+
+ \badcode *
+ /\1!
+ \enum Qt::Corner
+
+ This enum type specifies a corner in a rectangle:
+
+ \value TopLeftCorner
+ The top-left corner of the rectangle.
+ \value TopRightCorner
+ The top-right corner of the rectangle.
+ \value BottomLeftCorner
+ The bottom-left corner of the rectangle.
+ \value BottomRightCorner
+ The bottom-right corner of the rectangle.
+
+ \omitvalue TopLeft
+ \omitvalue TopRight
+ \omitvalue BottomLeft
+ \omitvalue BottomRight
+ Bottom-right (omitted; not documented).
+ \1/
+ \endcode
+
+ Note the inclusion of the namespace qualifier.
+
+ See also \l {value-command} {\\value} and \l {omitvalue-command} {\\omitvalue}.
+
+ \target example-command
+ \section1 \\example
+
+ The \\example command is for documenting an example. The argument
+ is the example's path relative to one of the paths listed in the
+ \l {exampledirs-variable} {exampledirs} variable in the QDoc
+ configuration file.
+
+ The documentation page will be output to \c {modulename-path-to-example}.html.
+ QDoc will add a list of all the example's source and images files at the end
+ of the page, unless \l {noautolist-command}{\\noautolist} command is used or
+ the configuration variable \l {url.examples-variable}{url.examples} is defined
+ for the project.
+
+ For example, if \l {exampledirs-variable} {exampledirs} contains
+ \c $QTDIR/examples/widgets/imageviewer, then
+
+ \badcode *
+ /\1!
+ \example widgets/imageviewer
+ \title ImageViewer Example
+ \subtitle
+
+ The example shows how to combine QLabel and QScrollArea
+ to display an image.
+
+ ...
+ \1/
+ \endcode
+
+ \b {See also:} \l {noautolist-command}{\\noautolist},
+ \l {url.examples-variable}{url.examples},
+ \l {meta-command}{\\meta}
+
+ \target externalpage-command
+ \section1 \\externalpage
+
+ The \\externalpage command assigns a title to an external URL.
+
+ \badcode *
+ /\1!
+ \externalpage http://doc.qt.io/
+ \title Qt Documentation Site
+ \1/
+ \endcode
+
+ This allows you to include a link to the external page in your
+ documentation this way:
+
+ \badcode *
+ /\1!
+ At the \l {Qt Documentation Site} you can find the latest
+ documentation for Qt, Qt Creator, the Qt SDK and much more.
+ \1/
+ \endcode
+
+ To achieve the same result without using the \\externalpage
+ command, you would have to hard-code the address into your
+ documentation:
+
+ \badcode *
+ /\1!
+ At the \l {http://doc.qt.io/}{Qt Documentation Site}
+ you can find the latest documentation for Qt, Qt Creator, the Qt SDK
+ and much more.
+ \1/
+ \endcode
+
+ The \\externalpage command makes it easier to maintain the
+ documentation. If the address changes, you only need to change the
+ argument of the \\externalpage command.
+
+ \target fn-command
+ \section1 \\fn (function)
+
+ The \\fn command is for documenting a function. The argument is
+ the function's signature, including its template parameters (if
+ any), return type, const-ness, and list of formal arguments with
+ types. If the named function doesn't exist, QDoc emits a warning.
+
+ Since QDoc version 6.0, the \\fn command can be used for documenting
+ class members that are not explicitly declared in the header,
+ but are implicitly generated by the compiler; default constructor
+ and destructor, copy constructor and move-copy constructor,
+ assignment operator, and move-assignment operator.
+
+ When documenting an hidden friend, it is required to prepend the
+ enclosing class name to the function name.
+ For example, for:
+
+ \code
+ class Foo {
+ ...
+ friend bool operator==(const Foo&, const Foo&) { ... }
+ ...
+ }
+ \endcode
+
+ The command should be written as \c{"\fn Foo::operator==(const
+ Foo&, const Foo&)"} and not as the free function \c{"\fn
+ operator==(const Foo&, const Foo&)"}.
+
+ Failure to do so will have QDoc complaining about being unable to
+ resolve the function.
+
+ \note The \\fn command is QDoc's default command: when no
+ topic command can be found in a QDoc comment, QDoc tries to tie
+ the documentation to the following code as if it is the
+ documentation for a function. Hence, it is normally not necessary
+ to include this command when documenting a function, if the
+ function's QDoc comment is written immediately above the function
+ implementation in the \c .cpp file. But it must be present when
+ documenting an inline function in the \c .cpp file that is
+ implemented in the \c .h file.
+
+ \badcode *
+ /\1!
+ \fn bool QToolBar::isAreaAllowed(Qt::ToolBarArea area) const
+
+ Returns \c true if this toolbar is dockable in the given
+ \a area; otherwise returns \c false.
+ \1/
+ \endcode
+
+ \note Running in debug mode (pass the \c {-debug} command line option
+ or set the \c QDOC_DEBUG environment variable before invoking QDoc)
+ can help troubleshoot \\fn commands that QDoc fails to parse. In
+ debug mode, additional diagnostic information is available.
+
+ See also \l {overload-command} {\\overload}.
+
+ \target group-command
+ \section1 \\group
+
+ The \\group command creates a separate page that lists the classes,
+ pages, or other entities belonging to a named group. The argument
+ is the group name.
+
+ A class is included in a group by using the \l {ingroup-command}
+ {\\ingroup} command. Overview pages can also be related to a group
+ using the same command, but the list of overview pages must be
+ requested explicitly using the \l {generatelist-command}
+ {\\generatelist} command (see example below).
+
+ The \\group command is typically followed by a \l {title-command}
+ {\\title} command and a short introduction to the group. The
+ HTML page for the group is written to an \c {.html} file named
+ <lower-case-group-name>.html.
+
+ Each entity in the group is listed as a link (using page title
+ or class name), followed by a decription from the \l {brief-command}
+ {\\brief} command in the entity's documentation.
+
+ \badcode *
+ /\1!
+ \group io
+ \title Input/Output and Networking
+ \1/
+ \endcode
+
+ QDoc generates a group page \c{io.html}.
+
+ Note that overview pages related to the group must be listed
+ explicitly using the \l {generatelist-command} {\\generatelist}
+ command with the \c related argument.
+
+ \badcode *
+ /\1!
+ \group architecture
+
+ \title Architecture
+
+ These documents describe aspects of Qt's architecture
+ and design, including overviews of core Qt features and
+ technologies.
+
+ \generatelist{related}
+ \1/
+ \endcode
+
+ See also \l {ingroup-command} {\\ingroup}, \l {annotatedlist-command}
+ {\\annotatedlist}, \l {generatelist-command} {\\generatelist}, and
+ \l {noautolist-command}{\\noautolist}.
+
+ \target headerfile-command
+ \section1 \\headerfile
+
+ The \\headerfile command is for documenting the global functions,
+ types and macros that are declared in a header file, but not in a
+ namespace. The argument is the name of the header file. The HTML
+ page is written to a \c {.html} file constructed from the header
+ file argument.
+
+ The documentation for a function, type, or macro that is declared
+ in the header file being documented, is included in the header file
+ page using the \l {relates-command} {\\relates} command.
+
+ If the argument doesn't exist as a header file, the \\headerfile
+ command creates a documentation page for the header file anyway.
+
+ \badcode *
+ /\1!
+ \headerfile <QtAlgorithms>
+
+ \title Generic Algorithms
+
+ \brief The <QtAlgorithms> header file provides
+ generic template-based algorithms.
+
+ Qt provides a number of global template functions in \c
+ <QtAlgorithms> that work on containers and perform
+ well-know algorithms.
+ \1/
+ \endcode
+
+ QDoc generates a header file page, \c{qtalgorithms.html}.
+
+ See also \l {inheaderfile-command}{\\inheaderfile}.
+
+ \target macro-command
+ \section1 \\macro
+
+ The \\macro command is for documenting a C++ macro. The argument
+ is the macro in one of three styles: function-like macros like
+ Q_ASSERT(), declaration-style macros like Q_PROPERTY(), and macros
+ without parentheses like Q_OBJECT.
+
+ The \\macro comment must contain a \l {relates-command}
+ {\\relates} command that attaches the macro comment to a class,
+ header file, or namespace. Otherwise, the documentation will be
+ lost.
+
+ \target module-command
+ \section1 \\module
+
+ The \\module creates a page that lists the classes belonging to
+ the module specified by the command's argument. A class included
+ in the module by including the \l {inmodule-command} {\\inmodule}
+ command in the \\class comment.
+
+ The \\module command is typically followed by a \l {title-command}
+ {\\title} and a \l {brief-command} {\\brief} command. Each class
+ is listed as a link to the class reference page followed by the
+ text from the class's \l {brief-command} {\\brief} command. For
+ example:
+
+ \badcode *
+ /\1!
+ \module QtNetwork
+
+ \title Qt Network Module
+
+ \brief Contains classes for writing TCP/IP clients and servers.
+
+ The network module provides classes to make network
+ programming easier and portable. It offers both
+ high-level classes such as QNetworkAccessManager that
+ implements application-level protocols, and
+ lower-level classes such as QTcpSocket, QTcpServer, and
+ QUdpSocket.
+ \1/
+ \endcode
+
+ The \l {noautolist-command} {\\noautolist} command can be used here
+ to omit the automatically generated list of classes at the end.
+
+ See also \l {inmodule-command} {\\inmodule}
+
+ \target namespace-command
+ \section1 \\namespace
+
+ The \\namespace command is for documenting the contents of the C++
+ namespace named as its argument. The reference page QDoc generates
+ for a namespace is similar to the reference page it generates for a
+ C++ class.
+
+ \badcode *
+ /\1!
+ \namespace Qt
+
+ \brief Contains miscellaneous identifiers used throughout the Qt library.
+ \1/
+ \endcode
+
+ Note that in C++, a particular namespace can be used in more
+ than one module, but when C++ elements from different modules
+ are declared in the same namespace, the namespace itself must
+ be documented in one module only. For example, namespace Qt in
+ the example above contains types and functions from both QtCore
+ and QtGui, but it is documented with the \\namespace command
+ only in QtCore.
+
+ \target page-command
+ \section1 \\page
+
+ The \\page command is for creating a stand-alone documentation
+ page.
+
+ The \\page command expects a single argument that represents the
+ name of the file where QDoc should store the page.
+
+ The page title is set using the \l {title-command} {\\title}
+ command.
+
+ \badcode *
+ /\1!
+ \page aboutqt.html
+
+ \title About Qt
+
+ Qt is a C++ toolkit for cross-platform GUI
+ application development. Qt provides single-source
+ portability across Microsoft Windows, macOS, Linux,
+ and all major commercial Unix variants.
+
+ Qt provides application developers with all the
+ functionality needed to build applications with
+ state-of-the-art graphical user interfaces. Qt is fully
+ object-oriented, easily extensible, and allows true
+ component programming.
+
+ ...
+ \1/
+ \endcode
+
+ QDoc renders this page in \c {aboutqt.html}.
+
+ \target property-command
+ \section1 \\property
+
+ The \\property command is for documenting a Qt property. The
+ argument is the full property name.
+
+ A property is defined using the Q_PROPERTY() macro. The macro
+ takes as arguments the property's name and its set, reset and get
+ functions.
+
+ \badcode
+ Q_PROPERTY(QString state READ state WRITE setState)
+ \endcode
+
+ The set, reset and get functions don't need to be documented,
+ documenting the property is sufficient. QDoc will generate a list
+ of the access function that will appear in the property
+ documentation which in turn will be located in the documentation
+ of the class that defines the property.
+
+ The \\property command comment typically includes a \l
+ {brief-command} {\\brief} command. For properties the \l
+ {brief-command} {\\brief} command's argument is a sentence
+ fragment that will be included in a one line description of the
+ property. The command follows the same rules for the
+ description as the \l {variable-command} {\\variable} command.
+
+ \badcode *
+ /\1!
+ \property QPushButton::flat
+ \brief Whether the border is disabled.
+
+ This property's default is false.
+ \1/
+ \endcode
+
+ \target qmlattachedproperty-command
+ \section1 \\qmlattachedproperty
+
+ The \\qmlattachedproperty command is for documenting a QML
+ property that will be attached to some QML type. See
+ \l{Attached Properties and Attached Signal Handlers}
+ {Attached Properties}. The argument is the rest of the line.
+ It must start with the property type, followed by the QML
+ type name where the property is declared, the \c{::}
+ qualifier, and finally the property name.
+
+ For example, to document a boolean QML attached property named
+ \c isCurrentItem for the \c ListView type:
+
+ \badcode *
+ /\1!
+ \qmlattachedproperty bool ListView::isCurrentItem
+
+ This attached property is \c true if this delegate is the current
+ item; otherwise false.
+
+ It is attached to each instance of the delegate.
+
+ This property may be used to adjust the appearance of the current
+ item, for example:
+
+ \snippet doc/src/snippets/declarative/listview/listview.qml isCurrentItem
+ \1/
+ \endcode
+
+ QDoc includes this attached property on the QML reference page for the
+ \l [QML] {ListView} type.
+
+ \note Like \l{qmlproperty-command}{\\qmlproperty}, \\qmlattachedproperty
+ accepts a QML module identifier as part of its argument.
+
+ \target qmlattachedsignal-command
+ \section1 \\qmlattachedsignal
+
+ The \\qmlattachedsignal command is for documenting an attachable
+ \l{Signal and Handler Event System}{signal}. The \\qmlattachedsignal
+ command is used just like the \l{qmlsignal-command} {\\qmlsignal} command.
+
+ The argument is the rest of the line. It should be the name of the
+ QML type where the signal is declared, the \c{::}
+ qualifier, and finally the signal name. For example, a QML
+ attached signal named \c add() in the \c GridView
+ element is documented like this:
+
+ \badcode *
+ /\1!
+ \qmlattachedsignal GridView::add()
+ This attached signal is emitted immediately after an item is added to the view.
+ \1/
+ \endcode
+
+ QDoc includes this documentation on the QML reference page for the
+ \l GridView element.
+
+ \note Like \l{qmlproperty-command}{\\qmlproperty}, \\qmlattachedsignal accepts
+ a QML module identifier as part of its argument.
+
+ \target qmlvaluetype-command
+ \section1 \\qmlvaluetype
+
+ The \\qmlvaluetype command is for documenting a \l [QtQml]
+ {QML Value Types}{value type} for QML. The command takes
+ a type name as its only argument.
+
+ \\qmlvaluetype is functionally identical to the
+ \l {qmltype-command}{\\qmltype} command. The only difference
+ is that the type will be titled (and grouped) as a
+ \e {QML value type}.
+
+ \target qmlclass-command
+ \section1 \\qmlclass
+
+ This command is deprecated. Use \l{qmltype-command} {\\qmltype}
+ instead.
+
+ \target qmlmethod-command
+ \section1 \\qmlmethod
+
+ The \\qmlmethod command is for documenting a QML method. The
+ argument is the complete method signature, including return
+ type and parameter names and types.
+
+ \badcode *
+ /\1!
+ \qmlmethod void TextInput::select(int start, int end)
+
+ Causes the text from \a start to \a end to be selected.
+
+ If either start or end is out of range, the selection is not changed.
+
+ After having called this, selectionStart will become the lesser, and
+ selectionEnd the greater (regardless of the order passed to this method).
+
+ \sa selectionStart, selectionEnd
+ \1/
+ \endcode
+
+ QDoc includes this documentation on the element reference page for the
+ \l{http://doc.qt.io/qt-5/qml-qtquick-textinput.html#select-method}
+ {TextInput} element.
+
+ \target qmltype-command
+ \section1 \\qmltype
+
+ The \\qmltype command is for documenting a QML type. The command
+ has one argument, which is the name of the QML type.
+
+ If the QML type has an equivalent C++ class, you can specify that class
+ with the \qdoccmd nativetype context command.
+
+ The \l {inqmlmodule-command}{\\inqmlmodule} command documents the
+ QML module the type belongs to. The argument passed to this command
+ must match with a documented \l {qmlmodule-command}{\\qmlmodule}
+ page.
+
+ \badcode *
+ /\1!
+ \qmltype Transform
+ \nativetype QGraphicsTransform
+ \inqmlmodule QtQuick
+
+ \brief Provides a way to build advanced transformations on Items.
+
+ The Transform element is a base type which cannot be
+ instantiated directly.
+ \1/
+ \endcode
+
+ Here, the \e{\\qmltype} comment includes \qdoccmd nativetype
+ to specify that a Transform is the QML counterpart to the
+ C++ class QGraphicsTransform. A \\qmltype comment should
+ always include a \l {since-command} {\\since} command, because all
+ QML types are new. It should also include a \l{brief-command}
+ {\\brief} description. If a QML type is a member of a QML type group,
+ the \\qmltype comment should include one or more \l{ingroup-command}
+ {\\ingroup} commands.
+
+ \target qmlproperty-command
+ \section1 \\qmlproperty
+
+ The \\qmlproperty command is for documenting a QML property. The
+ argument is the rest of the line. The argument text should be the
+ property type, followed by the QML type name, the \c{::}
+ qualifier, and finally the property name. If we have a QML
+ property named \c x in QML type \c Translate, and the property
+ has type \c {real}, the \\qmlproperty for it would look like this:
+
+ \badcode *
+ /\1!
+ \qmlproperty real Translate::x
+
+ The translation along the X axis.
+ \1/
+ \endcode
+
+ QDoc includes this QML property on the QML reference page for the
+ \l [QML] {Translate} type.
+
+ If the QML property is of enumeration type, or it holds a bit-wise
+ combination of flags, the \l{value-command}{\\value} command can
+ be used to document the acceptable values.
+
+ QDoc accepts also a fully qualified property name, including the
+ QML module identifier:
+
+ \badcode
+ \qmlproperty bool QtQuick.Controls::Button::highlighted
+ \endcode
+
+ If specified, the module identifier (above, \c {QtQuick.Controls})
+ must match with value passed to \l {inqmlmodule-command}{\\inqmlmodule}
+ command in the associated \\qmltype documentation. If the name of
+ the QML type the property belongs to is unique across all types in
+ the documentation project, the module identifier can be omitted.
+
+ \target qmlsignal-command
+ \section1 \\qmlsignal
+
+ The \\qmlsignal command is for documenting a QML signal.
+ The argument is the rest of the line. The arguments should be: the QML type
+ where the signal is declared, the \c{::} qualifier, and finally the signal
+ name. If we have a QML signal named \c clicked(), the documentation for it
+ would look like this:
+
+ \badcode *
+ /\1!
+ \qmlsignal MouseArea::clicked(MouseEvent mouse)
+
+ This signal is emitted when there is a click. A click is defined as a
+ press followed by a release, both inside the MouseArea.
+ \1/
+ \endcode
+
+ QDoc includes this documentation on the QML reference page for the
+ \l [QML] {MouseArea} type.
+
+ \note Like \l{qmlproperty-command}{\\qmlproperty}, \\qmlsignal
+ accepts a QML module identifier as part of its argument.
+
+ \target qmlmodule-command
+ \section1 \\qmlmodule
+
+ Use the \c{\qmlmodule} command to create a \c QML module page. A QML
+ module page is a collection of QML types or any related material. The
+ command takes an optional \c <VERSION> number argument, and is similar
+ to the \l{group-command}.
+
+ A QML type is associated with a module by adding the
+ \l{inqmlmodule-command}{\\inqmlmodule} command to the comment-block that
+ documents the type. You can link to any member of a QML module using the
+ module name and two colons (\c{::}) prefix.
+
+ \badcode *
+ /\1!
+ A link to the TabWidget of the UI Component is \l {UIComponent::TabWidget}.
+ \1/
+ \endcode
+
+ QDoc generates a page for the module that lists all the members of the
+ module.
+
+ \badcode *
+ /\1!
+ \qmlmodule ClickableComponents
+
+ This is a list of the Clickable Components set. A Clickable component
+ responds to a \c clicked() event.
+ \1/
+ \endcode
+
+ \target inqmlmodule-command
+ \section1 \\inqmlmodule
+
+ A QML type is marked as being available under a specific QML module
+ import by inserting the \\inqmlmodule command in a
+ \l {qmltype-command}{\\qmltype} topic. The command takes the module
+ (import) name, without a version number, as the only argument.
+
+ The QML module name must match with a QML module documented with
+ the (\l{qmlmodule-command}{\\qmlmodule} command).
+
+ \badcode *
+ /\1!
+ \qmltype ClickableButton
+ \inqmlmodule ClickableComponents
+
+ A clickable button that responds to the \c click() event.
+ \1/
+ \endcode
+
+ QDoc outputs a row \e {Import statement: import <qmlmodule>}
+ in a table at the top of the QML type reference page.
+
+ When linking to QML types, the QML module identifier may appear in
+ the link target. For example:
+
+ \badcode
+ \l {ClickableComponents::}{ClickableButton}
+ \endcode
+
+ Links to the type reference page, with \e ClickableButton as the
+ link text.
+
+ \target instantiates-command
+ \section1 \\instantiates
+
+ The \\instantiates command is deprecated since Qt 6.8.
+ Use \qdoccmd nativetype instead.
+
+
+ \target nativetype-command
+ \section1 \\nativetype
+
+ The \\nativetype-command must be used in conjunction with the
+ \qdoccmd qmltype topic command. The command takes a C++ class as its
+ argument. If QDoc cannot find the C++ class, it issues a warning. This
+ command was introduced with Qt 6.8.
+
+ Use the \\nativetype-command to specify what the type is called in C++.
+ This ensures that the requisites block generated in the documentation for
+ the QML type contains an "In C++" entry. The C++ class will have a
+ corresponding "In QML" entry.
+
+ Any one QML type can only have one native type. QDoc issues a warning if
+ redefinition occurs. However, multiple QML types can have the same C++
+ class as their native type. The C++ class documentation will contain a list
+ of all corresponding types in QML.
+
+ \badcode *
+ /\1!
+ \qmltype Transform
+ \nativetype QGraphicsTransform
+ \inqmlmodule QtQuick
+
+ \brief Provides a way to build advanced transformations on Items.
+
+ The Transform element is a base type which cannot be
+ instantiated directly.
+ \1/
+ \endcode
+
+ Here, the \e{\\qmltype} topic includes \e{\\nativetype} to specify that a
+ Transform is called QGraphicsTransform in C++.
+
+
+ \target typealias-command
+ \section1 \\typealias
+
+ The \\typealias command is similar to \l {typedef-command}{\\typedef},
+ but specific to documenting a C++ type alias:
+
+ \code
+ class Foo
+ {
+ public:
+ using ptr = void*;
+ // ...
+ }
+ \endcode
+
+ This can be documented as
+
+ \badcode *
+ /\1!
+ \typealias Foo::ptr
+ \1/
+ \endcode
+
+ The \\typealias command was introduced in QDoc 5.15.
+
+ See also \l {typedef-command}{\\typedef}.
+
+ \target typedef-command
+ \section1 \\typedef
+
+ The \\typedef command is for documenting a C++ typedef. The
+ argument is the name of the typedef. The documentation for
+ the typedef will be included in the reference documentation
+ for the class, namespace, or header file in which the typedef
+ is declared. To relate the \\typedef to a class, namespace, or
+ header file, the \\typedef comment must contain a
+ \l {relates-command} {\\relates} command.
+
+ \badcode *
+ /\1!
+ \typedef QObjectList
+ \relates QObject
+
+ Synonym for QList<QObject>.
+ \1/
+ \endcode
+
+ Other typedefs are located on the reference page for the class
+ that defines them.
+
+ \badcode *
+ /\1!
+ \typedef QList::Iterator
+
+ Qt-style synonym for QList::iterator.
+ \1/
+ \endcode
+
+ See also \l {typealias-command}{\\typealias}.
+
+ \target variable-command
+ \section1 \\variable
+
+ The \\variable command is for documenting a class member variable
+ or a constant. The argument is the variable or constant name. The
+ \\variable command comment includes a \l {brief-command} {\\brief}
+ command. QDoc generates the documentation based on the text from
+ \\brief command.
+
+ The documentation will be located in the in the associated class,
+ header file, or namespace documentation.
+
+ In case of a member variable:
+
+ \badcode *
+ /\1!
+ \variable QStyleOption::palette
+ \brief The palette that should be used when painting
+ the control
+ \1/
+ \endcode
+
+ You can also document constants with the \\variable command. For
+ example, suppose you have the \c Type and \c UserType constants in
+ the QTreeWidgetItem class:
+
+ \code
+ enum { Type = 0, UserType = 1000 };
+ \endcode
+
+ For these, the \\variable command can be used this way:
+
+ \badcode *
+ /\1!
+ \variable QTreeWidgetItem::Type
+
+ The default type for tree widget items.
+
+ \sa UserType, type()
+ \1/
+ \endcode
+
+ \badcode *
+ /\1!
+ \variable QTreeWidgetItem::UserType
+
+ The minimum value for custom types. Values below
+ UserType are reserved by Qt.
+
+ \sa Type, type()
+ \1/
+ \endcode
+
+*/
diff --git a/src/qdoc/qdoc/doc/qdoc-manual.qdoc b/src/qdoc/qdoc/doc/qdoc-manual.qdoc
new file mode 100644
index 000000000..da84fe430
--- /dev/null
+++ b/src/qdoc/qdoc/doc/qdoc-manual.qdoc
@@ -0,0 +1,59 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page qdoc-index.html
+ \nextpage Introduction to QDoc
+
+ \title QDoc Manual
+
+ \list
+ \li \l {Introduction to QDoc}
+ \li \l {Getting Started with QDoc}
+ \li \l {Command Index}
+ \li \l {Topic Commands}
+ \li \l {Context Commands}
+ \list
+ \li \l {Document Navigation}
+ \li \l {Status}
+ \li \l {Thread Support}
+ \li \l {Relating Things}
+ \li \l {Grouping Things}
+ \li \l {Naming Things}
+ \endlist
+ \li \l{Markup Commands}
+ \list
+ \li \l {Text Markup}
+ \li \l {Document Structure}
+ \li \l {Including Code Inline}
+ \li \l {Including External Code}
+ \li \l {Creating Links}
+ \li \l {Including Images}
+ \li \l {Tables and Lists}
+ \li \l {Special Content}
+ \li \l {Miscellaneous}
+ \endlist
+ \li \l {Macros}
+ \list
+ \li \l {CMake Text Snippets}
+ \li \l {HTML and DocBook Formatting}
+ \li \l {Product Names}
+ \li \l {Product Versions}
+ \li \l {Tabbed Content}
+ \li \l {youtube-macro}{Linking to YouTube content}
+ \li \l {Miscellaneous Macros}
+ \endlist
+ \li \l {The QDoc Configuration File}
+ \list
+ \li \l {Generic Configuration Variables}
+ \li \l {Creating Help Project Files}
+ \li \l {Format-specific Configuration Variables}
+ \li \l {Supporting Derived Projects}
+ \li \l {Example Manifest Files}
+ \li \l {qtgui.qdocconf}
+ \li \l {minimum.qdocconf}
+ \endlist
+ \li \l {How to Resolve QDoc Warnings}
+ \endlist
+*/
+
diff --git a/src/qdoc/qdoc/doc/qdoc-warnings.qdoc b/src/qdoc/qdoc/doc/qdoc-warnings.qdoc
new file mode 100644
index 000000000..d5d399a05
--- /dev/null
+++ b/src/qdoc/qdoc/doc/qdoc-warnings.qdoc
@@ -0,0 +1,832 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page qdoc-warnings.html
+ \previouspage Categories of Documentation
+
+ \title How to Resolve QDoc Warnings
+
+ QDoc may issue warnings when generating your documentation set.
+ This section describes what these warnings mean and how to resolve
+ them. This document does not describe warnings generated by Clang.
+
+ \section1 Can't link to <target>
+
+ QDoc issues this warning when one part of the documentation
+ (identified in the warning message) tries to refer to another,
+ but doesn't correctly specify that other, the target of the link.
+ This may arise because the reference to it is mistyped or because
+ the target has changed name (for a function or type) or title
+ (for another section).
+
+ Search the source code for that particular link target. If you get no
+ results, gradually make the search less specific until you find a match.
+
+ If the link target looks like the name of a type or function,
+ this could also be due to:
+
+ \list
+ \li The name (or, for functions, where specified, signature) used
+ where it is documented not matching the name used in its
+ declaration.
+ \li The link target being marked as \l {internal-command}{\\internal} when the linking
+ text was not.
+ \endlist
+
+ \section1 Found a \\target command outside table item in a table
+
+ If QDoc encounters a \l {target-command}{\\target command} that is not
+ preceded by an \\li command within a \l {table-command}
+ {\\table ... \\endtable block}, it issues this warning. The warning is
+ followed by the text
+ \e {Move the \\target inside the \\li to resolve this warning}.
+
+ \section1 Cannot find snippets file to quote from
+
+ QDoc issues this warning if it is unable to find the file named
+ after a \l {snippet-command}{\\snippet} or \l {quotefromfile-command}{\\quotefromfile} command.
+
+ Some useful steps for correcting this:
+
+ \list
+ \li Check if the snippet file name is correct. QDoc appends the
+ snippet file name to each of the directories given in the
+ search path, to get a path-name for a candidate file to look
+ for. It produces this error when none of these candidates exist.
+ \li Check the search path for snippets, given by the \c exampledirs
+ configuration variable in the \c{*.qdocconf} file. You may need
+ to add an entry to this path, or correct an existing entry.
+ \li Check if the snippet file exists, or if it has been moved, renamed
+ or removed, which may happen when there are changes to the source
+ code that QDoc tries to quote from.
+ \endlist
+
+ \section1 Unexpected \\snippet
+
+ QDoc issues this warning if it is unable to locate the snippet file
+ quoted in a \l {snippet-command}{\\snippet} command.
+
+ \section1 Undocumented QML <module> referred by <type> or its members
+
+ QDoc issues this warning if it is unable to locate a QML module
+ based on the identifier passed to the
+ \l {inqmlmodule-command}{\\inqmlmodule} or \l {qmlproperty-command}
+ {\\qmlproperty} commands associated with a QML \e type.
+
+ This means that either documentation for the \l {qmlmodule-command}
+ {\\qmlmodule} is missing, or an incorrect module identifier was
+ used in \\qmlproperty, \\qmlmethod, or \\qmlsignal command(s).
+
+ \section1 No such <type> in QML <module>
+
+ QDoc issues this warning if a \l {qmlproperty-command} {\\qmlproperty},
+ \l {qmlmethod-command} {\\qmlmethod}, or \l {qmlsignal-command}
+ {\\qmlsignal} command argument uses a QML module identifier, but
+ the associated \l {qmltype-command}{\\qmltype} does not belong to
+ that module.
+
+ The QML module identifier, if defined, \b must match with the
+ \l {inqmlmodule-command} {\\inqmlmodule} argument in the QML type
+ documentation. In most cases, QDoc is able to locate the QML type
+ without a module identifier.
+
+ \section1 Undocumented return value
+
+ For functions whose return-type is not void, QDoc checks if the return
+ value is documented. This warning is issued if the documentation for a
+ function or method doesn't contain a word starting with "return".
+
+ \section1 Undocumented parameter
+
+ QDoc requires the documentation of a function or method to describe
+ every parameter. It recognizes this by each parameter name (as
+ specified where the function or method is declared in a header file)
+ appearing after a \l {a-command}{\\a} command.
+
+ This requirement is not imposed for function overload documentation,
+ provided that the overload is marked with the \qdoccmd overload command
+ and a fully-documented function with the same name exists.
+
+ \section1 No such parameter
+
+ QDoc issues this warning when the parameter name given after an
+ \l{a-command}{\\a} command does not match any of the parameters named
+ in the header-file's declaration of the function or method being
+ documented.
+
+ \section1 Unknown macro
+
+ QDoc issues this warning when it sees a backslash, \c{\}, followed
+ by a token it doesn't recognize as the name of either a built-in command
+ or a \l {macro-variable}{user-defined macro}. When quoting code
+ that contains character escape sequences, you should enclose the
+ code in \\c{...} to prevent this warning against the escape sequences.
+
+ \section1 Failed to find function when parsing \\fn <signature>
+
+ When Clang parses a function signature following an \qdoccmd fn command,
+ it checks this against the declaration in the header file. If Clang
+ discovers discrepancies, it issues this warning message.
+
+ The signature must be fully qualified. Typical issues include missing or
+ incorrect template arguments, return type, or qualifiers such as
+ \e const.
+
+ \note The \c{\fn} signature for a \e {hidden friend} should be
+ fully qualified with the scope of the class that declares
+ the function.
+
+ \section1 QML type <TypeName> documented with <ClassName> as its native type. Replacing <ClassName> with <OtherClass>
+ If the \qdoccmd nativetype command is used with the same argument in
+ multiple QML type documentation comments that belong to the same
+ documentation project, QDoc issues this warning. To resolve this, ensure
+ that you use the \c {\nativetype} command only once for each C++ class.
+
+
+ \section1 Cannot tie this documentation to anything
+
+ QDoc found a \beginqdoc ... \endqdoc comment, with no \l{Topic Commands}{topic command}, that was
+ not immediately followed by a class, function or property definition.
+ It thus does not know what the comment documents.
+
+ \section1 This qdoc comment contains no topic command (e.g., \\module, \\page)
+
+ If a QDoc comment doesn't contain a \l {Topic Commands}{topic command}, QDoc does
+ not know what the comment documents, and issues this warning.
+ Very similar to \l {Cannot tie this documentation to anything}, but
+ specific to comments that are not in C++ or QML files.
+
+ \section1 <name> documented more than once
+
+ QDoc issues this warning when it finds two comments that
+ document the same item. The location of the previously seen
+ comment is provided in warning details.
+
+ For example, you see this warning when a function has a documentation
+ comment preceding its definition, and a separate \\fn comment
+ elsewhere.
+
+ \section1 Namespace <name> documented more than once
+
+ This warning means that a documentation set contains two comments
+ containing \l {namespace-command}{\\namespace} commands with the same
+ argument, <name>.
+
+ \section1 <name> is documented, but namespace <namespace> is not documented in any module
+
+ The documentation for \e {<name>} was found, but \e{<name>} is declared
+ under a namespace that is either undocumented or QDoc was unable to
+ locate the documentation for it.
+
+ This can be resolved by either documenting \e{<namespace>}, or if it is
+ already documented in another module, ensure that this module has
+ a dependency to it.
+
+ See also \l {depends-variable}{depends} and {indexes-variable}{indexes}.
+
+ \section1 Has no \\inmodule command
+
+ QDoc issues this warning if the QDoc comments do not relate
+ a class, namespace, or headerfile to a module with the
+ \l{inmodule-command}{\\inmodule} command.
+
+ If the QDoc comment describes an entity that's not a member of
+ some other entity (typically a namespace or class), it should use
+ either \l {relates-command}{\\relates} or \l {inmodule-command}{\\inmodule}
+ to associate it with its broader context. This warning is raised
+ if it does not.
+
+ \section1 Cannot find <name> specified with \\<command> in any header file
+
+ This means that QDoc cannot find a declaration of <name> in any
+ header file, but has found a comment that claims to document it.
+
+ An example:
+ \badcode
+ Cannot find 'Color::Red' specified with '\enum' in any header file.
+ \endcode
+
+ A documentation comment claims to describe an enum, but QDoc didn't
+ find a definition of that enum in a header file.
+
+ This may also be due to:
+
+ \list
+ \li a typo in <name> or <command>
+ \li a missing namespace-or-class prefix
+ \li <name> having moved to another namespace or class
+ \endlist
+
+ \section1 Unrecognizable QML module/component qualifier for <identifier>
+
+ A parameter passed to \l {qmlproperty-command}{\\qmlproperty} or
+ \l {qmlmethod-command}{\\qmlmethod} contains a combination of
+ qmlModule::qmlType::identifier that is not defined anywhere.
+
+ Example:
+ \badcode
+ Unrecognizable QML module/component qualifier for real QtQuick::DragHandler::DragAxis::minimum
+ \endcode
+
+ DragHandler doesn't have a property called DragAxis.
+
+ \section1 Missing property type for <name>
+
+ A declaration of a \l {qmlproperty-command}{\\qmlproperty} is missing its property type.
+
+ The \\qmlproperty command expects to be followed by the property type, then the
+ fully-qualified name of the property (i.e. the name ::-joined after the name of the
+ class it belongs to).
+
+ Incorrect:
+ \badcode
+ \qmlproperty MyWidget::count
+ \endcode
+
+ Correct:
+ \badcode
+ \qmlproperty int MyWidget::count
+ \endcode
+
+ \section1 QML property documented multiple times: <identifier>
+
+ QDoc uses this warning when it finds two QDoc comments that document
+ the same QML property, either by appearing just before its definition,
+ or using the \l {qmlproperty-command}{\\qmlproperty} command.
+
+ \section1 Command <command> not allowed with QML property commands
+
+ Example:
+
+ \badcode
+ \qmlproperty real QtQuick.Controls::RangeSlider::first.value
+ \qmlproperty real QtQuick.Controls::RangeSlider::first.position
+ \qmlproperty real QtQuick.Controls::RangeSlider::first.visualPosition
+ \qmlsignal void QtQuick.Controls::RangeSlider::first.moved()
+ \qmlsignal void QtQuick.Controls::RangeSlider::second.moved()
+ \endcode
+
+ Error message:
+
+ \badcode
+ Command '\\qmlsignal' not allowed with QML property commands
+ \endcode
+
+ This warning is specific to property group documentation. QDoc
+ allows multiple qmlproperty or qmlattachedproperty topic commands
+ in a single documentation comment to document a property group
+ where the last element in the path is <group>.<property>. Any other topic
+ commands triggers this warning.
+
+ \section1 Cannot find base function for <method> in <class>
+
+ QDoc produces this warning if \\reimp is used to document a method,
+ as an override of a virtual method, when no base class has a virtual
+ method with the given name and signature. This may happen because
+ the method it was written to override has changed its signature, or is
+ no longer virtual.
+
+ \section1 Illegal \\reimp; no documented virtual function for <command>
+
+ Qdoc tries to create a link to the function that this one reimplements,
+ but it could not find the link target, likely because that
+ function is not documented. This can also arise if no base class
+ has a virtual method with this name and signature; which might
+ arise due to a renaming, a change in signature or the base no
+ longer declaring it virtual.
+
+ \section1 <class> tries to inherit itself
+
+ The \l {inherits-command}{\\inherits} command is used to document that a QMl type
+ inherits some other QML type. This warning is issued if that other
+ QML type is the same as the QML type documented.
+
+ Example:
+
+ \badcode
+ \qmltype Foo
+ \inherits Foo
+ \endcode
+
+ \section1 \\nativetype is only allowed in \\qmltype
+
+ The \l{nativetype-command}{\\nativetype} command can only be used in a QDoc
+ comment that documents a QML type.
+
+ \section1 All properties in a group must belong to the same type: <name>
+
+ When documenting QML property groups, all properties listed in
+ the comment block must belong to the same QML type.
+
+ \section1 Cannot find project file for example <name>
+
+ In the example's source directory, QDoc expects to find a project
+ file named \c{CMakeLists.txt}, or a file with a \c{.pro}, \c{.qmlproject}, or
+ \c{.pyproject} extension where the base name matches that of the example
+ directory. For example, \c {examples/mymodule/helloworld/helloworld.pro}.
+
+ \section1 Cannot open file to quote from: <filename>
+
+ The search path for <filename> is defined by the following
+ variables in the \c{.qdocconf} file: \c{sources}, \c{sourcedirs},
+ and \c{exampledirs}.
+
+ QDoc failed to find a file named in a command (such as
+ \l {quotefromfile-command}{\\quotefromfile},
+ \l {snippet-command}{\\snippet}, \l {include-command}{\\include})
+ that tells it to retrieve content from the named file. It searches
+ each directory named in the search path. If there is no file with
+ this name in any of those directories, or the file is found but not
+ readable, QDoc issues this warning. Check that the combination of
+ search path and <filename> is correctly spelled, and that you have
+ read permissions for the file.
+
+ \note <filename> may include a directory name prefix; the whole
+ <filename> is appended to each directory in the search path.
+
+ \sa {Cannot find qdoc include file <filename>}
+
+ \section1 Missing format name after \\raw
+
+ The \l {raw-command}{\\raw} command and the corresponding \l {raw-command}{\\endraw}
+ command delimit a block of raw mark-up language code. The \\raw
+ command must be followed by the format name.
+
+ \section1 Macro cannot have both format-specific and qdoc-syntax definitions
+
+ A \l {macro-variable}{\\macro} that specifies an output format cannot also
+ have a generic definition.
+
+ An example of a configuration that triggers this warning:
+
+ \badcode
+ macro.gui = \b
+ macro.gui.HTML = "<b>\1</b>"
+ \endcode
+
+ \section1 Unknown command <name>
+
+ When a QDoc comment uses a backslash followed by a token that is not a QDoc
+ built-in command and has not been defined as a custom command
+ \l {macro-variable}{macro}, QDoc produces this warning. Check the spelling
+ of the command name, and check whether your QDoc configuration doesn't
+ include whatever would have defined it, if it's a custom command.
+
+ This may also be produced due to code being quoted in the QDoc comment,
+ for example the author may have referred to the C string termination
+ character \c{'\0'} or one of the other C string escape sequences such
+ as \c{'\n'} without escaping the backslash. Escape the backslash
+ as \c{\} to include a literal backslash in the documentation, or
+ enclose the code fragment in \c{\c{...}}, which suppresses
+ interpretation of backslashes as introducing QDoc commands.
+
+ \section1 Duplicate target name <target>
+
+ This warning is issued if you define two targets with the same parameter,
+ using either the \l {target-command}{\\target} or the \l {keyword-command}
+ {\\keyword} command. The target name given as parameter to these commands
+ must be unique. The warning is followed by "The previous occurrence is
+ here: [location]", where location contains a file name and line number.
+
+ \section1 Cannot find qdoc include file <filename>
+
+ QDoc failed to find an include file named in a command.
+ QDoc searches each directory named in the search path.
+ If there is no file with this name in any of those directories,
+ or the file found in that search is not readable, QDoc issues
+ this warning. Check that the combination of search path and
+ <filename> is correctly spelled, and that you have read permissions
+ for the file.
+
+ \note <filename> may include a directory name prefix; the whole
+ <filename> is appended to each directory in the search path.
+
+ \sa {Cannot open file to quote from: <filename>}
+
+ \section1 Cannot find <tag> in <file>
+
+ This means QDoc cannot find the identifier <id> in the
+ \l{include-command}{\\include} <file> or {snippet-command}{\\snippet} <file>.
+
+ \section1 Empty qdoc snippet <tag> in <file>
+
+ The snippet <tag> was found in the \l {snippet-command}{\\snippet} <file>, but it is empty.
+
+ \section1 Cannot nest <command> commands
+
+ This warning concerns formatting commands: bold, italic,
+ index, link, span, subscript, superscript, teletype, uicontrol,
+ underline. A formatting command cannot be used within the text
+ it applies to. An example of this:
+
+ \badcode
+ There is \b{no \b{super-}bold}.
+ \encode
+
+ \section1 Can't use <inner> in <outer>
+
+ This warning is issued for commands that cannot be nested.
+
+ Example:
+ \badcode
+ \list
+ \li \table
+ \row \li Hello \li Hi
+ \endtable
+ \endlist
+ \endcode
+
+ Results in the QDoc warning "Can't use '\\table' in '\\list'".
+
+ \section1 Missing <outer> before <inner>
+
+ Some examples:
+
+ \list
+ \li The \l {li-command}{\\li} command can only be used inside a \l {list-command}{\\list}
+ or a \l {row-command}{\\row} of a \l {table-command}{\\table}.
+ \li The \l {row-command}{\\row} and \l {header-command}{\\header} commands can only be
+ used within a \l {table-command}{\\table}.
+ \endlist
+
+ \section1 Unexpected <end_command>
+
+ This warning is issued if, for example, you have an \l {list-command}{\\endlist} without
+ a preceding \l {list-command}{\\list}. It applies to all commands that come in pairs (e.g.
+ startFoo/endFoo).
+
+ \section1 Missing comma in \\sa
+
+ The titles listed for a \l {sa-command}{\\sa} command should be separated
+ from one another with commas.
+
+ \section1 Macro <command> does not have a default definition
+
+ QDoc is attempting to expand a macro, and expects that macro to
+ have a default definition. Some macros may only have format-specific
+ definitions.
+
+ Example:
+ \badcode
+ macro.pi.HTML = "&pi;" # encodes the pi symbol for HTML output format
+ \endcode
+
+ There are however instances where macro expansion requires a
+ format-independent macro. For example, you can have macros in
+ section titles, but they must have default definitions.
+
+ \section1 Macro <macro> invoked with too few arguments (expected <many>, got <few>)
+
+ The given macro needs more parameters than it was given. See
+ the definition of the macro in the configuration for further
+ details.
+
+ \section1 Unbalanced parentheses in <text>
+
+ Points to a '(' without a corresponding ')', or vice versa.
+
+ \section1 No documentation for <name>
+
+ Example:
+
+ \badcode
+ Warning "No documentation for QNativeInterface."
+ \endcode
+
+ QDoc detects the declaration of namespace QNativeInterface in
+ a header file, but does not find a QDoc comment where that
+ namespace has been documented.
+
+ \section1 No such enum item <name> in <class>
+
+ Example:
+
+ \badcode
+ Cannot find 'QSGMaterialRhiShader::RenderState::DirtyState' specified
+ with \enum in any header file.
+ \endcode
+
+ QDoc issues this warning when it finds a \l{value-command}{\\value} directive in an
+ \l{enum-command}{\\enum} comment that names a value not found in the header file that declared
+ the enumerated type documented.
+
+ \section1 Undocumented enum item <enum> in <enum list>
+
+ <enum list>'s \l {value-command}{\\value} or \l {omitvalue-command}{\\omitvalue} entries
+ did not include one for \l {enum-command}{<enum>}, which is named in the
+ declaration of <enum list> in the header file.
+
+ \section1 Failed to find index: <filename>
+
+ Example:
+
+ \badcode
+ Failed to find index: path/to/QtCrator/appmanplugin/manual.index
+ \endcode
+
+ In this case, it clearly means the indexes variable contains a
+ typo in the path of the index file.
+
+ Incorrect:
+ \badcode
+ indexes += path/to/QtCrator/appmanplugin/manual.index
+ \endcode
+
+ Correct:
+ \badcode
+ indexes += path/to/QtCreator/appmanplugin/manual.index
+ \endcode
+
+ \section1 \\generatelist <group> is empty
+
+ Below a short overview of all possible arguments for \l {generatelist-command}{\\generatelist}:
+ \list
+ \li \\generatelist annotatedexamples
+ \li \\generatelist annotatedattributions
+ \li \\generatelist classes <prefix>
+ \li \\generatelist classesbymodule <module name>
+ \li \\generatelist qmltypesbymodule <module name>
+ \li \\generatelist functionindex
+ \li \\generatelist legalese
+ \li \\generatelist overviews
+ \li \\generatelist attributions
+ \li \\generatelist related
+ \endlist
+
+ QDoc issues this warning if you specify \c{\generatelist <group>}
+ and the group does not contain any items, or if you specify
+ \c{\generatelist <group> <pattern>} and no item in the group
+ matches the pattern.
+
+ \section1 \\generatelist <group> no such group
+
+ This warning issues if the argument to \l {generatelist-command}{\\generatelist}
+ is a non-existing group.
+
+ Example:
+
+ \code
+ \generatelist draganddrop
+ \endcode
+
+ This statement generates a list of classes or QML types in the
+ draganddrop group. Classes or QML types are added to the
+ draganddrop group by the \c{\l {ingroup-command}{\ingroup} draganddrop} command in their
+ \l {class-command}{\\class} or \l {qmltype-command}{\\qmltype} comment.
+
+ QDoc issues this warning message if no entity has
+ this \c{\ingroup draganddrop} statement.
+
+ \section1 Missing image: <imagefile>
+
+ The search path to the image is wrong, or the image file does not exist.
+
+ \section1 Can't link to <target>
+
+ This can have a variety of causes:
+
+ \list
+ \li The link target has not been defined with a QDoc topic command,
+ e.g. {title-command}{\\title} <target>.
+ \li The <target> contains a typo.
+ \li The document that contains that link target did not get compiled.
+ \li The document that contains that link target is in a module that is
+ not in the compilation path.
+ \li The link target is in another module, and a dependency to that
+ module was not set in the configuration or QDoc failed to locate
+ the index file for the dependency.
+ \endlist
+
+ \section1 Could not resolve QML import statement for type <name>
+
+ QDoc issues this warning if you document a QML type, but omit
+ the \l{inqmlmodule-command}{\\inqmlmodule} command.
+ Example:
+
+ \badcode
+ Could not resolve QML import statement for type 'ItemSelectionModel'
+ \encode
+
+ Incorrect:
+ \badcode
+ \qmltype ItemSelectionModel
+ \nativetype QItemSelectionModel
+ \since 5.5
+ \ingroup qtquick-models
+ \endcode
+ Correct:
+ \badcode
+ \qmltype ItemSelectionModel
+ \nativetype QItemSelectionModel
+ \inqmlmodule QtQml.Models
+ \since 5.5
+ \ingroup qtquick-models
+ \endcode
+
+ \section1 \\brief statement does not end with a full stop
+
+ The argument to the \\brief command is a sentence, summarizing
+ the topic documented, so should end in a full stop. It should
+ also be brief.
+
+ \section1 QtDeclarative not installed; cannot parse QML
+
+ QDoc issues this warning if it has been compiled without support
+ for QML parsing. This should not happen unless you have a
+ custom build of QDoc.
+
+ \section1 Invalid regular expression <regex>
+
+ Some QDoc commands take regular expressions as parameters. QDoc
+ gives this warning when the text given as such a parameter is not
+ a valid regular expression, usually due to it containing characters
+ with special meanings in regular expressions, that should have been
+ escaped.
+
+ Example:
+
+ \badcode
+ notifications.qdoc:56: (qdoc) warning: Invalid regular expression '^})$'
+ \endcode
+
+ \badcode
+ \quotefromfile webenginewidgets/notifications/data/index.html
+ \skipuntil resetPermission
+ \endcode
+
+ Invalid regular expression:
+ \badcode
+ \printuntil /^})$/
+ \endcode
+
+ Valid regular expression:
+ \badcode
+ \printuntil /^\}\)$/
+ \endcode
+
+
+ The \l {printuntil-command}{\\printuntil} command prints until it
+ meets a line consisting of only a right curly brace followed by a
+ right parenthesis. In this case, the curly brace and the parenthesis
+ need to be escaped because they have special meanings in regular
+ expressions.
+
+ \section1 Multiple index files found for dependency <indexfile>:<depend>
+ Using <indexfile> as index file for dependency <depend>
+
+ Multiple \c{-indexdir} paths were passed to QDoc as command line options,
+ and more than one contained an \c{.index} file that matches a dependency.
+ QDoc picks the one with the latest timestamp automatically.
+
+ Typically, this warning indicates that there are build artifacts left
+ from a previous documentation build.
+
+ \section1 Cannot locate index file for dependency <depend>
+
+ Example:
+
+ \badcode
+ "QMake" Cannot locate index file for dependency "activeqt"
+ \endcode
+
+ The documentation project QMake could not locate activeqt.index
+ in any of the specified index directories. In this case, the
+ specified index directories are specified in qmake.qdocconf.
+
+ \section1 Dependent modules specified, but no index directories were set.
+
+ QDoc expected to see one or more -indexdir arguments on the command line.
+ Without them, QDoc cannot locate the index files of any dependencies
+ defined with the 'depends' configuration variable.
+
+ \section1 Overrides a previous doc
+
+ QDoc issues this warning when it finds two comments that appear to
+ describe the same entity. The location of the previously seen
+ comment is provided in warning details.
+
+ \section1 Unrecognized list style <name>
+
+ \\list can take an optional argument: a single number or character
+ that modifies the list style. Refer to the {list-command}{\\list}
+ documentation for more details. If you use an argument that is
+ not recognized, QDoc issues this warning.
+
+ \section1 Unable to parse QML snippet: <code> at line <y>, column <x>
+
+ QDoc comments can contain QML code. This code can be found in a snippet,
+ or in the QDoc comments delimited by \l {qml-command}{\\qml} and {endqml-command}{\\endqml}.
+
+ Example:
+
+ If there is a syntax error in the QML code, QDoc issues the warning
+ \badcode
+ Unable to parse QML snippet: Syntax error at line 97, column 42
+ \endcode
+
+ Snippets can also contain QML and also there the code is checked. If there is
+ for example a missing curly brace in the code, QDoc issues the warning
+ \badcode
+ Unable to parse QML snippet: Expected token '{' at line 63, column 52
+ \endcode
+
+ QDoc often fails to parse incomplete QML snippets; in these cases,
+ it's often OK to replace the \\qml ... \\endqml commands with
+ \\code ... \\endcode to suppress this warning.
+
+ \section1 Command <command> failed at end of file <filename>
+
+ Example:
+
+ \badcode
+ Command "\snippet (//! [2]) failed at end of file qmlbars/qml/qmlbars/main.qml".
+ \endcode
+
+ In this case the warning means that the \l {snippet-command}{\\snippet} command did not find a second
+ label "\/\/! [2]" to mark the end of the snippet. It could also mean that it didn't
+ find any occurrence of that snippet tag in this snippet file.
+
+ Another example:
+
+ \badcode
+ Command '\skipto' failed at end of file 'styling/CMakeLists.txt".
+ \endcode
+
+ The \l {skipto-command}{\\skipto} + <pattern> moves the cursor to the next line containing
+ that pattern. If \\skipto doesn't find it, QDoc issues this warning.
+
+ \section1 Failed to open <file> for writing
+
+ This warning clearly means it cannot open a file for writing, probably because
+ of a wrong path, or permission to write in a certain directory.
+
+ \section1 This page title exists in more than one file
+
+ The \l {title-command}{\\title} command sets the title for a page.
+
+ \code
+ \page activeqt-server.html
+ \title Building ActiveX servers in Qt
+ \endcode
+
+ QDoc issues this warning if a certain title is used in more than one page.
+
+
+ \section1 The content is too long
+
+ QDoc uses a fixed-size buffer when tokenizing source files. If any single
+ token in the file has more characters than the maximum limit, QDoc issues
+ this warning.
+
+ While QDoc continues parsing the file, only the part of the token that
+ fits into the buffer is considered, meaning that the output might be
+ mangled.
+
+ To resolve this warning, the relevant content must be reduced in size,
+ either by splitting it, if possible, or by removing some of its parts.
+
+ The maximum amount of characters for a single token is shown alongside
+ the warning, for example:
+
+ \badcode
+ file.qdoc:71154: (qdoc) warning: The content is too long.
+
+ [The maximum amount of characters for this content is 524288.
+ Consider splitting it or reducing its size.]
+ \endcode
+
+ \note Since content that is too long is not parsed in full, QDoc may
+ issue warnings that are false positives. Resolve all warnings of this type
+ before fixing other warnings.
+
+ \section1 No documentation generated for function <name> in global scope
+
+ QDoc was able to match the documentation for a function \e {<name>} to its
+ declaration, but no output was generated because the function is declared
+ in the global namespace.
+
+ Use the \l {relates-command}{\\relates} command to associate the function
+ with a documented type, namespace, or a header file. The function is then
+ listed as a \e {related non-member} on the associated reference page.
+
+ \section1 Documentation configuration for <project> doesn't define a help project (qhp)
+
+ A valid Qt help configuration was expected but not provided in the project's
+ .qdocconf file.
+
+ See also \l{Creating Help Project Files} and \l {qhp-variable}{qhp}.
+
+ \section1 Already generated FILE for this project
+
+ While generating the documentation for a project, QDoc keeps track of the
+ file names of the files it has generated. QDoc will issue a warning when it
+ opens a file for writing if that file is known to have been generated
+ previously, in the current execution. This can happen if a \qdoccmd page
+ command uses the same name as \qdoccmd group, for example.
+
+ You can set the environment variable \c QDOC_ALL_OVERWRITES_ARE_WARNINGS to
+ unconditionally warn about all such events. This may be useful when tracking
+ down the offending definitions.
+
+*/
diff --git a/src/qdoc/qdoc/doc/qtgui-qdocconf.qdoc b/src/qdoc/qdoc/doc/qtgui-qdocconf.qdoc
new file mode 100644
index 000000000..b72f97271
--- /dev/null
+++ b/src/qdoc/qdoc/doc/qtgui-qdocconf.qdoc
@@ -0,0 +1,260 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+
+\page qtgui-qdocconf.html
+\title qtgui.qdocconf with Comments
+
+\brief A walkthrough of a typical qdocconf file.
+
+This document goes through a typical Qt 5 qdocconf file. The contents is taken from
+Qt GUI's \e qtgui.qdocconf file.
+
+Below you will find the full contents of \c qtgui.qdocconf. The subsequent section will discuss
+every statement in the qdocconf file.
+
+\badcode
+ include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf)
+
+ project = QtGui
+ description = Qt GUI Reference Documentation
+ url = http://doc.qt.io/qt
+ version = $QT_VERSION
+
+ examplesinstallpath = gui
+
+ qhp.projects = QtGui
+
+ qhp.QtGui.file = qtgui.qhp
+ qhp.QtGui.namespace = org.qt-project.qtgui.$QT_VERSION_TAG
+ qhp.QtGui.virtualFolder = qtgui
+ qhp.QtGui.indexTitle = Qt GUI
+ qhp.QtGui.indexRoot =
+
+ qhp.QtGui.subprojects = classes
+ qhp.QtGui.subprojects.classes.title = C++ Classes
+ qhp.QtGui.subprojects.classes.indexTitle = Qt GUI C++ Classes
+ qhp.QtGui.subprojects.classes.selectors = class headerfile
+ qhp.QtGui.subprojects.classes.sortPages = true
+
+ tagfile = qtgui.tags
+
+ depends += \
+ qtcore \
+ qtnetwork \
+ qtopengl \
+ qtsvg \
+ qtqml \
+ qtquick \
+ qtwidgets \
+ qtdoc
+
+ headerdirs += ..
+
+ sourcedirs += .. \
+ ../../../examples/gui/doc/src
+
+ excludedirs = ../../../examples/gui/doc/src/tmp
+
+ exampledirs += ../../../examples/gui \
+ snippets
+
+ imagedirs += images \
+ ../../../examples/gui/doc/images \
+ ../../../doc/src/images
+\endcode
+
+\title Qtgui.qdocconf with notes
+
+\badcode
+ include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf)
+\endcode
+
+QDoc inherits the default templates, macros, and settings from the directory
+specified from the \c $QT_INSTALL_DOCS variable. \c qmake prints the value of
+the variable.
+\badcode
+ qmake -query
+\endcode
+
+\b {See also}: \l {include}.
+
+\badcode
+ project = QtGui
+\endcode
+
+The \c project variable sets the name of the QDoc build. This name is also
+used to form the index file, which, in this case, will be \e qtgui.index. The
+name of the index file doesn't adopt the uppercase letters of the project name.
+
+\b {See also}: \l {project}.
+
+\badcode
+ description = Qt GUI Reference Documentation
+\endcode
+
+A short description of the project concerned.
+
+\badcode
+ url = http://doc.qt.io/qt-5
+\endcode
+
+The \c url variable holds the base url of the project.
+
+The URL is stored in the generated index file for the project.
+QDoc will use this as the base URL when constructing external links
+to content listed in the index.
+
+\note QDoc omits this value when the -installdir argument
+is specified when running QDoc.
+
+\badcode
+ examplesinstallpath = gui
+\endcode
+
+The \c examplesinstallpath variable indicates that the examples will be
+installed in the \e gui directory under the parent examples directory
+(for Qt, this is $QT_INSTALL_EXAMPLES).
+
+\note The examplepath variable has to match the example directory specified in
+ \c exampledirs.
+
+\note It is possible to override \c examplesinstallpath for a specific
+ \l {example-command}{\\example} using the \l {meta-command}{\\meta}
+ command.
+
+\b {See also}: \l {examplesinstallpath}, \l {exampledirs}, and
+\l {meta-command}{\\meta}.
+
+\badcode
+ qhp.projects = QtGui
+ qhp.QtGui.file = qtgui.qhp
+\endcode
+
+The following parameters are for creating a QHP file (\e .qhp). The
+\e qhelpgenerator program can convert the QHP file into a QCH file (\e .qch),
+which can be opened in Qt Assistant or Qt Creator.
+
+\badcode
+ qhp.QtGui.namespace = org.qt-project.qtgui.$QT_VERSION_TAG
+\endcode
+
+A unique identifier which enables QHelpEngine to retrieve the helpfile
+from a given link. This namespace is also used as a base url for links
+to the helpfile.
+
+\badcode
+ qhp.QtGui.virtualFolder = qtgui
+\endcode
+
+Virtual folders group documentation together into a single location. A
+virtual folder will become the root directory of all files referenced in
+a compressed help file.
+
+When two manuals are located in the same virtual folder, it is possible to
+refer to sections of the other manual using relative paths. The virtual
+folder tag is mandatory and the folder must not contain any '/'.
+
+\badcode
+ qhp.QtGui.indexTitle = Qt GUI
+\endcode
+
+This is the title of the page that has the contents.
+
+\badcode
+ qhp.QtGui.indexRoot =
+\endcode
+
+Specifies the title of the root (namespace) page to generate the documentation for.
+Typically defined as an empty string.
+
+\badcode
+ qhp.QtGui.subprojects = classes
+ qhp.QtGui.subprojects.classes.title = C++ Classes
+ qhp.QtGui.subprojects.classes.indexTitle = Qt GUI C++ Classes
+\endcode
+The subprojects specify the sections that are displayed in the table of contents
+for this project. In this example, the subproject, which is displayed in
+the Assistant's sidebar, is named "C++ Classes" and its index is the page
+titled "QT GUI C++ Classes".
+
+\badcode
+ qhp.QtGui.subprojects.classes.selectors = class fake:headerfile
+\endcode
+
+Lists all C++ classes and header files.
+
+See \l {Creating Help Project Files} for more information.
+
+\badcode
+ tagfile = ../../../doc/qtgui/qtgui.tags
+\endcode
+
+This specifies the Doxygen tag file that needs to be written when the html is generated
+by QDoc.
+
+\badcode
+depends += \
+ qtcore \
+ qtnetwork \
+ qtopengl \
+ qtsvg \
+ qtqml \
+ qtquick \
+ qtwidgets \
+ qtdoc
+\endcode
+
+Specifies the modules QDoc needs to load for generating output for Qt GUI.
+QDoc loads the index files for all modules listed in the depends statement in
+order to enable linking to pages in these modules.
+
+\badcode
+ headerdirs += ..
+\endcode
+
+Add the parent directory to the list of directories containing the header files
+associated with the \e .cpp source files.
+
+\badcode
+ sourcedirs += .. \
+ ../../../examples/gui/doc/src
+\endcode
+
+Add the specified directories to the list of directories containing the \e .cpp and
+\e .qdoc files used in the documentation.
+
+\badcode
+ excludedirs = ../../../examples/gui/doc/src/tmp
+\endcode
+
+The \c excludedirs variable is for listing directories that should not be processed
+by QDoc, even if the same directories are included by the \c sourcedirs or \c headerdirs
+variables.
+
+When executed, QDoc will ignore the directories listed.
+\b {See also}: \l {excludefiles}.
+
+\badcode
+ exampledirs += ../../../examples/gui \
+ snippets
+\endcode
+\b {See also}: \l {examples-variable}{examples}, \l {examplesinstallpath}.
+
+Add the two directories specified to the list of directories containing the source
+code of the example files.
+
+If QDoc encounters both \c exampledirs and \c examples, it will look first in the
+\c examples directory. QDoc will accept the first matching file it finds. QDoc will
+search in the directories specified, not in their subdirectories.
+
+\badcode
+ imagedirs += images \
+ ../../../examples/gui/doc/images \
+ ../../../doc/src/images \
+\endcode
+
+Add the directories specified above to the list of directories where the images
+can be found.
+*/
diff --git a/src/qdoc/qdoc/src/qdoc/access.h b/src/qdoc/qdoc/src/qdoc/access.h
new file mode 100644
index 000000000..96cad686b
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/access.h
@@ -0,0 +1,15 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QtCore/qglobal.h>
+
+#ifndef ACCESS_H
+#define ACCESS_H
+
+QT_BEGIN_NAMESPACE
+
+enum class Access : unsigned char { Public, Protected, Private };
+
+QT_END_NAMESPACE
+
+#endif // ACCESS_H
diff --git a/src/qdoc/qdoc/src/qdoc/aggregate.cpp b/src/qdoc/qdoc/src/qdoc/aggregate.cpp
new file mode 100644
index 000000000..bf210ba17
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/aggregate.cpp
@@ -0,0 +1,762 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "aggregate.h"
+
+#include "functionnode.h"
+#include "parameters.h"
+#include "typedefnode.h"
+#include "qdocdatabase.h"
+#include "qmlpropertynode.h"
+#include "qmltypenode.h"
+#include "sharedcommentnode.h"
+#include <vector>
+
+using namespace Qt::Literals::StringLiterals;
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class Aggregate
+ */
+
+/*! \fn Aggregate::Aggregate(NodeType type, Aggregate *parent, const QString &name)
+ The constructor should never be called directly. It is only called
+ by the constructors of subclasses of Aggregate. Those constructors
+ pass the node \a type they want to create, the \a parent of the new
+ node, and its \a name.
+ */
+
+/*!
+ Recursively set all non-related members in the list of children to
+ \nullptr, after which each aggregate can safely delete all children
+ in their list. Aggregate's destructor calls this only on the root
+ namespace node.
+ */
+void Aggregate::dropNonRelatedMembers()
+{
+ for (auto &child : m_children) {
+ if (!child)
+ continue;
+ if (child->parent() != this)
+ child = nullptr;
+ else if (child->isAggregate())
+ static_cast<Aggregate*>(child)->dropNonRelatedMembers();
+ }
+}
+
+/*!
+ Destroys this Aggregate; deletes each child.
+ */
+Aggregate::~Aggregate()
+{
+ // If this is the root, clear non-related children first
+ if (isNamespace() && name().isEmpty())
+ dropNonRelatedMembers();
+
+ m_enumChildren.clear();
+ m_nonfunctionMap.clear();
+ m_functionMap.clear();
+ qDeleteAll(m_children.begin(), m_children.end());
+ m_children.clear();
+}
+
+/*!
+ If \a genus is \c{Node::DontCare}, find the first node in
+ this node's child list that has the given \a name. If this
+ node is a QML type, be sure to also look in the children
+ of its property group nodes. Return the matching node or \c nullptr.
+
+ If \a genus is either \c{Node::CPP} or \c {Node::QML}, then
+ find all this node's children that have the given \a name,
+ and return the one that satisfies the \a genus requirement.
+ */
+Node *Aggregate::findChildNode(const QString &name, Node::Genus genus, int findFlags) const
+{
+ if (genus == Node::DontCare) {
+ Node *node = m_nonfunctionMap.value(name);
+ if (node)
+ return node;
+ } else {
+ const NodeList &nodes = m_nonfunctionMap.values(name);
+ for (auto *node : nodes) {
+ if (genus & node->genus()) {
+ if (findFlags & TypesOnly) {
+ if (!node->isTypedef() && !node->isClassNode()
+ && !node->isQmlType() && !node->isEnumType())
+ continue;
+ } else if (findFlags & IgnoreModules && node->isModule())
+ continue;
+ return node;
+ }
+ }
+ }
+ if (genus != Node::DontCare && !(genus & this->genus()))
+ return nullptr;
+
+ auto it = m_functionMap.find(name);
+ return it != m_functionMap.end() ? (*(*it).begin()) : nullptr;
+}
+
+/*!
+ Find all the child nodes of this node that are named
+ \a name and return them in \a nodes.
+ */
+void Aggregate::findChildren(const QString &name, NodeVector &nodes) const
+{
+ nodes.clear();
+ const auto &functions = m_functionMap.value(name);
+ nodes.reserve(functions.size() + m_nonfunctionMap.count(name));
+ for (auto f : functions)
+ nodes.emplace_back(f);
+ auto [it, end] = m_nonfunctionMap.equal_range(name);
+ while (it != end) {
+ nodes.emplace_back(*it);
+ ++it;
+ }
+}
+
+/*!
+ This function searches for a child node of this Aggregate,
+ such that the child node has the spacified \a name and the
+ function \a isMatch returns true for the node. The function
+ passed must be one of the isXxx() functions in class Node
+ that tests the node type.
+ */
+Node *Aggregate::findNonfunctionChild(const QString &name, bool (Node::*isMatch)() const)
+{
+ const NodeList &nodes = m_nonfunctionMap.values(name);
+ for (auto *node : nodes) {
+ if ((node->*(isMatch))())
+ return node;
+ }
+ return nullptr;
+}
+
+/*!
+ Find a function node that is a child of this node, such that
+ the function node has the specified \a name and \a parameters.
+ If \a parameters is empty but no matching function is found
+ that has no parameters, return the first non-internal primary
+ function or overload, whether it has parameters or not.
+
+ \sa normalizeOverloads()
+ */
+FunctionNode *Aggregate::findFunctionChild(const QString &name, const Parameters &parameters)
+{
+ auto map_it = m_functionMap.find(name);
+ if (map_it == m_functionMap.end())
+ return nullptr;
+
+ auto match_it = std::find_if((*map_it).begin(), (*map_it).end(),
+ [&parameters](const FunctionNode *fn) {
+ if (fn->isInternal())
+ return false;
+ if (parameters.count() != fn->parameters().count())
+ return false;
+ for (int i = 0; i < parameters.count(); ++i)
+ if (parameters.at(i).type() != fn->parameters().at(i).type())
+ return false;
+ return true;
+ });
+
+ if (match_it != (*map_it).end())
+ return *match_it;
+
+ // Assumes that overloads are already normalized; i.e, if there's
+ // an active function, it'll be found at the start of the list.
+ auto *fn = (*(*map_it).begin());
+ return (parameters.isEmpty() && !fn->isInternal()) ? fn : nullptr;
+}
+
+/*!
+ Returns the function node that is a child of this node, such
+ that the function described has the same name and signature
+ as the function described by the function node \a clone.
+
+ Returns \nullptr if no matching function was found.
+ */
+FunctionNode *Aggregate::findFunctionChild(const FunctionNode *clone)
+{
+ auto funcs_it = m_functionMap.find(clone->name());
+ if (funcs_it == m_functionMap.end())
+ return nullptr;
+
+ auto func_it = std::find_if((*funcs_it).begin(), (*funcs_it).end(),
+ [clone](const FunctionNode *fn) {
+ return compare(clone, fn) == 0;
+ });
+
+ return func_it != (*funcs_it).end() ? *func_it : nullptr;
+}
+
+/*!
+ Mark all child nodes that have no documentation as having
+ private access and internal status. qdoc will then ignore
+ them for documentation purposes.
+ */
+void Aggregate::markUndocumentedChildrenInternal()
+{
+ for (auto *child : std::as_const(m_children)) {
+ if (!child->hasDoc() && !child->isDontDocument()) {
+ if (!child->docMustBeGenerated()) {
+ if (child->isFunction()) {
+ if (static_cast<FunctionNode *>(child)->hasAssociatedProperties())
+ continue;
+ } else if (child->isTypedef()) {
+ if (static_cast<TypedefNode *>(child)->hasAssociatedEnum())
+ continue;
+ }
+ child->setAccess(Access::Private);
+ child->setStatus(Node::Internal);
+ }
+ }
+ if (child->isAggregate()) {
+ static_cast<Aggregate *>(child)->markUndocumentedChildrenInternal();
+ }
+ }
+}
+
+/*!
+ Adopts each non-aggregate C++ node (function/macro, typedef, enum, variable,
+ or a shared comment node with genus Node::CPP) in the global scope to the
+ aggregate specified in the node's documentation using the \\relates command.
+
+ If the target Aggregate is not found in the primary tree, creates a new
+ ProxyNode to use as the parent.
+*/
+void Aggregate::resolveRelates()
+{
+ Q_ASSERT(name().isEmpty()); // Must be called on the root namespace
+ auto *database = QDocDatabase::qdocDB();
+
+ for (auto *node : m_children) {
+ if (node->isRelatedNonmember() || node->isAggregate())
+ continue;
+ if (node->genus() != Node::CPP)
+ continue;
+
+ const auto &relates_args = node->doc().metaCommandArgs("relates"_L1);
+ if (relates_args.isEmpty())
+ continue;
+
+ auto *aggregate = database->findRelatesNode(relates_args[0].first.split("::"_L1));
+ if (!aggregate)
+ aggregate = new ProxyNode(this, relates_args[0].first);
+ else if (node->parent() == aggregate)
+ continue;
+
+ aggregate->adoptChild(node);
+ node->setRelatedNonmember(true);
+ }
+}
+
+/*!
+ Sorts the lists of overloads in the function map and assigns overload
+ numbers.
+
+ For sorting, active functions take precedence over internal ones, as well
+ as ones marked as \\overload - the latter ones typically do not contain
+ full documentation, so selecting them as the \e primary function
+ would cause unnecessary warnings to be generated.
+
+ Otherwise, the order is set as determined by FunctionNode::compare().
+ */
+void Aggregate::normalizeOverloads()
+{
+ for (auto map_it = m_functionMap.begin(); map_it != m_functionMap.end(); ++map_it) {
+ if ((*map_it).size() > 1) {
+ std::sort((*map_it).begin(), (*map_it).end(),
+ [](const FunctionNode *f1, const FunctionNode *f2) -> bool {
+ if (f1->isInternal() != f2->isInternal())
+ return f2->isInternal();
+ if (f1->isOverload() != f2->isOverload())
+ return f2->isOverload();
+ // Prioritize documented over undocumented
+ if (f1->hasDoc() != f2->hasDoc())
+ return f1->hasDoc();
+ return (compare(f1, f2) < 0);
+ });
+ // Set overload numbers
+ signed short n{0};
+ for (auto *fn : (*map_it))
+ fn->setOverloadNumber(n++);
+ }
+ }
+
+ for (auto *node : std::as_const(m_children)) {
+ if (node->isAggregate())
+ static_cast<Aggregate *>(node)->normalizeOverloads();
+ }
+}
+
+/*!
+ Returns a const reference to the list of child nodes of this
+ aggregate that are not function nodes. Duplicate nodes are
+ removed from the list.
+ */
+const NodeList &Aggregate::nonfunctionList()
+{
+ m_nonfunctionList = m_nonfunctionMap.values();
+ std::sort(m_nonfunctionList.begin(), m_nonfunctionList.end(), Node::nodeNameLessThan);
+ m_nonfunctionList.erase(std::unique(m_nonfunctionList.begin(), m_nonfunctionList.end()),
+ m_nonfunctionList.end());
+ return m_nonfunctionList;
+}
+
+/*! \fn bool Aggregate::isAggregate() const
+ Returns \c true because this node is an instance of Aggregate,
+ which means it can have children.
+ */
+
+/*!
+ Finds the enum type node that has \a enumValue as one of
+ its enum values and returns a pointer to it. Returns 0 if
+ no enum type node is found that has \a enumValue as one
+ of its values.
+ */
+const EnumNode *Aggregate::findEnumNodeForValue(const QString &enumValue) const
+{
+ for (const auto *node : m_enumChildren) {
+ const auto *en = static_cast<const EnumNode *>(node);
+ if (en->hasItem(enumValue))
+ return en;
+ }
+ return nullptr;
+}
+
+/*!
+ Adds the \a child to this node's child map using \a title
+ as the key. The \a child is not added to the child list
+ again, because it is presumed to already be there. We just
+ want to be able to find the child by its \a title.
+ */
+void Aggregate::addChildByTitle(Node *child, const QString &title)
+{
+ m_nonfunctionMap.insert(title, child);
+}
+
+/*!
+ Adds the \a child to this node's child list and sets the child's
+ parent pointer to this Aggregate. It then mounts the child with
+ mountChild().
+
+ The \a child is then added to this Aggregate's searchable maps
+ and lists.
+
+ \note This function does not test the child's parent pointer
+ for null before changing it. If the child's parent pointer
+ is not null, then it is being reparented. The child becomes
+ a child of this Aggregate, but it also remains a child of
+ the Aggregate that is it's old parent. But the child will
+ only have one parent, and it will be this Aggregate. The is
+ because of the \c relates command.
+
+ \sa mountChild(), dismountChild()
+ */
+void Aggregate::addChild(Node *child)
+{
+ m_children.append(child);
+ child->setParent(this);
+ child->setUrl(QString());
+ child->setIndexNodeFlag(isIndexNode());
+
+ if (child->isFunction()) {
+ m_functionMap[child->name()].emplace_back(static_cast<FunctionNode *>(child));
+ } else if (!child->name().isEmpty()) {
+ m_nonfunctionMap.insert(child->name(), child);
+ if (child->isEnumType())
+ m_enumChildren.append(child);
+ }
+}
+
+/*!
+ This Aggregate becomes the adoptive parent of \a child. The
+ \a child knows this Aggregate as its parent, but its former
+ parent continues to have pointers to the child in its child
+ list and in its searchable data structures. But the child is
+ also added to the child list and searchable data structures
+ of this Aggregate.
+ */
+void Aggregate::adoptChild(Node *child)
+{
+ if (child->parent() != this) {
+ m_children.append(child);
+ child->setParent(this);
+ if (child->isFunction()) {
+ m_functionMap[child->name()].emplace_back(static_cast<FunctionNode *>(child));
+ } else if (!child->name().isEmpty()) {
+ m_nonfunctionMap.insert(child->name(), child);
+ if (child->isEnumType())
+ m_enumChildren.append(child);
+ }
+ if (child->isSharedCommentNode()) {
+ auto *scn = static_cast<SharedCommentNode *>(child);
+ for (Node *n : scn->collective())
+ adoptChild(n);
+ }
+ }
+}
+
+/*!
+ If this node has a child that is a QML property named \a n, return a
+ pointer to that child. Otherwise, return \nullptr.
+ */
+QmlPropertyNode *Aggregate::hasQmlProperty(const QString &n) const
+{
+ NodeType goal = Node::QmlProperty;
+ for (auto *child : std::as_const(m_children)) {
+ if (child->nodeType() == goal) {
+ if (child->name() == n)
+ return static_cast<QmlPropertyNode *>(child);
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ If this node has a child that is a QML property named \a n and that
+ also matches \a attached, return a pointer to that child.
+ */
+QmlPropertyNode *Aggregate::hasQmlProperty(const QString &n, bool attached) const
+{
+ NodeType goal = Node::QmlProperty;
+ for (auto *child : std::as_const(m_children)) {
+ if (child->nodeType() == goal) {
+ if (child->name() == n && child->isAttached() == attached)
+ return static_cast<QmlPropertyNode *>(child);
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ Returns \c true if this aggregate has multiple function
+ overloads matching the name of \a fn.
+
+ \note Assumes \a fn is a member of this aggregate.
+*/
+bool Aggregate::hasOverloads(const FunctionNode *fn) const
+{
+ auto it = m_functionMap.find(fn->name());
+ return !(it == m_functionMap.end()) && (it.value().size() > 1);
+}
+
+/*
+ When deciding whether to include a function in the function
+ index, if the function is marked private, don't include it.
+ If the function is marked obsolete, don't include it. If the
+ function is marked internal, don't include it. Or if the
+ function is a destructor or any kind of constructor, don't
+ include it. Otherwise include it.
+ */
+static bool keep(FunctionNode *fn)
+{
+ if (fn->isPrivate() || fn->isDeprecated() || fn->isInternal() || fn->isSomeCtor() || fn->isDtor())
+ return false;
+ return true;
+}
+
+/*!
+ Insert all functions declared in this aggregate into the
+ \a functionIndex. Call the function recursively for each
+ child that is an aggregate.
+
+ Only include functions that are in the public API and
+ that are not constructors or destructors.
+ */
+void Aggregate::findAllFunctions(NodeMapMap &functionIndex)
+{
+ for (auto functions : m_functionMap) {
+ std::for_each(functions.begin(), functions.end(),
+ [&functionIndex](FunctionNode *fn) {
+ if (keep(fn))
+ functionIndex[fn->name()].insert(fn->parent()->fullDocumentName(), fn);
+ }
+ );
+ }
+
+ for (Node *node : std::as_const(m_children)) {
+ if (node->isAggregate() && !node->isPrivate() && !node->isDontDocument())
+ static_cast<Aggregate *>(node)->findAllFunctions(functionIndex);
+ }
+}
+
+/*!
+ For each child of this node, if the child is a namespace node,
+ insert the child into the \a namespaces multimap. If the child
+ is an aggregate, call this function recursively for that child.
+
+ When the function called with the root node of a tree, it finds
+ all the namespace nodes in that tree and inserts them into the
+ \a namespaces multimap.
+
+ The root node of a tree is a namespace, but it has no name, so
+ it is not inserted into the map. So, if this function is called
+ for each tree in the qdoc database, it finds all the namespace
+ nodes in the database.
+ */
+void Aggregate::findAllNamespaces(NodeMultiMap &namespaces)
+{
+ for (auto *node : std::as_const(m_children)) {
+ if (node->isAggregate() && !node->isPrivate()) {
+ if (node->isNamespace() && !node->name().isEmpty())
+ namespaces.insert(node->name(), node);
+ static_cast<Aggregate *>(node)->findAllNamespaces(namespaces);
+ }
+ }
+}
+
+/*!
+ Returns true if this aggregate contains at least one child
+ that is marked obsolete. Otherwise returns false.
+ */
+bool Aggregate::hasObsoleteMembers() const
+{
+ for (const auto *node : m_children)
+ if (!node->isPrivate() && node->isDeprecated()) {
+ if (node->isFunction() || node->isProperty() || node->isEnumType() || node->isTypedef()
+ || node->isTypeAlias() || node->isVariable() || node->isQmlProperty())
+ return true;
+ }
+ return false;
+}
+
+/*!
+ Finds all the obsolete C++ classes and QML types in this
+ aggregate and all the C++ classes and QML types with obsolete
+ members, and inserts them into maps used elsewhere for
+ generating documentation.
+ */
+void Aggregate::findAllObsoleteThings()
+{
+ for (auto *node : std::as_const(m_children)) {
+ if (!node->isPrivate()) {
+ if (node->isDeprecated()) {
+ if (node->isClassNode())
+ QDocDatabase::obsoleteClasses().insert(node->qualifyCppName(), node);
+ else if (node->isQmlType())
+ QDocDatabase::obsoleteQmlTypes().insert(node->qualifyQmlName(), node);
+ } else if (node->isClassNode()) {
+ auto *a = static_cast<Aggregate *>(node);
+ if (a->hasObsoleteMembers())
+ QDocDatabase::classesWithObsoleteMembers().insert(node->qualifyCppName(), node);
+ } else if (node->isQmlType()) {
+ auto *a = static_cast<Aggregate *>(node);
+ if (a->hasObsoleteMembers())
+ QDocDatabase::qmlTypesWithObsoleteMembers().insert(node->qualifyQmlName(),
+ node);
+ } else if (node->isAggregate()) {
+ static_cast<Aggregate *>(node)->findAllObsoleteThings();
+ }
+ }
+ }
+}
+
+/*!
+ Finds all the C++ classes, QML types, QML basic types, and examples
+ in this aggregate and inserts them into appropriate maps for later
+ use in generating documentation.
+ */
+void Aggregate::findAllClasses()
+{
+ for (auto *node : std::as_const(m_children)) {
+ if (!node->isPrivate() && !node->isInternal() && !node->isDontDocument()
+ && node->tree()->camelCaseModuleName() != QString("QDoc")) {
+ if (node->isClassNode()) {
+ QDocDatabase::cppClasses().insert(node->qualifyCppName().toLower(), node);
+ } else if (node->isQmlType()) {
+ QString name = node->name().toLower();
+ QDocDatabase::qmlTypes().insert(name, node);
+ // also add to the QML basic type map
+ if (node->isQmlBasicType())
+ QDocDatabase::qmlBasicTypes().insert(name, node);
+ } else if (node->isExample()) {
+ // use the module index title as key for the example map
+ QString title = node->tree()->indexTitle();
+ if (!QDocDatabase::examples().contains(title, node))
+ QDocDatabase::examples().insert(title, node);
+ } else if (node->isAggregate()) {
+ static_cast<Aggregate *>(node)->findAllClasses();
+ }
+ }
+ }
+}
+
+/*!
+ Find all the attribution pages in this node and insert them
+ into \a attributions.
+ */
+void Aggregate::findAllAttributions(NodeMultiMap &attributions)
+{
+ for (auto *node : std::as_const(m_children)) {
+ if (!node->isPrivate()) {
+ if (node->isPageNode() && static_cast<PageNode*>(node)->isAttribution())
+ attributions.insert(node->tree()->indexTitle(), node);
+ else if (node->isAggregate())
+ static_cast<Aggregate *>(node)->findAllAttributions(attributions);
+ }
+ }
+}
+
+/*!
+ Finds all the nodes in this node where a \e{since} command appeared
+ in the qdoc comment and sorts them into maps according to the kind
+ of node.
+
+ This function is used for generating the "New Classes... in x.y"
+ section on the \e{What's New in Qt x.y} page.
+ */
+void Aggregate::findAllSince()
+{
+ for (auto *node : std::as_const(m_children)) {
+ if (node->isRelatedNonmember() && node->parent() != this)
+ continue;
+ QString sinceString = node->since();
+ // Insert a new entry into each map for each new since string found.
+ if (node->isInAPI() && !sinceString.isEmpty()) {
+ // operator[] will insert a default-constructed value into the
+ // map if key is not found, which is what we want here.
+ auto &nsmap = QDocDatabase::newSinceMaps()[sinceString];
+ auto &ncmap = QDocDatabase::newClassMaps()[sinceString];
+ auto &nqcmap = QDocDatabase::newQmlTypeMaps()[sinceString];
+
+ if (node->isFunction()) {
+ // Insert functions into the general since map.
+ auto *fn = static_cast<FunctionNode *>(node);
+ if (!fn->isDeprecated() && !fn->isSomeCtor() && !fn->isDtor())
+ nsmap.insert(fn->name(), fn);
+ } else if (node->isClassNode()) {
+ // Insert classes into the since and class maps.
+ QString name = node->qualifyWithParentName();
+ nsmap.insert(name, node);
+ ncmap.insert(name, node);
+ } else if (node->isQmlType()) {
+ // Insert QML elements into the since and element maps.
+ QString name = node->qualifyWithParentName();
+ nsmap.insert(name, node);
+ nqcmap.insert(name, node);
+ } else if (node->isQmlProperty()) {
+ // Insert QML properties into the since map.
+ nsmap.insert(node->name(), node);
+ } else {
+ // Insert external documents into the general since map.
+ QString name = node->qualifyWithParentName();
+ nsmap.insert(name, node);
+ }
+ }
+ // Enum values - a special case as EnumItem is not a Node subclass
+ if (node->isInAPI() && node->isEnumType()) {
+ for (const auto &val : static_cast<EnumNode *>(node)->items()) {
+ sinceString = val.since();
+ if (sinceString.isEmpty())
+ continue;
+ // Insert to enum value map
+ QDocDatabase::newEnumValueMaps()[sinceString].insert(
+ node->name() + "::" + val.name(), node);
+ // Ugly hack: Insert into general map with an empty key -
+ // we need something in there to mark the corresponding
+ // section populated. See Sections class constructor.
+ QDocDatabase::newSinceMaps()[sinceString].replace(QString(), node);
+ }
+ }
+
+ // Recursively find child nodes with since commands.
+ if (node->isAggregate())
+ static_cast<Aggregate *>(node)->findAllSince();
+ }
+}
+
+/*!
+ Resolves the inheritance information for all QML type children
+ of this aggregate.
+*/
+void Aggregate::resolveQmlInheritance()
+{
+ NodeMap previousSearches;
+ for (auto *child : std::as_const(m_children)) {
+ if (!child->isQmlType())
+ continue;
+ static_cast<QmlTypeNode *>(child)->resolveInheritance(previousSearches);
+ }
+}
+
+/*!
+ Returns a word representing the kind of Aggregate this node is.
+ Currently only works for class, struct, and union, but it can
+ easily be extended. If \a cap is true, the word is capitalised.
+ */
+QString Aggregate::typeWord(bool cap) const
+{
+ if (cap) {
+ switch (nodeType()) {
+ case Node::Class:
+ return QLatin1String("Class");
+ case Node::Struct:
+ return QLatin1String("Struct");
+ case Node::Union:
+ return QLatin1String("Union");
+ default:
+ break;
+ }
+ } else {
+ switch (nodeType()) {
+ case Node::Class:
+ return QLatin1String("class");
+ case Node::Struct:
+ return QLatin1String("struct");
+ case Node::Union:
+ return QLatin1String("union");
+ default:
+ break;
+ }
+ }
+ return QString();
+}
+
+/*! \fn int Aggregate::count() const
+ Returns the number of children in the child list.
+ */
+
+/*! \fn const NodeList &Aggregate::childNodes() const
+ Returns a const reference to the child list.
+ */
+
+/*! \fn NodeList::ConstIterator Aggregate::constBegin() const
+ Returns a const iterator pointing at the beginning of the child list.
+ */
+
+/*! \fn NodeList::ConstIterator Aggregate::constEnd() const
+ Returns a const iterator pointing at the end of the child list.
+ */
+
+/*! \fn QmlTypeNode *Aggregate::qmlBaseNode() const
+ If this Aggregate is a QmlTypeNode, this function returns a pointer to
+ the QmlTypeNode that is its base type. Otherwise it returns \c nullptr.
+ A QmlTypeNode doesn't always have a base type, so even when this Aggregate
+ is aQmlTypeNode, the pointer returned can be \c nullptr.
+ */
+
+/*! \fn FunctionMap &Aggregate::functionMap()
+ Returns a reference to this Aggregate's function map, which
+ is a map of all the children of this Aggregate that are
+ FunctionNodes.
+ */
+
+/*! \fn void Aggregate::appendToRelatedByProxy(const NodeList &t)
+ Appends the list of node pointers to the list of elements that are
+ related to this Aggregate but are documented in a different module.
+
+ \sa relatedByProxy()
+ */
+
+/*! \fn NodeList &Aggregate::relatedByProxy()
+ Returns a reference to a list of node pointers where each element
+ points to a node in an index file for some other module, such that
+ whatever the node represents was documented in that other module,
+ but it is related to this Aggregate, so when the documentation for
+ this Aggregate is written, it will contain links to elements in the
+ other module.
+ */
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/aggregate.h b/src/qdoc/qdoc/src/qdoc/aggregate.h
new file mode 100644
index 000000000..a02633e04
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/aggregate.h
@@ -0,0 +1,118 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef AGGREGATE_H
+#define AGGREGATE_H
+
+#include "pagenode.h"
+
+#include <optional>
+#include <vector>
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+
+class FunctionNode;
+class QmlTypeNode;
+class QmlPropertyNode;
+
+class Aggregate : public PageNode
+{
+public:
+ using FunctionMap = QMap<QString, std::vector<FunctionNode*>>;
+
+ [[nodiscard]] Node *findChildNode(const QString &name, Node::Genus genus,
+ int findFlags = 0) const;
+ Node *findNonfunctionChild(const QString &name, bool (Node::*)() const);
+ void findChildren(const QString &name, NodeVector &nodes) const;
+ FunctionNode *findFunctionChild(const QString &name, const Parameters &parameters);
+ FunctionNode *findFunctionChild(const FunctionNode *clone);
+
+ void resolveRelates();
+ void normalizeOverloads();
+ void markUndocumentedChildrenInternal();
+
+ [[nodiscard]] bool isAggregate() const override { return true; }
+ [[nodiscard]] const EnumNode *findEnumNodeForValue(const QString &enumValue) const;
+
+ [[nodiscard]] qsizetype count() const { return m_children.size(); }
+ [[nodiscard]] const NodeList &childNodes() const { return m_children; }
+ const NodeList &nonfunctionList();
+ [[nodiscard]] NodeList::ConstIterator constBegin() const { return m_children.constBegin(); }
+ [[nodiscard]] NodeList::ConstIterator constEnd() const { return m_children.constEnd(); }
+
+ inline void setIncludeFile(const QString& include) { m_includeFile.emplace(include); }
+ // REMARK: Albeit not enforced at the API boundaries,
+ // downstream-user can assume that if there is a QString that was
+ // set, then that string is not empty.
+ [[nodiscard]] inline const std::optional<QString>& includeFile() const { return m_includeFile; }
+
+ [[nodiscard]] QmlPropertyNode *hasQmlProperty(const QString &) const;
+ [[nodiscard]] QmlPropertyNode *hasQmlProperty(const QString &, bool attached) const;
+ virtual QmlTypeNode *qmlBaseNode() const { return nullptr; }
+ void addChildByTitle(Node *child, const QString &title);
+ void addChild(Node *child);
+ void adoptChild(Node *child);
+
+ FunctionMap &functionMap() { return m_functionMap; }
+ void findAllFunctions(NodeMapMap &functionIndex);
+ void findAllNamespaces(NodeMultiMap &namespaces);
+ void findAllAttributions(NodeMultiMap &attributions);
+ [[nodiscard]] bool hasObsoleteMembers() const;
+ void findAllObsoleteThings();
+ void findAllClasses();
+ void findAllSince();
+ void resolveQmlInheritance();
+ bool hasOverloads(const FunctionNode *fn) const;
+ void appendToRelatedByProxy(const NodeList &t) { m_relatedByProxy.append(t); }
+ NodeList &relatedByProxy() { return m_relatedByProxy; }
+ [[nodiscard]] QString typeWord(bool cap) const;
+
+protected:
+ Aggregate(NodeType type, Aggregate *parent, const QString &name)
+ : PageNode(type, parent, name) {}
+ ~Aggregate() override;
+
+private:
+ friend class Node;
+ void dropNonRelatedMembers();
+
+protected:
+ NodeList m_children {};
+ NodeList m_relatedByProxy {};
+ FunctionMap m_functionMap {};
+
+private:
+ // REMARK: The member indicates the name of a file where the
+ // aggregate can be found from, for example, an header file that
+ // declares a class.
+ // For aggregates such as classes we expect this to always be set
+ // to a non-empty string after the code-parsing phase.
+ // Indeed, currently, by default, QDoc always generates such a
+ // string using the name of the aggregate if no include file can
+ // be propagated from some of the parents.
+ //
+ // Nonetheless, we are still forced to make this an optional, as
+ // this will not be true for all Aggregates.
+ //
+ // For example, for namespaces, we don't seem to set an include
+ // file and indeed doing so wouldn't be particularly meaningful.
+ //
+ // It is possible to assume in later code, especially the
+ // generation phase, that at least some classes of aggregates
+ // always have a value set here but we should, for the moment,
+ // still check for the possibility of something not to be there,
+ // or warn if we decide to ignore that, to be compliant with the
+ // current interface, whose change would require deep changes to
+ // QDoc internal structures.
+ std::optional<QString> m_includeFile{};
+ NodeList m_enumChildren {};
+ NodeMultiMap m_nonfunctionMap {};
+ NodeList m_nonfunctionList {};
+};
+
+QT_END_NAMESPACE
+
+#endif // AGGREGATE_H
diff --git a/src/qdoc/qdoc/src/qdoc/atom.cpp b/src/qdoc/qdoc/src/qdoc/atom.cpp
new file mode 100644
index 000000000..f887c4ec5
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/atom.cpp
@@ -0,0 +1,458 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "atom.h"
+
+#include "location.h"
+#include "qdocdatabase.h"
+
+#include <QtCore/qregularexpression.h>
+
+#include <cstdio>
+
+QT_BEGIN_NAMESPACE
+
+/*! \class Atom
+ \brief The Atom class is the fundamental unit for representing
+ documents internally.
+
+ Atoms have a \i type and are completed by a \i string whose
+ meaning depends on the \i type. For example, the string
+ \quotation
+ \i italic text looks nicer than \bold bold text
+ \endquotation
+ is represented by the following atoms:
+ \quotation
+ (FormattingLeft, ATOM_FORMATTING_ITALIC)
+ (String, "italic")
+ (FormattingRight, ATOM_FORMATTING_ITALIC)
+ (String, " text is more attractive than ")
+ (FormattingLeft, ATOM_FORMATTING_BOLD)
+ (String, "bold")
+ (FormattingRight, ATOM_FORMATTING_BOLD)
+ (String, " text")
+ \endquotation
+
+ \also Text
+*/
+
+/*! \enum Atom::AtomType
+
+ \value AnnotatedList
+ \value AutoLink
+ \value BaseName
+ \value BriefLeft
+ \value BriefRight
+ \value C
+ \value CaptionLeft
+ \value CaptionRight
+ \value Code
+ \value CodeBad
+ \value CodeQuoteArgument
+ \value CodeQuoteCommand
+ \value DetailsLeft
+ \value DetailsRight
+ \value DivLeft
+ \value DivRight
+ \value ExampleFileLink
+ \value ExampleImageLink
+ \value FormatElse
+ \value FormatEndif
+ \value FormatIf
+ \value FootnoteLeft
+ \value FootnoteRight
+ \value FormattingLeft
+ \value FormattingRight
+ \value GeneratedList
+ \value Image
+ \value ImageText
+ \value ImportantNote
+ \value InlineImage
+ \value Keyword
+ \value LineBreak
+ \value Link
+ \value LinkNode
+ \value ListLeft
+ \value ListItemNumber
+ \value ListTagLeft
+ \value ListTagRight
+ \value ListItemLeft
+ \value ListItemRight
+ \value ListRight
+ \value NavAutoLink
+ \value NavLink
+ \value Nop
+ \value Note
+ \value ParaLeft
+ \value ParaRight
+ \value Qml
+ \value QuotationLeft
+ \value QuotationRight
+ \value RawString
+ \value SectionLeft
+ \value SectionRight
+ \value SectionHeadingLeft
+ \value SectionHeadingRight
+ \value SidebarLeft
+ \value SidebarRight
+ \value SinceList
+ \value SinceTagLeft
+ \value SinceTagRight
+ \value String
+ \value TableLeft
+ \value TableRight
+ \value TableHeaderLeft
+ \value TableHeaderRight
+ \value TableRowLeft
+ \value TableRowRight
+ \value TableItemLeft
+ \value TableItemRight
+ \value TableOfContents
+ \value Target
+ \value UnhandledFormat
+ \value UnknownCommand
+*/
+
+static const struct
+{
+ const char *english;
+ int no;
+} atms[] = { { "AnnotatedList", Atom::AnnotatedList },
+ { "AutoLink", Atom::AutoLink },
+ { "BaseName", Atom::BaseName },
+ { "br", Atom::BR },
+ { "BriefLeft", Atom::BriefLeft },
+ { "BriefRight", Atom::BriefRight },
+ { "C", Atom::C },
+ { "CaptionLeft", Atom::CaptionLeft },
+ { "CaptionRight", Atom::CaptionRight },
+ { "Code", Atom::Code },
+ { "CodeBad", Atom::CodeBad },
+ { "CodeQuoteArgument", Atom::CodeQuoteArgument },
+ { "CodeQuoteCommand", Atom::CodeQuoteCommand },
+ { "ComparesLeft", Atom::ComparesLeft },
+ { "ComparesRight", Atom::ComparesRight },
+ { "DetailsLeft", Atom::DetailsLeft },
+ { "DetailsRight", Atom::DetailsRight },
+ { "DivLeft", Atom::DivLeft },
+ { "DivRight", Atom::DivRight },
+ { "ExampleFileLink", Atom::ExampleFileLink },
+ { "ExampleImageLink", Atom::ExampleImageLink },
+ { "FootnoteLeft", Atom::FootnoteLeft },
+ { "FootnoteRight", Atom::FootnoteRight },
+ { "FormatElse", Atom::FormatElse },
+ { "FormatEndif", Atom::FormatEndif },
+ { "FormatIf", Atom::FormatIf },
+ { "FormattingLeft", Atom::FormattingLeft },
+ { "FormattingRight", Atom::FormattingRight },
+ { "GeneratedList", Atom::GeneratedList },
+ { "hr", Atom::HR },
+ { "Image", Atom::Image },
+ { "ImageText", Atom::ImageText },
+ { "ImportantLeft", Atom::ImportantLeft },
+ { "ImportantRight", Atom::ImportantRight },
+ { "InlineImage", Atom::InlineImage },
+ { "Keyword", Atom::Keyword },
+ { "LegaleseLeft", Atom::LegaleseLeft },
+ { "LegaleseRight", Atom::LegaleseRight },
+ { "LineBreak", Atom::LineBreak },
+ { "Link", Atom::Link },
+ { "LinkNode", Atom::LinkNode },
+ { "ListLeft", Atom::ListLeft },
+ { "ListItemNumber", Atom::ListItemNumber },
+ { "ListTagLeft", Atom::ListTagLeft },
+ { "ListTagRight", Atom::ListTagRight },
+ { "ListItemLeft", Atom::ListItemLeft },
+ { "ListItemRight", Atom::ListItemRight },
+ { "ListRight", Atom::ListRight },
+ { "NavAutoLink", Atom::NavAutoLink },
+ { "NavLink", Atom::NavLink },
+ { "Nop", Atom::Nop },
+ { "NoteLeft", Atom::NoteLeft },
+ { "NoteRight", Atom::NoteRight },
+ { "ParaLeft", Atom::ParaLeft },
+ { "ParaRight", Atom::ParaRight },
+ { "Qml", Atom::Qml },
+ { "QuotationLeft", Atom::QuotationLeft },
+ { "QuotationRight", Atom::QuotationRight },
+ { "RawString", Atom::RawString },
+ { "SectionLeft", Atom::SectionLeft },
+ { "SectionRight", Atom::SectionRight },
+ { "SectionHeadingLeft", Atom::SectionHeadingLeft },
+ { "SectionHeadingRight", Atom::SectionHeadingRight },
+ { "SidebarLeft", Atom::SidebarLeft },
+ { "SidebarRight", Atom::SidebarRight },
+ { "SinceList", Atom::SinceList },
+ { "SinceTagLeft", Atom::SinceTagLeft },
+ { "SinceTagRight", Atom::SinceTagRight },
+ { "SnippetCommand", Atom::SnippetCommand },
+ { "SnippetIdentifier", Atom::SnippetIdentifier },
+ { "SnippetLocation", Atom::SnippetLocation },
+ { "String", Atom::String },
+ { "TableLeft", Atom::TableLeft },
+ { "TableRight", Atom::TableRight },
+ { "TableHeaderLeft", Atom::TableHeaderLeft },
+ { "TableHeaderRight", Atom::TableHeaderRight },
+ { "TableRowLeft", Atom::TableRowLeft },
+ { "TableRowRight", Atom::TableRowRight },
+ { "TableItemLeft", Atom::TableItemLeft },
+ { "TableItemRight", Atom::TableItemRight },
+ { "TableOfContents", Atom::TableOfContents },
+ { "Target", Atom::Target },
+ { "UnhandledFormat", Atom::UnhandledFormat },
+ { "WarningLeft", Atom::WarningLeft },
+ { "WarningRight", Atom::WarningRight },
+ { "UnknownCommand", Atom::UnknownCommand },
+ { nullptr, 0 } };
+
+/*! \fn Atom::Atom(AtomType type, const QString &string)
+
+ Constructs an atom of the specified \a type with the single
+ parameter \a string and does not put the new atom in a list.
+*/
+
+/*! \fn Atom::Atom(AtomType type, const QString &p1, const QString &p2)
+
+ Constructs an atom of the specified \a type with the two
+ parameters \a p1 and \a p2 and does not put the new atom
+ in a list.
+*/
+
+/*! \fn Atom(Atom *previous, AtomType type, const QString &string)
+
+ Constructs an atom of the specified \a type with the single
+ parameter \a string and inserts the new atom into the list
+ after the \a previous atom.
+*/
+
+/*! \fn Atom::Atom(Atom *previous, AtomType type, const QString &p1, const QString &p2)
+
+ Constructs an atom of the specified \a type with the two
+ parameters \a p1 and \a p2 and inserts the new atom into
+ the list after the \a previous atom.
+*/
+
+/*! \fn void Atom::appendChar(QChar ch)
+
+ Appends \a ch to the string parameter of this atom.
+
+ \also string()
+*/
+
+/*! \fn void Atom::concatenateString(const QString &string)
+
+ Appends \a string to the string parameter of this atom.
+
+ \also string()
+*/
+
+/*! \fn void Atom::chopString()
+
+ \also string()
+*/
+
+/*!
+ Starting from this Atom, searches the linked list for the
+ atom of specified type \a t and returns it. Returns \nullptr
+ if no such atom is found.
+*/
+const Atom *Atom::find(AtomType t) const
+{
+ const auto *a{this};
+ while (a && a->type() != t)
+ a = a->next();
+ return a;
+}
+
+/*!
+ Starting from this Atom, searches the linked list for the
+ atom of specified type \a t and string \a s, and returns it.
+ Returns \nullptr if no such atom is found.
+*/
+const Atom *Atom::find(AtomType t, const QString &s) const
+{
+ const auto *a{this};
+ while (a && (a->type() != t || a->string() != s))
+ a = a->next();
+ return a;
+}
+
+/*! \fn Atom *Atom::next()
+ Return the next atom in the atom list.
+ \also type(), string()
+*/
+
+/*!
+ Return the next Atom in the list if it is of AtomType \a t.
+ Otherwise return 0.
+ */
+const Atom *Atom::next(AtomType t) const
+{
+ return (m_next && (m_next->type() == t)) ? m_next : nullptr;
+}
+
+/*!
+ Return the next Atom in the list if it is of AtomType \a t
+ and its string part is \a s. Otherwise return 0.
+ */
+const Atom *Atom::next(AtomType t, const QString &s) const
+{
+ return (m_next && (m_next->type() == t) && (m_next->string() == s)) ? m_next : nullptr;
+}
+
+/*! \fn const Atom *Atom::next() const
+ Return the next atom in the atom list.
+ \also type(), string()
+*/
+
+/*! \fn AtomType Atom::type() const
+ Return the type of this atom.
+ \also string(), next()
+*/
+
+/*!
+ Return the type of this atom as a string. Return "Invalid" if
+ type() returns an impossible value.
+
+ This is only useful for debugging.
+
+ \also type()
+*/
+QString Atom::typeString() const
+{
+ static bool deja = false;
+
+ if (!deja) {
+ int i = 0;
+ while (atms[i].english != nullptr) {
+ if (atms[i].no != i)
+ Location::internalError(QStringLiteral("QDoc::Atom: atom %1 missing").arg(i));
+ ++i;
+ }
+ deja = true;
+ }
+
+ int i = static_cast<int>(type());
+ if (i < 0 || i > static_cast<int>(Last))
+ return QLatin1String("Invalid");
+ return QLatin1String(atms[i].english);
+}
+
+/*! \fn const QString &Atom::string() const
+
+ Returns the string parameter that together with the type
+ characterizes this atom.
+
+ \also type(), next()
+*/
+
+/*!
+ For a link atom, returns the string representing the link text
+ if one exist in the list of atoms.
+*/
+QString Atom::linkText() const
+{
+ Q_ASSERT(m_type == Atom::Link);
+ QString result;
+
+ if (next() && next()->string() == ATOM_FORMATTING_LINK) {
+ auto *atom = next()->next();
+ while (atom && atom->type() != Atom::FormattingRight) {
+ result += atom->string();
+ atom = atom->next();
+ }
+ return result;
+ }
+
+ return string();
+}
+
+/*!
+ The only constructor for LinkAtom. It creates an Atom of
+ type Atom::Link. \a p1 being the link target. \a p2 is the
+ parameters in square brackets. Normally there is just one
+ word in the square brackets, but there can be up to three
+ words separated by spaces. The constructor splits \a p2 on
+ the space character.
+ */
+LinkAtom::LinkAtom(const QString &p1, const QString &p2, Location location)
+ : Atom(Atom::Link, p1),
+ location(location),
+ m_resolved(false),
+ m_genus(Node::DontCare),
+ m_domain(nullptr),
+ m_squareBracketParams(p2)
+{
+ // nada.
+}
+
+/*!
+ This function resolves the parameters that were enclosed in
+ square brackets. If the parameters have already been resolved,
+ it does nothing and returns immediately.
+ */
+void LinkAtom::resolveSquareBracketParams()
+{
+ if (m_resolved)
+ return;
+ const QStringList params = m_squareBracketParams.toLower().split(QLatin1Char(' '));
+ for (const auto &param : params) {
+ if (!m_domain) {
+ m_domain = QDocDatabase::qdocDB()->findTree(param);
+ if (m_domain) {
+ continue;
+ }
+ }
+
+ if (param == "qml") {
+ m_genus = Node::QML;
+ continue;
+ }
+ if (param == "cpp") {
+ m_genus = Node::CPP;
+ continue;
+ }
+ if (param == "doc") {
+ m_genus = Node::DOC;
+ continue;
+ }
+ if (param == "api") {
+ m_genus = Node::API;
+ continue;
+ }
+ break;
+ }
+ m_resolved = true;
+}
+
+/*!
+ Standard copy constructor of LinkAtom \a t.
+ */
+LinkAtom::LinkAtom(const LinkAtom &t)
+ : Atom(Link, t.string()),
+ location(t.location),
+ m_resolved(t.m_resolved),
+ m_genus(t.m_genus),
+ m_domain(t.m_domain),
+ m_squareBracketParams(t.m_squareBracketParams)
+{
+ // nothing
+}
+
+/*!
+ Special copy constructor of LinkAtom \a t, where
+ where the new LinkAtom will not be the first one
+ in the list.
+ */
+LinkAtom::LinkAtom(Atom *previous, const LinkAtom &t)
+ : Atom(previous, Link, t.string()),
+ location(t.location),
+ m_resolved(t.m_resolved),
+ m_genus(t.m_genus),
+ m_domain(t.m_domain),
+ m_squareBracketParams(t.m_squareBracketParams)
+{
+ previous->m_next = this;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/atom.h b/src/qdoc/qdoc/src/qdoc/atom.h
new file mode 100644
index 000000000..7483c829e
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/atom.h
@@ -0,0 +1,223 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef ATOM_H
+#define ATOM_H
+
+#include "node.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+
+class Tree;
+class LinkAtom;
+
+class Atom
+{
+public:
+ enum AtomType {
+ AnnotatedList,
+ AutoLink,
+ BaseName,
+ BR,
+ BriefLeft,
+ BriefRight,
+ C,
+ CaptionLeft,
+ CaptionRight,
+ Code,
+ CodeBad,
+ CodeQuoteArgument,
+ CodeQuoteCommand,
+ ComparesLeft,
+ ComparesRight,
+ DetailsLeft,
+ DetailsRight,
+ DivLeft,
+ DivRight,
+ ExampleFileLink,
+ ExampleImageLink,
+ FootnoteLeft,
+ FootnoteRight,
+ FormatElse,
+ FormatEndif,
+ FormatIf,
+ FormattingLeft,
+ FormattingRight,
+ GeneratedList,
+ HR,
+ Image,
+ ImageText,
+ ImportantLeft,
+ ImportantRight,
+ InlineImage,
+ Keyword,
+ LegaleseLeft,
+ LegaleseRight,
+ LineBreak,
+ Link,
+ LinkNode,
+ ListLeft,
+ ListItemNumber,
+ ListTagLeft,
+ ListTagRight,
+ ListItemLeft,
+ ListItemRight,
+ ListRight,
+ NavAutoLink,
+ NavLink,
+ Nop,
+ NoteLeft,
+ NoteRight,
+ ParaLeft,
+ ParaRight,
+ Qml,
+ QuotationLeft,
+ QuotationRight,
+ RawString,
+ SectionLeft,
+ SectionRight,
+ SectionHeadingLeft,
+ SectionHeadingRight,
+ SidebarLeft,
+ SidebarRight,
+ SinceList,
+ SinceTagLeft,
+ SinceTagRight,
+ SnippetCommand,
+ SnippetIdentifier,
+ SnippetLocation,
+ String,
+ TableLeft,
+ TableRight,
+ TableHeaderLeft,
+ TableHeaderRight,
+ TableRowLeft,
+ TableRowRight,
+ TableItemLeft,
+ TableItemRight,
+ TableOfContents,
+ Target,
+ UnhandledFormat,
+ WarningLeft,
+ WarningRight,
+ UnknownCommand,
+ Last = UnknownCommand
+ };
+
+ friend class LinkAtom;
+
+ explicit Atom(AtomType type, const QString &string = "") : m_type(type), m_strs(string) { }
+
+ Atom(AtomType type, const QString &p1, const QString &p2) : m_type(type), m_strs(p1)
+ {
+ if (!p2.isEmpty())
+ m_strs << p2;
+ }
+
+ Atom(Atom *previous, AtomType type, const QString &string)
+ : m_next(previous->m_next), m_type(type), m_strs(string)
+ {
+ previous->m_next = this;
+ }
+
+ Atom(Atom *previous, AtomType type, const QString &p1, const QString &p2)
+ : m_next(previous->m_next), m_type(type), m_strs(p1)
+ {
+ if (!p2.isEmpty())
+ m_strs << p2;
+ previous->m_next = this;
+ }
+
+ virtual ~Atom() = default;
+
+ void appendChar(QChar ch) { m_strs[0] += ch; }
+ void concatenateString(const QString &string) { m_strs[0] += string; }
+ void append(const QString &string) { m_strs << string; }
+ void chopString() { m_strs[0].chop(1); }
+ void setString(const QString &string) { m_strs[0] = string; }
+ Atom *next() { return m_next; }
+ void setNext(Atom *newNext) { m_next = newNext; }
+
+ [[nodiscard]] const Atom *find(AtomType t) const;
+ [[nodiscard]] const Atom *find(AtomType t, const QString &s) const;
+ [[nodiscard]] const Atom *next() const { return m_next; }
+ [[nodiscard]] const Atom *next(AtomType t) const;
+ [[nodiscard]] const Atom *next(AtomType t, const QString &s) const;
+ [[nodiscard]] AtomType type() const { return m_type; }
+ [[nodiscard]] QString typeString() const;
+ [[nodiscard]] const QString &string() const { return m_strs[0]; }
+ [[nodiscard]] const QString &string(int i) const { return m_strs[i]; }
+ [[nodiscard]] qsizetype count() const { return m_strs.size(); }
+ [[nodiscard]] QString linkText() const;
+ [[nodiscard]] const QStringList &strings() const { return m_strs; }
+
+ [[nodiscard]] virtual bool isLinkAtom() const { return false; }
+ virtual Node::Genus genus() { return Node::DontCare; }
+ virtual Tree *domain() { return nullptr; }
+ virtual void resolveSquareBracketParams() {}
+
+protected:
+ Atom *m_next = nullptr;
+ AtomType m_type {};
+ QStringList m_strs {};
+};
+
+class LinkAtom : public Atom
+{
+public:
+ LinkAtom(const QString &p1, const QString &p2, Location location = Location());
+ LinkAtom(const LinkAtom &t);
+ LinkAtom(Atom *previous, const LinkAtom &t);
+ ~LinkAtom() override = default;
+
+ [[nodiscard]] bool isLinkAtom() const override { return true; }
+ Node::Genus genus() override
+ {
+ resolveSquareBracketParams();
+ return m_genus;
+ }
+ Tree *domain() override
+ {
+ resolveSquareBracketParams();
+ return m_domain;
+ }
+ void resolveSquareBracketParams() override;
+
+public:
+ Location location;
+
+protected:
+ bool m_resolved {};
+ Node::Genus m_genus {};
+ Tree *m_domain {};
+ QString m_squareBracketParams {};
+};
+
+#define ATOM_FORMATTING_BOLD "bold"
+#define ATOM_FORMATTING_INDEX "index"
+#define ATOM_FORMATTING_ITALIC "italic"
+#define ATOM_FORMATTING_LINK "link"
+#define ATOM_FORMATTING_PARAMETER "parameter"
+#define ATOM_FORMATTING_SPAN "span "
+#define ATOM_FORMATTING_SUBSCRIPT "subscript"
+#define ATOM_FORMATTING_SUPERSCRIPT "superscript"
+#define ATOM_FORMATTING_TELETYPE "teletype"
+#define ATOM_FORMATTING_TRADEMARK "trademark"
+#define ATOM_FORMATTING_UICONTROL "uicontrol"
+#define ATOM_FORMATTING_UNDERLINE "underline"
+
+#define ATOM_LIST_BULLET "bullet"
+#define ATOM_LIST_TAG "tag"
+#define ATOM_LIST_VALUE "value"
+#define ATOM_LIST_LOWERALPHA "loweralpha"
+#define ATOM_LIST_LOWERROMAN "lowerroman"
+#define ATOM_LIST_NUMERIC "numeric"
+#define ATOM_LIST_UPPERALPHA "upperalpha"
+#define ATOM_LIST_UPPERROMAN "upperroman"
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.cpp b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.cpp
new file mode 100644
index 000000000..799582139
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.cpp
@@ -0,0 +1,126 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "directorypath.h"
+
+/*!
+ * \class DirectoryPath
+ *
+ * \brief Represents a path to a directory that was known to exist on the
+ * filesystem.
+ *
+ * An instance of this type guarantees that, at the time of creation
+ * of the instance, the contained path represented an existing,
+ * readable, executable directory.
+ *
+ * The type is intended to be used whenever a user-provided path to a
+ * directory is encountered the first time, validating that it can be
+ * used later on for the duration of a QDoc execution and
+ * canonicalizing the original path.
+ *
+ * Such a usage example could be during the configuration process,
+ * when encountering the paths that defines where QDoc should search
+ * for images or other files.
+ *
+ * Similarly, it is intended to be used at the API boundaries,
+ * internally, to relieve the called element of the requirement to
+ * check the validity of a path when a directory is required and to
+ * ensure that a single format of the path is encountered.
+ *
+ * Do note that the guarantees provided by this type do not
+ * necessarily hold after the time of creation of an instance.
+ * Indeed, the underlying filesystem may have changed.
+ *
+ * It is possible to renew the contract by obtaining a new instance:
+ *
+ * \code
+ * DirectoryPath old...
+ *
+ * ...
+ *
+ * auto current{DirectoryPath:refine(old.value())};
+ * \endcode
+ *
+ * QDoc itself will not generally perform destructive operations on
+ * its input files during an execution and, as such, it is never
+ * required to renew a contract. Ensuring that the underlying input
+ * files are indeed immutable is out-of-scope for QDoc and it is
+ * allowed to consider a case where the contract was invalidated as
+ * undefined behavior.
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {wrapped_type_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_equality_operator_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_less_than_operator_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_strictly_less_than_operator_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_greater_than_operator_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_strictly_greater_than_operator_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {refine_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {value_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {copy_constructor_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {copy_assignment_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {move_constructor_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {move_assignment_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {conversion_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_equal_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_unequal_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_less_than_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_less_than_or_equal_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_greater_than_documentation} {DirectoryPath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_greater_than_or_equal_documentation} {DirectoryPath}
+ */
diff --git a/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.h b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.h
new file mode 100644
index 000000000..7ec1dd415
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/directorypath.h
@@ -0,0 +1,17 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "../refined_typedef.h"
+
+#include <optional>
+
+#include <QtCore/qstring.h>
+#include <QtCore/qfileinfo.h>
+
+QDOC_REFINED_TYPEDEF(QString, DirectoryPath) {
+ QFileInfo info{value};
+
+ return (info.isDir() && info.isReadable() && info.isExecutable()) ? std::optional(DirectoryPath{info.canonicalFilePath()}) : std::nullopt;
+}
diff --git a/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.cpp b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.cpp
new file mode 100644
index 000000000..d8964b6a6
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.cpp
@@ -0,0 +1,125 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "filepath.h"
+
+/*!
+ * \class FilePath
+ *
+ * \brief Represents a path to a file that was known to exist on the
+ * filesystem.
+ *
+ * An instance of this type guarantees that, at the time of creation
+ * of the instance, the contained path represented an existing,
+ * readable file.
+ *
+ * The type is intended to be used whenever a user-provided path to a
+ * file is encountered the first time, validating that it can be
+ * used later on for the duration of a QDoc execution and
+ * canonicalizing the original path.
+ *
+ * Such a usage example could be when resolving a file whose path is
+ * provided by the user.
+ *
+ * Similarly, it is intended to be used at the API boundaries,
+ * internally, to relieve the called element of the requirement to
+ * check the validity of a path when a file is required and to
+ * ensure that a single format of the path is encountered.
+ *
+ * Do note that the guarantees provided by this type do not
+ * necessarily hold after the time of creation of an instance.
+ * Indeed, the underlying filesystem may have changed.
+ *
+ * It is possible to renew the contract by obtaining a new instance:
+ *
+ * \code
+ * FilePath old...
+ *
+ * ...
+ *
+ * auto current{FilePath::refine(old.value())};
+ * \endcode
+ *
+ * QDoc itself will not generally perform destructive operations on
+ * its input files during an execution and, as such, it is never
+ * required to renew a contract. Ensuring that the underlying input
+ * files are indeed immutable is out-of-scope for QDoc and it is
+ * allowed to consider a case where the contract was invalidated as
+ * undefined behavior.
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {wrapped_type_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_equality_operator_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_less_than_operator_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_strictly_less_than_operator_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_greater_than_operator_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {has_strictly_greater_than_operator_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {refine_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {value_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {copy_constructor_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {copy_assignment_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {move_constructor_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {move_assignment_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {conversion_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_equal_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_unequal_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_less_than_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_less_than_or_equal_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_greater_than_documentation} {FilePath}
+ */
+
+/*!
+ * \include boundaries/refined_typedef_members.qdocinc {operator_greater_than_or_equal_documentation} {FilePath}
+ */
diff --git a/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.h b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.h
new file mode 100644
index 000000000..e8a9b2d35
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/filepath.h
@@ -0,0 +1,17 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "qdoc/boundaries/refined_typedef.h"
+
+#include <optional>
+
+#include <QtCore/qstring.h>
+#include <QtCore/qfileinfo.h>
+
+QDOC_REFINED_TYPEDEF(QString, FilePath) {
+ QFileInfo info{value};
+
+ return (info.isFile() && info.isReadable()) ? std::optional(FilePath{info.canonicalFilePath()}) : std::nullopt;
+}
diff --git a/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.cpp b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.cpp
new file mode 100644
index 000000000..4d5eca512
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.cpp
@@ -0,0 +1,96 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "resolvedfile.h"
+
+/*!
+ * \class ResolvedFile
+ *
+ * \brief Represents a file that is reachable by QDoc based on its
+ * current configuration.
+ *
+ * Instances of this type are, generally, intended to be generated by
+ * any process that needs to query the filesystem for the presence of
+ * some files based on a user-inputted path to ensure their
+ * availability.
+ *
+ * Such an example might be when QDoc is searching for a file whose
+ * path is provided by the user, such as the one in a snippet command,
+ * that should represent a file that is reachable with the current
+ * configuration.
+ *
+ * On the other side, logic that requires access to files that are
+ * known to be user-provided, such as the quoting of snippets, can use
+ * this type at the API boundary to signal that the file should be
+ * accessible so that they avoid the need to search for the file
+ * themselves.
+ *
+ * Do note that, semantically, this type doesn't actually guarantee
+ * anything about its origin and only guarantees whatever its members
+ * guarantee.
+ *
+ * The reasoning behind this lack of enforcement is to allow for an
+ * easier testing.
+ * As many parts of QDoc might require the presence of an instance of
+ * this type, we want to be able to construct those instances without
+ * the need to pass trough whichever valid generator for them.
+ *
+ * Nonetheless, inside QDoc, any boundary that requires an instance of
+ * this type can consider it guaranteed that the instance was
+ * generated trough some appropriate logic, and consider it a bug if
+ * such is not the case.
+ *
+ * An instance of this type provides two pieces of information.
+ *
+ * The path to the file that is considered resolved, accessible trough
+ * the get_path() method and the string that was used to resolve the
+ * file in the first place, accessible trough the get_query() method.
+ *
+ * The first should be used by consumer who needs to interact with the
+ * file itself, such as reading from it or copying it.
+ *
+ * The second is provided for context and can be used when consumers
+ * need to know what the user-inputted path was in the first place,
+ * for example when presenting debug information.
+ *
+ * It is not semantically guaranteed that this two pieces of
+ * information are actually related. Any such instance for which this
+ * is true should be considered malformed. Inside QDoc, tough,
+ * consumer of this type can consider it guaranteed that no malformed
+ * instance will be passed to them, and consider it a bug if it
+ * happens otherwise.
+ */
+
+/*!
+ * \fn ResolvedFile::ResolvedFile(QString query, FilePath filepath)
+ *
+ * Constructs an instance of this type from \a query and \a filepath.
+ *
+ * \a query should represent the user-inputted path that was used to
+ * resolve the file that this instance represents.
+ *
+ * \a filepath should represent the file that is found querying the
+ * filesystem trough \a query using an appropriate logic for resolving
+ * files.
+ *
+ * An instance that is built from \a query and \a filepath is
+ * guaranteed to return a value that is equivalent to \a query when
+ * get_query() is called and a value that is equivalent to \a
+ * filepath.value() when get_path() is called.
+ */
+
+/*!
+ * \fn const QString& ResolvedFile::get_query() const
+ *
+ * Returns a string representing the user-inputted path that was used
+ * to resolve the file.
+ */
+
+/*!
+ * \fn const QString& ResolvedFile::get_path() const
+ *
+ * Returns a string representing the canonicalized path to the file
+ * that was resolved.
+ *
+ * Access to this file is to be considered guranteed to be available.
+ */
diff --git a/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.h b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.h
new file mode 100644
index 000000000..e21120ab1
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/boundaries/filesystem/resolvedfile.h
@@ -0,0 +1,20 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "qdoc/boundaries/filesystem/filepath.h"
+
+#include <QString>
+
+struct ResolvedFile {
+public:
+ ResolvedFile(QString query, FilePath filepath) : query{query}, filepath{filepath} {}
+
+ [[nodiscard]] const QString& get_query() const { return query; }
+ [[nodiscard]] const QString& get_path() const { return filepath.value(); }
+
+private:
+ QString query;
+ FilePath filepath;
+};
diff --git a/src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef.h b/src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef.h
new file mode 100644
index 000000000..4f3a13b7c
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef.h
@@ -0,0 +1,207 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <QtCore/qglobal.h>
+
+#include <functional>
+#include <optional>
+#include <type_traits>
+
+// TODO: Express the documentation such that QDoc would be able to see
+// it and process it correctly. This probably means that we would like
+// to associate the definition with a namespace, albeit we could use
+// the header file too, and put the documentation in an empty cpp
+// file. This is delayed as there currently isn't much namespacing for
+// anything in QDoc and such a namespacing should be added gradually
+// and attentively.
+
+// TODO: Review the semantics for construction and optmize it. Should we copy the
+// value? Should we only allow rvalues?
+
+// TODO: There is an high chance that we will need to "compose"
+// refinitions later on when dealing, for example, with the basics of
+// user-provided paths.
+// For example, when requiring that each user-inputted path is purged
+// as per QFileInfo definition of purging.
+// For example, it might be that instead of passing QString around we
+// might pass some Path type that is a purged QString.
+// Then, any other refinement over paths will need to use that as a
+// base type.
+// To avoid the clutter that comes from that, if such will be the
+// case, we will need to change the definition of refine and value if
+// the passed in type was refined already.
+// That is, such that if we have:
+//
+// QDOC_REFINE_TYPE(QString, Path) { ... }
+// QDOC_REFINE_TYPE(Path, Foo) { ... }
+//
+// Foo refines a QString and Foo.value returns a QString. This should
+// in general be trivial as long as we add a way to identify, such as
+// a tag that refinements derive from, what type was declared through
+// QDOC_REFINED_TYPEDEF and what type was not.
+
+// TODO: Provide a way to generate a standard documentation for all
+// members of a type generated by QDOC_REFINED_TYPEDEF without having
+// to copy-paste include command everywhere.
+// The main problem of doing this is that the preprocessor strips
+// comments away, making it impossible to generate comments, and hence
+// QDoc documentation, with the preprocessor.
+
+/*!
+ * \macro QDOC_REFINED_TYPEDEF(_type, _name)
+ * \relates refined_typedef.hpp
+ *
+ * Declares a wrapper type for \c {_type}, with identifier \c {_name},
+ * that represents a subset of \c {_type} for which some conditions
+ * hold.
+ *
+ * For example:
+ *
+ * \code
+ QDOC_REFINED_TYPEDEF(std::size_t, Fin5) {
+ return (value < 5) : std::make_optional<Fin5>{value} : std::nullopt;
+ }
+ * \endcode
+ *
+ * Represents the subset of \c {std::size_t} that contains the value 0, 1,
+ * 2, 3 and 4, that is, the general finite set of cardinality 5.
+ *
+ * As the example shows, usages of the macro require some type, an
+ * identifier and some code.
+ *
+ * The type that is provided is the type that will be wrapped.
+ * Do note that we expect a type with no-qualifiers and that is not a
+ * pointer type. Types passed with those kind of qualifiers will be
+ * simplified to their base type.
+ *
+ * That is, for example, \c {int*}, \c {const int}, \c {const int&},
+ * \c {int&} all counts as \c {int}.
+ *
+ * The identifier that is passed is used as the name for the newly
+ * declared type that wraps the original type.
+ *
+ * The code block that is passed will be run when an instance of the
+ * newly created wrapper type is being obtained.
+ * If the wrapper type is T, the codeblock must return a \c
+ * {std::optional<T>}.
+ * The code block should perform any check that ensures that the
+ * guarantees provided by the wrapper type holds and return a value if
+ * such is the case, otherwise returning \c {std::nullopt}.
+ *
+ * Inside the code block, the identifier \e {value} is implicitly
+ * bound to an element of the wrapped type that the instance is being
+ * built from and for which the guarantees provided by the wrapper
+ * type must hold.
+ *
+ * When a call to QDOC_REFINED_TYPEDEF is successful, a type with the
+ * provided identifier is declared.
+ *
+ * Let T be a type declared trough a call of QDOC_REFINED_TYPEDEF and
+ * W be the type that it wraps.
+ *
+ * An instance of T can be obtained by calling T::refine with an
+ * element of W.
+ *
+ * If the element of W respects the guarantees that T provides, then
+ * the call will return an optional that contains an instance of T,
+ * othewise it will return an empty optional.
+ *
+ * When an instance of T is obtained, it will wrap the element of W that
+ * was used to obtain it.
+ *
+ * The wrapped value can be accessed trough the \c {value} method.
+ *
+ * For example, considering \c {Fin5}, we could obtain an instance of
+ * it as follows:
+ *
+ * \code
+ * auto instance = *(Fin5::refine(std::size_t{1}));
+ * \endcode
+ *
+ * With that instance available we can retrieve the original value as
+ * follows:
+ *
+ * \code
+ * instance.value(); // The value 1
+ * \endcode
+ */
+
+#define QDOC_REFINED_TYPEDEF(_type, _name) \
+ struct _name { \
+ public: \
+ using wrapped_type = std::remove_reference_t<std::remove_cv_t<std::remove_pointer_t<_type>>>; \
+ \
+ inline static constexpr auto has_equality_operator_v = std::is_invocable_r_v<bool, std::equal_to<>, wrapped_type, wrapped_type>; \
+ inline static constexpr auto has_less_than_operator_v = std::is_invocable_r_v<bool, std::less_equal<>, wrapped_type, wrapped_type>; \
+ inline static constexpr auto has_strictly_less_than_operator_v = std::is_invocable_r_v<bool, std::less<>, wrapped_type, wrapped_type>; \
+ inline static constexpr auto has_greater_than_operator_v = std::is_invocable_r_v<bool, std::greater_equal<>, wrapped_type, wrapped_type>; \
+ inline static constexpr auto has_strictly_greater_than_operator_v = std::is_invocable_r_v<bool, std::greater<>, wrapped_type, wrapped_type>; \
+ \
+ public: \
+ static std::optional<_name> refine(wrapped_type value); \
+ \
+ [[nodiscard]] const wrapped_type& value() const noexcept { return _value; } \
+ \
+ _name(const _name&) = default; \
+ _name& operator=(const _name&) = default; \
+ \
+ _name(_name&&) = default; \
+ _name& operator=(_name&&) = default; \
+ \
+ operator wrapped_type() const { return _value; } \
+ \
+ public: \
+ \
+ template< \
+ typename = std::enable_if_t< \
+ has_equality_operator_v \
+ > \
+ > \
+ bool operator==(const _name& rhs) const noexcept { return _value == rhs._value; } \
+ \
+ template< \
+ typename = std::enable_if_t< \
+ has_equality_operator_v \
+ > \
+ > \
+ bool operator!=(const _name& rhs) const noexcept { return !(_value == rhs._value); } \
+ \
+ template< \
+ typename = std::enable_if_t< \
+ has_less_than_operator_v \
+ > \
+ > \
+ bool operator<=(const _name& rhs) const noexcept { return _value <= rhs._value; } \
+ \
+ \
+ template< \
+ typename = std::enable_if_t< \
+ has_strictly_less_than_operator_v \
+ > \
+ > \
+ bool operator<(const _name& rhs) const noexcept { return _value < rhs._value; } \
+ \
+ template< \
+ typename = std::enable_if_t< \
+ has_greater_than_operator_v \
+ > \
+ > \
+ bool operator>=(const _name& rhs) const noexcept { return _value >= rhs._value; } \
+ \
+ template< \
+ typename = std::enable_if_t< \
+ has_strictly_greater_than_operator_v \
+ > \
+ > \
+ bool operator>(const _name& rhs) const noexcept { return _value > rhs._value; } \
+ \
+ private: \
+ _name(wrapped_type value) : _value{std::move(value)} {} \
+ \
+ private: \
+ wrapped_type _value; \
+ }; \
+ \
+ inline std::optional<_name> _name::refine(wrapped_type value)
diff --git a/src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef_members.qdocinc b/src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef_members.qdocinc
new file mode 100644
index 000000000..ab956f49f
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/boundaries/refined_typedef_members.qdocinc
@@ -0,0 +1,162 @@
+//! [wrapped_type_documentation]
+\typealias \1::wrapped_type
+
+The type that is wrapped by this type.
+
+This type is always deprived of qualifiers and is never a pointer
+type.
+//! [wrapped_type_documentation]
+
+//! [has_equality_operator_documentation]
+\variable \1::has_equality_operator_v
+
+True when the wrapped_type can be compared for equality.
+
+When this is the case, \1 can be compared for equality and inequality.
+//! [has_equality_operator_documentation]
+
+//! [has_less_than_operator_documentation]
+\variable \1::has_less_than_operator_v
+
+True when the wrapped_type can be compared for lesserness.
+
+When this is the case, \1 can be compared for lesserness.
+//! [has_less_than_operator_documentation]
+
+//! [has_strictly_less_than_operator_documentation]
+\variable \1::has_strictly_less_than_operator_v
+
+True when the wrapped_type can be compared for strict lesserness.
+
+When this is the case, \1 can be compared for strict lesserness.
+//! [has_stricly_less_than_operator_documentation]
+
+//! [has_greater_than_operator_documentation]
+\variable \1::has_greater_than_operator_v
+
+True when the wrapped_type can be compared for greaterness.
+
+When this is the case, \1 can be compared for greaterness.
+//! [has_less_than_operator_documentation]
+
+//! [has_strictly_greater_than_operator_documentation]
+\variable \1::has_strictly_greater_than_operator_v
+
+True when the wrapped_type can be compared for strict greaterness.
+
+When this is the case, \1 can be compared for strict greaterness.
+//! [has_stricly_greater_than_operator_documentation]
+
+//! [refine_documentation]
+\fn static std::optional<\1> \1::refine(wrapped_type value)
+
+Returns an instance of \1 wrapping \a value if \a value respects the
+guarantees that are required by \1.
+
+If such is not the case, \c {std::nullopt} is returned instead.
+//! [refine_documentation]
+
+//! [value_documentation]
+\fn const wrapped_type& \1::value() const noexcept
+
+Returns a const reference to the value that is wrapped by this
+instance.
+//! [value_documentation]
+
+//! [copy_constructor_documentation]
+\fn \1::\1(const \1& other)
+
+Copy-constructs an instance of \1 from \a other.
+
+This constructor is generated by the compiler.
+//! [copy_constructor_documentation]
+
+//! [copy_assignment_documentation]
+\fn \1::operator=(const \1& other)
+
+Copy-assigns to this instance of \1 from \a other.
+
+This constructor is generated by the compiler.
+//! [copy_assignment_documentation]
+
+//! [move_constructor_documentation]
+\fn \1::\1(\1&& other)
+
+Move-constructs an instance of \1 from \a other.
+
+The only valid operations on an instance that was moved-from are
+destruction and reassignment.
+
+This constructor is generated by the compiler.
+//! [move_constructor_documentation]
+
+//! [move_assignment_documentation]
+\fn \1::operator=(\1&& other)
+
+Move-assigns to this instance of \1 from \a other.
+
+The only valid operations on an instance that was moved-from are
+destruction and reassignment.
+
+This constructor is generated by the compiler.
+//! [move_assignment_documentation]
+
+//! [conversion_documentation]
+\fn operator wrapped_type() const
+
+Converts this instance to its wrapped value.
+//! [conversion_documentation]
+
+//! [operator_equal_documentation]
+\fn bool operator=(const \1& rhs) const noexcept
+
+Returns true if the value wrapped by this instance and \a rhs compare
+equal.
+
+Returns false otherwise.
+//! [operator_equal_documentation]
+
+//! [operator_unequal_documentation]
+\fn bool operator!=(const \1& rhs) const noexcept
+
+Returns true if the value wrapped by this instance and \a rhs do not
+compare equal.
+
+Returns false otherwise.
+//! [operator_unequal_documentation]
+
+//! [operator_less_than_documentation]
+\fn bool operator<(const \1& rhs) const noexcept
+
+Returns true if the value wrapped by this instance compares less than
+the value wrapped by \a rhs.
+
+Returns false otherwise.
+//! [operator_less_than_documentation]
+
+//! [operator_less_than_or_equal_documentation]
+\fn bool operator<=(const \1& rhs) const noexcept
+
+Returns true if the value wrapped by this instance compares less than
+or equal than the value wrapped by \a rhs.
+
+Returns false otherwise.
+//! [operator_less_than_or_equal_documentation]
+
+//! [operator_greater_than_documentation]
+\fn bool operator>(const \1& rhs) const noexcept
+
+Returns true if the value wrapped by this instance compares greater
+than the value wrapped by \a rhs.
+
+Returns false otherwise.
+//! [operator_greater_than_documentation]
+
+//! [operator_greater_than_or_equal_documentation]
+\fn bool operator>=(const \1& rhs) const noexcept
+
+Returns true if the value wrapped by this instance compares greater
+than or equal or equal than the value wrapped by \a rhs.
+
+Returns false otherwise.
+//! [operator_greater_than_or_equal_documentation]
diff --git a/src/qdoc/qdoc/src/qdoc/clang/AST/LLVM_LICENSE.txt b/src/qdoc/qdoc/src/qdoc/clang/AST/LLVM_LICENSE.txt
new file mode 100644
index 000000000..fa6ac5400
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/clang/AST/LLVM_LICENSE.txt
@@ -0,0 +1,279 @@
+==============================================================================
+The LLVM Project is under the Apache License v2.0 with LLVM Exceptions:
+==============================================================================
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+---- LLVM Exceptions to the Apache 2.0 License ----
+
+As an exception, if, as a result of your compiling your source code, portions
+of this Software are embedded into an Object form of such source code, you
+may redistribute such embedded portions in such Object form without complying
+with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
+
+In addition, if you combine or link compiled forms of this Software with
+software that is licensed under the GPLv2 ("Combined Software") and if a
+court of competent jurisdiction determines that the patent provision (Section
+3), the indemnity provision (Section 9) or other Section of the License
+conflicts with the conditions of the GPLv2, you may retroactively and
+prospectively choose to deem waived or otherwise exclude such Section(s) of
+the License, but only in their entirety and only with respect to the Combined
+Software.
+
+==============================================================================
+Software from third parties included in the LLVM Project:
+==============================================================================
+The LLVM Project contains third party software which is under different license
+terms. All such code will be identified clearly using at least one of two
+mechanisms:
+1) It will be in a separate directory tree with its own `LICENSE.txt` or
+ `LICENSE` file at the top containing the specific license and restrictions
+ which apply to that software, or
+2) It will contain specific license and restriction terms at the top of every
+ file.
+
+==============================================================================
+Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy):
+==============================================================================
+University of Illinois/NCSA
+Open Source License
+
+Copyright (c) 2003-2019 University of Illinois at Urbana-Champaign.
+All rights reserved.
+
+Developed by:
+
+ LLVM Team
+
+ University of Illinois at Urbana-Champaign
+
+ http://llvm.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal with
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimers.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimers in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the names of the LLVM Team, University of Illinois at
+ Urbana-Champaign, nor the names of its contributors may be used to
+ endorse or promote products derived from this Software without specific
+ prior written permission.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
+SOFTWARE.
+
diff --git a/src/qdoc/qdoc/src/qdoc/clang/AST/QualTypeNames.h b/src/qdoc/qdoc/src/qdoc/clang/AST/QualTypeNames.h
new file mode 100644
index 000000000..c6d331ea8
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/clang/AST/QualTypeNames.h
@@ -0,0 +1,491 @@
+//===------- QualTypeNames.cpp - Generate Complete QualType Names ---------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+// Those directives indirectly includes "clang/AST/Attrs.h" which
+// includes "clang/AST/Attrs.inc".
+// "clang/AST/Attrs.inc", produces some "C4267" warnings specifically
+// on MSVC 2019.
+// This in turn blocks CI integrations for configuration with that
+// compiler that treats warnings as errors.
+// As that header is not under our control, we disable the warning
+// completely when going through those includes.
+#include <QtCore/qcompilerdetection.h>
+
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_MSVC(4267)
+
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/DeclarationName.h"
+#include "clang/AST/GlobalDecl.h"
+#include "clang/AST/Mangle.h"
+
+QT_WARNING_POP
+
+#include <stdio.h>
+#include <memory>
+
+namespace clang {
+
+namespace TypeName {
+
+inline QualType getFullyQualifiedType(QualType QT, const ASTContext &Ctx,
+ bool WithGlobalNsPrefix);
+
+/// Create a NestedNameSpecifier for Namesp and its enclosing
+/// scopes.
+///
+/// \param[in] Ctx - the AST Context to be used.
+/// \param[in] Namesp - the NamespaceDecl for which a NestedNameSpecifier
+/// is requested.
+/// \param[in] WithGlobalNsPrefix - Indicate whether the global namespace
+/// specifier "::" should be prepended or not.
+static inline NestedNameSpecifier *createNestedNameSpecifier(
+ const ASTContext &Ctx,
+ const NamespaceDecl *Namesp,
+ bool WithGlobalNsPrefix);
+
+/// Create a NestedNameSpecifier for TagDecl and its enclosing
+/// scopes.
+///
+/// \param[in] Ctx - the AST Context to be used.
+/// \param[in] TD - the TagDecl for which a NestedNameSpecifier is
+/// requested.
+/// \param[in] FullyQualify - Convert all template arguments into fully
+/// qualified names.
+/// \param[in] WithGlobalNsPrefix - Indicate whether the global namespace
+/// specifier "::" should be prepended or not.
+static inline NestedNameSpecifier *createNestedNameSpecifier(
+ const ASTContext &Ctx, const TypeDecl *TD,
+ bool FullyQualify, bool WithGlobalNsPrefix);
+
+static inline NestedNameSpecifier *createNestedNameSpecifierForScopeOf(
+ const ASTContext &Ctx, const Decl *decl,
+ bool FullyQualified, bool WithGlobalNsPrefix);
+
+static inline NestedNameSpecifier *getFullyQualifiedNestedNameSpecifier(
+ const ASTContext &Ctx, NestedNameSpecifier *scope, bool WithGlobalNsPrefix);
+
+static inline bool getFullyQualifiedTemplateName(const ASTContext &Ctx,
+ TemplateName &TName,
+ bool WithGlobalNsPrefix) {
+ bool Changed = false;
+ NestedNameSpecifier *NNS = nullptr;
+
+ TemplateDecl *ArgTDecl = TName.getAsTemplateDecl();
+ // ArgTDecl won't be NULL because we asserted that this isn't a
+ // dependent context very early in the call chain.
+ assert(ArgTDecl != nullptr);
+ QualifiedTemplateName *QTName = TName.getAsQualifiedTemplateName();
+
+ if (QTName && !QTName->hasTemplateKeyword()) {
+ NNS = QTName->getQualifier();
+ NestedNameSpecifier *QNNS = getFullyQualifiedNestedNameSpecifier(
+ Ctx, NNS, WithGlobalNsPrefix);
+ if (QNNS != NNS) {
+ Changed = true;
+ NNS = QNNS;
+ } else {
+ NNS = nullptr;
+ }
+ } else {
+ NNS = createNestedNameSpecifierForScopeOf(
+ Ctx, ArgTDecl, true, WithGlobalNsPrefix);
+ }
+ if (NNS) {
+ TemplateName UnderlyingTN(ArgTDecl);
+ if (UsingShadowDecl *USD = TName.getAsUsingShadowDecl())
+ UnderlyingTN = TemplateName(USD);
+ TName =
+ Ctx.getQualifiedTemplateName(NNS,
+ /*TemplateKeyword=*/false, UnderlyingTN);
+ Changed = true;
+ }
+ return Changed;
+}
+
+static inline bool getFullyQualifiedTemplateArgument(const ASTContext &Ctx,
+ TemplateArgument &Arg,
+ bool WithGlobalNsPrefix) {
+ bool Changed = false;
+
+ // Note: we do not handle TemplateArgument::Expression, to replace it
+ // we need the information for the template instance decl.
+
+ if (Arg.getKind() == TemplateArgument::Template) {
+ TemplateName TName = Arg.getAsTemplate();
+ Changed = getFullyQualifiedTemplateName(Ctx, TName, WithGlobalNsPrefix);
+ if (Changed) {
+ Arg = TemplateArgument(TName);
+ }
+ } else if (Arg.getKind() == TemplateArgument::Type) {
+ QualType SubTy = Arg.getAsType();
+ // Check if the type needs more desugaring and recurse.
+ QualType QTFQ = getFullyQualifiedType(SubTy, Ctx, WithGlobalNsPrefix);
+ if (QTFQ != SubTy) {
+ Arg = TemplateArgument(QTFQ);
+ Changed = true;
+ }
+ }
+ return Changed;
+}
+
+static inline const Type *getFullyQualifiedTemplateType(const ASTContext &Ctx,
+ const Type *TypePtr,
+ bool WithGlobalNsPrefix) {
+ // DependentTemplateTypes exist within template declarations and
+ // definitions. Therefore we shouldn't encounter them at the end of
+ // a translation unit. If we do, the caller has made an error.
+ assert(!isa<DependentTemplateSpecializationType>(TypePtr));
+ // In case of template specializations, iterate over the arguments
+ // and fully qualify them as well.
+ if (const auto *TST = dyn_cast<const TemplateSpecializationType>(TypePtr)) {
+ bool MightHaveChanged = false;
+ SmallVector<TemplateArgument, 4> FQArgs;
+ // Cheap to copy and potentially modified by
+ // getFullyQualifedTemplateArgument.
+ for (TemplateArgument Arg : TST->template_arguments()) {
+ MightHaveChanged |= getFullyQualifiedTemplateArgument(
+ Ctx, Arg, WithGlobalNsPrefix);
+ FQArgs.push_back(Arg);
+ }
+
+ // If a fully qualified arg is different from the unqualified arg,
+ // allocate new type in the AST.
+ if (MightHaveChanged) {
+ QualType QT = Ctx.getTemplateSpecializationType(
+ TST->getTemplateName(), FQArgs,
+ TST->getCanonicalTypeInternal());
+ // getTemplateSpecializationType returns a fully qualified
+ // version of the specialization itself, so no need to qualify
+ // it.
+ return QT.getTypePtr();
+ }
+ } else if (const auto *TSTRecord = dyn_cast<const RecordType>(TypePtr)) {
+ // We are asked to fully qualify and we have a Record Type,
+ // which can point to a template instantiation with no sugar in any of
+ // its template argument, however we still need to fully qualify them.
+
+ if (const auto *TSTDecl =
+ dyn_cast<ClassTemplateSpecializationDecl>(TSTRecord->getDecl())) {
+ const TemplateArgumentList &TemplateArgs = TSTDecl->getTemplateArgs();
+
+ bool MightHaveChanged = false;
+ SmallVector<TemplateArgument, 4> FQArgs;
+ for (unsigned int I = 0, E = TemplateArgs.size(); I != E; ++I) {
+ // cheap to copy and potentially modified by
+ // getFullyQualifedTemplateArgument
+ TemplateArgument Arg(TemplateArgs[I]);
+ MightHaveChanged |= getFullyQualifiedTemplateArgument(
+ Ctx, Arg, WithGlobalNsPrefix);
+ FQArgs.push_back(Arg);
+ }
+
+ // If a fully qualified arg is different from the unqualified arg,
+ // allocate new type in the AST.
+ if (MightHaveChanged) {
+ TemplateName TN(TSTDecl->getSpecializedTemplate());
+ QualType QT = Ctx.getTemplateSpecializationType(
+ TN, FQArgs,
+ TSTRecord->getCanonicalTypeInternal());
+ // getTemplateSpecializationType returns a fully qualified
+ // version of the specialization itself, so no need to qualify
+ // it.
+ return QT.getTypePtr();
+ }
+ }
+ }
+ return TypePtr;
+}
+
+static inline NestedNameSpecifier *createOuterNNS(const ASTContext &Ctx, const Decl *D,
+ bool FullyQualify,
+ bool WithGlobalNsPrefix) {
+ const DeclContext *DC = D->getDeclContext();
+ if (const auto *NS = dyn_cast<NamespaceDecl>(DC)) {
+ while (NS && NS->isInline()) {
+ // Ignore inline namespace;
+ NS = dyn_cast<NamespaceDecl>(NS->getDeclContext());
+ }
+ if (NS && NS->getDeclName()) {
+ return createNestedNameSpecifier(Ctx, NS, WithGlobalNsPrefix);
+ }
+ return nullptr; // no starting '::', no anonymous
+ } else if (const auto *TD = dyn_cast<TagDecl>(DC)) {
+ return createNestedNameSpecifier(Ctx, TD, FullyQualify, WithGlobalNsPrefix);
+ } else if (const auto *TDD = dyn_cast<TypedefNameDecl>(DC)) {
+ return createNestedNameSpecifier(
+ Ctx, TDD, FullyQualify, WithGlobalNsPrefix);
+ } else if (WithGlobalNsPrefix && DC->isTranslationUnit()) {
+ return NestedNameSpecifier::GlobalSpecifier(Ctx);
+ }
+ return nullptr; // no starting '::' if |WithGlobalNsPrefix| is false
+}
+
+/// Return a fully qualified version of this name specifier.
+static inline NestedNameSpecifier *getFullyQualifiedNestedNameSpecifier(
+ const ASTContext &Ctx, NestedNameSpecifier *Scope,
+ bool WithGlobalNsPrefix) {
+ switch (Scope->getKind()) {
+ case NestedNameSpecifier::Global:
+ // Already fully qualified
+ return Scope;
+ case NestedNameSpecifier::Namespace:
+ return TypeName::createNestedNameSpecifier(
+ Ctx, Scope->getAsNamespace(), WithGlobalNsPrefix);
+ case NestedNameSpecifier::NamespaceAlias:
+ // Namespace aliases are only valid for the duration of the
+ // scope where they were introduced, and therefore are often
+ // invalid at the end of the TU. So use the namespace name more
+ // likely to be valid at the end of the TU.
+ return TypeName::createNestedNameSpecifier(
+ Ctx,
+ Scope->getAsNamespaceAlias()->getNamespace()->getCanonicalDecl(),
+ WithGlobalNsPrefix);
+ case NestedNameSpecifier::Identifier:
+ // A function or some other construct that makes it un-namable
+ // at the end of the TU. Skip the current component of the name,
+ // but use the name of it's prefix.
+ return getFullyQualifiedNestedNameSpecifier(
+ Ctx, Scope->getPrefix(), WithGlobalNsPrefix);
+ case NestedNameSpecifier::Super:
+ case NestedNameSpecifier::TypeSpec:
+ case NestedNameSpecifier::TypeSpecWithTemplate: {
+ const Type *Type = Scope->getAsType();
+ // Find decl context.
+ const TagDecl *TD = nullptr;
+ if (const TagType *TagDeclType = Type->getAs<TagType>()) {
+ TD = TagDeclType->getDecl();
+ } else {
+ TD = Type->getAsCXXRecordDecl();
+ }
+ if (TD) {
+ return TypeName::createNestedNameSpecifier(Ctx, TD,
+ true /*FullyQualified*/,
+ WithGlobalNsPrefix);
+ } else if (const auto *TDD = dyn_cast<TypedefType>(Type)) {
+ return TypeName::createNestedNameSpecifier(Ctx, TDD->getDecl(),
+ true /*FullyQualified*/,
+ WithGlobalNsPrefix);
+ }
+ return Scope;
+ }
+ }
+ llvm_unreachable("bad NNS kind");
+}
+
+/// Create a nested name specifier for the declaring context of
+/// the type.
+static inline NestedNameSpecifier *createNestedNameSpecifierForScopeOf(
+ const ASTContext &Ctx, const Decl *Decl,
+ bool FullyQualified, bool WithGlobalNsPrefix) {
+ assert(Decl);
+
+ const DeclContext *DC = Decl->getDeclContext()->getRedeclContext();
+ const auto *Outer = dyn_cast_or_null<NamedDecl>(DC);
+ const auto *OuterNS = dyn_cast_or_null<NamespaceDecl>(DC);
+ if (Outer && !(OuterNS && OuterNS->isAnonymousNamespace())) {
+ if (OuterNS) {
+ return createNestedNameSpecifier(Ctx, OuterNS, WithGlobalNsPrefix);
+ } else if (const auto *TD = dyn_cast<TagDecl>(Outer)) {
+ return createNestedNameSpecifier(
+ Ctx, TD, FullyQualified, WithGlobalNsPrefix);
+ } else if (isa<TranslationUnitDecl>(Outer)) {
+ // Context is the TU. Nothing needs to be done.
+ return nullptr;
+ } else {
+ // Decl's context was neither the TU, a namespace, nor a
+ // TagDecl, which means it is a type local to a scope, and not
+ // accessible at the end of the TU.
+ return nullptr;
+ }
+ } else if (WithGlobalNsPrefix && DC->isTranslationUnit()) {
+ return NestedNameSpecifier::GlobalSpecifier(Ctx);
+ }
+ return nullptr;
+}
+
+/// Create a nested name specifier for the declaring context of
+/// the type.
+static inline NestedNameSpecifier *createNestedNameSpecifierForScopeOf(
+ const ASTContext &Ctx, const Type *TypePtr,
+ bool FullyQualified, bool WithGlobalNsPrefix) {
+ if (!TypePtr) return nullptr;
+
+ Decl *Decl = nullptr;
+ // There are probably other cases ...
+ if (const auto *TDT = dyn_cast<TypedefType>(TypePtr)) {
+ Decl = TDT->getDecl();
+ } else if (const auto *TagDeclType = dyn_cast<TagType>(TypePtr)) {
+ Decl = TagDeclType->getDecl();
+ } else if (const auto *TST = dyn_cast<TemplateSpecializationType>(TypePtr)) {
+ Decl = TST->getTemplateName().getAsTemplateDecl();
+ } else {
+ Decl = TypePtr->getAsCXXRecordDecl();
+ }
+
+ if (!Decl) return nullptr;
+
+ return createNestedNameSpecifierForScopeOf(
+ Ctx, Decl, FullyQualified, WithGlobalNsPrefix);
+}
+
+inline NestedNameSpecifier *createNestedNameSpecifier(const ASTContext &Ctx,
+ const NamespaceDecl *Namespace,
+ bool WithGlobalNsPrefix) {
+ while (Namespace && Namespace->isInline()) {
+ // Ignore inline namespace;
+ Namespace = dyn_cast<NamespaceDecl>(Namespace->getDeclContext());
+ }
+ if (!Namespace) return nullptr;
+
+ bool FullyQualified = true; // doesn't matter, DeclContexts are namespaces
+ return NestedNameSpecifier::Create(
+ Ctx,
+ createOuterNNS(Ctx, Namespace, FullyQualified, WithGlobalNsPrefix),
+ Namespace);
+}
+
+inline NestedNameSpecifier *createNestedNameSpecifier(const ASTContext &Ctx,
+ const TypeDecl *TD,
+ bool FullyQualify,
+ bool WithGlobalNsPrefix) {
+ const Type *TypePtr = TD->getTypeForDecl();
+ if (isa<const TemplateSpecializationType>(TypePtr) ||
+ isa<const RecordType>(TypePtr)) {
+ // We are asked to fully qualify and we have a Record Type (which
+ // may point to a template specialization) or Template
+ // Specialization Type. We need to fully qualify their arguments.
+
+ TypePtr = getFullyQualifiedTemplateType(Ctx, TypePtr, WithGlobalNsPrefix);
+ }
+
+ return NestedNameSpecifier::Create(
+ Ctx, createOuterNNS(Ctx, TD, FullyQualify, WithGlobalNsPrefix),
+ false /*No TemplateKeyword*/, TypePtr);
+}
+
+/// Return the fully qualified type, including fully-qualified
+/// versions of any template parameters.
+inline QualType getFullyQualifiedType(QualType QT, const ASTContext &Ctx,
+ bool WithGlobalNsPrefix = false) {
+ // In case of myType* we need to strip the pointer first, fully
+ // qualify and attach the pointer once again.
+ if (isa<PointerType>(QT.getTypePtr())) {
+ // Get the qualifiers.
+ Qualifiers Quals = QT.getQualifiers();
+ QT = getFullyQualifiedType(QT->getPointeeType(), Ctx, WithGlobalNsPrefix);
+ QT = Ctx.getPointerType(QT);
+ // Add back the qualifiers.
+ QT = Ctx.getQualifiedType(QT, Quals);
+ return QT;
+ }
+
+ if (auto *MPT = dyn_cast<MemberPointerType>(QT.getTypePtr())) {
+ // Get the qualifiers.
+ Qualifiers Quals = QT.getQualifiers();
+ // Fully qualify the pointee and class types.
+ QT = getFullyQualifiedType(QT->getPointeeType(), Ctx, WithGlobalNsPrefix);
+ QualType Class = getFullyQualifiedType(QualType(MPT->getClass(), 0), Ctx,
+ WithGlobalNsPrefix);
+ QT = Ctx.getMemberPointerType(QT, Class.getTypePtr());
+ // Add back the qualifiers.
+ QT = Ctx.getQualifiedType(QT, Quals);
+ return QT;
+ }
+
+ // In case of myType& we need to strip the reference first, fully
+ // qualify and attach the reference once again.
+ if (isa<ReferenceType>(QT.getTypePtr())) {
+ // Get the qualifiers.
+ bool IsLValueRefTy = isa<LValueReferenceType>(QT.getTypePtr());
+ Qualifiers Quals = QT.getQualifiers();
+ QT = getFullyQualifiedType(QT->getPointeeType(), Ctx, WithGlobalNsPrefix);
+ // Add the r- or l-value reference type back to the fully
+ // qualified one.
+ if (IsLValueRefTy)
+ QT = Ctx.getLValueReferenceType(QT);
+ else
+ QT = Ctx.getRValueReferenceType(QT);
+ // Add back the qualifiers.
+ QT = Ctx.getQualifiedType(QT, Quals);
+ return QT;
+ }
+
+ // Remove the part of the type related to the type being a template
+ // parameter (we won't report it as part of the 'type name' and it
+ // is actually make the code below to be more complex (to handle
+ // those)
+ while (isa<SubstTemplateTypeParmType>(QT.getTypePtr())) {
+ // Get the qualifiers.
+ Qualifiers Quals = QT.getQualifiers();
+
+ QT = cast<SubstTemplateTypeParmType>(QT.getTypePtr())->desugar();
+
+ // Add back the qualifiers.
+ QT = Ctx.getQualifiedType(QT, Quals);
+ }
+
+ NestedNameSpecifier *Prefix = nullptr;
+ // Local qualifiers are attached to the QualType outside of the
+ // elaborated type. Retrieve them before descending into the
+ // elaborated type.
+ Qualifiers PrefixQualifiers = QT.getLocalQualifiers();
+ QT = QualType(QT.getTypePtr(), 0);
+#if LIBCLANG_VERSION_MAJOR >= 18
+ constexpr ElaboratedTypeKeyword ETK_None = ElaboratedTypeKeyword::None;
+#endif
+ ElaboratedTypeKeyword Keyword = ETK_None;
+ if (const auto *ETypeInput = dyn_cast<ElaboratedType>(QT.getTypePtr())) {
+ QT = ETypeInput->getNamedType();
+ assert(!QT.hasLocalQualifiers());
+ Keyword = ETypeInput->getKeyword();
+ }
+
+ // We don't consider the alias introduced by `using a::X` as a new type.
+ // The qualified name is still a::X.
+ if (const auto *UT = QT->getAs<UsingType>()) {
+ QT = Ctx.getQualifiedType(UT->getUnderlyingType(), PrefixQualifiers);
+ return getFullyQualifiedType(QT, Ctx, WithGlobalNsPrefix);
+ }
+
+ // Create a nested name specifier if needed.
+ Prefix = createNestedNameSpecifierForScopeOf(Ctx, QT.getTypePtr(),
+ true /*FullyQualified*/,
+ WithGlobalNsPrefix);
+
+ // In case of template specializations iterate over the arguments and
+ // fully qualify them as well.
+ if (isa<const TemplateSpecializationType>(QT.getTypePtr()) ||
+ isa<const RecordType>(QT.getTypePtr())) {
+ // We are asked to fully qualify and we have a Record Type (which
+ // may point to a template specialization) or Template
+ // Specialization Type. We need to fully qualify their arguments.
+
+ const Type *TypePtr = getFullyQualifiedTemplateType(
+ Ctx, QT.getTypePtr(), WithGlobalNsPrefix);
+ QT = QualType(TypePtr, 0);
+ }
+ if (Prefix || Keyword != ETK_None) {
+ QT = Ctx.getElaboratedType(Keyword, Prefix, QT);
+ }
+ QT = Ctx.getQualifiedType(QT, PrefixQualifiers);
+ return QT;
+}
+
+inline std::string getFullyQualifiedName(QualType QT,
+ const ASTContext &Ctx,
+ const PrintingPolicy &Policy,
+ bool WithGlobalNsPrefix = false) {
+ QualType FQQT = getFullyQualifiedType(QT, Ctx, WithGlobalNsPrefix);
+ return FQQT.getAsString(Policy);
+}
+
+} // end namespace TypeName
+} // end namespace clang
diff --git a/src/qdoc/qdoc/src/qdoc/clang/AST/qt_attribution.json b/src/qdoc/qdoc/src/qdoc/clang/AST/qt_attribution.json
new file mode 100644
index 000000000..13219767d
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/clang/AST/qt_attribution.json
@@ -0,0 +1,20 @@
+[
+ {
+ "Id": "llvm_clang_typename_namespace",
+ "Name": "QualTypeNames",
+ "QDocModule": "qdoc",
+ "QtUsage": "Used to have access to a version of clang::TypeName::getFullyQualifiedName that does not expand templates to an instance.",
+ "QtParts": [
+ "tools"
+ ],
+ "Files": "QualTypeNames.h",
+
+ "Description": "A part of Clang's C++ API that deals with fully qualifying types.",
+ "Homepage": "https://github.com/llvm/llvm-project",
+ "Version": "16.0",
+ "License": "Apache License 2.0",
+ "LicenseId": "Apache-2.0 WITH LLVM-exception",
+ "LicenseFile": "LLVM_LICENSE.txt",
+ "Copyright": "Copyright assigned to LLVM project contributors."
+ }
+]
diff --git a/src/qdoc/qdoc/src/qdoc/clangcodeparser.cpp b/src/qdoc/qdoc/src/qdoc/clangcodeparser.cpp
new file mode 100644
index 000000000..a414b55a3
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/clangcodeparser.cpp
@@ -0,0 +1,1904 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "clangcodeparser.h"
+#include "cppcodeparser.h"
+
+#include "access.h"
+#include "classnode.h"
+#include "codechunk.h"
+#include "config.h"
+#include "enumnode.h"
+#include "functionnode.h"
+#include "namespacenode.h"
+#include "propertynode.h"
+#include "qdocdatabase.h"
+#include "typedefnode.h"
+#include "variablenode.h"
+#include "utilities.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qelapsedtimer.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qscopedvaluerollback.h>
+#include <QtCore/qtemporarydir.h>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qvarlengtharray.h>
+
+#include <clang-c/Index.h>
+
+#include <clang/AST/Decl.h>
+#include <clang/AST/DeclFriend.h>
+#include <clang/AST/DeclTemplate.h>
+#include <clang/AST/Expr.h>
+#include <clang/AST/Type.h>
+#include <clang/AST/TypeLoc.h>
+#include <clang/Basic/SourceLocation.h>
+#include <clang/Frontend/ASTUnit.h>
+#include <clang/Lex/Lexer.h>
+#include <llvm/Support/Casting.h>
+
+#include "clang/AST/QualTypeNames.h"
+#include "template_declaration.h"
+
+#include <cstdio>
+
+QT_BEGIN_NAMESPACE
+
+struct CompilationIndex {
+ CXIndex index = nullptr;
+
+ operator CXIndex() {
+ return index;
+ }
+
+ ~CompilationIndex() {
+ clang_disposeIndex(index);
+ }
+};
+
+struct TranslationUnit {
+ CXTranslationUnit tu = nullptr;
+
+ operator CXTranslationUnit() {
+ return tu;
+ }
+
+ operator bool() {
+ return tu;
+ }
+
+ ~TranslationUnit() {
+ clang_disposeTranslationUnit(tu);
+ }
+};
+
+// We're printing diagnostics in ClangCodeParser::printDiagnostics,
+// so avoid clang itself printing them.
+static const auto kClangDontDisplayDiagnostics = 0;
+
+static CXTranslationUnit_Flags flags_ = static_cast<CXTranslationUnit_Flags>(0);
+
+constexpr const char fnDummyFileName[] = "/fn_dummyfile.cpp";
+
+#ifndef QT_NO_DEBUG_STREAM
+template<class T>
+static QDebug operator<<(QDebug debug, const std::vector<T> &v)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote();
+ debug.nospace();
+ const size_t size = v.size();
+ debug << "std::vector<>[" << size << "](";
+ for (size_t i = 0; i < size; ++i) {
+ if (i)
+ debug << ", ";
+ debug << v[i];
+ }
+ debug << ')';
+ return debug;
+}
+#endif // !QT_NO_DEBUG_STREAM
+
+static void printDiagnostics(const CXTranslationUnit &translationUnit)
+{
+ if (!lcQdocClang().isDebugEnabled())
+ return;
+
+ static const auto displayOptions = CXDiagnosticDisplayOptions::CXDiagnostic_DisplaySourceLocation
+ | CXDiagnosticDisplayOptions::CXDiagnostic_DisplayColumn
+ | CXDiagnosticDisplayOptions::CXDiagnostic_DisplayOption;
+
+ for (unsigned i = 0, numDiagnostics = clang_getNumDiagnostics(translationUnit); i < numDiagnostics; ++i) {
+ auto diagnostic = clang_getDiagnostic(translationUnit, i);
+ auto formattedDiagnostic = clang_formatDiagnostic(diagnostic, displayOptions);
+ qCDebug(lcQdocClang) << clang_getCString(formattedDiagnostic);
+ clang_disposeString(formattedDiagnostic);
+ clang_disposeDiagnostic(diagnostic);
+ }
+}
+
+/*!
+ * Returns the underlying Decl that \a cursor represents.
+ *
+ * This can be used to drop back down from a LibClang's CXCursor to
+ * the underlying C++ AST that Clang provides.
+ *
+ * It should be used when LibClang does not expose certain
+ * functionalities that are available in the C++ AST.
+ *
+ * The CXCursor should represent a declaration. Usages of this
+ * function on CXCursors that do not represent a declaration may
+ * produce undefined results.
+ */
+static const clang::Decl* get_cursor_declaration(CXCursor cursor) {
+ assert(clang_isDeclaration(clang_getCursorKind(cursor)));
+
+ return static_cast<const clang::Decl*>(cursor.data[0]);
+}
+
+
+/*!
+ * Returns a string representing the name of \a type as if it was
+ * referred to at the end of the translation unit that it was parsed
+ * from.
+ *
+ * For example, given the following code:
+ *
+ * \code
+ * namespace foo {
+ * template<typename T>
+ * struct Bar {
+ * using Baz = const T&;
+ *
+ * void bam(Baz);
+ * };
+ * }
+ * \endcode
+ *
+ * Given a parsed translation unit and an AST node, say \e {decl},
+ * representing the parameter declaration of the first argument of \c {bam},
+ * calling \c{get_fully_qualified_name(decl->getType(), * decl->getASTContext())}
+ * would result in the string \c {foo::Bar<T>::Baz}.
+ *
+ * This should generally be used every time the stringified
+ * representation of a type is acquired as part of parsing with Clang,
+ * so as to ensure a consistent behavior and output.
+ */
+static std::string get_fully_qualified_type_name(clang::QualType type, const clang::ASTContext& declaration_context) {
+ return clang::TypeName::getFullyQualifiedName(
+ type,
+ declaration_context,
+ declaration_context.getPrintingPolicy()
+ );
+}
+
+/*
+ * Retrieves expression as written in the original source code.
+ *
+ * declaration_context should be the ASTContext of the declaration
+ * from which the expression was extracted from.
+ *
+ * If the expression contains a leading equal sign it will be removed.
+ *
+ * Leading and trailing spaces will be similarly removed from the expression.
+ */
+static std::string get_expression_as_string(const clang::Expr* expression, const clang::ASTContext& declaration_context) {
+ QString default_value = QString::fromStdString(clang::Lexer::getSourceText(
+ clang::CharSourceRange::getTokenRange(expression->getSourceRange()),
+ declaration_context.getSourceManager(),
+ declaration_context.getLangOpts()
+ ).str());
+
+ if (default_value.startsWith("="))
+ default_value.remove(0, 1);
+
+ default_value = default_value.trimmed();
+
+ return default_value.toStdString();
+}
+
+/*
+ * Retrieves the default value of the passed in type template parameter as a string.
+ *
+ * The default value of a type template parameter is always a type,
+ * and its stringified representation will be return as the fully
+ * qualified version of the type.
+ *
+ * If the parameter has no default value the empty string will be returned.
+ */
+static std::string get_default_value_initializer_as_string(const clang::TemplateTypeParmDecl* parameter) {
+ return (parameter && parameter->hasDefaultArgument()) ?
+ get_fully_qualified_type_name(parameter->getDefaultArgument(), parameter->getASTContext()) :
+ "";
+
+}
+
+/*
+ * Retrieves the default value of the passed in non-type template parameter as a string.
+ *
+ * The default value of a non-type template parameter is an expression
+ * and its stringified representation will be return as it was written
+ * in the original code.
+ *
+ * If the parameter as no default value the empty string will be returned.
+ */
+static std::string get_default_value_initializer_as_string(const clang::NonTypeTemplateParmDecl* parameter) {
+ return (parameter && parameter->hasDefaultArgument()) ?
+ get_expression_as_string(parameter->getDefaultArgument(), parameter->getASTContext()) : "";
+
+}
+
+/*
+ * Retrieves the default value of the passed in template template parameter as a string.
+ *
+ * The default value of a template template parameter is a template
+ * name and its stringified representation will be returned as a fully
+ * qualified version of that name.
+ *
+ * If the parameter as no default value the empty string will be returned.
+ */
+static std::string get_default_value_initializer_as_string(const clang::TemplateTemplateParmDecl* parameter) {
+ std::string default_value{};
+
+ if (parameter && parameter->hasDefaultArgument()) {
+ const clang::TemplateName template_name = parameter->getDefaultArgument().getArgument().getAsTemplate();
+
+ llvm::raw_string_ostream ss{default_value};
+ template_name.print(ss, parameter->getASTContext().getPrintingPolicy(), clang::TemplateName::Qualified::Fully);
+ }
+
+ return default_value;
+}
+
+/*
+ * Retrieves the default value of the passed in function parameter as
+ * a string.
+ *
+ * The default value of a function parameter is an expression and its
+ * stringified representation will be returned as it was written in
+ * the original code.
+ *
+ * If the parameter as no default value or Clang was not able to yet
+ * parse it at this time the empty string will be returned.
+ */
+static std::string get_default_value_initializer_as_string(const clang::ParmVarDecl* parameter) {
+ if (!parameter || !parameter->hasDefaultArg() || parameter->hasUnparsedDefaultArg())
+ return "";
+
+ return get_expression_as_string(
+ parameter->hasUninstantiatedDefaultArg() ? parameter->getUninstantiatedDefaultArg() : parameter->getDefaultArg(),
+ parameter->getASTContext()
+ );
+}
+
+/*
+ * Retrieves the default value of the passed in declaration, based on
+ * its concrete type, as a string.
+ *
+ * If the declaration is a nullptr or the concrete type of the
+ * declaration is not a supported one, the returned string will be the
+ * empty string.
+ */
+static std::string get_default_value_initializer_as_string(const clang::NamedDecl* declaration) {
+ if (!declaration) return "";
+
+ if (auto type_template_parameter = llvm::dyn_cast<clang::TemplateTypeParmDecl>(declaration))
+ return get_default_value_initializer_as_string(type_template_parameter);
+
+ if (auto non_type_template_parameter = llvm::dyn_cast<clang::NonTypeTemplateParmDecl>(declaration))
+ return get_default_value_initializer_as_string(non_type_template_parameter);
+
+ if (auto template_template_parameter = llvm::dyn_cast<clang::TemplateTemplateParmDecl>(declaration)) {
+ return get_default_value_initializer_as_string(template_template_parameter);
+ }
+
+ if (auto function_parameter = llvm::dyn_cast<clang::ParmVarDecl>(declaration)) {
+ return get_default_value_initializer_as_string(function_parameter);
+ }
+
+ return "";
+}
+
+/*!
+ Call clang_visitChildren on the given cursor with the lambda as a callback
+ T can be any functor that is callable with a CXCursor parameter and returns a CXChildVisitResult
+ (in other word compatible with function<CXChildVisitResult(CXCursor)>
+ */
+template<typename T>
+bool visitChildrenLambda(CXCursor cursor, T &&lambda)
+{
+ CXCursorVisitor visitor = [](CXCursor c, CXCursor,
+ CXClientData client_data) -> CXChildVisitResult {
+ return (*static_cast<T *>(client_data))(c);
+ };
+ return clang_visitChildren(cursor, visitor, &lambda);
+}
+
+/*!
+ convert a CXString to a QString, and dispose the CXString
+ */
+static QString fromCXString(CXString &&string)
+{
+ QString ret = QString::fromUtf8(clang_getCString(string));
+ clang_disposeString(string);
+ return ret;
+}
+
+/*
+ * Returns an intermediate representation that models the the given
+ * template declaration.
+ */
+static RelaxedTemplateDeclaration get_template_declaration(const clang::TemplateDecl* template_declaration) {
+ assert(template_declaration);
+
+ RelaxedTemplateDeclaration template_declaration_ir{};
+
+ auto template_parameters = template_declaration->getTemplateParameters();
+ for (auto template_parameter : template_parameters->asArray()) {
+ auto kind{RelaxedTemplateParameter::Kind::TypeTemplateParameter};
+ std::string type{};
+
+ if (auto non_type_template_parameter = llvm::dyn_cast<clang::NonTypeTemplateParmDecl>(template_parameter)) {
+ kind = RelaxedTemplateParameter::Kind::NonTypeTemplateParameter;
+ type = get_fully_qualified_type_name(non_type_template_parameter->getType(), non_type_template_parameter->getASTContext());
+
+ // REMARK: QDoc uses this information to match a user
+ // provided documentation (for example from an "\fn"
+ // command) with a `Node` that was extracted from the
+ // code-base.
+ //
+ // Due to how QDoc obtains an AST for documentation that
+ // is provided by the user, there might be a mismatch in
+ // the type of certain non type template parameters.
+ //
+ // QDoc generally builds a fake out-of-line definition for
+ // a callable provided through an "\fn" command, when it
+ // needs to match it.
+ // In that context, certain type names may be dependent
+ // names, while they may not be when the element they
+ // represent is extracted from the code-base.
+ //
+ // This in turn makes their stringified representation
+ // different in the two contextes, as a dependent name may
+ // require the "typename" keyword to precede it.
+ //
+ // Since QDoc uses a very simplified model, and it
+ // generally doesn't need care about the exact name
+ // resolution rules for C++, since it passes by
+ // Clang-validated data, we remove the "typename" keyword
+ // if it prefixes the type representation, so that it
+ // doesn't impact the matching procedure..
+
+ // KLUDGE: Waiting for C++20 to avoid the conversion.
+ // Doesn't really impact performance in a
+ // meaningful way so it can be kept while waiting.
+ if (QString::fromStdString(type).startsWith("typename ")) type.erase(0, std::string("typename ").size());
+ }
+
+ auto template_template_parameter = llvm::dyn_cast<clang::TemplateTemplateParmDecl>(template_parameter);
+ if (template_template_parameter) kind = RelaxedTemplateParameter::Kind::TemplateTemplateParameter;
+
+ template_declaration_ir.parameters.push_back({
+ kind,
+ template_parameter->isTemplateParameterPack(),
+ {
+ type,
+ template_parameter->getNameAsString(),
+ get_default_value_initializer_as_string(template_parameter)
+ },
+ (template_template_parameter ?
+ std::optional<TemplateDeclarationStorage>(TemplateDeclarationStorage{
+ get_template_declaration(template_template_parameter).parameters
+ }) : std::nullopt)
+ });
+ }
+
+ return template_declaration_ir;
+}
+
+/*!
+ convert a CXSourceLocation to a qdoc Location
+ */
+static Location fromCXSourceLocation(CXSourceLocation location)
+{
+ unsigned int line, column;
+ CXString file;
+ clang_getPresumedLocation(location, &file, &line, &column);
+ Location l(fromCXString(std::move(file)));
+ l.setColumnNo(column);
+ l.setLineNo(line);
+ return l;
+}
+
+/*!
+ convert a CX_CXXAccessSpecifier to Node::Access
+ */
+static Access fromCX_CXXAccessSpecifier(CX_CXXAccessSpecifier spec)
+{
+ switch (spec) {
+ case CX_CXXPrivate:
+ return Access::Private;
+ case CX_CXXProtected:
+ return Access::Protected;
+ case CX_CXXPublic:
+ return Access::Public;
+ default:
+ return Access::Public;
+ }
+}
+
+/*!
+ Returns the spelling in the file for a source range
+ */
+
+struct FileCacheEntry
+{
+ QByteArray fileName;
+ QByteArray content;
+};
+
+static inline QString fromCache(const QByteArray &cache,
+ unsigned int offset1, unsigned int offset2)
+{
+ return QString::fromUtf8(cache.mid(offset1, offset2 - offset1));
+}
+
+static QString readFile(CXFile cxFile, unsigned int offset1, unsigned int offset2)
+{
+ using FileCache = QList<FileCacheEntry>;
+ static FileCache cache;
+
+ CXString cxFileName = clang_getFileName(cxFile);
+ const QByteArray fileName = clang_getCString(cxFileName);
+ clang_disposeString(cxFileName);
+
+ for (const auto &entry : std::as_const(cache)) {
+ if (fileName == entry.fileName)
+ return fromCache(entry.content, offset1, offset2);
+ }
+
+ QFile file(QString::fromUtf8(fileName));
+ if (file.open(QIODeviceBase::ReadOnly)) { // binary to match clang offsets
+ FileCacheEntry entry{fileName, file.readAll()};
+ cache.prepend(entry);
+ while (cache.size() > 5)
+ cache.removeLast();
+ return fromCache(entry.content, offset1, offset2);
+ }
+ return {};
+}
+
+static QString getSpelling(CXSourceRange range)
+{
+ auto start = clang_getRangeStart(range);
+ auto end = clang_getRangeEnd(range);
+ CXFile file1, file2;
+ unsigned int offset1, offset2;
+ clang_getFileLocation(start, &file1, nullptr, nullptr, &offset1);
+ clang_getFileLocation(end, &file2, nullptr, nullptr, &offset2);
+
+ if (file1 != file2 || offset2 <= offset1)
+ return QString();
+
+ return readFile(file1, offset1, offset2);
+}
+
+/*!
+ Returns the function name from a given cursor representing a
+ function declaration. This is usually clang_getCursorSpelling, but
+ not for the conversion function in which case it is a bit more complicated
+ */
+QString functionName(CXCursor cursor)
+{
+ if (clang_getCursorKind(cursor) == CXCursor_ConversionFunction) {
+ // For a CXCursor_ConversionFunction we don't want the spelling which would be something
+ // like "operator type-parameter-0-0" or "operator unsigned int". we want the actual name as
+ // spelled;
+ auto conversion_declaration =
+ static_cast<const clang::CXXConversionDecl*>(get_cursor_declaration(cursor));
+
+ return QLatin1String("operator ") + QString::fromStdString(get_fully_qualified_type_name(
+ conversion_declaration->getConversionType(),
+ conversion_declaration->getASTContext()
+ ));
+ }
+
+ QString name = fromCXString(clang_getCursorSpelling(cursor));
+
+ // Remove template stuff from constructor and destructor but not from operator<
+ auto ltLoc = name.indexOf('<');
+ if (ltLoc > 0 && !name.startsWith("operator<"))
+ name = name.left(ltLoc);
+ return name;
+}
+
+/*!
+ Reconstruct the qualified path name of a function that is
+ being overridden.
+ */
+static QString reconstructQualifiedPathForCursor(CXCursor cur)
+{
+ QString path;
+ auto kind = clang_getCursorKind(cur);
+ while (!clang_isInvalid(kind) && kind != CXCursor_TranslationUnit) {
+ switch (kind) {
+ case CXCursor_Namespace:
+ case CXCursor_StructDecl:
+ case CXCursor_ClassDecl:
+ case CXCursor_UnionDecl:
+ case CXCursor_ClassTemplate:
+ path.prepend("::");
+ path.prepend(fromCXString(clang_getCursorSpelling(cur)));
+ break;
+ case CXCursor_FunctionDecl:
+ case CXCursor_FunctionTemplate:
+ case CXCursor_CXXMethod:
+ case CXCursor_Constructor:
+ case CXCursor_Destructor:
+ case CXCursor_ConversionFunction:
+ path = functionName(cur);
+ break;
+ default:
+ break;
+ }
+ cur = clang_getCursorSemanticParent(cur);
+ kind = clang_getCursorKind(cur);
+ }
+ return path;
+}
+
+/*!
+ Find the node from the QDocDatabase \a qdb that corrseponds to the declaration
+ represented by the cursor \a cur, if it exists.
+ */
+static Node *findNodeForCursor(QDocDatabase *qdb, CXCursor cur)
+{
+ auto kind = clang_getCursorKind(cur);
+ if (clang_isInvalid(kind))
+ return nullptr;
+ if (kind == CXCursor_TranslationUnit)
+ return qdb->primaryTreeRoot();
+
+ Node *p = findNodeForCursor(qdb, clang_getCursorSemanticParent(cur));
+ if (p == nullptr)
+ return nullptr;
+ if (!p->isAggregate())
+ return nullptr;
+ auto parent = static_cast<Aggregate *>(p);
+
+ QString name = fromCXString(clang_getCursorSpelling(cur));
+ switch (kind) {
+ case CXCursor_Namespace:
+ return parent->findNonfunctionChild(name, &Node::isNamespace);
+ case CXCursor_StructDecl:
+ case CXCursor_ClassDecl:
+ case CXCursor_UnionDecl:
+ case CXCursor_ClassTemplate:
+ return parent->findNonfunctionChild(name, &Node::isClassNode);
+ case CXCursor_FunctionDecl:
+ case CXCursor_FunctionTemplate:
+ case CXCursor_CXXMethod:
+ case CXCursor_Constructor:
+ case CXCursor_Destructor:
+ case CXCursor_ConversionFunction: {
+ NodeVector candidates;
+ parent->findChildren(functionName(cur), candidates);
+ if (candidates.isEmpty())
+ return nullptr;
+
+ CXType funcType = clang_getCursorType(cur);
+ auto numArg = clang_getNumArgTypes(funcType);
+ bool isVariadic = clang_isFunctionTypeVariadic(funcType);
+ QVarLengthArray<QString, 20> args;
+
+ std::optional<RelaxedTemplateDeclaration> relaxed_template_declaration{std::nullopt};
+ if (kind == CXCursor_FunctionTemplate)
+ relaxed_template_declaration = get_template_declaration(
+ get_cursor_declaration(cur)->getAsFunction()->getDescribedFunctionTemplate()
+ );
+
+ for (Node *candidate : std::as_const(candidates)) {
+ if (!candidate->isFunction(Node::CPP))
+ continue;
+
+ auto fn = static_cast<FunctionNode *>(candidate);
+
+ if (!fn->templateDecl() && relaxed_template_declaration)
+ continue;
+
+ if (fn->templateDecl() && !relaxed_template_declaration)
+ continue;
+
+ if (fn->templateDecl() && relaxed_template_declaration &&
+ !are_template_declarations_substitutable(*fn->templateDecl(), *relaxed_template_declaration))
+ continue;
+
+ const Parameters &parameters = fn->parameters();
+
+ if (parameters.count() != numArg + isVariadic)
+ continue;
+
+ if (fn->isConst() != bool(clang_CXXMethod_isConst(cur)))
+ continue;
+
+ if (isVariadic && parameters.last().type() != QLatin1String("..."))
+ continue;
+
+ if (fn->isRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_LValue))
+ continue;
+
+ if (fn->isRefRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_RValue))
+ continue;
+
+ auto function_declaration = get_cursor_declaration(cur)->getAsFunction();
+
+ bool different = false;
+ for (int i = 0; i < numArg; ++i) {
+ CXType argType = clang_getArgType(funcType, i);
+
+ if (args.size() <= i)
+ args.append(QString::fromStdString(get_fully_qualified_type_name(
+ function_declaration->getParamDecl(i)->getOriginalType(),
+ function_declaration->getASTContext()
+ )));
+
+ QString recordedType = parameters.at(i).type();
+ QString typeSpelling = args.at(i);
+
+ different = recordedType != typeSpelling;
+
+ // Retry with a canonical type spelling
+ if (different && (argType.kind == CXType_Typedef || argType.kind == CXType_Elaborated)) {
+ QStringView canonicalType = parameters.at(i).canonicalType();
+ if (!canonicalType.isEmpty()) {
+ different = canonicalType !=
+ QString::fromStdString(get_fully_qualified_type_name(
+ function_declaration->getParamDecl(i)->getOriginalType().getCanonicalType(),
+ function_declaration->getASTContext()
+ ));
+ }
+ }
+
+ if (different) {
+ break;
+ }
+ }
+
+ if (!different)
+ return fn;
+ }
+ return nullptr;
+ }
+ case CXCursor_EnumDecl:
+ return parent->findNonfunctionChild(name, &Node::isEnumType);
+ case CXCursor_FieldDecl:
+ case CXCursor_VarDecl:
+ return parent->findNonfunctionChild(name, &Node::isVariable);
+ case CXCursor_TypedefDecl:
+ return parent->findNonfunctionChild(name, &Node::isTypedef);
+ default:
+ return nullptr;
+ }
+}
+
+static void setOverridesForFunction(FunctionNode *fn, CXCursor cursor)
+{
+ CXCursor *overridden;
+ unsigned int numOverridden = 0;
+ clang_getOverriddenCursors(cursor, &overridden, &numOverridden);
+ for (uint i = 0; i < numOverridden; ++i) {
+ QString path = reconstructQualifiedPathForCursor(overridden[i]);
+ if (!path.isEmpty()) {
+ fn->setOverride(true);
+ fn->setOverridesThis(path);
+ break;
+ }
+ }
+ clang_disposeOverriddenCursors(overridden);
+}
+
+class ClangVisitor
+{
+public:
+ ClangVisitor(QDocDatabase *qdb, const std::set<Config::HeaderFilePath> &allHeaders)
+ : qdb_(qdb), parent_(qdb->primaryTreeRoot())
+ {
+ std::transform(allHeaders.cbegin(), allHeaders.cend(), std::inserter(allHeaders_, allHeaders_.begin()),
+ [](const auto& header_file_path) { return header_file_path.filename; });
+ }
+
+ QDocDatabase *qdocDB() { return qdb_; }
+
+ CXChildVisitResult visitChildren(CXCursor cursor)
+ {
+ auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
+ auto loc = clang_getCursorLocation(cur);
+ if (clang_Location_isFromMainFile(loc))
+ return visitSource(cur, loc);
+ CXFile file;
+ clang_getFileLocation(loc, &file, nullptr, nullptr, nullptr);
+ bool isInteresting = false;
+ auto it = isInterestingCache_.find(file);
+ if (it != isInterestingCache_.end()) {
+ isInteresting = *it;
+ } else {
+ QFileInfo fi(fromCXString(clang_getFileName(file)));
+ // Match by file name in case of PCH/installed headers
+ isInteresting = allHeaders_.find(fi.fileName()) != allHeaders_.end();
+ isInterestingCache_[file] = isInteresting;
+ }
+ if (isInteresting) {
+ return visitHeader(cur, loc);
+ }
+
+ return CXChildVisit_Continue;
+ });
+ return ret ? CXChildVisit_Break : CXChildVisit_Continue;
+ }
+
+ /*
+ Not sure about all the possibilities, when the cursor
+ location is not in the main file.
+ */
+ CXChildVisitResult visitFnArg(CXCursor cursor, Node **fnNode, bool &ignoreSignature)
+ {
+ auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
+ auto loc = clang_getCursorLocation(cur);
+ if (clang_Location_isFromMainFile(loc))
+ return visitFnSignature(cur, loc, fnNode, ignoreSignature);
+ return CXChildVisit_Continue;
+ });
+ return ret ? CXChildVisit_Break : CXChildVisit_Continue;
+ }
+
+ Node *nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc);
+
+private:
+ /*!
+ SimpleLoc represents a simple location in the main source file,
+ which can be used as a key in a QMap.
+ */
+ struct SimpleLoc
+ {
+ unsigned int line {}, column {};
+ friend bool operator<(const SimpleLoc &a, const SimpleLoc &b)
+ {
+ return a.line != b.line ? a.line < b.line : a.column < b.column;
+ }
+ };
+ /*!
+ \variable ClangVisitor::declMap_
+ Map of all the declarations in the source file so we can match them
+ with a documentation comment.
+ */
+ QMap<SimpleLoc, CXCursor> declMap_;
+
+ QDocDatabase *qdb_;
+ Aggregate *parent_;
+ std::set<QString> allHeaders_;
+ QHash<CXFile, bool> isInterestingCache_; // doing a canonicalFilePath is slow, so keep a cache.
+
+ /*!
+ Returns true if the symbol should be ignored for the documentation.
+ */
+ bool ignoredSymbol(const QString &symbolName)
+ {
+ if (symbolName == QLatin1String("QPrivateSignal"))
+ return true;
+ // Ignore functions generated by property macros
+ if (symbolName.startsWith("_qt_property_"))
+ return true;
+ // Ignore template argument deduction guides
+ if (symbolName.startsWith("<deduction guide"))
+ return true;
+ return false;
+ }
+
+ CXChildVisitResult visitSource(CXCursor cursor, CXSourceLocation loc);
+ CXChildVisitResult visitHeader(CXCursor cursor, CXSourceLocation loc);
+ CXChildVisitResult visitFnSignature(CXCursor cursor, CXSourceLocation loc, Node **fnNode,
+ bool &ignoreSignature);
+ void processFunction(FunctionNode *fn, CXCursor cursor);
+ bool parseProperty(const QString &spelling, const Location &loc);
+ void readParameterNamesAndAttributes(FunctionNode *fn, CXCursor cursor);
+ Aggregate *getSemanticParent(CXCursor cursor);
+};
+
+/*!
+ Visits a cursor in the .cpp file.
+ This fills the declMap_
+ */
+CXChildVisitResult ClangVisitor::visitSource(CXCursor cursor, CXSourceLocation loc)
+{
+ auto kind = clang_getCursorKind(cursor);
+ if (clang_isDeclaration(kind)) {
+ SimpleLoc l;
+ clang_getPresumedLocation(loc, nullptr, &l.line, &l.column);
+ declMap_.insert(l, cursor);
+ return CXChildVisit_Recurse;
+ }
+ return CXChildVisit_Continue;
+}
+
+/*!
+ If the semantic and lexical parent cursors of \a cursor are
+ not the same, find the Aggregate node for the semantic parent
+ cursor and return it. Otherwise return the current parent.
+ */
+Aggregate *ClangVisitor::getSemanticParent(CXCursor cursor)
+{
+ CXCursor sp = clang_getCursorSemanticParent(cursor);
+ CXCursor lp = clang_getCursorLexicalParent(cursor);
+ if (!clang_equalCursors(sp, lp) && clang_isDeclaration(clang_getCursorKind(sp))) {
+ Node *spn = findNodeForCursor(qdb_, sp);
+ if (spn && spn->isAggregate()) {
+ return static_cast<Aggregate *>(spn);
+ }
+ }
+ return parent_;
+}
+
+CXChildVisitResult ClangVisitor::visitFnSignature(CXCursor cursor, CXSourceLocation, Node **fnNode,
+ bool &ignoreSignature)
+{
+ switch (clang_getCursorKind(cursor)) {
+ case CXCursor_Namespace:
+ return CXChildVisit_Recurse;
+ case CXCursor_FunctionDecl:
+ case CXCursor_FunctionTemplate:
+ case CXCursor_CXXMethod:
+ case CXCursor_Constructor:
+ case CXCursor_Destructor:
+ case CXCursor_ConversionFunction: {
+ ignoreSignature = false;
+ if (ignoredSymbol(functionName(cursor))) {
+ *fnNode = nullptr;
+ ignoreSignature = true;
+ } else {
+ *fnNode = findNodeForCursor(qdb_, cursor);
+ if (*fnNode) {
+ if ((*fnNode)->isFunction(Node::CPP)) {
+ auto *fn = static_cast<FunctionNode *>(*fnNode);
+ readParameterNamesAndAttributes(fn, cursor);
+ }
+ } else { // Possibly an implicitly generated special member
+ QString name = functionName(cursor);
+ if (ignoredSymbol(name))
+ return CXChildVisit_Continue;
+ Aggregate *semanticParent = getSemanticParent(cursor);
+ if (semanticParent && semanticParent->isClass()) {
+ auto *candidate = new FunctionNode(nullptr, name);
+ processFunction(candidate, cursor);
+ if (!candidate->isSpecialMemberFunction()) {
+ delete candidate;
+ return CXChildVisit_Continue;
+ }
+ candidate->setDefault(true);
+ semanticParent->addChild(*fnNode = candidate);
+ }
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return CXChildVisit_Continue;
+}
+
+CXChildVisitResult ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation loc)
+{
+ auto kind = clang_getCursorKind(cursor);
+
+ switch (kind) {
+ case CXCursor_TypeAliasTemplateDecl:
+ case CXCursor_TypeAliasDecl: {
+ QString aliasDecl = getSpelling(clang_getCursorExtent(cursor)).simplified();
+ QStringList typeAlias = aliasDecl.split(QLatin1Char('='));
+ if (typeAlias.size() == 2) {
+ typeAlias[0] = typeAlias[0].trimmed();
+ const QLatin1String usingString("using ");
+ qsizetype usingPos = typeAlias[0].indexOf(usingString);
+ if (usingPos != -1) {
+ typeAlias[0].remove(0, usingPos + usingString.size());
+ typeAlias[0] = typeAlias[0].split(QLatin1Char(' ')).first();
+ typeAlias[1] = typeAlias[1].trimmed();
+ auto *ta = new TypeAliasNode(parent_, typeAlias[0], typeAlias[1]);
+ ta->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
+ ta->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
+
+ if (kind == CXCursor_TypeAliasTemplateDecl) {
+ auto template_decl = llvm::dyn_cast<clang::TemplateDecl>(get_cursor_declaration(cursor));
+ ta->setTemplateDecl(get_template_declaration(template_decl));
+ }
+ }
+ }
+ return CXChildVisit_Continue;
+ }
+ case CXCursor_StructDecl:
+ case CXCursor_UnionDecl:
+ if (fromCXString(clang_getCursorSpelling(cursor)).isEmpty()) // anonymous struct or union
+ return CXChildVisit_Continue;
+ Q_FALLTHROUGH();
+ case CXCursor_ClassTemplate:
+ Q_FALLTHROUGH();
+ case CXCursor_ClassDecl: {
+ if (!clang_isCursorDefinition(cursor))
+ return CXChildVisit_Continue;
+
+ if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
+ return CXChildVisit_Continue;
+
+ QString className = fromCXString(clang_getCursorSpelling(cursor));
+
+ Aggregate *semanticParent = getSemanticParent(cursor);
+ if (semanticParent && semanticParent->findNonfunctionChild(className, &Node::isClassNode)) {
+ return CXChildVisit_Continue;
+ }
+
+ CXCursorKind actualKind = (kind == CXCursor_ClassTemplate) ?
+ clang_getTemplateCursorKind(cursor) : kind;
+
+ Node::NodeType type = Node::Class;
+ if (actualKind == CXCursor_StructDecl)
+ type = Node::Struct;
+ else if (actualKind == CXCursor_UnionDecl)
+ type = Node::Union;
+
+ auto *classe = new ClassNode(type, semanticParent, className);
+ classe->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
+ classe->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
+
+ if (kind == CXCursor_ClassTemplate) {
+ auto template_declaration = llvm::dyn_cast<clang::TemplateDecl>(get_cursor_declaration(cursor));
+ classe->setTemplateDecl(get_template_declaration(template_declaration));
+ }
+
+ QScopedValueRollback<Aggregate *> setParent(parent_, classe);
+ return visitChildren(cursor);
+ }
+ case CXCursor_CXXBaseSpecifier: {
+ if (!parent_->isClassNode())
+ return CXChildVisit_Continue;
+ auto access = fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor));
+ auto type = clang_getCursorType(cursor);
+ auto baseCursor = clang_getTypeDeclaration(type);
+ auto baseNode = findNodeForCursor(qdb_, baseCursor);
+ auto classe = static_cast<ClassNode *>(parent_);
+ if (baseNode == nullptr || !baseNode->isClassNode()) {
+ QString bcName = reconstructQualifiedPathForCursor(baseCursor);
+ classe->addUnresolvedBaseClass(access,
+ bcName.split(QLatin1String("::"), Qt::SkipEmptyParts));
+ return CXChildVisit_Continue;
+ }
+ auto baseClasse = static_cast<ClassNode *>(baseNode);
+ classe->addResolvedBaseClass(access, baseClasse);
+ return CXChildVisit_Continue;
+ }
+ case CXCursor_Namespace: {
+ QString namespaceName = fromCXString(clang_getCursorDisplayName(cursor));
+ NamespaceNode *ns = nullptr;
+ if (parent_)
+ ns = static_cast<NamespaceNode *>(
+ parent_->findNonfunctionChild(namespaceName, &Node::isNamespace));
+ if (!ns) {
+ ns = new NamespaceNode(parent_, namespaceName);
+ ns->setAccess(Access::Public);
+ ns->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
+ }
+ QScopedValueRollback<Aggregate *> setParent(parent_, ns);
+ return visitChildren(cursor);
+ }
+ case CXCursor_FunctionTemplate:
+ Q_FALLTHROUGH();
+ case CXCursor_FunctionDecl:
+ case CXCursor_CXXMethod:
+ case CXCursor_Constructor:
+ case CXCursor_Destructor:
+ case CXCursor_ConversionFunction: {
+ if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
+ return CXChildVisit_Continue;
+ QString name = functionName(cursor);
+ if (ignoredSymbol(name))
+ return CXChildVisit_Continue;
+ // constexpr constructors generate also a global instance; ignore
+ if (kind == CXCursor_Constructor && parent_ == qdb_->primaryTreeRoot())
+ return CXChildVisit_Continue;
+
+ auto *fn = new FunctionNode(parent_, name);
+ CXSourceRange range = clang_Cursor_getCommentRange(cursor);
+ if (!clang_Range_isNull(range)) {
+ QString comment = getSpelling(range);
+ if (comment.startsWith("//!")) {
+ qsizetype tag = comment.indexOf(QChar('['));
+ if (tag > 0) {
+ qsizetype end = comment.indexOf(QChar(']'), ++tag);
+ if (end > 0)
+ fn->setTag(comment.mid(tag, end - tag));
+ }
+ }
+ }
+
+ processFunction(fn, cursor);
+
+ if (kind == CXCursor_FunctionTemplate) {
+ auto template_declaration = get_cursor_declaration(cursor)->getAsFunction()->getDescribedFunctionTemplate();
+ fn->setTemplateDecl(get_template_declaration(template_declaration));
+ }
+
+ return CXChildVisit_Continue;
+ }
+#if CINDEX_VERSION >= 36
+ case CXCursor_FriendDecl: {
+ return visitChildren(cursor);
+ }
+#endif
+ case CXCursor_EnumDecl: {
+ auto *en = static_cast<EnumNode *>(findNodeForCursor(qdb_, cursor));
+ if (en && en->items().size())
+ return CXChildVisit_Continue; // Was already parsed, probably in another TU
+
+ QString enumTypeName = fromCXString(clang_getCursorSpelling(cursor));
+
+ if (clang_Cursor_isAnonymous(cursor)) {
+ enumTypeName = "anonymous";
+ if (parent_ && (parent_->isClassNode() || parent_->isNamespace())) {
+ Node *n = parent_->findNonfunctionChild(enumTypeName, &Node::isEnumType);
+ if (n)
+ en = static_cast<EnumNode *>(n);
+ }
+ }
+ if (!en) {
+ en = new EnumNode(parent_, enumTypeName, clang_EnumDecl_isScoped(cursor));
+ en->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
+ en->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
+ }
+
+ // Enum values
+ visitChildrenLambda(cursor, [&](CXCursor cur) {
+ if (clang_getCursorKind(cur) != CXCursor_EnumConstantDecl)
+ return CXChildVisit_Continue;
+
+ QString value;
+ visitChildrenLambda(cur, [&](CXCursor cur) {
+ if (clang_isExpression(clang_getCursorKind(cur))) {
+ value = getSpelling(clang_getCursorExtent(cur));
+ return CXChildVisit_Break;
+ }
+ return CXChildVisit_Continue;
+ });
+ if (value.isEmpty()) {
+ QLatin1String hex("0x");
+ if (!en->items().isEmpty() && en->items().last().value().startsWith(hex)) {
+ value = hex + QString::number(clang_getEnumConstantDeclValue(cur), 16);
+ } else {
+ value = QString::number(clang_getEnumConstantDeclValue(cur));
+ }
+ }
+
+ en->addItem(EnumItem(fromCXString(clang_getCursorSpelling(cur)), value));
+ return CXChildVisit_Continue;
+ });
+ return CXChildVisit_Continue;
+ }
+ case CXCursor_FieldDecl:
+ case CXCursor_VarDecl: {
+ if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
+ return CXChildVisit_Continue;
+
+ auto value_declaration =
+ llvm::dyn_cast<clang::ValueDecl>(get_cursor_declaration(cursor));
+ assert(value_declaration);
+
+ auto access = fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor));
+ auto var = new VariableNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
+
+ var->setAccess(access);
+ var->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
+ var->setLeftType(QString::fromStdString(get_fully_qualified_type_name(
+ value_declaration->getType(),
+ value_declaration->getASTContext()
+ )));
+ var->setStatic(kind == CXCursor_VarDecl && parent_->isClassNode());
+
+ return CXChildVisit_Continue;
+ }
+ case CXCursor_TypedefDecl: {
+ if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
+ return CXChildVisit_Continue;
+ auto *td = new TypedefNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
+ td->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
+ td->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
+ // Search to see if this is a Q_DECLARE_FLAGS (if the type is QFlags<ENUM>)
+ visitChildrenLambda(cursor, [&](CXCursor cur) {
+ if (clang_getCursorKind(cur) != CXCursor_TemplateRef
+ || fromCXString(clang_getCursorSpelling(cur)) != QLatin1String("QFlags"))
+ return CXChildVisit_Continue;
+ // Found QFlags<XXX>
+ visitChildrenLambda(cursor, [&](CXCursor cur) {
+ if (clang_getCursorKind(cur) != CXCursor_TypeRef)
+ return CXChildVisit_Continue;
+ auto *en =
+ findNodeForCursor(qdb_, clang_getTypeDeclaration(clang_getCursorType(cur)));
+ if (en && en->isEnumType())
+ static_cast<EnumNode *>(en)->setFlagsType(td);
+ return CXChildVisit_Break;
+ });
+ return CXChildVisit_Break;
+ });
+ return CXChildVisit_Continue;
+ }
+ default:
+ if (clang_isDeclaration(kind) && parent_->isClassNode()) {
+ // may be a property macro or a static_assert
+ // which is not exposed from the clang API
+ parseProperty(getSpelling(clang_getCursorExtent(cursor)),
+ fromCXSourceLocation(loc));
+ }
+ return CXChildVisit_Continue;
+ }
+}
+
+void ClangVisitor::readParameterNamesAndAttributes(FunctionNode *fn, CXCursor cursor)
+{
+ Parameters &parameters = fn->parameters();
+ // Visit the parameters and attributes
+ int i = 0;
+ visitChildrenLambda(cursor, [&](CXCursor cur) {
+ auto kind = clang_getCursorKind(cur);
+ if (kind == CXCursor_AnnotateAttr) {
+ QString annotation = fromCXString(clang_getCursorDisplayName(cur));
+ if (annotation == QLatin1String("qt_slot")) {
+ fn->setMetaness(FunctionNode::Slot);
+ } else if (annotation == QLatin1String("qt_signal")) {
+ fn->setMetaness(FunctionNode::Signal);
+ }
+ if (annotation == QLatin1String("qt_invokable"))
+ fn->setInvokable(true);
+ } else if (kind == CXCursor_CXXOverrideAttr) {
+ fn->setOverride(true);
+ } else if (kind == CXCursor_ParmDecl) {
+ if (i >= parameters.count())
+ return CXChildVisit_Break; // Attributes comes before parameters so we can break.
+
+ if (QString name = fromCXString(clang_getCursorSpelling(cur)); !name.isEmpty())
+ parameters[i].setName(name);
+
+ const clang::ParmVarDecl* parameter_declaration = llvm::dyn_cast<const clang::ParmVarDecl>(get_cursor_declaration(cur));
+ Q_ASSERT(parameter_declaration);
+
+ std::string default_value = get_default_value_initializer_as_string(parameter_declaration);
+
+ if (!default_value.empty())
+ parameters[i].setDefaultValue(QString::fromStdString(default_value));
+
+ ++i;
+ }
+ return CXChildVisit_Continue;
+ });
+}
+
+void ClangVisitor::processFunction(FunctionNode *fn, CXCursor cursor)
+{
+ CXCursorKind kind = clang_getCursorKind(cursor);
+ CXType funcType = clang_getCursorType(cursor);
+ fn->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
+ fn->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
+ fn->setStatic(clang_CXXMethod_isStatic(cursor));
+ fn->setConst(clang_CXXMethod_isConst(cursor));
+ fn->setVirtualness(!clang_CXXMethod_isVirtual(cursor)
+ ? FunctionNode::NonVirtual
+ : clang_CXXMethod_isPureVirtual(cursor)
+ ? FunctionNode::PureVirtual
+ : FunctionNode::NormalVirtual);
+
+ // REMARK: We assume that the following operations and casts are
+ // generally safe.
+ // Callers of those methods will generally check at the LibClang
+ // level the kind of cursor we are dealing with and will pass on
+ // only valid cursors that are of a function kind and that are at
+ // least a declaration.
+ //
+ // Failure to do so implies a bug in the call chain and should be
+ // dealt with as such.
+ const clang::Decl* declaration = get_cursor_declaration(cursor);
+
+ assert(declaration);
+
+ const clang::FunctionDecl* function_declaration = declaration->getAsFunction();
+
+ if (kind == CXCursor_Constructor
+ // a constructor template is classified as CXCursor_FunctionTemplate
+ || (kind == CXCursor_FunctionTemplate && fn->name() == parent_->name()))
+ fn->setMetaness(FunctionNode::Ctor);
+ else if (kind == CXCursor_Destructor)
+ fn->setMetaness(FunctionNode::Dtor);
+ else
+ fn->setReturnType(QString::fromStdString(get_fully_qualified_type_name(
+ function_declaration->getReturnType(),
+ function_declaration->getASTContext()
+ )));
+
+ const clang::CXXConstructorDecl* constructor_declaration = llvm::dyn_cast<const clang::CXXConstructorDecl>(function_declaration);
+
+ if (constructor_declaration && constructor_declaration->isCopyConstructor()) fn->setMetaness(FunctionNode::CCtor);
+ else if (constructor_declaration && constructor_declaration->isMoveConstructor()) fn->setMetaness(FunctionNode::MCtor);
+
+ const clang::CXXConversionDecl* conversion_declaration = llvm::dyn_cast<const clang::CXXConversionDecl>(function_declaration);
+
+ if (function_declaration->isConstexpr()) fn->markConstexpr();
+ if (
+ (constructor_declaration && constructor_declaration->isExplicit()) ||
+ (conversion_declaration && conversion_declaration->isExplicit())
+ ) fn->markExplicit();
+
+ const clang::CXXMethodDecl* method_declaration = llvm::dyn_cast<const clang::CXXMethodDecl>(function_declaration);
+
+ if (method_declaration && method_declaration->isCopyAssignmentOperator()) fn->setMetaness(FunctionNode::CAssign);
+ else if (method_declaration && method_declaration->isMoveAssignmentOperator()) fn->setMetaness(FunctionNode::MAssign);
+
+ const clang::FunctionType* function_type = function_declaration->getFunctionType();
+ const clang::FunctionProtoType* function_prototype = static_cast<const clang::FunctionProtoType*>(function_type);
+
+ if (function_prototype) {
+ clang::FunctionProtoType::ExceptionSpecInfo exception_specification = function_prototype->getExceptionSpecInfo();
+
+ if (exception_specification.Type != clang::ExceptionSpecificationType::EST_None) {
+ const std::string exception_specification_spelling =
+ exception_specification.NoexceptExpr ? get_expression_as_string(
+ exception_specification.NoexceptExpr,
+ function_declaration->getASTContext()
+ ) : "";
+
+ if (exception_specification_spelling != "false")
+ fn->markNoexcept(QString::fromStdString(exception_specification_spelling));
+ }
+ }
+
+ CXRefQualifierKind refQualKind = clang_Type_getCXXRefQualifier(funcType);
+ if (refQualKind == CXRefQualifier_LValue)
+ fn->setRef(true);
+ else if (refQualKind == CXRefQualifier_RValue)
+ fn->setRefRef(true);
+ // For virtual functions, determine what it overrides
+ // (except for destructor for which we do not want to classify as overridden)
+ if (!fn->isNonvirtual() && kind != CXCursor_Destructor)
+ setOverridesForFunction(fn, cursor);
+
+ Parameters &parameters = fn->parameters();
+ parameters.clear();
+ parameters.reserve(function_declaration->getNumParams());
+
+ for (clang::ParmVarDecl* const parameter_declaration : function_declaration->parameters()) {
+ clang::QualType parameter_type = parameter_declaration->getOriginalType();
+
+ parameters.append(QString::fromStdString(get_fully_qualified_type_name(
+ parameter_type,
+ parameter_declaration->getASTContext()
+ )));
+
+ if (!parameter_type.isCanonical())
+ parameters.last().setCanonicalType(QString::fromStdString(get_fully_qualified_type_name(
+ parameter_type.getCanonicalType(),
+ parameter_declaration->getASTContext()
+ )));
+ }
+
+ if (parameters.count() > 0) {
+ if (parameters.last().type().endsWith(QLatin1String("QPrivateSignal"))) {
+ parameters.pop_back(); // remove the QPrivateSignal argument
+ parameters.setPrivateSignal();
+ }
+ }
+
+ if (clang_isFunctionTypeVariadic(funcType))
+ parameters.append(QStringLiteral("..."));
+ readParameterNamesAndAttributes(fn, cursor);
+
+ if (declaration->getFriendObjectKind() != clang::Decl::FOK_None)
+ fn->setRelatedNonmember(true);
+}
+
+bool ClangVisitor::parseProperty(const QString &spelling, const Location &loc)
+{
+ if (!spelling.startsWith(QLatin1String("Q_PROPERTY"))
+ && !spelling.startsWith(QLatin1String("QDOC_PROPERTY"))
+ && !spelling.startsWith(QLatin1String("Q_OVERRIDE")))
+ return false;
+
+ qsizetype lpIdx = spelling.indexOf(QChar('('));
+ qsizetype rpIdx = spelling.lastIndexOf(QChar(')'));
+ if (lpIdx <= 0 || rpIdx <= lpIdx)
+ return false;
+
+ QString signature = spelling.mid(lpIdx + 1, rpIdx - lpIdx - 1);
+ signature = signature.simplified();
+ QStringList parts = signature.split(QChar(' '), Qt::SkipEmptyParts);
+
+ static const QStringList attrs =
+ QStringList() << "READ" << "MEMBER" << "WRITE"
+ << "NOTIFY" << "CONSTANT" << "FINAL"
+ << "REQUIRED" << "BINDABLE" << "DESIGNABLE"
+ << "RESET" << "REVISION" << "SCRIPTABLE"
+ << "STORED" << "USER";
+
+ // Find the location of the first attribute. All preceding parts
+ // represent the property type + name.
+ auto it = std::find_if(parts.cbegin(), parts.cend(),
+ [](const QString &attr) -> bool {
+ return attrs.contains(attr);
+ });
+
+ if (it == parts.cend() || std::distance(parts.cbegin(), it) < 2)
+ return false;
+
+ QStringList typeParts;
+ std::copy(parts.cbegin(), it, std::back_inserter(typeParts));
+ parts.erase(parts.cbegin(), it);
+ QString name = typeParts.takeLast();
+
+ // Move the pointer operator(s) from name to type
+ while (!name.isEmpty() && name.front() == QChar('*')) {
+ typeParts.last().push_back(name.front());
+ name.removeFirst();
+ }
+
+ // Need at least READ or MEMBER + getter/member name
+ if (parts.size() < 2 || name.isEmpty())
+ return false;
+
+ auto *property = new PropertyNode(parent_, name);
+ property->setAccess(Access::Public);
+ property->setLocation(loc);
+ property->setDataType(typeParts.join(QChar(' ')));
+
+ int i = 0;
+ while (i < parts.size()) {
+ const QString &key = parts.at(i++);
+ // Keywords with no associated values
+ if (key == "CONSTANT") {
+ property->setConstant();
+ } else if (key == "REQUIRED") {
+ property->setRequired();
+ }
+ if (i < parts.size()) {
+ QString value = parts.at(i++);
+ if (key == "READ") {
+ qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Getter);
+ } else if (key == "WRITE") {
+ qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Setter);
+ property->setWritable(true);
+ } else if (key == "MEMBER") {
+ property->setWritable(true);
+ } else if (key == "STORED") {
+ property->setStored(value.toLower() == "true");
+ } else if (key == "BINDABLE") {
+ property->setPropertyType(PropertyNode::PropertyType::BindableProperty);
+ qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Bindable);
+ } else if (key == "RESET") {
+ qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Resetter);
+ } else if (key == "NOTIFY") {
+ qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Notifier);
+ }
+ }
+ }
+ return true;
+}
+
+/*!
+ Given a comment at location \a loc, return a Node for this comment
+ \a nextCommentLoc is the location of the next comment so the declaration
+ must be inbetween.
+ Returns nullptr if no suitable declaration was found between the two comments.
+ */
+Node *ClangVisitor::nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc)
+{
+ ClangVisitor::SimpleLoc docloc;
+ clang_getPresumedLocation(loc, nullptr, &docloc.line, &docloc.column);
+ auto decl_it = declMap_.upperBound(docloc);
+ if (decl_it == declMap_.end())
+ return nullptr;
+
+ unsigned int declLine = decl_it.key().line;
+ unsigned int nextCommentLine;
+ clang_getPresumedLocation(nextCommentLoc, nullptr, &nextCommentLine, nullptr);
+ if (nextCommentLine < declLine)
+ return nullptr; // there is another comment before the declaration, ignore it.
+
+ // make sure the previous decl was finished.
+ if (decl_it != declMap_.begin()) {
+ CXSourceLocation prevDeclEnd = clang_getRangeEnd(clang_getCursorExtent(*(std::prev(decl_it))));
+ unsigned int prevDeclLine;
+ clang_getPresumedLocation(prevDeclEnd, nullptr, &prevDeclLine, nullptr);
+ if (prevDeclLine >= docloc.line) {
+ // The previous declaration was still going. This is only valid if the previous
+ // declaration is a parent of the next declaration.
+ auto parent = clang_getCursorLexicalParent(*decl_it);
+ if (!clang_equalCursors(parent, *(std::prev(decl_it))))
+ return nullptr;
+ }
+ }
+ auto *node = findNodeForCursor(qdb_, *decl_it);
+ // borrow the parameter name from the definition
+ if (node && node->isFunction(Node::CPP))
+ readParameterNamesAndAttributes(static_cast<FunctionNode *>(node), *decl_it);
+ return node;
+}
+
+ClangCodeParser::ClangCodeParser(
+ QDocDatabase* qdb,
+ Config& config,
+ const std::vector<QByteArray>& include_paths,
+ const QList<QByteArray>& defines,
+ std::optional<std::reference_wrapper<const PCHFile>> pch
+) : m_qdb{qdb},
+ m_includePaths{include_paths},
+ m_defines{defines},
+ m_pch{pch}
+{
+ m_allHeaders = config.getHeaderFiles();
+}
+
+static const char *defaultArgs_[] = {
+/*
+ https://bugreports.qt.io/browse/QTBUG-94365
+ An unidentified bug in Clang 15.x causes parsing failures due to errors in
+ the AST. This replicates only with C++20 support enabled - avoid the issue
+ by using C++17 with Clang 15.
+ */
+#if LIBCLANG_VERSION_MAJOR == 15
+ "-std=c++17",
+#else
+ "-std=c++20",
+#endif
+#ifndef Q_OS_WIN
+ "-fPIC",
+#else
+ "-fms-compatibility-version=19",
+#endif
+ "-DQ_QDOC",
+ "-DQ_CLANG_QDOC",
+ "-DQT_DISABLE_DEPRECATED_UP_TO=0",
+ "-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);",
+ "-DQT_ANNOTATE_CLASS2(type,a1,a2)=static_assert(sizeof(#a1,#a2),#type);",
+ "-DQT_ANNOTATE_FUNCTION(a)=__attribute__((annotate(#a)))",
+ "-DQT_ANNOTATE_ACCESS_SPECIFIER(a)=__attribute__((annotate(#a)))",
+ "-Wno-constant-logical-operand",
+ "-Wno-macro-redefined",
+ "-Wno-nullability-completeness",
+ "-fvisibility=default",
+ "-ferror-limit=0",
+ ("-I" CLANG_RESOURCE_DIR)
+};
+
+/*!
+ Load the default arguments and the defines into \a args.
+ Clear \a args first.
+ */
+void getDefaultArgs(const QList<QByteArray>& defines, std::vector<const char*>& args)
+{
+ args.clear();
+ args.insert(args.begin(), std::begin(defaultArgs_), std::end(defaultArgs_));
+
+ // Add the defines from the qdocconf file.
+ for (const auto &p : std::as_const(defines))
+ args.push_back(p.constData());
+}
+
+static QList<QByteArray> includePathsFromHeaders(const std::set<Config::HeaderFilePath> &allHeaders)
+{
+ QList<QByteArray> result;
+ for (const auto& [header_path, _] : allHeaders) {
+ const QByteArray path = "-I" + header_path.toLatin1();
+ const QByteArray parent =
+ "-I" + QDir::cleanPath(header_path + QLatin1String("/../")).toLatin1();
+ }
+
+ return result;
+}
+
+/*!
+ Load the include paths into \a moreArgs. If no include paths
+ were provided, try to guess reasonable include paths.
+ */
+void getMoreArgs(
+ const std::vector<QByteArray>& include_paths,
+ const std::set<Config::HeaderFilePath>& all_headers,
+ std::vector<const char*>& args
+) {
+ if (include_paths.empty()) {
+ /*
+ The include paths provided are inadequate. Make a list
+ of reasonable places to look for include files and use
+ that list instead.
+ */
+ qCWarning(lcQdoc) << "No include paths passed to qdoc; guessing reasonable include paths";
+
+ QString basicIncludeDir = QDir::cleanPath(QString(Config::installDir + "/../include"));
+ args.emplace_back(QByteArray("-I" + basicIncludeDir.toLatin1()).constData());
+
+ auto include_paths_from_headers = includePathsFromHeaders(all_headers);
+ args.insert(args.end(), include_paths_from_headers.begin(), include_paths_from_headers.end());
+ } else {
+ std::copy(include_paths.begin(), include_paths.end(), std::back_inserter(args));
+ }
+}
+
+/*!
+ Building the PCH must be possible when there are no .cpp
+ files, so it is moved here to its own member function, and
+ it is called after the list of header files is complete.
+ */
+std::optional<PCHFile> buildPCH(
+ QDocDatabase* qdb,
+ QString module_header,
+ const std::set<Config::HeaderFilePath>& all_headers,
+ const std::vector<QByteArray>& include_paths,
+ const QList<QByteArray>& defines
+) {
+ static std::vector<const char*> arguments{};
+
+ if (module_header.isEmpty()) return std::nullopt;
+
+ getDefaultArgs(defines, arguments);
+ getMoreArgs(include_paths, all_headers, arguments);
+
+ flags_ = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
+ | CXTranslationUnit_SkipFunctionBodies
+ | CXTranslationUnit_KeepGoing);
+
+ CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
+
+ QTemporaryDir pch_directory{QDir::tempPath() + QLatin1String("/qdoc_pch")};
+ if (!pch_directory.isValid()) return std::nullopt;
+
+ const QByteArray module = module_header.toUtf8();
+ QByteArray header;
+
+ qCDebug(lcQdoc) << "Build and visit PCH for" << module_header;
+ // A predicate for std::find_if() to locate a path to the module's header
+ // (e.g. QtGui/QtGui) to be used as pre-compiled header
+ struct FindPredicate
+ {
+ enum SearchType { Any, Module };
+ QByteArray &candidate_;
+ const QByteArray &module_;
+ SearchType type_;
+ FindPredicate(QByteArray &candidate, const QByteArray &module,
+ SearchType type = Any)
+ : candidate_(candidate), module_(module), type_(type)
+ {
+ }
+
+ bool operator()(const QByteArray &p) const
+ {
+ if (type_ != Any && !p.endsWith(module_))
+ return false;
+ candidate_ = p + "/";
+ candidate_.append(module_);
+ if (p.startsWith("-I"))
+ candidate_ = candidate_.mid(2);
+ return QFile::exists(QString::fromUtf8(candidate_));
+ }
+ };
+
+ // First, search for an include path that contains the module name, then any path
+ QByteArray candidate;
+ auto it = std::find_if(include_paths.begin(), include_paths.end(),
+ FindPredicate(candidate, module, FindPredicate::Module));
+ if (it == include_paths.end())
+ it = std::find_if(include_paths.begin(), include_paths.end(),
+ FindPredicate(candidate, module, FindPredicate::Any));
+ if (it != include_paths.end())
+ header = candidate;
+
+ if (header.isEmpty()) {
+ qWarning() << "(qdoc) Could not find the module header in include paths for module"
+ << module << " (include paths: " << include_paths << ")";
+ qWarning() << " Artificial module header built from header dirs in qdocconf "
+ "file";
+ }
+ arguments.push_back("-xc++");
+
+ TranslationUnit tu;
+
+ QString tmpHeader = pch_directory.path() + "/" + module;
+ if (QFile tmpHeaderFile(tmpHeader); tmpHeaderFile.open(QIODevice::Text | QIODevice::WriteOnly)) {
+ QTextStream out(&tmpHeaderFile);
+ if (header.isEmpty()) {
+ for (const auto& [header_path, header_name] : all_headers) {
+ if (!header_name.endsWith(QLatin1String("_p.h"))
+ && !header_name.startsWith(QLatin1String("moc_"))) {
+ QString line = QLatin1String("#include \"") + header_path
+ + QLatin1String("/") + header_name + QLatin1String("\"");
+ out << line << "\n";
+
+ }
+ }
+ } else {
+ QFileInfo headerFile(header);
+ if (!headerFile.exists()) {
+ qWarning() << "Could not find module header file" << header;
+ return std::nullopt;
+ }
+ out << QLatin1String("#include \"") + header + QLatin1String("\"");
+ }
+ }
+
+ CXErrorCode err =
+ clang_parseTranslationUnit2(index, tmpHeader.toLatin1().data(), arguments.data(),
+ static_cast<int>(arguments.size()), nullptr, 0,
+ flags_ | CXTranslationUnit_ForSerialization, &tu.tu);
+ qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << tmpHeader << arguments
+ << ") returns" << err;
+
+ printDiagnostics(tu);
+
+ if (err || !tu) {
+ qCCritical(lcQdoc) << "Could not create PCH file for " << module_header;
+ return std::nullopt;
+ }
+
+ QByteArray pch_name = pch_directory.path().toUtf8() + "/" + module + ".pch";
+ auto error = clang_saveTranslationUnit(tu, pch_name.constData(),
+ clang_defaultSaveOptions(tu));
+ if (error) {
+ qCCritical(lcQdoc) << "Could not save PCH file for" << module_header;
+ return std::nullopt;
+ }
+
+ // Visit the header now, as token from pre-compiled header won't be visited
+ // later
+ CXCursor cur = clang_getTranslationUnitCursor(tu);
+ ClangVisitor visitor(qdb, all_headers);
+ visitor.visitChildren(cur);
+ qCDebug(lcQdoc) << "PCH built and visited for" << module_header;
+
+ return std::make_optional(PCHFile{std::move(pch_directory), pch_name});
+}
+
+static float getUnpatchedVersion(QString t)
+{
+ if (t.count(QChar('.')) > 1)
+ t.truncate(t.lastIndexOf(QChar('.')));
+ return t.toFloat();
+}
+
+/*!
+ Get ready to parse the C++ cpp file identified by \a filePath
+ and add its parsed contents to the database. \a location is
+ used for reporting errors.
+
+ Call matchDocsAndStuff() to do all the parsing and tree building.
+ */
+ParsedCppFileIR ClangCodeParser::parse_cpp_file(const QString &filePath)
+{
+ flags_ = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
+ | CXTranslationUnit_SkipFunctionBodies
+ | CXTranslationUnit_KeepGoing);
+
+ CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
+
+ getDefaultArgs(m_defines, m_args);
+ if (m_pch && !filePath.endsWith(".mm")) {
+ m_args.push_back("-w");
+ m_args.push_back("-include-pch");
+ m_args.push_back((*m_pch).get().name.constData());
+ }
+ getMoreArgs(m_includePaths, m_allHeaders, m_args);
+
+ TranslationUnit tu;
+ CXErrorCode err =
+ clang_parseTranslationUnit2(index, filePath.toLocal8Bit(), m_args.data(),
+ static_cast<int>(m_args.size()), nullptr, 0, flags_, &tu.tu);
+ qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << filePath << m_args
+ << ") returns" << err;
+ printDiagnostics(tu);
+
+ if (err || !tu) {
+ qWarning() << "(qdoc) Could not parse source file" << filePath << " error code:" << err;
+ return {};
+ }
+
+ ParsedCppFileIR parse_result{};
+
+ CXCursor tuCur = clang_getTranslationUnitCursor(tu);
+ ClangVisitor visitor(m_qdb, m_allHeaders);
+ visitor.visitChildren(tuCur);
+
+ CXToken *tokens;
+ unsigned int numTokens = 0;
+ const QSet<QString> &commands = CppCodeParser::topic_commands + CppCodeParser::meta_commands;
+ clang_tokenize(tu, clang_getCursorExtent(tuCur), &tokens, &numTokens);
+
+ for (unsigned int i = 0; i < numTokens; ++i) {
+ if (clang_getTokenKind(tokens[i]) != CXToken_Comment)
+ continue;
+ QString comment = fromCXString(clang_getTokenSpelling(tu, tokens[i]));
+ if (!comment.startsWith("/*!"))
+ continue;
+
+ auto commentLoc = clang_getTokenLocation(tu, tokens[i]);
+ auto loc = fromCXSourceLocation(commentLoc);
+ auto end_loc = fromCXSourceLocation(clang_getRangeEnd(clang_getTokenExtent(tu, tokens[i])));
+ Doc::trimCStyleComment(loc, comment);
+
+ // Doc constructor parses the comment.
+ Doc doc(loc, end_loc, comment, commands, CppCodeParser::topic_commands);
+ if (hasTooManyTopics(doc))
+ continue;
+
+ if (doc.topicsUsed().isEmpty()) {
+ Node *n = nullptr;
+ if (i + 1 < numTokens) {
+ // Try to find the next declaration.
+ CXSourceLocation nextCommentLoc = commentLoc;
+ while (i + 2 < numTokens && clang_getTokenKind(tokens[i + 1]) != CXToken_Comment)
+ ++i; // already skip all the tokens that are not comments
+ nextCommentLoc = clang_getTokenLocation(tu, tokens[i + 1]);
+ n = visitor.nodeForCommentAtLocation(commentLoc, nextCommentLoc);
+ }
+
+ if (n) {
+ parse_result.tied.emplace_back(TiedDocumentation{doc, n});
+ } else if (CodeParser::isWorthWarningAbout(doc)) {
+ bool future = false;
+ if (doc.metaCommandsUsed().contains(COMMAND_SINCE)) {
+ QString sinceVersion = doc.metaCommandArgs(COMMAND_SINCE).at(0).first;
+ if (getUnpatchedVersion(sinceVersion) >
+ getUnpatchedVersion(Config::instance().get(CONFIG_VERSION).asString()))
+ future = true;
+ }
+ if (!future) {
+ doc.location().warning(
+ QStringLiteral("Cannot tie this documentation to anything"),
+ QStringLiteral("qdoc found a /*! ... */ comment, but there was no "
+ "topic command (e.g., '\\%1', '\\%2') in the "
+ "comment and no function definition following "
+ "the comment.")
+ .arg(COMMAND_FN, COMMAND_PAGE));
+ }
+ }
+ } else {
+ parse_result.untied.emplace_back(UntiedDocumentation{doc, QStringList()});
+
+ CXCursor cur = clang_getCursor(tu, commentLoc);
+ while (true) {
+ CXCursorKind kind = clang_getCursorKind(cur);
+ if (clang_isTranslationUnit(kind) || clang_isInvalid(kind))
+ break;
+ if (kind == CXCursor_Namespace) {
+ parse_result.untied.back().context << fromCXString(clang_getCursorSpelling(cur));
+ }
+ cur = clang_getCursorLexicalParent(cur);
+ }
+ }
+ }
+
+ clang_disposeTokens(tu, tokens, numTokens);
+ m_namespaceScope.clear();
+ s_fn.clear();
+
+ return parse_result;
+}
+
+/*!
+ Use clang to parse the function signature from a function
+ command. \a location is used for reporting errors. \a fnSignature
+ is the string to parse. It is always a function decl.
+ \a idTag is the optional bracketed argument passed to \\fn, or
+ an empty string.
+ \a context is a string list representing the scope (namespaces)
+ under which the function is declared.
+
+ Returns a variant that's either a Node instance tied to the
+ function declaration, or a parsing failure for later processing.
+ */
+std::variant<Node*, FnMatchError> FnCommandParser::operator()(const Location &location, const QString &fnSignature,
+ const QString &idTag, QStringList context)
+{
+ Node *fnNode = nullptr;
+ /*
+ If the \fn command begins with a tag, then don't try to
+ parse the \fn command with clang. Use the tag to search
+ for the correct function node. It is an error if it can
+ not be found. Return 0 in that case.
+ */
+ if (!idTag.isEmpty()) {
+ fnNode = m_qdb->findFunctionNodeForTag(idTag);
+ if (!fnNode) {
+ location.error(
+ QStringLiteral("tag \\fn [%1] not used in any include file in current module").arg(idTag));
+ } else {
+ /*
+ The function node was found. Use the formal
+ parameter names from the \fn command, because
+ they will be the names used in the documentation.
+ */
+ auto *fn = static_cast<FunctionNode *>(fnNode);
+ QStringList leftParenSplit = fnSignature.mid(fnSignature.indexOf(fn->name())).split('(');
+ if (leftParenSplit.size() > 1) {
+ QStringList rightParenSplit = leftParenSplit[1].split(')');
+ if (!rightParenSplit.empty()) {
+ QString params = rightParenSplit[0];
+ if (!params.isEmpty()) {
+ QStringList commaSplit = params.split(',');
+ Parameters &parameters = fn->parameters();
+ if (parameters.count() == commaSplit.size()) {
+ for (int i = 0; i < parameters.count(); ++i) {
+ QStringList blankSplit = commaSplit[i].split(' ', Qt::SkipEmptyParts);
+ if (blankSplit.size() > 1) {
+ QString pName = blankSplit.last();
+ // Remove any non-letters from the start of parameter name
+ auto it = std::find_if(std::begin(pName), std::end(pName),
+ [](const QChar &c) { return c.isLetter(); });
+ parameters[i].setName(
+ pName.remove(0, std::distance(std::begin(pName), it)));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return fnNode;
+ }
+ auto flags = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
+ | CXTranslationUnit_SkipFunctionBodies
+ | CXTranslationUnit_KeepGoing);
+
+ CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
+
+ getDefaultArgs(m_defines, m_args);
+
+ if (m_pch) {
+ m_args.push_back("-w");
+ m_args.push_back("-include-pch");
+ m_args.push_back((*m_pch).get().name.constData());
+ }
+
+ TranslationUnit tu;
+ QByteArray s_fn{};
+ for (const auto &ns : std::as_const(context))
+ s_fn.prepend("namespace " + ns.toUtf8() + " {");
+ s_fn += fnSignature.toUtf8();
+ if (!s_fn.endsWith(";"))
+ s_fn += "{ }";
+ s_fn.append(context.size(), '}');
+
+ const char *dummyFileName = fnDummyFileName;
+ CXUnsavedFile unsavedFile { dummyFileName, s_fn.constData(),
+ static_cast<unsigned long>(s_fn.size()) };
+ CXErrorCode err = clang_parseTranslationUnit2(index, dummyFileName, m_args.data(),
+ int(m_args.size()), &unsavedFile, 1, flags, &tu.tu);
+ qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << dummyFileName << m_args
+ << ") returns" << err;
+ printDiagnostics(tu);
+ if (err || !tu) {
+ location.error(QStringLiteral("clang could not parse \\fn %1").arg(fnSignature));
+ return fnNode;
+ } else {
+ /*
+ Always visit the tu if one is constructed, because
+ it might be possible to find the correct node, even
+ if clang detected diagnostics. Only bother to report
+ the diagnostics if they stop us finding the node.
+ */
+ CXCursor cur = clang_getTranslationUnitCursor(tu);
+ ClangVisitor visitor(m_qdb, m_allHeaders);
+ bool ignoreSignature = false;
+ visitor.visitFnArg(cur, &fnNode, ignoreSignature);
+
+ if (!fnNode) {
+ unsigned diagnosticCount = clang_getNumDiagnostics(tu);
+ const auto &config = Config::instance();
+ if (diagnosticCount > 0 && (!config.preparing() || config.singleExec())) {
+ return FnMatchError{ fnSignature, location };
+ }
+ }
+ }
+ return fnNode;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/clangcodeparser.h b/src/qdoc/qdoc/src/qdoc/clangcodeparser.h
new file mode 100644
index 000000000..d7568f9a4
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/clangcodeparser.h
@@ -0,0 +1,94 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CLANGCODEPARSER_H
+#define CLANGCODEPARSER_H
+
+#include "codeparser.h"
+#include "parsererror.h"
+#include "config.h"
+
+#include <QtCore/qtemporarydir.h>
+#include <QtCore/QStringList>
+
+#include <optional>
+
+typedef struct CXTranslationUnitImpl *CXTranslationUnit;
+
+class CppCodeParser;
+
+QT_BEGIN_NAMESPACE
+
+struct ParsedCppFileIR {
+ std::vector<UntiedDocumentation> untied;
+ std::vector<TiedDocumentation> tied;
+};
+
+struct PCHFile {
+ QTemporaryDir dir;
+ QByteArray name;
+};
+
+std::optional<PCHFile> buildPCH(
+ QDocDatabase* qdb,
+ QString module_header,
+ const std::set<Config::HeaderFilePath>& all_headers,
+ const std::vector<QByteArray>& include_paths,
+ const QList<QByteArray>& defines
+);
+
+struct FnCommandParser {
+ FnCommandParser(
+ QDocDatabase* qdb,
+ const std::set<Config::HeaderFilePath>& all_headers,
+ const QList<QByteArray>& defines,
+ std::optional<std::reference_wrapper<const PCHFile>> pch
+ ) : m_qdb{qdb},
+ m_allHeaders{all_headers},
+ m_defines{defines},
+ m_args{},
+ m_pch{pch}
+ {}
+
+ std::variant<Node*, FnMatchError> operator()(
+ const Location &location,
+ const QString &fnSignature,
+ const QString &idTag,
+ QStringList context
+ );
+
+private:
+ QDocDatabase* m_qdb;
+ const std::set<Config::HeaderFilePath>& m_allHeaders; // file name->path
+ QList<QByteArray> m_defines {};
+ std::vector<const char *> m_args {};
+ std::optional<std::reference_wrapper<const PCHFile>> m_pch;
+};
+
+class ClangCodeParser
+{
+public:
+ ClangCodeParser(
+ QDocDatabase* qdb,
+ Config&,
+ const std::vector<QByteArray>& include_paths,
+ const QList<QByteArray>& defines,
+ std::optional<std::reference_wrapper<const PCHFile>> pch
+ );
+
+ ParsedCppFileIR parse_cpp_file(const QString &filePath);
+
+private:
+ QDocDatabase* m_qdb{};
+ std::set<Config::HeaderFilePath> m_allHeaders {}; // file name->path
+ const std::vector<QByteArray>& m_includePaths;
+ QList<QByteArray> m_defines {};
+ std::vector<const char *> m_args {};
+ QStringList m_namespaceScope {};
+ QByteArray s_fn;
+ std::optional<std::reference_wrapper<const PCHFile>> m_pch;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/classnode.cpp b/src/qdoc/qdoc/src/qdoc/classnode.cpp
new file mode 100644
index 000000000..1b132f91e
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/classnode.cpp
@@ -0,0 +1,260 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "classnode.h"
+
+#include "functionnode.h"
+#include "propertynode.h"
+#include "qdocdatabase.h"
+#include "qmltypenode.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class ClassNode
+ \brief The ClassNode represents a C++ class.
+
+ It is also used to represent a C++ struct or union. There are some
+ actual uses for structs, but I don't think any unions have been
+ documented yet.
+ */
+
+/*!
+ Adds the base class \a node to this class's list of base
+ classes. The base class has the specified \a access. This
+ is a resolved base class.
+ */
+void ClassNode::addResolvedBaseClass(Access access, ClassNode *node)
+{
+ m_bases.append(RelatedClass(access, node));
+ node->m_derived.append(RelatedClass(access, this));
+}
+
+/*!
+ Adds the derived class \a node to this class's list of derived
+ classes. The derived class inherits this class with \a access.
+ */
+void ClassNode::addDerivedClass(Access access, ClassNode *node)
+{
+ m_derived.append(RelatedClass(access, node));
+}
+
+/*!
+ Add an unresolved base class to this class node's list of
+ base classes. The unresolved base class will be resolved
+ before the generate phase of qdoc. In an unresolved base
+ class, the pointer to the base class node is 0.
+ */
+void ClassNode::addUnresolvedBaseClass(Access access, const QStringList &path)
+{
+ m_bases.append(RelatedClass(access, path));
+}
+
+/*!
+ Search the child list to find the property node with the
+ specified \a name.
+ */
+PropertyNode *ClassNode::findPropertyNode(const QString &name)
+{
+ Node *n = findNonfunctionChild(name, &Node::isProperty);
+
+ if (n)
+ return static_cast<PropertyNode *>(n);
+
+ PropertyNode *pn = nullptr;
+
+ const QList<RelatedClass> &bases = baseClasses();
+ if (!bases.isEmpty()) {
+ for (const RelatedClass &base : bases) {
+ ClassNode *cn = base.m_node;
+ if (cn) {
+ pn = cn->findPropertyNode(name);
+ if (pn)
+ break;
+ }
+ }
+ }
+ const QList<RelatedClass> &ignoredBases = ignoredBaseClasses();
+ if (!ignoredBases.isEmpty()) {
+ for (const RelatedClass &base : ignoredBases) {
+ ClassNode *cn = base.m_node;
+ if (cn) {
+ pn = cn->findPropertyNode(name);
+ if (pn)
+ break;
+ }
+ }
+ }
+
+ return pn;
+}
+
+/*!
+ \a fn is an overriding function in this class or in a class
+ derived from this class. Find the node for the function that
+ \a fn overrides in this class's children or in one of this
+ class's base classes. Return a pointer to the overridden
+ function or return 0.
+
+ This should be revised because clang provides the path to the
+ overridden function. mws 15/12/2018
+ */
+FunctionNode *ClassNode::findOverriddenFunction(const FunctionNode *fn)
+{
+ for (auto &bc : m_bases) {
+ ClassNode *cn = bc.m_node;
+ if (cn == nullptr) {
+ cn = QDocDatabase::qdocDB()->findClassNode(bc.m_path);
+ bc.m_node = cn;
+ }
+ if (cn != nullptr) {
+ FunctionNode *result = cn->findFunctionChild(fn);
+ if (result != nullptr && !result->isInternal() && !result->isNonvirtual()
+ && result->hasDoc())
+ return result;
+ result = cn->findOverriddenFunction(fn);
+ if (result != nullptr && !result->isNonvirtual())
+ return result;
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ \a fn is an overriding function in this class or in a class
+ derived from this class. Find the node for the property that
+ \a fn overrides in this class's children or in one of this
+ class's base classes. Return a pointer to the overridden
+ property or return 0.
+ */
+PropertyNode *ClassNode::findOverriddenProperty(const FunctionNode *fn)
+{
+ for (auto &baseClass : m_bases) {
+ ClassNode *cn = baseClass.m_node;
+ if (cn == nullptr) {
+ cn = QDocDatabase::qdocDB()->findClassNode(baseClass.m_path);
+ baseClass.m_node = cn;
+ }
+ if (cn != nullptr) {
+ const NodeList &children = cn->childNodes();
+ for (const auto &child : children) {
+ if (child->isProperty()) {
+ auto *pn = static_cast<PropertyNode *>(child);
+ if (pn->name() == fn->name() || pn->hasAccessFunction(fn->name())) {
+ if (pn->hasDoc())
+ return pn;
+ }
+ }
+ }
+ PropertyNode *result = cn->findOverriddenProperty(fn);
+ if (result != nullptr)
+ return result;
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ Returns true if the class or struct represented by this class
+ node must be documented. If this function returns true, then
+ qdoc must find a qdoc comment for this class. If it returns
+ false, then the class need not be documented.
+ */
+bool ClassNode::docMustBeGenerated() const
+{
+ if (!hasDoc() || isPrivate() || isInternal() || isDontDocument())
+ return false;
+ if (declLocation().fileName().endsWith(QLatin1String("_p.h")) && !hasDoc())
+ return false;
+
+ return true;
+}
+
+/*!
+ A base class of this class node was private or internal.
+ That node's list of \a bases is traversed in this function.
+ Each of its public base classes is promoted to be a base
+ class of this node for documentation purposes. For each
+ private or internal class node in \a bases, this function
+ is called recursively with the list of base classes from
+ that private or internal class node.
+ */
+void ClassNode::promotePublicBases(const QList<RelatedClass> &bases)
+{
+ if (!bases.isEmpty()) {
+ for (qsizetype i = bases.size() - 1; i >= 0; --i) {
+ ClassNode *bc = bases.at(i).m_node;
+ if (bc == nullptr)
+ bc = QDocDatabase::qdocDB()->findClassNode(bases.at(i).m_path);
+ if (bc != nullptr) {
+ if (bc->isPrivate() || bc->isInternal())
+ promotePublicBases(bc->baseClasses());
+ else
+ m_bases.append(bases.at(i));
+ }
+ }
+ }
+}
+
+/*!
+ Remove private and internal bases classes from this class's list
+ of base classes. When a base class is removed from the list, add
+ its base classes to this class's list of base classes.
+ */
+void ClassNode::removePrivateAndInternalBases()
+{
+ int i;
+ i = 0;
+ QSet<ClassNode *> found;
+
+ // Remove private and duplicate base classes.
+ while (i < m_bases.size()) {
+ ClassNode *bc = m_bases.at(i).m_node;
+ if (bc == nullptr)
+ bc = QDocDatabase::qdocDB()->findClassNode(m_bases.at(i).m_path);
+ if (bc != nullptr
+ && (bc->isPrivate() || bc->isInternal() || bc->isDontDocument()
+ || found.contains(bc))) {
+ RelatedClass rc = m_bases.at(i);
+ m_bases.removeAt(i);
+ m_ignoredBases.append(rc);
+ promotePublicBases(bc->baseClasses());
+ } else {
+ ++i;
+ }
+ found.insert(bc);
+ }
+
+ i = 0;
+ while (i < m_derived.size()) {
+ ClassNode *dc = m_derived.at(i).m_node;
+ if (dc != nullptr && (dc->isPrivate() || dc->isInternal() || dc->isDontDocument())) {
+ m_derived.removeAt(i);
+ const QList<RelatedClass> &dd = dc->derivedClasses();
+ for (qsizetype j = dd.size() - 1; j >= 0; --j)
+ m_derived.insert(i, dd.at(j));
+ } else {
+ ++i;
+ }
+ }
+}
+
+/*!
+ */
+void ClassNode::resolvePropertyOverriddenFromPtrs(PropertyNode *pn)
+{
+ for (const auto &baseClass : std::as_const(baseClasses())) {
+ ClassNode *cn = baseClass.m_node;
+ if (cn) {
+ Node *n = cn->findNonfunctionChild(pn->name(), &Node::isProperty);
+ if (n) {
+ auto *baseProperty = static_cast<PropertyNode *>(n);
+ cn->resolvePropertyOverriddenFromPtrs(baseProperty);
+ pn->setOverriddenFrom(baseProperty);
+ } else
+ cn->resolvePropertyOverriddenFromPtrs(pn);
+ }
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/classnode.h b/src/qdoc/qdoc/src/qdoc/classnode.h
new file mode 100644
index 000000000..1ac944a34
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/classnode.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CLASSNODE_H
+#define CLASSNODE_H
+
+#include "aggregate.h"
+#include "relatedclass.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class FunctionNode;
+class PropertyNode;
+class QmlTypeNode;
+
+class ClassNode : public Aggregate
+{
+public:
+ ClassNode(NodeType type, Aggregate *parent, const QString &name) : Aggregate(type, parent, name)
+ {
+ }
+ [[nodiscard]] bool isFirstClassAggregate() const override { return true; }
+ [[nodiscard]] bool isClassNode() const override { return true; }
+ [[nodiscard]] bool isRelatableType() const override { return true; }
+ [[nodiscard]] bool isWrapper() const override { return m_wrapper; }
+ void setWrapper() override { m_wrapper = true; }
+
+ void addResolvedBaseClass(Access access, ClassNode *node);
+ void addDerivedClass(Access access, ClassNode *node);
+ void addUnresolvedBaseClass(Access access, const QStringList &path);
+ void removePrivateAndInternalBases();
+ void resolvePropertyOverriddenFromPtrs(PropertyNode *pn);
+
+ QList<RelatedClass> &baseClasses() { return m_bases; }
+ QList<RelatedClass> &derivedClasses() { return m_derived; }
+ QList<RelatedClass> &ignoredBaseClasses() { return m_ignoredBases; }
+
+ [[nodiscard]] const QList<RelatedClass> &baseClasses() const { return m_bases; }
+
+ [[nodiscard]] bool isAbstract() const override { return m_abstract; }
+ void setAbstract(bool b) override { m_abstract = b; }
+ PropertyNode *findPropertyNode(const QString &name);
+ FunctionNode *findOverriddenFunction(const FunctionNode *fn);
+ PropertyNode *findOverriddenProperty(const FunctionNode *fn);
+ [[nodiscard]] bool docMustBeGenerated() const override;
+
+ void insertQmlNativeType(QmlTypeNode *qmlTypeNode) { m_nativeTypeForQml << qmlTypeNode; }
+ bool isQmlNativeType() { return !m_nativeTypeForQml.empty(); }
+ const QSet<QmlTypeNode *> &qmlNativeTypes() { return m_nativeTypeForQml; }
+
+private:
+ void promotePublicBases(const QList<RelatedClass> &bases);
+
+private:
+ QList<RelatedClass> m_bases {};
+ QList<RelatedClass> m_derived {};
+ QList<RelatedClass> m_ignoredBases {};
+ bool m_abstract { false };
+ bool m_wrapper { false };
+ QSet<QmlTypeNode *> m_nativeTypeForQml;
+};
+
+QT_END_NAMESPACE
+
+#endif // CLASSNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/codechunk.cpp b/src/qdoc/qdoc/src/qdoc/codechunk.cpp
new file mode 100644
index 000000000..889e091af
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/codechunk.cpp
@@ -0,0 +1,104 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "codechunk.h"
+
+QT_BEGIN_NAMESPACE
+
+enum { Other, Alnum, Gizmo, Comma, LBrace, RBrace, RAngle, Colon, Paren };
+
+// entries 128 and above are Other
+static const int charCategory[256] = { Other, Other, Other, Other, Other, Other, Other, Other,
+ Other, Other, Other, Other, Other, Other, Other, Other,
+ Other, Other, Other, Other, Other, Other, Other, Other,
+ Other, Other, Other, Other, Other, Other, Other, Other,
+ // ! " # $ % & '
+ Other, Other, Other, Other, Other, Gizmo, Gizmo, Other,
+ // ( ) * + , - . /
+ Paren, Paren, Gizmo, Gizmo, Comma, Other, Other, Gizmo,
+ // 0 1 2 3 4 5 6 7
+ Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum,
+ // 8 9 : ; < = > ?
+ Alnum, Alnum, Colon, Other, Other, Gizmo, RAngle, Gizmo,
+ // @ A B C D E F G
+ Other, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum,
+ // H I J K L M N O
+ Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum,
+ // P Q R S T U V W
+ Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum,
+ // X Y Z [ \ ] ^ _
+ Alnum, Alnum, Alnum, Other, Other, Other, Gizmo, Alnum,
+ // ` a b c d e f g
+ Other, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum,
+ // h i j k l m n o
+ Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum,
+ // p q r s t u v w
+ Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum, Alnum,
+ // x y z { | } ~
+ Alnum, Alnum, Alnum, LBrace, Gizmo, RBrace, Other, Other };
+
+static const bool needSpace[9][9] = {
+ /* [ a + , { } > : ) */
+ /* [ */ { false, false, false, false, false, true, false, false, false },
+ /* a */ { false, true, true, false, false, true, false, false, false },
+ /* + */ { false, true, false, false, false, true, false, true, false },
+ /* , */ { true, true, true, true, true, true, true, true, false },
+ /* { */ { false, false, false, false, false, false, false, false, false },
+ /* } */ { false, false, false, false, false, false, false, false, false },
+ /* > */ { true, true, true, false, true, true, true, false, false },
+ /* : */ { false, false, true, true, true, true, true, false, false },
+ /* ( */ { false, false, false, false, false, false, false, false, false },
+};
+
+static int category(QChar ch)
+{
+ return charCategory[static_cast<int>(ch.toLatin1())];
+}
+
+/*!
+ \class CodeChunk
+
+ \brief The CodeChunk class represents a tiny piece of C++ code.
+
+ \note I think this class should be eliminated (mws 11/12/2018
+
+ The class provides conversion between a list of lexemes and a string. It adds
+ spaces at the right place for consistent style. The tiny pieces of code it
+ represents are data types, enum values, and default parameter values.
+
+ Apart from the piece of code itself, there are two bits of metainformation
+ stored in CodeChunk: the base and the hotspot. The base is the part of the
+ piece that may be a hypertext link. The base of
+
+ QMap<QString, QString>
+
+ is QMap.
+
+ The hotspot is the place the variable name should be inserted in the case of a
+ variable (or parameter) declaration. The hotspot of
+
+ char * []
+
+ is between '*' and '[]'.
+*/
+
+/*!
+ Appends \a lexeme to the current string contents, inserting
+ a space if appropriate.
+ */
+void CodeChunk::append(const QString &lexeme)
+{
+ if (!m_str.isEmpty() && !lexeme.isEmpty()) {
+ /*
+ Should there be a space or not between the code chunk so far and the
+ new lexeme?
+ */
+ int cat1 = category(m_str.at(m_str.size() - 1));
+ int cat2 = category(lexeme[0]);
+ if (needSpace[cat1][cat2])
+ m_str += QLatin1Char(' ');
+ }
+ m_str += lexeme;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/codechunk.h b/src/qdoc/qdoc/src/qdoc/codechunk.h
new file mode 100644
index 000000000..00ad26c6d
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/codechunk.h
@@ -0,0 +1,74 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CODECHUNK_H
+#define CODECHUNK_H
+
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+// ### get rid of that class
+
+class CodeChunk
+{
+public:
+ CodeChunk() : m_hotspot(-1) { }
+
+ void append(const QString &lexeme);
+ void appendHotspot()
+ {
+ if (m_hotspot == -1)
+ m_hotspot = m_str.size();
+ }
+
+ [[nodiscard]] bool isEmpty() const { return m_str.isEmpty(); }
+ void clear() { m_str.clear(); }
+ [[nodiscard]] QString toString() const { return m_str; }
+ [[nodiscard]] QString left() const
+ {
+ return m_str.left(m_hotspot == -1 ? m_str.size() : m_hotspot);
+ }
+ [[nodiscard]] QString right() const
+ {
+ return m_str.mid(m_hotspot == -1 ? m_str.size() : m_hotspot);
+ }
+
+private:
+ QString m_str {};
+ qsizetype m_hotspot {};
+};
+
+inline bool operator==(const CodeChunk &c, const CodeChunk &d)
+{
+ return c.toString() == d.toString();
+}
+
+inline bool operator!=(const CodeChunk &c, const CodeChunk &d)
+{
+ return !(c == d);
+}
+
+inline bool operator<(const CodeChunk &c, const CodeChunk &d)
+{
+ return c.toString() < d.toString();
+}
+
+inline bool operator>(const CodeChunk &c, const CodeChunk &d)
+{
+ return d < c;
+}
+
+inline bool operator<=(const CodeChunk &c, const CodeChunk &d)
+{
+ return !(c > d);
+}
+
+inline bool operator>=(const CodeChunk &c, const CodeChunk &d)
+{
+ return !(c < d);
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/codemarker.cpp b/src/qdoc/qdoc/src/qdoc/codemarker.cpp
new file mode 100644
index 000000000..28f84a946
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/codemarker.cpp
@@ -0,0 +1,448 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "codemarker.h"
+
+#include "classnode.h"
+#include "config.h"
+#include "functionnode.h"
+#include "node.h"
+#include "propertynode.h"
+#include "qmlpropertynode.h"
+
+#include <QtCore/qobjectdefs.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+QString CodeMarker::s_defaultLang;
+QList<CodeMarker *> CodeMarker::s_markers;
+
+
+/*!
+ When a code marker constructs itself, it puts itself into
+ the static list of code markers. All the code markers in
+ the static list get initialized in initialize(), which is
+ not called until after the qdoc configuration file has
+ been read.
+ */
+CodeMarker::CodeMarker()
+{
+ s_markers.prepend(this);
+}
+
+/*!
+ When a code marker destroys itself, it removes itself from
+ the static list of code markers.
+ */
+CodeMarker::~CodeMarker()
+{
+ s_markers.removeAll(this);
+}
+
+/*!
+ A code market performs no initialization by default. Marker-specific
+ initialization is performed in subclasses.
+ */
+void CodeMarker::initializeMarker() {}
+
+/*!
+ Terminating a code marker is trivial.
+ */
+void CodeMarker::terminateMarker()
+{
+ // nothing.
+}
+
+/*!
+ All the code markers in the static list are initialized
+ here, after the qdoc configuration file has been loaded.
+ */
+void CodeMarker::initialize()
+{
+ s_defaultLang = Config::instance().get(CONFIG_LANGUAGE).asString();
+ for (const auto &marker : std::as_const(s_markers))
+ marker->initializeMarker();
+}
+
+/*!
+ All the code markers in the static list are terminated here.
+ */
+void CodeMarker::terminate()
+{
+ for (const auto &marker : std::as_const(s_markers))
+ marker->terminateMarker();
+}
+
+CodeMarker *CodeMarker::markerForCode(const QString &code)
+{
+ CodeMarker *defaultMarker = markerForLanguage(s_defaultLang);
+ if (defaultMarker != nullptr && defaultMarker->recognizeCode(code))
+ return defaultMarker;
+
+ for (const auto &marker : std::as_const(s_markers)) {
+ if (marker->recognizeCode(code))
+ return marker;
+ }
+
+ return defaultMarker;
+}
+
+CodeMarker *CodeMarker::markerForFileName(const QString &fileName)
+{
+ CodeMarker *defaultMarker = markerForLanguage(s_defaultLang);
+ qsizetype dot = -1;
+ while ((dot = fileName.lastIndexOf(QLatin1Char('.'), dot)) != -1) {
+ QString ext = fileName.mid(dot + 1);
+ if (defaultMarker != nullptr && defaultMarker->recognizeExtension(ext))
+ return defaultMarker;
+ for (const auto &marker : std::as_const(s_markers)) {
+ if (marker->recognizeExtension(ext))
+ return marker;
+ }
+ --dot;
+ }
+ return defaultMarker;
+}
+
+CodeMarker *CodeMarker::markerForLanguage(const QString &lang)
+{
+ for (const auto &marker : std::as_const(s_markers)) {
+ if (marker->recognizeLanguage(lang))
+ return marker;
+ }
+ return nullptr;
+}
+
+const Node *CodeMarker::nodeForString(const QString &string)
+{
+#if QT_POINTER_SIZE == 4
+ const quintptr n = string.toUInt();
+#else
+ const quintptr n = string.toULongLong();
+#endif
+ return reinterpret_cast<const Node *>(n);
+}
+
+QString CodeMarker::stringForNode(const Node *node)
+{
+ return QString::number(reinterpret_cast<quintptr>(node));
+}
+
+/*!
+ Returns a string representing the \a node status, set using \preliminary, \since,
+ and \deprecated commands.
+
+ If a string is returned, it is one of:
+ \list
+ \li \c {"preliminary"}
+ \li \c {"since <version_since>, deprecated in <version_deprecated>"}
+ \li \c {"since <version_since>, until <version_deprecated>"}
+ \li \c {"since <version_since>"}
+ \li \c {"since <version_since>, deprecated"}
+ \li \c {"deprecated in <version_deprecated>"}
+ \li \c {"until <version_deprecated>"}
+ \li \c {"deprecated"}
+ \endlist
+
+ If \a node has no related status information, returns std::nullopt.
+*/
+static std::optional<QString> nodeStatusAsString(const Node *node)
+{
+ if (node->isPreliminary())
+ return std::optional(u"preliminary"_s);
+
+ QStringList result;
+ if (const auto &since = node->since(); !since.isEmpty())
+ result << "since %1"_L1.arg(since);
+ if (const auto &deprecated = node->deprecatedSince(); !deprecated.isEmpty()) {
+ if (node->isDeprecated())
+ result << "deprecated in %1"_L1.arg(deprecated);
+ else
+ result << "until %1"_L1.arg(deprecated);
+ } else if (node->isDeprecated()) {
+ result << u"deprecated"_s;
+ }
+
+ return result.isEmpty() ? std::nullopt : std::optional(result.join(u", "_s));
+}
+
+/*!
+ Returns the 'extra' synopsis string for \a node with status information,
+ using a specified section \a style.
+*/
+QString CodeMarker::extraSynopsis(const Node *node, Section::Style style)
+{
+ if (style != Section::Summary && style != Section::Details)
+ return {};
+
+ QStringList extra;
+ if (style == Section::Details) {
+ switch (node->nodeType()) {
+ case Node::Function: {
+ const auto *func = static_cast<const FunctionNode *>(node);
+ if (func->isStatic()) {
+ extra << "static";
+ } else if (!func->isNonvirtual()) {
+ if (func->isFinal())
+ extra << "final";
+ if (func->isOverride())
+ extra << "override";
+ if (func->isPureVirtual())
+ extra << "pure";
+ extra << "virtual";
+ }
+
+ if (func->isExplicit()) extra << "explicit";
+ if (func->isConstexpr()) extra << "constexpr";
+ if (auto noexcept_info = func->getNoexcept()) {
+ extra << (QString("noexcept") + (!(*noexcept_info).isEmpty() ? "(...)" : ""));
+ }
+
+ if (func->access() == Access::Protected)
+ extra << "protected";
+ else if (func->access() == Access::Private)
+ extra << "private";
+
+ if (func->isSignal()) {
+ if (func->parameters().isPrivateSignal())
+ extra << "private";
+ extra << "signal";
+ } else if (func->isSlot())
+ extra << "slot";
+ else if (func->isDefault())
+ extra << "default";
+ else if (func->isInvokable())
+ extra << "invokable";
+ }
+ break;
+ case Node::TypeAlias:
+ extra << "alias";
+ break;
+ case Node::Property: {
+ auto propertyNode = static_cast<const PropertyNode *>(node);
+ if (propertyNode->propertyType() == PropertyNode::PropertyType::BindableProperty)
+ extra << "bindable";
+ if (!propertyNode->isWritable())
+ extra << "read-only";
+ }
+ break;
+ case Node::QmlProperty: {
+ auto qmlProperty = static_cast<const QmlPropertyNode *>(node);
+ if (qmlProperty->isDefault())
+ extra << u"default"_s;
+ // Call non-const overloads to ensure attributes are fetched from
+ // associated C++ properties
+ else if (const_cast<QmlPropertyNode *>(qmlProperty)->isReadOnly())
+ extra << u"read-only"_s;
+ else if (const_cast<QmlPropertyNode *>(qmlProperty)->isRequired())
+ extra << u"required"_s;
+ else if (!qmlProperty->defaultValue().isEmpty()) {
+ extra << u"default: "_s + qmlProperty->defaultValue();
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ // Add status for both Summary and Details
+ if (auto status = nodeStatusAsString(node)) {
+ if (!extra.isEmpty())
+ extra.last() += ','_L1;
+ extra << *status;
+ }
+
+ QString extraStr = extra.join(QLatin1Char(' '));
+ if (!extraStr.isEmpty()) {
+ extraStr.prepend(style == Section::Details ? '[' : '(');
+ extraStr.append(style == Section::Details ? ']' : ')');
+ }
+
+ return extraStr;
+}
+
+static const QString samp = QLatin1String("&amp;");
+static const QString slt = QLatin1String("&lt;");
+static const QString sgt = QLatin1String("&gt;");
+static const QString squot = QLatin1String("&quot;");
+
+QString CodeMarker::protect(const QString &str)
+{
+ qsizetype n = str.size();
+ QString marked;
+ marked.reserve(n * 2 + 30);
+ const QChar *data = str.constData();
+ for (int i = 0; i != n; ++i) {
+ switch (data[i].unicode()) {
+ case '&':
+ marked += samp;
+ break;
+ case '<':
+ marked += slt;
+ break;
+ case '>':
+ marked += sgt;
+ break;
+ case '"':
+ marked += squot;
+ break;
+ default:
+ marked += data[i];
+ }
+ }
+ return marked;
+}
+
+void CodeMarker::appendProtectedString(QString *output, QStringView str)
+{
+ qsizetype n = str.size();
+ output->reserve(output->size() + n * 2 + 30);
+ const QChar *data = str.constData();
+ for (int i = 0; i != n; ++i) {
+ switch (data[i].unicode()) {
+ case '&':
+ *output += samp;
+ break;
+ case '<':
+ *output += slt;
+ break;
+ case '>':
+ *output += sgt;
+ break;
+ case '"':
+ *output += squot;
+ break;
+ default:
+ *output += data[i];
+ }
+ }
+}
+
+QString CodeMarker::typified(const QString &string, bool trailingSpace)
+{
+ QString result;
+ QString pendingWord;
+
+ for (int i = 0; i <= string.size(); ++i) {
+ QChar ch;
+ if (i != string.size())
+ ch = string.at(i);
+
+ QChar lower = ch.toLower();
+ if ((lower >= QLatin1Char('a') && lower <= QLatin1Char('z')) || ch.digitValue() >= 0
+ || ch == QLatin1Char('_') || ch == QLatin1Char(':')) {
+ pendingWord += ch;
+ } else {
+ if (!pendingWord.isEmpty()) {
+ bool isProbablyType = (pendingWord != QLatin1String("const"));
+ if (isProbablyType)
+ result += QLatin1String("<@type>");
+ result += pendingWord;
+ if (isProbablyType)
+ result += QLatin1String("</@type>");
+ }
+ pendingWord.clear();
+
+ switch (ch.unicode()) {
+ case '\0':
+ break;
+ case '&':
+ result += QLatin1String("&amp;");
+ break;
+ case '<':
+ result += QLatin1String("&lt;");
+ break;
+ case '>':
+ result += QLatin1String("&gt;");
+ break;
+ default:
+ result += ch;
+ }
+ }
+ }
+ if (trailingSpace && string.size()) {
+ if (!string.endsWith(QLatin1Char('*')) && !string.endsWith(QLatin1Char('&')))
+ result += QLatin1Char(' ');
+ }
+ return result;
+}
+
+QString CodeMarker::taggedNode(const Node *node)
+{
+ QString tag;
+ const QString &name = node->name();
+
+ switch (node->nodeType()) {
+ case Node::Namespace:
+ tag = QLatin1String("@namespace");
+ break;
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ tag = QLatin1String("@class");
+ break;
+ case Node::Enum:
+ tag = QLatin1String("@enum");
+ break;
+ case Node::TypeAlias:
+ case Node::Typedef:
+ tag = QLatin1String("@typedef");
+ break;
+ case Node::Function:
+ tag = QLatin1String("@function");
+ break;
+ case Node::Property:
+ tag = QLatin1String("@property");
+ break;
+ case Node::QmlType:
+ tag = QLatin1String("@property");
+ break;
+ case Node::Page:
+ tag = QLatin1String("@property");
+ break;
+ default:
+ tag = QLatin1String("@unknown");
+ break;
+ }
+ return (QLatin1Char('<') + tag + QLatin1Char('>') + protect(name) + QLatin1String("</") + tag
+ + QLatin1Char('>'));
+}
+
+QString CodeMarker::taggedQmlNode(const Node *node)
+{
+ QString tag;
+ if (node->isFunction()) {
+ const auto *fn = static_cast<const FunctionNode *>(node);
+ switch (fn->metaness()) {
+ case FunctionNode::QmlSignal:
+ tag = QLatin1String("@signal");
+ break;
+ case FunctionNode::QmlSignalHandler:
+ tag = QLatin1String("@signalhandler");
+ break;
+ case FunctionNode::QmlMethod:
+ tag = QLatin1String("@method");
+ break;
+ default:
+ tag = QLatin1String("@unknown");
+ break;
+ }
+ } else if (node->isQmlProperty()) {
+ tag = QLatin1String("@property");
+ } else {
+ tag = QLatin1String("@unknown");
+ }
+ return QLatin1Char('<') + tag + QLatin1Char('>') + protect(node->name()) + QLatin1String("</")
+ + tag + QLatin1Char('>');
+}
+
+QString CodeMarker::linkTag(const Node *node, const QString &body)
+{
+ return QLatin1String("<@link node=\"") + stringForNode(node) + QLatin1String("\">") + body
+ + QLatin1String("</@link>");
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/codemarker.h b/src/qdoc/qdoc/src/qdoc/codemarker.h
new file mode 100644
index 000000000..af668b650
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/codemarker.h
@@ -0,0 +1,67 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CODEMARKER_H
+#define CODEMARKER_H
+
+#include "atom.h"
+#include "sections.h"
+
+QT_BEGIN_NAMESPACE
+
+class CodeMarker
+{
+public:
+ CodeMarker();
+ virtual ~CodeMarker();
+
+ virtual void initializeMarker();
+ virtual void terminateMarker();
+ virtual bool recognizeCode(const QString & /*code*/) { return true; }
+ virtual bool recognizeExtension(const QString & /*extension*/) { return true; }
+ virtual bool recognizeLanguage(const QString & /*language*/) { return false; }
+ [[nodiscard]] virtual Atom::AtomType atomType() const { return Atom::Code; }
+ virtual QString markedUpCode(const QString &code, const Node * /*relative*/,
+ const Location & /*location*/)
+ {
+ return protect(code);
+ }
+ virtual QString markedUpSynopsis(const Node * /*node*/, const Node * /*relative*/,
+ Section::Style /*style*/)
+ {
+ return QString();
+ }
+ virtual QString markedUpQmlItem(const Node *, bool) { return QString(); }
+ virtual QString markedUpName(const Node * /*node*/) { return QString(); }
+ virtual QString markedUpEnumValue(const QString & /*enumValue*/, const Node * /*relative*/)
+ {
+ return QString();
+ }
+ virtual QString markedUpInclude(const QString & /*include*/) { return QString(); }
+
+ static void initialize();
+ static void terminate();
+ static CodeMarker *markerForCode(const QString &code);
+ static CodeMarker *markerForFileName(const QString &fileName);
+ static CodeMarker *markerForLanguage(const QString &lang);
+ static const Node *nodeForString(const QString &string);
+ static QString stringForNode(const Node *node);
+ static QString extraSynopsis(const Node *node, Section::Style style);
+
+ QString typified(const QString &string, bool trailingSpace = false);
+
+protected:
+ static QString protect(const QString &string);
+ static void appendProtectedString(QString *output, QStringView str);
+ QString taggedNode(const Node *node);
+ QString taggedQmlNode(const Node *node);
+ QString linkTag(const Node *node, const QString &body);
+
+private:
+ static QString s_defaultLang;
+ static QList<CodeMarker *> s_markers;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/codeparser.cpp b/src/qdoc/qdoc/src/qdoc/codeparser.cpp
new file mode 100644
index 000000000..ad35e6d65
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/codeparser.cpp
@@ -0,0 +1,139 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "codeparser.h"
+
+#include "config.h"
+#include "generator.h"
+#include "node.h"
+#include "qdocdatabase.h"
+
+#include <QtCore/qregularexpression.h>
+
+QT_BEGIN_NAMESPACE
+
+QList<CodeParser *> CodeParser::s_parsers;
+
+/*!
+ The constructor adds this code parser to the static
+ list of code parsers.
+ */
+CodeParser::CodeParser()
+{
+ m_qdb = QDocDatabase::qdocDB();
+ s_parsers.prepend(this);
+}
+
+/*!
+ The destructor removes this code parser from the static
+ list of code parsers.
+ */
+CodeParser::~CodeParser()
+{
+ s_parsers.removeAll(this);
+}
+
+/*!
+ Terminating a code parser is trivial.
+ */
+void CodeParser::terminateParser()
+{
+ // nothing.
+}
+
+/*!
+ All the code parsers in the static list are initialized here,
+ after the qdoc configuration variables have been set.
+ */
+void CodeParser::initialize()
+{
+ for (const auto &parser : std::as_const(s_parsers))
+ parser->initializeParser();
+}
+
+/*!
+ All the code parsers in the static list are terminated here.
+ */
+void CodeParser::terminate()
+{
+ for (const auto parser : s_parsers)
+ parser->terminateParser();
+}
+
+CodeParser *CodeParser::parserForLanguage(const QString &language)
+{
+ for (const auto parser : std::as_const(s_parsers)) {
+ if (parser->language() == language)
+ return parser;
+ }
+ return nullptr;
+}
+
+CodeParser *CodeParser::parserForSourceFile(const QString &filePath)
+{
+ QString fileName = QFileInfo(filePath).fileName();
+
+ for (const auto &parser : s_parsers) {
+ const QStringList sourcePatterns = parser->sourceFileNameFilter();
+ for (const QString &pattern : sourcePatterns) {
+ auto re = QRegularExpression::fromWildcard(pattern, Qt::CaseInsensitive);
+ if (re.match(fileName).hasMatch())
+ return parser;
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ \internal
+ */
+void CodeParser::extractPageLinkAndDesc(QStringView arg, QString *link, QString *desc)
+{
+ static const QRegularExpression bracedRegExp(
+ QRegularExpression::anchoredPattern(QLatin1String(R"(\{([^{}]*)\}(?:\{([^{}]*)\})?)")));
+ auto match = bracedRegExp.matchView(arg);
+ if (match.hasMatch()) {
+ *link = match.captured(1);
+ *desc = match.captured(2);
+ if (desc->isEmpty())
+ *desc = *link;
+ } else {
+ qsizetype spaceAt = arg.indexOf(QLatin1Char(' '));
+ if (arg.contains(QLatin1String(".html")) && spaceAt != -1) {
+ *link = arg.left(spaceAt).trimmed().toString();
+ *desc = arg.mid(spaceAt).trimmed().toString();
+ } else {
+ *link = arg.toString();
+ *desc = *link;
+ }
+ }
+}
+
+/*!
+ \internal
+ */
+void CodeParser::setLink(Node *node, Node::LinkType linkType, const QString &arg)
+{
+ QString link;
+ QString desc;
+ extractPageLinkAndDesc(arg, &link, &desc);
+ node->setLink(linkType, link, desc);
+}
+
+/*!
+ \brief Test for whether a doc comment warrants warnings.
+
+ Returns true if qdoc should report that it has found something
+ wrong with the qdoc comment in \a doc. Sometimes, qdoc should
+ not report the warning, for example, when the comment contains
+ the \c internal command, which normally means qdoc will not use
+ the comment in the documentation anyway, so there is no point
+ in reporting warnings about it.
+ */
+bool CodeParser::isWorthWarningAbout(const Doc &doc)
+{
+ return (Config::instance().showInternal()
+ || !doc.metaCommandsUsed().contains(QStringLiteral("internal")));
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/codeparser.h b/src/qdoc/qdoc/src/qdoc/codeparser.h
new file mode 100644
index 000000000..51d1ac2a4
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/codeparser.h
@@ -0,0 +1,142 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CODEPARSER_H
+#define CODEPARSER_H
+
+#include "node.h"
+
+#include <QtCore/qset.h>
+
+QT_BEGIN_NAMESPACE
+
+#define COMMAND_ABSTRACT QLatin1String("abstract")
+#define COMMAND_CLASS QLatin1String("class")
+#define COMMAND_COMPARES QLatin1String("compares")
+#define COMMAND_COMPARESWITH QLatin1String("compareswith")
+#define COMMAND_DEFAULT QLatin1String("default")
+#define COMMAND_DEPRECATED QLatin1String("deprecated") // ### don't document
+#define COMMAND_DONTDOCUMENT QLatin1String("dontdocument")
+#define COMMAND_ENUM QLatin1String("enum")
+#define COMMAND_EXAMPLE QLatin1String("example")
+#define COMMAND_EXTERNALPAGE QLatin1String("externalpage")
+#define COMMAND_FN QLatin1String("fn")
+#define COMMAND_GROUP QLatin1String("group")
+#define COMMAND_HEADERFILE QLatin1String("headerfile")
+#define COMMAND_INGROUP QLatin1String("ingroup")
+#define COMMAND_INHEADERFILE QLatin1String("inheaderfile")
+#define COMMAND_INMODULE QLatin1String("inmodule") // ### don't document
+#define COMMAND_INPUBLICGROUP QLatin1String("inpublicgroup")
+#define COMMAND_INQMLMODULE QLatin1String("inqmlmodule")
+#define COMMAND_INTERNAL QLatin1String("internal")
+#define COMMAND_MACRO QLatin1String("macro")
+#define COMMAND_MODULE QLatin1String("module")
+#define COMMAND_MODULESTATE QLatin1String("modulestate")
+#define COMMAND_NAMESPACE QLatin1String("namespace")
+#define COMMAND_NEXTPAGE QLatin1String("nextpage")
+#define COMMAND_NOAUTOLIST QLatin1String("noautolist")
+#define COMMAND_NONREENTRANT QLatin1String("nonreentrant")
+#define COMMAND_OBSOLETE QLatin1String("obsolete")
+#define COMMAND_OVERLOAD QLatin1String("overload")
+#define COMMAND_PAGE QLatin1String("page")
+#define COMMAND_PRELIMINARY QLatin1String("preliminary")
+#define COMMAND_PREVIOUSPAGE QLatin1String("previouspage")
+#define COMMAND_PROPERTY QLatin1String("property")
+#define COMMAND_QMLABSTRACT QLatin1String("qmlabstract")
+#define COMMAND_QMLATTACHEDMETHOD QLatin1String("qmlattachedmethod")
+#define COMMAND_QMLATTACHEDPROPERTY QLatin1String("qmlattachedproperty")
+#define COMMAND_QMLATTACHEDSIGNAL QLatin1String("qmlattachedsignal")
+#define COMMAND_QMLVALUETYPE QLatin1String("qmlvaluetype")
+#define COMMAND_QMLCLASS QLatin1String("qmlclass")
+#define COMMAND_QMLDEFAULT QLatin1String("qmldefault")
+#define COMMAND_QMLENUMERATORSFROM QLatin1String("qmlenumeratorsfrom")
+#define COMMAND_QMLINHERITS QLatin1String("inherits")
+#define COMMAND_QMLINSTANTIATES QLatin1String("instantiates") // TODO Qt 7.0.0 - Remove: Deprecated since 6.8.
+#define COMMAND_QMLMETHOD QLatin1String("qmlmethod")
+#define COMMAND_QMLMODULE QLatin1String("qmlmodule")
+#define COMMAND_QMLNATIVETYPE QLatin1String("nativetype")
+#define COMMAND_QMLPROPERTY QLatin1String("qmlproperty")
+#define COMMAND_QMLPROPERTYGROUP QLatin1String("qmlpropertygroup")
+#define COMMAND_QMLREADONLY QLatin1String("readonly")
+#define COMMAND_QMLREQUIRED QLatin1String("required")
+#define COMMAND_QMLSIGNAL QLatin1String("qmlsignal")
+#define COMMAND_QMLTYPE QLatin1String("qmltype")
+#define COMMAND_QTCMAKEPACKAGE QLatin1String("qtcmakepackage")
+#define COMMAND_QTCMAKETARGETITEM QLatin1String("qtcmaketargetitem")
+#define COMMAND_QTVARIABLE QLatin1String("qtvariable")
+#define COMMAND_REENTRANT QLatin1String("reentrant")
+#define COMMAND_REIMP QLatin1String("reimp")
+#define COMMAND_RELATES QLatin1String("relates")
+#define COMMAND_SINCE QLatin1String("since")
+#define COMMAND_STRUCT QLatin1String("struct")
+#define COMMAND_SUBTITLE QLatin1String("subtitle")
+#define COMMAND_STARTPAGE QLatin1String("startpage")
+#define COMMAND_THREADSAFE QLatin1String("threadsafe")
+#define COMMAND_TITLE QLatin1String("title")
+#define COMMAND_TYPEALIAS QLatin1String("typealias")
+#define COMMAND_TYPEDEF QLatin1String("typedef")
+#define COMMAND_VARIABLE QLatin1String("variable")
+#define COMMAND_VERSION QLatin1String("version")
+#define COMMAND_UNION QLatin1String("union")
+#define COMMAND_WRAPPER QLatin1String("wrapper")
+#define COMMAND_ATTRIBUTION QLatin1String("attribution")
+
+// deprecated alias of qmlvaluetype
+#define COMMAND_QMLBASICTYPE QLatin1String("qmlbasictype")
+
+class Location;
+class QString;
+class QDocDatabase;
+class CppCodeParser;
+
+struct UntiedDocumentation {
+ Doc documentation;
+ QStringList context;
+};
+
+struct TiedDocumentation {
+ Doc documentation;
+ Node* node;
+};
+
+class CodeParser
+{
+public:
+ static inline const QSet<QString> common_meta_commands{
+ COMMAND_ABSTRACT, COMMAND_DEFAULT, COMMAND_DEPRECATED, COMMAND_INGROUP,
+ COMMAND_INMODULE, COMMAND_INPUBLICGROUP, COMMAND_INQMLMODULE, COMMAND_INTERNAL,
+ COMMAND_MODULESTATE, COMMAND_NOAUTOLIST, COMMAND_NONREENTRANT, COMMAND_OBSOLETE,
+ COMMAND_PRELIMINARY, COMMAND_QMLABSTRACT, COMMAND_QMLDEFAULT, COMMAND_QMLENUMERATORSFROM, COMMAND_QMLINHERITS,
+ COMMAND_QMLREADONLY, COMMAND_QMLREQUIRED, COMMAND_QTCMAKEPACKAGE, COMMAND_QTCMAKETARGETITEM,
+ COMMAND_QTVARIABLE, COMMAND_REENTRANT, COMMAND_SINCE, COMMAND_STARTPAGE, COMMAND_SUBTITLE,
+ COMMAND_THREADSAFE, COMMAND_TITLE, COMMAND_WRAPPER, COMMAND_ATTRIBUTION,
+ };
+
+public:
+ CodeParser();
+ virtual ~CodeParser();
+
+ virtual void initializeParser() = 0;
+ virtual void terminateParser();
+ virtual QString language() = 0;
+ virtual QStringList sourceFileNameFilter() = 0;
+ virtual void parseSourceFile(const Location &location, const QString &filePath, CppCodeParser& cpp_code_parser) = 0;
+
+ static void initialize();
+ static void terminate();
+ static CodeParser *parserForLanguage(const QString &language);
+ static CodeParser *parserForSourceFile(const QString &filePath);
+ static void setLink(Node *node, Node::LinkType linkType, const QString &arg);
+ static bool isWorthWarningAbout(const Doc &doc);
+
+protected:
+ static void extractPageLinkAndDesc(QStringView arg, QString *link, QString *desc);
+ QDocDatabase *m_qdb {};
+
+private:
+ static QList<CodeParser *> s_parsers;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/collectionnode.cpp b/src/qdoc/qdoc/src/qdoc/collectionnode.cpp
new file mode 100644
index 000000000..833a9d037
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/collectionnode.cpp
@@ -0,0 +1,104 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "collectionnode.h"
+
+#include <QtCore/qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class CollectionNode
+ \brief A class for holding the members of a collection of doc pages.
+ */
+
+/*!
+ Appends \a node to the collection node's member list, if
+ and only if it isn't already in the member list.
+ */
+void CollectionNode::addMember(Node *node)
+{
+ if (!m_members.contains(node))
+ m_members.append(node);
+}
+
+/*!
+ Returns \c true if this collection node contains at least
+ one namespace node.
+ */
+bool CollectionNode::hasNamespaces() const
+{
+ return std::any_of(m_members.cbegin(), m_members.cend(), [](const Node *member) {
+ return member->isClassNode() && member->isInAPI();
+ });
+}
+
+/*!
+ Returns \c true if this collection node contains at least
+ one class node.
+ */
+bool CollectionNode::hasClasses() const
+{
+ return std::any_of(m_members.cbegin(), m_members.cend(), [](const Node *member) {
+ return member->isClassNode() && member->isInAPI();
+ });
+}
+
+/*!
+ \fn template <typename F> NodeMap CollectionNode::getMembers(const F &&predicate) const
+
+ Returns a map containing this collection node's member nodes for which \c
+ predicate(node) returns \c true. The \a predicate is a function or a
+ lambda that takes a const Node pointer as an argument and returns a bool.
+*/
+
+/*!
+ \fn NodeMap CollectionNode::getMembers(Node::NodeType type) const
+
+ Returns a map containing this collection node's member nodes with
+ a specified node \a type.
+*/
+
+/*!
+ Returns the logical module version.
+*/
+QString CollectionNode::logicalModuleVersion() const
+{
+ QStringList version;
+ version << m_logicalModuleVersionMajor << m_logicalModuleVersionMinor;
+ version.removeAll(QString());
+ return version.join(".");
+}
+
+/*!
+ This function accepts the logical module \a info as a string
+ list. If the logical module info contains the version number,
+ it splits the version number on the '.' character to get the
+ major and minor version numbers. Both major and minor version
+ numbers should be provided, but the minor version number is
+ not strictly necessary.
+ */
+void CollectionNode::setLogicalModuleInfo(const QStringList &info)
+{
+ m_logicalModuleName = info[0];
+ if (info.size() > 1) {
+ QStringList dotSplit = info[1].split(QLatin1Char('.'));
+ m_logicalModuleVersionMajor = dotSplit[0];
+ if (dotSplit.size() > 1)
+ m_logicalModuleVersionMinor = dotSplit[1];
+ else
+ m_logicalModuleVersionMinor = "0";
+ }
+}
+
+/*!
+ \fn void CollectionNode::setState(const QString &state)
+ \fn QString CollectionNode::state()
+
+ Sets or gets a description of this module's state. For example,
+ \e {"Technical Preview"}. This string is used when generating the
+ module's documentation page and reference pages of the module's
+ members.
+*/
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/collectionnode.h b/src/qdoc/qdoc/src/qdoc/collectionnode.h
new file mode 100644
index 000000000..3756d3534
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/collectionnode.h
@@ -0,0 +1,126 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef COLLECTIONNODE_H
+#define COLLECTIONNODE_H
+
+#include "pagenode.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class CollectionNode : public PageNode
+{
+public:
+ CollectionNode(NodeType type, Aggregate *parent, const QString &name)
+ : PageNode(type, parent, name)
+ {
+ }
+
+ [[nodiscard]] bool isCollectionNode() const override { return true; }
+ [[nodiscard]] QString qtVariable() const override { return m_qtVariable; }
+ void setQtVariable(const QString &v) override { m_qtVariable = v; }
+ [[nodiscard]] QString qtCMakeComponent() const override { return m_qtCMakeComponent; }
+ [[nodiscard]] QString qtCMakeTargetItem() const override { return m_qtCMakeTargetItem; }
+ void setQtCMakeComponent(const QString &target) override { m_qtCMakeComponent = target; }
+ void setQtCMakeTargetItem(const QString &target) override { m_qtCMakeTargetItem = target; }
+ void addMember(Node *node) override;
+ [[nodiscard]] bool hasNamespaces() const override;
+ [[nodiscard]] bool hasClasses() const override;
+ [[nodiscard]] bool wasSeen() const override { return m_seen; }
+
+ [[nodiscard]] QString fullTitle() const override { return title(); }
+ [[nodiscard]] QString logicalModuleName() const override { return m_logicalModuleName; }
+ [[nodiscard]] QString logicalModuleVersion() const override;
+ [[nodiscard]] QString logicalModuleIdentifier() const override
+ {
+ return m_logicalModuleName + m_logicalModuleVersionMajor;
+ }
+ [[nodiscard]] QString state() const { return m_state; }
+
+ template <typename F>
+ NodeMap getMembers(F &&predicate) const
+ {
+ NodeMap result;
+ for (const auto &member : std::as_const(m_members)) {
+ if (std::invoke(predicate, member) && member->isInAPI())
+ result.insert(member->name(), member);
+ }
+ return result;
+ }
+
+ NodeMap getMembers(Node::NodeType type) const
+ {
+ return getMembers([type](const Node *n) {
+ return n->nodeType() == type;
+ });
+ }
+
+ void setLogicalModuleInfo(const QStringList &info) override;
+ void setState(const QString &state) { m_state = state; }
+
+ // REMARK: Those methods are used by QDocDatabase as a performance
+ // detail to avoid merging a collection node multiple times. They
+ // should not be addressed in any other part of the code nor
+ // should their usage appear more than once in QDocDatabase,
+ // albeit this is not enforced.
+ // More information are provided in the comment for the definition
+ // of m_merged.
+ void markMerged() { m_merged = true; }
+ bool isMerged() { return m_merged; }
+
+ [[nodiscard]] const NodeList &members() const { return m_members; }
+
+ void markSeen() { m_seen = true; }
+ void markNotSeen() { m_seen = false; }
+
+private:
+ bool m_seen { false };
+ // REMARK: This is set by the database when merging the collection
+ // node and is later used to avoid merging the same collection
+ // multiple times.
+ // Currently, collection nodes may come from multiple projects,
+ // such that to have a complete overview of the members of a
+ // collection we need to rejoin all members for all instances of
+ // the "same" collection.
+ // This is done in QDocDatabase, generally through an external
+ // method call that is done ad-hoc when a source-of-truth
+ // collection node is needed.
+ // As each part of the code that need such a source-of-truth will
+ // need to merge the node, to avoid the overhead of a relatively
+ // expensive operation being performed multiple times, we expose
+ // this detail so that QDocDatabase can avoid performing the
+ // operation again.
+ // To avoid the coupling, QDocDatabase could keep track of the
+ // merged nodes itself, this is a bit less trivial that this
+ // implementation and doesn't address the source of the problem
+ // (the multiple merges themselves and the sequencing of the
+ // related operations) and as such was discarded in favor of this
+ // simpler implementation.
+ // Do note that outside the very specific purpose for which this
+ // member was made, no part of the code should refer to it and its
+ // associated methods.
+ // Should this start to be the case, we can switch to the more
+ // complex encapsulation into QDocDatabase without having to touch
+ // the outside user of the merges.
+ // Further down the line, this is expected to go away completely
+ // as other part of the code are streamlined.
+ // KLUDGE: Note that this whole exposure is done as a hackish
+ // solution to QTBUG-104237 and should not be considered final or
+ // dependable.
+ bool m_merged { false };
+ NodeList m_members {};
+ QString m_logicalModuleName {};
+ QString m_logicalModuleVersionMajor {};
+ QString m_logicalModuleVersionMinor {};
+ QString m_qtVariable {};
+ QString m_qtCMakeComponent {};
+ QString m_qtCMakeTargetItem {};
+ QString m_state {};
+};
+
+QT_END_NAMESPACE
+
+#endif // COLLECTIONNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/comparisoncategory.cpp b/src/qdoc/qdoc/src/qdoc/comparisoncategory.cpp
new file mode 100644
index 000000000..98f7aaf66
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/comparisoncategory.cpp
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*!
+ \enum ComparisonCategory
+ \internal
+
+ \value None No comparison is defined.
+ \value Strong Strong comparison is defined, see std::strong_ordering.
+ \value Weak Weak comparison is defined, see std::weak_ordering.
+ \value Partial A partial ordering is defined, see std::partial_ordering.
+ \value Equality Only (in)equality comparison is defined.
+*/
+
+/*!
+ \fn static inline std::string comparisonCategoryAsString(const ComparisonCategory &category)
+ \internal
+
+ Returns a string representation of the comparison category \a category.
+*/
+
+/*!
+ \fn static ComparisonCategory comparisonCategoryFromString(const std::string &string)
+ \internal
+
+ Returns a matching comparison category for a \a string representation, or
+ ComparisonCategory::None for an unknown category string.
+*/
diff --git a/src/qdoc/qdoc/src/qdoc/comparisoncategory.h b/src/qdoc/qdoc/src/qdoc/comparisoncategory.h
new file mode 100644
index 000000000..d3f36654b
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/comparisoncategory.h
@@ -0,0 +1,54 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef COMPARISONCATEGORY_H
+#define COMPARISONCATEGORY_H
+
+#include <string>
+
+QT_BEGIN_NAMESPACE
+
+enum struct ComparisonCategory : unsigned char {
+ None,
+ Strong,
+ Weak,
+ Partial,
+ Equality
+};
+
+static inline std::string comparisonCategoryAsString(ComparisonCategory category)
+{
+ switch (category) {
+ case ComparisonCategory::Strong:
+ return "strong";
+ case ComparisonCategory::Weak:
+ return "weak";
+ case ComparisonCategory::Partial:
+ return "partial";
+ case ComparisonCategory::Equality:
+ return "equality";
+ case ComparisonCategory::None:
+ [[fallthrough]];
+ default:
+ break;
+ }
+ return {};
+}
+
+static inline ComparisonCategory comparisonCategoryFromString(const std::string &string)
+{
+ if (string == "strong")
+ return ComparisonCategory::Strong;
+ if (string == "weak")
+ return ComparisonCategory::Weak;
+ if (string == "partial")
+ return ComparisonCategory::Partial;
+ if (string == "equality")
+ return ComparisonCategory::Equality;
+
+ return ComparisonCategory::None;
+}
+
+QT_END_NAMESPACE
+
+#endif // COMPARISONCATEGORY_H
diff --git a/src/qdoc/qdoc/src/qdoc/config.cpp b/src/qdoc/qdoc/src/qdoc/config.cpp
new file mode 100644
index 000000000..de987dae8
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/config.cpp
@@ -0,0 +1,1439 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "config.h"
+#include "utilities.h"
+
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qtemporaryfile.h>
+#include <QtCore/qtextstream.h>
+#include <QtCore/qvariant.h>
+#include <QtCore/qregularexpression.h>
+
+QT_BEGIN_NAMESPACE
+
+QString ConfigStrings::AUTOLINKERRORS = QStringLiteral("autolinkerrors");
+QString ConfigStrings::BUILDVERSION = QStringLiteral("buildversion");
+QString ConfigStrings::CODEINDENT = QStringLiteral("codeindent");
+QString ConfigStrings::CODEPREFIX = QStringLiteral("codeprefix");
+QString ConfigStrings::CODESUFFIX = QStringLiteral("codesuffix");
+QString ConfigStrings::CPPCLASSESPAGE = QStringLiteral("cppclassespage");
+QString ConfigStrings::CPPCLASSESTITLE = QStringLiteral("cppclassestitle");
+QString ConfigStrings::DEFINES = QStringLiteral("defines");
+QString ConfigStrings::DEPENDS = QStringLiteral("depends");
+QString ConfigStrings::DESCRIPTION = QStringLiteral("description");
+QString ConfigStrings::DOCBOOKEXTENSIONS = QStringLiteral("usedocbookextensions");
+QString ConfigStrings::ENDHEADER = QStringLiteral("endheader");
+QString ConfigStrings::EXAMPLEDIRS = QStringLiteral("exampledirs");
+QString ConfigStrings::EXAMPLES = QStringLiteral("examples");
+QString ConfigStrings::EXAMPLESINSTALLPATH = QStringLiteral("examplesinstallpath");
+QString ConfigStrings::EXCLUDEDIRS = QStringLiteral("excludedirs");
+QString ConfigStrings::EXCLUDEFILES = QStringLiteral("excludefiles");
+QString ConfigStrings::EXTRAIMAGES = QStringLiteral("extraimages");
+QString ConfigStrings::FALSEHOODS = QStringLiteral("falsehoods");
+QString ConfigStrings::FORMATTING = QStringLiteral("formatting");
+QString ConfigStrings::HEADERDIRS = QStringLiteral("headerdirs");
+QString ConfigStrings::HEADERS = QStringLiteral("headers");
+QString ConfigStrings::HEADERSCRIPTS = QStringLiteral("headerscripts");
+QString ConfigStrings::HEADERSTYLES = QStringLiteral("headerstyles");
+QString ConfigStrings::HOMEPAGE = QStringLiteral("homepage");
+QString ConfigStrings::HOMETITLE = QStringLiteral("hometitle");
+QString ConfigStrings::IGNOREDIRECTIVES = QStringLiteral("ignoredirectives");
+QString ConfigStrings::IGNORESINCE = QStringLiteral("ignoresince");
+QString ConfigStrings::IGNORETOKENS = QStringLiteral("ignoretokens");
+QString ConfigStrings::IGNOREWORDS = QStringLiteral("ignorewords");
+QString ConfigStrings::IMAGEDIRS = QStringLiteral("imagedirs");
+QString ConfigStrings::INCLUDEPATHS = QStringLiteral("includepaths");
+QString ConfigStrings::INCLUSIVE = QStringLiteral("inclusive");
+QString ConfigStrings::INDEXES = QStringLiteral("indexes");
+QString ConfigStrings::LANDINGPAGE = QStringLiteral("landingpage");
+QString ConfigStrings::LANDINGTITLE = QStringLiteral("landingtitle");
+QString ConfigStrings::LANGUAGE = QStringLiteral("language");
+QString ConfigStrings::LOCATIONINFO = QStringLiteral("locationinfo");
+QString ConfigStrings::LOGPROGRESS = QStringLiteral("logprogress");
+QString ConfigStrings::MACRO = QStringLiteral("macro");
+QString ConfigStrings::MANIFESTMETA = QStringLiteral("manifestmeta");
+QString ConfigStrings::MODULEHEADER = QStringLiteral("moduleheader");
+QString ConfigStrings::NATURALLANGUAGE = QStringLiteral("naturallanguage");
+QString ConfigStrings::NAVIGATION = QStringLiteral("navigation");
+QString ConfigStrings::NOLINKERRORS = QStringLiteral("nolinkerrors");
+QString ConfigStrings::OUTPUTDIR = QStringLiteral("outputdir");
+QString ConfigStrings::OUTPUTFORMATS = QStringLiteral("outputformats");
+QString ConfigStrings::OUTPUTPREFIXES = QStringLiteral("outputprefixes");
+QString ConfigStrings::OUTPUTSUFFIXES = QStringLiteral("outputsuffixes");
+QString ConfigStrings::PROJECT = QStringLiteral("project");
+QString ConfigStrings::REDIRECTDOCUMENTATIONTODEVNULL =
+ QStringLiteral("redirectdocumentationtodevnull");
+QString ConfigStrings::QHP = QStringLiteral("qhp");
+QString ConfigStrings::QUOTINGINFORMATION = QStringLiteral("quotinginformation");
+QString ConfigStrings::SCRIPTS = QStringLiteral("scripts");
+QString ConfigStrings::SHOWINTERNAL = QStringLiteral("showinternal");
+QString ConfigStrings::SINGLEEXEC = QStringLiteral("singleexec");
+QString ConfigStrings::SOURCEDIRS = QStringLiteral("sourcedirs");
+QString ConfigStrings::SOURCEENCODING = QStringLiteral("sourceencoding");
+QString ConfigStrings::SOURCES = QStringLiteral("sources");
+QString ConfigStrings::SPURIOUS = QStringLiteral("spurious");
+QString ConfigStrings::STYLESHEETS = QStringLiteral("stylesheets");
+QString ConfigStrings::SYNTAXHIGHLIGHTING = QStringLiteral("syntaxhighlighting");
+QString ConfigStrings::TABSIZE = QStringLiteral("tabsize");
+QString ConfigStrings::TAGFILE = QStringLiteral("tagfile");
+QString ConfigStrings::TIMESTAMPS = QStringLiteral("timestamps");
+QString ConfigStrings::TOCTITLES = QStringLiteral("toctitles");
+QString ConfigStrings::TRADEMARKSPAGE = QStringLiteral("trademarkspage");
+QString ConfigStrings::URL = QStringLiteral("url");
+QString ConfigStrings::VERSION = QStringLiteral("version");
+QString ConfigStrings::VERSIONSYM = QStringLiteral("versionsym");
+QString ConfigStrings::FILEEXTENSIONS = QStringLiteral("fileextensions");
+QString ConfigStrings::IMAGEEXTENSIONS = QStringLiteral("imageextensions");
+QString ConfigStrings::QMLTYPESPAGE = QStringLiteral("qmltypespage");
+QString ConfigStrings::QMLTYPESTITLE = QStringLiteral("qmltypestitle");
+QString ConfigStrings::WARNINGLIMIT = QStringLiteral("warninglimit");
+
+/*!
+ An entry in a stack, where each entry is a list
+ of string values.
+ */
+class MetaStackEntry
+{
+public:
+ void open();
+ void close();
+
+ QStringList accum;
+ QStringList next;
+};
+Q_DECLARE_TYPEINFO(MetaStackEntry, Q_RELOCATABLE_TYPE);
+
+/*!
+ Start accumulating values in a list by appending an empty
+ string to the list.
+ */
+void MetaStackEntry::open()
+{
+ next.append(QString());
+}
+
+/*!
+ Stop accumulating values and append the list of accumulated
+ values to the complete list of accumulated values.
+
+ */
+void MetaStackEntry::close()
+{
+ accum += next;
+ next.clear();
+}
+
+/*!
+ \class MetaStack
+
+ This class maintains a stack of values of config file variables.
+*/
+class MetaStack : private QStack<MetaStackEntry>
+{
+public:
+ MetaStack();
+
+ void process(QChar ch, const Location &location);
+ QStringList getExpanded(const Location &location);
+};
+
+/*!
+ The default constructor pushes a new stack entry and
+ opens it.
+ */
+MetaStack::MetaStack()
+{
+ push(MetaStackEntry());
+ top().open();
+}
+
+/*!
+ Processes the character \a ch using the \a location.
+ It really just builds up a name by appending \a ch to
+ it.
+ */
+void MetaStack::process(QChar ch, const Location &location)
+{
+ if (ch == QLatin1Char('{')) {
+ push(MetaStackEntry());
+ top().open();
+ } else if (ch == QLatin1Char('}')) {
+ if (size() == 1)
+ location.fatal(QStringLiteral("Unexpected '}'"));
+
+ top().close();
+ const QStringList suffixes = pop().accum;
+ const QStringList prefixes = top().next;
+
+ top().next.clear();
+ for (const auto &prefix : prefixes) {
+ for (const auto &suffix : suffixes)
+ top().next << prefix + suffix;
+ }
+ } else if (ch == QLatin1Char(',') && size() > 1) {
+ top().close();
+ top().open();
+ } else {
+ for (QString &topNext : top().next)
+ topNext += ch;
+ }
+}
+
+/*!
+ Returns the accumulated string values.
+ */
+QStringList MetaStack::getExpanded(const Location &location)
+{
+ if (size() > 1)
+ location.fatal(QStringLiteral("Missing '}'"));
+
+ top().close();
+ return top().accum;
+}
+
+const QString Config::dot = QLatin1String(".");
+bool Config::m_debug = false;
+bool Config::m_atomsDump = false;
+bool Config::generateExamples = true;
+QString Config::overrideOutputDir;
+QString Config::installDir;
+QSet<QString> Config::overrideOutputFormats;
+QMap<QString, QString> Config::m_extractedDirs;
+QStack<QString> Config::m_workingDirs;
+QMap<QString, QStringList> Config::m_includeFilesMap;
+
+/*!
+ \class ConfigVar
+ \brief contains all the information for a single config variable in a
+ .qdocconf file.
+*/
+
+/*!
+ Returns this configuration variable as a string.
+
+ If the variable is not defined, returns \a defaultString.
+
+ \note By default, \a defaultString is a null string.
+ This allows determining whether a configuration variable is
+ undefined (returns a null string) or defined as empty
+ (returns a non-null, empty string).
+*/
+QString ConfigVar::asString(const QString defaultString) const
+{
+ if (m_name.isEmpty())
+ return defaultString;
+
+ QString result(""); // an empty but non-null string
+ for (const auto &value : std::as_const(m_values)) {
+ if (!result.isEmpty() && !result.endsWith(QChar('\n')))
+ result.append(QChar(' '));
+ result.append(value.m_value);
+ }
+ return result;
+}
+
+/*!
+ Returns this config variable as a string list.
+*/
+QStringList ConfigVar::asStringList() const
+{
+ QStringList result;
+ for (const auto &value : std::as_const(m_values))
+ result << value.m_value;
+ return result;
+}
+
+/*!
+ Returns this config variable as a string set.
+*/
+QSet<QString> ConfigVar::asStringSet() const
+{
+ const auto &stringList = asStringList();
+ return QSet<QString>(stringList.cbegin(), stringList.cend());
+}
+
+/*!
+ Returns this config variable as a boolean.
+*/
+bool ConfigVar::asBool() const
+{
+ return QVariant(asString()).toBool();
+}
+
+/*!
+ Returns this configuration variable as an integer; iterates
+ through the string list, interpreting each
+ string in the list as an integer and adding it to a total sum.
+
+ Returns 0 if this variable is defined as empty, and
+ -1 if it's is not defined.
+ */
+int ConfigVar::asInt() const
+{
+ const QStringList strs = asStringList();
+ if (strs.isEmpty())
+ return -1;
+
+ int sum = 0;
+ for (const auto &str : strs)
+ sum += str.toInt();
+ return sum;
+}
+
+/*!
+ Appends values to this ConfigVar, and adjusts the ExpandVar
+ parameters so that they continue to refer to the correct values.
+*/
+void ConfigVar::append(const ConfigVar &other)
+{
+ m_expandVars << other.m_expandVars;
+ QList<ExpandVar>::Iterator it = m_expandVars.end();
+ it -= other.m_expandVars.size();
+ std::for_each(it, m_expandVars.end(), [this](ExpandVar &v) {
+ v.m_valueIndex += m_values.size();
+ });
+ m_values << other.m_values;
+ m_location = other.m_location;
+}
+
+/*!
+ \class Config
+ \brief The Config class contains the configuration variables
+ for controlling how qdoc produces documentation.
+
+ Its load() function reads, parses, and processes a qdocconf file.
+ */
+
+/*!
+ \enum Config::PathFlags
+
+ Flags used for retrieving canonicalized paths from Config.
+
+ \value Validate
+ Issue a warning for paths that do not exist and
+ remove them from the returned list.
+
+ \value IncludePaths
+ Assume the variable contains include paths with
+ prefixes such as \c{-I} that are to be removed
+ before canonicalizing and then re-inserted.
+
+ \omitvalue None
+
+ \sa getCanonicalPathList()
+*/
+
+/*!
+ Initializes the Config with \a programName and sets all
+ internal state variables to either default values or to ones
+ defined in command line arguments \a args.
+ */
+void Config::init(const QString &programName, const QStringList &args)
+{
+ m_prog = programName;
+ processCommandLineOptions(args);
+ reset();
+}
+
+Config::~Config()
+{
+ clear();
+}
+
+/*!
+ Clears the location and internal maps for config variables.
+ */
+void Config::clear()
+{
+ m_location = Location();
+ m_configVars.clear();
+ m_includeFilesMap.clear();
+ m_excludedPaths.reset();
+}
+
+/*!
+ Resets the Config instance - used by load()
+ */
+void Config::reset()
+{
+ clear();
+
+ // Default values
+ setStringList(CONFIG_CODEINDENT, QStringList("0"));
+ setStringList(CONFIG_FALSEHOODS, QStringList("0"));
+ setStringList(CONFIG_HEADERS + dot + CONFIG_FILEEXTENSIONS, QStringList("*.ch *.h *.h++ *.hh *.hpp *.hxx"));
+ setStringList(CONFIG_SOURCES + dot + CONFIG_FILEEXTENSIONS, QStringList("*.c++ *.cc *.cpp *.cxx *.mm *.qml *.qdoc"));
+ setStringList(CONFIG_LANGUAGE, QStringList("Cpp")); // i.e. C++
+ setStringList(CONFIG_OUTPUTFORMATS, QStringList("HTML"));
+ setStringList(CONFIG_TABSIZE, QStringList("8"));
+ setStringList(CONFIG_LOCATIONINFO, QStringList("true"));
+
+ // Publish options from the command line as config variables
+ const auto setListFlag = [this](const QString &key, bool test) {
+ setStringList(key, QStringList(test ? QStringLiteral("true") : QStringLiteral("false")));
+ };
+#define SET(opt, test) setListFlag(opt, m_parser.isSet(m_parser.test))
+ SET(CONFIG_SYNTAXHIGHLIGHTING, highlightingOption);
+ SET(CONFIG_SHOWINTERNAL, showInternalOption);
+ SET(CONFIG_SINGLEEXEC, singleExecOption);
+ SET(CONFIG_REDIRECTDOCUMENTATIONTODEVNULL, redirectDocumentationToDevNullOption);
+ SET(CONFIG_AUTOLINKERRORS, autoLinkErrorsOption);
+#undef SET
+ m_showInternal = m_configVars.value(CONFIG_SHOWINTERNAL).asBool();
+ setListFlag(CONFIG_NOLINKERRORS,
+ m_parser.isSet(m_parser.noLinkErrorsOption)
+ || qEnvironmentVariableIsSet("QDOC_NOLINKERRORS"));
+
+ // CONFIG_DEFINES and CONFIG_INCLUDEPATHS are set in load()
+}
+
+/*!
+ Loads and parses the qdoc configuration file \a fileName.
+ If a previous project was loaded, this function first resets the
+ Config instance. Then it calls the other load() function, which
+ does the loading, parsing, and processing of the configuration file.
+ */
+void Config::load(const QString &fileName)
+{
+ // Reset if a previous project was loaded
+ if (m_configVars.contains(CONFIG_PROJECT))
+ reset();
+
+ load(Location(), fileName);
+ if (m_location.isEmpty())
+ m_location = Location(fileName);
+ else
+ m_location.setEtc(true);
+
+ expandVariables();
+
+ // Add defines and includepaths from command line to their
+ // respective configuration variables. Values set here are
+ // always added to what's defined in configuration file.
+ insertStringList(CONFIG_DEFINES, m_defines);
+ insertStringList(CONFIG_INCLUDEPATHS, m_includePaths);
+
+ // Prefetch values that are used internally
+ m_exampleFiles = getCanonicalPathList(CONFIG_EXAMPLES);
+ m_exampleDirs = getCanonicalPathList(CONFIG_EXAMPLEDIRS);
+}
+
+/*!
+ Expands other config variables referred to in all stored ConfigVars.
+*/
+void Config::expandVariables()
+{
+ for (auto &configVar : m_configVars) {
+ for (auto it = configVar.m_expandVars.crbegin(); it != configVar.m_expandVars.crend(); ++it) {
+ Q_ASSERT(it->m_valueIndex < configVar.m_values.size());
+ const QString &key = it->m_var;
+ const auto &refVar = m_configVars.value(key);
+ if (refVar.m_name.isEmpty()) {
+ configVar.m_location.fatal(
+ QStringLiteral("Environment or configuration variable '%1' undefined")
+ .arg(it->m_var));
+ } else if (!refVar.m_expandVars.empty()) {
+ configVar.m_location.fatal(
+ QStringLiteral("Nested variable expansion not allowed"),
+ QStringLiteral("When expanding '%1' at %2:%3")
+ .arg(refVar.m_name, refVar.m_location.filePath(),
+ QString::number(refVar.m_location.lineNo())));
+ }
+ QString expanded;
+ if (it->m_delim.isNull())
+ expanded = m_configVars.value(key).asStringList().join(QString());
+ else
+ expanded = m_configVars.value(key).asStringList().join(it->m_delim);
+ configVar.m_values[it->m_valueIndex].m_value.insert(it->m_index, expanded);
+ }
+ configVar.m_expandVars.clear();
+ }
+}
+
+/*!
+ Sets the \a values of a configuration variable \a var from a string list.
+ */
+void Config::setStringList(const QString &var, const QStringList &values)
+{
+ m_configVars.insert(var, ConfigVar(var, values, QDir::currentPath()));
+}
+
+/*!
+ Adds the \a values from a string list to the configuration variable \a var.
+ Existing value(s) are kept.
+*/
+void Config::insertStringList(const QString &var, const QStringList &values)
+{
+ m_configVars[var].append(ConfigVar(var, values, QDir::currentPath()));
+}
+
+/*!
+ Process and store variables from the command line.
+ */
+void Config::processCommandLineOptions(const QStringList &args)
+{
+ m_parser.process(args);
+
+ m_defines = m_parser.values(m_parser.defineOption);
+ m_dependModules = m_parser.values(m_parser.dependsOption);
+ setIndexDirs();
+ setIncludePaths();
+
+ generateExamples = !m_parser.isSet(m_parser.noExamplesOption);
+ if (m_parser.isSet(m_parser.installDirOption))
+ installDir = m_parser.value(m_parser.installDirOption);
+ if (m_parser.isSet(m_parser.outputDirOption))
+ overrideOutputDir = QDir(m_parser.value(m_parser.outputDirOption)).absolutePath();
+
+ const auto outputFormats = m_parser.values(m_parser.outputFormatOption);
+ for (const auto &format : outputFormats)
+ overrideOutputFormats.insert(format);
+ m_debug = m_parser.isSet(m_parser.debugOption) || qEnvironmentVariableIsSet("QDOC_DEBUG");
+ m_atomsDump = m_parser.isSet(m_parser.atomsDumpOption);
+ m_showInternal = m_parser.isSet(m_parser.showInternalOption)
+ || qEnvironmentVariableIsSet("QDOC_SHOW_INTERNAL");
+
+ if (m_parser.isSet(m_parser.prepareOption))
+ m_qdocPass = Prepare;
+ if (m_parser.isSet(m_parser.generateOption))
+ m_qdocPass = Generate;
+ if (m_debug || m_parser.isSet(m_parser.logProgressOption))
+ setStringList(CONFIG_LOGPROGRESS, QStringList("true"));
+ if (m_parser.isSet(m_parser.timestampsOption))
+ setStringList(CONFIG_TIMESTAMPS, QStringList("true"));
+ if (m_parser.isSet(m_parser.useDocBookExtensions))
+ setStringList(CONFIG_DOCBOOKEXTENSIONS, QStringList("true"));
+}
+
+void Config::setIncludePaths()
+{
+ QDir currentDir = QDir::current();
+ const auto addIncludePaths = [this, currentDir](const char *flag, const QStringList &paths) {
+ for (const auto &path : paths)
+ m_includePaths << currentDir.absoluteFilePath(path).insert(0, flag);
+ };
+
+ addIncludePaths("-I", m_parser.values(m_parser.includePathOption));
+#ifdef QDOC_PASS_ISYSTEM
+ addIncludePaths("-isystem", m_parser.values(m_parser.includePathSystemOption));
+#endif
+ addIncludePaths("-F", m_parser.values(m_parser.frameworkOption));
+}
+
+/*!
+ Stores paths from -indexdir command line option(s).
+ */
+void Config::setIndexDirs()
+{
+ m_indexDirs = m_parser.values(m_parser.indexDirOption);
+ auto it = std::remove_if(m_indexDirs.begin(), m_indexDirs.end(),
+ [](const QString &s) { return !QFile::exists(s); });
+
+ std::for_each(it, m_indexDirs.end(), [](const QString &s) {
+ qCWarning(lcQdoc) << "Cannot find index directory: " << s;
+ });
+ m_indexDirs.erase(it, m_indexDirs.end());
+}
+
+/*!
+ Function to return the correct outputdir for the output \a format.
+ If \a format is not specified, defaults to 'HTML'.
+ outputdir can be set using the qdocconf or the command-line
+ variable -outputdir.
+ */
+QString Config::getOutputDir(const QString &format) const
+{
+ QString t;
+ if (overrideOutputDir.isNull())
+ t = m_configVars.value(CONFIG_OUTPUTDIR).asString();
+ else
+ t = overrideOutputDir;
+ if (m_configVars.value(CONFIG_SINGLEEXEC).asBool()) {
+ QString project = m_configVars.value(CONFIG_PROJECT).asString();
+ t += QLatin1Char('/') + project.toLower();
+ }
+ if (m_configVars.value(format + Config::dot + "nosubdirs").asBool()) {
+ QString singleOutputSubdir = m_configVars.value(format + Config::dot + "outputsubdir").asString();
+ if (singleOutputSubdir.isEmpty())
+ singleOutputSubdir = "html";
+ t += QLatin1Char('/') + singleOutputSubdir;
+ }
+ return QDir::cleanPath(t);
+}
+
+/*!
+ Function to return the correct outputformats.
+ outputformats can be set using the qdocconf or the command-line
+ variable -outputformat.
+ */
+QSet<QString> Config::getOutputFormats() const
+{
+ if (overrideOutputFormats.isEmpty())
+ return m_configVars.value(CONFIG_OUTPUTFORMATS).asStringSet();
+ else
+ return overrideOutputFormats;
+}
+
+// TODO: [late-canonicalization][pod-configuration]
+// The canonicalization for paths is done at the time where they are
+// required, and done each time they are requested.
+// Instead, config should be parsed to an intermediate format that is
+// a POD type that already contains canonicalized representations for
+// each element.
+// Those representations should provide specific guarantees about
+// their format and be representable at the API boundaries.
+//
+// This would ensure that the correct canonicalization is always
+// applied, is applied only once and that dependent sub-logics can be
+// written in a way that doesn't require branching or futher
+// canonicalization.
+
+/*!
+ Returns a path list where all paths from the config variable \a var
+ are canonicalized. If \a flags contains \c Validate, outputs a warning
+ for invalid paths. The \c IncludePaths flag is used as a hint to strip
+ away potential prefixes found in include paths before attempting to
+ canonicalize.
+ */
+QStringList Config::getCanonicalPathList(const QString &var, PathFlags flags) const
+{
+ QStringList result;
+ const auto &configVar = m_configVars.value(var);
+
+ for (const auto &value : configVar.m_values) {
+ const QString &currentPath = value.m_path;
+ QString rawValue = value.m_value.simplified();
+ QString prefix;
+
+ if (flags & IncludePaths) {
+ const QStringList prefixes = QStringList()
+ << QLatin1String("-I")
+ << QLatin1String("-F")
+ << QLatin1String("-isystem");
+ const auto end = std::end(prefixes);
+ const auto it =
+ std::find_if(std::begin(prefixes), end,
+ [&rawValue](const QString &p) {
+ return rawValue.startsWith(p);
+ });
+ if (it != end) {
+ prefix = *it;
+ rawValue.remove(0, it->size());
+ if (rawValue.isEmpty())
+ continue;
+ } else {
+ prefix = prefixes[0]; // -I as default
+ }
+ }
+
+ QDir dir(rawValue.trimmed());
+ const QString path = dir.path();
+
+ if (dir.isRelative())
+ dir.setPath(currentPath + QLatin1Char('/') + path);
+ if ((flags & Validate) && !QFileInfo::exists(dir.path()))
+ configVar.m_location.warning(QStringLiteral("Cannot find file or directory: %1").arg(path));
+ else {
+ const QString canonicalPath = dir.canonicalPath();
+ if (!canonicalPath.isEmpty())
+ result.append(prefix + canonicalPath);
+ else if (path.contains(QLatin1Char('*')) || path.contains(QLatin1Char('?')))
+ result.append(path);
+ else
+ qCDebug(lcQdoc) <<
+ qUtf8Printable(QStringLiteral("%1: Ignored nonexistent path \'%2\'")
+ .arg(configVar.m_location.toString(), rawValue));
+ }
+ }
+ return result;
+}
+
+/*!
+ Calls getRegExpList() with the control variable \a var and
+ iterates through the resulting list of regular expressions,
+ concatenating them with extra characters to form a single
+ QRegularExpression, which is then returned.
+
+ \sa getRegExpList()
+ */
+QRegularExpression Config::getRegExp(const QString &var) const
+{
+ QString pattern;
+ const auto subRegExps = getRegExpList(var);
+
+ for (const auto &regExp : subRegExps) {
+ if (!regExp.isValid())
+ return regExp;
+ if (!pattern.isEmpty())
+ pattern += QLatin1Char('|');
+ pattern += QLatin1String("(?:") + regExp.pattern() + QLatin1Char(')');
+ }
+ if (pattern.isEmpty())
+ pattern = QLatin1String("$x"); // cannot match
+ return QRegularExpression(pattern);
+}
+
+/*!
+ Looks up the configuration variable \a var in the string list
+ map, converts the string list to a list of regular expressions,
+ and returns it.
+ */
+QList<QRegularExpression> Config::getRegExpList(const QString &var) const
+{
+ const QStringList strs = m_configVars.value(var).asStringList();
+ QList<QRegularExpression> regExps;
+ for (const auto &str : strs)
+ regExps += QRegularExpression(str);
+ return regExps;
+}
+
+/*!
+ This function is slower than it could be. What it does is
+ find all the keys that begin with \a var + dot and return
+ the matching keys in a set, stripped of the matching prefix
+ and dot.
+ */
+QSet<QString> Config::subVars(const QString &var) const
+{
+ QSet<QString> result;
+ QString varDot = var + QLatin1Char('.');
+ for (auto it = m_configVars.constBegin(); it != m_configVars.constEnd(); ++it) {
+ if (it.key().startsWith(varDot)) {
+ QString subVar = it.key().mid(varDot.size());
+ int dot = subVar.indexOf(QLatin1Char('.'));
+ if (dot != -1)
+ subVar.truncate(dot);
+ result.insert(subVar);
+ }
+ }
+ return result;
+}
+
+/*!
+ Searches for a path to \a fileName in 'sources', 'sourcedirs', and
+ 'exampledirs' config variables and returns a full path to the first
+ match found. If the file is not found, returns an empty string.
+ */
+QString Config::getIncludeFilePath(const QString &fileName) const
+{
+ QString ext = QFileInfo(fileName).suffix();
+
+ if (!m_includeFilesMap.contains(ext)) {
+ QStringList result = getCanonicalPathList(CONFIG_SOURCES);
+ result.erase(std::remove_if(result.begin(), result.end(),
+ [&](const QString &s) { return !s.endsWith(ext); }),
+ result.end());
+ const QStringList dirs =
+ getCanonicalPathList(CONFIG_SOURCEDIRS) +
+ getCanonicalPathList(CONFIG_EXAMPLEDIRS);
+
+ for (const auto &dir : dirs)
+ result += getFilesHere(dir, "*." + ext, location());
+ result.removeDuplicates();
+ m_includeFilesMap.insert(ext, result);
+ }
+ const QStringList &paths = (*m_includeFilesMap.find(ext));
+ QString match = fileName;
+ if (!match.startsWith('/'))
+ match.prepend('/');
+ for (const auto &path : paths) {
+ if (path.endsWith(match))
+ return path;
+ }
+ return QString();
+}
+
+/*!
+ Builds and returns a list of file pathnames for the file
+ type specified by \a filesVar (e.g. "headers" or "sources").
+ The files are found in the directories specified by
+ \a dirsVar, and they are filtered by \a defaultNameFilter
+ if a better filter can't be constructed from \a filesVar.
+ The directories in \a excludedDirs are avoided. The files
+ in \a excludedFiles are not included in the return list.
+ */
+QStringList Config::getAllFiles(const QString &filesVar, const QString &dirsVar,
+ const QSet<QString> &excludedDirs,
+ const QSet<QString> &excludedFiles)
+{
+ QStringList result = getCanonicalPathList(filesVar, Validate);
+ const QStringList dirs = getCanonicalPathList(dirsVar, Validate);
+
+ const QString nameFilter = m_configVars.value(filesVar + dot + CONFIG_FILEEXTENSIONS).asString();
+
+ for (const auto &dir : dirs)
+ result += getFilesHere(dir, nameFilter, location(), excludedDirs, excludedFiles);
+ return result;
+}
+
+QStringList Config::getExampleQdocFiles(const QSet<QString> &excludedDirs,
+ const QSet<QString> &excludedFiles)
+{
+ QStringList result;
+ const QStringList dirs = getCanonicalPathList("exampledirs");
+ const QString nameFilter = " *.qdoc";
+
+ for (const auto &dir : dirs)
+ result += getFilesHere(dir, nameFilter, location(), excludedDirs, excludedFiles);
+ return result;
+}
+
+QStringList Config::getExampleImageFiles(const QSet<QString> &excludedDirs,
+ const QSet<QString> &excludedFiles)
+{
+ QStringList result;
+ const QStringList dirs = getCanonicalPathList("exampledirs");
+ const QString nameFilter = m_configVars.value(CONFIG_EXAMPLES + dot + CONFIG_IMAGEEXTENSIONS).asString();
+
+ for (const auto &dir : dirs)
+ result += getFilesHere(dir, nameFilter, location(), excludedDirs, excludedFiles);
+ return result;
+}
+
+// TODO: [misplaced-logic][examples][pod-configuration]
+// The definition of how an example is structured and how to find its
+// components should not be part of Config or, for that matter,
+// CppCodeParser, which is the actual caller of this method.
+// Move this method to a more appropriate place as soon as a suitable
+// place is available for it.
+
+/*!
+ Returns the path to the project file for \a examplePath, or an empty string
+ if no project file was found.
+ */
+QString Config::getExampleProjectFile(const QString &examplePath)
+{
+ QFileInfo fileInfo(examplePath);
+ QStringList validNames;
+ validNames << QLatin1String("CMakeLists.txt")
+ << fileInfo.fileName() + QLatin1String(".pro")
+ << fileInfo.fileName() + QLatin1String(".qmlproject")
+ << fileInfo.fileName() + QLatin1String(".pyproject")
+ << QLatin1String("qbuild.pro"); // legacy
+
+ QString projectFile;
+
+ for (const auto &name : std::as_const(validNames)) {
+ projectFile = Config::findFile(Location(), m_exampleFiles, m_exampleDirs,
+ examplePath + QLatin1Char('/') + name);
+ if (!projectFile.isEmpty())
+ return projectFile;
+ }
+
+ return projectFile;
+}
+
+// TODO: [pod-configuration]
+// Remove findFile completely from the configuration.
+// External usages of findFile were already removed but a last caller
+// of this method exists internally to Config in
+// `getExampleProjectFile`.
+// That method has to be removed at some point and this method should
+// go with it.
+// Do notice that FileResolver is the replacement for findFile but it
+// is designed, for now, with a scope that does only care about the
+// usages of findFile that are outside the Config class.
+// More specifically, it was designed to replace only the uses of
+// findFile that deal with user provided queries or queries related to
+// that.
+// The logic that is used internally in Config is the same, but has a
+// different conceptual meaning.
+// When findFile is permanently removed, it must be considered whether
+// FileResolver itself should be used for the same logic or not.
+
+/*!
+ \a fileName is the path of the file to find.
+
+ \a files and \a dirs are the lists where we must find the
+ components of \a fileName.
+
+ \a location is used for obtaining the file and line numbers
+ for report qdoc errors.
+ */
+QString Config::findFile(const Location &location, const QStringList &files,
+ const QStringList &dirs, const QString &fileName,
+ QString *userFriendlyFilePath)
+{
+ if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) {
+ if (userFriendlyFilePath)
+ *userFriendlyFilePath = fileName;
+ return fileName;
+ }
+
+ QFileInfo fileInfo;
+ QStringList components = fileName.split(QLatin1Char('?'));
+ QString firstComponent = components.first();
+
+ for (const auto &file : files) {
+ if (file == firstComponent || file.endsWith(QLatin1Char('/') + firstComponent)) {
+ fileInfo.setFile(file);
+ if (!fileInfo.exists())
+ location.fatal(QStringLiteral("File '%1' does not exist").arg(file));
+ break;
+ }
+ }
+
+ if (fileInfo.fileName().isEmpty()) {
+ for (const auto &dir : dirs) {
+ fileInfo.setFile(QDir(dir), firstComponent);
+ if (fileInfo.exists())
+ break;
+ }
+ }
+
+ if (userFriendlyFilePath)
+ userFriendlyFilePath->clear();
+ if (!fileInfo.exists())
+ return QString();
+
+ // <<REMARK: This is actually dead code. It is unclear what it tries
+ // to do and why but its usage is unnecessary in the current
+ // codebase.
+ // Indeed, the whole concept of the "userFriendlyFilePath" is
+ // removed for file searching.
+ // It will be removed directly with the whole of findFile, but it
+ // should not be considered anymore until then.
+ if (userFriendlyFilePath) {
+ for (auto c = components.constBegin();;) {
+ bool isArchive = (c != components.constEnd() - 1);
+ userFriendlyFilePath->append(*c);
+
+ if (isArchive) {
+ QString extracted = m_extractedDirs[fileInfo.filePath()];
+
+ ++c;
+ fileInfo.setFile(QDir(extracted), *c);
+ } else {
+ break;
+ }
+
+ userFriendlyFilePath->append(QLatin1Char('?'));
+ }
+ }
+ // REMARK>>
+
+ return fileInfo.filePath();
+}
+
+// TODO: [pod-configuration]
+// An intermediate representation for the configuration should only
+// contain data that will later be destructured into subsystem that
+// care about specific subsets of the configuration and can carry that
+// information with them, uniquely.
+// Remove copyFile, moving it into whatever will have the unique
+// resposability of knowing how to build an output directory for a
+// QDoc execution.
+// Should copy file being used for not only copying file to the build
+// output directory, split its responsabilities into smaller elements
+// instead of forcing the logic together.
+
+/*!
+ Copies the \a sourceFilePath to the file name constructed by
+ concatenating \a targetDirPath and the file name from the
+ \a userFriendlySourceFilePath. \a location is for identifying
+ the file and line number where a qdoc error occurred. The
+ constructed output file name is returned.
+ */
+QString Config::copyFile(const Location &location, const QString &sourceFilePath,
+ const QString &userFriendlySourceFilePath, const QString &targetDirPath)
+{
+ // TODO: A copying operation should only be performed on files
+ // that we assume to be available. Ensure that this is true at the
+ // API boundary and bubble up the error checking and reporting to
+ // call-site users. Possibly this will be as simple as
+ // ResolvedFile, but could not be done at the time of the introduction of
+ // that type as we first need to encapsulate the logic for
+ // copying files into an appropriate subsystem and have a better
+ // understanding of call-site usages.
+
+ QFile inFile(sourceFilePath);
+ if (!inFile.open(QFile::ReadOnly)) {
+ location.warning(QStringLiteral("Cannot open input file for copy: '%1': %2")
+ .arg(sourceFilePath, inFile.errorString()));
+ return QString();
+ }
+
+ // TODO: [non-canonical-representation]
+ // Similar to other part of QDoc, we do a series of non-intuitive
+ // checks to canonicalize some multi-format parameter into
+ // something we can use.
+ // Understand which of those formats are actually in use and
+ // provide a canonicalized version that can be requested at the
+ // API boundary to ensure that correct formatting is used.
+ // If possible, gradually bubble up the canonicalization until a
+ // single entry-point in the program exists where the
+ // canonicalization can be processed to avoid complicating
+ // intermediate steps.
+ // ADDENDUM 1: At least one usage of this seems to depend on the
+ // processing done for files coming from
+ // Generator::copyTemplateFile, which are expressed as absolute
+ // paths. This seems to be the only usage that is currently
+ // needed, hence a temporary new implementation is provided that
+ // only takes this case into account.
+ // Do notice that we assume that in this case we always want a
+ // flat structure, that is, we are copying the file as a direct
+ // child of the target directory.
+ // Nonetheless, it is possible that this case will not be needed,
+ // such that it can be removed later on, or that it will be nedeed
+ // in multiple places such that an higher level interface for it
+ // should be provided.
+ // Furthermoe, it might be possible that there is an edge case
+ // that is now not considered, as it is unknown, that was
+ // considered before.
+ // As it is now unclear what kind of paths are used here, what
+ // format they have, why they are used and why they have some
+ // specific format, further processing is avoided but a more
+ // torough overview of what should is needed must be done when
+ // more information are gathered and this function is extracted
+ // away from config.
+
+ QString outFileName{userFriendlySourceFilePath};
+ QFileInfo outFileNameInfo{userFriendlySourceFilePath};
+ if (outFileNameInfo.isAbsolute())
+ outFileName = outFileNameInfo.fileName();
+
+ outFileName = targetDirPath + "/" + outFileName;
+ QDir targetDir(targetDirPath);
+ if (!targetDir.exists())
+ targetDir.mkpath(".");
+
+ QFile outFile(outFileName);
+ if (!outFile.open(QFile::WriteOnly)) {
+ // TODO: [uncrentralized-warning]
+ location.warning(QStringLiteral("Cannot open output file for copy: '%1': %2")
+ .arg(outFileName, outFile.errorString()));
+ return QString();
+ }
+
+ // TODO: There shouldn't be any particular advantage to copying
+ // the file by readying its content and writing it compared to
+ // asking the underlying system to do the copy for us.
+ // Consider simplifying this part by avoiding doing the manual
+ // work ourselves.
+
+ char buffer[1024];
+ qsizetype len;
+ while ((len = inFile.read(buffer, sizeof(buffer))) > 0)
+ outFile.write(buffer, len);
+ return outFileName;
+}
+
+/*!
+ Finds the largest unicode digit in \a value in the range
+ 1..7 and returns it.
+ */
+int Config::numParams(const QString &value)
+{
+ int max = 0;
+ for (int i = 0; i != value.size(); ++i) {
+ uint c = value[i].unicode();
+ if (c > 0 && c < 8)
+ max = qMax(max, static_cast<int>(c));
+ }
+ return max;
+}
+
+/*!
+ Returns \c true if \a ch is a letter, number, '_', '.',
+ '{', '}', or ','.
+ */
+bool Config::isMetaKeyChar(QChar ch)
+{
+ return ch.isLetterOrNumber() || ch == QLatin1Char('_') || ch == QLatin1Char('.')
+ || ch == QLatin1Char('{') || ch == QLatin1Char('}') || ch == QLatin1Char(',');
+}
+
+/*!
+ \a fileName is a master qdocconf file. It contains a list of
+ qdocconf files and nothing else. Read the list and return it.
+ */
+QStringList Config::loadMaster(const QString &fileName)
+{
+ Location location;
+ QFile fin(fileName);
+ if (!fin.open(QFile::ReadOnly | QFile::Text)) {
+ if (!Config::installDir.isEmpty()) {
+ qsizetype prefix = location.filePath().size() - location.fileName().size();
+ fin.setFileName(Config::installDir + QLatin1Char('/')
+ + fileName.right(fileName.size() - prefix));
+ }
+ if (!fin.open(QFile::ReadOnly | QFile::Text))
+ location.fatal(QStringLiteral("Cannot open master qdocconf file '%1': %2")
+ .arg(fileName, fin.errorString()));
+ }
+ QTextStream stream(&fin);
+ QStringList qdocFiles;
+ QDir configDir(QFileInfo(fileName).canonicalPath());
+ QString line = stream.readLine();
+ while (!line.isNull()) {
+ if (!line.isEmpty())
+ qdocFiles.append(QFileInfo(configDir, line).filePath());
+ line = stream.readLine();
+ }
+ fin.close();
+ return qdocFiles;
+}
+
+/*!
+ Load, parse, and process a qdoc configuration file. This
+ function is only called by the other load() function, but
+ this one is recursive, i.e., it calls itself when it sees
+ an \c{include} statement in the qdoc configuration file.
+ */
+void Config::load(Location location, const QString &fileName)
+{
+ QFileInfo fileInfo(fileName);
+ pushWorkingDir(fileInfo.canonicalPath());
+ static const QRegularExpression keySyntax(QRegularExpression::anchoredPattern(QLatin1String("\\w+(?:\\.\\w+)*")));
+
+#define SKIP_CHAR() \
+ do { \
+ location.advance(c); \
+ ++i; \
+ c = text.at(i); \
+ cc = c.unicode(); \
+ } while (0)
+
+#define SKIP_SPACES() \
+ while (c.isSpace() && cc != '\n') \
+ SKIP_CHAR()
+
+#define PUT_CHAR() \
+ word += c; \
+ SKIP_CHAR();
+
+ if (location.depth() > 16)
+ location.fatal(QStringLiteral("Too many nested includes"));
+
+ QFile fin(fileInfo.fileName());
+ if (!fin.open(QFile::ReadOnly | QFile::Text)) {
+ if (!Config::installDir.isEmpty()) {
+ qsizetype prefix = location.filePath().size() - location.fileName().size();
+ fin.setFileName(Config::installDir + QLatin1Char('/')
+ + fileName.right(fileName.size() - prefix));
+ }
+ if (!fin.open(QFile::ReadOnly | QFile::Text))
+ location.fatal(
+ QStringLiteral("Cannot open file '%1': %2").arg(fileName, fin.errorString()));
+ }
+
+ QTextStream stream(&fin);
+ QString text = stream.readAll();
+ text += QLatin1String("\n\n");
+ text += QLatin1Char('\0');
+ fin.close();
+
+ location.push(fileName);
+ location.start();
+
+ int i = 0;
+ QChar c = text.at(0);
+ uint cc = c.unicode();
+ while (i < text.size()) {
+ if (cc == 0) {
+ ++i;
+ } else if (c.isSpace()) {
+ SKIP_CHAR();
+ } else if (cc == '#') {
+ do {
+ SKIP_CHAR();
+ } while (cc != '\n');
+ } else if (isMetaKeyChar(c)) {
+ Location keyLoc = location;
+ bool plus = false;
+ QStringList rhsValues;
+ QList<ExpandVar> expandVars;
+ QString word;
+ bool inQuote = false;
+ bool needsExpansion = false;
+
+ MetaStack stack;
+ do {
+ stack.process(c, location);
+ SKIP_CHAR();
+ } while (isMetaKeyChar(c));
+
+ const QStringList keys = stack.getExpanded(location);
+ SKIP_SPACES();
+
+ if (keys.size() == 1 && keys.first() == QLatin1String("include")) {
+ QString includeFile;
+
+ if (cc != '(')
+ location.fatal(QStringLiteral("Bad include syntax"));
+ SKIP_CHAR();
+ SKIP_SPACES();
+
+ while (!c.isSpace() && cc != '#' && cc != ')') {
+
+ if (cc == '$') {
+ QString var;
+ SKIP_CHAR();
+ while (c.isLetterOrNumber() || cc == '_') {
+ var += c;
+ SKIP_CHAR();
+ }
+ if (!var.isEmpty()) {
+ const QByteArray val = qgetenv(var.toLatin1().data());
+ if (val.isNull()) {
+ location.fatal(QStringLiteral("Environment variable '%1' undefined")
+ .arg(var));
+ } else {
+ includeFile += QString::fromLatin1(val);
+ }
+ }
+ } else {
+ includeFile += c;
+ SKIP_CHAR();
+ }
+ }
+ SKIP_SPACES();
+ if (cc != ')')
+ location.fatal(QStringLiteral("Bad include syntax"));
+ SKIP_CHAR();
+ SKIP_SPACES();
+ if (cc != '#' && cc != '\n')
+ location.fatal(QStringLiteral("Trailing garbage"));
+
+ /*
+ Here is the recursive call.
+ */
+ load(location, QFileInfo(QDir(m_workingDirs.top()), includeFile).filePath());
+ } else {
+ /*
+ It wasn't an include statement, so it's something else.
+ We must see either '=' or '+=' next. If not, fatal error.
+ */
+ if (cc == '+') {
+ plus = true;
+ SKIP_CHAR();
+ }
+ if (cc != '=')
+ location.fatal(QStringLiteral("Expected '=' or '+=' after key"));
+ SKIP_CHAR();
+ SKIP_SPACES();
+
+ for (;;) {
+ if (cc == '\\') {
+ qsizetype metaCharPos;
+
+ SKIP_CHAR();
+ if (cc == '\n') {
+ SKIP_CHAR();
+ } else if (cc > '0' && cc < '8') {
+ word += QChar(c.digitValue());
+ SKIP_CHAR();
+ } else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c))
+ != -1) {
+ word += QLatin1Char("\a\b\f\n\r\t\v"[metaCharPos]);
+ SKIP_CHAR();
+ } else {
+ PUT_CHAR();
+ }
+ } else if (c.isSpace() || cc == '#') {
+ if (inQuote) {
+ if (cc == '\n')
+ location.fatal(QStringLiteral("Unterminated string"));
+ PUT_CHAR();
+ } else {
+ if (!word.isEmpty() || needsExpansion) {
+ rhsValues << word;
+ word.clear();
+ needsExpansion = false;
+ }
+ if (cc == '\n' || cc == '#')
+ break;
+ SKIP_SPACES();
+ }
+ } else if (cc == '"') {
+ if (inQuote) {
+ if (!word.isEmpty() || needsExpansion)
+ rhsValues << word;
+ word.clear();
+ needsExpansion = false;
+ }
+ inQuote = !inQuote;
+ SKIP_CHAR();
+ } else if (cc == '$') {
+ QString var;
+ QChar delim(' ');
+ bool braces = false;
+ SKIP_CHAR();
+ if (cc == '{') {
+ SKIP_CHAR();
+ braces = true;
+ }
+ while (c.isLetterOrNumber() || cc == '_') {
+ var += c;
+ SKIP_CHAR();
+ }
+ if (braces) {
+ if (cc == ',') {
+ SKIP_CHAR();
+ delim = c;
+ SKIP_CHAR();
+ }
+ if (cc == '}')
+ SKIP_CHAR();
+ else if (delim == '}')
+ delim = QChar(); // null delimiter
+ else
+ location.fatal(QStringLiteral("Missing '}'"));
+ }
+ if (!var.isEmpty()) {
+ const QByteArray val = qgetenv(var.toLatin1().constData());
+ if (val.isNull()) {
+ expandVars << ExpandVar(rhsValues.size(), word.size(), var, delim);
+ needsExpansion = true;
+ } else if (braces) { // ${VAR} inserts content from an env. variable for processing
+ text.insert(i, QString::fromLatin1(val));
+ c = text.at(i);
+ cc = c.unicode();
+ } else { // while $VAR simply reads the value and stores it to a config variable.
+ word += QString::fromLatin1(val);
+ }
+ }
+ } else {
+ if (!inQuote && cc == '=')
+ location.fatal(QStringLiteral("Unexpected '='"));
+ PUT_CHAR();
+ }
+ }
+ for (const auto &key : keys) {
+ if (!keySyntax.match(key).hasMatch())
+ keyLoc.fatal(QStringLiteral("Invalid key '%1'").arg(key));
+
+ ConfigVar configVar(key, rhsValues, QDir::currentPath(), keyLoc, expandVars);
+ if (plus && m_configVars.contains(key)) {
+ m_configVars[key].append(configVar);
+ } else {
+ m_configVars.insert(key, configVar);
+ }
+ }
+ }
+ } else {
+ location.fatal(QStringLiteral("Unexpected character '%1' at beginning of line").arg(c));
+ }
+ }
+ popWorkingDir();
+
+#undef SKIP_CHAR
+#undef SKIP_SPACES
+#undef PUT_CHAR
+}
+
+bool Config::isFileExcluded(const QString &fileName, const QSet<QString> &excludedFiles)
+{
+ for (const QString &entry : excludedFiles) {
+ if (entry.contains(QLatin1Char('*')) || entry.contains(QLatin1Char('?'))) {
+ QRegularExpression re(QRegularExpression::wildcardToRegularExpression(entry));
+ if (re.match(fileName).hasMatch())
+ return true;
+ }
+ }
+ return excludedFiles.contains(fileName);
+}
+
+QStringList Config::getFilesHere(const QString &uncleanDir, const QString &nameFilter,
+ const Location &location, const QSet<QString> &excludedDirs,
+ const QSet<QString> &excludedFiles)
+{
+ // TODO: Understand why location is used to branch the
+ // canonicalization and why the two different methods are used.
+ QString dir =
+ location.isEmpty() ? QDir::cleanPath(uncleanDir) : QDir(uncleanDir).canonicalPath();
+ QStringList result;
+ if (excludedDirs.contains(dir))
+ return result;
+
+ QDir dirInfo(dir);
+
+ dirInfo.setNameFilters(nameFilter.split(QLatin1Char(' ')));
+ dirInfo.setSorting(QDir::Name);
+ dirInfo.setFilter(QDir::Files);
+ QStringList fileNames = dirInfo.entryList();
+ for (const auto &file : std::as_const(fileNames)) {
+ // TODO: Understand if this is needed and, should it be, if it
+ // is indeed the only case that should be considered.
+ if (!file.startsWith(QLatin1Char('~'))) {
+ QString s = dirInfo.filePath(file);
+ QString c = QDir::cleanPath(s);
+ if (!isFileExcluded(c, excludedFiles))
+ result.append(c);
+ }
+ }
+
+ dirInfo.setNameFilters(QStringList(QLatin1String("*")));
+ dirInfo.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
+ fileNames = dirInfo.entryList();
+ for (const auto &file : fileNames)
+ result += getFilesHere(dirInfo.filePath(file), nameFilter, location, excludedDirs,
+ excludedFiles);
+ return result;
+}
+
+/*!
+ Set \a dir as the working directory and push it onto the
+ stack of working directories.
+ */
+void Config::pushWorkingDir(const QString &dir)
+{
+ m_workingDirs.push(dir);
+ QDir::setCurrent(dir);
+}
+
+/*!
+ Pop the top entry from the stack of working directories.
+ Set the working directory to the next one on the stack,
+ if one exists.
+ */
+void Config::popWorkingDir()
+{
+ Q_ASSERT(!m_workingDirs.isEmpty());
+ m_workingDirs.pop();
+ if (!m_workingDirs.isEmpty())
+ QDir::setCurrent(m_workingDirs.top());
+}
+
+const Config::ExcludedPaths& Config::getExcludedPaths() {
+ if (m_excludedPaths)
+ return *m_excludedPaths;
+
+ const auto &excludedDirList = getCanonicalPathList(CONFIG_EXCLUDEDIRS);
+ const auto &excludedFilesList = getCanonicalPathList(CONFIG_EXCLUDEFILES);
+
+ QSet<QString> excludedDirs = QSet<QString>(excludedDirList.cbegin(), excludedDirList.cend());
+ QSet<QString> excludedFiles = QSet<QString>(excludedFilesList.cbegin(), excludedFilesList.cend());
+
+ m_excludedPaths.emplace(ExcludedPaths{excludedDirs, excludedFiles});
+
+ return *m_excludedPaths;
+}
+
+std::set<Config::HeaderFilePath> Config::getHeaderFiles() {
+ static QStringList accepted_header_file_extensions{
+ "ch", "h", "h++", "hh", "hpp", "hxx"
+ };
+
+ const auto& [excludedDirs, excludedFiles] = getExcludedPaths();
+
+ QStringList headerList =
+ getAllFiles(CONFIG_HEADERS, CONFIG_HEADERDIRS, excludedDirs, excludedFiles);
+
+ std::set<HeaderFilePath> headers{};
+
+ for (const auto& header : headerList) {
+ if (header.contains("doc/snippets")) continue;
+
+ if (!accepted_header_file_extensions.contains(QFileInfo{header}.suffix()))
+ continue;
+
+ headers.insert(HeaderFilePath{QFileInfo{header}.canonicalPath(), QFileInfo{header}.fileName()});
+ }
+
+ return headers;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/config.h b/src/qdoc/qdoc/src/qdoc/config.h
new file mode 100644
index 000000000..30dad4746
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/config.h
@@ -0,0 +1,409 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#include "location.h"
+#include "qdoccommandlineparser.h"
+#include "singleton.h"
+
+#include <QtCore/qmap.h>
+#include <QtCore/qset.h>
+#include <QtCore/qstack.h>
+#include <QtCore/qstringlist.h>
+
+#include <set>
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+class Config;
+
+/*
+ Contains information about a location
+ where a ConfigVar string needs to be expanded
+ from another config variable.
+*/
+struct ExpandVar
+{
+ int m_valueIndex {};
+ int m_index {};
+ QString m_var {};
+ QChar m_delim {};
+
+ ExpandVar(int valueIndex, int index, QString var, const QChar &delim)
+ : m_valueIndex(valueIndex), m_index(index), m_var(std::move(var)), m_delim(delim)
+ {
+ }
+};
+
+class ConfigVar
+{
+public:
+ struct ConfigValue {
+ QString m_value;
+ QString m_path;
+ };
+
+ [[nodiscard]] QString asString(const QString defaultString = QString()) const;
+ [[nodiscard]] QStringList asStringList() const;
+ [[nodiscard]] QSet<QString> asStringSet() const;
+ [[nodiscard]] bool asBool() const;
+ [[nodiscard]] int asInt() const;
+ [[nodiscard]] const Location &location() const { return m_location; }
+
+ ConfigVar() = default;
+ ConfigVar(QString name, const QStringList &values, const QString &dir,
+ const Location &loc = Location(),
+ const QList<ExpandVar> &expandVars = QList<ExpandVar>())
+ : m_name(std::move(name)), m_location(loc), m_expandVars(expandVars)
+ {
+ for (const auto &v : values)
+ m_values << ConfigValue {v, dir};
+ }
+
+private:
+ void append(const ConfigVar &other);
+
+private:
+ QString m_name {};
+ QList<ConfigValue> m_values {};
+ Location m_location {};
+ QList<ExpandVar> m_expandVars {};
+
+ friend class Config;
+};
+
+/*
+ In this multimap, the key is a config variable name.
+ */
+typedef QMap<QString, ConfigVar> ConfigVarMap;
+
+class Config : public Singleton<Config>
+{
+public:
+ ~Config();
+
+ enum QDocPass { Neither, Prepare, Generate };
+
+ enum PathFlags : unsigned char {
+ None = 0x0,
+ // TODO: [unenforced-unclear-validation]
+ // The Validate flag is used, for example, during the retrival
+ // of paths in getCanonicalPathList.
+ // It is unclear what kind of validation it performs, if any,
+ // and when this validation is required.
+ // Instead, remove this kind of flag and ensure that any
+ // amount of required validation is performed during the
+ // parsing step, if possilbe, and only once.
+ // Furthemore, ensure any such validation removes some
+ // uncertainty on dependent subsystems, moving constraints to
+ // preconditions and expressing them at the API boundaries.
+ Validate = 0x1,
+ IncludePaths = 0x2
+ };
+
+ void init(const QString &programName, const QStringList &args);
+ [[nodiscard]] bool getDebug() const { return m_debug; }
+ [[nodiscard]] bool getAtomsDump() const { return m_atomsDump; }
+ [[nodiscard]] bool showInternal() const { return m_showInternal; }
+
+ void clear();
+ void reset();
+ void load(const QString &fileName);
+ void setStringList(const QString &var, const QStringList &values);
+ void insertStringList(const QString &var, const QStringList &values);
+
+ void showHelp(int exitCode = 0) { m_parser.showHelp(exitCode); }
+ [[nodiscard]] QStringList qdocFiles() const { return m_parser.positionalArguments(); }
+ [[nodiscard]] const QString &programName() const { return m_prog; }
+ [[nodiscard]] const Location &location() const { return m_location; }
+ [[nodiscard]] const ConfigVar &get(const QString &var) const
+ {
+ // Avoid injecting default-constructed values to map if var doesn't exist
+ static ConfigVar empty;
+ auto it = m_configVars.constFind(var);
+ return (it != m_configVars.constEnd()) ? *it : empty;
+ }
+ [[nodiscard]] QString getOutputDir(const QString &format = QString("HTML")) const;
+ [[nodiscard]] QSet<QString> getOutputFormats() const;
+ [[nodiscard]] QStringList getCanonicalPathList(const QString &var,
+ PathFlags flags = None) const;
+ [[nodiscard]] QRegularExpression getRegExp(const QString &var) const;
+ [[nodiscard]] QList<QRegularExpression> getRegExpList(const QString &var) const;
+ [[nodiscard]] QSet<QString> subVars(const QString &var) const;
+ QStringList getAllFiles(const QString &filesVar, const QString &dirsVar,
+ const QSet<QString> &excludedDirs = QSet<QString>(),
+ const QSet<QString> &excludedFiles = QSet<QString>());
+ [[nodiscard]] QString getIncludeFilePath(const QString &fileName) const;
+ QStringList getExampleQdocFiles(const QSet<QString> &excludedDirs,
+ const QSet<QString> &excludedFiles);
+ QStringList getExampleImageFiles(const QSet<QString> &excludedDirs,
+ const QSet<QString> &excludedFiles);
+ QString getExampleProjectFile(const QString &examplePath);
+
+ static QStringList loadMaster(const QString &fileName);
+ static bool isFileExcluded(const QString &fileName, const QSet<QString> &excludedFiles);
+ static QStringList getFilesHere(const QString &dir, const QString &nameFilter,
+ const Location &location = Location(),
+ const QSet<QString> &excludedDirs = QSet<QString>(),
+ const QSet<QString> &excludedFiles = QSet<QString>());
+ static QString findFile(const Location &location, const QStringList &files,
+ const QStringList &dirs, const QString &fileName,
+ QString *userFriendlyFilePath = nullptr);
+ static QString copyFile(const Location &location, const QString &sourceFilePath,
+ const QString &userFriendlySourceFilePath,
+ const QString &targetDirPath);
+ static int numParams(const QString &value);
+ static void pushWorkingDir(const QString &dir);
+ static void popWorkingDir();
+
+ static const QString dot;
+
+ static bool generateExamples;
+ static QString installDir;
+ static QString overrideOutputDir;
+ static QSet<QString> overrideOutputFormats;
+
+ [[nodiscard]] inline bool singleExec() const;
+ [[nodiscard]] inline bool dualExec() const;
+ QStringList &defines() { return m_defines; }
+ QStringList &dependModules() { return m_dependModules; }
+ QStringList &includePaths() { return m_includePaths; }
+ QStringList &indexDirs() { return m_indexDirs; }
+ [[nodiscard]] QString currentDir() const { return m_currentDir; }
+ void setCurrentDir(const QString &path) { m_currentDir = path; }
+ [[nodiscard]] QString previousCurrentDir() const { return m_previousCurrentDir; }
+ void setPreviousCurrentDir(const QString &path) { m_previousCurrentDir = path; }
+
+ void setQDocPass(const QDocPass &pass) { m_qdocPass = pass; };
+ [[nodiscard]] bool preparing() const { return (m_qdocPass == Prepare); }
+ [[nodiscard]] bool generating() const { return (m_qdocPass == Generate); }
+
+ struct ExcludedPaths {
+ QSet<QString> excluded_directories;
+ QSet<QString> excluded_files;
+ };
+ const ExcludedPaths& getExcludedPaths();
+
+ struct HeaderFilePath {
+ QString path;
+ QString filename;
+
+ friend bool operator<(const HeaderFilePath& lhs, const HeaderFilePath& rhs) {
+ return std::tie(lhs.path, lhs.filename) < std::tie(rhs.path, rhs.filename);
+ }
+ };
+ std::set<HeaderFilePath> getHeaderFiles();
+
+private:
+ void processCommandLineOptions(const QStringList &args);
+ void setIncludePaths();
+ void setIndexDirs();
+ void expandVariables();
+
+ QStringList m_dependModules {};
+ QStringList m_defines {};
+ QStringList m_includePaths {};
+ QStringList m_indexDirs {};
+ QStringList m_exampleFiles {};
+ QStringList m_exampleDirs {};
+ QString m_currentDir {};
+ QString m_previousCurrentDir {};
+ std::optional<ExcludedPaths> m_excludedPaths{};
+
+ bool m_showInternal { false };
+ static bool m_debug;
+
+ // An option that can be set trough a similarly named command-line option.
+ // When this is set, every time QDoc parses a block-comment, a
+ // human-readable presentation of the `Atom`s structure for that
+ // block will shown to the user.
+ static bool m_atomsDump;
+
+ static bool isMetaKeyChar(QChar ch);
+ void load(Location location, const QString &fileName);
+
+ QString m_prog {};
+ Location m_location {};
+ ConfigVarMap m_configVars {};
+
+ static QMap<QString, QString> m_extractedDirs;
+ static QStack<QString> m_workingDirs;
+ static QMap<QString, QStringList> m_includeFilesMap;
+ QDocCommandLineParser m_parser {};
+
+ QDocPass m_qdocPass { Neither };
+};
+
+struct ConfigStrings
+{
+ static QString ALIAS;
+ static QString AUTOLINKERRORS;
+ static QString BUILDVERSION;
+ static QString CODEINDENT;
+ static QString CODEPREFIX;
+ static QString CODESUFFIX;
+ static QString CPPCLASSESPAGE;
+ static QString CPPCLASSESTITLE;
+ static QString DEFINES;
+ static QString DEPENDS;
+ static QString DESCRIPTION;
+ static QString DOCBOOKEXTENSIONS;
+ static QString ENDHEADER;
+ static QString EXAMPLEDIRS;
+ static QString EXAMPLES;
+ static QString EXAMPLESINSTALLPATH;
+ static QString EXCLUDEDIRS;
+ static QString EXCLUDEFILES;
+ static QString EXTRAIMAGES;
+ static QString FALSEHOODS;
+ static QString FORMATTING;
+ static QString HEADERDIRS;
+ static QString HEADERS;
+ static QString HEADERSCRIPTS;
+ static QString HEADERSTYLES;
+ static QString HOMEPAGE;
+ static QString HOMETITLE;
+ static QString IGNOREDIRECTIVES;
+ static QString IGNORETOKENS;
+ static QString IGNORESINCE;
+ static QString IGNOREWORDS;
+ static QString IMAGEDIRS;
+ static QString IMAGES;
+ static QString INCLUDEPATHS;
+ static QString INCLUSIVE;
+ static QString INDEXES;
+ static QString LANDINGPAGE;
+ static QString LANDINGTITLE;
+ static QString LANGUAGE;
+ static QString LOCATIONINFO;
+ static QString LOGPROGRESS;
+ static QString MACRO;
+ static QString MANIFESTMETA;
+ static QString MODULEHEADER;
+ static QString NATURALLANGUAGE;
+ static QString NAVIGATION;
+ static QString NOLINKERRORS;
+ static QString OUTPUTDIR;
+ static QString OUTPUTFORMATS;
+ static QString OUTPUTPREFIXES;
+ static QString OUTPUTSUFFIXES;
+ static QString PROJECT;
+ static QString REDIRECTDOCUMENTATIONTODEVNULL;
+ static QString QHP;
+ static QString QUOTINGINFORMATION;
+ static QString SCRIPTS;
+ static QString SHOWINTERNAL;
+ static QString SINGLEEXEC;
+ static QString SOURCEDIRS;
+ static QString SOURCEENCODING;
+ static QString SOURCES;
+ static QString SPURIOUS;
+ static QString STYLESHEETS;
+ static QString SYNTAXHIGHLIGHTING;
+ static QString TABSIZE;
+ static QString TAGFILE;
+ static QString TIMESTAMPS;
+ static QString TOCTITLES;
+ static QString TRADEMARKSPAGE;
+ static QString URL;
+ static QString VERSION;
+ static QString VERSIONSYM;
+ static QString FILEEXTENSIONS;
+ static QString IMAGEEXTENSIONS;
+ static QString QMLTYPESPAGE;
+ static QString QMLTYPESTITLE;
+ static QString WARNINGLIMIT;
+};
+
+#define CONFIG_AUTOLINKERRORS ConfigStrings::AUTOLINKERRORS
+#define CONFIG_BUILDVERSION ConfigStrings::BUILDVERSION
+#define CONFIG_CODEINDENT ConfigStrings::CODEINDENT
+#define CONFIG_CODEPREFIX ConfigStrings::CODEPREFIX
+#define CONFIG_CODESUFFIX ConfigStrings::CODESUFFIX
+#define CONFIG_CPPCLASSESPAGE ConfigStrings::CPPCLASSESPAGE
+#define CONFIG_CPPCLASSESTITLE ConfigStrings::CPPCLASSESTITLE
+#define CONFIG_DEFINES ConfigStrings::DEFINES
+#define CONFIG_DEPENDS ConfigStrings::DEPENDS
+#define CONFIG_DESCRIPTION ConfigStrings::DESCRIPTION
+#define CONFIG_DOCBOOKEXTENSIONS ConfigStrings::DOCBOOKEXTENSIONS
+#define CONFIG_ENDHEADER ConfigStrings::ENDHEADER
+#define CONFIG_EXAMPLEDIRS ConfigStrings::EXAMPLEDIRS
+#define CONFIG_EXAMPLES ConfigStrings::EXAMPLES
+#define CONFIG_EXAMPLESINSTALLPATH ConfigStrings::EXAMPLESINSTALLPATH
+#define CONFIG_EXCLUDEDIRS ConfigStrings::EXCLUDEDIRS
+#define CONFIG_EXCLUDEFILES ConfigStrings::EXCLUDEFILES
+#define CONFIG_EXTRAIMAGES ConfigStrings::EXTRAIMAGES
+#define CONFIG_FALSEHOODS ConfigStrings::FALSEHOODS
+#define CONFIG_FORMATTING ConfigStrings::FORMATTING
+#define CONFIG_HEADERDIRS ConfigStrings::HEADERDIRS
+#define CONFIG_HEADERS ConfigStrings::HEADERS
+#define CONFIG_HEADERSCRIPTS ConfigStrings::HEADERSCRIPTS
+#define CONFIG_HEADERSTYLES ConfigStrings::HEADERSTYLES
+#define CONFIG_HOMEPAGE ConfigStrings::HOMEPAGE
+#define CONFIG_HOMETITLE ConfigStrings::HOMETITLE
+#define CONFIG_IGNOREDIRECTIVES ConfigStrings::IGNOREDIRECTIVES
+#define CONFIG_IGNORESINCE ConfigStrings::IGNORESINCE
+#define CONFIG_IGNORETOKENS ConfigStrings::IGNORETOKENS
+#define CONFIG_IGNOREWORDS ConfigStrings::IGNOREWORDS
+#define CONFIG_IMAGEDIRS ConfigStrings::IMAGEDIRS
+#define CONFIG_INCLUDEPATHS ConfigStrings::INCLUDEPATHS
+#define CONFIG_INCLUSIVE ConfigStrings::INCLUSIVE
+#define CONFIG_INDEXES ConfigStrings::INDEXES
+#define CONFIG_LANDINGPAGE ConfigStrings::LANDINGPAGE
+#define CONFIG_LANDINGTITLE ConfigStrings::LANDINGTITLE
+#define CONFIG_LANGUAGE ConfigStrings::LANGUAGE
+#define CONFIG_LOCATIONINFO ConfigStrings::LOCATIONINFO
+#define CONFIG_LOGPROGRESS ConfigStrings::LOGPROGRESS
+#define CONFIG_MACRO ConfigStrings::MACRO
+#define CONFIG_MANIFESTMETA ConfigStrings::MANIFESTMETA
+#define CONFIG_MODULEHEADER ConfigStrings::MODULEHEADER
+#define CONFIG_NATURALLANGUAGE ConfigStrings::NATURALLANGUAGE
+#define CONFIG_NAVIGATION ConfigStrings::NAVIGATION
+#define CONFIG_NOLINKERRORS ConfigStrings::NOLINKERRORS
+#define CONFIG_OUTPUTDIR ConfigStrings::OUTPUTDIR
+#define CONFIG_OUTPUTFORMATS ConfigStrings::OUTPUTFORMATS
+#define CONFIG_OUTPUTPREFIXES ConfigStrings::OUTPUTPREFIXES
+#define CONFIG_OUTPUTSUFFIXES ConfigStrings::OUTPUTSUFFIXES
+#define CONFIG_PROJECT ConfigStrings::PROJECT
+#define CONFIG_REDIRECTDOCUMENTATIONTODEVNULL ConfigStrings::REDIRECTDOCUMENTATIONTODEVNULL
+#define CONFIG_QHP ConfigStrings::QHP
+#define CONFIG_QUOTINGINFORMATION ConfigStrings::QUOTINGINFORMATION
+#define CONFIG_SCRIPTS ConfigStrings::SCRIPTS
+#define CONFIG_SHOWINTERNAL ConfigStrings::SHOWINTERNAL
+#define CONFIG_SINGLEEXEC ConfigStrings::SINGLEEXEC
+#define CONFIG_SOURCEDIRS ConfigStrings::SOURCEDIRS
+#define CONFIG_SOURCEENCODING ConfigStrings::SOURCEENCODING
+#define CONFIG_SOURCES ConfigStrings::SOURCES
+#define CONFIG_SPURIOUS ConfigStrings::SPURIOUS
+#define CONFIG_STYLESHEETS ConfigStrings::STYLESHEETS
+#define CONFIG_SYNTAXHIGHLIGHTING ConfigStrings::SYNTAXHIGHLIGHTING
+#define CONFIG_TABSIZE ConfigStrings::TABSIZE
+#define CONFIG_TAGFILE ConfigStrings::TAGFILE
+#define CONFIG_TIMESTAMPS ConfigStrings::TIMESTAMPS
+#define CONFIG_TOCTITLES ConfigStrings::TOCTITLES
+#define CONFIG_TRADEMARKSPAGE ConfigStrings::TRADEMARKSPAGE
+#define CONFIG_URL ConfigStrings::URL
+#define CONFIG_VERSION ConfigStrings::VERSION
+#define CONFIG_VERSIONSYM ConfigStrings::VERSIONSYM
+#define CONFIG_FILEEXTENSIONS ConfigStrings::FILEEXTENSIONS
+#define CONFIG_IMAGEEXTENSIONS ConfigStrings::IMAGEEXTENSIONS
+#define CONFIG_QMLTYPESPAGE ConfigStrings::QMLTYPESPAGE
+#define CONFIG_QMLTYPESTITLE ConfigStrings::QMLTYPESTITLE
+#define CONFIG_WARNINGLIMIT ConfigStrings::WARNINGLIMIT
+
+inline bool Config::singleExec() const
+{
+ return m_configVars.value(CONFIG_SINGLEEXEC).asBool();
+}
+
+inline bool Config::dualExec() const
+{
+ return !m_configVars.value(CONFIG_SINGLEEXEC).asBool();
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/cppcodemarker.cpp b/src/qdoc/qdoc/src/qdoc/cppcodemarker.cpp
new file mode 100644
index 000000000..7fb26db0c
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/cppcodemarker.cpp
@@ -0,0 +1,594 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "cppcodemarker.h"
+
+#include "access.h"
+#include "enumnode.h"
+#include "functionnode.h"
+#include "namespacenode.h"
+#include "propertynode.h"
+#include "qmlpropertynode.h"
+#include "text.h"
+#include "tree.h"
+#include "typedefnode.h"
+#include "variablenode.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qregularexpression.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+/*!
+ Returns \c true.
+ */
+bool CppCodeMarker::recognizeCode(const QString & /* code */)
+{
+ return true;
+}
+
+/*!
+ Returns \c true if \a ext is any of a list of file extensions
+ for the C++ language.
+ */
+bool CppCodeMarker::recognizeExtension(const QString &extension)
+{
+ QByteArray ext = extension.toLatin1();
+ return ext == "c" || ext == "c++" || ext == "qdoc" || ext == "qtt" || ext == "qtx"
+ || ext == "cc" || ext == "cpp" || ext == "cxx" || ext == "ch" || ext == "h"
+ || ext == "h++" || ext == "hh" || ext == "hpp" || ext == "hxx";
+}
+
+/*!
+ Returns \c true if \a lang is either "C" or "Cpp".
+ */
+bool CppCodeMarker::recognizeLanguage(const QString &lang)
+{
+ return lang == QLatin1String("C") || lang == QLatin1String("Cpp");
+}
+
+/*!
+ Returns the type of atom used to represent C++ code in the documentation.
+*/
+Atom::AtomType CppCodeMarker::atomType() const
+{
+ return Atom::Code;
+}
+
+QString CppCodeMarker::markedUpCode(const QString &code, const Node *relative,
+ const Location &location)
+{
+ return addMarkUp(code, relative, location);
+}
+
+QString CppCodeMarker::markedUpSynopsis(const Node *node, const Node * /* relative */,
+ Section::Style style)
+{
+ const int MaxEnumValues = 6;
+ const FunctionNode *func;
+ const VariableNode *variable;
+ const EnumNode *enume;
+ QString synopsis;
+ QString name;
+
+ name = taggedNode(node);
+ if (style != Section::Details)
+ name = linkTag(node, name);
+ name = "<@name>" + name + "</@name>";
+
+ if (style == Section::Details) {
+ if (!node->isRelatedNonmember() && !node->isProxyNode() && !node->parent()->name().isEmpty()
+ && !node->parent()->isHeader() && !node->isProperty() && !node->isQmlNode()) {
+ name.prepend(taggedNode(node->parent()) + "::");
+ }
+ }
+
+ switch (node->nodeType()) {
+ case Node::Namespace:
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ synopsis = Node::nodeTypeString(node->nodeType());
+ synopsis += QLatin1Char(' ') + name;
+ break;
+ case Node::Function:
+ func = (const FunctionNode *)node;
+ if (style == Section::Details) {
+ auto templateDecl = node->templateDecl();
+ if (templateDecl)
+ synopsis = protect((*templateDecl).to_qstring()) + QLatin1Char(' ');
+ }
+ if (style != Section::AllMembers && !func->returnType().isEmpty())
+ synopsis += typified(func->returnType(), true);
+ synopsis += name;
+ if (!func->isMacroWithoutParams()) {
+ synopsis += QLatin1Char('(');
+ if (!func->parameters().isEmpty()) {
+ const Parameters &parameters = func->parameters();
+ for (int i = 0; i < parameters.count(); ++i) {
+ if (i > 0)
+ synopsis += ", ";
+ QString name = parameters.at(i).name();
+ QString type = parameters.at(i).type();
+ QString value = parameters.at(i).defaultValue();
+ bool trailingSpace = style != Section::AllMembers && !name.isEmpty();
+ synopsis += typified(type, trailingSpace);
+ if (style != Section::AllMembers && !name.isEmpty())
+ synopsis += "<@param>" + protect(name) + "</@param>";
+ if (style != Section::AllMembers && !value.isEmpty())
+ synopsis += " = " + protect(value);
+ }
+ }
+ synopsis += QLatin1Char(')');
+ }
+ if (func->isConst())
+ synopsis += " const";
+
+ if (style == Section::Summary || style == Section::Accessors) {
+ if (!func->isNonvirtual())
+ synopsis.prepend("virtual ");
+ if (func->isFinal())
+ synopsis.append(" final");
+ if (func->isOverride())
+ synopsis.append(" override");
+ if (func->isPureVirtual())
+ synopsis.append(" = 0");
+ if (func->isRef())
+ synopsis.append(" &");
+ else if (func->isRefRef())
+ synopsis.append(" &&");
+ } else if (style == Section::AllMembers) {
+ if (!func->returnType().isEmpty() && func->returnType() != "void")
+ synopsis += " : " + typified(func->returnType());
+ } else {
+ if (func->isRef())
+ synopsis.append(" &");
+ else if (func->isRefRef())
+ synopsis.append(" &&");
+ }
+ break;
+ case Node::Enum:
+ enume = static_cast<const EnumNode *>(node);
+ synopsis = "enum ";
+ if (enume->isScoped())
+ synopsis += "class ";
+ synopsis += name;
+ if (style == Section::Summary) {
+ synopsis += " { ";
+
+ QStringList documentedItems = enume->doc().enumItemNames();
+ if (documentedItems.isEmpty()) {
+ const auto &enumItems = enume->items();
+ for (const auto &item : enumItems)
+ documentedItems << item.name();
+ }
+ const QStringList omitItems = enume->doc().omitEnumItemNames();
+ for (const auto &item : omitItems)
+ documentedItems.removeAll(item);
+
+ if (documentedItems.size() > MaxEnumValues) {
+ // Take the last element and keep it safe, then elide the surplus.
+ const QString last = documentedItems.last();
+ documentedItems = documentedItems.mid(0, MaxEnumValues - 1);
+ documentedItems += "&hellip;";
+ documentedItems += last;
+ }
+ synopsis += documentedItems.join(QLatin1String(", "));
+
+ if (!documentedItems.isEmpty())
+ synopsis += QLatin1Char(' ');
+ synopsis += QLatin1Char('}');
+ }
+ break;
+ case Node::TypeAlias:
+ if (style == Section::Details) {
+ auto templateDecl = node->templateDecl();
+ if (templateDecl)
+ synopsis += protect((*templateDecl).to_qstring()) + QLatin1Char(' ');
+ }
+ synopsis += name;
+ break;
+ case Node::Typedef:
+ if (static_cast<const TypedefNode *>(node)->associatedEnum())
+ synopsis = "flags ";
+ synopsis += name;
+ break;
+ case Node::Property: {
+ auto property = static_cast<const PropertyNode *>(node);
+ synopsis = name + " : " + typified(property->qualifiedDataType());
+ break;
+ }
+ case Node::QmlProperty: {
+ auto property = static_cast<const QmlPropertyNode *>(node);
+ synopsis = name + " : " + typified(property->dataType());
+ break;
+ }
+ case Node::Variable:
+ variable = static_cast<const VariableNode *>(node);
+ if (style == Section::AllMembers) {
+ synopsis = name + " : " + typified(variable->dataType());
+ } else {
+ synopsis = typified(variable->leftType(), true) + name + protect(variable->rightType());
+ }
+ break;
+ default:
+ synopsis = name;
+ }
+
+ QString extra = CodeMarker::extraSynopsis(node, style);
+ if (!extra.isEmpty()) {
+ extra.prepend(u"<@extra>"_s);
+ extra.append(u"</@extra> "_s);
+ }
+
+ return extra + synopsis;
+}
+
+/*!
+ */
+QString CppCodeMarker::markedUpQmlItem(const Node *node, bool summary)
+{
+ QString name = taggedQmlNode(node);
+ QString synopsis;
+
+ if (summary) {
+ name = linkTag(node, name);
+ } else if (node->isQmlProperty()) {
+ const auto *pn = static_cast<const QmlPropertyNode *>(node);
+ if (pn->isAttached())
+ name.prepend(pn->element() + QLatin1Char('.'));
+ }
+ name = "<@name>" + name + "</@name>";
+ if (node->isQmlProperty()) {
+ const auto *pn = static_cast<const QmlPropertyNode *>(node);
+ synopsis = name + " : " + typified(pn->dataType());
+ } else if (node->isFunction(Node::QML)) {
+ const auto *func = static_cast<const FunctionNode *>(node);
+ if (!func->returnType().isEmpty())
+ synopsis = typified(func->returnType(), true) + name;
+ else
+ synopsis = name;
+ synopsis += QLatin1Char('(');
+ if (!func->parameters().isEmpty()) {
+ const Parameters &parameters = func->parameters();
+ for (int i = 0; i < parameters.count(); ++i) {
+ if (i > 0)
+ synopsis += ", ";
+ QString name = parameters.at(i).name();
+ QString type = parameters.at(i).type();
+ QString paramName;
+ if (!name.isEmpty()) {
+ synopsis += typified(type, true);
+ paramName = name;
+ } else {
+ paramName = type;
+ }
+ synopsis += "<@param>" + protect(paramName) + "</@param>";
+ }
+ }
+ synopsis += QLatin1Char(')');
+ } else {
+ synopsis = name;
+ }
+
+ QString extra = CodeMarker::extraSynopsis(node, summary ? Section::Summary : Section::Details);
+ if (!extra.isEmpty()) {
+ extra.prepend(u" <@extra>"_s);
+ extra.append(u"</@extra>"_s);
+ }
+
+ return synopsis + extra;
+}
+
+QString CppCodeMarker::markedUpName(const Node *node)
+{
+ QString name = linkTag(node, taggedNode(node));
+ if (node->isFunction() && !node->isMacro())
+ name += "()";
+ return name;
+}
+
+QString CppCodeMarker::markedUpEnumValue(const QString &enumValue, const Node *relative)
+{
+ const auto *node = relative->parent();
+
+ if (relative->isQmlProperty()) {
+ const auto *qpn = static_cast<const QmlPropertyNode*>(relative);
+ if (qpn->enumNode() && !enumValue.startsWith("%1."_L1.arg(qpn->enumPrefix())))
+ return "%1<@op>.</@op>%2"_L1.arg(qpn->enumPrefix(), enumValue);
+ }
+
+ if (!relative->isEnumType()) {
+ return enumValue;
+ }
+
+ QStringList parts;
+ while (!node->isHeader() && node->parent()) {
+ parts.prepend(markedUpName(node));
+ if (node->parent() == relative || node->parent()->name().isEmpty())
+ break;
+ node = node->parent();
+ }
+ if (static_cast<const EnumNode *>(relative)->isScoped())
+ parts.append(relative->name());
+
+ parts.append(enumValue);
+ return parts.join(QLatin1String("<@op>::</@op>"));
+}
+
+QString CppCodeMarker::markedUpInclude(const QString &include)
+{
+ return "<@preprocessor>#include &lt;<@headerfile>" + include + "</@headerfile>&gt;</@preprocessor>";
+}
+
+/*
+ @char
+ @class
+ @comment
+ @function
+ @keyword
+ @number
+ @op
+ @preprocessor
+ @string
+ @type
+*/
+
+QString CppCodeMarker::addMarkUp(const QString &in, const Node * /* relative */,
+ const Location & /* location */)
+{
+ static QSet<QString> types{
+ QLatin1String("bool"), QLatin1String("char"), QLatin1String("double"),
+ QLatin1String("float"), QLatin1String("int"), QLatin1String("long"),
+ QLatin1String("short"), QLatin1String("signed"), QLatin1String("unsigned"),
+ QLatin1String("uint"), QLatin1String("ulong"), QLatin1String("ushort"),
+ QLatin1String("uchar"), QLatin1String("void"), QLatin1String("qlonglong"),
+ QLatin1String("qulonglong"), QLatin1String("qint"), QLatin1String("qint8"),
+ QLatin1String("qint16"), QLatin1String("qint32"), QLatin1String("qint64"),
+ QLatin1String("quint"), QLatin1String("quint8"), QLatin1String("quint16"),
+ QLatin1String("quint32"), QLatin1String("quint64"), QLatin1String("qreal"),
+ QLatin1String("cond")
+ };
+
+ static QSet<QString> keywords{
+ QLatin1String("and"), QLatin1String("and_eq"), QLatin1String("asm"), QLatin1String("auto"),
+ QLatin1String("bitand"), QLatin1String("bitor"), QLatin1String("break"),
+ QLatin1String("case"), QLatin1String("catch"), QLatin1String("class"),
+ QLatin1String("compl"), QLatin1String("const"), QLatin1String("const_cast"),
+ QLatin1String("continue"), QLatin1String("default"), QLatin1String("delete"),
+ QLatin1String("do"), QLatin1String("dynamic_cast"), QLatin1String("else"),
+ QLatin1String("enum"), QLatin1String("explicit"), QLatin1String("export"),
+ QLatin1String("extern"), QLatin1String("false"), QLatin1String("for"),
+ QLatin1String("friend"), QLatin1String("goto"), QLatin1String("if"),
+ QLatin1String("include"), QLatin1String("inline"), QLatin1String("monitor"),
+ QLatin1String("mutable"), QLatin1String("namespace"), QLatin1String("new"),
+ QLatin1String("not"), QLatin1String("not_eq"), QLatin1String("operator"),
+ QLatin1String("or"), QLatin1String("or_eq"), QLatin1String("private"),
+ QLatin1String("protected"), QLatin1String("public"), QLatin1String("register"),
+ QLatin1String("reinterpret_cast"), QLatin1String("return"), QLatin1String("sizeof"),
+ QLatin1String("static"), QLatin1String("static_cast"), QLatin1String("struct"),
+ QLatin1String("switch"), QLatin1String("template"), QLatin1String("this"),
+ QLatin1String("throw"), QLatin1String("true"), QLatin1String("try"),
+ QLatin1String("typedef"), QLatin1String("typeid"), QLatin1String("typename"),
+ QLatin1String("union"), QLatin1String("using"), QLatin1String("virtual"),
+ QLatin1String("volatile"), QLatin1String("wchar_t"), QLatin1String("while"),
+ QLatin1String("xor"), QLatin1String("xor_eq"), QLatin1String("synchronized"),
+ // Qt specific
+ QLatin1String("signals"), QLatin1String("slots"), QLatin1String("emit")
+ };
+
+ QString code = in;
+ QString out;
+ QStringView text;
+ int braceDepth = 0;
+ int parenDepth = 0;
+ int i = 0;
+ int start = 0;
+ int finish = 0;
+ QChar ch;
+ static const QRegularExpression classRegExp(QRegularExpression::anchoredPattern("Qt?(?:[A-Z3]+[a-z][A-Za-z]*|t)"));
+ static const QRegularExpression functionRegExp(QRegularExpression::anchoredPattern("q([A-Z][a-z]+)+"));
+ static const QRegularExpression findFunctionRegExp(QStringLiteral("^\\s*\\("));
+ bool atEOF = false;
+
+ auto readChar = [&]() {
+ if (i < code.size())
+ ch = code[i++];
+ else
+ atEOF = true;
+ };
+
+ readChar();
+ while (!atEOF) {
+ QString tag;
+ bool target = false;
+
+ if (ch.isLetter() || ch == '_') {
+ QString ident;
+ do {
+ ident += ch;
+ finish = i;
+ readChar();
+ } while (!atEOF && (ch.isLetterOrNumber() || ch == '_'));
+
+ if (classRegExp.match(ident).hasMatch()) {
+ tag = QStringLiteral("type");
+ } else if (functionRegExp.match(ident).hasMatch()) {
+ tag = QStringLiteral("func");
+ target = true;
+ } else if (types.contains(ident)) {
+ tag = QStringLiteral("type");
+ } else if (keywords.contains(ident)) {
+ tag = QStringLiteral("keyword");
+ } else if (braceDepth == 0 && parenDepth == 0) {
+ if (code.indexOf(findFunctionRegExp, i - 1) == i - 1)
+ tag = QStringLiteral("func");
+ target = true;
+ }
+ } else if (ch.isDigit()) {
+ do {
+ finish = i;
+ readChar();
+ } while (!atEOF && (ch.isLetterOrNumber() || ch == '.' || ch == '\''));
+ tag = QStringLiteral("number");
+ } else {
+ switch (ch.unicode()) {
+ case '+':
+ case '-':
+ case '!':
+ case '%':
+ case '^':
+ case '&':
+ case '*':
+ case ',':
+ case '.':
+ case '<':
+ case '=':
+ case '>':
+ case '?':
+ case '[':
+ case ']':
+ case '|':
+ case '~':
+ finish = i;
+ readChar();
+ tag = QStringLiteral("op");
+ break;
+ case '"':
+ finish = i;
+ readChar();
+
+ while (!atEOF && ch != '"') {
+ if (ch == '\\')
+ readChar();
+ readChar();
+ }
+ finish = i;
+ readChar();
+ tag = QStringLiteral("string");
+ break;
+ case '#':
+ finish = i;
+ readChar();
+ while (!atEOF && ch != '\n') {
+ if (ch == '\\')
+ readChar();
+ finish = i;
+ readChar();
+ }
+ tag = QStringLiteral("preprocessor");
+ break;
+ case '\'':
+ finish = i;
+ readChar();
+
+ while (!atEOF && ch != '\'') {
+ if (ch == '\\')
+ readChar();
+ readChar();
+ }
+ finish = i;
+ readChar();
+ tag = QStringLiteral("char");
+ break;
+ case '(':
+ finish = i;
+ readChar();
+ ++parenDepth;
+ break;
+ case ')':
+ finish = i;
+ readChar();
+ --parenDepth;
+ break;
+ case ':':
+ finish = i;
+ readChar();
+ if (!atEOF && ch == ':') {
+ finish = i;
+ readChar();
+ tag = QStringLiteral("op");
+ }
+ break;
+ case '/':
+ finish = i;
+ readChar();
+ if (!atEOF && ch == '/') {
+ do {
+ finish = i;
+ readChar();
+ } while (!atEOF && ch != '\n');
+ tag = QStringLiteral("comment");
+ } else if (ch == '*') {
+ bool metAster = false;
+ bool metAsterSlash = false;
+
+ finish = i;
+ readChar();
+
+ while (!metAsterSlash) {
+ if (atEOF)
+ break;
+ if (ch == '*')
+ metAster = true;
+ else if (metAster && ch == '/')
+ metAsterSlash = true;
+ else
+ metAster = false;
+ finish = i;
+ readChar();
+ }
+ tag = QStringLiteral("comment");
+ } else {
+ tag = QStringLiteral("op");
+ }
+ break;
+ case '{':
+ finish = i;
+ readChar();
+ braceDepth++;
+ break;
+ case '}':
+ finish = i;
+ readChar();
+ braceDepth--;
+ break;
+ default:
+ finish = i;
+ readChar();
+ }
+ }
+
+ text = QStringView{code}.mid(start, finish - start);
+ start = finish;
+
+ if (!tag.isEmpty()) {
+ out += QStringLiteral("<@");
+ out += tag;
+ if (target) {
+ out += QStringLiteral(" target=\"");
+ out += text;
+ out += QStringLiteral("()\"");
+ }
+ out += QStringLiteral(">");
+ }
+
+ appendProtectedString(&out, text);
+
+ if (!tag.isEmpty()) {
+ out += QStringLiteral("</@");
+ out += tag;
+ out += QStringLiteral(">");
+ }
+ }
+
+ if (start < code.size()) {
+ appendProtectedString(&out, QStringView{code}.mid(start));
+ }
+
+ return out;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/cppcodemarker.h b/src/qdoc/qdoc/src/qdoc/cppcodemarker.h
new file mode 100644
index 000000000..b0c5f3615
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/cppcodemarker.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CPPCODEMARKER_H
+#define CPPCODEMARKER_H
+
+#include "codemarker.h"
+
+QT_BEGIN_NAMESPACE
+
+class CppCodeMarker : public CodeMarker
+{
+public:
+ CppCodeMarker() = default;
+ ~CppCodeMarker() override = default;
+
+ bool recognizeCode(const QString &code) override;
+ bool recognizeExtension(const QString &ext) override;
+ bool recognizeLanguage(const QString &lang) override;
+ [[nodiscard]] Atom::AtomType atomType() const override;
+ QString markedUpCode(const QString &code, const Node *relative,
+ const Location &location) override;
+ QString markedUpSynopsis(const Node *node, const Node *relative, Section::Style style) override;
+ QString markedUpQmlItem(const Node *node, bool summary) override;
+ QString markedUpName(const Node *node) override;
+ QString markedUpEnumValue(const QString &enumValue, const Node *relative) override;
+ QString markedUpInclude(const QString &include) override;
+
+private:
+ QString addMarkUp(const QString &protectedCode, const Node *relative, const Location &location);
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp b/src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp
new file mode 100644
index 000000000..d2e8d7c63
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp
@@ -0,0 +1,1021 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "cppcodeparser.h"
+
+#include "access.h"
+#include "classnode.h"
+#include "clangcodeparser.h"
+#include "collectionnode.h"
+#include "comparisoncategory.h"
+#include "config.h"
+#include "examplenode.h"
+#include "externalpagenode.h"
+#include "functionnode.h"
+#include "generator.h"
+#include "headernode.h"
+#include "namespacenode.h"
+#include "qdocdatabase.h"
+#include "qmltypenode.h"
+#include "qmlpropertynode.h"
+#include "sharedcommentnode.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qmap.h>
+
+#include <algorithm>
+
+using namespace Qt::Literals::StringLiterals;
+
+QT_BEGIN_NAMESPACE
+
+/*
+ All these can appear in a C++ namespace. Don't add
+ anything that can't be in a C++ namespace.
+ */
+static const QMap<QString, Node::NodeType> s_nodeTypeMap{
+ { COMMAND_NAMESPACE, Node::Namespace }, { COMMAND_NAMESPACE, Node::Namespace },
+ { COMMAND_CLASS, Node::Class }, { COMMAND_STRUCT, Node::Struct },
+ { COMMAND_UNION, Node::Union }, { COMMAND_ENUM, Node::Enum },
+ { COMMAND_TYPEALIAS, Node::TypeAlias }, { COMMAND_TYPEDEF, Node::Typedef },
+ { COMMAND_PROPERTY, Node::Property }, { COMMAND_VARIABLE, Node::Variable }
+};
+
+typedef bool (Node::*NodeTypeTestFunc)() const;
+static const QMap<QString, NodeTypeTestFunc> s_nodeTypeTestFuncMap{
+ { COMMAND_NAMESPACE, &Node::isNamespace }, { COMMAND_CLASS, &Node::isClassNode },
+ { COMMAND_STRUCT, &Node::isStruct }, { COMMAND_UNION, &Node::isUnion },
+ { COMMAND_ENUM, &Node::isEnumType }, { COMMAND_TYPEALIAS, &Node::isTypeAlias },
+ { COMMAND_TYPEDEF, &Node::isTypedef }, { COMMAND_PROPERTY, &Node::isProperty },
+ { COMMAND_VARIABLE, &Node::isVariable },
+};
+
+CppCodeParser::CppCodeParser(FnCommandParser&& parser)
+ : fn_parser{parser}
+{
+ Config &config = Config::instance();
+ QStringList exampleFilePatterns{config.get(CONFIG_EXAMPLES
+ + Config::dot
+ + CONFIG_FILEEXTENSIONS).asStringList()};
+
+ if (!exampleFilePatterns.isEmpty())
+ m_exampleNameFilter = exampleFilePatterns.join(' ');
+ else
+ m_exampleNameFilter = "*.cpp *.h *.js *.xq *.svg *.xml *.ui";
+
+ QStringList exampleImagePatterns{config.get(CONFIG_EXAMPLES
+ + Config::dot
+ + CONFIG_IMAGEEXTENSIONS).asStringList()};
+
+ if (!exampleImagePatterns.isEmpty())
+ m_exampleImageFilter = exampleImagePatterns.join(' ');
+ else
+ m_exampleImageFilter = "*.png";
+
+ m_showLinkErrors = !config.get(CONFIG_NOLINKERRORS).asBool();
+}
+
+/*!
+ Process the topic \a command found in the \a doc with argument \a arg.
+ */
+Node *CppCodeParser::processTopicCommand(const Doc &doc, const QString &command,
+ const ArgPair &arg)
+{
+ QDocDatabase* database = QDocDatabase::qdocDB();
+
+ if (command == COMMAND_FN) {
+ Q_UNREACHABLE();
+ } else if (s_nodeTypeMap.contains(command)) {
+ /*
+ We should only get in here if the command refers to
+ something that can appear in a C++ namespace,
+ i.e. a class, another namespace, an enum, a typedef,
+ a property or a variable. I think these are handled
+ this way to allow the writer to refer to the entity
+ without including the namespace qualifier.
+ */
+ Node::NodeType type = s_nodeTypeMap[command];
+ QStringList words = arg.first.split(QLatin1Char(' '));
+ QStringList path;
+ qsizetype idx = 0;
+ Node *node = nullptr;
+
+ if (type == Node::Variable && words.size() > 1)
+ idx = words.size() - 1;
+ path = words[idx].split("::");
+
+ node = database->findNodeByNameAndType(path, s_nodeTypeTestFuncMap[command]);
+ // Allow representing a type alias as a class
+ if (node == nullptr && command == COMMAND_CLASS) {
+ node = database->findNodeByNameAndType(path, &Node::isTypeAlias);
+ if (node) {
+ auto access = node->access();
+ auto loc = node->location();
+ auto templateDecl = node->templateDecl();
+ node = new ClassNode(Node::Class, node->parent(), node->name());
+ node->setAccess(access);
+ node->setLocation(loc);
+ node->setTemplateDecl(templateDecl);
+ }
+ }
+ if (node == nullptr) {
+ if (CodeParser::isWorthWarningAbout(doc)) {
+ doc.location().warning(
+ QStringLiteral("Cannot find '%1' specified with '\\%2' in any header file")
+ .arg(arg.first, command));
+ }
+ } else if (node->isAggregate()) {
+ if (type == Node::Namespace) {
+ auto *ns = static_cast<NamespaceNode *>(node);
+ ns->markSeen();
+ ns->setWhereDocumented(ns->tree()->camelCaseModuleName());
+ }
+ }
+ return node;
+ } else if (command == COMMAND_EXAMPLE) {
+ if (Config::generateExamples) {
+ auto *en = new ExampleNode(database->primaryTreeRoot(), arg.first);
+ en->setLocation(doc.startLocation());
+ setExampleFileLists(en);
+ return en;
+ }
+ } else if (command == COMMAND_EXTERNALPAGE) {
+ auto *epn = new ExternalPageNode(database->primaryTreeRoot(), arg.first);
+ epn->setLocation(doc.startLocation());
+ return epn;
+ } else if (command == COMMAND_HEADERFILE) {
+ auto *hn = new HeaderNode(database->primaryTreeRoot(), arg.first);
+ hn->setLocation(doc.startLocation());
+ return hn;
+ } else if (command == COMMAND_GROUP) {
+ CollectionNode *cn = database->addGroup(arg.first);
+ cn->setLocation(doc.startLocation());
+ cn->markSeen();
+ return cn;
+ } else if (command == COMMAND_MODULE) {
+ CollectionNode *cn = database->addModule(arg.first);
+ cn->setLocation(doc.startLocation());
+ cn->markSeen();
+ return cn;
+ } else if (command == COMMAND_QMLMODULE) {
+ QStringList blankSplit = arg.first.split(QLatin1Char(' '));
+ CollectionNode *cn = database->addQmlModule(blankSplit[0]);
+ cn->setLogicalModuleInfo(blankSplit);
+ cn->setLocation(doc.startLocation());
+ cn->markSeen();
+ return cn;
+ } else if (command == COMMAND_PAGE) {
+ auto *pn = new PageNode(database->primaryTreeRoot(), arg.first.split(' ').front());
+ pn->setLocation(doc.startLocation());
+ return pn;
+ } else if (command == COMMAND_QMLTYPE ||
+ command == COMMAND_QMLVALUETYPE ||
+ command == COMMAND_QMLBASICTYPE) {
+ auto nodeType = (command == COMMAND_QMLTYPE) ? Node::QmlType : Node::QmlValueType;
+ QString qmid;
+ if (auto args = doc.metaCommandArgs(COMMAND_INQMLMODULE); !args.isEmpty())
+ qmid = args.first().first;
+ auto *qcn = database->findQmlTypeInPrimaryTree(qmid, arg.first);
+ // A \qmlproperty may have already constructed a placeholder type
+ // without providing a module identifier; allow such cases
+ if (!qcn && !qmid.isEmpty())
+ qcn = database->findQmlTypeInPrimaryTree(QString(), arg.first);
+ if (!qcn || qcn->nodeType() != nodeType)
+ qcn = new QmlTypeNode(database->primaryTreeRoot(), arg.first, nodeType);
+ if (!qmid.isEmpty())
+ database->addToQmlModule(qmid, qcn);
+ qcn->setLocation(doc.startLocation());
+ return qcn;
+ } else if ((command == COMMAND_QMLSIGNAL) || (command == COMMAND_QMLMETHOD)
+ || (command == COMMAND_QMLATTACHEDSIGNAL) || (command == COMMAND_QMLATTACHEDMETHOD)) {
+ Q_UNREACHABLE();
+ }
+ return nullptr;
+}
+
+/*!
+ A QML property argument has the form...
+
+ <type> <QML-type>::<name>
+ <type> <QML-module>::<QML-type>::<name>
+
+ This function splits the argument into one of those
+ two forms. The three part form is the old form, which
+ was used before the creation of Qt Quick 2 and Qt
+ Components. A <QML-module> is the QML equivalent of a
+ C++ namespace. So this function splits \a arg on "::"
+ and stores the parts in \a type, \a module, \a qmlTypeName,
+ and \a name, and returns \c true. If any part other than
+ \a module is not found, a qdoc warning is emitted and
+ false is returned.
+
+ \note The two QML types \e{Component} and \e{QtObject}
+ never have a module qualifier.
+ */
+bool CppCodeParser::splitQmlPropertyArg(const QString &arg, QString &type, QString &module,
+ QString &qmlTypeName, QString &name,
+ const Location &location)
+{
+ QStringList blankSplit = arg.split(QLatin1Char(' '));
+ if (blankSplit.size() > 1) {
+ type = blankSplit[0];
+ QStringList colonSplit(blankSplit[1].split("::"));
+ if (colonSplit.size() == 3) {
+ module = colonSplit[0];
+ qmlTypeName = colonSplit[1];
+ name = colonSplit[2];
+ return true;
+ }
+ if (colonSplit.size() == 2) {
+ module.clear();
+ qmlTypeName = colonSplit[0];
+ name = colonSplit[1];
+ return true;
+ }
+ location.warning(
+ QStringLiteral("Unrecognizable QML module/component qualifier for %1").arg(arg));
+ } else {
+ location.warning(QStringLiteral("Missing property type for %1").arg(arg));
+ }
+ return false;
+}
+
+std::vector<TiedDocumentation> CppCodeParser::processQmlProperties(const UntiedDocumentation &untied)
+{
+ const Doc &doc = untied.documentation;
+ const TopicList &topics = doc.topicsUsed();
+ if (topics.isEmpty())
+ return {};
+
+ QString arg;
+ QString type;
+ QString group;
+ QString qmlModule;
+ QString property;
+ QString qmlTypeName;
+
+ std::vector<TiedDocumentation> tied{};
+
+ Topic topic = topics.at(0);
+ arg = topic.m_args;
+ if (splitQmlPropertyArg(arg, type, qmlModule, qmlTypeName, property, doc.location())) {
+ qsizetype i = property.indexOf('.');
+ if (i != -1)
+ group = property.left(i);
+ }
+
+ QDocDatabase *database = QDocDatabase::qdocDB();
+
+ NodeList sharedNodes;
+ QmlTypeNode *qmlType = database->findQmlTypeInPrimaryTree(qmlModule, qmlTypeName);
+ // Note: Constructing a QmlType node by default, as opposed to QmlValueType.
+ // This may lead to unexpected behavior if documenting \qmlvaluetype's properties
+ // before the type itself.
+ if (qmlType == nullptr) {
+ qmlType = new QmlTypeNode(database->primaryTreeRoot(), qmlTypeName, Node::QmlType);
+ qmlType->setLocation(doc.startLocation());
+ if (!qmlModule.isEmpty())
+ database->addToQmlModule(qmlModule, qmlType);
+ }
+
+ for (const auto &topicCommand : topics) {
+ QString cmd = topicCommand.m_topic;
+ arg = topicCommand.m_args;
+ if ((cmd == COMMAND_QMLPROPERTY) || (cmd == COMMAND_QMLATTACHEDPROPERTY)) {
+ bool attached = cmd.contains(QLatin1String("attached"));
+ if (splitQmlPropertyArg(arg, type, qmlModule, qmlTypeName, property, doc.location())) {
+ if (qmlType != database->findQmlTypeInPrimaryTree(qmlModule, qmlTypeName)) {
+ doc.startLocation().warning(
+ QStringLiteral(
+ "All properties in a group must belong to the same type: '%1'")
+ .arg(arg));
+ continue;
+ }
+ QmlPropertyNode *existingProperty = qmlType->hasQmlProperty(property, attached);
+ if (existingProperty) {
+ processMetaCommands(doc, existingProperty);
+ if (!doc.body().isEmpty()) {
+ doc.startLocation().warning(
+ QStringLiteral("QML property documented multiple times: '%1'")
+ .arg(arg), QStringLiteral("also seen here: %1")
+ .arg(existingProperty->location().toString()));
+ }
+ continue;
+ }
+ auto *qpn = new QmlPropertyNode(qmlType, property, type, attached);
+ qpn->setLocation(doc.startLocation());
+ qpn->setGenus(Node::QML);
+
+ tied.emplace_back(TiedDocumentation{doc, qpn});
+
+ sharedNodes << qpn;
+ }
+ } else {
+ doc.startLocation().warning(
+ QStringLiteral("Command '\\%1'; not allowed with QML property commands")
+ .arg(cmd));
+ }
+ }
+
+ // Construct a SharedCommentNode (scn) if multiple topics generated
+ // valid nodes. Note that it's important to do this *after* constructing
+ // the topic nodes - which need to be written to index before the related
+ // scn.
+ if (sharedNodes.size() > 1) {
+ auto *scn = new SharedCommentNode(qmlType, sharedNodes.size(), group);
+ scn->setLocation(doc.startLocation());
+
+ tied.emplace_back(TiedDocumentation{doc, scn});
+
+ for (const auto n : sharedNodes)
+ scn->append(n);
+ scn->sort();
+ }
+
+ return tied;
+}
+
+/*!
+ Process the metacommand \a command in the context of the
+ \a node associated with the topic command and the \a doc.
+ \a arg is the argument to the metacommand.
+
+ \a node is guaranteed to be non-null.
+ */
+void CppCodeParser::processMetaCommand(const Doc &doc, const QString &command,
+ const ArgPair &argPair, Node *node)
+{
+ QDocDatabase* database = QDocDatabase::qdocDB();
+
+ QString arg = argPair.first;
+ if (command == COMMAND_INHEADERFILE) {
+ // TODO: [incorrect-constructs][header-arg]
+ // The emptiness check for arg is required as,
+ // currently, DocParser fancies passing (without any warning)
+ // incorrect constructs doen the chain, such as an
+ // "\inheaderfile" command with no argument.
+ //
+ // As it is the case here, we require further sanity checks to
+ // preserve some of the semantic for the later phases.
+ // This generally has a ripple effect on the whole codebase,
+ // making it more complex and increasesing the surface of bugs.
+ //
+ // The following emptiness check should be removed as soon as
+ // DocParser is enhanced with correct semantics.
+ if (node->isAggregate() && !arg.isEmpty())
+ static_cast<Aggregate *>(node)->setIncludeFile(arg);
+ else
+ doc.location().warning(QStringLiteral("Ignored '\\%1'").arg(COMMAND_INHEADERFILE));
+ } else if (command == COMMAND_COMPARES) {
+ processComparesCommand(node, arg, doc.location());
+ } else if (command == COMMAND_COMPARESWITH) {
+ if (!node->isClassNode())
+ doc.location().warning(
+ u"Found \\%1 command outside of \\%2 context."_s
+ .arg(COMMAND_COMPARESWITH, COMMAND_CLASS));
+ } else if (command == COMMAND_OVERLOAD) {
+ /*
+ Note that this might set the overload flag of the
+ primary function. This is ok because the overload
+ flags and overload numbers will be resolved later
+ in Aggregate::normalizeOverloads().
+ */
+ if (node->isFunction())
+ static_cast<FunctionNode *>(node)->setOverloadFlag();
+ else if (node->isSharedCommentNode())
+ static_cast<SharedCommentNode *>(node)->setOverloadFlags();
+ else
+ doc.location().warning(QStringLiteral("Ignored '\\%1'").arg(COMMAND_OVERLOAD));
+ } else if (command == COMMAND_REIMP) {
+ if (node->parent() && !node->parent()->isInternal()) {
+ if (node->isFunction()) {
+ auto *fn = static_cast<FunctionNode *>(node);
+ // The clang visitor class will have set the
+ // qualified name of the overridden function.
+ // If the name of the overridden function isn't
+ // set, issue a warning.
+ if (fn->overridesThis().isEmpty() && CodeParser::isWorthWarningAbout(doc)) {
+ doc.location().warning(
+ QStringLiteral("Cannot find base function for '\\%1' in %2()")
+ .arg(COMMAND_REIMP, node->name()),
+ QStringLiteral("The function either doesn't exist in any "
+ "base class with the same signature or it "
+ "exists but isn't virtual."));
+ }
+ fn->setReimpFlag();
+ } else {
+ doc.location().warning(
+ QStringLiteral("Ignored '\\%1' in %2").arg(COMMAND_REIMP, node->name()));
+ }
+ }
+ } else if (command == COMMAND_RELATES) {
+ // REMARK: Generates warnings only; Node instances are
+ // adopted from the root namespace to other Aggregates
+ // in a post-processing step, Aggregate::resolveRelates(),
+ // after all topic commands are processed.
+ if (node->isAggregate()) {
+ doc.location().warning("Invalid '\\%1' not allowed in '\\%2'"_L1
+ .arg(COMMAND_RELATES, node->nodeTypeString()));
+ } else if (!node->isRelatedNonmember() && node->parent()->isClassNode()) {
+ if (!doc.isInternal()) {
+ doc.location().warning("Invalid '\\%1' ('%2' must be global)"_L1
+ .arg(COMMAND_RELATES, node->name()));
+ }
+ }
+ } else if (command == COMMAND_NEXTPAGE) {
+ CodeParser::setLink(node, Node::NextLink, arg);
+ } else if (command == COMMAND_PREVIOUSPAGE) {
+ CodeParser::setLink(node, Node::PreviousLink, arg);
+ } else if (command == COMMAND_STARTPAGE) {
+ CodeParser::setLink(node, Node::StartLink, arg);
+ } else if (command == COMMAND_QMLINHERITS) {
+ if (node->name() == arg)
+ doc.location().warning(QStringLiteral("%1 tries to inherit itself").arg(arg));
+ else if (node->isQmlType()) {
+ auto *qmlType = static_cast<QmlTypeNode *>(node);
+ qmlType->setQmlBaseName(arg);
+ }
+ } else if (command == COMMAND_QMLNATIVETYPE || command == COMMAND_QMLINSTANTIATES) {
+ if (command == COMMAND_QMLINSTANTIATES)
+ doc.location().report(u"\\instantiates is deprected and will be removed in a future version. Use \\nativetype instead."_s);
+ // TODO: COMMAND_QMLINSTANTIATES is deprecated since 6.8. Its remains should be removed no later than Qt 7.0.0.
+ processQmlNativeTypeCommand(node, arg, doc.location());
+ } else if (command == COMMAND_DEFAULT) {
+ if (!node->isQmlProperty()) {
+ doc.location().warning(QStringLiteral("Ignored '\\%1', applies only to '\\%2'")
+ .arg(command, COMMAND_QMLPROPERTY));
+ } else if (arg.isEmpty()) {
+ doc.location().warning(QStringLiteral("Expected an argument for '\\%1' (maybe you meant '\\%2'?)")
+ .arg(command, COMMAND_QMLDEFAULT));
+ } else {
+ static_cast<QmlPropertyNode *>(node)->setDefaultValue(arg);
+ }
+ } else if (command == COMMAND_QMLDEFAULT) {
+ node->markDefault();
+ } else if (command == COMMAND_QMLENUMERATORSFROM) {
+ if (!node->isQmlProperty()) {
+ doc.location().warning("Ignored '\\%1', applies only to '\\%2'"_L1
+ .arg(command, COMMAND_QMLPROPERTY));
+ } else if (!static_cast<QmlPropertyNode*>(node)->setEnumNode(argPair.first, argPair.second)) {
+ doc.location().warning("Failed to find C++ enumeration '%2' passed to \\%1"_L1
+ .arg(command, arg), "Use \\value commands instead"_L1);
+ }
+ } else if (command == COMMAND_QMLREADONLY) {
+ node->markReadOnly(true);
+ } else if (command == COMMAND_QMLREQUIRED) {
+ if (!node->isQmlProperty())
+ doc.location().warning(QStringLiteral("Ignored '\\%1'").arg(COMMAND_QMLREQUIRED));
+ else
+ static_cast<QmlPropertyNode *>(node)->setRequired();
+ } else if ((command == COMMAND_QMLABSTRACT) || (command == COMMAND_ABSTRACT)) {
+ if (node->isQmlType())
+ node->setAbstract(true);
+ } else if (command == COMMAND_DEPRECATED) {
+ node->setDeprecated(argPair.second);
+ } else if (command == COMMAND_INGROUP || command == COMMAND_INPUBLICGROUP) {
+ // Note: \ingroup and \inpublicgroup are the same (and now recognized as such).
+ database->addToGroup(arg, node);
+ } else if (command == COMMAND_INMODULE) {
+ database->addToModule(arg, node);
+ } else if (command == COMMAND_INQMLMODULE) {
+ // Handled when parsing topic commands
+ } else if (command == COMMAND_OBSOLETE) {
+ node->setStatus(Node::Deprecated);
+ } else if (command == COMMAND_NONREENTRANT) {
+ node->setThreadSafeness(Node::NonReentrant);
+ } else if (command == COMMAND_PRELIMINARY) {
+ // \internal wins.
+ if (!node->isInternal())
+ node->setStatus(Node::Preliminary);
+ } else if (command == COMMAND_INTERNAL) {
+ if (!Config::instance().showInternal())
+ node->markInternal();
+ } else if (command == COMMAND_REENTRANT) {
+ node->setThreadSafeness(Node::Reentrant);
+ } else if (command == COMMAND_SINCE) {
+ node->setSince(arg);
+ } else if (command == COMMAND_WRAPPER) {
+ node->setWrapper();
+ } else if (command == COMMAND_THREADSAFE) {
+ node->setThreadSafeness(Node::ThreadSafe);
+ } else if (command == COMMAND_TITLE) {
+ if (!node->setTitle(arg))
+ doc.location().warning(QStringLiteral("Ignored '\\%1'").arg(COMMAND_TITLE));
+ else if (node->isExample())
+ database->addExampleNode(static_cast<ExampleNode *>(node));
+ } else if (command == COMMAND_SUBTITLE) {
+ if (!node->setSubtitle(arg))
+ doc.location().warning(QStringLiteral("Ignored '\\%1'").arg(COMMAND_SUBTITLE));
+ } else if (command == COMMAND_QTVARIABLE) {
+ node->setQtVariable(arg);
+ if (!node->isModule() && !node->isQmlModule())
+ doc.location().warning(
+ QStringLiteral(
+ "Command '\\%1' is only meaningful in '\\module' and '\\qmlmodule'.")
+ .arg(COMMAND_QTVARIABLE));
+ } else if (command == COMMAND_QTCMAKEPACKAGE) {
+ if (node->isModule())
+ node->setQtCMakeComponent(arg);
+ else
+ doc.location().warning(
+ QStringLiteral("Command '\\%1' is only meaningful in '\\module'.")
+ .arg(COMMAND_QTCMAKEPACKAGE));
+ } else if (command == COMMAND_QTCMAKETARGETITEM) {
+ if (node->isModule())
+ node->setQtCMakeTargetItem(arg);
+ else
+ doc.location().warning(
+ QStringLiteral("Command '\\%1' is only meaningful in '\\module'.")
+ .arg(COMMAND_QTCMAKETARGETITEM));
+ } else if (command == COMMAND_MODULESTATE ) {
+ if (!node->isModule() && !node->isQmlModule()) {
+ doc.location().warning(
+ QStringLiteral(
+ "Command '\\%1' is only meaningful in '\\module' and '\\qmlmodule'.")
+ .arg(COMMAND_MODULESTATE));
+ } else {
+ static_cast<CollectionNode*>(node)->setState(arg);
+ }
+ } else if (command == COMMAND_NOAUTOLIST) {
+ if (!node->isCollectionNode() && !node->isExample()) {
+ doc.location().warning(
+ QStringLiteral(
+ "Command '\\%1' is only meaningful in '\\module', '\\qmlmodule', `\\group` and `\\example`.")
+ .arg(COMMAND_NOAUTOLIST));
+ } else {
+ static_cast<PageNode*>(node)->setNoAutoList(true);
+ }
+ } else if (command == COMMAND_ATTRIBUTION) {
+ // TODO: This condition is not currently exact enough, as it
+ // will allow any non-aggregate `PageNode` to use the command,
+ // For example, an `ExampleNode`.
+ //
+ // The command is intended only for internal usage by
+ // "qattributionscanner" and should only work on `PageNode`s
+ // that are generated from a "\page" command.
+ //
+ // It is already possible to provide a more restricted check,
+ // albeit in a somewhat dirty way. It is not expected that
+ // this warning will have any particular use.
+ // If it so happens that a case where the too-broad scope of
+ // the warning is a problem or hides a bug, modify the
+ // condition to be restrictive enough.
+ // Otherwise, wait until a more torough look at QDoc's
+ // internal representations an way to enable "Attribution
+ // Pages" is performed before looking at the issue again.
+ if (!node->isTextPageNode()) {
+ doc.location().warning(u"Command '\\%1' is only meaningful in '\\%2'"_s.arg(COMMAND_ATTRIBUTION, COMMAND_PAGE));
+ } else { static_cast<PageNode*>(node)->markAttribution(); }
+ }
+}
+
+/*!
+ \internal
+ Processes the argument \a arg that's passed to the \\compares command,
+ and sets the comparison category of the \a node accordingly.
+
+ If the argument is invalid, issue a warning at the location the command
+ appears through \a loc.
+*/
+void CppCodeParser::processComparesCommand(Node *node, const QString &arg, const Location &loc)
+{
+ if (!node->isClassNode()) {
+ loc.warning(u"Found \\%1 command outside of \\%2 context."_s.arg(COMMAND_COMPARES,
+ COMMAND_CLASS));
+ return;
+ }
+
+ if (auto category = comparisonCategoryFromString(arg.toStdString());
+ category != ComparisonCategory::None) {
+ node->setComparisonCategory(category);
+ } else {
+ loc.warning(u"Invalid argument to \\%1 command: `%2`"_s.arg(COMMAND_COMPARES, arg),
+ u"Valid arguments are `strong`, `weak`, `partial`, or `equality`."_s);
+ }
+}
+
+/*!
+ The topic command has been processed, and now \a doc and
+ \a node are passed to this function to get the metacommands
+ from \a doc and process them one at a time. \a node is the
+ node where \a doc resides.
+ */
+void CppCodeParser::processMetaCommands(const Doc &doc, Node *node)
+{
+ std::vector<Node*> nodes_to_process{};
+ if (node->isSharedCommentNode()) {
+ auto scn = static_cast<SharedCommentNode*>(node);
+
+ nodes_to_process.reserve(scn->count() + 1);
+ std::copy(scn->collective().cbegin(), scn->collective().cend(), std::back_inserter(nodes_to_process));
+ }
+
+ // REMARK: Ordering is important here. If node is a
+ // SharedCommentNode it MUST be processed after all its child
+ // nodes.
+ // Failure to do so can incur in incorrect warnings.
+ // For example, if a shared documentation has a "\relates" command.
+ // When the command is processed for the SharedCommentNode it will
+ // apply to all its child nodes.
+ // If a child node is processed after the SharedCommentNode that
+ // contains it, that "\relates" command will be considered applied
+ // already, resulting in a warning.
+ nodes_to_process.push_back(node);
+
+ const QStringList metaCommandsUsed = doc.metaCommandsUsed().values();
+ for (const auto &command : metaCommandsUsed) {
+ const ArgList args = doc.metaCommandArgs(command);
+ for (const auto &arg : args) {
+ std::for_each(nodes_to_process.cbegin(), nodes_to_process.cend(), [this, doc, command, arg](auto node){
+ processMetaCommand(doc, command, arg, node);
+ });
+ }
+ }
+}
+
+/*!
+ Parse QML signal/method topic commands.
+ */
+FunctionNode *CppCodeParser::parseOtherFuncArg(const QString &topic, const Location &location,
+ const QString &funcArg)
+{
+ QString funcName;
+ QString returnType;
+
+ qsizetype leftParen = funcArg.indexOf(QChar('('));
+ if (leftParen > 0)
+ funcName = funcArg.left(leftParen);
+ else
+ funcName = funcArg;
+ qsizetype firstBlank = funcName.indexOf(QChar(' '));
+ if (firstBlank > 0) {
+ returnType = funcName.left(firstBlank);
+ funcName = funcName.right(funcName.size() - firstBlank - 1);
+ }
+
+ QStringList colonSplit(funcName.split("::"));
+ if (colonSplit.size() < 2) {
+ QString msg = "Unrecognizable QML module/component qualifier for " + funcArg;
+ location.warning(msg.toLatin1().data());
+ return nullptr;
+ }
+ QString moduleName;
+ QString elementName;
+ if (colonSplit.size() > 2) {
+ moduleName = colonSplit[0];
+ elementName = colonSplit[1];
+ } else {
+ elementName = colonSplit[0];
+ }
+ funcName = colonSplit.last();
+
+ QDocDatabase* database = QDocDatabase::qdocDB();
+
+ auto *aggregate = database->findQmlTypeInPrimaryTree(moduleName, elementName);
+ // Note: Constructing a QmlType node by default, as opposed to QmlValueType.
+ // This may lead to unexpected behavior if documenting \qmlvaluetype's methods
+ // before the type itself.
+ if (!aggregate) {
+ aggregate = new QmlTypeNode(database->primaryTreeRoot(), elementName, Node::QmlType);
+ aggregate->setLocation(location);
+ if (!moduleName.isEmpty())
+ database->addToQmlModule(moduleName, aggregate);
+ }
+
+ QString params;
+ QStringList leftParenSplit = funcArg.split('(');
+ if (leftParenSplit.size() > 1) {
+ QStringList rightParenSplit = leftParenSplit[1].split(')');
+ if (!rightParenSplit.empty())
+ params = rightParenSplit[0];
+ }
+
+ FunctionNode::Metaness metaness = FunctionNode::getMetanessFromTopic(topic);
+ bool attached = topic.contains(QLatin1String("attached"));
+ auto *fn = new FunctionNode(metaness, aggregate, funcName, attached);
+ fn->setAccess(Access::Public);
+ fn->setLocation(location);
+ fn->setReturnType(returnType);
+ fn->setParameters(params);
+ return fn;
+}
+
+/*!
+ Parse the macro arguments in \a macroArg ad hoc, without using
+ any actual parser. If successful, return a pointer to the new
+ FunctionNode for the macro. Otherwise return null. \a location
+ is used for reporting errors.
+ */
+FunctionNode *CppCodeParser::parseMacroArg(const Location &location, const QString &macroArg)
+{
+ QDocDatabase* database = QDocDatabase::qdocDB();
+
+ QStringList leftParenSplit = macroArg.split('(');
+ if (leftParenSplit.isEmpty())
+ return nullptr;
+ QString macroName;
+ FunctionNode *oldMacroNode = nullptr;
+ QStringList blankSplit = leftParenSplit[0].split(' ');
+ if (!blankSplit.empty()) {
+ macroName = blankSplit.last();
+ oldMacroNode = database->findMacroNode(macroName);
+ }
+ QString returnType;
+ if (blankSplit.size() > 1) {
+ blankSplit.removeLast();
+ returnType = blankSplit.join(' ');
+ }
+ QString params;
+ if (leftParenSplit.size() > 1) {
+ const QString &afterParen = leftParenSplit.at(1);
+ qsizetype rightParen = afterParen.indexOf(')');
+ if (rightParen >= 0)
+ params = afterParen.left(rightParen);
+ }
+ int i = 0;
+ while (i < macroName.size() && !macroName.at(i).isLetter())
+ i++;
+ if (i > 0) {
+ returnType += QChar(' ') + macroName.left(i);
+ macroName = macroName.mid(i);
+ }
+ FunctionNode::Metaness metaness = FunctionNode::MacroWithParams;
+ if (params.isEmpty())
+ metaness = FunctionNode::MacroWithoutParams;
+ auto *macro = new FunctionNode(metaness, database->primaryTreeRoot(), macroName);
+ macro->setAccess(Access::Public);
+ macro->setLocation(location);
+ macro->setReturnType(returnType);
+ macro->setParameters(params);
+ if (oldMacroNode && macro->parent() == oldMacroNode->parent()
+ && compare(macro, oldMacroNode) == 0) {
+ location.warning(QStringLiteral("\\macro %1 documented more than once")
+ .arg(macroArg), QStringLiteral("also seen here: %1")
+ .arg(oldMacroNode->doc().location().toString()));
+ }
+ return macro;
+}
+
+void CppCodeParser::setExampleFileLists(ExampleNode *en)
+{
+ Config &config = Config::instance();
+ QString fullPath = config.getExampleProjectFile(en->name());
+ if (fullPath.isEmpty()) {
+ QString details = QLatin1String("Example directories: ")
+ + config.getCanonicalPathList(CONFIG_EXAMPLEDIRS).join(QLatin1Char(' '));
+ en->location().warning(
+ QStringLiteral("Cannot find project file for example '%1'").arg(en->name()),
+ details);
+ return;
+ }
+
+ QDir exampleDir(QFileInfo(fullPath).dir());
+
+ const auto& [excludeDirs, excludeFiles] = config.getExcludedPaths();
+
+ QStringList exampleFiles = Config::getFilesHere(exampleDir.path(), m_exampleNameFilter,
+ Location(), excludeDirs, excludeFiles);
+ // Search for all image files under the example project, excluding doc/images directory.
+ QSet<QString> excludeDocDirs(excludeDirs);
+ excludeDocDirs.insert(exampleDir.path() + QLatin1String("/doc/images"));
+ QStringList imageFiles = Config::getFilesHere(exampleDir.path(), m_exampleImageFilter,
+ Location(), excludeDocDirs, excludeFiles);
+ if (!exampleFiles.isEmpty()) {
+ // move main.cpp to the end, if it exists
+ QString mainCpp;
+
+ const auto isGeneratedOrMainCpp = [&mainCpp](const QString &fileName) {
+ if (fileName.endsWith("/main.cpp")) {
+ if (mainCpp.isEmpty())
+ mainCpp = fileName;
+ return true;
+ }
+ return fileName.contains("/qrc_") || fileName.contains("/moc_")
+ || fileName.contains("/ui_");
+ };
+
+ exampleFiles.erase(
+ std::remove_if(exampleFiles.begin(), exampleFiles.end(), isGeneratedOrMainCpp),
+ exampleFiles.end());
+
+ if (!mainCpp.isEmpty())
+ exampleFiles.append(mainCpp);
+
+ // Add any resource and project files
+ exampleFiles += Config::getFilesHere(exampleDir.path(),
+ QLatin1String("*.qrc *.pro *.qmlproject *.pyproject CMakeLists.txt qmldir"),
+ Location(), excludeDirs, excludeFiles);
+ }
+
+ const qsizetype pathLen = exampleDir.path().size() - en->name().size();
+ for (auto &file : exampleFiles)
+ file = file.mid(pathLen);
+ for (auto &file : imageFiles)
+ file = file.mid(pathLen);
+
+ en->setFiles(exampleFiles, fullPath.mid(pathLen));
+ en->setImages(imageFiles);
+}
+
+/*!
+ returns true if \a t is \e {qmlsignal}, \e {qmlmethod},
+ \e {qmlattachedsignal}, or \e {qmlattachedmethod}.
+ */
+bool CppCodeParser::isQMLMethodTopic(const QString &t)
+{
+ return (t == COMMAND_QMLSIGNAL || t == COMMAND_QMLMETHOD || t == COMMAND_QMLATTACHEDSIGNAL
+ || t == COMMAND_QMLATTACHEDMETHOD);
+}
+
+/*!
+ Returns true if \a t is \e {qmlproperty}, \e {qmlpropertygroup},
+ or \e {qmlattachedproperty}.
+ */
+bool CppCodeParser::isQMLPropertyTopic(const QString &t)
+{
+ return (t == COMMAND_QMLPROPERTY || t == COMMAND_QMLATTACHEDPROPERTY);
+}
+
+std::pair<std::vector<TiedDocumentation>, std::vector<FnMatchError>>
+CppCodeParser::processTopicArgs(const UntiedDocumentation &untied)
+{
+ const Doc &doc = untied.documentation;
+
+ if (doc.topicsUsed().isEmpty())
+ return {};
+
+ QDocDatabase *database = QDocDatabase::qdocDB();
+
+ const QString topic = doc.topicsUsed().first().m_topic;
+
+ std::vector<TiedDocumentation> tied{};
+ std::vector<FnMatchError> errors{};
+
+ if (isQMLPropertyTopic(topic)) {
+ auto tied_qml = processQmlProperties(untied);
+ tied.insert(tied.end(), tied_qml.begin(), tied_qml.end());
+ } else {
+ ArgList args = doc.metaCommandArgs(topic);
+ Node *node = nullptr;
+ if (args.size() == 1) {
+ if (topic == COMMAND_FN) {
+ if (Config::instance().showInternal() || !doc.isInternal()) {
+ auto result = fn_parser(doc.location(), args[0].first, args[0].second, untied.context);
+ if (auto *error = std::get_if<FnMatchError>(&result))
+ errors.emplace_back(*error);
+ else
+ node = std::get<Node*>(result);
+ }
+ } else if (topic == COMMAND_MACRO) {
+ node = parseMacroArg(doc.location(), args[0].first);
+ } else if (isQMLMethodTopic(topic)) {
+ node = parseOtherFuncArg(topic, doc.location(), args[0].first);
+ } else if (topic == COMMAND_DONTDOCUMENT) {
+ database->primaryTree()->addToDontDocumentMap(args[0].first);
+ } else {
+ node = processTopicCommand(doc, topic, args[0]);
+ }
+ if (node != nullptr) {
+ tied.emplace_back(TiedDocumentation{doc, node});
+ }
+ } else if (args.size() > 1) {
+ QList<SharedCommentNode *> sharedCommentNodes;
+ for (const auto &arg : std::as_const(args)) {
+ node = nullptr;
+ if (topic == COMMAND_FN) {
+ if (Config::instance().showInternal() || !doc.isInternal()) {
+ auto result = fn_parser(doc.location(), arg.first, arg.second, untied.context);
+ if (auto *error = std::get_if<FnMatchError>(&result))
+ errors.emplace_back(*error);
+ else
+ node = std::get<Node*>(result);
+ }
+ } else if (topic == COMMAND_MACRO) {
+ node = parseMacroArg(doc.location(), arg.first);
+ } else if (isQMLMethodTopic(topic)) {
+ node = parseOtherFuncArg(topic, doc.location(), arg.first);
+ } else {
+ node = processTopicCommand(doc, topic, arg);
+ }
+ if (node != nullptr) {
+ bool found = false;
+ for (SharedCommentNode *scn : sharedCommentNodes) {
+ if (scn->parent() == node->parent()) {
+ scn->append(node);
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ auto *scn = new SharedCommentNode(node);
+ sharedCommentNodes.append(scn);
+ tied.emplace_back(TiedDocumentation{doc, scn});
+ }
+ }
+ }
+ for (auto *scn : sharedCommentNodes)
+ scn->sort();
+ }
+ }
+ return std::make_pair(tied, errors);
+}
+
+/*!
+ For each node that is part of C++ API and produces a documentation
+ page, this function ensures that the node belongs to a module.
+ */
+static void checkModuleInclusion(Node *n)
+{
+ if (n->physicalModuleName().isEmpty()) {
+ if (n->isInAPI() && !n->name().isEmpty()) {
+ switch (n->nodeType()) {
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ case Node::Namespace:
+ case Node::HeaderFile:
+ break;
+ default:
+ return;
+ }
+ n->setPhysicalModuleName(Generator::defaultModuleName());
+ QDocDatabase::qdocDB()->addToModule(Generator::defaultModuleName(), n);
+ n->doc().location().warning(
+ QStringLiteral("Documentation for %1 '%2' has no \\inmodule command; "
+ "using project name by default: %3")
+ .arg(Node::nodeTypeString(n->nodeType()), n->name(),
+ n->physicalModuleName()));
+ }
+ }
+}
+
+void CppCodeParser::processMetaCommands(const std::vector<TiedDocumentation> &tied)
+{
+ for (auto [doc, node] : tied) {
+ processMetaCommands(doc, node);
+ node->setDoc(doc);
+ checkModuleInclusion(node);
+ if (node->isAggregate()) {
+ auto *aggregate = static_cast<Aggregate *>(node);
+
+ if (!aggregate->includeFile()) {
+ Aggregate *parent = aggregate;
+ while (parent->physicalModuleName().isEmpty() && (parent->parent() != nullptr))
+ parent = parent->parent();
+
+ if (parent == aggregate)
+ // TODO: Understand if the name can be empty.
+ // In theory it should not be possible as
+ // there would be no aggregate to refer to
+ // such that this code is never reached.
+ //
+ // If the name can be empty, this would
+ // endanger users of the include file down the
+ // line, forcing them to ensure that, further
+ // to there being an actual include file, that
+ // include file is not an empty string, such
+ // that we would require a different way to
+ // generate the include file here.
+ aggregate->setIncludeFile(aggregate->name());
+ else if (aggregate->includeFile())
+ aggregate->setIncludeFile(*parent->includeFile());
+ }
+ }
+ }
+}
+
+void CppCodeParser::processQmlNativeTypeCommand(Node *node, const QString &arg, const Location &location)
+{
+ Q_ASSERT(node);
+ if (!node->isQmlNode()) {
+ location.warning(
+ QStringLiteral("Command '\\%1' is only meaningful in '\\%2'")
+ .arg(COMMAND_QMLNATIVETYPE, COMMAND_QMLTYPE));
+ return;
+ }
+
+ auto qmlNode = static_cast<QmlTypeNode *>(node);
+
+ QDocDatabase *database = QDocDatabase::qdocDB();
+ auto classNode = database->findClassNode(arg.split(u"::"_s));
+
+ if (!classNode) {
+ if (m_showLinkErrors) {
+ location.warning(
+ QStringLiteral("C++ class %2 not found: \\%1 %2")
+ .arg(COMMAND_QMLNATIVETYPE, arg));
+ }
+ return;
+ }
+
+ if (qmlNode->classNode()) {
+ location.warning(
+ QStringLiteral("QML type %1 documented with %2 as its native type. Replacing %2 with %3")
+ .arg(qmlNode->name(), qmlNode->classNode()->name(), arg));
+ }
+
+ qmlNode->setClassNode(classNode);
+ classNode->insertQmlNativeType(qmlNode);
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/cppcodeparser.h b/src/qdoc/qdoc/src/qdoc/cppcodeparser.h
new file mode 100644
index 000000000..32e11d05b
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/cppcodeparser.h
@@ -0,0 +1,111 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CPPCODEPARSER_H
+#define CPPCODEPARSER_H
+
+#include "clangcodeparser.h"
+#include "codeparser.h"
+#include "parsererror.h"
+#include "utilities.h"
+
+QT_BEGIN_NAMESPACE
+
+class ClassNode;
+class ExampleNode;
+class FunctionNode;
+class Aggregate;
+
+class CppCodeParser
+{
+public:
+ static inline const QSet<QString> topic_commands{
+ COMMAND_CLASS, COMMAND_DONTDOCUMENT, COMMAND_ENUM, COMMAND_EXAMPLE,
+ COMMAND_EXTERNALPAGE, COMMAND_FN, COMMAND_GROUP, COMMAND_HEADERFILE,
+ COMMAND_MACRO, COMMAND_MODULE, COMMAND_NAMESPACE, COMMAND_PAGE,
+ COMMAND_PROPERTY, COMMAND_TYPEALIAS, COMMAND_TYPEDEF, COMMAND_VARIABLE,
+ COMMAND_QMLTYPE, COMMAND_QMLPROPERTY, COMMAND_QMLPROPERTYGROUP,
+ COMMAND_QMLATTACHEDPROPERTY, COMMAND_QMLSIGNAL, COMMAND_QMLATTACHEDSIGNAL,
+ COMMAND_QMLMETHOD, COMMAND_QMLATTACHEDMETHOD, COMMAND_QMLVALUETYPE, COMMAND_QMLBASICTYPE,
+ COMMAND_QMLMODULE, COMMAND_STRUCT, COMMAND_UNION,
+ };
+
+ static inline const QSet<QString> meta_commands = QSet<QString>(CodeParser::common_meta_commands)
+ << COMMAND_COMPARES << COMMAND_COMPARESWITH << COMMAND_INHEADERFILE
+ << COMMAND_NEXTPAGE << COMMAND_OVERLOAD << COMMAND_PREVIOUSPAGE
+ << COMMAND_QMLINSTANTIATES << COMMAND_QMLNATIVETYPE << COMMAND_REIMP << COMMAND_RELATES;
+
+public:
+ explicit CppCodeParser(FnCommandParser&& parser);
+
+ FunctionNode *parseMacroArg(const Location &location, const QString &macroArg);
+ FunctionNode *parseOtherFuncArg(const QString &topic, const Location &location,
+ const QString &funcArg);
+ static bool isQMLMethodTopic(const QString &t);
+ static bool isQMLPropertyTopic(const QString &t);
+
+ std::pair<std::vector<TiedDocumentation>, std::vector<FnMatchError>>
+ processTopicArgs(const UntiedDocumentation &untied);
+
+ void processMetaCommand(const Doc &doc, const QString &command, const ArgPair &argLocPair,
+ Node *node);
+ void processMetaCommands(const Doc &doc, Node *node);
+ void processMetaCommands(const std::vector<TiedDocumentation> &tied);
+
+protected:
+ virtual Node *processTopicCommand(const Doc &doc, const QString &command,
+ const ArgPair &arg);
+ std::vector<TiedDocumentation> processQmlProperties(const UntiedDocumentation& untied);
+ bool splitQmlPropertyArg(const QString &arg, QString &type, QString &module, QString &element,
+ QString &name, const Location &location);
+
+private:
+ void setExampleFileLists(ExampleNode *en);
+ static void processComparesCommand(Node *node, const QString &arg, const Location &loc);
+ void processQmlNativeTypeCommand(Node *node, const QString &arg, const Location &loc);
+
+private:
+ FnCommandParser fn_parser;
+ QString m_exampleNameFilter;
+ QString m_exampleImageFilter;
+ bool m_showLinkErrors { false };
+};
+
+/*!
+ * \internal
+ * \brief Checks if there are too many topic commands in \a doc.
+ *
+ * This method compares the commands used in \a doc with the set of topic
+ * commands. If zero or one topic command is found, or if all found topic
+ * commands are {\\qml*}-commands, the method returns \c false.
+ *
+ * If more than one topic command is found, QDoc issues a warning and the list
+ * of topic commands used in \a doc, and the method returns \c true.
+ */
+[[nodiscard]] inline bool hasTooManyTopics(const Doc &doc)
+{
+ const QSet<QString> topicCommandsUsed = CppCodeParser::topic_commands & doc.metaCommandsUsed();
+
+ if (topicCommandsUsed.empty() || topicCommandsUsed.size() == 1)
+ return false;
+ if (std::all_of(topicCommandsUsed.cbegin(), topicCommandsUsed.cend(),
+ [](const auto &cmd) { return cmd.startsWith(QLatin1String("qml")); }))
+ return false;
+
+ const QStringList commands = topicCommandsUsed.values();
+ const QString topicCommands{ std::accumulate(
+ commands.cbegin(), commands.cend(), QString{},
+ [index = qsizetype{ 0 }, numberOfCommands = commands.size()](
+ const QString &accumulator, const QString &topic) mutable -> QString {
+ return accumulator + QLatin1String("\\") + topic
+ + Utilities::separator(index++, numberOfCommands);
+ }) };
+
+ doc.location().warning(
+ QStringLiteral("Multiple topic commands found in comment: %1").arg(topicCommands));
+ return true;
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/doc.cpp b/src/qdoc/qdoc/src/qdoc/doc.cpp
new file mode 100644
index 000000000..c994dc807
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/doc.cpp
@@ -0,0 +1,420 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "doc.h"
+
+#include "atom.h"
+#include "config.h"
+#include "codemarker.h"
+#include "docparser.h"
+#include "docprivate.h"
+#include "generator.h"
+#include "qmltypenode.h"
+#include "quoter.h"
+#include "text.h"
+#include "utilities.h"
+
+#include <qcryptographichash.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+DocUtilities &Doc::m_utilities = DocUtilities::instance();
+
+/*!
+ \typedef ArgList
+ \relates Doc
+
+ A list of metacommand arguments that appear in a Doc. Each entry
+ in the list is a <QString, QString> pair (ArgPair):
+
+ \list
+ \li \c {ArgPair.first} - arguments passed to the command.
+ \li \c {ArgPair.second} - optional argument string passed
+ within brackets immediately following the command.
+ \endlist
+*/
+
+/*!
+ Parse the qdoc comment \a source. Build up a list of all the topic
+ commands found including their arguments. This constructor is used
+ when there can be more than one topic command in theqdoc comment.
+ Normally, there is only one topic command in a qdoc comment, but in
+ QML documentation, there is the case where the qdoc \e{qmlproperty}
+ command can appear multiple times in a qdoc comment.
+ */
+Doc::Doc(const Location &start_loc, const Location &end_loc, const QString &source,
+ const QSet<QString> &metaCommandSet, const QSet<QString> &topics)
+{
+ m_priv = new DocPrivate(start_loc, end_loc, source);
+ DocParser parser;
+ parser.parse(source, m_priv, metaCommandSet, topics);
+
+ if (Config::instance().getAtomsDump()) {
+ start_loc.information(u"==== Atoms Structure for block comment starting at %1 ===="_s.arg(
+ start_loc.toString()));
+ body().dump();
+ end_loc.information(
+ u"==== Ending atoms Structure for block comment ending at %1 ===="_s.arg(
+ end_loc.toString()));
+ }
+}
+
+Doc::Doc(const Doc &doc) : m_priv(nullptr)
+{
+ operator=(doc);
+}
+
+Doc::~Doc()
+{
+ if (m_priv && m_priv->deref())
+ delete m_priv;
+}
+
+Doc &Doc::operator=(const Doc &doc)
+{
+ if (&doc == this)
+ return *this;
+ if (doc.m_priv)
+ doc.m_priv->ref();
+ if (m_priv && m_priv->deref())
+ delete m_priv;
+ m_priv = doc.m_priv;
+ return *this;
+}
+
+/*!
+ Returns the starting location of a qdoc comment.
+ */
+const Location &Doc::location() const
+{
+ static const Location dummy;
+ return m_priv == nullptr ? dummy : m_priv->m_start_loc;
+}
+
+/*!
+ Returns the starting location of a qdoc comment.
+ */
+const Location &Doc::startLocation() const
+{
+ return location();
+}
+
+const QString &Doc::source() const
+{
+ static QString null;
+ return m_priv == nullptr ? null : m_priv->m_src;
+}
+
+bool Doc::isEmpty() const
+{
+ return m_priv == nullptr || m_priv->m_src.isEmpty();
+}
+
+const Text &Doc::body() const
+{
+ static const Text dummy;
+ return m_priv == nullptr ? dummy : m_priv->m_text;
+}
+
+Text Doc::briefText(bool inclusive) const
+{
+ return body().subText(Atom::BriefLeft, Atom::BriefRight, nullptr, inclusive);
+}
+
+Text Doc::trimmedBriefText(const QString &className) const
+{
+ QString classNameOnly = className;
+ if (className.contains("::"))
+ classNameOnly = className.split("::").last();
+
+ Text originalText = briefText();
+ Text resultText;
+ const Atom *atom = originalText.firstAtom();
+ if (atom) {
+ QString briefStr;
+ QString whats;
+ /*
+ This code is really ugly. The entire \brief business
+ should be rethought.
+ */
+ while (atom) {
+ if (atom->type() == Atom::AutoLink || atom->type() == Atom::String) {
+ briefStr += atom->string();
+ } else if (atom->type() == Atom::C) {
+ briefStr += Generator::plainCode(atom->string());
+ }
+ atom = atom->next();
+ }
+
+ QStringList w = briefStr.split(QLatin1Char(' '));
+ if (!w.isEmpty() && w.first() == "Returns") {
+ } else {
+ if (!w.isEmpty() && w.first() == "The")
+ w.removeFirst();
+
+ if (!w.isEmpty() && (w.first() == className || w.first() == classNameOnly))
+ w.removeFirst();
+
+ if (!w.isEmpty()
+ && ((w.first() == "class") || (w.first() == "function") || (w.first() == "macro")
+ || (w.first() == "widget") || (w.first() == "namespace")
+ || (w.first() == "header")))
+ w.removeFirst();
+
+ if (!w.isEmpty() && (w.first() == "is" || w.first() == "provides"))
+ w.removeFirst();
+
+ if (!w.isEmpty() && (w.first() == "a" || w.first() == "an"))
+ w.removeFirst();
+ }
+
+ whats = w.join(' ');
+
+ if (whats.endsWith(QLatin1Char('.')))
+ whats.truncate(whats.size() - 1);
+
+ if (!whats.isEmpty())
+ whats[0] = whats[0].toUpper();
+
+ // ### move this once \brief is abolished for properties
+ resultText << whats;
+ }
+ return resultText;
+}
+
+Text Doc::legaleseText() const
+{
+ if (m_priv == nullptr || !m_priv->m_hasLegalese)
+ return Text();
+ else
+ return body().subText(Atom::LegaleseLeft, Atom::LegaleseRight);
+}
+
+QSet<QString> Doc::parameterNames() const
+{
+ return m_priv == nullptr ? QSet<QString>() : m_priv->m_params;
+}
+
+QStringList Doc::enumItemNames() const
+{
+ return m_priv == nullptr ? QStringList() : m_priv->m_enumItemList;
+}
+
+QStringList Doc::omitEnumItemNames() const
+{
+ return m_priv == nullptr ? QStringList() : m_priv->m_omitEnumItemList;
+}
+
+QSet<QString> Doc::metaCommandsUsed() const
+{
+ return m_priv == nullptr ? QSet<QString>() : m_priv->m_metacommandsUsed;
+}
+
+/*!
+ Returns true if the set of metacommands used in the doc
+ comment contains \e {internal}.
+ */
+bool Doc::isInternal() const
+{
+ return metaCommandsUsed().contains(QLatin1String("internal"));
+}
+
+/*!
+ Returns true if the set of metacommands used in the doc
+ comment contains \e {reimp}.
+ */
+bool Doc::isMarkedReimp() const
+{
+ return metaCommandsUsed().contains(QLatin1String("reimp"));
+}
+
+/*!
+ Returns a reference to the list of topic commands used in the
+ current qdoc comment. Normally there is only one, but there
+ can be multiple \e{qmlproperty} commands, for example.
+ */
+TopicList Doc::topicsUsed() const
+{
+ return m_priv == nullptr ? TopicList() : m_priv->m_topics;
+}
+
+ArgList Doc::metaCommandArgs(const QString &metacommand) const
+{
+ return m_priv == nullptr ? ArgList() : m_priv->m_metaCommandMap.value(metacommand);
+}
+
+QList<Text> Doc::alsoList() const
+{
+ return m_priv == nullptr ? QList<Text>() : m_priv->m_alsoList;
+}
+
+bool Doc::hasTableOfContents() const
+{
+ return m_priv && m_priv->extra && !m_priv->extra->m_tableOfContents.isEmpty();
+}
+
+bool Doc::hasKeywords() const
+{
+ return m_priv && m_priv->extra && !m_priv->extra->m_keywords.isEmpty();
+}
+
+bool Doc::hasTargets() const
+{
+ return m_priv && m_priv->extra && !m_priv->extra->m_targets.isEmpty();
+}
+
+const QList<Atom *> &Doc::tableOfContents() const
+{
+ m_priv->constructExtra();
+ return m_priv->extra->m_tableOfContents;
+}
+
+const QList<int> &Doc::tableOfContentsLevels() const
+{
+ m_priv->constructExtra();
+ return m_priv->extra->m_tableOfContentsLevels;
+}
+
+const QList<Atom *> &Doc::keywords() const
+{
+ m_priv->constructExtra();
+ return m_priv->extra->m_keywords;
+}
+
+const QList<Atom *> &Doc::targets() const
+{
+ m_priv->constructExtra();
+ return m_priv->extra->m_targets;
+}
+
+QStringMultiMap *Doc::metaTagMap() const
+{
+ return m_priv && m_priv->extra ? &m_priv->extra->m_metaMap : nullptr;
+}
+
+QMultiMap<ComparisonCategory, Text> *Doc::comparesWithMap() const
+{
+ return m_priv && m_priv->extra ? &m_priv->extra->m_comparesWithMap : nullptr;
+}
+
+void Doc::initialize(FileResolver& file_resolver)
+{
+ Config &config = Config::instance();
+ DocParser::initialize(config, file_resolver);
+
+ const auto &configMacros = config.subVars(CONFIG_MACRO);
+ for (const auto &macroName : configMacros) {
+ QString macroDotName = CONFIG_MACRO + Config::dot + macroName;
+ Macro macro;
+ macro.numParams = -1;
+ const auto &macroConfigVar = config.get(macroDotName);
+ macro.m_defaultDef = macroConfigVar.asString();
+ if (!macro.m_defaultDef.isEmpty()) {
+ macro.m_defaultDefLocation = macroConfigVar.location();
+ macro.numParams = Config::numParams(macro.m_defaultDef);
+ }
+ bool silent = false;
+
+ const auto &macroDotNames = config.subVars(macroDotName);
+ for (const auto &f : macroDotNames) {
+ const auto &macroSubVar = config.get(macroDotName + Config::dot + f);
+ QString def{macroSubVar.asString()};
+ if (!def.isEmpty()) {
+ macro.m_otherDefs.insert(f, def);
+ int m = Config::numParams(def);
+ if (macro.numParams == -1)
+ macro.numParams = m;
+ // .match definition is a regular expression that contains no params
+ else if (macro.numParams != m && f != QLatin1String("match")) {
+ if (!silent) {
+ QString other = QStringLiteral("default");
+ if (macro.m_defaultDef.isEmpty())
+ other = macro.m_otherDefs.constBegin().key();
+ macroSubVar.location().warning(
+ QStringLiteral("Macro '\\%1' takes inconsistent number of "
+ "arguments (%2 %3, %4 %5)")
+ .arg(macroName, f, QString::number(m), other,
+ QString::number(macro.numParams)));
+ silent = true;
+ }
+ if (macro.numParams < m)
+ macro.numParams = m;
+ }
+ }
+ }
+ if (macro.numParams != -1)
+ m_utilities.macroHash.insert(macroName, macro);
+ }
+}
+
+/*!
+ All the heap allocated variables are deleted.
+ */
+void Doc::terminate()
+{
+ m_utilities.cmdHash.clear();
+ m_utilities.macroHash.clear();
+}
+
+/*!
+ Trims the deadwood out of \a str. i.e., this function
+ cleans up \a str.
+ */
+void Doc::trimCStyleComment(Location &location, QString &str)
+{
+ QString cleaned;
+ Location m = location;
+ bool metAsterColumn = true;
+ int asterColumn = location.columnNo() + 1;
+ int i;
+
+ for (i = 0; i < str.size(); ++i) {
+ if (m.columnNo() == asterColumn) {
+ if (str[i] != '*')
+ break;
+ cleaned += ' ';
+ metAsterColumn = true;
+ } else {
+ if (str[i] == '\n') {
+ if (!metAsterColumn)
+ break;
+ metAsterColumn = false;
+ }
+ cleaned += str[i];
+ }
+ m.advance(str[i]);
+ }
+ if (cleaned.size() == str.size())
+ str = cleaned;
+
+ for (int i = 0; i < 3; ++i)
+ location.advance(str[i]);
+ str = str.mid(3, str.size() - 5);
+}
+
+void Doc::quoteFromFile(const Location &location, Quoter &quoter, ResolvedFile resolved_file)
+{
+ // TODO: quoteFromFile should not care about modifying a stateful
+ // quoter from the outside, instead, it should produce a quoter
+ // that allows the caller to retrieve the required information
+ // about the quoted file.
+ //
+ // When changing the way in which quoting works, this kind of
+ // spread resposability should be removed, together with quoteFromFile.
+ quoter.reset();
+
+ QString code;
+ {
+ QFile input_file{resolved_file.get_path()};
+ if (!input_file.open(QFile::ReadOnly))
+ return;
+ code = DocParser::untabifyEtc(QTextStream{&input_file}.readAll());
+ }
+
+ CodeMarker *marker = CodeMarker::markerForFileName(resolved_file.get_path());
+ quoter.quoteFromFile(resolved_file.get_path(), code, marker->markedUpCode(code, nullptr, location));
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/doc.h b/src/qdoc/qdoc/src/qdoc/doc.h
new file mode 100644
index 000000000..50e88c72f
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/doc.h
@@ -0,0 +1,92 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef DOC_H
+#define DOC_H
+
+#include "location.h"
+#include "comparisoncategory.h"
+#include "docutilities.h"
+#include "topic.h"
+
+#include "filesystem/fileresolver.h"
+#include "boundaries/filesystem/resolvedfile.h"
+
+#include <QtCore/qmap.h>
+#include <QtCore/qset.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class Atom;
+class DocPrivate;
+class Quoter;
+class Text;
+
+typedef std::pair<QString, QString> ArgPair;
+typedef QList<ArgPair> ArgList;
+typedef QMultiMap<QString, QString> QStringMultiMap;
+
+class Doc
+{
+public:
+ // the order is important
+ enum Sections {
+ NoSection = -1,
+ Section1 = 1,
+ Section2 = 2,
+ Section3 = 3,
+ Section4 = 4
+ };
+
+ Doc() = default;
+ Doc(const Location &start_loc, const Location &end_loc, const QString &source,
+ const QSet<QString> &metaCommandSet, const QSet<QString> &topics);
+ Doc(const Doc &doc);
+ ~Doc();
+
+ Doc &operator=(const Doc &doc);
+
+ [[nodiscard]] const Location &location() const;
+ [[nodiscard]] const Location &startLocation() const;
+ [[nodiscard]] bool isEmpty() const;
+ [[nodiscard]] const QString &source() const;
+ [[nodiscard]] const Text &body() const;
+ [[nodiscard]] Text briefText(bool inclusive = false) const;
+ [[nodiscard]] Text trimmedBriefText(const QString &className) const;
+ [[nodiscard]] Text legaleseText() const;
+ [[nodiscard]] QSet<QString> parameterNames() const;
+ [[nodiscard]] QStringList enumItemNames() const;
+ [[nodiscard]] QStringList omitEnumItemNames() const;
+ [[nodiscard]] QSet<QString> metaCommandsUsed() const;
+ [[nodiscard]] TopicList topicsUsed() const;
+ [[nodiscard]] ArgList metaCommandArgs(const QString &metaCommand) const;
+ [[nodiscard]] QList<Text> alsoList() const;
+ [[nodiscard]] bool hasTableOfContents() const;
+ [[nodiscard]] bool hasKeywords() const;
+ [[nodiscard]] bool hasTargets() const;
+ [[nodiscard]] bool isInternal() const;
+ [[nodiscard]] bool isMarkedReimp() const;
+ [[nodiscard]] const QList<Atom *> &tableOfContents() const;
+ [[nodiscard]] const QList<int> &tableOfContentsLevels() const;
+ [[nodiscard]] const QList<Atom *> &keywords() const;
+ [[nodiscard]] const QList<Atom *> &targets() const;
+ [[nodiscard]] QStringMultiMap *metaTagMap() const;
+ [[nodiscard]] QMultiMap<ComparisonCategory, Text> *comparesWithMap() const;
+
+ static void initialize(FileResolver& file_resolver);
+ static void terminate();
+ static void trimCStyleComment(Location &location, QString &str);
+ static void quoteFromFile(const Location &location, Quoter &quoter,
+ ResolvedFile resolved_file);
+
+private:
+ DocPrivate *m_priv { nullptr };
+ static DocUtilities &m_utilities;
+};
+Q_DECLARE_TYPEINFO(Doc, Q_RELOCATABLE_TYPE);
+typedef QList<Doc> DocList;
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/docbookgenerator.cpp b/src/qdoc/qdoc/src/qdoc/docbookgenerator.cpp
new file mode 100644
index 000000000..aefb70766
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/docbookgenerator.cpp
@@ -0,0 +1,4765 @@
+// Copyright (C) 2019 Thibaut Cuvelier
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "docbookgenerator.h"
+
+#include "access.h"
+#include "aggregate.h"
+#include "classnode.h"
+#include "codemarker.h"
+#include "collectionnode.h"
+#include "comparisoncategory.h"
+#include "config.h"
+#include "enumnode.h"
+#include "examplenode.h"
+#include "functionnode.h"
+#include "generator.h"
+#include "node.h"
+#include "propertynode.h"
+#include "quoter.h"
+#include "qdocdatabase.h"
+#include "qmlpropertynode.h"
+#include "sharedcommentnode.h"
+#include "typedefnode.h"
+#include "variablenode.h"
+
+#include <QtCore/qlist.h>
+#include <QtCore/qmap.h>
+#include <QtCore/quuid.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qversionnumber.h>
+
+#include <cctype>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+static const char dbNamespace[] = "http://docbook.org/ns/docbook";
+static const char xlinkNamespace[] = "http://www.w3.org/1999/xlink";
+static const char itsNamespace[] = "http://www.w3.org/2005/11/its";
+
+DocBookGenerator::DocBookGenerator(FileResolver& file_resolver) : XmlGenerator(file_resolver) {}
+
+inline void DocBookGenerator::newLine()
+{
+ m_writer->writeCharacters("\n");
+}
+
+void DocBookGenerator::writeXmlId(const QString &id)
+{
+ if (id.isEmpty())
+ return;
+
+ m_writer->writeAttribute("xml:id", registerRef(id, true));
+}
+
+void DocBookGenerator::writeXmlId(const Node *node)
+{
+ if (!node)
+ return;
+
+ // Specifically for nodes, do not use the same code path as for QString
+ // inputs, as refForNode calls registerRef in all cases. Calling
+ // registerRef a second time adds a character to "disambiguate" the two IDs
+ // (the one returned by refForNode, then the one that is written as
+ // xml:id).
+ QString id = Generator::cleanRef(refForNode(node), true);
+ if (!id.isEmpty())
+ m_writer->writeAttribute("xml:id", id);
+}
+
+void DocBookGenerator::startSectionBegin(const QString &id)
+{
+ m_hasSection = true;
+
+ m_writer->writeStartElement(dbNamespace, "section");
+ writeXmlId(id);
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "title");
+}
+
+void DocBookGenerator::startSectionBegin(const Node *node)
+{
+ m_writer->writeStartElement(dbNamespace, "section");
+ writeXmlId(node);
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "title");
+}
+
+void DocBookGenerator::startSectionEnd()
+{
+ m_writer->writeEndElement(); // title
+ newLine();
+}
+
+void DocBookGenerator::startSection(const QString &id, const QString &title)
+{
+ startSectionBegin(id);
+ m_writer->writeCharacters(title);
+ startSectionEnd();
+}
+
+void DocBookGenerator::startSection(const Node *node, const QString &title)
+{
+ startSectionBegin(node);
+ m_writer->writeCharacters(title);
+ startSectionEnd();
+}
+
+void DocBookGenerator::startSection(const QString &title)
+{
+ // No xml:id given: down the calls, "" is interpreted as "no ID".
+ startSection("", title);
+}
+
+void DocBookGenerator::endSection()
+{
+ m_writer->writeEndElement(); // section
+ newLine();
+}
+
+void DocBookGenerator::writeAnchor(const QString &id)
+{
+ if (id.isEmpty())
+ return;
+
+ m_writer->writeEmptyElement(dbNamespace, "anchor");
+ writeXmlId(id);
+ newLine();
+}
+
+/*!
+ Initializes the DocBook output generator's data structures
+ from the configuration (Config).
+ */
+void DocBookGenerator::initializeGenerator()
+{
+ // Excerpts from HtmlGenerator::initializeGenerator.
+ Generator::initializeGenerator();
+ m_config = &Config::instance();
+
+ m_project = m_config->get(CONFIG_PROJECT).asString();
+
+ m_projectDescription = m_config->get(CONFIG_DESCRIPTION).asString();
+ if (m_projectDescription.isEmpty() && !m_project.isEmpty())
+ m_projectDescription = m_project + QLatin1String(" Reference Documentation");
+
+ m_naturalLanguage = m_config->get(CONFIG_NATURALLANGUAGE).asString();
+ if (m_naturalLanguage.isEmpty())
+ m_naturalLanguage = QLatin1String("en");
+
+ m_buildVersion = m_config->get(CONFIG_BUILDVERSION).asString();
+ m_useDocBook52 = m_config->get(CONFIG_DOCBOOKEXTENSIONS).asBool() ||
+ m_config->get(format() + Config::dot + "usedocbookextensions").asBool();
+ m_useITS = m_config->get(format() + Config::dot + "its").asBool();
+}
+
+QString DocBookGenerator::format()
+{
+ return "DocBook";
+}
+
+/*!
+ Returns "xml" for this subclass of Generator.
+ */
+QString DocBookGenerator::fileExtension() const
+{
+ return "xml";
+}
+
+/*!
+ Generate the documentation for \a relative. i.e. \a relative
+ is the node that represents the entity where a qdoc comment
+ was found, and \a text represents the qdoc comment.
+ */
+bool DocBookGenerator::generateText(const Text &text, const Node *relative)
+{
+ // From Generator::generateText.
+ if (!text.firstAtom())
+ return false;
+
+ int numAtoms = 0;
+ initializeTextOutput();
+ generateAtomList(text.firstAtom(), relative, nullptr, true, numAtoms);
+ closeTextSections();
+ return true;
+}
+
+QString removeCodeMarkers(const QString& code) {
+ QString rewritten = code;
+ static const QRegularExpression re("(<@[^>&]*>)|(<\\/@[^&>]*>)");
+ rewritten.replace(re, "");
+ return rewritten;
+}
+
+/*!
+ Generate DocBook from an instance of Atom.
+ */
+qsizetype DocBookGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMarker*)
+{
+ Q_ASSERT(m_writer);
+ // From HtmlGenerator::generateAtom, without warning generation.
+ int idx = 0;
+ int skipAhead = 0;
+ Node::Genus genus = Node::DontCare;
+
+ switch (atom->type()) {
+ case Atom::AutoLink:
+ // Allow auto-linking to nodes in API reference
+ genus = Node::API;
+ Q_FALLTHROUGH();
+ case Atom::NavAutoLink:
+ if (!m_inLink && !m_inContents && !m_inSectionHeading) {
+ const Node *node = nullptr;
+ QString link = getAutoLink(atom, relative, &node, genus);
+ if (!link.isEmpty() && node && node->isDeprecated()
+ && relative->parent() != node && !relative->isDeprecated()) {
+ link.clear();
+ }
+ if (link.isEmpty()) {
+ m_writer->writeCharacters(atom->string());
+ } else {
+ beginLink(link, node, relative);
+ generateLink(atom);
+ endLink();
+ }
+ } else {
+ m_writer->writeCharacters(atom->string());
+ }
+ break;
+ case Atom::BaseName:
+ break;
+ case Atom::BriefLeft:
+ if (!hasBrief(relative)) {
+ skipAhead = skipAtoms(atom, Atom::BriefRight);
+ break;
+ }
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_inPara = true;
+ rewritePropertyBrief(atom, relative);
+ break;
+ case Atom::BriefRight:
+ if (hasBrief(relative)) {
+ m_writer->writeEndElement(); // para
+ m_inPara = false;
+ newLine();
+ }
+ break;
+ case Atom::C:
+ // This may at one time have been used to mark up C++ code but it is
+ // now widely used to write teletype text. As a result, text marked
+ // with the \c command is not passed to a code marker.
+ if (m_inTeletype)
+ m_writer->writeCharacters(plainCode(atom->string()));
+ else
+ m_writer->writeTextElement(dbNamespace, "code", plainCode(atom->string()));
+ break;
+ case Atom::CaptionLeft:
+ m_writer->writeStartElement(dbNamespace, "title");
+ break;
+ case Atom::CaptionRight:
+ endLink();
+ m_writer->writeEndElement(); // title
+ newLine();
+ break;
+ case Atom::Qml:
+ m_writer->writeStartElement(dbNamespace, "programlisting");
+ m_writer->writeAttribute("language", "qml");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ m_writer->writeCharacters(removeCodeMarkers(atom->string()));
+ m_writer->writeEndElement(); // programlisting
+ newLine();
+ break;
+ case Atom::Code:
+ m_writer->writeStartElement(dbNamespace, "programlisting");
+ m_writer->writeAttribute("language", "cpp");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ m_writer->writeCharacters(removeCodeMarkers(atom->string()));
+ m_writer->writeEndElement(); // programlisting
+ newLine();
+ break;
+ case Atom::CodeBad:
+ m_writer->writeStartElement(dbNamespace, "programlisting");
+ m_writer->writeAttribute("language", "cpp");
+ m_writer->writeAttribute("role", "bad");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ m_writer->writeCharacters(removeCodeMarkers(atom->string()));
+ m_writer->writeEndElement(); // programlisting
+ newLine();
+ break;
+ case Atom::DetailsLeft:
+ case Atom::DetailsRight:
+ break;
+ case Atom::DivLeft:
+ case Atom::DivRight:
+ break;
+ case Atom::FootnoteLeft:
+ m_writer->writeStartElement(dbNamespace, "footnote");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_inPara = true;
+ break;
+ case Atom::FootnoteRight:
+ m_writer->writeEndElement(); // para
+ m_inPara = false;
+ newLine();
+ m_writer->writeEndElement(); // footnote
+ break;
+ case Atom::FormatElse:
+ case Atom::FormatEndif:
+ case Atom::FormatIf:
+ break;
+ case Atom::FormattingLeft:
+ if (atom->string() == ATOM_FORMATTING_BOLD) {
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ } else if (atom->string() == ATOM_FORMATTING_ITALIC) {
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ } else if (atom->string() == ATOM_FORMATTING_UNDERLINE) {
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "underline");
+ } else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT) {
+ m_writer->writeStartElement(dbNamespace, "subscript");
+ } else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT) {
+ m_writer->writeStartElement(dbNamespace, "superscript");
+ } else if (atom->string() == ATOM_FORMATTING_TELETYPE
+ || atom->string() == ATOM_FORMATTING_PARAMETER) {
+ m_writer->writeStartElement(dbNamespace, "code");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+
+ if (atom->string() == ATOM_FORMATTING_PARAMETER)
+ m_writer->writeAttribute("role", "parameter");
+ else // atom->string() == ATOM_FORMATTING_TELETYPE
+ m_inTeletype = true;
+ } else if (atom->string() == ATOM_FORMATTING_UICONTROL) {
+ m_writer->writeStartElement(dbNamespace, "guilabel");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ } else if (atom->string() == ATOM_FORMATTING_TRADEMARK) {
+ m_writer->writeStartElement(dbNamespace,
+ appendTrademark(atom->find(Atom::FormattingRight)) ?
+ "trademark" : "phrase");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ } else {
+ relative->location().warning(QStringLiteral("Unsupported formatting: %1").arg(atom->string()));
+ }
+ break;
+ case Atom::FormattingRight:
+ if (atom->string() == ATOM_FORMATTING_BOLD || atom->string() == ATOM_FORMATTING_ITALIC
+ || atom->string() == ATOM_FORMATTING_UNDERLINE
+ || atom->string() == ATOM_FORMATTING_SUBSCRIPT
+ || atom->string() == ATOM_FORMATTING_SUPERSCRIPT
+ || atom->string() == ATOM_FORMATTING_TELETYPE
+ || atom->string() == ATOM_FORMATTING_PARAMETER
+ || atom->string() == ATOM_FORMATTING_UICONTROL
+ || atom->string() == ATOM_FORMATTING_TRADEMARK) {
+ m_writer->writeEndElement();
+ } else if (atom->string() == ATOM_FORMATTING_LINK) {
+ if (atom->string() == ATOM_FORMATTING_TELETYPE)
+ m_inTeletype = false;
+ endLink();
+ } else {
+ relative->location().warning(QStringLiteral("Unsupported formatting: %1").arg(atom->string()));
+ }
+ break;
+ case Atom::AnnotatedList:
+ if (const CollectionNode *cn = m_qdb->getCollectionNode(atom->string(), Node::Group))
+ generateList(cn, atom->string());
+ break;
+ case Atom::GeneratedList: {
+ bool hasGeneratedSomething = false;
+ if (atom->string() == QLatin1String("annotatedclasses")
+ || atom->string() == QLatin1String("attributions")
+ || atom->string() == QLatin1String("namespaces")) {
+ const NodeMultiMap things = atom->string() == QLatin1String("annotatedclasses")
+ ? m_qdb->getCppClasses()
+ : atom->string() == QLatin1String("attributions") ? m_qdb->getAttributions()
+ : m_qdb->getNamespaces();
+ generateAnnotatedList(relative, things.values(), atom->string());
+ hasGeneratedSomething = !things.isEmpty();
+ } else if (atom->string() == QLatin1String("annotatedexamples")
+ || atom->string() == QLatin1String("annotatedattributions")) {
+ const NodeMultiMap things = atom->string() == QLatin1String("annotatedexamples")
+ ? m_qdb->getAttributions()
+ : m_qdb->getExamples();
+ generateAnnotatedLists(relative, things, atom->string());
+ hasGeneratedSomething = !things.isEmpty();
+ } else if (atom->string() == QLatin1String("classes")
+ || atom->string() == QLatin1String("qmlbasictypes") // deprecated!
+ || atom->string() == QLatin1String("qmlvaluetypes")
+ || atom->string() == QLatin1String("qmltypes")) {
+ const NodeMultiMap things = atom->string() == QLatin1String("classes")
+ ? m_qdb->getCppClasses()
+ : (atom->string() == QLatin1String("qmlvaluetypes")
+ || atom->string() == QLatin1String("qmlbasictypes"))
+ ? m_qdb->getQmlValueTypes()
+ : m_qdb->getQmlTypes();
+ generateCompactList(relative, things, true, QString(), atom->string());
+ hasGeneratedSomething = !things.isEmpty();
+ } else if (atom->string().contains("classes ")) {
+ QString rootName = atom->string().mid(atom->string().indexOf("classes") + 7).trimmed();
+ NodeMultiMap things = m_qdb->getCppClasses();
+
+ hasGeneratedSomething = !things.isEmpty();
+ generateCompactList(relative, things, true, rootName, atom->string());
+ } else if ((idx = atom->string().indexOf(QStringLiteral("bymodule"))) != -1) {
+ QString moduleName = atom->string().mid(idx + 8).trimmed();
+ Node::NodeType moduleType = typeFromString(atom);
+ QDocDatabase *qdb = QDocDatabase::qdocDB();
+ if (const CollectionNode *cn = qdb->getCollectionNode(moduleName, moduleType)) {
+ NodeMap map;
+ switch (moduleType) {
+ case Node::Module:
+ // classesbymodule <module_name>
+ map = cn->getMembers([](const Node *n){ return n->isClassNode(); });
+ break;
+ case Node::QmlModule:
+ if (atom->string().contains(QLatin1String("qmlvaluetypes")))
+ map = cn->getMembers(Node::QmlValueType); // qmlvaluetypesbymodule <module_name>
+ else
+ map = cn->getMembers(Node::QmlType); // qmltypesbymodule <module_name>
+ break;
+ default: // fall back to generating all members
+ generateAnnotatedList(relative, cn->members(), atom->string());
+ hasGeneratedSomething = !cn->members().isEmpty();
+ break;
+ }
+ if (!map.isEmpty()) {
+ generateAnnotatedList(relative, map.values(), atom->string());
+ hasGeneratedSomething = true;
+ }
+ }
+ } else if (atom->string() == QLatin1String("classhierarchy")) {
+ generateClassHierarchy(relative, m_qdb->getCppClasses());
+ hasGeneratedSomething = !m_qdb->getCppClasses().isEmpty();
+ } else if (atom->string().startsWith("obsolete")) {
+ QString prefix = atom->string().contains("cpp") ? QStringLiteral("Q") : QString();
+ const NodeMultiMap &things = atom->string() == QLatin1String("obsoleteclasses")
+ ? m_qdb->getObsoleteClasses()
+ : atom->string() == QLatin1String("obsoleteqmltypes")
+ ? m_qdb->getObsoleteQmlTypes()
+ : atom->string() == QLatin1String("obsoletecppmembers")
+ ? m_qdb->getClassesWithObsoleteMembers()
+ : m_qdb->getQmlTypesWithObsoleteMembers();
+ generateCompactList(relative, things, false, prefix, atom->string());
+ hasGeneratedSomething = !things.isEmpty();
+ } else if (atom->string() == QLatin1String("functionindex")) {
+ generateFunctionIndex(relative);
+ hasGeneratedSomething = !m_qdb->getFunctionIndex().isEmpty();
+ } else if (atom->string() == QLatin1String("legalese")) {
+ generateLegaleseList(relative);
+ hasGeneratedSomething = !m_qdb->getLegaleseTexts().isEmpty();
+ } else if (atom->string() == QLatin1String("overviews")
+ || atom->string() == QLatin1String("cpp-modules")
+ || atom->string() == QLatin1String("qml-modules")
+ || atom->string() == QLatin1String("related")) {
+ generateList(relative, atom->string());
+ hasGeneratedSomething = true; // Approximation, because there is
+ // some nontrivial logic in generateList.
+ } else if (const auto *cn = m_qdb->getCollectionNode(atom->string(), Node::Group); cn) {
+ generateAnnotatedList(cn, cn->members(), atom->string(), ItemizedList);
+ hasGeneratedSomething = true; // Approximation
+ }
+
+ // There must still be some content generated for the DocBook document
+ // to be valid (except if already in a paragraph).
+ if (!hasGeneratedSomething && !m_inPara) {
+ m_writer->writeEmptyElement(dbNamespace, "para");
+ newLine();
+ }
+ }
+ break;
+ case Atom::SinceList:
+ // Table of contents, should automatically be generated by the DocBook processor.
+ Q_FALLTHROUGH();
+ case Atom::LineBreak:
+ case Atom::BR:
+ case Atom::HR:
+ // Not supported in DocBook.
+ break;
+ case Atom::Image: // mediaobject
+ // An Image atom is always followed by an ImageText atom,
+ // containing the alternative text.
+ // If no caption is present, we just output a <db:mediaobject>,
+ // avoiding the wrapper as it is not required.
+ // For bordered images, there is another atom before the
+ // caption, DivRight (the corresponding DivLeft being just
+ // before the image).
+
+ if (atom->next() && matchAhead(atom->next(), Atom::DivRight) && atom->next()->next()
+ && matchAhead(atom->next()->next(), Atom::CaptionLeft)) {
+ // If there is a caption, there must be a <db:figure>
+ // wrapper starting with the caption.
+ Q_ASSERT(atom->next());
+ Q_ASSERT(atom->next()->next());
+ Q_ASSERT(atom->next()->next()->next());
+ Q_ASSERT(atom->next()->next()->next()->next());
+ Q_ASSERT(atom->next()->next()->next()->next()->next());
+
+ m_writer->writeStartElement(dbNamespace, "figure");
+ newLine();
+
+ const Atom *current = atom->next()->next()->next();
+ skipAhead += 2;
+
+ Q_ASSERT(current->type() == Atom::CaptionLeft);
+ generateAtom(current, relative, nullptr);
+ current = current->next();
+ ++skipAhead;
+
+ while (current->type() != Atom::CaptionRight) { // The actual caption.
+ generateAtom(current, relative, nullptr);
+ current = current->next();
+ ++skipAhead;
+ }
+
+ Q_ASSERT(current->type() == Atom::CaptionRight);
+ generateAtom(current, relative, nullptr);
+ current = current->next();
+ ++skipAhead;
+
+ m_closeFigureWrapper = true;
+ }
+
+ if (atom->next() && matchAhead(atom->next(), Atom::CaptionLeft)) {
+ // If there is a caption, there must be a <db:figure>
+ // wrapper starting with the caption.
+ Q_ASSERT(atom->next());
+ Q_ASSERT(atom->next()->next());
+ Q_ASSERT(atom->next()->next()->next());
+ Q_ASSERT(atom->next()->next()->next()->next());
+
+ m_writer->writeStartElement(dbNamespace, "figure");
+ newLine();
+
+ const Atom *current = atom->next()->next();
+ ++skipAhead;
+
+ Q_ASSERT(current->type() == Atom::CaptionLeft);
+ generateAtom(current, relative, nullptr);
+ current = current->next();
+ ++skipAhead;
+
+ while (current->type() != Atom::CaptionRight) { // The actual caption.
+ generateAtom(current, relative, nullptr);
+ current = current->next();
+ ++skipAhead;
+ }
+
+ Q_ASSERT(current->type() == Atom::CaptionRight);
+ generateAtom(current, relative, nullptr);
+ current = current->next();
+ ++skipAhead;
+
+ m_closeFigureWrapper = true;
+ }
+
+ Q_FALLTHROUGH();
+ case Atom::InlineImage: { // inlinemediaobject
+ // TODO: [generator-insufficient-structural-abstraction]
+ // The structure of the computations for this part of the
+ // docbook generation and the same parts in other format
+ // generators is the same.
+ //
+ // The difference, instead, lies in what the generated output
+ // is like. A correct abstraction for a generator would take
+ // this structural equivalence into account and encapsulate it
+ // into a driver for the format generators.
+ //
+ // This would avoid the replication of content, and the
+ // subsequent friction for changes and desynchronization
+ // between generators.
+ //
+ // Review all the generators routines and find the actual
+ // skeleton that is shared between them, then consider it when
+ // extracting the logic for the generation phase.
+ QString tag = atom->type() == Atom::Image ? "mediaobject" : "inlinemediaobject";
+ m_writer->writeStartElement(dbNamespace, tag);
+ newLine();
+
+ auto maybe_resolved_file{file_resolver.resolve(atom->string())};
+ if (!maybe_resolved_file) {
+ // TODO: [uncetnralized-admonition][failed-resolve-file]
+ relative->location().warning(QStringLiteral("Missing image: %1").arg(atom->string()));
+
+ m_writer->writeStartElement(dbNamespace, "textobject");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeTextElement(dbNamespace, "emphasis",
+ "[Missing image " + atom->string() + "]");
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // textobject
+ newLine();
+ } else {
+ ResolvedFile file{*maybe_resolved_file};
+ QString file_name{QFileInfo{file.get_path()}.fileName()};
+
+ // TODO: [uncentralized-output-directory-structure]
+ Config::copyFile(relative->doc().location(), file.get_path(), file_name, outputDir() + QLatin1String("/images"));
+
+ if (atom->next() && !atom->next()->string().isEmpty()
+ && atom->next()->type() == Atom::ImageText) {
+ m_writer->writeTextElement(dbNamespace, "alt", atom->next()->string());
+ newLine();
+ }
+
+ m_writer->writeStartElement(dbNamespace, "imageobject");
+ newLine();
+ m_writer->writeEmptyElement(dbNamespace, "imagedata");
+ // TODO: [uncentralized-output-directory-structure]
+ m_writer->writeAttribute("fileref", "images/" + file_name);
+ newLine();
+ m_writer->writeEndElement(); // imageobject
+ newLine();
+
+ // TODO: [uncentralized-output-directory-structure]
+ setImageFileName(relative, "images/" + file_name);
+ }
+
+ m_writer->writeEndElement(); // [inline]mediaobject
+ if (atom->type() == Atom::Image)
+ newLine();
+
+ if (m_closeFigureWrapper) {
+ m_writer->writeEndElement(); // figure
+ newLine();
+ m_closeFigureWrapper = false;
+ }
+ } break;
+ case Atom::ImageText:
+ break;
+ case Atom::ImportantLeft:
+ case Atom::NoteLeft:
+ case Atom::WarningLeft: {
+ QString admonType = atom->typeString().toLower();
+ // Remove 'Left' to get the admonition type
+ admonType.chop(4);
+ m_writer->writeStartElement(dbNamespace, admonType);
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_inPara = true;
+ } break;
+ case Atom::ImportantRight:
+ case Atom::NoteRight:
+ case Atom::WarningRight:
+ m_writer->writeEndElement(); // para
+ m_inPara = false;
+ newLine();
+ m_writer->writeEndElement(); // note/important
+ newLine();
+ break;
+ case Atom::LegaleseLeft:
+ case Atom::LegaleseRight:
+ break;
+ case Atom::Link:
+ case Atom::NavLink: {
+ const Node *node = nullptr;
+ QString link = getLink(atom, relative, &node);
+ beginLink(link, node, relative); // Ended at Atom::FormattingRight
+ skipAhead = 1;
+ } break;
+ case Atom::LinkNode: {
+ const Node *node = CodeMarker::nodeForString(atom->string());
+ beginLink(linkForNode(node, relative), node, relative);
+ skipAhead = 1;
+ } break;
+ case Atom::ListLeft:
+ if (m_inPara) {
+ // The variable m_inPara is not set in a very smart way, because
+ // it ignores nesting. This might in theory create false positives
+ // here. A better solution would be to track the depth of
+ // paragraphs the generator is in, but determining the right check
+ // for this condition is far from trivial (think of nested lists).
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_inPara = false;
+ }
+
+ if (atom->string() == ATOM_LIST_BULLET) {
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+ } else if (atom->string() == ATOM_LIST_TAG) {
+ m_writer->writeStartElement(dbNamespace, "variablelist");
+ newLine();
+ } else if (atom->string() == ATOM_LIST_VALUE) {
+ m_writer->writeStartElement(dbNamespace, "informaltable");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "thead");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "tr");
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "th", "Constant");
+ newLine();
+
+ m_threeColumnEnumValueTable = isThreeColumnEnumValueTable(atom);
+ if (m_threeColumnEnumValueTable && relative->nodeType() == Node::Enum) {
+ // With three columns, if not in \enum topic, skip the value column
+ m_writer->writeTextElement(dbNamespace, "th", "Value");
+ newLine();
+ }
+
+ if (!isOneColumnValueTable(atom)) {
+ m_writer->writeTextElement(dbNamespace, "th", "Description");
+ newLine();
+ }
+
+ m_writer->writeEndElement(); // tr
+ newLine();
+ m_writer->writeEndElement(); // thead
+ newLine();
+ } else { // No recognized list type.
+ m_writer->writeStartElement(dbNamespace, "orderedlist");
+
+ if (atom->next() != nullptr && atom->next()->string().toInt() > 1)
+ m_writer->writeAttribute("startingnumber", atom->next()->string());
+
+ if (atom->string() == ATOM_LIST_UPPERALPHA)
+ m_writer->writeAttribute("numeration", "upperalpha");
+ else if (atom->string() == ATOM_LIST_LOWERALPHA)
+ m_writer->writeAttribute("numeration", "loweralpha");
+ else if (atom->string() == ATOM_LIST_UPPERROMAN)
+ m_writer->writeAttribute("numeration", "upperroman");
+ else if (atom->string() == ATOM_LIST_LOWERROMAN)
+ m_writer->writeAttribute("numeration", "lowerroman");
+ else // (atom->string() == ATOM_LIST_NUMERIC)
+ m_writer->writeAttribute("numeration", "arabic");
+
+ newLine();
+ }
+ m_inList++;
+ break;
+ case Atom::ListItemNumber:
+ break;
+ case Atom::ListTagLeft:
+ if (atom->string() == ATOM_LIST_TAG) {
+ m_writer->writeStartElement(dbNamespace, "varlistentry");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "item");
+ } else { // (atom->string() == ATOM_LIST_VALUE)
+ std::pair<QString, int> pair = getAtomListValue(atom);
+ skipAhead = pair.second;
+
+ m_writer->writeStartElement(dbNamespace, "tr");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "td");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ generateEnumValue(pair.first, relative);
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // td
+ newLine();
+
+ if (relative->nodeType() == Node::Enum) {
+ const auto enume = static_cast<const EnumNode *>(relative);
+ QString itemValue = enume->itemValue(atom->next()->string());
+
+ m_writer->writeStartElement(dbNamespace, "td");
+ if (itemValue.isEmpty())
+ m_writer->writeCharacters("?");
+ else {
+ m_writer->writeStartElement(dbNamespace, "code");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ m_writer->writeCharacters(itemValue);
+ m_writer->writeEndElement(); // code
+ }
+ m_writer->writeEndElement(); // td
+ newLine();
+ }
+ }
+ m_inList++;
+ break;
+ case Atom::SinceTagRight:
+ if (atom->string() == ATOM_LIST_TAG) {
+ m_writer->writeEndElement(); // item
+ newLine();
+ }
+ break;
+ case Atom::ListTagRight:
+ if (m_inList > 0 && atom->string() == ATOM_LIST_TAG) {
+ m_writer->writeEndElement(); // item
+ newLine();
+ m_inList = false;
+ }
+ break;
+ case Atom::ListItemLeft:
+ if (m_inList > 0) {
+ m_inListItemLineOpen = false;
+ if (atom->string() == ATOM_LIST_TAG) {
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_inPara = true;
+ } else if (atom->string() == ATOM_LIST_VALUE) {
+ if (m_threeColumnEnumValueTable) {
+ if (matchAhead(atom, Atom::ListItemRight)) {
+ m_writer->writeEmptyElement(dbNamespace, "td");
+ newLine();
+ m_inListItemLineOpen = false;
+ } else {
+ m_writer->writeStartElement(dbNamespace, "td");
+ newLine();
+ m_inListItemLineOpen = true;
+ }
+ }
+ } else {
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ }
+ // Don't skip a paragraph, DocBook requires them within list items.
+ }
+ break;
+ case Atom::ListItemRight:
+ if (m_inList > 0) {
+ if (atom->string() == ATOM_LIST_TAG) {
+ m_writer->writeEndElement(); // para
+ m_inPara = false;
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ m_writer->writeEndElement(); // varlistentry
+ newLine();
+ } else if (atom->string() == ATOM_LIST_VALUE) {
+ if (m_inListItemLineOpen) {
+ m_writer->writeEndElement(); // td
+ newLine();
+ m_inListItemLineOpen = false;
+ }
+ m_writer->writeEndElement(); // tr
+ newLine();
+ } else {
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ }
+ }
+ break;
+ case Atom::ListRight:
+ // Depending on atom->string(), closing a different item:
+ // - ATOM_LIST_BULLET: itemizedlist
+ // - ATOM_LIST_TAG: variablelist
+ // - ATOM_LIST_VALUE: informaltable
+ // - ATOM_LIST_NUMERIC: orderedlist
+ m_writer->writeEndElement();
+ newLine();
+ m_inList--;
+ break;
+ case Atom::Nop:
+ break;
+ case Atom::ParaLeft:
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_inPara = true;
+ break;
+ case Atom::ParaRight:
+ endLink();
+ if (m_inPara) {
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_inPara = false;
+ }
+ break;
+ case Atom::QuotationLeft:
+ m_writer->writeStartElement(dbNamespace, "blockquote");
+ m_inBlockquote = true;
+ break;
+ case Atom::QuotationRight:
+ m_writer->writeEndElement(); // blockquote
+ newLine();
+ m_inBlockquote = false;
+ break;
+ case Atom::RawString: {
+ m_writer->device()->write(atom->string().toUtf8());
+ }
+ break;
+ case Atom::SectionLeft:
+ m_hasSection = true;
+
+ currentSectionLevel = atom->string().toInt() + hOffset(relative);
+ // Level 1 is dealt with at the header level (info tag).
+ if (currentSectionLevel > 1) {
+ // Unfortunately, SectionRight corresponds to the end of any section,
+ // i.e. going to a new section, even deeper.
+ while (!sectionLevels.empty() && sectionLevels.top() >= currentSectionLevel) {
+ sectionLevels.pop();
+ m_writer->writeEndElement(); // section
+ newLine();
+ }
+
+ sectionLevels.push(currentSectionLevel);
+
+ m_writer->writeStartElement(dbNamespace, "section");
+ writeXmlId(Tree::refForAtom(atom));
+ newLine();
+ // Unlike startSectionBegin, don't start a title here.
+ }
+
+ if (matchAhead(atom, Atom::SectionHeadingLeft) &&
+ matchAhead(atom->next(), Atom::String) &&
+ matchAhead(atom->next()->next(), Atom::SectionHeadingRight) &&
+ matchAhead(atom->next()->next()->next(), Atom::SectionRight) &&
+ !atom->next()->next()->next()->next()->next()) {
+ // A lonely section at the end of the document indicates that a
+ // generated list of some sort should be within this section.
+ // Close this section later on, in generateFooter().
+ generateAtom(atom->next(), relative, nullptr);
+ generateAtom(atom->next()->next(), relative, nullptr);
+ generateAtom(atom->next()->next()->next(), relative, nullptr);
+
+ m_closeSectionAfterGeneratedList = true;
+ skipAhead += 4;
+ sectionLevels.pop();
+ }
+
+ if (!matchAhead(atom, Atom::SectionHeadingLeft)) {
+ // No section title afterwards, make one up. This likely indicates a problem in the original documentation.
+ m_writer->writeTextElement(dbNamespace, "title", "");
+ }
+ break;
+ case Atom::SectionRight:
+ // All the logic about closing sections is done in the SectionLeft case
+ // and generateFooter() for the end of the page.
+ break;
+ case Atom::SectionHeadingLeft:
+ // Level 1 is dealt with at the header level (info tag).
+ if (currentSectionLevel > 1) {
+ m_writer->writeStartElement(dbNamespace, "title");
+ m_inSectionHeading = true;
+ }
+ break;
+ case Atom::SectionHeadingRight:
+ // Level 1 is dealt with at the header level (info tag).
+ if (currentSectionLevel > 1) {
+ m_writer->writeEndElement(); // title
+ newLine();
+ m_inSectionHeading = false;
+ }
+ break;
+ case Atom::SidebarLeft:
+ m_writer->writeStartElement(dbNamespace, "sidebar");
+ break;
+ case Atom::SidebarRight:
+ m_writer->writeEndElement(); // sidebar
+ newLine();
+ break;
+ case Atom::String:
+ if (m_inLink && !m_inContents && !m_inSectionHeading)
+ generateLink(atom);
+ else
+ m_writer->writeCharacters(atom->string());
+ break;
+ case Atom::TableLeft: {
+ std::pair<QString, QString> pair = getTableWidthAttr(atom);
+ QString attr = pair.second;
+ QString width = pair.first;
+
+ if (m_inPara) {
+ m_writer->writeEndElement(); // para or blockquote
+ newLine();
+ m_inPara = false;
+ }
+
+ m_tableHeaderAlreadyOutput = false;
+
+ m_writer->writeStartElement(dbNamespace, "informaltable");
+ m_writer->writeAttribute("style", attr);
+ if (!width.isEmpty())
+ m_writer->writeAttribute("width", width);
+ newLine();
+ } break;
+ case Atom::TableRight:
+ m_tableWidthAttr = {"", ""};
+ m_writer->writeEndElement(); // table
+ newLine();
+ break;
+ case Atom::TableHeaderLeft: {
+ if (matchAhead(atom, Atom::TableHeaderRight)) {
+ ++skipAhead;
+ break;
+ }
+
+ if (m_tableHeaderAlreadyOutput) {
+ // Headers are only allowed at the beginning of the table: close
+ // the table and reopen one.
+ m_writer->writeEndElement(); // table
+ newLine();
+
+ const QString &attr = m_tableWidthAttr.second;
+ const QString &width = m_tableWidthAttr.first;
+
+ m_writer->writeStartElement(dbNamespace, "informaltable");
+ m_writer->writeAttribute("style", attr);
+ if (!width.isEmpty())
+ m_writer->writeAttribute("width", width);
+ newLine();
+ } else {
+ m_tableHeaderAlreadyOutput = true;
+ }
+
+ const Atom *next = atom->next();
+ QString id{""};
+ if (matchAhead(atom, Atom::Target)) {
+ id = Utilities::asAsciiPrintable(next->string());
+ next = next->next();
+ ++skipAhead;
+ }
+
+ m_writer->writeStartElement(dbNamespace, "thead");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "tr");
+ writeXmlId(id);
+ newLine();
+ m_inTableHeader = true;
+
+ if (!matchAhead(atom, Atom::TableItemLeft)) {
+ m_closeTableCell = true;
+ m_writer->writeStartElement(dbNamespace, "td");
+ newLine();
+ }
+ }
+ break;
+ case Atom::TableHeaderRight:
+ if (m_closeTableCell) {
+ m_closeTableCell = false;
+ m_writer->writeEndElement(); // td
+ newLine();
+ }
+
+ m_writer->writeEndElement(); // tr
+ newLine();
+ if (matchAhead(atom, Atom::TableHeaderLeft)) {
+ skipAhead = 1;
+ m_writer->writeStartElement(dbNamespace, "tr");
+ newLine();
+ } else {
+ m_writer->writeEndElement(); // thead
+ newLine();
+ m_inTableHeader = false;
+ }
+ break;
+ case Atom::TableRowLeft: {
+ if (matchAhead(atom, Atom::TableRowRight)) {
+ skipAhead = 1;
+ break;
+ }
+
+ QString id{""};
+ bool hasTarget {false};
+ if (matchAhead(atom, Atom::Target)) {
+ id = Utilities::asAsciiPrintable(atom->next()->string());
+ ++skipAhead;
+ hasTarget = true;
+ }
+
+ m_writer->writeStartElement(dbNamespace, "tr");
+ writeXmlId(id);
+
+ if (atom->string().isEmpty()) {
+ m_writer->writeAttribute("valign", "top");
+ } else {
+ // Basic parsing of attributes, should be enough. The input string (atom->string())
+ // looks like:
+ // arg1="val1" arg2="val2"
+ QStringList args = atom->string().split("\"", Qt::SkipEmptyParts);
+ // arg1=, val1, arg2=, val2,
+ // \-- 1st --/ \-- 2nd --/ \-- remainder
+ const int nArgs = args.size();
+
+ if (nArgs % 2) {
+ // Problem...
+ relative->doc().location().warning(
+ QStringLiteral("Error when parsing attributes for the table: got \"%1\"")
+ .arg(atom->string()));
+ }
+ for (int i = 0; i + 1 < nArgs; i += 2) {
+ // args.at(i): name of the attribute being set.
+ // args.at(i + 1): value of the said attribute.
+ const QString &attr = args.at(i).chopped(1);
+ if (attr == "id") { // Too bad if there is an anchor later on
+ // (currently never happens).
+ writeXmlId(args.at(i + 1));
+ } else {
+ m_writer->writeAttribute(attr, args.at(i + 1));
+ }
+ }
+ }
+ newLine();
+
+ // If there is nothing in this row, close it right now. There might be keywords before the row contents.
+ bool isRowEmpty = hasTarget ? !matchAhead(atom->next(), Atom::TableItemLeft) : !matchAhead(atom, Atom::TableItemLeft);
+ if (isRowEmpty && matchAhead(atom, Atom::Keyword)) {
+ const Atom* next = atom->next();
+ while (matchAhead(next, Atom::Keyword))
+ next = next->next();
+ isRowEmpty = !matchAhead(next, Atom::TableItemLeft);
+ }
+
+ if (isRowEmpty) {
+ m_closeTableRow = true;
+ m_writer->writeEndElement(); // td
+ newLine();
+ }
+ }
+ break;
+ case Atom::TableRowRight:
+ if (m_closeTableRow) {
+ m_closeTableRow = false;
+ m_writer->writeEndElement(); // td
+ newLine();
+ }
+
+ m_writer->writeEndElement(); // tr
+ newLine();
+ break;
+ case Atom::TableItemLeft:
+ m_writer->writeStartElement(dbNamespace, m_inTableHeader ? "th" : "td");
+
+ for (int i = 0; i < atom->count(); ++i) {
+ const QString &p = atom->string(i);
+ if (p.contains('=')) {
+ QStringList lp = p.split(QLatin1Char('='));
+ m_writer->writeAttribute(lp.at(0), lp.at(1));
+ } else {
+ QStringList spans = p.split(QLatin1Char(','));
+ if (spans.size() == 2) {
+ if (spans.at(0) != "1")
+ m_writer->writeAttribute("colspan", spans.at(0).trimmed());
+ if (spans.at(1) != "1")
+ m_writer->writeAttribute("rowspan", spans.at(1).trimmed());
+ }
+ }
+ }
+ newLine();
+ // No skipahead, as opposed to HTML: in DocBook, the text must be wrapped in paragraphs.
+ break;
+ case Atom::TableItemRight:
+ m_writer->writeEndElement(); // th if m_inTableHeader, otherwise td
+ newLine();
+ break;
+ case Atom::TableOfContents:
+ Q_FALLTHROUGH();
+ case Atom::Keyword:
+ break;
+ case Atom::Target:
+ // Sometimes, there is a \target just before a section title with the same ID. Only output one xml:id.
+ if (matchAhead(atom, Atom::SectionRight) && matchAhead(atom->next(), Atom::SectionLeft)) {
+ QString nextId = Utilities::asAsciiPrintable(
+ Text::sectionHeading(atom->next()->next()).toString());
+ QString ownId = Utilities::asAsciiPrintable(atom->string());
+ if (nextId == ownId)
+ break;
+ }
+
+ writeAnchor(Utilities::asAsciiPrintable(atom->string()));
+ break;
+ case Atom::UnhandledFormat:
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ m_writer->writeCharacters("<Missing DocBook>");
+ m_writer->writeEndElement(); // emphasis
+ break;
+ case Atom::UnknownCommand:
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ m_writer->writeCharacters("<Unknown command>");
+ m_writer->writeStartElement(dbNamespace, "code");
+ m_writer->writeCharacters(atom->string());
+ m_writer->writeEndElement(); // code
+ m_writer->writeEndElement(); // emphasis
+ break;
+ case Atom::CodeQuoteArgument:
+ case Atom::CodeQuoteCommand:
+ case Atom::ComparesLeft:
+ case Atom::ComparesRight:
+ case Atom::SnippetCommand:
+ case Atom::SnippetIdentifier:
+ case Atom::SnippetLocation:
+ // No output (ignore).
+ break;
+ default:
+ unknownAtom(atom);
+ }
+ return skipAhead;
+}
+
+void DocBookGenerator::generateClassHierarchy(const Node *relative, NodeMultiMap &classMap)
+{
+ // From HtmlGenerator::generateClassHierarchy.
+ if (classMap.isEmpty())
+ return;
+
+ std::function<void(ClassNode *)> generateClassAndChildren
+ = [this, &relative, &generateClassAndChildren](ClassNode * classe) {
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+
+ // This class.
+ m_writer->writeStartElement(dbNamespace, "para");
+ generateFullName(classe, relative);
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ // Children, if any.
+ bool hasChild = false;
+ for (const RelatedClass &relatedClass : classe->derivedClasses()) {
+ if (relatedClass.m_node && relatedClass.m_node->isInAPI()) {
+ hasChild = true;
+ break;
+ }
+ }
+
+ if (hasChild) {
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+
+ for (const RelatedClass &relatedClass: classe->derivedClasses()) {
+ if (relatedClass.m_node && relatedClass.m_node->isInAPI()) {
+ generateClassAndChildren(relatedClass.m_node);
+ }
+ }
+
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+ }
+
+ // End this class.
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ };
+
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+
+ for (const auto &it : classMap) {
+ auto *classe = static_cast<ClassNode *>(it);
+ if (classe->baseClasses().isEmpty())
+ generateClassAndChildren(classe);
+ }
+
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+}
+
+void DocBookGenerator::generateLink(const Atom *atom)
+{
+ Q_ASSERT(m_inLink);
+
+ // From HtmlGenerator::generateLink.
+ if (m_linkNode && m_linkNode->isFunction()) {
+ auto match = XmlGenerator::m_funcLeftParen.match(atom->string());
+ if (match.hasMatch()) {
+ // C++: move () outside of link
+ qsizetype leftParenLoc = match.capturedStart(1);
+ m_writer->writeCharacters(atom->string().left(leftParenLoc));
+ endLink();
+ m_writer->writeCharacters(atom->string().mid(leftParenLoc));
+ return;
+ }
+ }
+ m_writer->writeCharacters(atom->string());
+}
+
+/*!
+ This version of the function is called when the \a link is known
+ to be correct.
+ */
+void DocBookGenerator::beginLink(const QString &link, const Node *node, const Node *relative)
+{
+ // From HtmlGenerator::beginLink.
+ m_writer->writeStartElement(dbNamespace, "link");
+ m_writer->writeAttribute(xlinkNamespace, "href", link);
+ if (node && !(relative && node->status() == relative->status())
+ && node->isDeprecated())
+ m_writer->writeAttribute("role", "deprecated");
+ m_inLink = true;
+ m_linkNode = node;
+}
+
+void DocBookGenerator::endLink()
+{
+ // From HtmlGenerator::endLink.
+ if (m_inLink)
+ m_writer->writeEndElement(); // link
+ m_inLink = false;
+ m_linkNode = nullptr;
+}
+
+void DocBookGenerator::generateList(const Node *relative, const QString &selector)
+{
+ // From HtmlGenerator::generateList, without warnings, changing prototype.
+ CNMap cnm;
+ Node::NodeType type = Node::NoType;
+ if (selector == QLatin1String("overviews"))
+ type = Node::Group;
+ else if (selector == QLatin1String("cpp-modules"))
+ type = Node::Module;
+ else if (selector == QLatin1String("qml-modules"))
+ type = Node::QmlModule;
+
+ if (type != Node::NoType) {
+ NodeList nodeList;
+ m_qdb->mergeCollections(type, cnm, relative);
+ const QList<CollectionNode *> collectionList = cnm.values();
+ nodeList.reserve(collectionList.size());
+ for (auto *collectionNode : collectionList)
+ nodeList.append(collectionNode);
+ generateAnnotatedList(relative, nodeList, selector);
+ } else {
+ /*
+ \generatelist {selector} is only allowed in a comment where
+ the topic is \group, \module, or \qmlmodule.
+ */
+ Node *n = const_cast<Node *>(relative);
+ auto *cn = static_cast<CollectionNode *>(n);
+ m_qdb->mergeCollections(cn);
+ generateAnnotatedList(cn, cn->members(), selector);
+ }
+}
+
+/*!
+ Outputs an annotated list of the nodes in \a nodeList.
+ A two-column table is output.
+ */
+void DocBookGenerator::generateAnnotatedList(const Node *relative, const NodeList &nodeList,
+ const QString &selector, GeneratedListType type)
+{
+ if (nodeList.isEmpty())
+ return;
+
+ // Do nothing if all items are internal or obsolete.
+ if (std::all_of(nodeList.cbegin(), nodeList.cend(), [](const Node *n) {
+ return n->isInternal() || n->isDeprecated(); })) {
+ return;
+ }
+
+ // Detect if there is a need for a variablelist (i.e. titles mapped to
+ // descriptions) or a regular itemizedlist (only titles).
+ bool noItemsHaveTitle =
+ type == ItemizedList || std::all_of(nodeList.begin(), nodeList.end(),
+ [](const Node* node) {
+ return node->doc().briefText().toString().isEmpty();
+ });
+
+ // Wrap the list in a section if needed.
+ if (type == AutoSection && m_hasSection)
+ startSection("", "Contents");
+
+ // From WebXMLGenerator::generateAnnotatedList.
+ if (!nodeList.isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, noItemsHaveTitle ? "itemizedlist" : "variablelist");
+ m_writer->writeAttribute("role", selector);
+ newLine();
+
+ NodeList members{nodeList};
+ std::sort(members.begin(), members.end(), Node::nodeNameLessThan);
+ for (const auto &node : std::as_const(members)) {
+ if (node->isInternal() || node->isDeprecated())
+ continue;
+
+ if (noItemsHaveTitle) {
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ } else {
+ m_writer->writeStartElement(dbNamespace, "varlistentry");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "term");
+ }
+ generateFullName(node, relative);
+ if (noItemsHaveTitle) {
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ } else {
+ m_writer->writeEndElement(); // term
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters(node->doc().briefText().toString());
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ m_writer->writeEndElement(); // varlistentry
+ }
+ newLine();
+ }
+
+ m_writer->writeEndElement(); // itemizedlist or variablelist
+ newLine();
+ }
+
+ if (type == AutoSection && m_hasSection)
+ endSection();
+}
+
+/*!
+ Outputs a series of annotated lists from the nodes in \a nmm,
+ divided into sections based by the key names in the multimap.
+ */
+void DocBookGenerator::generateAnnotatedLists(const Node *relative, const NodeMultiMap &nmm,
+ const QString &selector)
+{
+ // From HtmlGenerator::generateAnnotatedLists.
+ for (const QString &name : nmm.uniqueKeys()) {
+ if (!name.isEmpty())
+ startSection(name.toLower(), name);
+ generateAnnotatedList(relative, nmm.values(name), selector);
+ if (!name.isEmpty())
+ endSection();
+ }
+}
+
+/*!
+ This function finds the common prefix of the names of all
+ the classes in the class map \a nmm and then generates a
+ compact list of the class names alphabetized on the part
+ of the name not including the common prefix. You can tell
+ the function to use \a comonPrefix as the common prefix,
+ but normally you let it figure it out itself by looking at
+ the name of the first and last classes in the class map
+ \a nmm.
+ */
+void DocBookGenerator::generateCompactList(const Node *relative, const NodeMultiMap &nmm,
+ bool includeAlphabet, const QString &commonPrefix,
+ const QString &selector)
+{
+ // From HtmlGenerator::generateCompactList. No more "includeAlphabet", this should be handled by
+ // the DocBook toolchain afterwards.
+ // TODO: In DocBook, probably no need for this method: this is purely presentational, i.e. to be
+ // fully handled by the DocBook toolchain.
+
+ if (nmm.isEmpty())
+ return;
+
+ const int NumParagraphs = 37; // '0' to '9', 'A' to 'Z', '_'
+ qsizetype commonPrefixLen = commonPrefix.size();
+
+ /*
+ Divide the data into 37 paragraphs: 0, ..., 9, A, ..., Z,
+ underscore (_). QAccel will fall in paragraph 10 (A) and
+ QXtWidget in paragraph 33 (X). This is the only place where we
+ assume that NumParagraphs is 37. Each paragraph is a NodeMultiMap.
+ */
+ NodeMultiMap paragraph[NumParagraphs + 1];
+ QString paragraphName[NumParagraphs + 1];
+ QSet<char> usedParagraphNames;
+
+ for (auto c = nmm.constBegin(); c != nmm.constEnd(); ++c) {
+ QStringList pieces = c.key().split("::");
+ int idx = commonPrefixLen;
+ if (idx > 0 && !pieces.last().startsWith(commonPrefix, Qt::CaseInsensitive))
+ idx = 0;
+ QString last = pieces.last().toLower();
+ QString key = last.mid(idx);
+
+ int paragraphNr = NumParagraphs - 1;
+
+ if (key[0].digitValue() != -1) {
+ paragraphNr = key[0].digitValue();
+ } else if (key[0] >= QLatin1Char('a') && key[0] <= QLatin1Char('z')) {
+ paragraphNr = 10 + key[0].unicode() - 'a';
+ }
+
+ paragraphName[paragraphNr] = key[0].toUpper();
+ usedParagraphNames.insert(key[0].toLower().cell());
+ paragraph[paragraphNr].insert(last, c.value());
+ }
+
+ /*
+ Each paragraph j has a size: paragraph[j].count(). In the
+ discussion, we will assume paragraphs 0 to 5 will have sizes
+ 3, 1, 4, 1, 5, 9.
+
+ We now want to compute the paragraph offset. Paragraphs 0 to 6
+ start at offsets 0, 3, 4, 8, 9, 14, 23.
+ */
+ int paragraphOffset[NumParagraphs + 1]; // 37 + 1
+ paragraphOffset[0] = 0;
+ for (int i = 0; i < NumParagraphs; i++) // i = 0..36
+ paragraphOffset[i + 1] = paragraphOffset[i] + paragraph[i].size();
+
+ // Output the alphabet as a row of links.
+ if (includeAlphabet && !usedParagraphNames.isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "simplelist");
+ newLine();
+
+ for (int i = 0; i < 26; i++) {
+ QChar ch('a' + i);
+ if (usedParagraphNames.contains(char('a' + i))) {
+ m_writer->writeStartElement(dbNamespace, "member");
+ generateSimpleLink(ch, ch.toUpper());
+ m_writer->writeEndElement(); // member
+ newLine();
+ }
+ }
+
+ m_writer->writeEndElement(); // simplelist
+ newLine();
+ }
+
+ // Actual output.
+ int curParNr = 0;
+ int curParOffset = 0;
+ QString previousName;
+ bool multipleOccurrences = false;
+
+ m_writer->writeStartElement(dbNamespace, "variablelist");
+ m_writer->writeAttribute("role", selector);
+ newLine();
+
+ for (int i = 0; i < nmm.size(); i++) {
+ while ((curParNr < NumParagraphs) && (curParOffset == paragraph[curParNr].size())) {
+
+ ++curParNr;
+ curParOffset = 0;
+ }
+
+ // Starting a new paragraph means starting a new varlistentry.
+ if (curParOffset == 0) {
+ if (i > 0) {
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ m_writer->writeEndElement(); // varlistentry
+ newLine();
+ }
+
+ m_writer->writeStartElement(dbNamespace, "varlistentry");
+ if (includeAlphabet)
+ writeXmlId(paragraphName[curParNr][0].toLower());
+ newLine();
+
+ m_writer->writeStartElement(dbNamespace, "term");
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ m_writer->writeCharacters(paragraphName[curParNr]);
+ m_writer->writeEndElement(); // emphasis
+ m_writer->writeEndElement(); // term
+ newLine();
+
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+ }
+
+ // Output a listitem for the current offset in the current paragraph.
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+
+ if ((curParNr < NumParagraphs) && !paragraphName[curParNr].isEmpty()) {
+ NodeMultiMap::Iterator it;
+ NodeMultiMap::Iterator next;
+ it = paragraph[curParNr].begin();
+ for (int j = 0; j < curParOffset; j++)
+ ++it;
+
+ // Cut the name into pieces to determine whether it is simple (one piece) or complex
+ // (more than one piece).
+ QStringList pieces{it.value()->fullName(relative).split("::"_L1)};
+ const auto &name{pieces.last()};
+ next = it;
+ ++next;
+ if (name != previousName)
+ multipleOccurrences = false;
+ if ((next != paragraph[curParNr].end()) && (name == next.value()->name())) {
+ multipleOccurrences = true;
+ previousName = name;
+ }
+ if (multipleOccurrences && pieces.size() == 1)
+ pieces.last().append(": "_L1.arg(it.value()->tree()->camelCaseModuleName()));
+
+ // Write the link to the element, which is identical if the element is obsolete or not.
+ m_writer->writeStartElement(dbNamespace, "link");
+ m_writer->writeAttribute(xlinkNamespace, "href", linkForNode(*it, relative));
+ if (const QString type = targetType(it.value()); !type.isEmpty())
+ m_writer->writeAttribute("role", type);
+ m_writer->writeCharacters(pieces.last());
+ m_writer->writeEndElement(); // link
+
+ // Outside the link, give the full name of the node if it is complex.
+ if (pieces.size() > 1) {
+ m_writer->writeCharacters(" (");
+ generateFullName(it.value()->parent(), relative);
+ m_writer->writeCharacters(")");
+ }
+ }
+
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+
+ curParOffset++;
+ }
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ m_writer->writeEndElement(); // varlistentry
+ newLine();
+
+ m_writer->writeEndElement(); // variablelist
+ newLine();
+}
+
+void DocBookGenerator::generateFunctionIndex(const Node *relative)
+{
+ // From HtmlGenerator::generateFunctionIndex.
+
+ // First list: links to parts of the second list, one item per letter.
+ m_writer->writeStartElement(dbNamespace, "simplelist");
+ m_writer->writeAttribute("role", "functionIndex");
+ newLine();
+ for (int i = 0; i < 26; i++) {
+ QChar ch('a' + i);
+ m_writer->writeStartElement(dbNamespace, "member");
+ m_writer->writeAttribute(xlinkNamespace, "href", QString("#") + ch);
+ m_writer->writeCharacters(ch.toUpper());
+ m_writer->writeEndElement(); // member
+ newLine();
+ }
+ m_writer->writeEndElement(); // simplelist
+ newLine();
+
+ // Second list: the actual list of functions, sorted by alphabetical
+ // order. One entry of the list per letter.
+ if (m_qdb->getFunctionIndex().isEmpty())
+ return;
+ char nextLetter = 'a';
+ char currentLetter;
+
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+
+ NodeMapMap &funcIndex = m_qdb->getFunctionIndex();
+ QMap<QString, NodeMap>::ConstIterator f = funcIndex.constBegin();
+ while (f != funcIndex.constEnd()) {
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters(f.key() + ": ");
+
+ currentLetter = f.key()[0].unicode();
+ while (islower(currentLetter) && currentLetter >= nextLetter) {
+ writeAnchor(QString(nextLetter));
+ nextLetter++;
+ }
+
+ NodeMap::ConstIterator s = (*f).constBegin();
+ while (s != (*f).constEnd()) {
+ m_writer->writeCharacters(" ");
+ generateFullName((*s)->parent(), relative);
+ ++s;
+ }
+
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ ++f;
+ }
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+}
+
+void DocBookGenerator::generateLegaleseList(const Node *relative)
+{
+ // From HtmlGenerator::generateLegaleseList.
+ TextToNodeMap &legaleseTexts = m_qdb->getLegaleseTexts();
+ for (auto it = legaleseTexts.cbegin(), end = legaleseTexts.cend(); it != end; ++it) {
+ Text text = it.key();
+ generateText(text, relative);
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+ do {
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ generateFullName(it.value(), relative);
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ ++it;
+ } while (it != legaleseTexts.constEnd() && it.key() == text);
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+ }
+}
+
+void DocBookGenerator::generateBrief(const Node *node)
+{
+ // From HtmlGenerator::generateBrief. Also see generateHeader, which is specifically dealing
+ // with the DocBook header (and thus wraps the brief in an abstract).
+ Text brief = node->doc().briefText();
+
+ if (!brief.isEmpty()) {
+ if (!brief.lastAtom()->string().endsWith('.'))
+ brief << Atom(Atom::String, ".");
+
+ m_writer->writeStartElement(dbNamespace, "para");
+ generateText(brief, node);
+ m_writer->writeEndElement(); // para
+ newLine();
+ }
+}
+
+bool DocBookGenerator::generateSince(const Node *node)
+{
+ // From Generator::generateSince.
+ if (!node->since().isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("This " + typeString(node) + " was introduced in ");
+ m_writer->writeCharacters(formatSince(node) + ".");
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ return true;
+ }
+
+ return false;
+}
+
+/*!
+ Generate the DocBook header for the file, including the abstract.
+ Equivalent to calling generateTitle and generateBrief in HTML.
+*/
+void DocBookGenerator::generateHeader(const QString &title, const QString &subTitle,
+ const Node *node)
+{
+ refMap.clear();
+
+ // Output the DocBook header.
+ m_writer->writeStartElement(dbNamespace, "info");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "title");
+ if (node->genus() & Node::API && m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ m_writer->writeCharacters(title);
+ m_writer->writeEndElement(); // title
+ newLine();
+
+ if (!subTitle.isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "subtitle");
+ if (node->genus() & Node::API && m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ m_writer->writeCharacters(subTitle);
+ m_writer->writeEndElement(); // subtitle
+ newLine();
+ }
+
+ if (!m_project.isEmpty()) {
+ m_writer->writeTextElement(dbNamespace, "productname", m_project);
+ newLine();
+ }
+
+ if (!m_buildVersion.isEmpty()) {
+ m_writer->writeTextElement(dbNamespace, "edition", m_buildVersion);
+ newLine();
+ }
+
+ if (!m_projectDescription.isEmpty()) {
+ m_writer->writeTextElement(dbNamespace, "titleabbrev", m_projectDescription);
+ newLine();
+ }
+
+ // Deal with links.
+ // Adapted from HtmlGenerator::generateHeader (output part: no need to update a navigationLinks
+ // or useSeparator field, as this content is only output in the info tag, not in the main
+ // content).
+ if (node && !node->links().empty()) {
+ std::pair<QString, QString> linkPair;
+ std::pair<QString, QString> anchorPair;
+ const Node *linkNode;
+
+ if (node->links().contains(Node::PreviousLink)) {
+ linkPair = node->links()[Node::PreviousLink];
+ linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
+ if (!linkNode || linkNode == node)
+ anchorPair = linkPair;
+ else
+ anchorPair = anchorForNode(linkNode);
+
+ m_writer->writeStartElement(dbNamespace, "extendedlink");
+ m_writer->writeAttribute(xlinkNamespace, "type", "extended");
+ m_writer->writeEmptyElement(dbNamespace, "link");
+ m_writer->writeAttribute(xlinkNamespace, "to", anchorPair.first);
+ m_writer->writeAttribute(xlinkNamespace, "type", "arc");
+ m_writer->writeAttribute(xlinkNamespace, "arcrole", "prev");
+ if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
+ m_writer->writeAttribute(xlinkNamespace, "title", anchorPair.second);
+ else
+ m_writer->writeAttribute(xlinkNamespace, "title", linkPair.second);
+ m_writer->writeEndElement(); // extendedlink
+ newLine();
+ }
+ if (node->links().contains(Node::NextLink)) {
+ linkPair = node->links()[Node::NextLink];
+ linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
+ if (!linkNode || linkNode == node)
+ anchorPair = linkPair;
+ else
+ anchorPair = anchorForNode(linkNode);
+
+ m_writer->writeStartElement(dbNamespace, "extendedlink");
+ m_writer->writeAttribute(xlinkNamespace, "type", "extended");
+ m_writer->writeEmptyElement(dbNamespace, "link");
+ m_writer->writeAttribute(xlinkNamespace, "to", anchorPair.first);
+ m_writer->writeAttribute(xlinkNamespace, "type", "arc");
+ m_writer->writeAttribute(xlinkNamespace, "arcrole", "next");
+ if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
+ m_writer->writeAttribute(xlinkNamespace, "title", anchorPair.second);
+ else
+ m_writer->writeAttribute(xlinkNamespace, "title", linkPair.second);
+ m_writer->writeEndElement(); // extendedlink
+ newLine();
+ }
+ if (node->links().contains(Node::StartLink)) {
+ linkPair = node->links()[Node::StartLink];
+ linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
+ if (!linkNode || linkNode == node)
+ anchorPair = linkPair;
+ else
+ anchorPair = anchorForNode(linkNode);
+
+ m_writer->writeStartElement(dbNamespace, "extendedlink");
+ m_writer->writeAttribute(xlinkNamespace, "type", "extended");
+ m_writer->writeEmptyElement(dbNamespace, "link");
+ m_writer->writeAttribute(xlinkNamespace, "to", anchorPair.first);
+ m_writer->writeAttribute(xlinkNamespace, "type", "arc");
+ m_writer->writeAttribute(xlinkNamespace, "arcrole", "start");
+ if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
+ m_writer->writeAttribute(xlinkNamespace, "title", anchorPair.second);
+ else
+ m_writer->writeAttribute(xlinkNamespace, "title", linkPair.second);
+ m_writer->writeEndElement(); // extendedlink
+ newLine();
+ }
+ }
+
+ // Deal with the abstract (what qdoc calls brief).
+ if (node) {
+ // Adapted from HtmlGenerator::generateBrief, without extraction marks. The parameter
+ // addLink is always false. Factoring this function out is not as easy as in HtmlGenerator:
+ // abstracts only happen in the header (info tag), slightly different tags must be used at
+ // other places. Also includes code from HtmlGenerator::generateCppReferencePage to handle
+ // the name spaces.
+ m_writer->writeStartElement(dbNamespace, "abstract");
+ newLine();
+
+ bool generatedSomething = false;
+
+ Text brief;
+ const NamespaceNode *ns =
+ node->isNamespace() ? static_cast<const NamespaceNode *>(node) : nullptr;
+ if (ns && !ns->hasDoc() && ns->docNode()) {
+ NamespaceNode *NS = ns->docNode();
+ brief << "The " << ns->name()
+ << " namespace includes the following elements from module "
+ << ns->tree()->camelCaseModuleName() << ". The full namespace is "
+ << "documented in module " << NS->tree()->camelCaseModuleName()
+ << Atom(Atom::LinkNode, fullDocumentLocation(NS))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << Atom(Atom::String, " here.")
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+ } else {
+ brief = node->doc().briefText();
+ }
+
+ if (!brief.isEmpty()) {
+ if (!brief.lastAtom()->string().endsWith('.'))
+ brief << Atom(Atom::String, ".");
+
+ m_writer->writeStartElement(dbNamespace, "para");
+ generateText(brief, node);
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ generatedSomething = true;
+ }
+
+ // Generate other paragraphs that should go into the abstract.
+ generatedSomething |= generateStatus(node);
+ generatedSomething |= generateSince(node);
+ generatedSomething |= generateThreadSafeness(node);
+ generatedSomething |= generateComparisonCategory(node);
+ generatedSomething |= generateComparisonList(node);
+
+ // An abstract cannot be empty, hence use the project description.
+ if (!generatedSomething)
+ m_writer->writeTextElement(dbNamespace, "para", m_projectDescription + ".");
+
+ m_writer->writeEndElement(); // abstract
+ newLine();
+ }
+
+ // End of the DocBook header.
+ m_writer->writeEndElement(); // info
+ newLine();
+}
+
+void DocBookGenerator::closeTextSections()
+{
+ while (!sectionLevels.isEmpty()) {
+ sectionLevels.pop();
+ endSection();
+ }
+}
+
+void DocBookGenerator::generateFooter()
+{
+ if (m_closeSectionAfterGeneratedList) {
+ m_closeSectionAfterGeneratedList = false;
+ endSection();
+ }
+ if (m_closeSectionAfterRawTitle) {
+ m_closeSectionAfterRawTitle = false;
+ endSection();
+ }
+
+ closeTextSections();
+ m_writer->writeEndElement(); // article
+}
+
+void DocBookGenerator::generateSimpleLink(const QString &href, const QString &text)
+{
+ m_writer->writeStartElement(dbNamespace, "link");
+ m_writer->writeAttribute(xlinkNamespace, "href", href);
+ m_writer->writeCharacters(text);
+ m_writer->writeEndElement(); // link
+}
+
+void DocBookGenerator::generateObsoleteMembers(const Sections &sections)
+{
+ // From HtmlGenerator::generateObsoleteMembersFile.
+ SectionPtrVector summary_spv; // Summaries are ignored in DocBook (table of contents).
+ SectionPtrVector details_spv;
+ if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
+ return;
+
+ Aggregate *aggregate = sections.aggregate();
+ startSection("obsolete", "Obsolete Members for " + aggregate->name());
+
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ m_writer->writeCharacters("The following members of class ");
+ generateSimpleLink(linkForNode(aggregate, nullptr), aggregate->name());
+ m_writer->writeCharacters(" are deprecated.");
+ m_writer->writeEndElement(); // emphasis bold
+ m_writer->writeCharacters(" We strongly advise against using them in new code.");
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ for (const Section *section : details_spv) {
+ const QString &title = "Obsolete " + section->title();
+ startSection(title.toLower(), title);
+
+ const NodeVector &members = section->obsoleteMembers();
+ NodeVector::ConstIterator m = members.constBegin();
+ while (m != members.constEnd()) {
+ if ((*m)->access() != Access::Private)
+ generateDetailedMember(*m, aggregate);
+ ++m;
+ }
+
+ endSection();
+ }
+
+ endSection();
+}
+
+/*!
+ Generates a separate section where obsolete members of the QML
+ type \a qcn are listed. The \a marker is used to generate
+ the section lists, which are then traversed and output here.
+
+ Note that this function currently only handles correctly the
+ case where \a status is \c {Section::Deprecated}.
+ */
+void DocBookGenerator::generateObsoleteQmlMembers(const Sections &sections)
+{
+ // From HtmlGenerator::generateObsoleteQmlMembersFile.
+ SectionPtrVector summary_spv; // Summaries are not useful in DocBook.
+ SectionPtrVector details_spv;
+ if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
+ return;
+
+ Aggregate *aggregate = sections.aggregate();
+ startSection("obsolete", "Obsolete Members for " + aggregate->name());
+
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ m_writer->writeCharacters("The following members of QML type ");
+ generateSimpleLink(linkForNode(aggregate, nullptr), aggregate->name());
+ m_writer->writeCharacters(" are deprecated.");
+ m_writer->writeEndElement(); // emphasis bold
+ m_writer->writeCharacters(" We strongly advise against using them in new code.");
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ for (const auto *section : details_spv) {
+ const QString &title = "Obsolete " + section->title();
+ startSection(title.toLower(), title);
+
+ const NodeVector &members = section->obsoleteMembers();
+ NodeVector::ConstIterator m = members.constBegin();
+ while (m != members.constEnd()) {
+ if ((*m)->access() != Access::Private)
+ generateDetailedQmlMember(*m, aggregate);
+ ++m;
+ }
+
+ endSection();
+ }
+
+ endSection();
+}
+
+static QString nodeToSynopsisTag(const Node *node)
+{
+ // Order from Node::nodeTypeString.
+ if (node->isClass() || node->isQmlType())
+ return QStringLiteral("classsynopsis");
+ if (node->isNamespace())
+ return QStringLiteral("packagesynopsis");
+ if (node->isPageNode()) {
+ node->doc().location().warning("Unexpected document node in nodeToSynopsisTag");
+ return QString();
+ }
+ if (node->isEnumType())
+ return QStringLiteral("enumsynopsis");
+ if (node->isTypedef())
+ return QStringLiteral("typedefsynopsis");
+ if (node->isFunction()) {
+ // Signals are also encoded as functions (including QML ones).
+ const auto fn = static_cast<const FunctionNode *>(node);
+ if (fn->isCtor() || fn->isCCtor() || fn->isMCtor())
+ return QStringLiteral("constructorsynopsis");
+ if (fn->isDtor())
+ return QStringLiteral("destructorsynopsis");
+ return QStringLiteral("methodsynopsis");
+ }
+ if (node->isProperty() || node->isVariable() || node->isQmlProperty())
+ return QStringLiteral("fieldsynopsis");
+
+ node->doc().location().warning(QString("Unknown node tag %1").arg(node->nodeTypeString()));
+ return QStringLiteral("synopsis");
+}
+
+void DocBookGenerator::generateStartRequisite(const QString &description)
+{
+ m_writer->writeStartElement(dbNamespace, "varlistentry");
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "term", description);
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_inPara = true;
+}
+
+void DocBookGenerator::generateEndRequisite()
+{
+ m_writer->writeEndElement(); // para
+ m_inPara = false;
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ m_writer->writeEndElement(); // varlistentry
+ newLine();
+}
+
+void DocBookGenerator::generateRequisite(const QString &description, const QString &value)
+{
+ generateStartRequisite(description);
+ m_writer->writeCharacters(value);
+ generateEndRequisite();
+}
+
+/*!
+ * \internal
+ * Generates the CMake (\a description) requisites
+ */
+void DocBookGenerator::generateCMakeRequisite(const QStringList &values)
+{
+ const QString description("CMake");
+ generateStartRequisite(description);
+ m_writer->writeCharacters(values.first());
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters(values.last());
+ generateEndRequisite();
+}
+
+void DocBookGenerator::generateSortedNames(const ClassNode *cn, const QList<RelatedClass> &rc)
+{
+ // From Generator::appendSortedNames.
+ QMap<QString, ClassNode *> classMap;
+ QList<RelatedClass>::ConstIterator r = rc.constBegin();
+ while (r != rc.constEnd()) {
+ ClassNode *rcn = (*r).m_node;
+ if (rcn && rcn->access() == Access::Public && rcn->status() != Node::Internal
+ && !rcn->doc().isEmpty()) {
+ classMap[rcn->plainFullName(cn).toLower()] = rcn;
+ }
+ ++r;
+ }
+
+ QStringList classNames = classMap.keys();
+ classNames.sort();
+
+ int index = 0;
+ for (const QString &className : classNames) {
+ generateFullName(classMap.value(className), cn);
+ m_writer->writeCharacters(Utilities::comma(index++, classNames.size()));
+ }
+}
+
+void DocBookGenerator::generateSortedQmlNames(const Node *base, const NodeList &subs)
+{
+ // From Generator::appendSortedQmlNames.
+ QMap<QString, Node *> classMap;
+
+ for (auto sub : subs)
+ classMap[sub->plainFullName(base).toLower()] = sub;
+
+ QStringList names = classMap.keys();
+ names.sort();
+
+ int index = 0;
+ for (const QString &name : names) {
+ generateFullName(classMap.value(name), base);
+ m_writer->writeCharacters(Utilities::comma(index++, names.size()));
+ }
+}
+
+/*!
+ Lists the required imports and includes.
+*/
+void DocBookGenerator::generateRequisites(const Aggregate *aggregate)
+{
+ // Adapted from HtmlGenerator::generateRequisites, but simplified: no need to store all the
+ // elements, they can be produced one by one.
+
+ // Generate the requisites first separately: if some of them are generated, output them in a wrapper.
+ // This complexity is required to ensure the DocBook file is valid: an empty list is not valid. It is not easy
+ // to write a truly comprehensive condition.
+ QXmlStreamWriter* oldWriter = m_writer;
+ QString output;
+ m_writer = new QXmlStreamWriter(&output);
+
+ // Includes.
+ if (aggregate->includeFile()) generateRequisite("Header", *aggregate->includeFile());
+
+ // Since and project.
+ if (!aggregate->since().isEmpty())
+ generateRequisite("Since", formatSince(aggregate));
+
+ if (aggregate->isClassNode() || aggregate->isNamespace()) {
+ // CMake and QT variable.
+ const CollectionNode *cn =
+ m_qdb->getCollectionNode(aggregate->physicalModuleName(), Node::Module);
+ if (cn && !cn->qtCMakeComponent().isEmpty()) {
+ const QString qtComponent = "Qt" + QString::number(QT_VERSION_MAJOR);
+ const QString findpackageText = "find_package(" + qtComponent
+ + " REQUIRED COMPONENTS " + cn->qtCMakeComponent() + ")";
+ const QString targetItem =
+ cn->qtCMakeTargetItem().isEmpty() ? cn->qtCMakeComponent() : cn->qtCMakeTargetItem();
+ const QString targetLinkLibrariesText = "target_link_libraries(mytarget PRIVATE "
+ + qtComponent + "::" + targetItem + ")";
+ const QStringList cmakeInfo { findpackageText, targetLinkLibrariesText };
+ generateCMakeRequisite(cmakeInfo);
+ }
+ if (cn && !cn->qtVariable().isEmpty())
+ generateRequisite("qmake", "QT += " + cn->qtVariable());
+ }
+
+ if (aggregate->nodeType() == Node::Class) {
+ // Native type information.
+ auto *classe = const_cast<ClassNode *>(static_cast<const ClassNode *>(aggregate));
+ if (classe && classe->isQmlNativeType() && classe->status() != Node::Internal) {
+ generateStartRequisite("In QML");
+
+ qsizetype idx{0};
+ QList<QmlTypeNode *> nativeTypes { classe->qmlNativeTypes().cbegin(), classe->qmlNativeTypes().cend()};
+ std::sort(nativeTypes.begin(), nativeTypes.end(), Node::nodeNameLessThan);
+
+ for (const auto &item : std::as_const(nativeTypes)) {
+ generateFullName(item, classe);
+ m_writer->writeCharacters(
+ Utilities::comma(idx++, nativeTypes.size()));
+ }
+ generateEndRequisite();
+ }
+
+ // Inherits.
+ QList<RelatedClass>::ConstIterator r;
+ if (!classe->baseClasses().isEmpty()) {
+ generateStartRequisite("Inherits");
+
+ r = classe->baseClasses().constBegin();
+ int index = 0;
+ while (r != classe->baseClasses().constEnd()) {
+ if ((*r).m_node) {
+ generateFullName((*r).m_node, classe);
+
+ if ((*r).m_access == Access::Protected)
+ m_writer->writeCharacters(" (protected)");
+ else if ((*r).m_access == Access::Private)
+ m_writer->writeCharacters(" (private)");
+ m_writer->writeCharacters(
+ Utilities::comma(index++, classe->baseClasses().size()));
+ }
+ ++r;
+ }
+
+ generateEndRequisite();
+ }
+
+ // Inherited by.
+ if (!classe->derivedClasses().isEmpty()) {
+ generateStartRequisite("Inherited By");
+ generateSortedNames(classe, classe->derivedClasses());
+ generateEndRequisite();
+ }
+ }
+
+ // Group.
+ if (!aggregate->groupNames().empty()) {
+ generateStartRequisite("Group");
+ generateGroupReferenceText(aggregate);
+ generateEndRequisite();
+ }
+
+ // Status.
+ if (auto status = formatStatus(aggregate, m_qdb); status)
+ generateRequisite("Status", status.value());
+
+ // Write the elements as a list if not empty.
+ delete m_writer;
+ m_writer = oldWriter;
+
+ if (!output.isEmpty()) {
+ // Namespaces are mangled in this output, because QXmlStreamWriter doesn't know about them. (Letting it know
+ // would imply generating the xmlns declaration one more time.)
+ static const QRegularExpression xmlTag(R"(<(/?)n\d+:)"); // Only for DocBook tags.
+ static const QRegularExpression xmlnsDocBookDefinition(R"( xmlns:n\d+=")" + QString{dbNamespace} + "\"");
+ static const QRegularExpression xmlnsXLinkDefinition(R"( xmlns:n\d+=")" + QString{xlinkNamespace} + "\"");
+ static const QRegularExpression xmlAttr(R"( n\d+:)"); // Only for XLink attributes.
+ // Space at the beginning!
+ const QString cleanOutput = output.replace(xmlTag, R"(<\1db:)")
+ .replace(xmlnsDocBookDefinition, "")
+ .replace(xmlnsXLinkDefinition, "")
+ .replace(xmlAttr, " xlink:");
+
+ m_writer->writeStartElement(dbNamespace, "variablelist");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ newLine();
+
+ m_writer->device()->write(cleanOutput.toUtf8());
+
+ m_writer->writeEndElement(); // variablelist
+ newLine();
+ }
+}
+
+/*!
+ Lists the required imports and includes.
+*/
+void DocBookGenerator::generateQmlRequisites(const QmlTypeNode *qcn)
+{
+ // From HtmlGenerator::generateQmlRequisites, but simplified: no need to store all the elements,
+ // they can be produced one by one.
+ if (!qcn)
+ return;
+
+ const CollectionNode *collection = qcn->logicalModule();
+
+ NodeList subs;
+ QmlTypeNode::subclasses(qcn, subs);
+
+ QmlTypeNode *base = qcn->qmlBaseNode();
+ while (base && base->isInternal()) {
+ base = base->qmlBaseNode();
+ }
+
+ // Skip import statement for \internal collections
+ const bool generate_import_statement = !qcn->logicalModuleName().isEmpty() && (!collection || !collection->isInternal() || m_showInternal);
+ // Detect if anything is generated in this method. If not, exit early to avoid having an empty list.
+ const bool generates_something = generate_import_statement || !qcn->since().isEmpty() || !subs.isEmpty() || base;
+
+ if (!generates_something)
+ return;
+
+ // Start writing the elements as a list.
+ m_writer->writeStartElement(dbNamespace, "variablelist");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ newLine();
+
+ if (generate_import_statement) {
+ QStringList parts = QStringList() << "import" << qcn->logicalModuleName() << qcn->logicalModuleVersion();
+ generateRequisite("Import Statement", parts.join(' ').trimmed());
+ }
+
+ // Since and project.
+ if (!qcn->since().isEmpty())
+ generateRequisite("Since:", formatSince(qcn));
+
+ // Inherited by.
+ if (!subs.isEmpty()) {
+ generateStartRequisite("Inherited By:");
+ generateSortedQmlNames(qcn, subs);
+ generateEndRequisite();
+ }
+
+ // Inherits.
+ if (base) {
+ const Node *otherNode = nullptr;
+ Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(base));
+ QString link = getAutoLink(&a, qcn, &otherNode);
+
+ generateStartRequisite("Inherits:");
+ generateSimpleLink(link, base->name());
+ generateEndRequisite();
+ }
+
+ // Native type information.
+ ClassNode *cn = (const_cast<QmlTypeNode *>(qcn))->classNode();
+ if (cn && cn->isQmlNativeType() && cn->status() != Node::Internal) {
+ generateStartRequisite("In C++:");
+ generateSimpleLink(fullDocumentLocation(cn), cn->name());
+ generateEndRequisite();
+ }
+
+ // Group.
+ if (!qcn->groupNames().empty()) {
+ generateStartRequisite("Group");
+ generateGroupReferenceText(qcn);
+ generateEndRequisite();
+ }
+
+ // Status.
+ if (auto status = formatStatus(qcn, m_qdb); status)
+ generateRequisite("Status:", status.value());
+
+ m_writer->writeEndElement(); // variablelist
+ newLine();
+}
+
+bool DocBookGenerator::generateStatus(const Node *node)
+{
+ // From Generator::generateStatus.
+ switch (node->status()) {
+ case Node::Active:
+ // Output the module 'state' description if set.
+ if (node->isModule() || node->isQmlModule()) {
+ const QString &state = static_cast<const CollectionNode*>(node)->state();
+ if (!state.isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("This " + typeString(node) + " is in ");
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeCharacters(state);
+ m_writer->writeEndElement(); // emphasis
+ m_writer->writeCharacters(" state.");
+ m_writer->writeEndElement(); // para
+ newLine();
+ return true;
+ }
+ }
+ if (const auto version = node->deprecatedSince(); !version.isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("This " + typeString(node)
+ + " is scheduled for deprecation in version "
+ + version + ".");
+ m_writer->writeEndElement(); // para
+ newLine();
+ return true;
+ }
+ return false;
+ case Node::Preliminary:
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ m_writer->writeCharacters("This " + typeString(node)
+ + " is under development and is subject to change.");
+ m_writer->writeEndElement(); // emphasis
+ m_writer->writeEndElement(); // para
+ newLine();
+ return true;
+ case Node::Deprecated:
+ m_writer->writeStartElement(dbNamespace, "para");
+ if (node->isAggregate()) {
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ }
+ m_writer->writeCharacters("This " + typeString(node) + " is deprecated");
+ if (const QString &version = node->deprecatedSince(); !version.isEmpty()) {
+ m_writer->writeCharacters(" since ");
+ if (node->isQmlNode() && !node->logicalModuleName().isEmpty())
+ m_writer->writeCharacters(node->logicalModuleName() + " ");
+ m_writer->writeCharacters(version);
+ }
+ m_writer->writeCharacters(". We strongly advise against using it in new code.");
+ if (node->isAggregate())
+ m_writer->writeEndElement(); // emphasis
+ m_writer->writeEndElement(); // para
+ newLine();
+ return true;
+ case Node::Internal:
+ default:
+ return false;
+ }
+}
+
+/*!
+ Generate a list of function signatures. The function nodes
+ are in \a nodes.
+ */
+void DocBookGenerator::generateSignatureList(const NodeList &nodes)
+{
+ // From Generator::signatureList and Generator::appendSignature.
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+
+ NodeList::ConstIterator n = nodes.constBegin();
+ while (n != nodes.constEnd()) {
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+
+ generateSimpleLink(currentGenerator()->fullDocumentLocation(*n),
+ (*n)->signature(Node::SignaturePlain));
+
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+ ++n;
+ }
+
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+}
+
+/*!
+ * Return a string representing a text that exposes information about
+ * the groups that the \a node is part of.
+ */
+void DocBookGenerator::generateGroupReferenceText(const Node* node)
+{
+ // From HtmlGenerator::groupReferenceText
+
+ if (!node->isAggregate())
+ return;
+ const auto aggregate = static_cast<const Aggregate *>(node);
+
+ const QStringList &groups_names{aggregate->groupNames()};
+ if (!groups_names.empty()) {
+ m_writer->writeCharacters(aggregate->name() + " is part of ");
+ m_writer->writeStartElement(dbNamespace, "simplelist");
+
+ for (qsizetype index{0}; index < groups_names.size(); ++index) {
+ CollectionNode* group{m_qdb->groups()[groups_names[index]]};
+ m_qdb->mergeCollections(group);
+
+ m_writer->writeStartElement(dbNamespace, "member");
+ if (QString target{linkForNode(group, nullptr)}; !target.isEmpty())
+ generateSimpleLink(target, group->fullTitle());
+ else
+ m_writer->writeCharacters(group->name());
+ m_writer->writeEndElement(); // member
+ }
+
+ m_writer->writeEndElement(); // simplelist
+ newLine();
+ }
+}
+
+/*!
+ Generates text that explains how threadsafe and/or reentrant
+ \a node is.
+ */
+bool DocBookGenerator::generateThreadSafeness(const Node *node)
+{
+ // From Generator::generateThreadSafeness
+ Node::ThreadSafeness ts = node->threadSafeness();
+
+ const Node *reentrantNode;
+ Atom reentrantAtom = Atom(Atom::Link, "reentrant");
+ QString linkReentrant = getAutoLink(&reentrantAtom, node, &reentrantNode);
+ const Node *threadSafeNode;
+ Atom threadSafeAtom = Atom(Atom::Link, "thread-safe");
+ QString linkThreadSafe = getAutoLink(&threadSafeAtom, node, &threadSafeNode);
+
+ if (ts == Node::NonReentrant) {
+ m_writer->writeStartElement(dbNamespace, "warning");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("This " + typeString(node) + " is not ");
+ generateSimpleLink(linkReentrant, "reentrant");
+ m_writer->writeCharacters(".");
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // warning
+
+ return true;
+ } else if (ts == Node::Reentrant || ts == Node::ThreadSafe) {
+ m_writer->writeStartElement(dbNamespace, "note");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+
+ if (node->isAggregate()) {
+ m_writer->writeCharacters("All functions in this " + typeString(node) + " are ");
+ if (ts == Node::ThreadSafe)
+ generateSimpleLink(linkThreadSafe, "thread-safe");
+ else
+ generateSimpleLink(linkReentrant, "reentrant");
+
+ NodeList reentrant;
+ NodeList threadsafe;
+ NodeList nonreentrant;
+ bool exceptions = hasExceptions(node, reentrant, threadsafe, nonreentrant);
+ if (!exceptions || (ts == Node::Reentrant && !threadsafe.isEmpty())) {
+ m_writer->writeCharacters(".");
+ m_writer->writeEndElement(); // para
+ newLine();
+ } else {
+ m_writer->writeCharacters(" with the following exceptions:");
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+
+ if (ts == Node::Reentrant) {
+ if (!nonreentrant.isEmpty()) {
+ m_writer->writeCharacters("These functions are not ");
+ generateSimpleLink(linkReentrant, "reentrant");
+ m_writer->writeCharacters(":");
+ m_writer->writeEndElement(); // para
+ newLine();
+ generateSignatureList(nonreentrant);
+ }
+ if (!threadsafe.isEmpty()) {
+ m_writer->writeCharacters("These functions are also ");
+ generateSimpleLink(linkThreadSafe, "thread-safe");
+ m_writer->writeCharacters(":");
+ m_writer->writeEndElement(); // para
+ newLine();
+ generateSignatureList(threadsafe);
+ }
+ } else { // thread-safe
+ if (!reentrant.isEmpty()) {
+ m_writer->writeCharacters("These functions are only ");
+ generateSimpleLink(linkReentrant, "reentrant");
+ m_writer->writeCharacters(":");
+ m_writer->writeEndElement(); // para
+ newLine();
+ generateSignatureList(reentrant);
+ }
+ if (!nonreentrant.isEmpty()) {
+ m_writer->writeCharacters("These functions are not ");
+ generateSimpleLink(linkReentrant, "reentrant");
+ m_writer->writeCharacters(":");
+ m_writer->writeEndElement(); // para
+ newLine();
+ generateSignatureList(nonreentrant);
+ }
+ }
+ }
+ } else {
+ m_writer->writeCharacters("This " + typeString(node) + " is ");
+ if (ts == Node::ThreadSafe)
+ generateSimpleLink(linkThreadSafe, "thread-safe");
+ else
+ generateSimpleLink(linkReentrant, "reentrant");
+ m_writer->writeCharacters(".");
+ m_writer->writeEndElement(); // para
+ newLine();
+ }
+ m_writer->writeEndElement(); // note
+ newLine();
+
+ return true;
+ }
+
+ return false;
+}
+
+/*!
+ Generate the body of the documentation from the qdoc comment
+ found with the entity represented by the \a node.
+ */
+void DocBookGenerator::generateBody(const Node *node)
+{
+ // From Generator::generateBody, without warnings.
+ const FunctionNode *fn = node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr;
+
+ if (!node->hasDoc()) {
+ /*
+ Test for special function, like a destructor or copy constructor,
+ that has no documentation.
+ */
+ if (fn) {
+ QString t;
+ if (fn->isDtor()) {
+ t = "Destroys the instance of " + fn->parent()->name() + ".";
+ if (fn->isVirtual())
+ t += " The destructor is virtual.";
+ } else if (fn->isCtor()) {
+ t = "Default constructs an instance of " + fn->parent()->name() + ".";
+ } else if (fn->isCCtor()) {
+ t = "Copy constructor.";
+ } else if (fn->isMCtor()) {
+ t = "Move-copy constructor.";
+ } else if (fn->isCAssign()) {
+ t = "Copy-assignment constructor.";
+ } else if (fn->isMAssign()) {
+ t = "Move-assignment constructor.";
+ }
+
+ if (!t.isEmpty())
+ m_writer->writeTextElement(dbNamespace, "para", t);
+ }
+ } else if (!node->isSharingComment()) {
+ // Reimplements clause and type alias info precede body text
+ if (fn && !fn->overridesThis().isEmpty())
+ generateReimplementsClause(fn);
+ else if (node->isProperty()) {
+ if (static_cast<const PropertyNode *>(node)->propertyType() != PropertyNode::PropertyType::StandardProperty)
+ generateAddendum(node, BindableProperty, nullptr, false);
+ }
+
+ // Generate the body.
+ if (!generateText(node->doc().body(), node)) {
+ if (node->isMarkedReimp())
+ return;
+ }
+
+ // Output what is after the main body.
+ if (fn) {
+ if (fn->isQmlSignal())
+ generateAddendum(node, QmlSignalHandler, nullptr, true);
+ if (fn->isPrivateSignal())
+ generateAddendum(node, PrivateSignal, nullptr, true);
+ if (fn->isInvokable())
+ generateAddendum(node, Invokable, nullptr, true);
+ if (fn->hasAssociatedProperties())
+ generateAddendum(node, AssociatedProperties, nullptr, true);
+ }
+
+ // Warning generation skipped with respect to Generator::generateBody.
+ }
+
+ generateEnumValuesForQmlProperty(node, nullptr);
+ generateRequiredLinks(node);
+}
+
+/*!
+ Generates either a link to the project folder for example \a node, or a list
+ of links files/images if 'url.examples config' variable is not defined.
+
+ Does nothing for non-example nodes.
+*/
+void DocBookGenerator::generateRequiredLinks(const Node *node)
+{
+ // From Generator::generateRequiredLinks.
+ if (!node->isExample())
+ return;
+
+ const auto en = static_cast<const ExampleNode *>(node);
+ QString exampleUrl{Config::instance().get(CONFIG_URL + Config::dot + CONFIG_EXAMPLES).asString()};
+
+ if (exampleUrl.isEmpty()) {
+ if (!en->noAutoList()) {
+ generateFileList(en, false); // files
+ generateFileList(en, true); // images
+ }
+ } else {
+ generateLinkToExample(en, exampleUrl);
+ }
+}
+
+/*!
+ The path to the example replaces a placeholder '\1' character if
+ one is found in the \a baseUrl string. If no such placeholder is found,
+ the path is appended to \a baseUrl, after a '/' character if \a baseUrl did
+ not already end in one.
+*/
+void DocBookGenerator::generateLinkToExample(const ExampleNode *en, const QString &baseUrl)
+{
+ // From Generator::generateLinkToExample.
+ QString exampleUrl(baseUrl);
+ QString link;
+#ifndef QT_BOOTSTRAPPED
+ link = QUrl(exampleUrl).host();
+#endif
+ if (!link.isEmpty())
+ link.prepend(" @ ");
+ link.prepend("Example project");
+
+ const QLatin1Char separator('/');
+ const QLatin1Char placeholder('\1');
+ if (!exampleUrl.contains(placeholder)) {
+ if (!exampleUrl.endsWith(separator))
+ exampleUrl += separator;
+ exampleUrl += placeholder;
+ }
+
+ // Construct a path to the example; <install path>/<example name>
+ QStringList path = QStringList()
+ << Config::instance().get(CONFIG_EXAMPLESINSTALLPATH).asString() << en->name();
+ path.removeAll(QString());
+
+ // Write the link to the example. Typically, this link comes after sections, hence
+ // wrap it in a section too.
+ startSection("Example project");
+
+ m_writer->writeStartElement(dbNamespace, "para");
+ generateSimpleLink(exampleUrl.replace(placeholder, path.join(separator)), link);
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ endSection();
+}
+
+// TODO: [multi-purpose-function-with-flag][generate-file-list]
+
+/*!
+ This function is called when the documentation for an example is
+ being formatted. It outputs a list of files for the example, which
+ can be the example's source files or the list of images used by the
+ example. The images are copied into a subtree of
+ \c{...doc/html/images/used-in-examples/...}
+*/
+void DocBookGenerator::generateFileList(const ExampleNode *en, bool images)
+{
+ // TODO: [possibly-stale-duplicate-code][generator-insufficient-structural-abstraction]
+ // Review and compare this code with
+ // Generator::generateFileList.
+ // Some subtle changes that might be semantically equivalent are
+ // present between the two.
+ // Supposedly, this version is to be considered stale compared to
+ // Generator's one and it might be possible to remove it in favor
+ // of that as long as the difference in output are taken into consideration.
+
+ // From Generator::generateFileList
+ QString tag;
+ QStringList paths;
+ if (images) {
+ paths = en->images();
+ tag = "Images:";
+ } else { // files
+ paths = en->files();
+ tag = "Files:";
+ }
+ std::sort(paths.begin(), paths.end(), Generator::comparePaths);
+
+ if (paths.isEmpty())
+ return;
+
+ startSection("", "List of Files");
+
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters(tag);
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ startSection("List of Files");
+
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ newLine();
+
+ for (const auto &path : std::as_const(paths)) {
+ auto maybe_resolved_file{file_resolver.resolve(path)};
+ if (!maybe_resolved_file) {
+ // TODO: [uncentralized-admonition][failed-resolve-file]
+ QString details = std::transform_reduce(
+ file_resolver.get_search_directories().cbegin(),
+ file_resolver.get_search_directories().cend(),
+ u"Searched directories:"_s,
+ std::plus(),
+ [](const DirectoryPath &directory_path) -> QString { return u' ' + directory_path.value(); }
+ );
+
+ en->location().warning(u"Cannot find file to quote from: %1"_s.arg(path), details);
+
+ continue;
+ }
+
+ auto file{*maybe_resolved_file};
+ if (images) addImageToCopy(en, file);
+ else generateExampleFilePage(en, file);
+
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ generateSimpleLink(file.get_query(), file.get_query());
+ m_writer->writeEndElement(); // para
+ m_writer->writeEndElement(); // listitem
+ newLine();
+ }
+
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+
+ endSection();
+}
+
+/*!
+ Generate a file with the contents of a C++ or QML source file.
+ */
+void DocBookGenerator::generateExampleFilePage(const Node *node, ResolvedFile resolved_file, CodeMarker*)
+{
+ // TODO: [generator-insufficient-structural-abstraction]
+
+ // From HtmlGenerator::generateExampleFilePage.
+ if (!node->isExample())
+ return;
+
+ // TODO: Understand if this is safe.
+ const auto en = static_cast<const ExampleNode *>(node);
+
+ // Store current (active) writer
+ QXmlStreamWriter *currentWriter = m_writer;
+ m_writer = startDocument(en, resolved_file.get_query());
+ generateHeader(en->fullTitle(), en->subtitle(), en);
+
+ Text text;
+ Quoter quoter;
+ Doc::quoteFromFile(en->doc().location(), quoter, resolved_file);
+ QString code = quoter.quoteTo(en->location(), QString(), QString());
+ CodeMarker *codeMarker = CodeMarker::markerForFileName(resolved_file.get_path());
+ text << Atom(codeMarker->atomType(), code);
+ Atom a(codeMarker->atomType(), code);
+ generateText(text, en);
+
+ endDocument(); // Delete m_writer.
+ m_writer = currentWriter; // Restore writer.
+}
+
+void DocBookGenerator::generateReimplementsClause(const FunctionNode *fn)
+{
+ // From Generator::generateReimplementsClause, without warning generation.
+ if (fn->overridesThis().isEmpty() || !fn->parent()->isClassNode())
+ return;
+
+ auto cn = static_cast<ClassNode *>(fn->parent());
+
+ if (const FunctionNode *overrides = cn->findOverriddenFunction(fn);
+ overrides && !overrides->isPrivate() && !overrides->parent()->isPrivate()) {
+ if (overrides->hasDoc()) {
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("Reimplements: ");
+ QString fullName =
+ overrides->parent()->name() + "::" + overrides->signature(Node::SignaturePlain);
+ generateFullName(overrides->parent(), fullName, overrides);
+ m_writer->writeCharacters(".");
+ m_writer->writeEndElement(); // para
+ newLine();
+ return;
+ }
+ }
+
+ if (const PropertyNode *sameName = cn->findOverriddenProperty(fn); sameName && sameName->hasDoc()) {
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("Reimplements an access function for property: ");
+ QString fullName = sameName->parent()->name() + "::" + sameName->name();
+ generateFullName(sameName->parent(), fullName, sameName);
+ m_writer->writeCharacters(".");
+ m_writer->writeEndElement(); // para
+ newLine();
+ return;
+ }
+}
+
+void DocBookGenerator::generateAlsoList(const Node *node)
+{
+ // From Generator::generateAlsoList.
+ QList<Text> alsoList = node->doc().alsoList();
+ supplementAlsoList(node, alsoList);
+
+ if (!alsoList.isEmpty()) {
+ startSection("See Also");
+
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeCharacters("See also ");
+ m_writer->writeEndElement(); // emphasis
+ newLine();
+
+ m_writer->writeStartElement(dbNamespace, "simplelist");
+ m_writer->writeAttribute("type", "vert");
+ m_writer->writeAttribute("role", "see-also");
+ newLine();
+
+ for (const Text &text : alsoList) {
+ m_writer->writeStartElement(dbNamespace, "member");
+ generateText(text, node);
+ m_writer->writeEndElement(); // member
+ newLine();
+ }
+
+ m_writer->writeEndElement(); // simplelist
+ newLine();
+
+ m_writer->writeEndElement(); // para
+ newLine();
+
+ endSection();
+ }
+}
+
+/*!
+ Open a new file to write XML contents, including the DocBook
+ opening tag.
+ */
+QXmlStreamWriter *DocBookGenerator::startGenericDocument(const Node *node, const QString &fileName)
+{
+ QFile *outFile = openSubPageFile(node, fileName);
+ m_writer = new QXmlStreamWriter(outFile);
+ m_writer->setAutoFormatting(false); // We need a precise handling of line feeds.
+
+ m_writer->writeStartDocument();
+ newLine();
+ m_writer->writeNamespace(dbNamespace, "db");
+ m_writer->writeNamespace(xlinkNamespace, "xlink");
+ if (m_useITS)
+ m_writer->writeNamespace(itsNamespace, "its");
+ m_writer->writeStartElement(dbNamespace, "article");
+ m_writer->writeAttribute("version", "5.2");
+ if (!m_naturalLanguage.isEmpty())
+ m_writer->writeAttribute("xml:lang", m_naturalLanguage);
+ newLine();
+
+ // Reset the state for the new document.
+ sectionLevels.resize(0);
+ m_inPara = false;
+ m_inList = 0;
+
+ return m_writer;
+}
+
+QXmlStreamWriter *DocBookGenerator::startDocument(const Node *node)
+{
+ m_hasSection = false;
+ refMap.clear();
+
+ QString fileName = Generator::fileName(node, fileExtension());
+ return startGenericDocument(node, fileName);
+}
+
+QXmlStreamWriter *DocBookGenerator::startDocument(const ExampleNode *en, const QString &file)
+{
+ m_hasSection = false;
+
+ QString fileName = linkForExampleFile(file);
+ return startGenericDocument(en, fileName);
+}
+
+void DocBookGenerator::endDocument()
+{
+ m_writer->writeEndElement(); // article
+ m_writer->writeEndDocument();
+
+ m_writer->device()->close();
+ delete m_writer->device();
+ delete m_writer;
+ m_writer = nullptr;
+}
+
+/*!
+ Generate a reference page for the C++ class, namespace, or
+ header file documented in \a node.
+ */
+void DocBookGenerator::generateCppReferencePage(Node *node)
+{
+ // Based on HtmlGenerator::generateCppReferencePage.
+ Q_ASSERT(node->isAggregate());
+ const auto aggregate = static_cast<const Aggregate *>(node);
+
+ QString title;
+ QString rawTitle;
+ QString fullTitle;
+ if (aggregate->isNamespace()) {
+ rawTitle = aggregate->plainName();
+ fullTitle = aggregate->plainFullName();
+ title = rawTitle + " Namespace";
+ } else if (aggregate->isClass()) {
+ rawTitle = aggregate->plainName();
+
+ auto templateDecl = node->templateDecl();
+ if (templateDecl)
+ fullTitle = QString("%1 %2 ").arg((*templateDecl).to_qstring(), aggregate->typeWord(false));
+
+ fullTitle += aggregate->plainFullName();
+ title = rawTitle + QLatin1Char(' ') + aggregate->typeWord(true);
+ } else if (aggregate->isHeader()) {
+ title = fullTitle = rawTitle = aggregate->fullTitle();
+ }
+
+ QString subtitleText;
+ if (rawTitle != fullTitle)
+ subtitleText = fullTitle;
+
+ // Start producing the DocBook file.
+ m_writer = startDocument(node);
+
+ // Info container.
+ generateHeader(title, subtitleText, aggregate);
+
+ generateRequisites(aggregate);
+ generateStatus(aggregate);
+
+ // Element synopsis.
+ generateDocBookSynopsis(node);
+
+ // Actual content.
+ if (!aggregate->doc().isEmpty()) {
+ startSection("details", "Detailed Description");
+
+ generateBody(aggregate);
+ generateAlsoList(aggregate);
+
+ endSection();
+ }
+
+ Sections sections(const_cast<Aggregate *>(aggregate));
+ SectionVector sectionVector =
+ (aggregate->isNamespace() || aggregate->isHeader()) ?
+ sections.stdDetailsSections() :
+ sections.stdCppClassDetailsSections();
+ for (const Section &section : sectionVector) {
+ if (section.members().isEmpty())
+ continue;
+
+ startSection(section.title().toLower(), section.title());
+
+ for (const Node *member : section.members()) {
+ if (member->access() == Access::Private) // ### check necessary?
+ continue;
+
+ if (member->nodeType() != Node::Class) {
+ // This function starts its own section.
+ generateDetailedMember(member, aggregate);
+ } else {
+ startSectionBegin();
+ m_writer->writeCharacters("class ");
+ generateFullName(member, aggregate);
+ startSectionEnd();
+
+ generateBrief(member);
+
+ endSection();
+ }
+ }
+
+ endSection();
+ }
+
+ generateObsoleteMembers(sections);
+
+ endDocument();
+}
+
+void DocBookGenerator::generateSynopsisInfo(const QString &key, const QString &value)
+{
+ m_writer->writeStartElement(dbNamespace, "synopsisinfo");
+ m_writer->writeAttribute("role", key);
+ m_writer->writeCharacters(value);
+ m_writer->writeEndElement(); // synopsisinfo
+ newLine();
+}
+
+void DocBookGenerator::generateModifier(const QString &value)
+{
+ m_writer->writeTextElement(dbNamespace, "modifier", value);
+ newLine();
+}
+
+/*!
+ Generate the metadata for the given \a node in DocBook.
+ */
+void DocBookGenerator::generateDocBookSynopsis(const Node *node)
+{
+ if (!node)
+ return;
+
+ // From Generator::generateStatus, HtmlGenerator::generateRequisites,
+ // Generator::generateThreadSafeness, QDocIndexFiles::generateIndexSection.
+
+ // This function is the major place where DocBook extensions are used.
+ if (!m_useDocBook52)
+ return;
+
+ // Nothing to export in some cases. Note that isSharedCommentNode() returns
+ // true also for QML property groups.
+ if (node->isGroup() || node->isSharedCommentNode() || node->isModule() || node->isQmlModule() || node->isPageNode())
+ return;
+
+ // Cast the node to several subtypes (null pointer if the node is not of the required type).
+ const Aggregate *aggregate =
+ node->isAggregate() ? static_cast<const Aggregate *>(node) : nullptr;
+ const ClassNode *classNode = node->isClass() ? static_cast<const ClassNode *>(node) : nullptr;
+ const FunctionNode *functionNode =
+ node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr;
+ const PropertyNode *propertyNode =
+ node->isProperty() ? static_cast<const PropertyNode *>(node) : nullptr;
+ const VariableNode *variableNode =
+ node->isVariable() ? static_cast<const VariableNode *>(node) : nullptr;
+ const EnumNode *enumNode = node->isEnumType() ? static_cast<const EnumNode *>(node) : nullptr;
+ const QmlPropertyNode *qpn =
+ node->isQmlProperty() ? static_cast<const QmlPropertyNode *>(node) : nullptr;
+ const QmlTypeNode *qcn = node->isQmlType() ? static_cast<const QmlTypeNode *>(node) : nullptr;
+ // Typedefs are ignored, as they correspond to enums.
+ // Groups and modules are ignored.
+ // Documents are ignored, they have no interesting metadata.
+
+ // Start the synopsis tag.
+ QString synopsisTag = nodeToSynopsisTag(node);
+ m_writer->writeStartElement(dbNamespace, synopsisTag);
+ newLine();
+
+ // Name and basic properties of each tag (like types and parameters).
+ if (node->isClass()) {
+ m_writer->writeStartElement(dbNamespace, "ooclass");
+ m_writer->writeTextElement(dbNamespace, "classname", node->plainName());
+ m_writer->writeEndElement(); // ooclass
+ newLine();
+ } else if (node->isNamespace()) {
+ m_writer->writeTextElement(dbNamespace, "namespacename", node->plainName());
+ newLine();
+ } else if (node->isQmlType()) {
+ m_writer->writeStartElement(dbNamespace, "ooclass");
+ m_writer->writeTextElement(dbNamespace, "classname", node->plainName());
+ m_writer->writeEndElement(); // ooclass
+ newLine();
+ if (!qcn->groupNames().isEmpty())
+ m_writer->writeAttribute("groups", qcn->groupNames().join(QLatin1Char(',')));
+ } else if (node->isProperty()) {
+ m_writer->writeTextElement(dbNamespace, "modifier", "(Qt property)");
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "type", propertyNode->dataType());
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "varname", node->plainName());
+ newLine();
+ } else if (node->isVariable()) {
+ if (variableNode->isStatic()) {
+ m_writer->writeTextElement(dbNamespace, "modifier", "static");
+ newLine();
+ }
+ m_writer->writeTextElement(dbNamespace, "type", variableNode->dataType());
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "varname", node->plainName());
+ newLine();
+ } else if (node->isEnumType()) {
+ m_writer->writeTextElement(dbNamespace, "enumname", node->plainName());
+ newLine();
+ } else if (node->isQmlProperty()) {
+ QString name = node->name();
+ if (qpn->isAttached())
+ name.prepend(qpn->element() + QLatin1Char('.'));
+
+ m_writer->writeTextElement(dbNamespace, "type", qpn->dataType());
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "varname", name);
+ newLine();
+
+ if (qpn->isAttached()) {
+ m_writer->writeTextElement(dbNamespace, "modifier", "attached");
+ newLine();
+ }
+ if (!(const_cast<QmlPropertyNode *>(qpn))->isReadOnly()) {
+ m_writer->writeTextElement(dbNamespace, "modifier", "writable");
+ newLine();
+ }
+ if ((const_cast<QmlPropertyNode *>(qpn))->isRequired()) {
+ m_writer->writeTextElement(dbNamespace, "modifier", "required");
+ newLine();
+ }
+ if (qpn->isReadOnly()) {
+ generateModifier("[read-only]");
+ newLine();
+ }
+ if (qpn->isDefault()) {
+ generateModifier("[default]");
+ newLine();
+ }
+ } else if (node->isFunction()) {
+ if (functionNode->virtualness() != "non")
+ generateModifier("virtual");
+ if (functionNode->isConst())
+ generateModifier("const");
+ if (functionNode->isStatic())
+ generateModifier("static");
+
+ if (!functionNode->isMacro() && !functionNode->isCtor() &&
+ !functionNode->isCCtor() && !functionNode->isMCtor()
+ && !functionNode->isDtor()) {
+ if (functionNode->returnType() == "void")
+ m_writer->writeEmptyElement(dbNamespace, "void");
+ else
+ m_writer->writeTextElement(dbNamespace, "type", functionNode->returnType());
+ newLine();
+ }
+ // Remove two characters from the plain name to only get the name
+ // of the method without parentheses (only for functions, not macros).
+ QString name = node->plainName();
+ if (name.endsWith("()"))
+ name.chop(2);
+ m_writer->writeTextElement(dbNamespace, "methodname", name);
+ newLine();
+
+ if (functionNode->parameters().isEmpty()) {
+ m_writer->writeEmptyElement(dbNamespace, "void");
+ newLine();
+ }
+
+ const Parameters &lp = functionNode->parameters();
+ for (int i = 0; i < lp.count(); ++i) {
+ const Parameter &parameter = lp.at(i);
+ m_writer->writeStartElement(dbNamespace, "methodparam");
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "type", parameter.type());
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "parameter", parameter.name());
+ newLine();
+ if (!parameter.defaultValue().isEmpty()) {
+ m_writer->writeTextElement(dbNamespace, "initializer", parameter.defaultValue());
+ newLine();
+ }
+ m_writer->writeEndElement(); // methodparam
+ newLine();
+ }
+
+ if (functionNode->isDefault())
+ generateModifier("default");
+ if (functionNode->isFinal())
+ generateModifier("final");
+ if (functionNode->isOverride())
+ generateModifier("override");
+ } else if (node->isTypedef()) {
+ m_writer->writeTextElement(dbNamespace, "typedefname", node->plainName());
+ newLine();
+ } else {
+ node->doc().location().warning(
+ QStringLiteral("Unexpected node type in generateDocBookSynopsis: %1")
+ .arg(node->nodeTypeString()));
+ newLine();
+ }
+
+ // Enums and typedefs.
+ if (enumNode) {
+ for (const EnumItem &item : enumNode->items()) {
+ m_writer->writeStartElement(dbNamespace, "enumitem");
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "enumidentifier", item.name());
+ newLine();
+ m_writer->writeTextElement(dbNamespace, "enumvalue", item.value());
+ newLine();
+ m_writer->writeEndElement(); // enumitem
+ newLine();
+ }
+
+ if (enumNode->items().isEmpty()) {
+ // If the enumeration is empty (really rare case), still produce
+ // something for the DocBook document to be valid.
+ m_writer->writeStartElement(dbNamespace, "enumitem");
+ newLine();
+ m_writer->writeEmptyElement(dbNamespace, "enumidentifier");
+ newLine();
+ m_writer->writeEndElement(); // enumitem
+ newLine();
+ }
+ }
+
+ // Below: only synopsisinfo within synopsisTag. These elements must be at
+ // the end of the tag, as per DocBook grammar.
+
+ // Information for functions that could not be output previously
+ // (synopsisinfo).
+ if (node->isFunction()) {
+ generateSynopsisInfo("meta", functionNode->metanessString());
+
+ if (functionNode->isOverload()) {
+ generateSynopsisInfo("overload", "overload");
+ generateSynopsisInfo("overload-number",
+ QString::number(functionNode->overloadNumber()));
+ }
+
+ if (functionNode->isRef())
+ generateSynopsisInfo("refness", QString::number(1));
+ else if (functionNode->isRefRef())
+ generateSynopsisInfo("refness", QString::number(2));
+
+ if (functionNode->hasAssociatedProperties()) {
+ QStringList associatedProperties;
+ const auto &nodes = functionNode->associatedProperties();
+ for (const Node *n : nodes) {
+ const auto pn = static_cast<const PropertyNode *>(n);
+ associatedProperties << pn->name();
+ }
+ associatedProperties.sort();
+ generateSynopsisInfo("associated-property",
+ associatedProperties.join(QLatin1Char(',')));
+ }
+
+ QString signature = functionNode->signature(Node::SignatureReturnType);
+ // 'const' is already part of FunctionNode::signature()
+ if (functionNode->isFinal())
+ signature += " final";
+ if (functionNode->isOverride())
+ signature += " override";
+ if (functionNode->isPureVirtual())
+ signature += " = 0";
+ else if (functionNode->isDefault())
+ signature += " = default";
+ generateSynopsisInfo("signature", signature);
+ }
+
+ // Accessibility status.
+ if (!node->isPageNode() && !node->isCollectionNode()) {
+ switch (node->access()) {
+ case Access::Public:
+ generateSynopsisInfo("access", "public");
+ break;
+ case Access::Protected:
+ generateSynopsisInfo("access", "protected");
+ break;
+ case Access::Private:
+ generateSynopsisInfo("access", "private");
+ break;
+ default:
+ break;
+ }
+ if (node->isAbstract())
+ generateSynopsisInfo("abstract", "true");
+ }
+
+ // Status.
+ switch (node->status()) {
+ case Node::Active:
+ generateSynopsisInfo("status", "active");
+ break;
+ case Node::Preliminary:
+ generateSynopsisInfo("status", "preliminary");
+ break;
+ case Node::Deprecated:
+ generateSynopsisInfo("status", "deprecated");
+ break;
+ case Node::Internal:
+ generateSynopsisInfo("status", "internal");
+ break;
+ default:
+ generateSynopsisInfo("status", "main");
+ break;
+ }
+
+ // C++ classes and name spaces.
+ if (aggregate) {
+ // Includes.
+ if (aggregate->includeFile()) generateSynopsisInfo("headers", *aggregate->includeFile());
+
+ // Since and project.
+ if (!aggregate->since().isEmpty())
+ generateSynopsisInfo("since", formatSince(aggregate));
+
+ if (aggregate->nodeType() == Node::Class || aggregate->nodeType() == Node::Namespace) {
+ // CMake and QT variable.
+ if (!aggregate->physicalModuleName().isEmpty()) {
+ const CollectionNode *cn =
+ m_qdb->getCollectionNode(aggregate->physicalModuleName(), Node::Module);
+ if (cn && !cn->qtCMakeComponent().isEmpty()) {
+ const QString qtComponent = "Qt" + QString::number(QT_VERSION_MAJOR);
+ const QString findpackageText = "find_package(" + qtComponent
+ + " REQUIRED COMPONENTS " + cn->qtCMakeComponent() + ")";
+ const QString targetLinkLibrariesText =
+ "target_link_libraries(mytarget PRIVATE " + qtComponent + "::" + cn->qtCMakeComponent()
+ + ")";
+ generateSynopsisInfo("cmake-find-package", findpackageText);
+ generateSynopsisInfo("cmake-target-link-libraries", targetLinkLibrariesText);
+ }
+ if (cn && !cn->qtVariable().isEmpty())
+ generateSynopsisInfo("qmake", "QT += " + cn->qtVariable());
+ }
+ }
+
+ if (aggregate->nodeType() == Node::Class) {
+ // Native type
+ auto *classe = const_cast<ClassNode *>(static_cast<const ClassNode *>(aggregate));
+ if (classe && classe->isQmlNativeType() && classe->status() != Node::Internal) {
+ m_writer->writeStartElement(dbNamespace, "synopsisinfo");
+ m_writer->writeAttribute("role", "nativeTypeFor");
+
+ QList<QmlTypeNode *> nativeTypes { classe->qmlNativeTypes().cbegin(), classe->qmlNativeTypes().cend()};
+ std::sort(nativeTypes.begin(), nativeTypes.end(), Node::nodeNameLessThan);
+
+ for (auto item : std::as_const(nativeTypes)) {
+ const Node *otherNode{nullptr};
+ Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(item));
+ const QString &link = getAutoLink(&a, aggregate, &otherNode);
+ generateSimpleLink(link, item->name());
+ }
+
+ m_writer->writeEndElement(); // synopsisinfo
+ }
+
+ // Inherits.
+ QList<RelatedClass>::ConstIterator r;
+ if (!classe->baseClasses().isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "synopsisinfo");
+ m_writer->writeAttribute("role", "inherits");
+
+ r = classe->baseClasses().constBegin();
+ int index = 0;
+ while (r != classe->baseClasses().constEnd()) {
+ if ((*r).m_node) {
+ generateFullName((*r).m_node, classe);
+
+ if ((*r).m_access == Access::Protected) {
+ m_writer->writeCharacters(" (protected)");
+ } else if ((*r).m_access == Access::Private) {
+ m_writer->writeCharacters(" (private)");
+ }
+ m_writer->writeCharacters(
+ Utilities::comma(index++, classe->baseClasses().size()));
+ }
+ ++r;
+ }
+
+ m_writer->writeEndElement(); // synopsisinfo
+ newLine();
+ }
+
+ // Inherited by.
+ if (!classe->derivedClasses().isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "synopsisinfo");
+ m_writer->writeAttribute("role", "inheritedBy");
+ generateSortedNames(classe, classe->derivedClasses());
+ m_writer->writeEndElement(); // synopsisinfo
+ newLine();
+ }
+ }
+ }
+
+ // QML types.
+ if (qcn) {
+ // Module name and version (i.e. import).
+ QString logicalModuleVersion;
+ const CollectionNode *collection =
+ m_qdb->getCollectionNode(qcn->logicalModuleName(), qcn->nodeType());
+ if (collection)
+ logicalModuleVersion = collection->logicalModuleVersion();
+ else
+ logicalModuleVersion = qcn->logicalModuleVersion();
+
+ QStringList importText;
+ importText << "import " + qcn->logicalModuleName();
+ if (!logicalModuleVersion.isEmpty())
+ importText << logicalModuleVersion;
+ generateSynopsisInfo("import", importText.join(' '));
+
+ // Since and project.
+ if (!qcn->since().isEmpty())
+ generateSynopsisInfo("since", formatSince(qcn));
+
+ // Inherited by.
+ NodeList subs;
+ QmlTypeNode::subclasses(qcn, subs);
+ if (!subs.isEmpty()) {
+ m_writer->writeTextElement(dbNamespace, "synopsisinfo");
+ m_writer->writeAttribute("role", "inheritedBy");
+ generateSortedQmlNames(qcn, subs);
+ m_writer->writeEndElement(); // synopsisinfo
+ newLine();
+ }
+
+ // Inherits.
+ QmlTypeNode *base = qcn->qmlBaseNode();
+ while (base && base->isInternal())
+ base = base->qmlBaseNode();
+ if (base) {
+ const Node *otherNode = nullptr;
+ Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(base));
+ QString link = getAutoLink(&a, base, &otherNode);
+
+ m_writer->writeTextElement(dbNamespace, "synopsisinfo");
+ m_writer->writeAttribute("role", "inherits");
+ generateSimpleLink(link, base->name());
+ m_writer->writeEndElement(); // synopsisinfo
+ newLine();
+ }
+
+ // Native type
+ ClassNode *cn = (const_cast<QmlTypeNode *>(qcn))->classNode();
+
+ if (cn && cn->isQmlNativeType() && (cn->status() != Node::Internal)) {
+ const Node *otherNode = nullptr;
+ Atom a = Atom(Atom::LinkNode, CodeMarker::stringForNode(qcn));
+ QString link = getAutoLink(&a, cn, &otherNode);
+
+ m_writer->writeTextElement(dbNamespace, "synopsisinfo");
+ m_writer->writeAttribute("role", "nativeType");
+ generateSimpleLink(link, cn->name());
+ m_writer->writeEndElement(); // synopsisinfo
+ newLine();
+ }
+ }
+
+ // Thread safeness.
+ switch (node->threadSafeness()) {
+ case Node::UnspecifiedSafeness:
+ generateSynopsisInfo("threadsafeness", "unspecified");
+ break;
+ case Node::NonReentrant:
+ generateSynopsisInfo("threadsafeness", "non-reentrant");
+ break;
+ case Node::Reentrant:
+ generateSynopsisInfo("threadsafeness", "reentrant");
+ break;
+ case Node::ThreadSafe:
+ generateSynopsisInfo("threadsafeness", "thread safe");
+ break;
+ default:
+ generateSynopsisInfo("threadsafeness", "unspecified");
+ break;
+ }
+
+ // Module.
+ if (!node->physicalModuleName().isEmpty())
+ generateSynopsisInfo("module", node->physicalModuleName());
+
+ // Group.
+ if (classNode && !classNode->groupNames().isEmpty()) {
+ generateSynopsisInfo("groups", classNode->groupNames().join(QLatin1Char(',')));
+ } else if (qcn && !qcn->groupNames().isEmpty()) {
+ generateSynopsisInfo("groups", qcn->groupNames().join(QLatin1Char(',')));
+ }
+
+ // Properties.
+ if (propertyNode) {
+ for (const Node *fnNode : propertyNode->getters()) {
+ if (fnNode) {
+ const auto funcNode = static_cast<const FunctionNode *>(fnNode);
+ generateSynopsisInfo("getter", funcNode->name());
+ }
+ }
+ for (const Node *fnNode : propertyNode->setters()) {
+ if (fnNode) {
+ const auto funcNode = static_cast<const FunctionNode *>(fnNode);
+ generateSynopsisInfo("setter", funcNode->name());
+ }
+ }
+ for (const Node *fnNode : propertyNode->resetters()) {
+ if (fnNode) {
+ const auto funcNode = static_cast<const FunctionNode *>(fnNode);
+ generateSynopsisInfo("resetter", funcNode->name());
+ }
+ }
+ for (const Node *fnNode : propertyNode->notifiers()) {
+ if (fnNode) {
+ const auto funcNode = static_cast<const FunctionNode *>(fnNode);
+ generateSynopsisInfo("notifier", funcNode->name());
+ }
+ }
+ }
+
+ m_writer->writeEndElement(); // nodeToSynopsisTag (like classsynopsis)
+ newLine();
+
+ // The typedef associated to this enum. It is output *after* the main tag,
+ // i.e. it must be after the synopsisinfo.
+ if (enumNode && enumNode->flagsType()) {
+ m_writer->writeStartElement(dbNamespace, "typedefsynopsis");
+ newLine();
+
+ m_writer->writeTextElement(dbNamespace, "typedefname",
+ enumNode->flagsType()->fullDocumentName());
+ newLine();
+
+ m_writer->writeEndElement(); // typedefsynopsis
+ newLine();
+ }
+}
+
+QString taggedNode(const Node *node)
+{
+ // From CodeMarker::taggedNode, but without the tag part (i.e. only the QML specific case
+ // remaining).
+ // TODO: find a better name for this.
+ if (node->nodeType() == Node::QmlType && node->name().startsWith(QLatin1String("QML:")))
+ return node->name().mid(4);
+ return node->name();
+}
+
+/*!
+ Parses a string with method/variable name and (return) type
+ to include type tags.
+ */
+void DocBookGenerator::typified(const QString &string, const Node *relative, bool trailingSpace,
+ bool generateType)
+{
+ // Adapted from CodeMarker::typified and HtmlGenerator::highlightedCode.
+ // Note: CppCodeMarker::markedUpIncludes is not needed for DocBook, as this part is natively
+ // generated as DocBook. Hence, there is no need to reimplement <@headerfile> from
+ // HtmlGenerator::highlightedCode.
+ QString result;
+ QString pendingWord;
+
+ for (int i = 0; i <= string.size(); ++i) {
+ QChar ch;
+ if (i != string.size())
+ ch = string.at(i);
+
+ QChar lower = ch.toLower();
+ if ((lower >= QLatin1Char('a') && lower <= QLatin1Char('z')) || ch.digitValue() >= 0
+ || ch == QLatin1Char('_') || ch == QLatin1Char(':')) {
+ pendingWord += ch;
+ } else {
+ if (!pendingWord.isEmpty()) {
+ bool isProbablyType = (pendingWord != QLatin1String("const"));
+ if (generateType && isProbablyType) {
+ // Flush the current buffer.
+ m_writer->writeCharacters(result);
+ result.truncate(0);
+
+ // Add the link, logic from HtmlGenerator::highlightedCode.
+ const Node *n = m_qdb->findTypeNode(pendingWord, relative, Node::DontCare);
+ QString href;
+ if (!(n && n->isQmlBasicType())
+ || (relative
+ && (relative->genus() == n->genus() || Node::DontCare == n->genus()))) {
+ href = linkForNode(n, relative);
+ }
+
+ m_writer->writeStartElement(dbNamespace, "type");
+ if (href.isEmpty())
+ m_writer->writeCharacters(pendingWord);
+ else
+ generateSimpleLink(href, pendingWord);
+ m_writer->writeEndElement(); // type
+ } else {
+ result += pendingWord;
+ }
+ }
+ pendingWord.clear();
+
+ if (ch.unicode() != '\0')
+ result += ch;
+ }
+ }
+
+ if (trailingSpace && string.size()) {
+ if (!string.endsWith(QLatin1Char('*')) && !string.endsWith(QLatin1Char('&')))
+ result += QLatin1Char(' ');
+ }
+
+ m_writer->writeCharacters(result);
+}
+
+void DocBookGenerator::generateSynopsisName(const Node *node, const Node *relative,
+ bool generateNameLink)
+{
+ // Implements the rewriting of <@link> from HtmlGenerator::highlightedCode, only due to calls to
+ // CodeMarker::linkTag in CppCodeMarker::markedUpSynopsis.
+ QString name = taggedNode(node);
+
+ if (!generateNameLink) {
+ m_writer->writeCharacters(name);
+ return;
+ }
+
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ generateSimpleLink(linkForNode(node, relative), name);
+ m_writer->writeEndElement(); // emphasis
+}
+
+void DocBookGenerator::generateParameter(const Parameter &parameter, const Node *relative,
+ bool generateExtra, bool generateType)
+{
+ const QString &pname = parameter.name();
+ const QString &ptype = parameter.type();
+ QString paramName;
+ if (!pname.isEmpty()) {
+ typified(ptype, relative, true, generateType);
+ paramName = pname;
+ } else {
+ paramName = ptype;
+ }
+
+ if (generateExtra || pname.isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeCharacters(paramName);
+ m_writer->writeEndElement(); // emphasis
+ }
+
+ const QString &pvalue = parameter.defaultValue();
+ if (generateExtra && !pvalue.isEmpty())
+ m_writer->writeCharacters(" = " + pvalue);
+}
+
+void DocBookGenerator::generateSynopsis(const Node *node, const Node *relative,
+ Section::Style style)
+{
+ // From HtmlGenerator::generateSynopsis (conditions written as booleans).
+ const bool generateExtra = style != Section::AllMembers;
+ const bool generateType = style != Section::Details;
+ const bool generateNameLink = style != Section::Details;
+
+ // From CppCodeMarker::markedUpSynopsis, reversed the generation of "extra" and "synopsis".
+ const int MaxEnumValues = 6;
+
+ if (generateExtra) {
+ if (auto extra = CodeMarker::extraSynopsis(node, style); !extra.isEmpty())
+ m_writer->writeCharacters(extra + " ");
+ }
+
+ // Then generate the synopsis.
+ QString namePrefix {};
+ if (style == Section::Details) {
+ if (!node->isRelatedNonmember() && !node->isProxyNode() && !node->parent()->name().isEmpty()
+ && !node->parent()->isHeader() && !node->isProperty() && !node->isQmlNode()) {
+ namePrefix = taggedNode(node->parent()) + "::";
+ }
+ }
+
+ switch (node->nodeType()) {
+ case Node::Namespace:
+ m_writer->writeCharacters("namespace ");
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+ break;
+ case Node::Class:
+ m_writer->writeCharacters("class ");
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+ break;
+ case Node::Function: {
+ const auto func = (const FunctionNode *)node;
+
+ // First, the part coming before the name.
+ if (style == Section::Summary || style == Section::Accessors) {
+ if (!func->isNonvirtual())
+ m_writer->writeCharacters(QStringLiteral("virtual "));
+ }
+
+ // Name and parameters.
+ if (style != Section::AllMembers && !func->returnType().isEmpty())
+ typified(func->returnType(), relative, true, generateType);
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+
+ if (!func->isMacroWithoutParams()) {
+ m_writer->writeCharacters(QStringLiteral("("));
+ if (!func->parameters().isEmpty()) {
+ const Parameters &parameters = func->parameters();
+ for (int i = 0; i < parameters.count(); i++) {
+ if (i > 0)
+ m_writer->writeCharacters(QStringLiteral(", "));
+ generateParameter(parameters.at(i), relative, generateExtra, generateType);
+ }
+ }
+ m_writer->writeCharacters(QStringLiteral(")"));
+ }
+
+ if (func->isConst())
+ m_writer->writeCharacters(QStringLiteral(" const"));
+
+ if (style == Section::Summary || style == Section::Accessors) {
+ // virtual is prepended, if needed.
+ QString synopsis;
+ if (func->isFinal())
+ synopsis += QStringLiteral(" final");
+ if (func->isOverride())
+ synopsis += QStringLiteral(" override");
+ if (func->isPureVirtual())
+ synopsis += QStringLiteral(" = 0");
+ if (func->isRef())
+ synopsis += QStringLiteral(" &");
+ else if (func->isRefRef())
+ synopsis += QStringLiteral(" &&");
+ m_writer->writeCharacters(synopsis);
+ } else if (style == Section::AllMembers) {
+ if (!func->returnType().isEmpty() && func->returnType() != "void") {
+ m_writer->writeCharacters(QStringLiteral(" : "));
+ typified(func->returnType(), relative, false, generateType);
+ }
+ } else {
+ QString synopsis;
+ if (func->isRef())
+ synopsis += QStringLiteral(" &");
+ else if (func->isRefRef())
+ synopsis += QStringLiteral(" &&");
+ m_writer->writeCharacters(synopsis);
+ }
+ } break;
+ case Node::Enum: {
+ const auto enume = static_cast<const EnumNode *>(node);
+ m_writer->writeCharacters(QStringLiteral("enum "));
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+
+ QString synopsis;
+ if (style == Section::Summary) {
+ synopsis += " { ";
+
+ QStringList documentedItems = enume->doc().enumItemNames();
+ if (documentedItems.isEmpty()) {
+ const auto &enumItems = enume->items();
+ for (const auto &item : enumItems)
+ documentedItems << item.name();
+ }
+ const QStringList omitItems = enume->doc().omitEnumItemNames();
+ for (const auto &item : omitItems)
+ documentedItems.removeAll(item);
+
+ if (documentedItems.size() > MaxEnumValues) {
+ // Take the last element and keep it safe, then elide the surplus.
+ const QString last = documentedItems.last();
+ documentedItems = documentedItems.mid(0, MaxEnumValues - 1);
+ documentedItems += "&#x2026;"; // Ellipsis: in HTML, &hellip;.
+ documentedItems += last;
+ }
+ synopsis += documentedItems.join(QLatin1String(", "));
+
+ if (!documentedItems.isEmpty())
+ synopsis += QLatin1Char(' ');
+ synopsis += QLatin1Char('}');
+ }
+ m_writer->writeCharacters(synopsis);
+ } break;
+ case Node::TypeAlias: {
+ if (style == Section::Details) {
+ auto templateDecl = node->templateDecl();
+ if (templateDecl)
+ m_writer->writeCharacters((*templateDecl).to_qstring() + QLatin1Char(' '));
+ }
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+ } break;
+ case Node::Typedef: {
+ if (static_cast<const TypedefNode *>(node)->associatedEnum())
+ m_writer->writeCharacters("flags ");
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+ } break;
+ case Node::Property: {
+ const auto property = static_cast<const PropertyNode *>(node);
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+ m_writer->writeCharacters(" : ");
+ typified(property->qualifiedDataType(), relative, false, generateType);
+ } break;
+ case Node::Variable: {
+ const auto variable = static_cast<const VariableNode *>(node);
+ if (style == Section::AllMembers) {
+ generateSynopsisName(node, relative, generateNameLink);
+ m_writer->writeCharacters(" : ");
+ typified(variable->dataType(), relative, false, generateType);
+ } else {
+ typified(variable->leftType(), relative, false, generateType);
+ m_writer->writeCharacters(" ");
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+ m_writer->writeCharacters(variable->rightType());
+ }
+ } break;
+ default:
+ m_writer->writeCharacters(namePrefix);
+ generateSynopsisName(node, relative, generateNameLink);
+ }
+}
+
+void DocBookGenerator::generateEnumValue(const QString &enumValue, const Node *relative)
+{
+ // From CppCodeMarker::markedUpEnumValue, simplifications from Generator::plainCode (removing
+ // <@op>). With respect to CppCodeMarker::markedUpEnumValue, the order of generation of parents
+ // must be reversed so that they are processed in the order
+ const auto *node = relative->parent();
+
+ if (relative->isQmlProperty()) {
+ const auto *qpn = static_cast<const QmlPropertyNode*>(relative);
+ if (qpn->enumNode() && !enumValue.startsWith("%1."_L1.arg(qpn->enumPrefix()))) {
+ m_writer->writeCharacters("%1.%2"_L1.arg(qpn->enumPrefix(), enumValue));
+ return;
+ }
+ }
+
+ if (!relative->isEnumType()) {
+ m_writer->writeCharacters(enumValue);
+ return;
+ }
+
+ QList<const Node *> parents;
+ while (!node->isHeader() && node->parent()) {
+ parents.prepend(node);
+ if (node->parent() == relative || node->parent()->name().isEmpty())
+ break;
+ node = node->parent();
+ }
+ if (static_cast<const EnumNode *>(relative)->isScoped())
+ parents << relative;
+
+ m_writer->writeStartElement(dbNamespace, "code");
+ for (auto parent : parents) {
+ generateSynopsisName(parent, relative, true);
+ m_writer->writeCharacters("::");
+ }
+
+ m_writer->writeCharacters(enumValue);
+ m_writer->writeEndElement(); // code
+}
+
+/*!
+ If the node is an overloaded signal, and a node with an
+ example on how to connect to it
+
+ Someone didn't finish writing this comment, and I don't know what this
+ function is supposed to do, so I have not tried to complete the comment
+ yet.
+ */
+void DocBookGenerator::generateOverloadedSignal(const Node *node)
+{
+ // From Generator::generateOverloadedSignal.
+ QString code = getOverloadedSignalCode(node);
+ if (code.isEmpty())
+ return;
+
+ m_writer->writeStartElement(dbNamespace, "note");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("Signal ");
+ m_writer->writeTextElement(dbNamespace, "emphasis", node->name());
+ m_writer->writeCharacters(" is overloaded in this class. To connect to this "
+ "signal by using the function pointer syntax, Qt "
+ "provides a convenient helper for obtaining the "
+ "function pointer as shown in this example:");
+ m_writer->writeTextElement(dbNamespace, "code", code);
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // note
+ newLine();
+}
+
+/*!
+ Generates an addendum note of type \a type for \a node. \a marker
+ is unused in this generator.
+*/
+void DocBookGenerator::generateAddendum(const Node *node, Addendum type, CodeMarker *marker,
+ bool generateNote)
+{
+ Q_UNUSED(marker)
+ Q_ASSERT(node && !node->name().isEmpty());
+ if (generateNote) {
+ m_writer->writeStartElement(dbNamespace, "note");
+ newLine();
+ }
+ switch (type) {
+ case Invokable:
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters(
+ "This function can be invoked via the meta-object system and from QML. See ");
+ generateSimpleLink(node->url(), "Q_INVOKABLE");
+ m_writer->writeCharacters(".");
+ m_writer->writeEndElement(); // para
+ newLine();
+ break;
+ case PrivateSignal:
+ m_writer->writeTextElement(
+ dbNamespace, "para",
+ "This is a private signal. It can be used in signal connections but "
+ "cannot be emitted by the user.");
+ break;
+ case QmlSignalHandler:
+ {
+ QString handler(node->name());
+ int prefixLocation = handler.lastIndexOf('.', -2) + 1;
+ handler[prefixLocation] = handler[prefixLocation].toTitleCase();
+ handler.insert(prefixLocation, QLatin1String("on"));
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("The corresponding handler is ");
+ m_writer->writeTextElement(dbNamespace, "code", handler);
+ m_writer->writeCharacters(".");
+ m_writer->writeEndElement(); // para
+ newLine();
+ break;
+ }
+ case AssociatedProperties:
+ {
+ if (!node->isFunction())
+ return;
+ const auto *fn = static_cast<const FunctionNode *>(node);
+ auto propertyNodes = fn->associatedProperties();
+ if (propertyNodes.isEmpty())
+ return;
+ std::sort(propertyNodes.begin(), propertyNodes.end(), Node::nodeNameLessThan);
+ for (const auto propertyNode : std::as_const(propertyNodes)) {
+ QString msg;
+ const auto pn = static_cast<const PropertyNode *>(propertyNode);
+ switch (pn->role(fn)) {
+ case PropertyNode::FunctionRole::Getter:
+ msg = QStringLiteral("Getter function");
+ break;
+ case PropertyNode::FunctionRole::Setter:
+ msg = QStringLiteral("Setter function");
+ break;
+ case PropertyNode::FunctionRole::Resetter:
+ msg = QStringLiteral("Resetter function");
+ break;
+ case PropertyNode::FunctionRole::Notifier:
+ msg = QStringLiteral("Notifier signal");
+ break;
+ default:
+ continue;
+ }
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters(msg + " for property ");
+ generateSimpleLink(linkForNode(pn, nullptr), pn->name());
+ m_writer->writeCharacters(". ");
+ m_writer->writeEndElement(); // para
+ newLine();
+ }
+ break;
+ }
+ case BindableProperty:
+ {
+ const Node *linkNode;
+ Atom linkAtom = Atom(Atom::Link, "QProperty");
+ QString link = getAutoLink(&linkAtom, node, &linkNode);
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("This property supports ");
+ generateSimpleLink(link, "QProperty");
+ m_writer->writeCharacters(" bindings.");
+ m_writer->writeEndElement(); // para
+ newLine();
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (generateNote) {
+ m_writer->writeEndElement(); // note
+ newLine();
+ }
+}
+
+void DocBookGenerator::generateDetailedMember(const Node *node, const PageNode *relative)
+{
+ // From HtmlGenerator::generateDetailedMember.
+ bool closeSupplementarySection = false;
+
+ if (node->isSharedCommentNode()) {
+ const auto *scn = reinterpret_cast<const SharedCommentNode *>(node);
+ const QList<Node *> &collective = scn->collective();
+
+ bool firstFunction = true;
+ for (const auto *sharedNode : collective) {
+ if (firstFunction) {
+ startSectionBegin(sharedNode);
+ } else {
+ m_writer->writeStartElement(dbNamespace, "bridgehead");
+ m_writer->writeAttribute("renderas", "sect2");
+ writeXmlId(sharedNode);
+ }
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+
+ generateSynopsis(sharedNode, relative, Section::Details);
+
+ if (firstFunction) {
+ startSectionEnd();
+ firstFunction = false;
+ } else {
+ m_writer->writeEndElement(); // bridgehead
+ newLine();
+ }
+ }
+ } else {
+ const EnumNode *etn;
+ if (node->isEnumType() && (etn = static_cast<const EnumNode *>(node))->flagsType()) {
+ startSectionBegin(node);
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ generateSynopsis(etn, relative, Section::Details);
+ startSectionEnd();
+
+ m_writer->writeStartElement(dbNamespace, "bridgehead");
+ m_writer->writeAttribute("renderas", "sect2");
+ generateSynopsis(etn->flagsType(), relative, Section::Details);
+ m_writer->writeEndElement(); // bridgehead
+ newLine();
+ } else {
+ startSectionBegin(node);
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ generateSynopsis(node, relative, Section::Details);
+ startSectionEnd();
+ }
+ }
+ Q_ASSERT(m_hasSection);
+
+ generateDocBookSynopsis(node);
+
+ generateStatus(node);
+ generateBody(node);
+
+ // If the body ends with a section, the rest of the description must be wrapped in a section too.
+ if (node->hasDoc() && node->doc().body().firstAtom() && node->doc().body().lastAtom()->type() == Atom::SectionRight) {
+ closeSupplementarySection = true;
+ startSection("", "Notes");
+ }
+
+ generateOverloadedSignal(node);
+ generateComparisonCategory(node);
+ generateThreadSafeness(node);
+ generateSince(node);
+
+ if (node->isProperty()) {
+ const auto property = static_cast<const PropertyNode *>(node);
+ if (property->propertyType() == PropertyNode::PropertyType::StandardProperty) {
+ Section section("", "", "", "", Section::Accessors);
+
+ section.appendMembers(property->getters().toVector());
+ section.appendMembers(property->setters().toVector());
+ section.appendMembers(property->resetters().toVector());
+
+ if (!section.members().isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "para");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ m_writer->writeCharacters("Access functions:");
+ newLine();
+ m_writer->writeEndElement(); // emphasis
+ newLine();
+ m_writer->writeEndElement(); // para
+ newLine();
+ generateSectionList(section, node);
+ }
+
+ Section notifiers("", "", "", "", Section::Accessors);
+ notifiers.appendMembers(property->notifiers().toVector());
+
+ if (!notifiers.members().isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "para");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "emphasis");
+ m_writer->writeAttribute("role", "bold");
+ m_writer->writeCharacters("Notifier signal:");
+ newLine();
+ m_writer->writeEndElement(); // emphasis
+ newLine();
+ m_writer->writeEndElement(); // para
+ newLine();
+ generateSectionList(notifiers, node);
+ }
+ }
+ } else if (node->isEnumType()) {
+ const auto en = static_cast<const EnumNode *>(node);
+
+ if (m_qflagsHref.isEmpty()) {
+ Node *qflags = m_qdb->findClassNode(QStringList("QFlags"));
+ if (qflags)
+ m_qflagsHref = linkForNode(qflags, nullptr);
+ }
+
+ if (en->flagsType()) {
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("The ");
+ m_writer->writeStartElement(dbNamespace, "code");
+ m_writer->writeCharacters(en->flagsType()->name());
+ m_writer->writeEndElement(); // code
+ m_writer->writeCharacters(" type is a typedef for ");
+ m_writer->writeStartElement(dbNamespace, "code");
+ generateSimpleLink(m_qflagsHref, "QFlags");
+ m_writer->writeCharacters("<" + en->name() + ">. ");
+ m_writer->writeEndElement(); // code
+ m_writer->writeCharacters("It stores an OR combination of ");
+ m_writer->writeStartElement(dbNamespace, "code");
+ m_writer->writeCharacters(en->name());
+ m_writer->writeEndElement(); // code
+ m_writer->writeCharacters(" values.");
+ m_writer->writeEndElement(); // para
+ newLine();
+ }
+ }
+
+ if (closeSupplementarySection)
+ endSection();
+
+ // The list of linked pages is always in its own section.
+ generateAlsoList(node);
+
+ // Close the section for this member.
+ endSection(); // section
+}
+
+void DocBookGenerator::generateSectionList(const Section &section, const Node *relative,
+ bool useObsoleteMembers)
+{
+ // From HtmlGenerator::generateSectionList, just generating a list (not tables).
+ const NodeVector &members =
+ (useObsoleteMembers ? section.obsoleteMembers() : section.members());
+ if (!members.isEmpty()) {
+ bool hasPrivateSignals = false;
+ bool isInvokable = false;
+
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ newLine();
+
+ NodeVector::ConstIterator m = members.constBegin();
+ while (m != members.constEnd()) {
+ if ((*m)->access() == Access::Private) {
+ ++m;
+ continue;
+ }
+
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ newLine();
+ m_writer->writeStartElement(dbNamespace, "para");
+
+ // prefix no more needed.
+ generateSynopsis(*m, relative, section.style());
+ if ((*m)->isFunction()) {
+ const auto fn = static_cast<const FunctionNode *>(*m);
+ if (fn->isPrivateSignal())
+ hasPrivateSignals = true;
+ else if (fn->isInvokable())
+ isInvokable = true;
+ }
+
+ m_writer->writeEndElement(); // para
+ newLine();
+ m_writer->writeEndElement(); // listitem
+ newLine();
+
+ ++m;
+ }
+
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+
+ if (hasPrivateSignals)
+ generateAddendum(relative, Generator::PrivateSignal, nullptr, true);
+ if (isInvokable)
+ generateAddendum(relative, Generator::Invokable, nullptr, true);
+ }
+
+ if (!useObsoleteMembers && section.style() == Section::Summary
+ && !section.inheritedMembers().isEmpty()) {
+ m_writer->writeStartElement(dbNamespace, "itemizedlist");
+ if (m_useITS)
+ m_writer->writeAttribute(itsNamespace, "translate", "no");
+ newLine();
+
+ generateSectionInheritedList(section, relative);
+
+ m_writer->writeEndElement(); // itemizedlist
+ newLine();
+ }
+}
+
+void DocBookGenerator::generateSectionInheritedList(const Section &section, const Node *relative)
+{
+ // From HtmlGenerator::generateSectionInheritedList.
+ QList<std::pair<Aggregate *, int>>::ConstIterator p = section.inheritedMembers().constBegin();
+ while (p != section.inheritedMembers().constEnd()) {
+ m_writer->writeStartElement(dbNamespace, "listitem");
+ m_writer->writeCharacters(QString::number((*p).second) + u' ');
+ if ((*p).second == 1)
+ m_writer->writeCharacters(section.singular());
+ else
+ m_writer->writeCharacters(section.plural());
+ m_writer->writeCharacters(" inherited from ");
+ generateSimpleLink(fileName((*p).first) + '#'
+ + Generator::cleanRef(section.title().toLower()),
+ (*p).first->plainFullName(relative));
+ ++p;
+ }
+}
+
+/*!
+ Generate the DocBook page for an entity that doesn't map
+ to any underlying parsable C++ or QML element.
+ */
+void DocBookGenerator::generatePageNode(PageNode *pn)
+{
+ // From HtmlGenerator::generatePageNode, remove anything related to TOCs.
+ Q_ASSERT(m_writer == nullptr);
+ m_writer = startDocument(pn);
+
+ generateHeader(pn->fullTitle(), pn->subtitle(), pn);
+ generateBody(pn);
+ generateAlsoList(pn);
+ generateFooter();
+
+ endDocument();
+}
+
+/*!
+ Generate the DocBook page for a QML type. \qcn is the QML type.
+ */
+void DocBookGenerator::generateQmlTypePage(QmlTypeNode *qcn)
+{
+ // From HtmlGenerator::generateQmlTypePage.
+ // Start producing the DocBook file.
+ Q_ASSERT(m_writer == nullptr);
+ m_writer = startDocument(qcn);
+
+ Generator::setQmlTypeContext(qcn);
+ QString title = qcn->fullTitle();
+ if (qcn->isQmlBasicType())
+ title.append(" QML Value Type");
+ else
+ title.append(" QML Type");
+ // TODO: for ITS attribute, only apply translate="no" on qcn->fullTitle(),
+ // not its suffix (which should be translated). generateHeader doesn't
+ // allow this kind of input, the title isn't supposed to be structured.
+ // Ideally, do the same in HTML.
+
+ generateHeader(title, qcn->subtitle(), qcn);
+ generateQmlRequisites(qcn);
+ generateStatus(qcn);
+
+ startSection("details", "Detailed Description");
+ generateBody(qcn);
+
+ generateAlsoList(qcn);
+
+ endSection();
+
+ Sections sections(qcn);
+ for (const auto &section : sections.stdQmlTypeDetailsSections()) {
+ if (!section.isEmpty()) {
+ startSection(section.title().toLower(), section.title());
+
+ for (const auto &member : section.members())
+ generateDetailedQmlMember(member, qcn);
+
+ endSection();
+ }
+ }
+
+ generateObsoleteQmlMembers(sections);
+
+ generateFooter();
+ Generator::setQmlTypeContext(nullptr);
+
+ endDocument();
+}
+
+/*!
+ Outputs the DocBook detailed documentation for a section
+ on a QML element reference page.
+ */
+void DocBookGenerator::generateDetailedQmlMember(Node *node, const Aggregate *relative)
+{
+ // From HtmlGenerator::generateDetailedQmlMember, with elements from
+ // CppCodeMarker::markedUpQmlItem and HtmlGenerator::generateQmlItem.
+ auto getQmlPropertyTitle = [&](QmlPropertyNode *n) {
+ QString title{CodeMarker::extraSynopsis(n, Section::Details)};
+ if (!title.isEmpty())
+ title += ' '_L1;
+ // Finalise generation of name, as per CppCodeMarker::markedUpQmlItem.
+ if (n->isAttached())
+ title += n->element() + QLatin1Char('.');
+ title += n->name() + " : " + n->dataType();
+
+ return title;
+ };
+
+ auto generateQmlMethodTitle = [&](Node *node) {
+ generateSynopsis(node, relative, Section::Details);
+ };
+
+ if (node->isPropertyGroup()) {
+ const auto *scn = static_cast<const SharedCommentNode *>(node);
+
+ QString heading;
+ if (!scn->name().isEmpty())
+ heading = scn->name() + " group";
+ else
+ heading = node->name();
+ startSection(scn, heading);
+ // This last call creates a title for this section. In other words,
+ // titles are forbidden for the rest of the section, hence the use of
+ // bridgehead.
+
+ const QList<Node *> sharedNodes = scn->collective();
+ for (const auto &sharedNode : sharedNodes) {
+ if (sharedNode->isQmlProperty()) {
+ auto *qpn = static_cast<QmlPropertyNode *>(sharedNode);
+
+ m_writer->writeStartElement(dbNamespace, "bridgehead");
+ m_writer->writeAttribute("renderas", "sect2");
+ writeXmlId(qpn);
+ m_writer->writeCharacters(getQmlPropertyTitle(qpn));
+ m_writer->writeEndElement(); // bridgehead
+ newLine();
+
+ generateDocBookSynopsis(qpn);
+ }
+ }
+ } else if (node->isQmlProperty()) {
+ auto qpn = static_cast<QmlPropertyNode *>(node);
+ startSection(qpn, getQmlPropertyTitle(qpn));
+ generateDocBookSynopsis(qpn);
+ } else if (node->isSharedCommentNode()) {
+ const auto scn = reinterpret_cast<const SharedCommentNode *>(node);
+ const QList<Node *> &sharedNodes = scn->collective();
+
+ // In the section, generate a title for the first node, then bridgeheads for
+ // the next ones.
+ int i = 0;
+ for (const auto &sharedNode : sharedNodes) {
+ // Ignore this element if there is nothing to generate.
+ if (!sharedNode->isFunction(Node::QML) && !sharedNode->isQmlProperty()) {
+ continue;
+ }
+
+ // Write the tag containing the title.
+ if (i == 0) {
+ startSectionBegin(sharedNode);
+ } else {
+ m_writer->writeStartElement(dbNamespace, "bridgehead");
+ m_writer->writeAttribute("renderas", "sect2");
+ }
+
+ // Write the title.
+ if (sharedNode->isFunction(Node::QML))
+ generateQmlMethodTitle(sharedNode);
+ else if (sharedNode->isQmlProperty())
+ m_writer->writeCharacters(
+ getQmlPropertyTitle(static_cast<QmlPropertyNode *>(sharedNode)));
+
+ // Complete the title and the synopsis.
+ if (i == 0)
+ startSectionEnd();
+ else
+ m_writer->writeEndElement(); // bridgehead
+ generateDocBookSynopsis(sharedNode);
+ ++i;
+ }
+
+ // If the list is empty, still generate a section.
+ if (i == 0) {
+ startSectionBegin(refForNode(node));
+
+ if (node->isFunction(Node::QML))
+ generateQmlMethodTitle(node);
+ else if (node->isQmlProperty())
+ m_writer->writeCharacters(
+ getQmlPropertyTitle(static_cast<QmlPropertyNode *>(node)));
+
+ startSectionEnd();
+ }
+ } else { // assume the node is a method/signal handler
+ startSectionBegin(node);
+ generateQmlMethodTitle(node);
+ startSectionEnd();
+ }
+
+ generateStatus(node);
+ generateBody(node);
+ generateThreadSafeness(node);
+ generateSince(node);
+ generateAlsoList(node);
+
+ endSection();
+}
+
+/*!
+ Recursive writing of DocBook files from the root \a node.
+ */
+void DocBookGenerator::generateDocumentation(Node *node)
+{
+ // Mainly from Generator::generateDocumentation, with parts from
+ // Generator::generateDocumentation and WebXMLGenerator::generateDocumentation.
+ // Don't generate nodes that are already processed, or if they're not
+ // supposed to generate output, ie. external, index or images nodes.
+ if (!node->url().isNull())
+ return;
+ if (node->isIndexNode())
+ return;
+ if (node->isInternal() && !m_showInternal)
+ return;
+ if (node->isExternalPage())
+ return;
+
+ if (node->parent()) {
+ if (node->isCollectionNode()) {
+ /*
+ A collection node collects: groups, C++ modules, or QML
+ modules. Testing for a CollectionNode must be done
+ before testing for a TextPageNode because a
+ CollectionNode is a PageNode at this point.
+
+ Don't output an HTML page for the collection node unless
+ the \group, \module, or \qmlmodule command was actually
+ seen by qdoc in the qdoc comment for the node.
+
+ A key prerequisite in this case is the call to
+ mergeCollections(cn). We must determine whether this
+ group, module, or QML module has members in other
+ modules. We know at this point that cn's members list
+ contains only members in the current module. Therefore,
+ before outputting the page for cn, we must search for
+ members of cn in the other modules and add them to the
+ members list.
+ */
+ auto cn = static_cast<CollectionNode *>(node);
+ if (cn->wasSeen()) {
+ m_qdb->mergeCollections(cn);
+ generateCollectionNode(cn);
+ } else if (cn->isGenericCollection()) {
+ // Currently used only for the module's related orphans page
+ // but can be generalized for other kinds of collections if
+ // other use cases pop up.
+ generateGenericCollectionPage(cn);
+ }
+ } else if (node->isTextPageNode()) { // Pages.
+ generatePageNode(static_cast<PageNode *>(node));
+ } else if (node->isAggregate()) { // Aggregates.
+ if ((node->isClassNode() || node->isHeader() || node->isNamespace())
+ && node->docMustBeGenerated()) {
+ generateCppReferencePage(static_cast<Aggregate *>(node));
+ } else if (node->isQmlType()) { // Includes QML value types
+ generateQmlTypePage(static_cast<QmlTypeNode *>(node));
+ } else if (node->isProxyNode()) {
+ generateProxyPage(static_cast<Aggregate *>(node));
+ }
+ }
+ }
+
+ if (node->isAggregate()) {
+ auto *aggregate = static_cast<Aggregate *>(node);
+ for (auto c : aggregate->childNodes()) {
+ if (node->isPageNode() && !node->isPrivate())
+ generateDocumentation(c);
+ }
+ }
+}
+
+void DocBookGenerator::generateProxyPage(Aggregate *aggregate)
+{
+ // Adapted from HtmlGenerator::generateProxyPage.
+ Q_ASSERT(aggregate->isProxyNode());
+
+ // Start producing the DocBook file.
+ Q_ASSERT(m_writer == nullptr);
+ m_writer = startDocument(aggregate);
+
+ // Info container.
+ generateHeader(aggregate->plainFullName(), "", aggregate);
+
+ // No element synopsis.
+
+ // Actual content.
+ if (!aggregate->doc().isEmpty()) {
+ startSection("details", "Detailed Description");
+
+ generateBody(aggregate);
+ generateAlsoList(aggregate);
+
+ endSection();
+ }
+
+ Sections sections(aggregate);
+ SectionVector *detailsSections = &sections.stdDetailsSections();
+
+ for (const auto &section : std::as_const(*detailsSections)) {
+ if (section.isEmpty())
+ continue;
+
+ startSection(section.title().toLower(), section.title());
+
+ const QList<Node *> &members = section.members();
+ for (const auto &member : members) {
+ if (!member->isPrivate()) { // ### check necessary?
+ if (!member->isClassNode()) {
+ generateDetailedMember(member, aggregate);
+ } else {
+ startSectionBegin();
+ generateFullName(member, aggregate);
+ startSectionEnd();
+
+ generateBrief(member);
+ endSection();
+ }
+ }
+ }
+
+ endSection();
+ }
+
+ generateFooter();
+
+ endDocument();
+}
+
+/*!
+ Generate the HTML page for a group, module, or QML module.
+ */
+void DocBookGenerator::generateCollectionNode(CollectionNode *cn)
+{
+ // Adapted from HtmlGenerator::generateCollectionNode.
+ // Start producing the DocBook file.
+ Q_ASSERT(m_writer == nullptr);
+ m_writer = startDocument(cn);
+
+ // Info container.
+ generateHeader(cn->fullTitle(), cn->subtitle(), cn);
+
+ // Element synopsis.
+ generateDocBookSynopsis(cn);
+
+ // Generate brief for C++ modules, status for all modules.
+ if (cn->genus() != Node::DOC && cn->genus() != Node::DontCare) {
+ if (cn->isModule())
+ generateBrief(cn);
+ generateStatus(cn);
+ generateSince(cn);
+ }
+
+ // Actual content.
+ if (cn->isModule()) {
+ if (!cn->noAutoList()) {
+ NodeMap nmm{cn->getMembers(Node::Namespace)};
+ if (!nmm.isEmpty()) {
+ startSection("namespaces", "Namespaces");
+ generateAnnotatedList(cn, nmm.values(), "namespaces");
+ endSection();
+ }
+ nmm = cn->getMembers([](const Node *n){ return n->isClassNode(); });
+ if (!nmm.isEmpty()) {
+ startSection("classes", "Classes");
+ generateAnnotatedList(cn, nmm.values(), "classes");
+ endSection();
+ }
+ }
+ }
+
+ bool generatedTitle = false;
+ if (cn->isModule() && !cn->doc().briefText().isEmpty()) {
+ startSection("details", "Detailed Description");
+ generatedTitle = true;
+ }
+ // The anchor is only needed if the node has a body.
+ else if (
+ // generateBody generates something.
+ !cn->doc().body().isEmpty() ||
+ // generateAlsoList generates something.
+ !cn->doc().alsoList().empty() ||
+ // generateAnnotatedList generates something.
+ (!cn->noAutoList() && (cn->isGroup() || cn->isQmlModule()))) {
+ writeAnchor("details");
+ }
+
+ generateBody(cn);
+ generateAlsoList(cn);
+
+ if (!cn->noAutoList() && (cn->isGroup() || cn->isQmlModule()))
+ generateAnnotatedList(cn, cn->members(), "members", AutoSection);
+
+ if (generatedTitle)
+ endSection();
+
+ generateFooter();
+
+ endDocument();
+}
+
+/*!
+ Generate the HTML page for a generic collection. This is usually
+ a collection of C++ elements that are related to an element in
+ a different module.
+ */
+void DocBookGenerator::generateGenericCollectionPage(CollectionNode *cn)
+{
+ // Adapted from HtmlGenerator::generateGenericCollectionPage.
+ // TODO: factor out this code to generate a file name.
+ QString name = cn->name().toLower();
+ name.replace(QChar(' '), QString("-"));
+ QString filename = cn->tree()->physicalModuleName() + "-" + name + "." + fileExtension();
+
+ // Start producing the DocBook file.
+ Q_ASSERT(m_writer == nullptr);
+ m_writer = startGenericDocument(cn, filename);
+
+ // Info container.
+ generateHeader(cn->fullTitle(), cn->subtitle(), cn);
+
+ // Element synopsis.
+ generateDocBookSynopsis(cn);
+
+ // Actual content.
+ m_writer->writeStartElement(dbNamespace, "para");
+ m_writer->writeCharacters("Each function or type documented here is related to a class or "
+ "namespace that is documented in a different module. The reference "
+ "page for that class or namespace will link to the function or type "
+ "on this page.");
+ m_writer->writeEndElement(); // para
+
+ const CollectionNode *cnc = cn;
+ const QList<Node *> members = cn->members();
+ for (const auto &member : members)
+ generateDetailedMember(member, cnc);
+
+ generateFooter();
+
+ endDocument();
+}
+
+void DocBookGenerator::generateFullName(const Node *node, const Node *relative)
+{
+ Q_ASSERT(node);
+ Q_ASSERT(relative);
+
+ // From Generator::appendFullName.
+ m_writer->writeStartElement(dbNamespace, "link");
+ m_writer->writeAttribute(xlinkNamespace, "href", fullDocumentLocation(node));
+ m_writer->writeAttribute(xlinkNamespace, "role", targetType(node));
+ m_writer->writeCharacters(node->fullName(relative));
+ m_writer->writeEndElement(); // link
+}
+
+void DocBookGenerator::generateFullName(const Node *apparentNode, const QString &fullName,
+ const Node *actualNode)
+{
+ Q_ASSERT(apparentNode);
+ Q_ASSERT(actualNode);
+
+ // From Generator::appendFullName.
+ m_writer->writeStartElement(dbNamespace, "link");
+ m_writer->writeAttribute(xlinkNamespace, "href", fullDocumentLocation(actualNode));
+ m_writer->writeAttribute("role", targetType(actualNode));
+ m_writer->writeCharacters(fullName);
+ m_writer->writeEndElement(); // link
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/docbookgenerator.h b/src/qdoc/qdoc/src/qdoc/docbookgenerator.h
new file mode 100644
index 000000000..5defbeb82
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/docbookgenerator.h
@@ -0,0 +1,168 @@
+// Copyright (C) 2019 Thibaut Cuvelier
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef DOCBOOKGENERATOR_H
+#define DOCBOOKGENERATOR_H
+
+#include "codemarker.h"
+#include "config.h"
+#include "xmlgenerator.h"
+#include "filesystem/fileresolver.h"
+
+#include <QtCore/qhash.h>
+#include <QtCore/qxmlstream.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+class ExampleNode;
+class FunctionNode;
+
+class DocBookGenerator : public XmlGenerator
+{
+public:
+ explicit DocBookGenerator(FileResolver& file_resolver);
+
+ void initializeGenerator() override;
+ QString format() override;
+
+protected:
+ [[nodiscard]] QString fileExtension() const override;
+ void generateDocumentation(Node *node) override;
+ using Generator::generateCppReferencePage;
+ void generateCppReferencePage(Node *node);
+ using Generator::generatePageNode;
+ void generatePageNode(PageNode *pn);
+ using Generator::generateQmlTypePage;
+ void generateQmlTypePage(QmlTypeNode *qcn);
+ using Generator::generateCollectionNode;
+ void generateCollectionNode(CollectionNode *cn);
+ using Generator::generateGenericCollectionPage;
+ void generateGenericCollectionPage(CollectionNode *cn);
+ using Generator::generateProxyPage;
+ void generateProxyPage(Aggregate *aggregate);
+
+ void generateList(const Node *relative, const QString &selector);
+ void generateHeader(const QString &title, const QString &subtitle, const Node *node);
+ void closeTextSections();
+ void generateFooter();
+ void generateDocBookSynopsis(const Node *node);
+ void generateRequisites(const Aggregate *inner);
+ void generateQmlRequisites(const QmlTypeNode *qcn);
+ void generateSortedNames(const ClassNode *cn, const QList<RelatedClass> &rc);
+ void generateSortedQmlNames(const Node *base, const NodeList &subs);
+ bool generateStatus(const Node *node);
+ void generateGroupReferenceText(const Node *node);
+ bool generateThreadSafeness(const Node *node);
+ bool generateSince(const Node *node);
+ void generateAddendum(const Node *node, Generator::Addendum type, CodeMarker *marker,
+ bool generateNote) override;
+ using Generator::generateBody;
+ void generateBody(const Node *node);
+
+ bool generateText(const Text &text, const Node *relative) override;
+ qsizetype generateAtom(const Atom *atom, const Node *relative, CodeMarker*) override;
+
+private:
+
+ enum GeneratedListType { Auto, AutoSection, ItemizedList };
+
+ QXmlStreamWriter *startDocument(const Node *node);
+ QXmlStreamWriter *startDocument(const ExampleNode *en, const QString &file);
+ QXmlStreamWriter *startGenericDocument(const Node *node, const QString &fileName);
+ void endDocument();
+
+ void generateAnnotatedList(const Node *relative, const NodeList &nodeList,
+ const QString &selector, GeneratedListType type = Auto);
+ void generateAnnotatedLists(const Node *relative, const NodeMultiMap &nmm,
+ const QString &selector);
+ void generateCompactList(const Node *relative, const NodeMultiMap &nmm, bool includeAlphabet,
+ const QString &commonPrefix, const QString &selector);
+ using Generator::generateFileList;
+ void generateFileList(const ExampleNode *en, bool images);
+ void generateObsoleteMembers(const Sections &sections);
+ void generateObsoleteQmlMembers(const Sections &sections);
+ void generateSectionList(const Section &section, const Node *relative,
+ bool useObsoleteMembers = false);
+ void generateSectionInheritedList(const Section &section, const Node *relative);
+ void generateSynopsisName(const Node *node, const Node *relative, bool generateNameLink);
+ void generateParameter(const Parameter &parameter, const Node *relative, bool generateExtra,
+ bool generateType);
+ void generateSynopsis(const Node *node, const Node *relative, Section::Style style);
+ void generateEnumValue(const QString &enumValue, const Node *relative);
+ void generateDetailedMember(const Node *node, const PageNode *relative);
+ void generateDetailedQmlMember(Node *node, const Aggregate *relative);
+
+ void generateFullName(const Node *node, const Node *relative);
+ void generateFullName(const Node *apparentNode, const QString &fullName,
+ const Node *actualNode);
+ void generateBrief(const Node *node);
+ void generateAlsoList(const Node *node) override;
+ void generateSignatureList(const NodeList &nodes);
+ void generateReimplementsClause(const FunctionNode *fn);
+ void generateClassHierarchy(const Node *relative, NodeMultiMap &classMap);
+ void generateFunctionIndex(const Node *relative);
+ void generateLegaleseList(const Node *relative);
+ void generateExampleFilePage(const Node *en, ResolvedFile resolved_file, CodeMarker* = nullptr) override;
+ void generateOverloadedSignal(const Node *node);
+ void generateRequiredLinks(const Node *node);
+ void generateLinkToExample(const ExampleNode *en, const QString &baseUrl);
+
+ void typified(const QString &string, const Node *relative, bool trailingSpace = false,
+ bool generateType = true);
+ void generateLink(const Atom *atom);
+ void beginLink(const QString &link, const Node *node, const Node *relative);
+ void endLink();
+ void writeXmlId(const QString &id);
+ void writeXmlId(const Node *node);
+ inline void newLine();
+ void startSectionBegin(const QString &id = "");
+ void startSectionBegin(const Node *node);
+ void startSectionEnd();
+ void startSection(const QString &id, const QString &title);
+ void startSection(const Node *node, const QString &title);
+ void startSection(const QString &title);
+ void endSection();
+ void writeAnchor(const QString &id);
+ void generateSimpleLink(const QString &href, const QString &text);
+ void generateStartRequisite(const QString &description);
+ void generateEndRequisite();
+ void generateRequisite(const QString &description, const QString &value);
+ void generateCMakeRequisite(const QStringList &values);
+ void generateSynopsisInfo(const QString &key, const QString &value);
+ void generateModifier(const QString &value);
+
+ // Generator state when outputting the documentation.
+ bool m_inListItemLineOpen { false };
+ int currentSectionLevel { 0 };
+ QStack<int> sectionLevels {};
+ QString m_qflagsHref {};
+ bool m_inTeletype { false };
+ bool m_hasSection { false };
+ bool m_closeSectionAfterGeneratedList { false };
+ bool m_closeSectionAfterRawTitle { false };
+ bool m_closeFigureWrapper { false };
+ bool m_tableHeaderAlreadyOutput { false };
+ bool m_closeTableRow { false };
+ bool m_closeTableCell { false };
+ std::pair<QString, QString> m_tableWidthAttr {};
+ bool m_inPara { false }; // Ignores nesting of paragraphs (like list items).
+ bool m_inBlockquote { false };
+ unsigned m_inList { 0 }; // Depth in number of nested lists.
+
+ // Generator configuration, set before starting the generation.
+ QString m_project {};
+ QString m_projectDescription {};
+ QString m_naturalLanguage {};
+ QString m_buildVersion {};
+ QXmlStreamWriter *m_writer { nullptr };
+ bool m_useDocBook52 { false }; // Enable tags from DocBook 5.2. Also called "extensions".
+ bool m_useITS { false }; // Enable ITS attributes for parts that should not be translated.
+
+ Config *m_config { nullptr };
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/docparser.cpp b/src/qdoc/qdoc/src/qdoc/docparser.cpp
new file mode 100644
index 000000000..ee9a187b7
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/docparser.cpp
@@ -0,0 +1,2836 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include "docparser.h"
+
+#include "codemarker.h"
+#include "doc.h"
+#include "docprivate.h"
+#include "editdistance.h"
+#include "macro.h"
+#include "openedlist.h"
+#include "tokenizer.h"
+
+#include <QtCore/qfile.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qtextstream.h>
+
+#include <cctype>
+#include <climits>
+#include <functional>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+DocUtilities &DocParser::s_utilities = DocUtilities::instance();
+
+enum {
+ CMD_A,
+ CMD_ANNOTATEDLIST,
+ CMD_B,
+ CMD_BADCODE,
+ CMD_BOLD,
+ CMD_BR,
+ CMD_BRIEF,
+ CMD_C,
+ CMD_CAPTION,
+ CMD_CODE,
+ CMD_CODELINE,
+ CMD_COMPARESWITH,
+ CMD_DETAILS,
+ CMD_DIV,
+ CMD_DOTS,
+ CMD_E,
+ CMD_ELSE,
+ CMD_ENDCODE,
+ CMD_ENDCOMPARESWITH,
+ CMD_ENDDETAILS,
+ CMD_ENDDIV,
+ CMD_ENDFOOTNOTE,
+ CMD_ENDIF,
+ CMD_ENDLEGALESE,
+ CMD_ENDLINK,
+ CMD_ENDLIST,
+ CMD_ENDMAPREF,
+ CMD_ENDOMIT,
+ CMD_ENDQUOTATION,
+ CMD_ENDRAW,
+ CMD_ENDSECTION1,
+ CMD_ENDSECTION2,
+ CMD_ENDSECTION3,
+ CMD_ENDSECTION4,
+ CMD_ENDSIDEBAR,
+ CMD_ENDTABLE,
+ CMD_FOOTNOTE,
+ CMD_GENERATELIST,
+ CMD_HEADER,
+ CMD_HR,
+ CMD_I,
+ CMD_IF,
+ CMD_IMAGE,
+ CMD_IMPORTANT,
+ CMD_INCLUDE,
+ CMD_INLINEIMAGE,
+ CMD_INDEX,
+ CMD_INPUT,
+ CMD_KEYWORD,
+ CMD_L,
+ CMD_LEGALESE,
+ CMD_LI,
+ CMD_LINK,
+ CMD_LIST,
+ CMD_META,
+ CMD_NOTE,
+ CMD_O,
+ CMD_OMIT,
+ CMD_OMITVALUE,
+ CMD_OVERLOAD,
+ CMD_PRINTLINE,
+ CMD_PRINTTO,
+ CMD_PRINTUNTIL,
+ CMD_QUOTATION,
+ CMD_QUOTEFILE,
+ CMD_QUOTEFROMFILE,
+ CMD_RAW,
+ CMD_ROW,
+ CMD_SA,
+ CMD_SECTION1,
+ CMD_SECTION2,
+ CMD_SECTION3,
+ CMD_SECTION4,
+ CMD_SIDEBAR,
+ CMD_SINCELIST,
+ CMD_SKIPLINE,
+ CMD_SKIPTO,
+ CMD_SKIPUNTIL,
+ CMD_SNIPPET,
+ CMD_SPAN,
+ CMD_SUB,
+ CMD_SUP,
+ CMD_TABLE,
+ CMD_TABLEOFCONTENTS,
+ CMD_TARGET,
+ CMD_TM,
+ CMD_TT,
+ CMD_UICONTROL,
+ CMD_UNDERLINE,
+ CMD_UNICODE,
+ CMD_VALUE,
+ CMD_WARNING,
+ CMD_QML,
+ CMD_ENDQML,
+ CMD_CPP,
+ CMD_ENDCPP,
+ CMD_CPPTEXT,
+ CMD_ENDCPPTEXT,
+ NOT_A_CMD
+};
+
+static struct
+{
+ const char *name;
+ int no;
+ bool is_formatting_command { false };
+} cmds[] = { { "a", CMD_A, true },
+ { "annotatedlist", CMD_ANNOTATEDLIST },
+ { "b", CMD_B, true },
+ { "badcode", CMD_BADCODE },
+ { "bold", CMD_BOLD, true },
+ { "br", CMD_BR },
+ { "brief", CMD_BRIEF },
+ { "c", CMD_C, true },
+ { "caption", CMD_CAPTION },
+ { "code", CMD_CODE },
+ { "codeline", CMD_CODELINE },
+ { "compareswith", CMD_COMPARESWITH },
+ { "details", CMD_DETAILS },
+ { "div", CMD_DIV },
+ { "dots", CMD_DOTS },
+ { "e", CMD_E, true },
+ { "else", CMD_ELSE },
+ { "endcode", CMD_ENDCODE },
+ { "endcompareswith", CMD_ENDCOMPARESWITH },
+ { "enddetails", CMD_ENDDETAILS },
+ { "enddiv", CMD_ENDDIV },
+ { "endfootnote", CMD_ENDFOOTNOTE },
+ { "endif", CMD_ENDIF },
+ { "endlegalese", CMD_ENDLEGALESE },
+ { "endlink", CMD_ENDLINK },
+ { "endlist", CMD_ENDLIST },
+ { "endmapref", CMD_ENDMAPREF },
+ { "endomit", CMD_ENDOMIT },
+ { "endquotation", CMD_ENDQUOTATION },
+ { "endraw", CMD_ENDRAW },
+ { "endsection1", CMD_ENDSECTION1 }, // ### don't document for now
+ { "endsection2", CMD_ENDSECTION2 }, // ### don't document for now
+ { "endsection3", CMD_ENDSECTION3 }, // ### don't document for now
+ { "endsection4", CMD_ENDSECTION4 }, // ### don't document for now
+ { "endsidebar", CMD_ENDSIDEBAR },
+ { "endtable", CMD_ENDTABLE },
+ { "footnote", CMD_FOOTNOTE },
+ { "generatelist", CMD_GENERATELIST },
+ { "header", CMD_HEADER },
+ { "hr", CMD_HR },
+ { "i", CMD_I, true },
+ { "if", CMD_IF },
+ { "image", CMD_IMAGE },
+ { "important", CMD_IMPORTANT },
+ { "include", CMD_INCLUDE },
+ { "inlineimage", CMD_INLINEIMAGE },
+ { "index", CMD_INDEX }, // ### don't document for now
+ { "input", CMD_INPUT },
+ { "keyword", CMD_KEYWORD },
+ { "l", CMD_L },
+ { "legalese", CMD_LEGALESE },
+ { "li", CMD_LI },
+ { "link", CMD_LINK },
+ { "list", CMD_LIST },
+ { "meta", CMD_META },
+ { "note", CMD_NOTE },
+ { "o", CMD_O },
+ { "omit", CMD_OMIT },
+ { "omitvalue", CMD_OMITVALUE },
+ { "overload", CMD_OVERLOAD },
+ { "printline", CMD_PRINTLINE },
+ { "printto", CMD_PRINTTO },
+ { "printuntil", CMD_PRINTUNTIL },
+ { "quotation", CMD_QUOTATION },
+ { "quotefile", CMD_QUOTEFILE },
+ { "quotefromfile", CMD_QUOTEFROMFILE },
+ { "raw", CMD_RAW },
+ { "row", CMD_ROW },
+ { "sa", CMD_SA },
+ { "section1", CMD_SECTION1 },
+ { "section2", CMD_SECTION2 },
+ { "section3", CMD_SECTION3 },
+ { "section4", CMD_SECTION4 },
+ { "sidebar", CMD_SIDEBAR },
+ { "sincelist", CMD_SINCELIST },
+ { "skipline", CMD_SKIPLINE },
+ { "skipto", CMD_SKIPTO },
+ { "skipuntil", CMD_SKIPUNTIL },
+ { "snippet", CMD_SNIPPET },
+ { "span", CMD_SPAN },
+ { "sub", CMD_SUB, true },
+ { "sup", CMD_SUP, true },
+ { "table", CMD_TABLE },
+ { "tableofcontents", CMD_TABLEOFCONTENTS },
+ { "target", CMD_TARGET },
+ { "tm", CMD_TM, true },
+ { "tt", CMD_TT, true },
+ { "uicontrol", CMD_UICONTROL, true },
+ { "underline", CMD_UNDERLINE, true },
+ { "unicode", CMD_UNICODE },
+ { "value", CMD_VALUE },
+ { "warning", CMD_WARNING },
+ { "qml", CMD_QML },
+ { "endqml", CMD_ENDQML },
+ { "cpp", CMD_CPP },
+ { "endcpp", CMD_ENDCPP },
+ { "cpptext", CMD_CPPTEXT },
+ { "endcpptext", CMD_ENDCPPTEXT },
+ { nullptr, 0 } };
+
+int DocParser::s_tabSize;
+QStringList DocParser::s_ignoreWords;
+bool DocParser::s_quoting = false;
+FileResolver *DocParser::file_resolver{ nullptr };
+static void processComparesWithCommand(DocPrivate *priv, const Location &location);
+
+static QString cleanLink(const QString &link)
+{
+ qsizetype colonPos = link.indexOf(':');
+ if ((colonPos == -1) || (!link.startsWith("file:") && !link.startsWith("mailto:")))
+ return link;
+ return link.mid(colonPos + 1).simplified();
+}
+
+void DocParser::initialize(const Config &config, FileResolver &file_resolver)
+{
+ s_tabSize = config.get(CONFIG_TABSIZE).asInt();
+ s_ignoreWords = config.get(CONFIG_IGNOREWORDS).asStringList();
+
+ int i = 0;
+ while (cmds[i].name) {
+ s_utilities.cmdHash.insert(cmds[i].name, cmds[i].no);
+
+ if (cmds[i].no != i)
+ Location::internalError(QStringLiteral("command %1 missing").arg(i));
+ ++i;
+ }
+
+ // If any of the formats define quotinginformation, activate quoting
+ DocParser::s_quoting = config.get(CONFIG_QUOTINGINFORMATION).asBool();
+ const auto &outputFormats = config.getOutputFormats();
+ for (const auto &format : outputFormats)
+ DocParser::s_quoting = DocParser::s_quoting
+ || config.get(format + Config::dot + CONFIG_QUOTINGINFORMATION).asBool();
+
+ // KLUDGE: file_resolver is temporarily a pointer. See the
+ // comment for file_resolver in the header file for more context.
+ DocParser::file_resolver = &file_resolver;
+}
+
+/*!
+ Parse the \a source string to build a Text data structure
+ in \a docPrivate. The Text data structure is a linked list
+ of Atoms.
+
+ \a metaCommandSet is the set of metacommands that may be
+ found in \a source. These metacommands are not markup text
+ commands. They are topic commands and related metacommands.
+ */
+void DocParser::parse(const QString &source, DocPrivate *docPrivate,
+ const QSet<QString> &metaCommandSet, const QSet<QString> &possibleTopics)
+{
+ m_input = source;
+ m_position = 0;
+ m_inputLength = m_input.size();
+ m_cachedLocation = docPrivate->m_start_loc;
+ m_cachedPosition = 0;
+ m_private = docPrivate;
+ m_private->m_text << Atom::Nop;
+ m_private->m_topics.clear();
+
+ m_paragraphState = OutsideParagraph;
+ m_inTableHeader = false;
+ m_inTableRow = false;
+ m_inTableItem = false;
+ m_indexStartedParagraph = false;
+ m_pendingParagraphLeftType = Atom::Nop;
+ m_pendingParagraphRightType = Atom::Nop;
+
+ m_braceDepth = 0;
+ m_currentSection = Doc::NoSection;
+ m_openedCommands.push(CMD_OMIT);
+ m_quoter.reset();
+
+ CodeMarker *marker = nullptr;
+ Atom *currentLinkAtom = nullptr;
+ QString p1, p2;
+ QStack<bool> preprocessorSkipping;
+ int numPreprocessorSkipping = 0;
+
+ while (m_position < m_inputLength) {
+ QChar ch = m_input.at(m_position);
+
+ switch (ch.unicode()) {
+ case '\\': {
+ QString cmdStr;
+ m_backslashPosition = m_position;
+ ++m_position;
+ while (m_position < m_inputLength) {
+ ch = m_input.at(m_position);
+ if (ch.isLetterOrNumber()) {
+ cmdStr += ch;
+ ++m_position;
+ } else {
+ break;
+ }
+ }
+ m_endPosition = m_position;
+ if (cmdStr.isEmpty()) {
+ if (m_position < m_inputLength) {
+ enterPara();
+ if (m_input.at(m_position).isSpace()) {
+ skipAllSpaces();
+ appendChar(QLatin1Char(' '));
+ } else {
+ appendChar(m_input.at(m_position++));
+ }
+ }
+ } else {
+ // Ignore quoting atoms to make appendToCode()
+ // append to the correct atom.
+ if (!s_quoting || !isQuote(m_private->m_text.lastAtom()))
+ m_lastAtom = m_private->m_text.lastAtom();
+
+ int cmd = s_utilities.cmdHash.value(cmdStr, NOT_A_CMD);
+ switch (cmd) {
+ case CMD_A:
+ enterPara();
+ p1 = getArgument();
+ appendAtom(Atom(Atom::FormattingLeft, ATOM_FORMATTING_PARAMETER));
+ appendAtom(Atom(Atom::String, p1));
+ appendAtom(Atom(Atom::FormattingRight, ATOM_FORMATTING_PARAMETER));
+ m_private->m_params.insert(p1);
+ break;
+ case CMD_BADCODE:
+ leavePara();
+ appendAtom(Atom(Atom::CodeBad,
+ getCode(CMD_BADCODE, marker, getMetaCommandArgument(cmdStr))));
+ break;
+ case CMD_BR:
+ enterPara();
+ appendAtom(Atom(Atom::BR));
+ break;
+ case CMD_BOLD:
+ location().warning(QStringLiteral("'\\bold' is deprecated. Use '\\b'"));
+ Q_FALLTHROUGH();
+ case CMD_B:
+ startFormat(ATOM_FORMATTING_BOLD, cmd);
+ break;
+ case CMD_BRIEF:
+ leavePara();
+ enterPara(Atom::BriefLeft, Atom::BriefRight);
+ break;
+ case CMD_C:
+ enterPara();
+ p1 = untabifyEtc(getArgument(ArgumentParsingOptions::Verbatim));
+ marker = CodeMarker::markerForCode(p1);
+ appendAtom(Atom(Atom::C, marker->markedUpCode(p1, nullptr, location())));
+ break;
+ case CMD_CAPTION:
+ leavePara();
+ enterPara(Atom::CaptionLeft, Atom::CaptionRight);
+ break;
+ case CMD_CODE:
+ leavePara();
+ appendAtom(Atom(Atom::Code, getCode(CMD_CODE, nullptr, getMetaCommandArgument(cmdStr))));
+ break;
+ case CMD_QML:
+ leavePara();
+ appendAtom(Atom(Atom::Qml,
+ getCode(CMD_QML, CodeMarker::markerForLanguage(QLatin1String("QML")),
+ getMetaCommandArgument(cmdStr))));
+ break;
+ case CMD_DETAILS:
+ leavePara();
+ appendAtom(Atom(Atom::DetailsLeft, getArgument()));
+ m_openedCommands.push(cmd);
+ break;
+ case CMD_ENDDETAILS:
+ leavePara();
+ appendAtom(Atom(Atom::DetailsRight));
+ closeCommand(cmd);
+ break;
+ case CMD_DIV:
+ leavePara();
+ p1 = getArgument(ArgumentParsingOptions::Verbatim);
+ appendAtom(Atom(Atom::DivLeft, p1));
+ m_openedCommands.push(cmd);
+ break;
+ case CMD_ENDDIV:
+ leavePara();
+ appendAtom(Atom(Atom::DivRight));
+ closeCommand(cmd);
+ break;
+ case CMD_CODELINE:
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, " "));
+ }
+ if (isCode(m_lastAtom) && m_lastAtom->string().endsWith("\n\n"))
+ m_lastAtom->chopString();
+ appendToCode("\n");
+ break;
+ case CMD_DOTS: {
+ QString arg = getOptionalArgument();
+ if (arg.isEmpty())
+ arg = "4";
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, arg));
+ }
+ if (isCode(m_lastAtom) && m_lastAtom->string().endsWith("\n\n"))
+ m_lastAtom->chopString();
+
+ int indent = arg.toInt();
+ for (int i = 0; i < indent; ++i)
+ appendToCode(" ");
+ appendToCode("...\n");
+ break;
+ }
+ case CMD_ELSE:
+ if (!preprocessorSkipping.empty()) {
+ if (preprocessorSkipping.top()) {
+ --numPreprocessorSkipping;
+ } else {
+ ++numPreprocessorSkipping;
+ }
+ preprocessorSkipping.top() = !preprocessorSkipping.top();
+ (void)getRestOfLine(); // ### should ensure that it's empty
+ if (numPreprocessorSkipping)
+ skipToNextPreprocessorCommand();
+ } else {
+ location().warning(
+ QStringLiteral("Unexpected '\\%1'").arg(cmdName(CMD_ELSE)));
+ }
+ break;
+ case CMD_ENDCODE:
+ closeCommand(cmd);
+ break;
+ case CMD_ENDQML:
+ closeCommand(cmd);
+ break;
+ case CMD_ENDFOOTNOTE:
+ if (closeCommand(cmd)) {
+ leavePara();
+ appendAtom(Atom(Atom::FootnoteRight));
+ }
+ break;
+ case CMD_ENDIF:
+ if (preprocessorSkipping.size() > 0) {
+ if (preprocessorSkipping.pop())
+ --numPreprocessorSkipping;
+ (void)getRestOfLine(); // ### should ensure that it's empty
+ if (numPreprocessorSkipping)
+ skipToNextPreprocessorCommand();
+ } else {
+ location().warning(
+ QStringLiteral("Unexpected '\\%1'").arg(cmdName(CMD_ENDIF)));
+ }
+ break;
+ case CMD_ENDLEGALESE:
+ if (closeCommand(cmd)) {
+ leavePara();
+ appendAtom(Atom(Atom::LegaleseRight));
+ }
+ break;
+ case CMD_ENDLINK:
+ if (closeCommand(cmd)) {
+ if (m_private->m_text.lastAtom()->type() == Atom::String
+ && m_private->m_text.lastAtom()->string().endsWith(QLatin1Char(' ')))
+ m_private->m_text.lastAtom()->chopString();
+ appendAtom(Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK));
+ }
+ break;
+ case CMD_ENDLIST:
+ if (closeCommand(cmd)) {
+ leavePara();
+ if (m_openedLists.top().isStarted()) {
+ appendAtom(Atom(Atom::ListItemRight, m_openedLists.top().styleString()));
+ appendAtom(Atom(Atom::ListRight, m_openedLists.top().styleString()));
+ }
+ m_openedLists.pop();
+ }
+ break;
+ case CMD_ENDOMIT:
+ closeCommand(cmd);
+ break;
+ case CMD_ENDQUOTATION:
+ if (closeCommand(cmd)) {
+ leavePara();
+ appendAtom(Atom(Atom::QuotationRight));
+ }
+ break;
+ case CMD_ENDRAW:
+ location().warning(
+ QStringLiteral("Unexpected '\\%1'").arg(cmdName(CMD_ENDRAW)));
+ break;
+ case CMD_ENDSECTION1:
+ endSection(Doc::Section1, cmd);
+ break;
+ case CMD_ENDSECTION2:
+ endSection(Doc::Section2, cmd);
+ break;
+ case CMD_ENDSECTION3:
+ endSection(Doc::Section3, cmd);
+ break;
+ case CMD_ENDSECTION4:
+ endSection(Doc::Section4, cmd);
+ break;
+ case CMD_ENDSIDEBAR:
+ if (closeCommand(cmd)) {
+ leavePara();
+ appendAtom(Atom(Atom::SidebarRight));
+ }
+ break;
+ case CMD_ENDTABLE:
+ if (closeCommand(cmd)) {
+ leaveTableRow();
+ appendAtom(Atom(Atom::TableRight));
+ }
+ break;
+ case CMD_FOOTNOTE:
+ if (openCommand(cmd)) {
+ enterPara();
+ appendAtom(Atom(Atom::FootnoteLeft));
+ }
+ break;
+ case CMD_ANNOTATEDLIST:
+ appendAtom(Atom(Atom::AnnotatedList, getArgument()));
+ break;
+ case CMD_SINCELIST:
+ leavePara();
+ appendAtom(Atom(Atom::SinceList, getRestOfLine().simplified()));
+ break;
+ case CMD_GENERATELIST: {
+ QString arg1 = getArgument();
+ QString arg2 = getOptionalArgument();
+ if (!arg2.isEmpty())
+ arg1 += " " + arg2;
+ appendAtom(Atom(Atom::GeneratedList, arg1));
+ } break;
+ case CMD_HEADER:
+ if (m_openedCommands.top() == CMD_TABLE) {
+ leaveTableRow();
+ appendAtom(Atom(Atom::TableHeaderLeft));
+ m_inTableHeader = true;
+ } else {
+ if (m_openedCommands.contains(CMD_TABLE))
+ location().warning(QStringLiteral("Cannot use '\\%1' within '\\%2'")
+ .arg(cmdName(CMD_HEADER),
+ cmdName(m_openedCommands.top())));
+ else
+ location().warning(
+ QStringLiteral("Cannot use '\\%1' outside of '\\%2'")
+ .arg(cmdName(CMD_HEADER), cmdName(CMD_TABLE)));
+ }
+ break;
+ case CMD_I:
+ location().warning(QStringLiteral(
+ "'\\i' is deprecated. Use '\\e' for italic or '\\li' for list item"));
+ Q_FALLTHROUGH();
+ case CMD_E:
+ startFormat(ATOM_FORMATTING_ITALIC, cmd);
+ break;
+ case CMD_HR:
+ leavePara();
+ appendAtom(Atom(Atom::HR));
+ break;
+ case CMD_IF:
+ preprocessorSkipping.push(!Tokenizer::isTrue(getRestOfLine()));
+ if (preprocessorSkipping.top())
+ ++numPreprocessorSkipping;
+ if (numPreprocessorSkipping)
+ skipToNextPreprocessorCommand();
+ break;
+ case CMD_IMAGE:
+ leaveValueList();
+ appendAtom(Atom(Atom::Image, getArgument()));
+ appendAtom(Atom(Atom::ImageText, getRestOfLine()));
+ break;
+ case CMD_IMPORTANT:
+ leavePara();
+ enterPara(Atom::ImportantLeft, Atom::ImportantRight);
+ break;
+ case CMD_INCLUDE:
+ case CMD_INPUT: {
+ QString fileName = getArgument();
+ QStringList parameters;
+ QString identifier;
+ if (isLeftBraceAhead()) {
+ identifier = getArgument();
+ while (isLeftBraceAhead() && parameters.size() < 9)
+ parameters << getArgument();
+ } else {
+ identifier = getRestOfLine();
+ }
+ include(fileName, identifier, parameters);
+ break;
+ }
+ case CMD_INLINEIMAGE:
+ enterPara();
+ appendAtom(Atom(Atom::InlineImage, getArgument()));
+ //Append ImageText only if the following
+ //argument is enclosed in braces.
+ if (isLeftBraceAhead()) {
+ appendAtom(Atom(Atom::ImageText, getArgument()));
+ appendAtom(Atom(Atom::String, " "));
+ }
+ break;
+ case CMD_INDEX:
+ if (m_paragraphState == OutsideParagraph) {
+ enterPara();
+ m_indexStartedParagraph = true;
+ } else {
+ const Atom *last = m_private->m_text.lastAtom();
+ if (m_indexStartedParagraph
+ && (last->type() != Atom::FormattingRight
+ || last->string() != ATOM_FORMATTING_INDEX))
+ m_indexStartedParagraph = false;
+ }
+ startFormat(ATOM_FORMATTING_INDEX, cmd);
+ break;
+ case CMD_KEYWORD:
+ leavePara();
+ insertKeyword(getRestOfLine());
+ break;
+ case CMD_L:
+ enterPara();
+ if (isLeftBracketAhead())
+ p2 = getBracketedArgument();
+
+ p1 = getArgument();
+
+ appendAtom(LinkAtom(p1, p2, location()));
+
+ if (isLeftBraceAhead()) {
+ currentLinkAtom = m_private->m_text.lastAtom();
+ startFormat(ATOM_FORMATTING_LINK, cmd);
+ } else {
+ appendAtom(Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK));
+ appendAtom(Atom(Atom::String, cleanLink(p1)));
+ appendAtom(Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK));
+ }
+
+ p2.clear();
+
+ break;
+ case CMD_LEGALESE:
+ leavePara();
+ if (openCommand(cmd))
+ appendAtom(Atom(Atom::LegaleseLeft));
+ docPrivate->m_hasLegalese = true;
+ break;
+ case CMD_LINK:
+ if (openCommand(cmd)) {
+ enterPara();
+ p1 = getArgument();
+ appendAtom(Atom(Atom::Link, p1));
+ appendAtom(Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK));
+ skipSpacesOrOneEndl();
+ }
+ break;
+ case CMD_LIST:
+ if (openCommand(cmd)) {
+ leavePara();
+ m_openedLists.push(OpenedList(location(), getOptionalArgument()));
+ }
+ break;
+ case CMD_META:
+ m_private->constructExtra();
+ p1 = getArgument();
+ m_private->extra->m_metaMap.insert(p1, getArgument());
+ break;
+ case CMD_NOTE:
+ leavePara();
+ enterPara(Atom::NoteLeft, Atom::NoteRight);
+ break;
+ case CMD_O:
+ location().warning(QStringLiteral("'\\o' is deprecated. Use '\\li'"));
+ Q_FALLTHROUGH();
+ case CMD_LI:
+ leavePara();
+ if (m_openedCommands.top() == CMD_LIST) {
+ if (m_openedLists.top().isStarted())
+ appendAtom(Atom(Atom::ListItemRight, m_openedLists.top().styleString()));
+ else
+ appendAtom(Atom(Atom::ListLeft, m_openedLists.top().styleString()));
+ m_openedLists.top().next();
+ appendAtom(Atom(Atom::ListItemNumber, m_openedLists.top().numberString()));
+ appendAtom(Atom(Atom::ListItemLeft, m_openedLists.top().styleString()));
+ enterPara();
+ } else if (m_openedCommands.top() == CMD_TABLE) {
+ p1 = "1,1";
+ p2.clear();
+ if (isLeftBraceAhead()) {
+ p1 = getArgument();
+ if (isLeftBraceAhead())
+ p2 = getArgument();
+ }
+
+ if (!m_inTableHeader && !m_inTableRow) {
+ location().warning(
+ QStringLiteral("Missing '\\%1' or '\\%2' before '\\%3'")
+ .arg(cmdName(CMD_HEADER), cmdName(CMD_ROW),
+ cmdName(CMD_LI)));
+ appendAtom(Atom(Atom::TableRowLeft));
+ m_inTableRow = true;
+ } else if (m_inTableItem) {
+ appendAtom(Atom(Atom::TableItemRight));
+ m_inTableItem = false;
+ }
+
+ appendAtom(Atom(Atom::TableItemLeft, p1, p2));
+ m_inTableItem = true;
+ } else
+ location().warning(
+ QStringLiteral("Command '\\%1' outside of '\\%2' and '\\%3'")
+ .arg(cmdName(cmd), cmdName(CMD_LIST), cmdName(CMD_TABLE)));
+ break;
+ case CMD_OMIT:
+ getUntilEnd(cmd);
+ break;
+ case CMD_OMITVALUE: {
+ leavePara();
+ p1 = getArgument();
+ if (!m_private->m_enumItemList.contains(p1))
+ m_private->m_enumItemList.append(p1);
+ if (!m_private->m_omitEnumItemList.contains(p1))
+ m_private->m_omitEnumItemList.append(p1);
+ skipSpacesOrOneEndl();
+ // Skip potential description paragraph
+ while (m_position < m_inputLength && !isBlankLine()) {
+ skipAllSpaces();
+ if (qsizetype pos = m_position; pos < m_input.size()
+ && m_input.at(pos++).unicode() == '\\') {
+ QString nextCmdStr;
+ while (pos < m_input.size() && m_input[pos].isLetterOrNumber())
+ nextCmdStr += m_input[pos++];
+ int nextCmd = s_utilities.cmdHash.value(cmdStr, NOT_A_CMD);
+ if (nextCmd == cmd || nextCmd == CMD_VALUE)
+ break;
+ }
+ getRestOfLine();
+ }
+ break;
+ }
+ case CMD_COMPARESWITH:
+ leavePara();
+ p1 = getRestOfLine();
+ if (openCommand(cmd))
+ appendAtom(Atom(Atom::ComparesLeft, p1));
+ break;
+ case CMD_ENDCOMPARESWITH:
+ if (closeCommand(cmd)) {
+ leavePara();
+ appendAtom(Atom(Atom::ComparesRight));
+ processComparesWithCommand(m_private, location());
+ }
+ break;
+ case CMD_PRINTLINE: {
+ leavePara();
+ QString rest = getRestOfLine();
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, rest));
+ }
+ appendToCode(m_quoter.quoteLine(location(), cmdStr, rest));
+ break;
+ }
+ case CMD_PRINTTO: {
+ leavePara();
+ QString rest = getRestOfLine();
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, rest));
+ }
+ appendToCode(m_quoter.quoteTo(location(), cmdStr, rest));
+ break;
+ }
+ case CMD_PRINTUNTIL: {
+ leavePara();
+ QString rest = getRestOfLine();
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, rest));
+ }
+ appendToCode(m_quoter.quoteUntil(location(), cmdStr, rest));
+ break;
+ }
+ case CMD_QUOTATION:
+ if (openCommand(cmd)) {
+ leavePara();
+ appendAtom(Atom(Atom::QuotationLeft));
+ }
+ break;
+ case CMD_QUOTEFILE: {
+ leavePara();
+
+ QString fileName = getArgument();
+ quoteFromFile(fileName);
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, fileName));
+ }
+ appendAtom(Atom(Atom::Code, m_quoter.quoteTo(location(), cmdStr, QString())));
+ m_quoter.reset();
+ break;
+ }
+ case CMD_QUOTEFROMFILE: {
+ leavePara();
+ QString arg = getArgument();
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, arg));
+ }
+ quoteFromFile(arg);
+ break;
+ }
+ case CMD_RAW:
+ leavePara();
+ p1 = getRestOfLine();
+ if (p1.isEmpty())
+ location().warning(QStringLiteral("Missing format name after '\\%1'")
+ .arg(cmdName(CMD_RAW)));
+ appendAtom(Atom(Atom::FormatIf, p1));
+ appendAtom(Atom(Atom::RawString, untabifyEtc(getUntilEnd(cmd))));
+ appendAtom(Atom(Atom::FormatElse));
+ appendAtom(Atom(Atom::FormatEndif));
+ break;
+ case CMD_ROW:
+ if (m_openedCommands.top() == CMD_TABLE) {
+ p1.clear();
+ if (isLeftBraceAhead())
+ p1 = getArgument(ArgumentParsingOptions::Verbatim);
+ leaveTableRow();
+ appendAtom(Atom(Atom::TableRowLeft, p1));
+ m_inTableRow = true;
+ } else {
+ if (m_openedCommands.contains(CMD_TABLE))
+ location().warning(QStringLiteral("Cannot use '\\%1' within '\\%2'")
+ .arg(cmdName(CMD_ROW),
+ cmdName(m_openedCommands.top())));
+ else
+ location().warning(QStringLiteral("Cannot use '\\%1' outside of '\\%2'")
+ .arg(cmdName(CMD_ROW), cmdName(CMD_TABLE)));
+ }
+ break;
+ case CMD_SA:
+ parseAlso();
+ break;
+ case CMD_SECTION1:
+ startSection(Doc::Section1, cmd);
+ break;
+ case CMD_SECTION2:
+ startSection(Doc::Section2, cmd);
+ break;
+ case CMD_SECTION3:
+ startSection(Doc::Section3, cmd);
+ break;
+ case CMD_SECTION4:
+ startSection(Doc::Section4, cmd);
+ break;
+ case CMD_SIDEBAR:
+ if (openCommand(cmd)) {
+ leavePara();
+ appendAtom(Atom(Atom::SidebarLeft));
+ }
+ break;
+ case CMD_SKIPLINE: {
+ leavePara();
+ QString rest = getRestOfLine();
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, rest));
+ }
+ m_quoter.quoteLine(location(), cmdStr, rest);
+ break;
+ }
+ case CMD_SKIPTO: {
+ leavePara();
+ QString rest = getRestOfLine();
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, rest));
+ }
+ m_quoter.quoteTo(location(), cmdStr, rest);
+ break;
+ }
+ case CMD_SKIPUNTIL: {
+ leavePara();
+ QString rest = getRestOfLine();
+ if (s_quoting) {
+ appendAtom(Atom(Atom::CodeQuoteCommand, cmdStr));
+ appendAtom(Atom(Atom::CodeQuoteArgument, rest));
+ }
+ m_quoter.quoteUntil(location(), cmdStr, rest);
+ break;
+ }
+ case CMD_SPAN:
+ p1 = ATOM_FORMATTING_SPAN + getArgument(ArgumentParsingOptions::Verbatim);
+ startFormat(p1, cmd);
+ break;
+ case CMD_SNIPPET: {
+ leavePara();
+ QString snippet = getArgument();
+ QString identifier = getRestOfLine();
+ if (s_quoting) {
+ appendAtom(Atom(Atom::SnippetCommand, cmdStr));
+ appendAtom(Atom(Atom::SnippetLocation, snippet));
+ appendAtom(Atom(Atom::SnippetIdentifier, identifier));
+ }
+ marker = CodeMarker::markerForFileName(snippet);
+ quoteFromFile(snippet);
+ appendToCode(m_quoter.quoteSnippet(location(), identifier), marker->atomType());
+ break;
+ }
+ case CMD_SUB:
+ startFormat(ATOM_FORMATTING_SUBSCRIPT, cmd);
+ break;
+ case CMD_SUP:
+ startFormat(ATOM_FORMATTING_SUPERSCRIPT, cmd);
+ break;
+ case CMD_TABLE:
+ leaveValueList();
+ p1 = getOptionalArgument();
+ p2 = getOptionalArgument();
+ if (openCommand(cmd)) {
+ leavePara();
+ appendAtom(Atom(Atom::TableLeft, p1, p2));
+ m_inTableHeader = false;
+ m_inTableRow = false;
+ m_inTableItem = false;
+ }
+ break;
+ case CMD_TABLEOFCONTENTS:
+ p1 = "1";
+ if (isLeftBraceAhead())
+ p1 = getArgument();
+ p1 += QLatin1Char(',');
+ p1 += QString::number((int)getSectioningUnit());
+ appendAtom(Atom(Atom::TableOfContents, p1));
+ break;
+ case CMD_TARGET:
+ if (m_openedCommands.top() == CMD_TABLE && !m_inTableItem) {
+ location().warning("Found a \\target command outside table item in a table.\n"
+ "Move the \\target inside the \\li to resolve this warning.");
+ }
+ insertTarget(getRestOfLine());
+ break;
+ case CMD_TM:
+ // Ignore command while parsing \section<N> argument
+ if (m_paragraphState != InSingleLineParagraph)
+ startFormat(ATOM_FORMATTING_TRADEMARK, cmd);
+ break;
+ case CMD_TT:
+ startFormat(ATOM_FORMATTING_TELETYPE, cmd);
+ break;
+ case CMD_UICONTROL:
+ startFormat(ATOM_FORMATTING_UICONTROL, cmd);
+ break;
+ case CMD_UNDERLINE:
+ startFormat(ATOM_FORMATTING_UNDERLINE, cmd);
+ break;
+ case CMD_UNICODE: {
+ enterPara();
+ p1 = getArgument();
+ bool ok;
+ uint unicodeChar = p1.toUInt(&ok, 0);
+ if (!ok || (unicodeChar == 0x0000) || (unicodeChar > 0xFFFE))
+ location().warning(
+ QStringLiteral("Invalid Unicode character '%1' specified with '%2'")
+ .arg(p1, cmdName(CMD_UNICODE)));
+ else
+ appendAtom(Atom(Atom::String, QChar(unicodeChar)));
+ break;
+ }
+ case CMD_VALUE:
+ leaveValue();
+ if (m_openedLists.top().style() == OpenedList::Value) {
+ QString p2;
+ p1 = getArgument();
+ if (p1.startsWith(QLatin1String("[since "))
+ && p1.endsWith(QLatin1String("]"))) {
+ p2 = p1.mid(7, p1.size() - 8);
+ p1 = getArgument();
+ }
+ if (!m_private->m_enumItemList.contains(p1))
+ m_private->m_enumItemList.append(p1);
+
+ m_openedLists.top().next();
+ appendAtom(Atom(Atom::ListTagLeft, ATOM_LIST_VALUE));
+ appendAtom(Atom(Atom::String, p1));
+ appendAtom(Atom(Atom::ListTagRight, ATOM_LIST_VALUE));
+ if (!p2.isEmpty()) {
+ appendAtom(Atom(Atom::SinceTagLeft, ATOM_LIST_VALUE));
+ appendAtom(Atom(Atom::String, p2));
+ appendAtom(Atom(Atom::SinceTagRight, ATOM_LIST_VALUE));
+ }
+ appendAtom(Atom(Atom::ListItemLeft, ATOM_LIST_VALUE));
+
+ skipSpacesOrOneEndl();
+ if (isBlankLine())
+ appendAtom(Atom(Atom::Nop));
+ } else {
+ // ### unknown problems
+ }
+ break;
+ case CMD_WARNING:
+ leavePara();
+ enterPara(Atom::WarningLeft, Atom::WarningRight);
+ break;
+ case CMD_OVERLOAD:
+ leavePara();
+ m_private->m_metacommandsUsed.insert(cmdStr);
+ p1.clear();
+ if (!isBlankLine())
+ p1 = getRestOfLine();
+ if (!p1.isEmpty()) {
+ appendAtom(Atom(Atom::ParaLeft));
+ appendAtom(Atom(Atom::String, "This function overloads "));
+ appendAtom(Atom(Atom::AutoLink, p1));
+ appendAtom(Atom(Atom::String, "."));
+ appendAtom(Atom(Atom::ParaRight));
+ } else {
+ appendAtom(Atom(Atom::ParaLeft));
+ appendAtom(Atom(Atom::String, "This is an overloaded function."));
+ appendAtom(Atom(Atom::ParaRight));
+ p1 = getMetaCommandArgument(cmdStr);
+ }
+ m_private->m_metaCommandMap[cmdStr].append(ArgPair(p1, QString()));
+ break;
+ case NOT_A_CMD:
+ if (metaCommandSet.contains(cmdStr)) {
+ QString arg;
+ QString bracketedArg;
+ m_private->m_metacommandsUsed.insert(cmdStr);
+ if (isLeftBracketAhead())
+ bracketedArg = getBracketedArgument();
+ // Force a linebreak after \obsolete or \deprecated
+ // to treat potential arguments as a new text paragraph.
+ if (m_position < m_inputLength
+ && (cmdStr == QLatin1String("obsolete")
+ || cmdStr == QLatin1String("deprecated")))
+ m_input[m_position] = '\n';
+ else
+ arg = getMetaCommandArgument(cmdStr);
+ m_private->m_metaCommandMap[cmdStr].append(ArgPair(arg, bracketedArg));
+ if (possibleTopics.contains(cmdStr)) {
+ if (!cmdStr.endsWith(QLatin1String("propertygroup")))
+ m_private->m_topics.append(Topic(cmdStr, arg));
+ }
+ } else if (s_utilities.macroHash.contains(cmdStr)) {
+ const Macro &macro = s_utilities.macroHash.value(cmdStr);
+ QStringList macroArgs;
+ int numPendingFi = 0;
+ int numFormatDefs = 0;
+ for (auto it = macro.m_otherDefs.constBegin();
+ it != macro.m_otherDefs.constEnd(); ++it) {
+ if (it.key() != "match") {
+ if (numFormatDefs == 0)
+ macroArgs = getMacroArguments(cmdStr, macro);
+ appendAtom(Atom(Atom::FormatIf, it.key()));
+ expandMacro(*it, macroArgs);
+ ++numFormatDefs;
+ if (it == macro.m_otherDefs.constEnd()) {
+ appendAtom(Atom(Atom::FormatEndif));
+ } else {
+ appendAtom(Atom(Atom::FormatElse));
+ ++numPendingFi;
+ }
+ }
+ }
+ while (numPendingFi-- > 0)
+ appendAtom(Atom(Atom::FormatEndif));
+
+ if (!macro.m_defaultDef.isEmpty()) {
+ if (numFormatDefs > 0) {
+ macro.m_defaultDefLocation.warning(
+ QStringLiteral("Macro cannot have both "
+ "format-specific and qdoc-"
+ "syntax definitions"));
+ } else {
+ QString expanded = expandMacroToString(cmdStr, macro);
+ m_input.replace(m_backslashPosition,
+ m_endPosition - m_backslashPosition, expanded);
+ m_inputLength = m_input.size();
+ m_position = m_backslashPosition;
+ }
+ }
+ } else if (isAutoLinkString(cmdStr)) {
+ appendWord(cmdStr);
+ } else {
+ if (!cmdStr.endsWith("propertygroup")) {
+ // The QML property group commands are no longer required
+ // for grouping QML properties. They are allowed but ignored.
+ location().warning(QStringLiteral("Unknown command '\\%1'").arg(cmdStr),
+ detailsUnknownCommand(metaCommandSet, cmdStr));
+ }
+ enterPara();
+ appendAtom(Atom(Atom::UnknownCommand, cmdStr));
+ }
+ }
+ } // case '\\' (qdoc markup command)
+ break;
+ }
+ case '-': { // Catch en-dash (--) and em-dash (---) markup here.
+ enterPara();
+ qsizetype dashCount = 1;
+ ++m_position;
+
+ // Figure out how many hyphens in a row.
+ while ((m_position < m_inputLength) && (m_input.at(m_position) == '-')) {
+ ++dashCount;
+ ++m_position;
+ }
+
+ if (dashCount == 3) {
+ // 3 hyphens, append an em-dash character.
+ const QChar emDash(8212);
+ appendChar(emDash);
+ } else if (dashCount == 2) {
+ // 2 hyphens; append an en-dash character.
+ const QChar enDash(8211);
+ appendChar(enDash);
+ } else {
+ // dashCount is either one or more than three. Append a hyphen
+ // the appropriate number of times. This ensures '----' doesn't
+ // end up as an em-dash followed by a hyphen in the output.
+ for (qsizetype i = 0; i < dashCount; ++i)
+ appendChar('-');
+ }
+ break;
+ }
+ case '{':
+ enterPara();
+ appendChar('{');
+ ++m_braceDepth;
+ ++m_position;
+ break;
+ case '}': {
+ --m_braceDepth;
+ ++m_position;
+
+ auto format = m_pendingFormats.find(m_braceDepth);
+ if (format == m_pendingFormats.end()) {
+ enterPara();
+ appendChar('}');
+ } else {
+ const auto &last{m_private->m_text.lastAtom()->string()};
+ appendAtom(Atom(Atom::FormattingRight, *format));
+ if (*format == ATOM_FORMATTING_INDEX) {
+ if (m_indexStartedParagraph)
+ skipAllSpaces();
+ } else if (*format == ATOM_FORMATTING_LINK) {
+ // hack for C++ to support links like
+ // \l{QString::}{count()}
+ if (currentLinkAtom && currentLinkAtom->string().endsWith("::")) {
+ QString suffix =
+ Text::subText(currentLinkAtom, m_private->m_text.lastAtom())
+ .toString();
+ currentLinkAtom->concatenateString(suffix);
+ }
+ currentLinkAtom = nullptr;
+ } else if (*format == ATOM_FORMATTING_TRADEMARK) {
+ m_private->m_text.lastAtom()->append(last);
+ }
+ m_pendingFormats.erase(format);
+ }
+ break;
+ }
+ // Do not parse content after '//!' comments
+ case '/': {
+ if (m_position + 2 < m_inputLength)
+ if (m_input.at(m_position + 1) == '/')
+ if (m_input.at(m_position + 2) == '!') {
+ m_position += 2;
+ getRestOfLine();
+ if (m_input.at(m_position - 1) == '\n')
+ --m_position;
+ break;
+ }
+ Q_FALLTHROUGH(); // fall through
+ }
+ default: {
+ bool newWord;
+ switch (m_private->m_text.lastAtom()->type()) {
+ case Atom::ParaLeft:
+ newWord = true;
+ break;
+ default:
+ newWord = false;
+ }
+
+ if (m_paragraphState == OutsideParagraph) {
+ if (ch.isSpace()) {
+ ++m_position;
+ newWord = false;
+ } else {
+ enterPara();
+ newWord = true;
+ }
+ } else {
+ if (ch.isSpace()) {
+ ++m_position;
+ if ((ch == '\n')
+ && (m_paragraphState == InSingleLineParagraph || isBlankLine())) {
+ leavePara();
+ newWord = false;
+ } else {
+ appendChar(' ');
+ newWord = true;
+ }
+ } else {
+ newWord = true;
+ }
+ }
+
+ if (newWord) {
+ qsizetype startPos = m_position;
+ // No auto-linking inside links
+ bool autolink = (!m_pendingFormats.isEmpty() &&
+ m_pendingFormats.last() == ATOM_FORMATTING_LINK) ?
+ false : isAutoLinkString(m_input, m_position);
+ if (m_position == startPos) {
+ if (!ch.isSpace()) {
+ appendChar(ch);
+ ++m_position;
+ }
+ } else {
+ QString word = m_input.mid(startPos, m_position - startPos);
+ if (autolink) {
+ if (s_ignoreWords.contains(word) || word.startsWith(QString("__")))
+ appendWord(word);
+ else
+ appendAtom(Atom(Atom::AutoLink, word));
+ } else {
+ appendWord(word);
+ }
+ }
+ }
+ } // default:
+ } // switch (ch.unicode())
+ }
+ leaveValueList();
+
+ // for compatibility
+ if (m_openedCommands.top() == CMD_LEGALESE) {
+ appendAtom(Atom(Atom::LegaleseRight));
+ m_openedCommands.pop();
+ }
+
+ if (m_openedCommands.top() != CMD_OMIT) {
+ location().warning(
+ QStringLiteral("Missing '\\%1'").arg(endCmdName(m_openedCommands.top())));
+ } else if (preprocessorSkipping.size() > 0) {
+ location().warning(QStringLiteral("Missing '\\%1'").arg(cmdName(CMD_ENDIF)));
+ }
+
+ if (m_currentSection > Doc::NoSection) {
+ appendAtom(Atom(Atom::SectionRight, QString::number(m_currentSection)));
+ m_currentSection = Doc::NoSection;
+ }
+
+ m_private->m_text.stripFirstAtom();
+}
+
+/*!
+ Returns the current location.
+ */
+Location &DocParser::location()
+{
+ while (!m_openedInputs.isEmpty() && m_openedInputs.top() <= m_position) {
+ m_cachedLocation.pop();
+ m_cachedPosition = m_openedInputs.pop();
+ }
+ while (m_cachedPosition < m_position)
+ m_cachedLocation.advance(m_input.at(m_cachedPosition++));
+ return m_cachedLocation;
+}
+
+QString DocParser::detailsUnknownCommand(const QSet<QString> &metaCommandSet, const QString &str)
+{
+ QSet<QString> commandSet = metaCommandSet;
+ int i = 0;
+ while (cmds[i].name != nullptr) {
+ commandSet.insert(cmds[i].name);
+ ++i;
+ }
+
+ QString best = nearestName(str, commandSet);
+ if (best.isEmpty())
+ return QString();
+ return QStringLiteral("Maybe you meant '\\%1'?").arg(best);
+}
+
+/*!
+ \internal
+
+ Issues a warning about the duplicate definition of a target or keyword in
+ at \a location. \a duplicateDefinition is the target being processed; the
+ already registered definition is \a previousDefinition.
+ */
+static void warnAboutPreexistingTarget(const Location &location, const QString &duplicateDefinition, const QString &previousDefinition)
+{
+ location.warning(
+ QStringLiteral("Duplicate target name '%1'. The previous occurrence is here: %2")
+ .arg(duplicateDefinition, previousDefinition));
+}
+
+/*!
+ \internal
+
+ \brief Registers \a target as a linkable entity.
+
+ The main purpose of this method is to register a target as defined
+ along with its location, so that becomes a valid link target from other
+ parts of the documentation.
+
+ If the \a target name is already registered, a warning is issued,
+ as multiple definitions are problematic.
+
+ \sa insertKeyword, target-command
+ */
+void DocParser::insertTarget(const QString &target)
+{
+ if (m_targetMap.contains(target))
+ return warnAboutPreexistingTarget(location(), target, m_targetMap[target].toString());
+
+ m_targetMap.insert(target, location());
+ m_private->constructExtra();
+
+ appendAtom(Atom(Atom::Target, target));
+ m_private->extra->m_targets.append(m_private->m_text.lastAtom());
+}
+
+/*!
+ \internal
+
+ \brief Registers \a keyword as a linkable entity.
+
+ The main purpose of this method is to register a keyword as defined
+ along with its location, so that becomes a valid link target from other
+ parts of the documentation.
+
+ If the \a keyword name is already registered, a warning is issued,
+ as multiple definitions are problematic.
+
+ \sa insertTarget, keyword-command
+ */
+void DocParser::insertKeyword(const QString &keyword)
+{
+ if (m_targetMap.contains(keyword))
+ return warnAboutPreexistingTarget(location(), keyword, m_targetMap[keyword].toString());
+
+ m_targetMap.insert(keyword, location());
+ m_private->constructExtra();
+
+ appendAtom(Atom(Atom::Keyword, keyword));
+ m_private->extra->m_keywords.append(m_private->m_text.lastAtom());
+}
+
+void DocParser::include(const QString &fileName, const QString &identifier, const QStringList &parameters)
+{
+ if (location().depth() > 16)
+ location().fatal(QStringLiteral("Too many nested '\\%1's").arg(cmdName(CMD_INCLUDE)));
+ QString filePath = Config::instance().getIncludeFilePath(fileName);
+ if (filePath.isEmpty()) {
+ location().warning(QStringLiteral("Cannot find qdoc include file '%1'").arg(fileName));
+ } else {
+ QFile inFile(filePath);
+ if (!inFile.open(QFile::ReadOnly)) {
+ location().warning(
+ QStringLiteral("Cannot open qdoc include file '%1'").arg(filePath));
+ } else {
+ location().push(fileName);
+ QTextStream inStream(&inFile);
+ QString includedContent = inStream.readAll();
+ inFile.close();
+
+ if (identifier.isEmpty()) {
+ expandArgumentsInString(includedContent, parameters);
+ m_input.insert(m_position, includedContent);
+ m_inputLength = m_input.size();
+ m_openedInputs.push(m_position + includedContent.size());
+ } else {
+ QStringList lineBuffer = includedContent.split(QLatin1Char('\n'));
+ qsizetype bufLen{lineBuffer.size()};
+ qsizetype i;
+ QStringView trimmedLine;
+ for (i = 0; i < bufLen; ++i) {
+ trimmedLine = QStringView{lineBuffer[i]}.trimmed();
+ if (trimmedLine.startsWith(QLatin1String("//!")) &&
+ trimmedLine.contains(identifier))
+ break;
+ }
+ if (i < bufLen - 1) {
+ ++i;
+ } else {
+ location().warning(
+ QStringLiteral("Cannot find '%1' in '%2'").arg(identifier, filePath));
+ return;
+ }
+ QString result;
+ do {
+ trimmedLine = QStringView{lineBuffer[i]}.trimmed();
+ if (trimmedLine.startsWith(QLatin1String("//!")) &&
+ trimmedLine.contains(identifier))
+ break;
+ else
+ result += lineBuffer[i] + QLatin1Char('\n');
+ ++i;
+ } while (i < bufLen);
+
+ expandArgumentsInString(result, parameters);
+ if (result.isEmpty()) {
+ location().warning(QStringLiteral("Empty qdoc snippet '%1' in '%2'")
+ .arg(identifier, filePath));
+ } else {
+ m_input.insert(m_position, result);
+ m_inputLength = m_input.size();
+ m_openedInputs.push(m_position + result.size());
+ }
+ }
+ }
+ }
+}
+
+void DocParser::startFormat(const QString &format, int cmd)
+{
+ enterPara();
+
+ for (const auto &item : std::as_const(m_pendingFormats)) {
+ if (item == format) {
+ location().warning(QStringLiteral("Cannot nest '\\%1' commands").arg(cmdName(cmd)));
+ return;
+ }
+ }
+
+ appendAtom(Atom(Atom::FormattingLeft, format));
+
+ if (isLeftBraceAhead()) {
+ skipSpacesOrOneEndl();
+ m_pendingFormats.insert(m_braceDepth, format);
+ ++m_braceDepth;
+ ++m_position;
+ } else {
+ const auto &arg{getArgument()};
+ appendAtom(Atom(Atom::String, arg));
+ appendAtom(Atom(Atom::FormattingRight, format));
+ if (format == ATOM_FORMATTING_INDEX && m_indexStartedParagraph) {
+ skipAllSpaces();
+ m_indexStartedParagraph = false;
+ } else if (format == ATOM_FORMATTING_TRADEMARK) {
+ m_private->m_text.lastAtom()->append(arg);
+ }
+ }
+}
+
+bool DocParser::openCommand(int cmd)
+{
+ int outer = m_openedCommands.top();
+ bool ok = true;
+
+ if (cmd == CMD_COMPARESWITH && m_openedCommands.contains(cmd)) {
+ location().warning(u"Cannot nest '\\%1' commands"_s.arg(cmdName(cmd)));
+ return false;
+ } else if (cmd != CMD_LINK) {
+ if (outer == CMD_LIST) {
+ ok = (cmd == CMD_FOOTNOTE || cmd == CMD_LIST);
+ } else if (outer == CMD_SIDEBAR) {
+ ok = (cmd == CMD_LIST || cmd == CMD_QUOTATION || cmd == CMD_SIDEBAR);
+ } else if (outer == CMD_QUOTATION) {
+ ok = (cmd == CMD_LIST);
+ } else if (outer == CMD_TABLE) {
+ ok = (cmd == CMD_LIST || cmd == CMD_FOOTNOTE || cmd == CMD_QUOTATION);
+ } else if (outer == CMD_FOOTNOTE || outer == CMD_LINK) {
+ ok = false;
+ }
+ }
+
+ if (ok) {
+ m_openedCommands.push(cmd);
+ } else {
+ location().warning(
+ QStringLiteral("Can't use '\\%1' in '\\%2'").arg(cmdName(cmd), cmdName(outer)));
+ }
+ return ok;
+}
+
+/*!
+ Returns \c true if \a word qualifies for auto-linking.
+
+ A word qualifies for auto-linking if either:
+
+ \list
+ \li It is composed of only upper and lowercase characters
+ \li AND It contains at least one uppercase character that is not
+ the first character of word
+ \li AND it contains at least two lowercase characters
+ \endlist
+
+ Or
+
+ \list
+ \li It is composed only of uppercase characters, lowercase
+ characters, characters in [_@] and the \c {"::"} sequence.
+ \li It contains at least one uppercase character that is not
+ the first character of word or it contains at least one
+ lowercase character
+ \li AND it contains at least one character in [_@] or it
+ contains at least one \c {"::"} sequence.
+ \endlist
+
+ Inserting or suffixing, but not prefixing, any sequence in [0-9]+
+ in a word that qualifies for auto-linking by the above rules
+ preserves the auto-linkability of the word.
+
+ Suffixing the sequence \c {"()"} to a word that qualifies for
+ auto-linking by the above rules preserves the auto-linkability of
+ a word.
+
+ FInally, a word qualifies for auto-linking if:
+
+ \list
+ \li It is composed of only uppercase characters, lowercase
+ characters and the sequence \c {"()"}
+ \li AND it contains one lowercase character and a sequence of zero, one
+ or two upper or lowercase characters
+ \li AND it contains exactly one sequence \c {"()"}
+ \li AND it contains one sequence \c {"()"} as the last two
+ characters of word
+ \endlist
+
+ For example, \c {"fOo"}, \c {"FooBar"} and \c {"foobaR"} qualify
+ for auto-linking by the first rule.
+
+ \c {"QT_DEBUG"}, \c {"::Qt"} and \c {"std::move"} qualifies for
+ auto-linking by the second rule.
+
+ \c {"SIMDVector256"} qualifies by suffixing \c {"SIMDVector"},
+ which qualifies by the first rule, with the sequence \c {"256"}
+
+ \c {"FooBar::Bar()"} qualifies by suffixing \c {"FooBar::Bar"},
+ which qualifies by the first and second rule, with the sequence \c
+ {"()"}.
+
+ \c {"Foo()"} and \c {"a()"} qualifies by the last rule.
+
+ Instead, \c {"Q"}, \c {"flower"}, \c {"_"} and \c {"()"} do not
+ qualify for auto-linking.
+
+ The rules are intended as a heuristic to catch common cases in the
+ Qt documentation where a word might represent an important
+ documented element such as a class or a method that could be
+ linked to while at the same time avoiding catching common words
+ such as \c {"A"} or \c {"Nonetheless"}.
+
+ The heuristic assumes that Qt's codebase respects a style where
+ camelCasing is the standard for most of the elements, a function
+ call is identified by the use of parenthesis and certain elements,
+ such as macros, might be fully uppercase.
+
+ Furthemore, it assumes that the Qt codebase is written in a
+ language that has an identifier grammar similar to the one for
+ C++.
+*/
+inline bool DocParser::isAutoLinkString(const QString &word)
+{
+ qsizetype start = 0;
+ return isAutoLinkString(word, start) && (start == word.size());
+}
+
+/*!
+ Returns \c true if a prefix of a substring of \a word qualifies
+ for auto-linking.
+
+ Respects the same parsing rules as the unary overload.
+
+ \a curPos defines the offset, from the first character of \ word,
+ at which the parsed substring starts.
+
+ When the call completes, \a curPos represents the offset, from the
+ first character of word, that is the successor of the offset of
+ the last parsed character.
+
+ If the return value of the call is \c true, it is guaranteed that
+ the prefix of the substring of \word that contains the characters
+ from the initial value of \a curPos and up to but not including \a
+ curPos qualifies for auto-linking.
+
+ If \a curPos is initially zero, the considered substring is the
+ entirety of \a word.
+*/
+bool DocParser::isAutoLinkString(const QString &word, qsizetype &curPos)
+{
+ qsizetype len = word.size();
+ qsizetype startPos = curPos;
+ int numUppercase = 0;
+ int numLowercase = 0;
+ int numStrangeSymbols = 0;
+
+ while (curPos < len) {
+ unsigned char latin1Ch = word.at(curPos).toLatin1();
+ if (islower(latin1Ch)) {
+ ++numLowercase;
+ ++curPos;
+ } else if (isupper(latin1Ch)) {
+ if (curPos > startPos)
+ ++numUppercase;
+ ++curPos;
+ } else if (isdigit(latin1Ch)) {
+ if (curPos > startPos)
+ ++curPos;
+ else
+ break;
+ } else if (latin1Ch == '_' || latin1Ch == '@') {
+ ++numStrangeSymbols;
+ ++curPos;
+ } else if ((latin1Ch == ':') && (curPos < len - 1)
+ && (word.at(curPos + 1) == QLatin1Char(':'))) {
+ ++numStrangeSymbols;
+ curPos += 2;
+ } else if (latin1Ch == '(') {
+ if ((curPos < len - 1) && (word.at(curPos + 1) == QLatin1Char(')'))) {
+ ++numStrangeSymbols;
+ m_position += 2;
+ }
+
+ break;
+ } else {
+ break;
+ }
+ }
+
+ return ((numUppercase >= 1 && numLowercase >= 2) || (numStrangeSymbols > 0 && (numUppercase + numLowercase >= 1)));
+}
+
+bool DocParser::closeCommand(int endCmd)
+{
+ if (endCmdFor(m_openedCommands.top()) == endCmd && m_openedCommands.size() > 1) {
+ m_openedCommands.pop();
+ return true;
+ } else {
+ bool contains = false;
+ QStack<int> opened2 = m_openedCommands;
+ while (opened2.size() > 1) {
+ if (endCmdFor(opened2.top()) == endCmd) {
+ contains = true;
+ break;
+ }
+ opened2.pop();
+ }
+
+ if (contains) {
+ while (endCmdFor(m_openedCommands.top()) != endCmd && m_openedCommands.size() > 1) {
+ location().warning(
+ QStringLiteral("Missing '\\%1' before '\\%2'")
+ .arg(endCmdName(m_openedCommands.top()), cmdName(endCmd)));
+ m_openedCommands.pop();
+ }
+ } else {
+ location().warning(QStringLiteral("Unexpected '\\%1'").arg(cmdName(endCmd)));
+ }
+ return false;
+ }
+}
+
+void DocParser::startSection(Doc::Sections unit, int cmd)
+{
+ leaveValueList();
+
+ if (m_currentSection == Doc::NoSection) {
+ m_currentSection = static_cast<Doc::Sections>(unit);
+ m_private->constructExtra();
+ } else {
+ endSection(unit, cmd);
+ }
+
+ appendAtom(Atom(Atom::SectionLeft, QString::number(unit)));
+ m_private->constructExtra();
+ m_private->extra->m_tableOfContents.append(m_private->m_text.lastAtom());
+ m_private->extra->m_tableOfContentsLevels.append(unit);
+ enterPara(Atom::SectionHeadingLeft, Atom::SectionHeadingRight, QString::number(unit));
+ m_currentSection = unit;
+}
+
+void DocParser::endSection(int, int) // (int unit, int endCmd)
+{
+ leavePara();
+ appendAtom(Atom(Atom::SectionRight, QString::number(m_currentSection)));
+ m_currentSection = (Doc::NoSection);
+}
+
+/*!
+ \internal
+ \brief Parses arguments to QDoc's see also command.
+
+ Parses space or comma separated arguments passed to the \\sa command.
+ Multi-line input requires that the arguments are comma separated. Wrap
+ arguments in curly braces for multi-word targets, and for scope resolution
+ (for example, {QString::}{count()}).
+
+ This method updates the list of links for the See also section.
+
+ \sa {DocPrivate::}{addAlso()}, getArgument()
+ */
+void DocParser::parseAlso()
+{
+ auto line_comment = [this]() -> bool {
+ skipSpacesOnLine();
+ if (m_position + 2 > m_inputLength)
+ return false;
+ if (m_input[m_position].unicode() == '/') {
+ if (m_input[m_position + 1].unicode() == '/') {
+ if (m_input[m_position + 2].unicode() == '!') {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ auto skip_everything_until_newline = [this]() -> void {
+ while (m_position < m_inputLength && m_input[m_position] != '\n')
+ ++m_position;
+ };
+
+ leavePara();
+ skipSpacesOnLine();
+ while (m_position < m_inputLength && m_input[m_position] != '\n') {
+ QString target;
+ QString str;
+ bool skipMe = false;
+
+ if (m_input[m_position] == '{') {
+ target = getArgument();
+ skipSpacesOnLine();
+ if (m_position < m_inputLength && m_input[m_position] == '{') {
+ str = getArgument();
+
+ // hack for C++ to support links like \l{QString::}{count()}
+ if (target.endsWith("::"))
+ target += str;
+ } else {
+ str = target;
+ }
+ } else {
+ target = getArgument();
+ str = cleanLink(target);
+ if (target == QLatin1String("and") || target == QLatin1String("."))
+ skipMe = true;
+ }
+
+ if (!skipMe) {
+ Text also;
+ also << Atom(Atom::Link, target) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << str << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+ m_private->addAlso(also);
+ }
+
+ skipSpacesOnLine();
+
+ if (line_comment())
+ skip_everything_until_newline();
+
+ if (m_position < m_inputLength && m_input[m_position] == ',') {
+ m_position++;
+ if (line_comment())
+ skip_everything_until_newline();
+ skipSpacesOrOneEndl();
+ } else if (m_position >= m_inputLength || m_input[m_position] != '\n') {
+ location().warning(QStringLiteral("Missing comma in '\\%1'").arg(cmdName(CMD_SA)));
+ }
+ }
+}
+
+void DocParser::appendAtom(const Atom& atom) {
+ m_private->m_text << atom;
+}
+
+void DocParser::appendAtom(const LinkAtom& atom) {
+ m_private->m_text << atom;
+}
+
+void DocParser::appendChar(QChar ch)
+{
+ if (m_private->m_text.lastAtom()->type() != Atom::String)
+ appendAtom(Atom(Atom::String));
+ Atom *atom = m_private->m_text.lastAtom();
+ if (ch == QLatin1Char(' ')) {
+ if (!atom->string().endsWith(QLatin1Char(' ')))
+ atom->appendChar(QLatin1Char(' '));
+ } else
+ atom->appendChar(ch);
+}
+
+void DocParser::appendWord(const QString &word)
+{
+ if (m_private->m_text.lastAtom()->type() != Atom::String) {
+ appendAtom(Atom(Atom::String, word));
+ } else
+ m_private->m_text.lastAtom()->concatenateString(word);
+}
+
+void DocParser::appendToCode(const QString &markedCode)
+{
+ if (!isCode(m_lastAtom)) {
+ appendAtom(Atom(Atom::Code));
+ m_lastAtom = m_private->m_text.lastAtom();
+ }
+ m_lastAtom->concatenateString(markedCode);
+}
+
+void DocParser::appendToCode(const QString &markedCode, Atom::AtomType defaultType)
+{
+ if (!isCode(m_lastAtom)) {
+ appendAtom(Atom(defaultType, markedCode));
+ m_lastAtom = m_private->m_text.lastAtom();
+ } else {
+ m_lastAtom->concatenateString(markedCode);
+ }
+}
+
+void DocParser::enterPara(Atom::AtomType leftType, Atom::AtomType rightType, const QString &string)
+{
+ if (m_paragraphState != OutsideParagraph)
+ return;
+
+ if ((m_private->m_text.lastAtom()->type() != Atom::ListItemLeft)
+ && (m_private->m_text.lastAtom()->type() != Atom::DivLeft)
+ && (m_private->m_text.lastAtom()->type() != Atom::DetailsLeft)) {
+ leaveValueList();
+ }
+
+ appendAtom(Atom(leftType, string));
+ m_indexStartedParagraph = false;
+ m_pendingParagraphLeftType = leftType;
+ m_pendingParagraphRightType = rightType;
+ m_pendingParagraphString = string;
+ if (leftType == Atom::SectionHeadingLeft) {
+ m_paragraphState = InSingleLineParagraph;
+ } else {
+ m_paragraphState = InMultiLineParagraph;
+ }
+ skipSpacesOrOneEndl();
+}
+
+void DocParser::leavePara()
+{
+ if (m_paragraphState == OutsideParagraph)
+ return;
+
+ if (!m_pendingFormats.isEmpty()) {
+ location().warning(QStringLiteral("Missing '}'"));
+ m_pendingFormats.clear();
+ }
+
+ if (m_private->m_text.lastAtom()->type() == m_pendingParagraphLeftType) {
+ m_private->m_text.stripLastAtom();
+ } else {
+ if (m_private->m_text.lastAtom()->type() == Atom::String
+ && m_private->m_text.lastAtom()->string().endsWith(QLatin1Char(' '))) {
+ m_private->m_text.lastAtom()->chopString();
+ }
+ appendAtom(Atom(m_pendingParagraphRightType, m_pendingParagraphString));
+ }
+ m_paragraphState = OutsideParagraph;
+ m_indexStartedParagraph = false;
+ m_pendingParagraphRightType = Atom::Nop;
+ m_pendingParagraphString.clear();
+}
+
+void DocParser::leaveValue()
+{
+ leavePara();
+ if (m_openedLists.isEmpty()) {
+ m_openedLists.push(OpenedList(OpenedList::Value));
+ appendAtom(Atom(Atom::ListLeft, ATOM_LIST_VALUE));
+ } else {
+ if (m_private->m_text.lastAtom()->type() == Atom::Nop)
+ m_private->m_text.stripLastAtom();
+ appendAtom(Atom(Atom::ListItemRight, ATOM_LIST_VALUE));
+ }
+}
+
+void DocParser::leaveValueList()
+{
+ leavePara();
+ if (!m_openedLists.isEmpty() && (m_openedLists.top().style() == OpenedList::Value)) {
+ if (m_private->m_text.lastAtom()->type() == Atom::Nop)
+ m_private->m_text.stripLastAtom();
+ appendAtom(Atom(Atom::ListItemRight, ATOM_LIST_VALUE));
+ appendAtom(Atom(Atom::ListRight, ATOM_LIST_VALUE));
+ m_openedLists.pop();
+ }
+}
+
+void DocParser::leaveTableRow()
+{
+ if (m_inTableItem) {
+ leavePara();
+ appendAtom(Atom(Atom::TableItemRight));
+ m_inTableItem = false;
+ }
+ if (m_inTableHeader) {
+ appendAtom(Atom(Atom::TableHeaderRight));
+ m_inTableHeader = false;
+ }
+ if (m_inTableRow) {
+ appendAtom(Atom(Atom::TableRowRight));
+ m_inTableRow = false;
+ }
+}
+
+void DocParser::quoteFromFile(const QString &filename)
+{
+ // KLUDGE: We dereference file_resolver as it is temporarily a pointer.
+ // See the comment for file_resolver in the header files for more context.
+ //
+ // We spefically dereference it, instead of using the arrow
+ // operator, to better represent that we do not consider this as
+ // an actual pointer, as it should not be.
+ //
+ // Do note that we are considering it informally safe to
+ // dereference the pointer, as we expect it to always hold a value
+ // at this point, but actual enforcement of this appears nowhere
+ // in the codebase.
+ auto maybe_resolved_file{(*file_resolver).resolve(filename)};
+ if (!maybe_resolved_file) {
+ // TODO: [uncentralized-admonition][failed-resolve-file]
+ // This warning is required in multiple places.
+ // To ensure the consistency of the warning and avoid
+ // duplicating code everywhere, provide a centralized effort
+ // where the warning message can be generated (but not
+ // issued).
+ // The current format is based on what was used before, review
+ // it when it is moved out.
+ QString details = std::transform_reduce(
+ (*file_resolver).get_search_directories().cbegin(),
+ (*file_resolver).get_search_directories().cend(),
+ u"Searched directories:"_s,
+ std::plus(),
+ [](const DirectoryPath &directory_path) -> QString { return u' ' + directory_path.value(); }
+ );
+
+ location().warning(u"Cannot find file to quote from: %1"_s.arg(filename), details);
+
+ // REMARK: The following is duplicated from
+ // Doc::quoteFromFile. If, for some reason (such as a file
+ // that is inaccessible), the quoting fails but, previously,
+ // the logic duplicated here was still run.
+ // This is not true anymore as quoteFromFile does require a
+ // resolved file to be run now.
+ // It is not entirely clear if this is required for the
+ // semantics of DocParser to be preserved, but for the sake of
+ // avoiding premature breakages this was retained.
+ // Do note that this should be considered temporary as the
+ // quoter state, if any will be preserved, should not be
+ // managed in such a spread and unlocal way.
+ m_quoter.reset();
+
+ CodeMarker *marker = CodeMarker::markerForFileName(QString{});
+ m_quoter.quoteFromFile(filename, QString{}, marker->markedUpCode(QString{}, nullptr, location()));
+ } else Doc::quoteFromFile(location(), m_quoter, *maybe_resolved_file);
+}
+
+/*!
+ Expands a macro in-place in input.
+
+ Expects the current \e pos in the input to point to a backslash, and the macro to have a
+ default definition. Format-specific macros are not expanded.
+
+ Behavior depends on \a options:
+
+ \value ArgumentParsingOptions::Default
+ Default macro expansion; the string following the backslash
+ must be a macro with a default definition.
+ \value ArgumentParsingOptions::Verbatim
+ The string following the backslash is rendered verbatim;
+ No macro expansion is performed.
+ \value ArgumentParsingOptions::MacroArguments
+ Used for parsing argument(s) for a macro. Allows expanding
+ macros, and also preserves a subset of commands (formatting
+ commands) within the macro argument.
+
+ \note In addition to macros, a valid use for a backslash in an argument include
+ escaping non-alnum characters, and splitting a single argument across multiple
+ lines by escaping newlines. Escaping is also handled here.
+
+ Returns \c true on successful macro expansion.
+ */
+bool DocParser::expandMacro(ArgumentParsingOptions options)
+{
+ Q_ASSERT(m_input[m_position].unicode() == '\\');
+
+ if (options == ArgumentParsingOptions::Verbatim)
+ return false;
+
+ QString cmdStr;
+ qsizetype backslashPos = m_position++;
+ while (m_position < m_input.size() && m_input[m_position].isLetterOrNumber())
+ cmdStr += m_input[m_position++];
+
+ m_endPosition = m_position;
+ if (!cmdStr.isEmpty()) {
+ if (s_utilities.macroHash.contains(cmdStr)) {
+ const Macro &macro = s_utilities.macroHash.value(cmdStr);
+ if (!macro.m_defaultDef.isEmpty()) {
+ QString expanded = expandMacroToString(cmdStr, macro);
+ m_input.replace(backslashPos, m_position - backslashPos, expanded);
+ m_inputLength = m_input.size();
+ m_position = backslashPos;
+ return true;
+ } else {
+ location().warning("Macro '%1' does not have a default definition"_L1.arg(cmdStr));
+ }
+ } else {
+ int cmd = s_utilities.cmdHash.value(cmdStr, NOT_A_CMD);
+ m_position = backslashPos;
+ if (options != ArgumentParsingOptions::MacroArguments
+ || cmd == NOT_A_CMD || !cmds[cmd].is_formatting_command) {
+ location().warning("Unknown macro '%1'"_L1.arg(cmdStr));
+ ++m_position;
+ }
+ }
+ } else if (m_input[m_position].isSpace()) {
+ skipAllSpaces();
+ } else if (m_input[m_position].unicode() == '\\') {
+ // allow escaping a backslash
+ m_input.remove(m_position--, 1);
+ --m_inputLength;
+ }
+ return false;
+}
+
+void DocParser::expandMacro(const QString &def, const QStringList &args)
+{
+ if (args.isEmpty()) {
+ appendAtom(Atom(Atom::RawString, def));
+ } else {
+ QString rawString;
+
+ for (int j = 0; j < def.size(); ++j) {
+ if (int paramNo = def[j].unicode(); paramNo >= 1 && paramNo <= args.length()) {
+ if (!rawString.isEmpty()) {
+ appendAtom(Atom(Atom::RawString, rawString));
+ rawString.clear();
+ }
+ appendAtom(Atom(Atom::String, args[paramNo - 1]));
+ } else {
+ rawString += def[j];
+ }
+ }
+ if (!rawString.isEmpty())
+ appendAtom(Atom(Atom::RawString, rawString));
+ }
+}
+
+QString DocParser::expandMacroToString(const QString &name, const Macro &macro)
+{
+ const QString &def{macro.m_defaultDef};
+ QString rawString;
+
+ if (macro.numParams == 0) {
+ rawString = macro.m_defaultDef;
+ } else {
+ QStringList args{getMacroArguments(name, macro)};
+
+ for (int j = 0; j < def.size(); ++j) {
+ int paramNo = def[j].unicode();
+ rawString += (paramNo >= 1 && paramNo <= args.length()) ? args[paramNo - 1] : def[j];
+ }
+ }
+ QString matchExpr{macro.m_otherDefs.value("match")};
+ if (matchExpr.isEmpty())
+ return rawString;
+
+ QString result;
+ QRegularExpression re(matchExpr);
+ int capStart = (re.captureCount() > 0) ? 1 : 0;
+ qsizetype i = 0;
+ QRegularExpressionMatch match;
+ while ((match = re.match(rawString, i)).hasMatch()) {
+ for (int c = capStart; c <= re.captureCount(); ++c)
+ result += match.captured(c);
+ i = match.capturedEnd();
+ }
+
+ return result;
+}
+
+Doc::Sections DocParser::getSectioningUnit()
+{
+ QString name = getOptionalArgument();
+
+ if (name == "section1") {
+ return Doc::Section1;
+ } else if (name == "section2") {
+ return Doc::Section2;
+ } else if (name == "section3") {
+ return Doc::Section3;
+ } else if (name == "section4") {
+ return Doc::Section4;
+ } else if (name.isEmpty()) {
+ return Doc::NoSection;
+ } else {
+ location().warning(QStringLiteral("Invalid section '%1'").arg(name));
+ return Doc::NoSection;
+ }
+}
+
+/*!
+ Gets an argument that is enclosed in braces and returns it
+ without the enclosing braces. On entry, the current character
+ is the left brace. On exit, the current character is the one
+ that comes after the right brace.
+
+ If \a options is ArgumentParsingOptions::Verbatim, no macro
+ expansion is performed, nor is the returned string stripped
+ of extra whitespace.
+ */
+QString DocParser::getBracedArgument(ArgumentParsingOptions options)
+{
+ QString arg;
+ int delimDepth = 0;
+ if (m_position < m_input.size() && m_input[m_position] == '{') {
+ ++m_position;
+ while (m_position < m_input.size() && delimDepth >= 0) {
+ switch (m_input[m_position].unicode()) {
+ case '{':
+ ++delimDepth;
+ arg += QLatin1Char('{');
+ ++m_position;
+ break;
+ case '}':
+ --delimDepth;
+ if (delimDepth >= 0)
+ arg += QLatin1Char('}');
+ ++m_position;
+ break;
+ case '\\':
+ if (!expandMacro(options))
+ arg += m_input[m_position++];
+ break;
+ default:
+ if (m_input[m_position].isSpace() && options != ArgumentParsingOptions::Verbatim)
+ arg += QChar(' ');
+ else
+ arg += m_input[m_position];
+ ++m_position;
+ }
+ }
+ if (delimDepth > 0)
+ location().warning(QStringLiteral("Missing '}'"));
+ }
+ m_endPosition = m_position;
+ return arg;
+}
+
+/*!
+ Parses and returns an argument for a command, using
+ specific parsing \a options.
+
+ Typically, an argument ends at the next white-space. However,
+ braces can be used to group words:
+
+ {a few words}
+
+ Also, opening and closing parentheses have to match. Thus,
+
+ printf("%d\n", x)
+
+ is an argument too, although it contains spaces. Finally,
+ trailing punctuation is not included in an argument, nor is 's.
+*/
+QString DocParser::getArgument(ArgumentParsingOptions options)
+{
+ skipSpacesOrOneEndl();
+
+ int delimDepth = 0;
+ qsizetype startPos = m_position;
+ QString arg = getBracedArgument(options);
+ if (arg.isEmpty()) {
+ while ((m_position < m_input.size())
+ && ((delimDepth > 0) || ((delimDepth == 0) && !m_input[m_position].isSpace()))) {
+ switch (m_input[m_position].unicode()) {
+ case '(':
+ case '[':
+ case '{':
+ ++delimDepth;
+ arg += m_input[m_position];
+ ++m_position;
+ break;
+ case ')':
+ case ']':
+ case '}':
+ --delimDepth;
+ if (m_position == startPos || delimDepth >= 0) {
+ arg += m_input[m_position];
+ ++m_position;
+ }
+ break;
+ case '\\':
+ if (!expandMacro(options))
+ arg += m_input[m_position++];
+ break;
+ default:
+ arg += m_input[m_position];
+ ++m_position;
+ }
+ }
+ m_endPosition = m_position;
+ if ((arg.size() > 1) && (QString(".,:;!?").indexOf(m_input[m_position - 1]) != -1)
+ && !arg.endsWith("...")) {
+ arg.truncate(arg.size() - 1);
+ --m_position;
+ }
+ if (arg.size() > 2 && m_input.mid(m_position - 2, 2) == "'s") {
+ arg.truncate(arg.size() - 2);
+ m_position -= 2;
+ }
+ }
+ return arg.simplified();
+}
+
+/*!
+ Gets an argument that is enclosed in brackets and returns it
+ without the enclosing brackets. On entry, the current character
+ is the left bracket. On exit, the current character is the one
+ that comes after the right bracket.
+ */
+QString DocParser::getBracketedArgument()
+{
+ QString arg;
+ int delimDepth = 0;
+ skipSpacesOrOneEndl();
+ if (m_position < m_input.size() && m_input[m_position] == '[') {
+ ++m_position;
+ while (m_position < m_input.size() && delimDepth >= 0) {
+ switch (m_input[m_position].unicode()) {
+ case '[':
+ ++delimDepth;
+ arg += QLatin1Char('[');
+ ++m_position;
+ break;
+ case ']':
+ --delimDepth;
+ if (delimDepth >= 0)
+ arg += QLatin1Char(']');
+ ++m_position;
+ break;
+ case '\\':
+ arg += m_input[m_position];
+ ++m_position;
+ break;
+ default:
+ arg += m_input[m_position];
+ ++m_position;
+ }
+ }
+ if (delimDepth > 0)
+ location().warning(QStringLiteral("Missing ']'"));
+ }
+ return arg;
+}
+
+
+/*!
+ Returns the list of arguments passed to a \a macro with name \a name.
+
+ If a macro takes more than a single argument, they are expected to be
+ wrapped in braces.
+*/
+QStringList DocParser::getMacroArguments(const QString &name, const Macro &macro)
+{
+ QStringList args;
+ for (int i = 0; i < macro.numParams; ++i) {
+ if (macro.numParams == 1 || isLeftBraceAhead()) {
+ args << getArgument(ArgumentParsingOptions::MacroArguments);
+ } else {
+ location().warning(QStringLiteral("Macro '\\%1' invoked with too few"
+ " arguments (expected %2, got %3)")
+ .arg(name)
+ .arg(macro.numParams)
+ .arg(i));
+ break;
+ }
+ }
+ return args;
+}
+
+QString DocParser::getOptionalArgument()
+{
+ skipSpacesOrOneEndl();
+ if (m_position + 1 < m_input.size() && m_input[m_position] == '\\'
+ && m_input[m_position + 1].isLetterOrNumber()) {
+ return QString();
+ } else {
+ return getArgument();
+ }
+}
+
+/*!
+ \brief Create a string that may optionally span multiple lines as one line.
+
+ Process a block of text that may span multiple lines using trailing
+ backslashes (`\`) as line continuation character. Trailing backslashes and
+ any newline character that follow them are removed.
+
+ Returns a string as if it was one continuous line of text. If trailing
+ backslashes are removed, the method returns a "simplified" QString, which
+ means any sequence of internal whitespace is replaced with a single space.
+
+ Whitespace at the start and end is always removed from the returned string.
+
+ \sa QString::simplified(), QString::trimmed().
+ */
+QString DocParser::getRestOfLine()
+{
+ auto lineHasTrailingBackslash = [this](bool trailingBackslash) -> bool {
+ while (m_position < m_inputLength && m_input[m_position] != '\n') {
+ if (m_input[m_position] == '\\' && !trailingBackslash) {
+ trailingBackslash = true;
+ ++m_position;
+ skipSpacesOnLine();
+ } else {
+ trailingBackslash = false;
+ ++m_position;
+ }
+ }
+ return trailingBackslash;
+ };
+
+ QString rest_of_line;
+ skipSpacesOnLine();
+ bool trailing_backslash{ false };
+ bool return_simplified_string{ false };
+
+ for (qsizetype start_position = m_position; m_position < m_inputLength; ++m_position) {
+ trailing_backslash = lineHasTrailingBackslash(trailing_backslash);
+
+ if (!rest_of_line.isEmpty())
+ rest_of_line += QLatin1Char(' ');
+ rest_of_line += m_input.sliced(start_position, m_position - start_position);
+
+ if (trailing_backslash) {
+ rest_of_line.truncate(rest_of_line.lastIndexOf('\\'));
+ return_simplified_string = true;
+ }
+
+ if (m_position < m_inputLength)
+ ++m_position;
+
+ if (!trailing_backslash)
+ break;
+ start_position = m_position;
+ }
+
+ if (return_simplified_string)
+ return rest_of_line.simplified();
+
+ return rest_of_line.trimmed();
+}
+
+/*!
+ The metacommand argument is normally the remaining text to
+ the right of the metacommand itself. The extra blanks are
+ stripped and the argument string is returned.
+ */
+QString DocParser::getMetaCommandArgument(const QString &cmdStr)
+{
+ skipSpacesOnLine();
+
+ qsizetype begin = m_position;
+ int parenDepth = 0;
+
+ while (m_position < m_input.size() && (m_input[m_position] != '\n' || parenDepth > 0)) {
+ if (m_input.at(m_position) == '(')
+ ++parenDepth;
+ else if (m_input.at(m_position) == ')')
+ --parenDepth;
+ else if (m_input.at(m_position) == '\\' && expandMacro(ArgumentParsingOptions::Default))
+ continue;
+ ++m_position;
+ }
+ if (m_position == m_input.size() && parenDepth > 0) {
+ m_position = begin;
+ location().warning(QStringLiteral("Unbalanced parentheses in '%1'").arg(cmdStr));
+ }
+
+ QString t = m_input.mid(begin, m_position - begin).simplified();
+ skipSpacesOnLine();
+ return t;
+}
+
+QString DocParser::getUntilEnd(int cmd)
+{
+ int endCmd = endCmdFor(cmd);
+ QRegularExpression rx("\\\\" + cmdName(endCmd) + "\\b");
+ QString t;
+ auto match = rx.match(m_input, m_position);
+
+ if (!match.hasMatch()) {
+ location().warning(QStringLiteral("Missing '\\%1'").arg(cmdName(endCmd)));
+ m_position = m_input.size();
+ } else {
+ qsizetype end = match.capturedStart();
+ t = m_input.mid(m_position, end - m_position);
+ m_position = match.capturedEnd();
+ }
+ return t;
+}
+
+void DocParser::expandArgumentsInString(QString &str, const QStringList &args)
+{
+ if (args.isEmpty())
+ return;
+
+ qsizetype paramNo;
+ qsizetype j = 0;
+ while (j < str.size()) {
+ if (str[j] == '\\' && j < str.size() - 1 && (paramNo = str[j + 1].digitValue()) >= 1
+ && paramNo <= args.size()) {
+ const QString &r = args[paramNo - 1];
+ str.replace(j, 2, r);
+ j += qMin(1, r.size());
+ } else {
+ ++j;
+ }
+ }
+}
+
+/*!
+ Returns the marked-up code following the code-quoting command \a cmd, expanding
+ any arguments passed in \a argStr.
+
+ Uses the \a marker to mark up the code. If it's \c nullptr, resolve the marker
+ based on the topic and the quoted code itself.
+*/
+QString DocParser::getCode(int cmd, CodeMarker *marker, const QString &argStr)
+{
+ QString code = untabifyEtc(getUntilEnd(cmd));
+ expandArgumentsInString(code, argStr.split(" ", Qt::SkipEmptyParts));
+
+ int indent = indentLevel(code);
+ code = dedent(indent, code);
+
+ // If we're in a QML topic, check if the QML marker recognizes the code
+ if (!marker && !m_private->m_topics.isEmpty()
+ && m_private->m_topics[0].m_topic.startsWith("qml")) {
+ auto qmlMarker = CodeMarker::markerForLanguage("QML");
+ marker = (qmlMarker && qmlMarker->recognizeCode(code)) ? qmlMarker : nullptr;
+ }
+ if (marker == nullptr)
+ marker = CodeMarker::markerForCode(code);
+ return marker->markedUpCode(code, nullptr, location());
+}
+
+bool DocParser::isBlankLine()
+{
+ qsizetype i = m_position;
+
+ while (i < m_inputLength && m_input[i].isSpace()) {
+ if (m_input[i] == '\n')
+ return true;
+ ++i;
+ }
+ return false;
+}
+
+bool DocParser::isLeftBraceAhead()
+{
+ int numEndl = 0;
+ qsizetype i = m_position;
+
+ while (i < m_inputLength && m_input[i].isSpace() && numEndl < 2) {
+ // ### bug with '\\'
+ if (m_input[i] == '\n')
+ numEndl++;
+ ++i;
+ }
+ return numEndl < 2 && i < m_inputLength && m_input[i] == '{';
+}
+
+bool DocParser::isLeftBracketAhead()
+{
+ int numEndl = 0;
+ qsizetype i = m_position;
+
+ while (i < m_inputLength && m_input[i].isSpace() && numEndl < 2) {
+ // ### bug with '\\'
+ if (m_input[i] == '\n')
+ numEndl++;
+ ++i;
+ }
+ return numEndl < 2 && i < m_inputLength && m_input[i] == '[';
+}
+
+/*!
+ Skips to the next non-space character or EOL.
+ */
+void DocParser::skipSpacesOnLine()
+{
+ while ((m_position < m_input.size()) && m_input[m_position].isSpace()
+ && (m_input[m_position].unicode() != '\n'))
+ ++m_position;
+}
+
+/*!
+ Skips spaces and one EOL.
+ */
+void DocParser::skipSpacesOrOneEndl()
+{
+ qsizetype firstEndl = -1;
+ while (m_position < m_input.size() && m_input[m_position].isSpace()) {
+ QChar ch = m_input[m_position];
+ if (ch == '\n') {
+ if (firstEndl == -1) {
+ firstEndl = m_position;
+ } else {
+ m_position = firstEndl;
+ break;
+ }
+ }
+ ++m_position;
+ }
+}
+
+void DocParser::skipAllSpaces()
+{
+ while (m_position < m_inputLength && m_input[m_position].isSpace())
+ ++m_position;
+}
+
+void DocParser::skipToNextPreprocessorCommand()
+{
+ QRegularExpression rx("\\\\(?:" + cmdName(CMD_IF) + QLatin1Char('|') + cmdName(CMD_ELSE)
+ + QLatin1Char('|') + cmdName(CMD_ENDIF) + ")\\b");
+ auto match = rx.match(m_input, m_position + 1); // ### + 1 necessary?
+
+ if (!match.hasMatch())
+ m_position = m_input.size();
+ else
+ m_position = match.capturedStart();
+}
+
+int DocParser::endCmdFor(int cmd)
+{
+ switch (cmd) {
+ case CMD_BADCODE:
+ return CMD_ENDCODE;
+ case CMD_CODE:
+ return CMD_ENDCODE;
+ case CMD_COMPARESWITH:
+ return CMD_ENDCOMPARESWITH;
+ case CMD_DETAILS:
+ return CMD_ENDDETAILS;
+ case CMD_DIV:
+ return CMD_ENDDIV;
+ case CMD_QML:
+ return CMD_ENDQML;
+ case CMD_FOOTNOTE:
+ return CMD_ENDFOOTNOTE;
+ case CMD_LEGALESE:
+ return CMD_ENDLEGALESE;
+ case CMD_LINK:
+ return CMD_ENDLINK;
+ case CMD_LIST:
+ return CMD_ENDLIST;
+ case CMD_OMIT:
+ return CMD_ENDOMIT;
+ case CMD_QUOTATION:
+ return CMD_ENDQUOTATION;
+ case CMD_RAW:
+ return CMD_ENDRAW;
+ case CMD_SECTION1:
+ return CMD_ENDSECTION1;
+ case CMD_SECTION2:
+ return CMD_ENDSECTION2;
+ case CMD_SECTION3:
+ return CMD_ENDSECTION3;
+ case CMD_SECTION4:
+ return CMD_ENDSECTION4;
+ case CMD_SIDEBAR:
+ return CMD_ENDSIDEBAR;
+ case CMD_TABLE:
+ return CMD_ENDTABLE;
+ default:
+ return cmd;
+ }
+}
+
+QString DocParser::cmdName(int cmd)
+{
+ return cmds[cmd].name;
+}
+
+QString DocParser::endCmdName(int cmd)
+{
+ return cmdName(endCmdFor(cmd));
+}
+
+QString DocParser::untabifyEtc(const QString &str)
+{
+ QString result;
+ result.reserve(str.size());
+ int column = 0;
+
+ for (const auto &character : str) {
+ if (character == QLatin1Char('\r'))
+ continue;
+ if (character == QLatin1Char('\t')) {
+ result += &" "[column % s_tabSize];
+ column = ((column / s_tabSize) + 1) * s_tabSize;
+ continue;
+ }
+ if (character == QLatin1Char('\n')) {
+ while (result.endsWith(QLatin1Char(' ')))
+ result.chop(1);
+ result += character;
+ column = 0;
+ continue;
+ }
+ result += character;
+ ++column;
+ }
+
+ while (result.endsWith("\n\n"))
+ result.truncate(result.size() - 1);
+ while (result.startsWith(QLatin1Char('\n')))
+ result = result.mid(1);
+
+ return result;
+}
+
+int DocParser::indentLevel(const QString &str)
+{
+ int minIndent = INT_MAX;
+ int column = 0;
+
+ for (const auto &character : str) {
+ if (character == '\n') {
+ column = 0;
+ } else {
+ if (character != ' ' && column < minIndent)
+ minIndent = column;
+ ++column;
+ }
+ }
+ return minIndent;
+}
+
+QString DocParser::dedent(int level, const QString &str)
+{
+ if (level == 0)
+ return str;
+
+ QString result;
+ int column = 0;
+
+ for (const auto &character : str) {
+ if (character == QLatin1Char('\n')) {
+ result += '\n';
+ column = 0;
+ } else {
+ if (column >= level)
+ result += character;
+ ++column;
+ }
+ }
+ return result;
+}
+
+/*!
+ Returns \c true if \a atom represents a code snippet.
+ */
+bool DocParser::isCode(const Atom *atom)
+{
+ Atom::AtomType type = atom->type();
+ return (type == Atom::Code || type == Atom::Qml);
+}
+
+/*!
+ Returns \c true if \a atom represents quoting information.
+ */
+bool DocParser::isQuote(const Atom *atom)
+{
+ Atom::AtomType type = atom->type();
+ return (type == Atom::CodeQuoteArgument || type == Atom::CodeQuoteCommand
+ || type == Atom::SnippetCommand || type == Atom::SnippetIdentifier
+ || type == Atom::SnippetLocation);
+}
+
+/*!
+ \internal
+ Processes the arguments passed to the \\compareswith block command.
+ The arguments are stored as text within the first atom of the block
+ (Atom::ComparesLeft).
+
+ Extracts the comparison category and the list of types, and stores
+ the information into a map accessed via \a priv.
+*/
+static void processComparesWithCommand(DocPrivate *priv, const Location &location)
+{
+ static auto take_while = [](QStringView input, auto predicate) {
+ QStringView::size_type end{0};
+
+ while (end < input.size() && std::invoke(predicate, input[end]))
+ ++end;
+
+ return std::make_tuple(input.sliced(0, end), input.sliced(end));
+ };
+
+ static auto peek = [](QStringView input, QChar c) {
+ return !input.empty() && input.first() == c;
+ };
+
+ static auto skip_one = [](QStringView input) {
+ if (input.empty()) return std::make_tuple(QStringView{}, input);
+ else return std::make_tuple(input.sliced(0, 1), input.sliced(1));
+ };
+
+ static auto enclosed = [](QStringView input, QChar open, QChar close) {
+ if (!peek(input, open)) return std::make_tuple(QStringView{}, input);
+
+ auto [opened, without_open] = skip_one(input);
+ auto [parsed, remaining] = take_while(without_open, [close](QChar c){ return c != close; });
+
+ if (remaining.empty()) return std::make_tuple(QStringView{}, input);
+
+ auto [closed, without_close] = skip_one(remaining);
+
+ return std::make_tuple(parsed.trimmed(), without_close);
+ };
+
+ static auto one_of = [](auto first, auto second) {
+ return [first, second](QStringView input) {
+ auto [parsed, remaining] = std::invoke(first, input);
+
+ if (parsed.empty()) return std::invoke(second, input);
+ else return std::make_tuple(parsed, remaining);
+ };
+ };
+
+ static auto collect = [](QStringView input, auto parser) {
+ QStringList collected{};
+
+ while (true) {
+ auto [parsed, remaining] = std::invoke(parser, input);
+
+ if (parsed.empty()) break;
+ collected.append(parsed.toString());
+
+ input = remaining;
+ };
+
+ return collected;
+ };
+
+ static auto spaces = [](QStringView input) {
+ return take_while(input, [](QChar c){ return c.isSpace(); });
+ };
+
+ static auto word = [](QStringView input) {
+ return take_while(input, [](QChar c){ return !c.isSpace(); });
+ };
+
+ static auto parse_argument = [](QStringView input) {
+ auto [_, without_spaces] = spaces(input);
+
+ return one_of(
+ [](QStringView input){ return enclosed(input, '{', '}'); },
+ word
+ )(without_spaces);
+ };
+
+ const QString cmd{DocParser::cmdName(CMD_COMPARESWITH)};
+ Text text = priv->m_text.splitAtFirst(Atom::ComparesLeft);
+
+ auto *atom = text.firstAtom();
+ QStringList segments = collect(atom->string(), parse_argument);
+
+ QString categoryString;
+ if (!segments.isEmpty())
+ categoryString = segments.takeFirst();
+ auto category = comparisonCategoryFromString(categoryString.toStdString());
+
+ if (category == ComparisonCategory::None) {
+ location.warning(u"Invalid argument to \\%1 command: `%2`"_s.arg(cmd, categoryString),
+ u"Valid arguments are `strong`, `weak`, `partial`, or `equality`."_s);
+ return;
+ }
+
+ if (segments.isEmpty()) {
+ location.warning(u"Missing argument to \\%1 command."_s.arg(cmd),
+ u"Provide at least one type name, or a list of types separated by spaces."_s);
+ return;
+ }
+
+ // Store cleaned-up type names back into the atom
+ segments.removeDuplicates();
+ atom->setString(segments.join(QLatin1Char(';')));
+
+ // Add an entry to meta-command map for error handling in CppCodeParser
+ priv->m_metaCommandMap[cmd].append(ArgPair(categoryString, atom->string()));
+ priv->m_metacommandsUsed.insert(cmd);
+
+ priv->constructExtra();
+ const auto end{priv->extra->m_comparesWithMap.cend()};
+ priv->extra->m_comparesWithMap.insert(end, category, text);
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/docparser.h b/src/qdoc/qdoc/src/qdoc/docparser.h
new file mode 100644
index 000000000..c577e12ba
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/docparser.h
@@ -0,0 +1,176 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#ifndef DOCPARSER_H
+#define DOCPARSER_H
+
+#include "atom.h"
+#include "config.h"
+#include "docutilities.h"
+#include "location.h"
+#include "openedlist.h"
+#include "quoter.h"
+
+#include "filesystem/fileresolver.h"
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/qglobalstatic.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qstack.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class Doc;
+class DocPrivate;
+class CodeMarker;
+struct Macro;
+
+class DocParser
+{
+public:
+ void parse(const QString &source, DocPrivate *docPrivate, const QSet<QString> &metaCommandSet,
+ const QSet<QString> &possibleTopics);
+
+ static void initialize(const Config &config, FileResolver& file_resolver);
+ static int endCmdFor(int cmd);
+ static QString cmdName(int cmd);
+ static QString endCmdName(int cmd);
+ static QString untabifyEtc(const QString &str);
+ static int indentLevel(const QString &str);
+ static QString dedent(int level, const QString &str);
+
+ static int s_tabSize;
+ static QStringList s_ignoreWords;
+ static bool s_quoting;
+
+private:
+
+ enum class ArgumentParsingOptions {
+ Default,
+ Verbatim,
+ MacroArguments
+ };
+
+ Location &location();
+ QString detailsUnknownCommand(const QSet<QString> &metaCommandSet, const QString &str);
+ void insertTarget(const QString &target);
+ void insertKeyword(const QString &keyword);
+ void include(const QString &fileName, const QString &identifier, const QStringList &parameters);
+ void startFormat(const QString &format, int cmd);
+ bool openCommand(int cmd);
+ bool closeCommand(int endCmd);
+ void startSection(Doc::Sections unit, int cmd);
+ void endSection(int unit, int endCmd);
+ void parseAlso();
+ void appendAtom(const Atom&);
+ void appendAtom(const LinkAtom&);
+ void appendChar(QChar ch);
+ void appendWord(const QString &word);
+ void appendToCode(const QString &code);
+ void appendToCode(const QString &code, Atom::AtomType defaultType);
+ void enterPara(Atom::AtomType leftType = Atom::ParaLeft,
+ Atom::AtomType rightType = Atom::ParaRight, const QString &string = QString());
+ void leavePara();
+ void leaveValue();
+ void leaveValueList();
+ void leaveTableRow();
+ void quoteFromFile(const QString& filename);
+ bool expandMacro(ArgumentParsingOptions options);
+ void expandMacro(const QString &def, const QStringList &args);
+ QString expandMacroToString(const QString &name, const Macro &macro);
+ Doc::Sections getSectioningUnit();
+ QString getArgument(ArgumentParsingOptions options = ArgumentParsingOptions::Default);
+ QString getBracedArgument(ArgumentParsingOptions options);
+ QString getBracketedArgument();
+ QStringList getMacroArguments(const QString &name, const Macro &macro);
+ QString getOptionalArgument();
+ QString getRestOfLine();
+ QString getMetaCommandArgument(const QString &cmdStr);
+ QString getUntilEnd(int cmd);
+ QString getCode(int cmd, CodeMarker *marker, const QString &argStr = QString());
+
+ inline bool isAutoLinkString(const QString &word);
+ bool isAutoLinkString(const QString &word, qsizetype &curPos);
+ bool isBlankLine();
+ bool isLeftBraceAhead();
+ bool isLeftBracketAhead();
+ void skipSpacesOnLine();
+ void skipSpacesOrOneEndl();
+ void skipAllSpaces();
+ void skipToNextPreprocessorCommand();
+ static bool isCode(const Atom *atom);
+ static bool isQuote(const Atom *atom);
+ static void expandArgumentsInString(QString &str, const QStringList &args);
+
+ QStack<qsizetype> m_openedInputs {};
+
+ QString m_input {};
+ qsizetype m_position {};
+ qsizetype m_backslashPosition {};
+ qsizetype m_endPosition {};
+ qsizetype m_inputLength {};
+ Location m_cachedLocation {};
+ qsizetype m_cachedPosition {};
+
+ DocPrivate *m_private { nullptr };
+ enum ParagraphState { OutsideParagraph, InSingleLineParagraph, InMultiLineParagraph };
+ ParagraphState m_paragraphState {};
+ bool m_inTableHeader {};
+ bool m_inTableRow {};
+ bool m_inTableItem {};
+ bool m_indexStartedParagraph {}; // ### rename
+ Atom::AtomType m_pendingParagraphLeftType {};
+ Atom::AtomType m_pendingParagraphRightType {};
+ QString m_pendingParagraphString {};
+
+ int m_braceDepth {};
+ Doc::Sections m_currentSection {};
+ QMap<QString, Location> m_targetMap {};
+ QMap<int, QString> m_pendingFormats {};
+ QStack<int> m_openedCommands {};
+ QStack<OpenedList> m_openedLists {};
+ Quoter m_quoter {};
+ Atom *m_lastAtom { nullptr };
+
+ static DocUtilities &s_utilities;
+
+ // KLUDGE: When parsing documentation, there is a need to find
+ // files to resolve quoting commands. Ideally, the system that
+ // takes care of this would be a non-static member that is a
+ // reference that is passed at
+ // construction time.
+ // Nonetheless, with how the current codebase is constructed, this
+ // has proven to be extremely difficult until more changes are
+ // done. In particular, the construction of a DocParser happens in
+ // multiple places at multiple depths and, in particular, happens
+ // in one of Doc's constructor.
+ // Doc itself is built, again, in multiple places at multiple
+ // depths, making it clumsy and sometimes infeasible to pass the
+ // dependency around so that it is available at the required
+ // places. In particular, this stems from the fact that Doc is
+ // holding many responsabilities and is spread troughtout much of
+ // the codebase in different ways. DocParser mostly depends on Doc
+ // and Doc currently depends on DocParser, making the two
+ // difficult to separate.
+ //
+ // In the future, we expect Doc to mostly be removed, such as to
+ // remove this dependencies and the parsing of documentation to
+ // happen near main and atomically from other endevours, producing
+ // an intermediate representation that is consumed by later
+ // phases.
+ // At that point, it should be possible to not have this kind of
+ // indirection while, for now, the only accessible way to pass
+ // this dependency is trough the initialize method which passes
+ // for Doc::initialize.
+ //
+ // Furthemore, as we cannot late-bind a reference, and having a
+ // desire to avoid an unnecessary copy, we are thus forced to use
+ // a different storage method, in this case a pointer.
+ // This too should be removed later on, using reference or move
+ // semantic depending on the required data-flow.
+ static FileResolver* file_resolver;
+};
+
+QT_END_NAMESPACE
+
+#endif // DOCPARSER_H
diff --git a/src/qdoc/qdoc/src/qdoc/docprivate.cpp b/src/qdoc/qdoc/src/qdoc/docprivate.cpp
new file mode 100644
index 000000000..a7c178d57
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/docprivate.cpp
@@ -0,0 +1,30 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include "docprivate.h"
+
+#include "text.h"
+
+#include <QtCore/qhash.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ Deletes the DocPrivateExtra.
+ */
+DocPrivate::~DocPrivate()
+{
+ delete extra;
+}
+
+void DocPrivate::addAlso(const Text &also)
+{
+ m_alsoList.append(also);
+}
+
+void DocPrivate::constructExtra()
+{
+ if (extra == nullptr)
+ extra = new DocPrivateExtra;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/docprivate.h b/src/qdoc/qdoc/src/qdoc/docprivate.h
new file mode 100644
index 000000000..7402290c9
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/docprivate.h
@@ -0,0 +1,76 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#ifndef DOCPRIVATE_H
+#define DOCPRIVATE_H
+
+#include "atom.h"
+#include "config.h"
+#include "codemarker.h"
+#include "doc.h"
+#include "editdistance.h"
+#include "generator.h"
+#include "utilities.h"
+#include "openedlist.h"
+#include "quoter.h"
+#include "text.h"
+#include "tokenizer.h"
+
+#include <QtCore/qdatetime.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qtextstream.h>
+
+#include <cctype>
+#include <climits>
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+typedef QMap<QString, ArgList> CommandMap;
+
+struct DocPrivateExtra
+{
+ QList<Atom *> m_tableOfContents {};
+ QList<int> m_tableOfContentsLevels {};
+ QList<Atom *> m_keywords {};
+ QList<Atom *> m_targets {};
+ QStringMultiMap m_metaMap {};
+ QMultiMap<ComparisonCategory, Text> m_comparesWithMap {};
+};
+
+class DocPrivate
+{
+public:
+ explicit DocPrivate(const Location &start = Location(), const Location &end = Location(),
+ QString source = QString())
+ : m_start_loc(start), m_end_loc(end), m_src(std::move(source)), m_hasLegalese(false) {};
+ ~DocPrivate();
+
+ void addAlso(const Text &also);
+ void constructExtra();
+ void ref() { ++count; }
+ bool deref() { return (--count == 0); }
+
+ int count { 1 };
+ // ### move some of this in DocPrivateExtra
+ Location m_start_loc {};
+ Location m_end_loc {};
+ QString m_src {};
+ Text m_text {};
+ QSet<QString> m_params {};
+ QList<Text> m_alsoList {};
+ QStringList m_enumItemList {};
+ QStringList m_omitEnumItemList {};
+ QSet<QString> m_metacommandsUsed {};
+ CommandMap m_metaCommandMap {};
+ DocPrivateExtra *extra { nullptr };
+ TopicList m_topics {};
+
+ bool m_hasLegalese : 1;
+};
+
+QT_END_NAMESPACE
+
+#endif // DOCPRIVATE_H
diff --git a/src/qdoc/qdoc/src/qdoc/docutilities.h b/src/qdoc/qdoc/src/qdoc/docutilities.h
new file mode 100644
index 000000000..d4483ac73
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/docutilities.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#ifndef DOCUTILITIES_H
+#define DOCUTILITIES_H
+
+#include "macro.h"
+#include "singleton.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qmap.h>
+
+QT_BEGIN_NAMESPACE
+
+typedef QHash<QString, int> QHash_QString_int;
+typedef QHash<QString, Macro> QHash_QString_Macro;
+
+struct DocUtilities : public Singleton<DocUtilities>
+{
+public:
+ QHash_QString_int cmdHash;
+ QHash_QString_Macro macroHash;
+};
+
+QT_END_NAMESPACE
+
+#endif // DOCUTILITIES_H
diff --git a/src/qdoc/qdoc/src/qdoc/editdistance.cpp b/src/qdoc/qdoc/src/qdoc/editdistance.cpp
new file mode 100644
index 000000000..303979ec3
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/editdistance.cpp
@@ -0,0 +1,68 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "editdistance.h"
+
+QT_BEGIN_NAMESPACE
+
+int editDistance(const QString &s, const QString &t)
+{
+#define D(i, j) d[(i)*n + (j)]
+ int i;
+ int j;
+ qsizetype m = s.size() + 1;
+ qsizetype n = t.size() + 1;
+ int *d = new int[m * n];
+ int result;
+
+ for (i = 0; i < m; ++i)
+ D(i, 0) = i;
+ for (j = 0; j < n; ++j)
+ D(0, j) = j;
+ for (i = 1; i < m; ++i) {
+ for (j = 1; j < n; ++j) {
+ if (s[i - 1] == t[j - 1]) {
+ D(i, j) = D(i - 1, j - 1);
+ } else {
+ int x = D(i - 1, j);
+ int y = D(i - 1, j - 1);
+ int z = D(i, j - 1);
+ D(i, j) = 1 + qMin(qMin(x, y), z);
+ }
+ }
+ }
+ result = D(m - 1, n - 1);
+ delete[] d;
+ return result;
+#undef D
+}
+
+QString nearestName(const QString &actual, const QSet<QString> &candidates)
+{
+ if (actual.isEmpty())
+ return QString();
+
+ int deltaBest = 10000;
+ int numBest = 0;
+ QString best;
+
+ for (const auto &candidate : candidates) {
+ if (candidate[0] == actual[0]) {
+ int delta = editDistance(actual, candidate);
+ if (delta < deltaBest) {
+ deltaBest = delta;
+ numBest = 1;
+ best = candidate;
+ } else if (delta == deltaBest) {
+ ++numBest;
+ }
+ }
+ }
+
+ if (numBest == 1 && deltaBest <= 2 && actual.size() + best.size() >= 5)
+ return best;
+
+ return QString();
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/editdistance.h b/src/qdoc/qdoc/src/qdoc/editdistance.h
new file mode 100644
index 000000000..dfa6a42dd
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/editdistance.h
@@ -0,0 +1,17 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef EDITDISTANCE_H
+#define EDITDISTANCE_H
+
+#include <QtCore/qset.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+int editDistance(const QString &s, const QString &t);
+QString nearestName(const QString &actual, const QSet<QString> &candidates);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/enumitem.h b/src/qdoc/qdoc/src/qdoc/enumitem.h
new file mode 100644
index 000000000..06e9d42a9
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/enumitem.h
@@ -0,0 +1,36 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef ENUMITEM_H
+#define ENUMITEM_H
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+class EnumItem
+{
+public:
+ EnumItem() = default;
+ EnumItem(QString name, QString value, QString since = QString())
+ : m_name(std::move(name)),
+ m_value(std::move(value)),
+ m_since(std::move(since)) {}
+
+ [[nodiscard]] const QString &name() const { return m_name; }
+ [[nodiscard]] const QString &value() const { return m_value; }
+ [[nodiscard]] const QString &since() const { return m_since; }
+ void setSince(const QString &since) { m_since = since; }
+
+private:
+ QString m_name {};
+ QString m_value {};
+ QString m_since {};
+};
+
+QT_END_NAMESPACE
+
+#endif // ENUMITEM_H
diff --git a/src/qdoc/qdoc/src/qdoc/enumnode.cpp b/src/qdoc/qdoc/src/qdoc/enumnode.cpp
new file mode 100644
index 000000000..48a2f81aa
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/enumnode.cpp
@@ -0,0 +1,82 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "enumnode.h"
+
+#include "aggregate.h"
+#include "typedefnode.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class EnumNode
+ */
+
+/*!
+ Add \a item to the enum type's item list.
+ */
+void EnumNode::addItem(const EnumItem &item)
+{
+ m_items.append(item);
+ m_names.insert(item.name());
+}
+
+/*!
+ Returns the access level of the enumeration item named \a name.
+ Apparently it is private if it has been omitted by qdoc's
+ omitvalue command. Otherwise it is public.
+ */
+Access EnumNode::itemAccess(const QString &name) const
+{
+ if (doc().omitEnumItemNames().contains(name))
+ return Access::Private;
+ return Access::Public;
+}
+
+/*!
+ Returns the enum value associated with the enum \a name.
+ */
+QString EnumNode::itemValue(const QString &name) const
+{
+ for (const auto &item : std::as_const(m_items)) {
+ if (item.name() == name)
+ return item.value();
+ }
+ return QString();
+}
+
+/*!
+ Sets \a since information to a named enum \a value, if it
+ exists in this enum.
+*/
+void EnumNode::setSince(const QString &value, const QString &since)
+{
+ auto it = std::find_if(m_items.begin(), m_items.end(), [value](EnumItem ev) {
+ return ev.name() == value;
+ });
+ if (it != m_items.end())
+ it->setSince(since);
+}
+
+/*!
+ Clone this node on the heap and make the clone a child of
+ \a parent.
+
+ Returns a pointer to the clone.
+ */
+Node *EnumNode::clone(Aggregate *parent)
+{
+ auto *en = new EnumNode(*this); // shallow copy
+ en->setParent(nullptr);
+ parent->addChild(en);
+
+ return en;
+}
+
+void EnumNode::setFlagsType(TypedefNode *typedefNode)
+{
+ m_flagsType = typedefNode;
+ typedefNode->setAssociatedEnum(this);
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/enumnode.h b/src/qdoc/qdoc/src/qdoc/enumnode.h
new file mode 100644
index 000000000..47139ae4d
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/enumnode.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef ENUMNODE_H
+#define ENUMNODE_H
+
+#include "access.h"
+#include "node.h"
+#include "typedefnode.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qset.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+
+class EnumNode : public Node
+{
+public:
+ EnumNode(Aggregate *parent, const QString &name, bool isScoped = false)
+ : Node(Enum, parent, name), m_isScoped(isScoped)
+ {
+ }
+
+ void addItem(const EnumItem &item);
+ void setFlagsType(TypedefNode *typedefNode);
+ bool hasItem(const QString &name) const { return m_names.contains(name); }
+ bool isScoped() const { return m_isScoped; }
+
+ const QList<EnumItem> &items() const { return m_items; }
+ Access itemAccess(const QString &name) const;
+ const TypedefNode *flagsType() const { return m_flagsType; }
+ QString itemValue(const QString &name) const;
+ Node *clone(Aggregate *parent) override;
+ void setSince(const QString &value, const QString &since);
+
+private:
+ QList<EnumItem> m_items {};
+ QSet<QString> m_names {};
+ const TypedefNode *m_flagsType { nullptr };
+ bool m_isScoped { false };
+};
+
+QT_END_NAMESPACE
+
+#endif // ENUMNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/examplenode.h b/src/qdoc/qdoc/src/qdoc/examplenode.h
new file mode 100644
index 000000000..920092e4e
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/examplenode.h
@@ -0,0 +1,42 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef EXAMPLENODE_H
+#define EXAMPLENODE_H
+
+#include "pagenode.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+
+class ExampleNode : public PageNode
+{
+public:
+ ExampleNode(Aggregate *parent, const QString &name) : PageNode(Node::Example, parent, name) {}
+ [[nodiscard]] QString imageFileName() const override { return m_imageFileName; }
+ void setImageFileName(const QString &ifn) override { m_imageFileName = ifn; }
+ [[nodiscard]] const QStringList &files() const { return m_files; }
+ [[nodiscard]] const QStringList &images() const { return m_images; }
+ [[nodiscard]] const QString &projectFile() const { return m_projectFile; }
+ void setFiles(const QStringList &files, const QString &projectFile)
+ {
+ m_files = files;
+ m_projectFile = projectFile;
+ }
+ void setImages(const QStringList &images) { m_images = images; }
+ void appendFile(QString &file) { m_files.append(file); }
+ void appendImage(QString &image) { m_images.append(image); }
+
+private:
+ QString m_imageFileName {};
+ QString m_projectFile {};
+ QStringList m_files {};
+ QStringList m_images {};
+};
+
+QT_END_NAMESPACE
+
+#endif // EXAMPLENODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/externalpagenode.cpp b/src/qdoc/qdoc/src/qdoc/externalpagenode.cpp
new file mode 100644
index 000000000..30a583d00
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/externalpagenode.cpp
@@ -0,0 +1,28 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "externalpagenode.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class ExternalPageNode
+
+ \brief The ExternalPageNode represents an external documentation page.
+
+ Qdoc can generate links to pages that are not part of the documentation
+ being generated. 3rd party software pages are often referenced by links
+ from the QT documentation. Qdoc creates an ExternalPageNode when it sees
+ an \c {\\externalpage} command. The HTML generator can then use the node
+ when it needs to create links to the external page.
+
+ ExternalPageNode inherits PageNode.
+*/
+
+/*! \fn ExternalPageNode::ExternalPageNode(Aggregate *parent, const QString &name)
+ The constructor creates an ExternalPageNode as a child node of \a parent.
+ It's \a name is the argument from the \c {\\externalpage} command. The node
+ type is Node::ExternalPage, and the page type is Node::ArticlePage.
+ */
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/externalpagenode.h b/src/qdoc/qdoc/src/qdoc/externalpagenode.h
new file mode 100644
index 000000000..e67aab0e8
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/externalpagenode.h
@@ -0,0 +1,25 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef EXTERNALPAGENODE_H
+#define EXTERNALPAGENODE_H
+
+#include "pagenode.h"
+
+#include <QtCore/qglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class ExternalPageNode : public PageNode
+{
+public:
+ ExternalPageNode(Aggregate *parent, const QString &url)
+ : PageNode(Node::ExternalPage, parent, url)
+ {
+ setUrl(url);
+ }
+};
+
+QT_END_NAMESPACE
+
+#endif // EXTERNALPAGENODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.cpp b/src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.cpp
new file mode 100644
index 000000000..aaa489085
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.cpp
@@ -0,0 +1,161 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "fileresolver.h"
+
+#include "qdoc/boundaries/filesystem/filepath.h"
+
+#include <QDir>
+
+#include <iostream>
+#include <algorithm>
+
+/*!
+ * \class FileResolver
+ * \brief Encapsulate the logic that QDoc uses to find files whose
+ * path is provided by the user and that are relative to the current
+ * configuration.
+ *
+ * A FileResolver instance is configured during creation, defining the
+ * root directories that the search should be performed on.
+ *
+ * Afterwards, it can be used to resolve paths relative to those
+ * directories, by querying through the resolve() method.
+ *
+ * Queries are resolved through a linear search through root
+ * directories, finding at most one file each time.
+ * A file is considered to be resolved if, from any root directory,
+ * the query represents an existing file.
+ *
+ * For example, consider the following directory structure on some
+ * filesystem:
+ *
+ * \badcode
+ * foo/
+ * |
+ * |-bar/
+ * |-|
+ * | |-anotherfile.txt
+ * |-file.txt
+ * \endcode
+ *
+ * And consider an instance of FileResolver tha considers \e{foo/} to
+ * be a root directory for search.
+ *
+ * Then, queries such as \e {bar/anotherfile.txt} and \e {file.txt}
+ * will be resolved.
+ *
+ * Instead, queries such as \e {foobar.cpp}, \e {bar}, and \e
+ * {foo/bar/anotherfile.txt} will not be resolved, as they do not
+ * represent any file reachable from a root directory for search.
+ *
+ * It is important to note that FileResolver always searches its root
+ * directories in an order that is based on the lexicographic ordering
+ * of the path of its root directories.
+ *
+ * For example, consider the following directory structure on some
+ * filesystem:
+ *
+ * \badcode
+ * foo/
+ * |
+ * |-bar/
+ * |-|
+ * | |-file.txt
+ * |-foobar/
+ * |-|
+ * | |-file.txt
+ * \endcode
+ *
+ * And consider an instance of FileResolver that considers \e
+ * {foo/bar/} and \e {foo/foobar/} to be root directories for search.
+ *
+ * Then, when the query \e {file.txt} is resolved, it will always
+ * resolve to the file in \e {bar}, as \e {bar} will be searched
+ * before \e {foobar}.
+ *
+ * We say that \e {foobar/file.txt} is shadowed by \e {bar/file.txt}.
+ *
+ * Currently, if this is an issue, it is possible to resolve it by
+ * using a common ancestor as a root directory instead of using
+ * multiples directories.
+ *
+ * In the previous example, if \e {foo} is instead chosen as the root
+ * directory for search, then queries \e {bar/file.txt} and \e
+ * {foobar/file.txt} can be used to uniquely resolve the two files,
+ * removing the shadowing.
+ * */
+
+/*!
+ * Constructs an instance of FileResolver with the directories in \a
+ * search_directories as root directories for searching.
+ *
+ * Duplicates in \a search_directories do not affect the resolution of
+ * files for the instance.
+ *
+ * For example, if \a search_directories contains some directory D
+ * more than once, the constructed instance will resolve files
+ * equivalently to an instance constructed with a single appearance of
+ * D.
+ *
+ * The order of \a search_directories does not affect the resolution
+ * of files for an instance.
+ *
+ * For example, if \a search_directories contains a permutation of
+ * directories D1, D2, ..., Dn, then the constructed instance will
+ * resolve files equivalently to an instance constructed from a
+ * difference permutation of the same directories.
+ */
+FileResolver::FileResolver(std::vector<DirectoryPath>&& search_directories)
+ : search_directories{std::move(search_directories)}
+{
+ std::sort(this->search_directories.begin(), this->search_directories.end());
+ this->search_directories.erase (
+ std::unique(this->search_directories.begin(), this->search_directories.end()),
+ this->search_directories.end()
+ );
+}
+
+// REMARK: Note that we do not treat absolute path specially.
+// This will in general mean that they cannot get resolved (albeit
+// there is a peculiar instance in which they can considering that
+// most path formats treat multiple adjacent separators as one).
+//
+// While we need to treat them at some point with a specific
+// intention, this was avoided at the current moment as it is
+// unrequired to build the actual documentation.
+//
+// In particular, avoiding this choice now allows us to move it to a
+// later stage where we can work with the origin of the data itself.
+// User-inputted paths come into the picture during the configuration
+// process and when parsing qdoc comments, there is a good chance that
+// some amount of sophistication will be required to handle this data
+// at the code level, for example to ensure that multiplatform
+// handling of paths is performed correctly.
+//
+// This will then define how we should handle absolute paths, if we
+// can receive them at all and so on.
+
+/*!
+* Returns a ResolvedFile if \a query can be resolved or std::nullopt
+* otherwise.
+*
+* The returned ResolvedFile, if any, will contain the provided \a
+* query and the path that the \a query was resolved to.
+*/
+[[nodiscard]] std::optional<ResolvedFile> FileResolver::resolve(QString query) const {
+ for (auto& directory_path : search_directories) {
+ auto maybe_filepath = FilePath::refine(QDir(directory_path.value() + "/" + query).path());
+ if (maybe_filepath) return ResolvedFile{std::move(query), std::move(*maybe_filepath)};
+ }
+
+ return std::nullopt;
+}
+
+/*!
+ * \fn FileResolver::get_search_directories() const
+ *
+ * Returns a const-reference to a collection of root search
+ * directories that this instance will use during the resolution of
+ * files.
+ */
diff --git a/src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.h b/src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.h
new file mode 100644
index 000000000..be574da30
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/filesystem/fileresolver.h
@@ -0,0 +1,24 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "qdoc/boundaries/filesystem/directorypath.h"
+#include "qdoc/boundaries/filesystem/resolvedfile.h"
+
+#include <optional>
+#include <vector>
+
+#include <QtCore/qstring.h>
+
+class FileResolver {
+public:
+ FileResolver(std::vector<DirectoryPath>&& search_directories);
+
+ [[nodiscard]] std::optional<ResolvedFile> resolve(QString filename) const;
+
+ [[nodiscard]] const std::vector<DirectoryPath>& get_search_directories() const { return search_directories; }
+
+private:
+ std::vector<DirectoryPath> search_directories;
+};
diff --git a/src/qdoc/qdoc/src/qdoc/functionnode.cpp b/src/qdoc/qdoc/src/qdoc/functionnode.cpp
new file mode 100644
index 000000000..82933e0ac
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/functionnode.cpp
@@ -0,0 +1,473 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "functionnode.h"
+#include "propertynode.h"
+
+#include <string>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class FunctionNode
+
+ This node is used to represent any kind of function being
+ documented. It can represent a C++ class member function, a C++
+ global function, a QML method, or a macro, with or without
+ parameters.
+
+ A C++ function can be a signal, a slot, a constructor of any
+ kind, a destructor, a copy or move assignment operator, or
+ just a plain old member function or a global function.
+
+ A QML method can be a plain old method, or a
+ signal or signal handler.
+
+ If the function is an overload, its overload flag is
+ true.
+
+ The function node also has an overload number. If the
+ node's overload flag is set, this overload number is
+ positive; otherwise, the overload number is 0.
+ */
+
+/*!
+ Construct a function node for a C++ function. It's parent
+ is \a parent, and it's name is \a name.
+
+ \note The function node's overload flag is set to false, and
+ its overload number is set to 0. These data members are set
+ in normalizeOverloads(), when all the overloads are known.
+ */
+FunctionNode::FunctionNode(Aggregate *parent, const QString &name)
+ : Node(Function, parent, name),
+ m_const(false),
+ m_default(false),
+ m_static(false),
+ m_reimpFlag(false),
+ m_attached(false),
+ m_overloadFlag(false),
+ m_isFinal(false),
+ m_isOverride(false),
+ m_isRef(false),
+ m_isRefRef(false),
+ m_isInvokable(false),
+ m_explicit{false},
+ m_constexpr{false},
+ m_metaness(Plain),
+ m_virtualness(NonVirtual),
+ m_overloadNumber(0)
+{
+ // nothing
+}
+
+/*!
+ Construct a function node for a QML method or signal, specified
+ by ther Metaness value \a type. It's parent is \a parent, and
+ it's name is \a name. If \a attached is true, it is an attached
+ method or signal.
+
+ \note The function node's overload flag is set to false, and
+ its overload number is set to 0. These data members are set
+ in normalizeOverloads(), when all the overloads are known.
+ */
+FunctionNode::FunctionNode(Metaness kind, Aggregate *parent, const QString &name, bool attached)
+ : Node(Function, parent, name),
+ m_const(false),
+ m_default(false),
+ m_static(false),
+ m_reimpFlag(false),
+ m_attached(attached),
+ m_overloadFlag(false),
+ m_isFinal(false),
+ m_isOverride(false),
+ m_isRef(false),
+ m_isRefRef(false),
+ m_isInvokable(false),
+ m_explicit{false},
+ m_constexpr{false},
+ m_metaness(kind),
+ m_virtualness(NonVirtual),
+ m_overloadNumber(0)
+{
+ setGenus(getGenus(m_metaness));
+ if (!isCppNode() && name.startsWith("__"))
+ setStatus(Internal);
+}
+
+/*!
+ Clone this node on the heap and make the clone a child of
+ \a parent. Return the pointer to the clone.
+ */
+Node *FunctionNode::clone(Aggregate *parent)
+{
+ auto *fn = new FunctionNode(*this); // shallow copy
+ fn->setParent(nullptr);
+ parent->addChild(fn);
+ return fn;
+}
+
+/*!
+ Returns this function's virtualness value as a string
+ for use as an attribute value in index files.
+ */
+QString FunctionNode::virtualness() const
+{
+ switch (m_virtualness) {
+ case FunctionNode::NormalVirtual:
+ return QLatin1String("virtual");
+ case FunctionNode::PureVirtual:
+ return QLatin1String("pure");
+ case FunctionNode::NonVirtual:
+ default:
+ break;
+ }
+ return QLatin1String("non");
+}
+
+/*!
+ Sets the function node's virtualness value based on the value
+ of string \a value, which is the value of the function's \e{virtual}
+ attribute in an index file. If \a value is \e{pure}, and if the
+ parent() is a C++ class, set the parent's \e abstract flag to
+ \c {true}.
+ */
+void FunctionNode::setVirtualness(const QString &value)
+{
+ if (value == QLatin1String("pure")) {
+ m_virtualness = PureVirtual;
+ if (parent() && parent()->isClassNode())
+ parent()->setAbstract(true);
+ return;
+ }
+
+ m_virtualness = (value == QLatin1String("virtual")) ? NormalVirtual : NonVirtual;
+}
+
+static QMap<QString, FunctionNode::Metaness> metanessMap_;
+static void buildMetanessMap()
+{
+ metanessMap_["plain"] = FunctionNode::Plain;
+ metanessMap_["signal"] = FunctionNode::Signal;
+ metanessMap_["slot"] = FunctionNode::Slot;
+ metanessMap_["constructor"] = FunctionNode::Ctor;
+ metanessMap_["copy-constructor"] = FunctionNode::CCtor;
+ metanessMap_["move-constructor"] = FunctionNode::MCtor;
+ metanessMap_["destructor"] = FunctionNode::Dtor;
+ metanessMap_["macro"] = FunctionNode::MacroWithParams;
+ metanessMap_["macrowithparams"] = FunctionNode::MacroWithParams;
+ metanessMap_["macrowithoutparams"] = FunctionNode::MacroWithoutParams;
+ metanessMap_["copy-assign"] = FunctionNode::CAssign;
+ metanessMap_["move-assign"] = FunctionNode::MAssign;
+ metanessMap_["native"] = FunctionNode::Native;
+ metanessMap_["qmlsignal"] = FunctionNode::QmlSignal;
+ metanessMap_["qmlsignalhandler"] = FunctionNode::QmlSignalHandler;
+ metanessMap_["qmlmethod"] = FunctionNode::QmlMethod;
+}
+
+static QMap<QString, FunctionNode::Metaness> topicMetanessMap_;
+static void buildTopicMetanessMap()
+{
+ topicMetanessMap_["fn"] = FunctionNode::Plain;
+ topicMetanessMap_["qmlsignal"] = FunctionNode::QmlSignal;
+ topicMetanessMap_["qmlattachedsignal"] = FunctionNode::QmlSignal;
+ topicMetanessMap_["qmlmethod"] = FunctionNode::QmlMethod;
+ topicMetanessMap_["qmlattachedmethod"] = FunctionNode::QmlMethod;
+}
+
+/*!
+ Determines the Genus value for this FunctionNode given the
+ Metaness value \a metaness. Returns the Genus value. \a metaness must be
+ one of the values of Metaness. If not, Node::DontCare is
+ returned.
+ */
+Node::Genus FunctionNode::getGenus(FunctionNode::Metaness metaness)
+{
+ switch (metaness) {
+ case FunctionNode::Plain:
+ case FunctionNode::Signal:
+ case FunctionNode::Slot:
+ case FunctionNode::Ctor:
+ case FunctionNode::Dtor:
+ case FunctionNode::CCtor:
+ case FunctionNode::MCtor:
+ case FunctionNode::MacroWithParams:
+ case FunctionNode::MacroWithoutParams:
+ case FunctionNode::Native:
+ case FunctionNode::CAssign:
+ case FunctionNode::MAssign:
+ return Node::CPP;
+ case FunctionNode::QmlSignal:
+ case FunctionNode::QmlSignalHandler:
+ case FunctionNode::QmlMethod:
+ return Node::QML;
+ }
+
+ return Node::DontCare;
+}
+
+/*!
+ This static function converts the string \a value to an enum
+ value for the kind of function named by \a value.
+ */
+FunctionNode::Metaness FunctionNode::getMetaness(const QString &value)
+{
+ if (metanessMap_.isEmpty())
+ buildMetanessMap();
+ return metanessMap_[value];
+}
+
+/*!
+ This static function converts the topic string \a topic to an enum
+ value for the kind of function this FunctionNode represents.
+ */
+FunctionNode::Metaness FunctionNode::getMetanessFromTopic(const QString &topic)
+{
+ if (topicMetanessMap_.isEmpty())
+ buildTopicMetanessMap();
+ return topicMetanessMap_[topic];
+}
+
+/*!
+ Sets the function node's overload number to \a number. If \a number
+ is 0, the function node's overload flag is set to false. If
+ \a number is greater than 0, the overload flag is set to true.
+ */
+void FunctionNode::setOverloadNumber(signed short number)
+{
+ m_overloadNumber = number;
+ m_overloadFlag = (number > 0);
+}
+
+/*!
+ \fn void FunctionNode::setReimpFlag()
+
+ Sets the function node's reimp flag to \c true, which means
+ the \e {\\reimp} command was used in the qdoc comment. It is
+ supposed to mean that the function reimplements a virtual
+ function in a base class.
+ */
+
+/*!
+ Returns a string representing the kind of function this
+ Function node represents, which depends on the Metaness
+ value.
+ */
+QString FunctionNode::kindString() const
+{
+ switch (m_metaness) {
+ case FunctionNode::QmlSignal:
+ return "QML signal";
+ case FunctionNode::QmlSignalHandler:
+ return "QML signal handler";
+ case FunctionNode::QmlMethod:
+ return "QML method";
+ default:
+ return "function";
+ }
+}
+
+/*!
+ Returns a string representing the Metaness enum value for
+ this function. It is used in index files.
+ */
+QString FunctionNode::metanessString() const
+{
+ switch (m_metaness) {
+ case FunctionNode::Plain:
+ return "plain";
+ case FunctionNode::Signal:
+ return "signal";
+ case FunctionNode::Slot:
+ return "slot";
+ case FunctionNode::Ctor:
+ return "constructor";
+ case FunctionNode::CCtor:
+ return "copy-constructor";
+ case FunctionNode::MCtor:
+ return "move-constructor";
+ case FunctionNode::Dtor:
+ return "destructor";
+ case FunctionNode::MacroWithParams:
+ return "macrowithparams";
+ case FunctionNode::MacroWithoutParams:
+ return "macrowithoutparams";
+ case FunctionNode::Native:
+ return "native";
+ case FunctionNode::CAssign:
+ return "copy-assign";
+ case FunctionNode::MAssign:
+ return "move-assign";
+ case FunctionNode::QmlSignal:
+ return "qmlsignal";
+ case FunctionNode::QmlSignalHandler:
+ return "qmlsignalhandler";
+ case FunctionNode::QmlMethod:
+ return "qmlmethod";
+ default:
+ return "plain";
+ }
+}
+
+/*!
+ Adds the "associated" property \a p to this function node.
+ The function might be the setter or getter for a property,
+ for example.
+ */
+void FunctionNode::addAssociatedProperty(PropertyNode *p)
+{
+ m_associatedProperties.append(p);
+}
+
+/*!
+ \reimp
+
+ Returns \c true if this is an access function for an obsolete property,
+ otherwise calls the base implementation of isDeprecated().
+*/
+bool FunctionNode::isDeprecated() const
+{
+ auto it = std::find_if_not(m_associatedProperties.begin(), m_associatedProperties.end(),
+ [](const Node *p) -> bool { return p->isDeprecated(); });
+
+ if (!m_associatedProperties.isEmpty() && it == m_associatedProperties.end())
+ return true;
+
+ return Node::isDeprecated();
+}
+
+/*! \fn unsigned char FunctionNode::overloadNumber() const
+ Returns the overload number for this function.
+ */
+
+/*!
+ Reconstructs and returns the function's signature.
+
+ Specific parts of the signature are included according to
+ flags in \a options:
+
+ \value Node::SignaturePlain
+ Plain signature
+ \value Node::SignatureDefaultValues
+ Include any default argument values
+ \value Node::SignatureReturnType
+ Include return type
+ \value Node::SignatureTemplateParams
+ Include \c {template <parameter_list>} if one exists
+ */
+QString FunctionNode::signature(Node::SignatureOptions options) const
+{
+ QStringList elements;
+
+ if (options & Node::SignatureTemplateParams && templateDecl())
+ elements << (*templateDecl()).to_qstring();
+ if (options & Node::SignatureReturnType)
+ elements << m_returnType;
+ elements.removeAll(QString());
+
+ if (!isMacroWithoutParams()) {
+ elements << name() + QLatin1Char('(')
+ + m_parameters.signature(options & Node::SignatureDefaultValues)
+ + QLatin1Char(')');
+ if (!isMacro()) {
+ if (isConst())
+ elements << QStringLiteral("const");
+ if (isRef())
+ elements << QStringLiteral("&");
+ else if (isRefRef())
+ elements << QStringLiteral("&&");
+ }
+ } else {
+ elements << name();
+ }
+ return elements.join(QLatin1Char(' '));
+}
+
+/*!
+ \fn int FunctionNode::compare(const FunctionNode *f1, const FunctionNode *f2)
+
+ Compares FunctionNode \a f1 with \a f2, assumed to have identical names.
+ Returns an integer less than, equal to, or greater than zero if f1 is
+ considered less than, equal to, or greater than f2.
+
+ The main purpose is to provide stable ordering for function overloads.
+ */
+[[nodiscard]] int compare(const FunctionNode *f1, const FunctionNode *f2)
+{
+ // Compare parameter count
+ int param_count{f1->parameters().count()};
+
+ if (int param_diff = param_count - f2->parameters().count(); param_diff != 0)
+ return param_diff;
+
+ // Constness
+ if (f1->isConst() != f2->isConst())
+ return f1->isConst() ? 1 : -1;
+
+ // Reference qualifiers
+ if (f1->isRef() != f2->isRef())
+ return f1->isRef() ? 1 : -1;
+ if (f1->isRefRef() != f2->isRefRef())
+ return f1->isRefRef() ? 1 : -1;
+
+ // Attachedness (applies to QML methods)
+ if (f1->isAttached() != f2->isAttached())
+ return f1->isAttached() ? 1 : -1;
+
+ // Parameter types
+ const Parameters &p1{f1->parameters()};
+ const Parameters &p2{f2->parameters()};
+ for (qsizetype i = 0; i < param_count; ++i) {
+ if (int type_comp = QString::compare(p1.at(i).type(), p2.at(i).type());
+ type_comp != 0) {
+ return type_comp;
+ }
+ }
+
+ // Template declarations
+ const auto t1{f1->templateDecl()};
+ const auto t2{f2->templateDecl()};
+ if (!t1 && !t2)
+ return 0;
+
+ if (t1 && t2)
+ return (*t1).to_std_string().compare((*t2).to_std_string());
+
+ return t1 ? 1 : -1;
+}
+
+/*!
+ In some cases, it is ok for a public function to be not documented.
+ For example, the macro Q_OBJECT adds several functions to the API of
+ a class, but these functions are normally not meant to be documented.
+ So if a function node doesn't have documentation, then if its name is
+ in the list of functions that it is ok not to document, this function
+ returns true. Otherwise, it returns false.
+
+ These are the member function names added by macros. Usually they
+ are not documented, but they can be documented, so this test avoids
+ reporting an error if they are not documented.
+
+ But maybe we should generate a standard text for each of them?
+ */
+bool FunctionNode::isIgnored() const
+{
+ if (!hasDoc()) {
+ if (name().startsWith(QLatin1String("qt_")) || name() == QLatin1String("metaObject")
+ || name() == QLatin1String("tr") || name() == QLatin1String("trUtf8")
+ || name() == QLatin1String("d_func")) {
+ return true;
+ }
+ QString s = signature(Node::SignatureReturnType);
+ if (s.contains(QLatin1String("enum_type")) && s.contains(QLatin1String("operator|")))
+ return true;
+ }
+ return false;
+}
+
+/*!
+ \fn bool FunctionNode::hasOverloads() const
+ Returns \c true if this function has overloads.
+ */
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/functionnode.h b/src/qdoc/qdoc/src/qdoc/functionnode.h
new file mode 100644
index 000000000..dca8c7e44
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/functionnode.h
@@ -0,0 +1,202 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef FUNCTIONNODE_H
+#define FUNCTIONNODE_H
+
+#include "aggregate.h"
+#include "node.h"
+#include "parameters.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+#include <optional>
+
+QT_BEGIN_NAMESPACE
+
+class FunctionNode : public Node
+{
+public:
+ enum Virtualness { NonVirtual, NormalVirtual, PureVirtual };
+
+ enum Metaness {
+ Plain,
+ Signal,
+ Slot,
+ Ctor,
+ Dtor,
+ CCtor, // copy constructor
+ MCtor, // move-copy constructor
+ MacroWithParams,
+ MacroWithoutParams,
+ Native,
+ CAssign, // copy-assignment operator
+ MAssign, // move-assignment operator
+ QmlSignal,
+ QmlSignalHandler,
+ QmlMethod,
+ };
+
+ FunctionNode(Aggregate *parent, const QString &name); // C++ function (Plain)
+ FunctionNode(Metaness type, Aggregate *parent, const QString &name, bool attached = false);
+
+ Node *clone(Aggregate *parent) override;
+ [[nodiscard]] Metaness metaness() const { return m_metaness; }
+ [[nodiscard]] QString metanessString() const;
+ void setMetaness(Metaness metaness) { m_metaness = metaness; }
+ [[nodiscard]] QString kindString() const;
+ static Metaness getMetaness(const QString &value);
+ static Metaness getMetanessFromTopic(const QString &topic);
+ static Genus getGenus(Metaness metaness);
+
+ void setReturnType(const QString &type) { m_returnType = type; }
+ void setVirtualness(const QString &value);
+ void setVirtualness(Virtualness virtualness) { m_virtualness = virtualness; }
+ void setConst(bool b) { m_const = b; }
+ void setDefault(bool b) { m_default = b; }
+ void setStatic(bool b) { m_static = b; }
+ void setReimpFlag() { m_reimpFlag = true; }
+ void setOverridesThis(const QString &path) { m_overridesThis = path; }
+
+ [[nodiscard]] const QString &returnType() const { return m_returnType; }
+ [[nodiscard]] QString virtualness() const;
+ [[nodiscard]] bool isConst() const { return m_const; }
+ [[nodiscard]] bool isDefault() const override { return m_default; }
+ [[nodiscard]] bool isStatic() const override { return m_static; }
+ [[nodiscard]] bool isOverload() const { return m_overloadFlag; }
+ [[nodiscard]] bool isMarkedReimp() const override { return m_reimpFlag; }
+ [[nodiscard]] bool isSomeCtor() const { return isCtor() || isCCtor() || isMCtor(); }
+ [[nodiscard]] bool isMacroWithParams() const { return (m_metaness == MacroWithParams); }
+ [[nodiscard]] bool isMacroWithoutParams() const { return (m_metaness == MacroWithoutParams); }
+ [[nodiscard]] bool isMacro() const override
+ {
+ return (isMacroWithParams() || isMacroWithoutParams());
+ }
+ [[nodiscard]] bool isDeprecated() const override;
+
+ void markExplicit() { m_explicit = true; }
+ bool isExplicit() const { return m_explicit; }
+
+ void markConstexpr() { m_constexpr = true; }
+ bool isConstexpr() const { return m_constexpr; }
+
+ void markNoexcept(QString expression = "") { m_noexcept = expression; }
+ const std::optional<QString>& getNoexcept() const { return m_noexcept; }
+
+ [[nodiscard]] bool isCppFunction() const { return m_metaness == Plain; } // Is this correct?
+ [[nodiscard]] bool isSignal() const { return (m_metaness == Signal); }
+ [[nodiscard]] bool isSlot() const { return (m_metaness == Slot); }
+ [[nodiscard]] bool isCtor() const { return (m_metaness == Ctor); }
+ [[nodiscard]] bool isDtor() const { return (m_metaness == Dtor); }
+ [[nodiscard]] bool isCCtor() const { return (m_metaness == CCtor); }
+ [[nodiscard]] bool isMCtor() const { return (m_metaness == MCtor); }
+ [[nodiscard]] bool isCAssign() const { return (m_metaness == CAssign); }
+ [[nodiscard]] bool isMAssign() const { return (m_metaness == MAssign); }
+
+ [[nodiscard]] bool isQmlMethod() const { return (m_metaness == QmlMethod); }
+ [[nodiscard]] bool isQmlSignal() const { return (m_metaness == QmlSignal); }
+ [[nodiscard]] bool isQmlSignalHandler() const { return (m_metaness == QmlSignalHandler); }
+
+ [[nodiscard]] bool isSpecialMemberFunction() const
+ {
+ return (isCtor() || isDtor() || isCCtor() || isMCtor() || isCAssign() || isMAssign());
+ }
+ [[nodiscard]] bool isNonvirtual() const { return (m_virtualness == NonVirtual); }
+ [[nodiscard]] bool isVirtual() const { return (m_virtualness == NormalVirtual); }
+ [[nodiscard]] bool isPureVirtual() const { return (m_virtualness == PureVirtual); }
+ [[nodiscard]] bool returnsBool() const { return (m_returnType == QLatin1String("bool")); }
+
+ Parameters &parameters() { return m_parameters; }
+ [[nodiscard]] const Parameters &parameters() const { return m_parameters; }
+ [[nodiscard]] bool isPrivateSignal() const { return m_parameters.isPrivateSignal(); }
+ void setParameters(const QString &signature) { m_parameters.set(signature); }
+ [[nodiscard]] QString signature(Node::SignatureOptions options) const override;
+
+ [[nodiscard]] const QString &overridesThis() const { return m_overridesThis; }
+ [[nodiscard]] const QList<PropertyNode *> &associatedProperties() const { return m_associatedProperties; }
+ [[nodiscard]] bool hasAssociatedProperties() const { return !m_associatedProperties.isEmpty(); }
+ [[nodiscard]] bool hasOneAssociatedProperty() const
+ {
+ return (m_associatedProperties.size() == 1);
+ }
+ [[nodiscard]] QString element() const override { return parent()->name(); }
+ [[nodiscard]] bool isAttached() const override { return m_attached; }
+ [[nodiscard]] QString qmlTypeName() const override { return parent()->qmlTypeName(); }
+ [[nodiscard]] QString logicalModuleName() const override
+ {
+ return parent()->logicalModuleName();
+ }
+ [[nodiscard]] QString logicalModuleVersion() const override
+ {
+ return parent()->logicalModuleVersion();
+ }
+ [[nodiscard]] QString logicalModuleIdentifier() const override
+ {
+ return parent()->logicalModuleIdentifier();
+ }
+
+ void setFinal(bool b) { m_isFinal = b; }
+ [[nodiscard]] bool isFinal() const { return m_isFinal; }
+
+ void setOverride(bool b) { m_isOverride = b; }
+ [[nodiscard]] bool isOverride() const { return m_isOverride; }
+
+ void setRef(bool b) { m_isRef = b; }
+ [[nodiscard]] bool isRef() const { return m_isRef; }
+
+ void setRefRef(bool b) { m_isRefRef = b; }
+ [[nodiscard]] bool isRefRef() const { return m_isRefRef; }
+
+ void setInvokable(bool b) { m_isInvokable = b; }
+ [[nodiscard]] bool isInvokable() const { return m_isInvokable; }
+
+ [[nodiscard]] bool hasTag(const QString &tag) const override { return (m_tag == tag); }
+ void setTag(const QString &tag) { m_tag = tag; }
+ [[nodiscard]] const QString &tag() const { return m_tag; }
+ [[nodiscard]] bool isIgnored() const;
+ [[nodiscard]] bool hasOverloads() const
+ {
+ return (m_overloadFlag || (parent() && parent()->hasOverloads(this)));
+ }
+ void setOverloadFlag() { m_overloadFlag = true; }
+ void setOverloadNumber(signed short number);
+ [[nodiscard]] signed short overloadNumber() const { return m_overloadNumber; }
+
+ friend int compare(const FunctionNode *f1, const FunctionNode *f2);
+
+private:
+ void addAssociatedProperty(PropertyNode *property);
+
+ friend class Aggregate;
+ friend class PropertyNode;
+
+ bool m_const : 1;
+ bool m_default : 1;
+ bool m_static : 1;
+ bool m_reimpFlag : 1;
+ bool m_attached : 1;
+ bool m_overloadFlag : 1;
+ bool m_isFinal : 1;
+ bool m_isOverride : 1;
+ bool m_isRef : 1;
+ bool m_isRefRef : 1;
+ bool m_isInvokable : 1;
+ bool m_explicit;
+ bool m_constexpr;
+
+ std::optional<QString> m_noexcept;
+
+ Metaness m_metaness {};
+ Virtualness m_virtualness{ NonVirtual };
+ signed short m_overloadNumber {};
+ QString m_returnType {};
+ QString m_overridesThis {};
+ QString m_tag {};
+ QList<PropertyNode *> m_associatedProperties {};
+ Parameters m_parameters {};
+};
+
+QT_END_NAMESPACE
+
+#endif // FUNCTIONNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/generator.cpp b/src/qdoc/qdoc/src/qdoc/generator.cpp
new file mode 100644
index 000000000..d1b3642c3
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/generator.cpp
@@ -0,0 +1,2216 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "generator.h"
+
+#include "access.h"
+#include "aggregate.h"
+#include "classnode.h"
+#include "codemarker.h"
+#include "collectionnode.h"
+#include "comparisoncategory.h"
+#include "config.h"
+#include "doc.h"
+#include "editdistance.h"
+#include "enumnode.h"
+#include "examplenode.h"
+#include "functionnode.h"
+#include "node.h"
+#include "openedlist.h"
+#include "propertynode.h"
+#include "qdocdatabase.h"
+#include "qmltypenode.h"
+#include "qmlpropertynode.h"
+#include "quoter.h"
+#include "sharedcommentnode.h"
+#include "tokenizer.h"
+#include "typedefnode.h"
+#include "utilities.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qregularexpression.h>
+
+#ifndef QT_BOOTSTRAPPED
+# include "QtCore/qurl.h"
+#endif
+
+#include <string>
+
+using namespace std::literals::string_literals;
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+Generator *Generator::s_currentGenerator;
+QMap<QString, QMap<QString, QString>> Generator::s_fmtLeftMaps;
+QMap<QString, QMap<QString, QString>> Generator::s_fmtRightMaps;
+QList<Generator *> Generator::s_generators;
+QString Generator::s_outDir;
+QString Generator::s_outSubdir;
+QStringList Generator::s_outFileNames;
+QSet<QString> Generator::s_trademarks;
+QSet<QString> Generator::s_outputFormats;
+QHash<QString, QString> Generator::s_outputPrefixes;
+QHash<QString, QString> Generator::s_outputSuffixes;
+QString Generator::s_project;
+bool Generator::s_noLinkErrors = false;
+bool Generator::s_autolinkErrors = false;
+bool Generator::s_redirectDocumentationToDevNull = false;
+bool Generator::s_useOutputSubdirs = true;
+QmlTypeNode *Generator::s_qmlTypeContext = nullptr;
+
+static QRegularExpression tag("</?@[^>]*>");
+static QLatin1String amp("&amp;");
+static QLatin1String gt("&gt;");
+static QLatin1String lt("&lt;");
+static QLatin1String quot("&quot;");
+
+/*!
+ Constructs the generator base class. Prepends the newly
+ constructed generator to the list of output generators.
+ Sets a pointer to the QDoc database singleton, which is
+ available to the generator subclasses.
+ */
+Generator::Generator(FileResolver& file_resolver)
+ : file_resolver{file_resolver}
+{
+ m_qdb = QDocDatabase::qdocDB();
+ s_generators.prepend(this);
+}
+
+/*!
+ Destroys the generator after removing it from the list of
+ output generators.
+ */
+Generator::~Generator()
+{
+ s_generators.removeAll(this);
+}
+
+void Generator::appendFullName(Text &text, const Node *apparentNode, const Node *relative,
+ const Node *actualNode)
+{
+ if (actualNode == nullptr)
+ actualNode = apparentNode;
+ text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << Atom(Atom::String, apparentNode->plainFullName(relative))
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+}
+
+void Generator::appendFullName(Text &text, const Node *apparentNode, const QString &fullName,
+ const Node *actualNode)
+{
+ if (actualNode == nullptr)
+ actualNode = apparentNode;
+ text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, fullName)
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+}
+
+/*!
+ Append the signature for the function named in \a node to
+ \a text, so that is a link to the documentation for that
+ function.
+ */
+void Generator::appendSignature(Text &text, const Node *node)
+{
+ text << Atom(Atom::LinkNode, CodeMarker::stringForNode(node))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << Atom(Atom::String, node->signature(Node::SignaturePlain))
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+}
+
+/*!
+ Generate a bullet list of function signatures. The function
+ nodes are in \a nodes. It uses the \a relative node and the
+ \a marker for the generation.
+ */
+void Generator::signatureList(const NodeList &nodes, const Node *relative, CodeMarker *marker)
+{
+ Text text;
+ int count = 0;
+ text << Atom(Atom::ListLeft, QString("bullet"));
+ for (const auto &node : nodes) {
+ text << Atom(Atom::ListItemNumber, QString::number(++count));
+ text << Atom(Atom::ListItemLeft, QString("bullet"));
+ appendSignature(text, node);
+ text << Atom(Atom::ListItemRight, QString("bullet"));
+ }
+ text << Atom(Atom::ListRight, QString("bullet"));
+ generateText(text, relative, marker);
+}
+
+int Generator::appendSortedNames(Text &text, const ClassNode *cn, const QList<RelatedClass> &rc)
+{
+ QMap<QString, Text> classMap;
+ for (const auto &relatedClass : rc) {
+ ClassNode *rcn = relatedClass.m_node;
+ if (rcn && rcn->isInAPI()) {
+ Text className;
+ appendFullName(className, rcn, cn);
+ classMap[className.toString().toLower()] = className;
+ }
+ }
+
+ int index = 0;
+ const QStringList classNames = classMap.keys();
+ for (const auto &className : classNames) {
+ text << classMap[className];
+ text << Utilities::comma(index++, classNames.size());
+ }
+ return index;
+}
+
+int Generator::appendSortedQmlNames(Text &text, const Node *base, const NodeList &subs)
+{
+ QMap<QString, Text> classMap;
+
+ for (const auto sub : subs) {
+ Text full_name;
+ appendFullName(full_name, sub, base);
+ classMap[full_name.toString().toLower()] = full_name;
+ }
+
+ int index = 0;
+ const auto &names = classMap.keys();
+ for (const auto &name : names)
+ text << classMap[name] << Utilities::comma(index++, names.size());
+ return index;
+}
+
+/*!
+ Creates the file named \a fileName in the output directory
+ and returns a QFile pointing to this file. In particular,
+ this method deals with errors when opening the file:
+ the returned QFile is always valid and can be written to.
+
+ \sa beginSubPage()
+ */
+QFile *Generator::openSubPageFile(const Node *node, const QString &fileName)
+{
+ if (s_outFileNames.contains(fileName))
+ node->location().warning("Already generated %1 for this project"_L1.arg(fileName));
+
+ QString path = outputDir() + QLatin1Char('/') + fileName;
+
+ auto outPath = s_redirectDocumentationToDevNull ? QStringLiteral("/dev/null") : path;
+ auto outFile = new QFile(outPath);
+
+ if (!s_redirectDocumentationToDevNull && outFile->exists()) {
+ const QString warningText {"Output file already exists, overwriting %1"_L1.arg(outFile->fileName())};
+ if (qEnvironmentVariableIsSet("QDOC_ALL_OVERWRITES_ARE_WARNINGS"))
+ node->location().warning(warningText);
+ else
+ qCDebug(lcQdoc) << qUtf8Printable(warningText);
+ }
+
+ if (!outFile->open(QFile::WriteOnly | QFile::Text)) {
+ node->location().fatal(
+ QStringLiteral("Cannot open output file '%1'").arg(outFile->fileName()));
+ }
+
+ qCDebug(lcQdoc, "Writing: %s", qPrintable(path));
+ s_outFileNames << fileName;
+ s_trademarks.clear();
+ return outFile;
+}
+
+/*!
+ Creates the file named \a fileName in the output directory.
+ Attaches a QTextStream to the created file, which is written
+ to all over the place using out().
+ */
+void Generator::beginSubPage(const Node *node, const QString &fileName)
+{
+ QFile *outFile = openSubPageFile(node, fileName);
+ auto *out = new QTextStream(outFile);
+ outStreamStack.push(out);
+}
+
+/*!
+ Flush the text stream associated with the subpage, and
+ then pop it off the text stream stack and delete it.
+ This terminates output of the subpage.
+ */
+void Generator::endSubPage()
+{
+ outStreamStack.top()->flush();
+ delete outStreamStack.top()->device();
+ delete outStreamStack.pop();
+}
+
+QString Generator::fileBase(const Node *node) const
+{
+ if (!node->isPageNode() && !node->isCollectionNode())
+ node = node->parent();
+
+ if (node->hasFileNameBase())
+ return node->fileNameBase();
+
+ QString base{node->name()};
+ if (base.endsWith(".html"))
+ base.truncate(base.size() - 5);
+
+ if (node->isCollectionNode()) {
+ if (node->isQmlModule())
+ base.append("-qmlmodule");
+ else if (node->isModule())
+ base.append("-module");
+ base.append(outputSuffix(node));
+ } else if (node->isTextPageNode()) {
+ if (node->isExample()) {
+ base.prepend("%1-"_L1.arg(s_project.toLower()));
+ base.append("-example");
+ }
+ } else if (node->isQmlType()) {
+ /*
+ To avoid file name conflicts in the html directory,
+ we prepend a prefix (by default, "qml-") and an optional suffix
+ to the file name. The suffix, if one exists, is appended to the
+ module name.
+
+ For historical reasons, skip the module name qualifier for QML value types
+ in order to avoid excess redirects in the online docs. TODO: re-assess
+ */
+ if (!node->logicalModuleName().isEmpty() && !node->isQmlBasicType()
+ && (!node->logicalModule()->isInternal() || m_showInternal))
+ base.prepend("%1%2-"_L1.arg(node->logicalModuleName(), outputSuffix(node)));
+
+ } else if (node->isProxyNode()) {
+ base.append("-%1-proxy"_L1.arg(node->tree()->physicalModuleName()));
+ } else {
+ base.clear();
+ const Node *p = node;
+ forever {
+ const Node *pp = p->parent();
+ base.prepend(p->name());
+ if (pp == nullptr || pp->name().isEmpty() || pp->isTextPageNode())
+ break;
+ base.prepend('-'_L1);
+ p = pp;
+ }
+ if (node->isNamespace() && !node->name().isEmpty()) {
+ const auto *ns = static_cast<const NamespaceNode *>(node);
+ if (!ns->isDocumentedHere()) {
+ base.append(QLatin1String("-sub-"));
+ base.append(ns->tree()->camelCaseModuleName());
+ }
+ }
+ base.append(outputSuffix(node));
+ }
+
+ base.prepend(outputPrefix(node));
+ QString canonicalName{ Utilities::asAsciiPrintable(base) };
+ Node *n = const_cast<Node *>(node);
+ n->setFileNameBase(canonicalName);
+ return canonicalName;
+}
+
+/*!
+ Constructs an href link from an example file name, which
+ is a \a path to the example file. If \a fileExt is empty
+ (default value), retrieve the file extension from
+ the generator.
+ */
+QString Generator::linkForExampleFile(const QString &path, const QString &fileExt)
+{
+ QString link{path};
+ link.prepend(s_project.toLower() + QLatin1Char('-'));
+
+ QString canonicalName{ Utilities::asAsciiPrintable(link) };
+ canonicalName.append(QLatin1Char('.'));
+ canonicalName.append(fileExt.isEmpty() ? fileExtension() : fileExt);
+ return canonicalName;
+}
+
+/*!
+ Helper function to construct a title for a file or image page
+ included in an example.
+*/
+QString Generator::exampleFileTitle(const ExampleNode *relative, const QString &fileName)
+{
+ QString suffix;
+ if (relative->files().contains(fileName))
+ suffix = QLatin1String(" Example File");
+ else if (relative->images().contains(fileName))
+ suffix = QLatin1String(" Image File");
+ else
+ return suffix;
+
+ return fileName.mid(fileName.lastIndexOf(QLatin1Char('/')) + 1) + suffix;
+}
+
+/*!
+ If the \a node has a URL, return the URL as the file name.
+ Otherwise, construct the file name from the fileBase() and
+ either the provided \a extension or fileExtension(), and
+ return the constructed name.
+ */
+QString Generator::fileName(const Node *node, const QString &extension) const
+{
+ if (!node->url().isEmpty())
+ return node->url();
+
+ QString name = fileBase(node) + QLatin1Char('.');
+ return name + (extension.isNull() ? fileExtension() : extension);
+}
+
+/*!
+ Clean the given \a ref to be used as an HTML anchor or an \c xml:id.
+ If \a xmlCompliant is set to \c true, a stricter process is used, as XML
+ is more rigorous in what it accepts. Otherwise, if \a xmlCompliant is set to
+ \c false, the basic HTML transformations are applied.
+
+ More specifically, only XML NCNames are allowed
+ (https://www.w3.org/TR/REC-xml-names/#NT-NCName).
+ */
+QString Generator::cleanRef(const QString &ref, bool xmlCompliant)
+{
+ // XML-compliance is ensured in two ways:
+ // - no digit (0-9) at the beginning of an ID (many IDs do not respect this property)
+ // - no colon (:) anywhere in the ID (occurs very rarely)
+
+ QString clean;
+
+ if (ref.isEmpty())
+ return clean;
+
+ clean.reserve(ref.size() + 20);
+ const QChar c = ref[0];
+ const uint u = c.unicode();
+
+ if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (!xmlCompliant && u >= '0' && u <= '9')) {
+ clean += c;
+ } else if (xmlCompliant && u >= '0' && u <= '9') {
+ clean += QLatin1Char('A') + c;
+ } else if (u == '~') {
+ clean += "dtor.";
+ } else if (u == '_') {
+ clean += "underscore.";
+ } else {
+ clean += QLatin1Char('A');
+ }
+
+ for (int i = 1; i < ref.size(); i++) {
+ const QChar c = ref[i];
+ const uint u = c.unicode();
+ if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (u >= '0' && u <= '9') || u == '-'
+ || u == '_' || (xmlCompliant && u == ':') || u == '.') {
+ clean += c;
+ } else if (c.isSpace()) {
+ clean += QLatin1Char('-');
+ } else if (u == '!') {
+ clean += "-not";
+ } else if (u == '&') {
+ clean += "-and";
+ } else if (u == '<') {
+ clean += "-lt";
+ } else if (u == '=') {
+ clean += "-eq";
+ } else if (u == '>') {
+ clean += "-gt";
+ } else if (u == '#') {
+ clean += QLatin1Char('#');
+ } else {
+ clean += QLatin1Char('-');
+ clean += QString::number(static_cast<int>(u), 16);
+ }
+ }
+ return clean;
+}
+
+QMap<QString, QString> &Generator::formattingLeftMap()
+{
+ return s_fmtLeftMaps[format()];
+}
+
+QMap<QString, QString> &Generator::formattingRightMap()
+{
+ return s_fmtRightMaps[format()];
+}
+
+/*!
+ Returns the full document location.
+ */
+QString Generator::fullDocumentLocation(const Node *node)
+{
+ if (node == nullptr)
+ return QString();
+ if (!node->url().isEmpty())
+ return node->url();
+
+ QString parentName;
+ QString anchorRef;
+
+ if (node->isNamespace()) {
+ /*
+ The root namespace has no name - check for this before creating
+ an attribute containing the location of any documentation.
+ */
+ if (!fileBase(node).isEmpty())
+ parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
+ else
+ return QString();
+ } else if (node->isQmlType()) {
+ return fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
+ } else if (node->isTextPageNode() || node->isCollectionNode()) {
+ parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
+ } else if (fileBase(node).isEmpty())
+ return QString();
+
+ Node *parentNode = nullptr;
+
+ if ((parentNode = node->parent())) {
+ // use the parent's name unless the parent is the root namespace
+ if (!node->parent()->isNamespace() || !node->parent()->name().isEmpty())
+ parentName = fullDocumentLocation(node->parent());
+ }
+
+ switch (node->nodeType()) {
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ case Node::Namespace:
+ case Node::Proxy:
+ parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension();
+ break;
+ case Node::Function: {
+ const auto *fn = static_cast<const FunctionNode *>(node);
+ switch (fn->metaness()) {
+ case FunctionNode::QmlSignal:
+ anchorRef = QLatin1Char('#') + node->name() + "-signal";
+ break;
+ case FunctionNode::QmlSignalHandler:
+ anchorRef = QLatin1Char('#') + node->name() + "-signal-handler";
+ break;
+ case FunctionNode::QmlMethod:
+ anchorRef = QLatin1Char('#') + node->name() + "-method";
+ break;
+ default:
+ if (fn->isDtor())
+ anchorRef = "#dtor." + fn->name().mid(1);
+ else if (fn->hasOneAssociatedProperty() && fn->doc().isEmpty())
+ return fullDocumentLocation(fn->associatedProperties()[0]);
+ else if (fn->overloadNumber() > 0)
+ anchorRef = QLatin1Char('#') + cleanRef(fn->name()) + QLatin1Char('-')
+ + QString::number(fn->overloadNumber());
+ else
+ anchorRef = QLatin1Char('#') + cleanRef(fn->name());
+ break;
+ }
+ break;
+ }
+ /*
+ Use node->name() instead of fileBase(node) as
+ the latter returns the name in lower-case. For
+ HTML anchors, we need to preserve the case.
+ */
+ case Node::Enum:
+ anchorRef = QLatin1Char('#') + node->name() + "-enum";
+ break;
+ case Node::Typedef: {
+ const auto *tdef = static_cast<const TypedefNode *>(node);
+ if (tdef->associatedEnum())
+ return fullDocumentLocation(tdef->associatedEnum());
+ } Q_FALLTHROUGH();
+ case Node::TypeAlias:
+ anchorRef = QLatin1Char('#') + node->name() + "-typedef";
+ break;
+ case Node::Property:
+ anchorRef = QLatin1Char('#') + node->name() + "-prop";
+ break;
+ case Node::SharedComment: {
+ if (!node->isPropertyGroup())
+ break;
+ } Q_FALLTHROUGH();
+ case Node::QmlProperty:
+ if (node->isAttached())
+ anchorRef = QLatin1Char('#') + node->name() + "-attached-prop";
+ else
+ anchorRef = QLatin1Char('#') + node->name() + "-prop";
+ break;
+ case Node::Variable:
+ anchorRef = QLatin1Char('#') + node->name() + "-var";
+ break;
+ case Node::QmlType:
+ case Node::Page:
+ case Node::Group:
+ case Node::HeaderFile:
+ case Node::Module:
+ case Node::QmlModule: {
+ parentName = fileBase(node);
+ parentName.replace(QLatin1Char('/'), QLatin1Char('-'))
+ .replace(QLatin1Char('.'), QLatin1Char('-'));
+ parentName += QLatin1Char('.') + currentGenerator()->fileExtension();
+ } break;
+ default:
+ break;
+ }
+
+ if (!node->isClassNode() && !node->isNamespace()) {
+ if (node->isDeprecated())
+ parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(),
+ "-obsolete." + currentGenerator()->fileExtension());
+ }
+
+ return parentName.toLower() + anchorRef;
+}
+
+void Generator::generateAlsoList(const Node *node, CodeMarker *marker)
+{
+ QList<Text> alsoList = node->doc().alsoList();
+ supplementAlsoList(node, alsoList);
+
+ if (!alsoList.isEmpty()) {
+ Text text;
+ text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) << "See also "
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
+
+ for (int i = 0; i < alsoList.size(); ++i)
+ text << alsoList.at(i) << Utilities::separator(i, alsoList.size());
+
+ text << Atom::ParaRight;
+ generateText(text, node, marker);
+ }
+}
+
+const Atom *Generator::generateAtomList(const Atom *atom, const Node *relative, CodeMarker *marker,
+ bool generate, int &numAtoms)
+{
+ while (atom != nullptr) {
+ if (atom->type() == Atom::FormatIf) {
+ int numAtoms0 = numAtoms;
+ bool rightFormat = canHandleFormat(atom->string());
+ atom = generateAtomList(atom->next(), relative, marker, generate && rightFormat,
+ numAtoms);
+ if (atom == nullptr)
+ return nullptr;
+
+ if (atom->type() == Atom::FormatElse) {
+ ++numAtoms;
+ atom = generateAtomList(atom->next(), relative, marker, generate && !rightFormat,
+ numAtoms);
+ if (atom == nullptr)
+ return nullptr;
+ }
+
+ if (atom->type() == Atom::FormatEndif) {
+ if (generate && numAtoms0 == numAtoms) {
+ relative->location().warning(QStringLiteral("Output format %1 not handled %2")
+ .arg(format(), outFileName()));
+ Atom unhandledFormatAtom(Atom::UnhandledFormat, format());
+ generateAtomList(&unhandledFormatAtom, relative, marker, generate, numAtoms);
+ }
+ atom = atom->next();
+ }
+ } else if (atom->type() == Atom::FormatElse || atom->type() == Atom::FormatEndif) {
+ return atom;
+ } else {
+ int n = 1;
+ if (generate) {
+ n += generateAtom(atom, relative, marker);
+ numAtoms += n;
+ }
+ while (n-- > 0)
+ atom = atom->next();
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ Generate the body of the documentation from the qdoc comment
+ found with the entity represented by the \a node.
+ */
+void Generator::generateBody(const Node *node, CodeMarker *marker)
+{
+ const FunctionNode *fn = node->isFunction() ? static_cast<const FunctionNode *>(node) : nullptr;
+ if (!node->hasDoc()) {
+ /*
+ Test for special function, like a destructor or copy constructor,
+ that has no documentation.
+ */
+ if (fn) {
+ if (fn->isDtor()) {
+ Text text;
+ text << "Destroys the instance of ";
+ text << fn->parent()->name() << ".";
+ if (fn->isVirtual())
+ text << " The destructor is virtual.";
+ out() << "<p>";
+ generateText(text, node, marker);
+ out() << "</p>";
+ } else if (fn->isCtor()) {
+ Text text;
+ text << "Default constructs an instance of ";
+ text << fn->parent()->name() << ".";
+ out() << "<p>";
+ generateText(text, node, marker);
+ out() << "</p>";
+ } else if (fn->isCCtor()) {
+ Text text;
+ text << "Copy constructor.";
+ out() << "<p>";
+ generateText(text, node, marker);
+ out() << "</p>";
+ } else if (fn->isMCtor()) {
+ Text text;
+ text << "Move-copy constructor.";
+ out() << "<p>";
+ generateText(text, node, marker);
+ out() << "</p>";
+ } else if (fn->isCAssign()) {
+ Text text;
+ text << "Copy-assignment operator.";
+ out() << "<p>";
+ generateText(text, node, marker);
+ out() << "</p>";
+ } else if (fn->isMAssign()) {
+ Text text;
+ text << "Move-assignment operator.";
+ out() << "<p>";
+ generateText(text, node, marker);
+ out() << "</p>";
+ } else if (!node->isWrapper() && !node->isMarkedReimp()) {
+ if (!fn->isIgnored()) // undocumented functions added by Q_OBJECT
+ node->location().warning(QStringLiteral("No documentation for '%1'")
+ .arg(node->plainSignature()));
+ }
+ } else if (!node->isWrapper() && !node->isMarkedReimp()) {
+ // Don't require documentation of things defined in Q_GADGET
+ if (node->name() != QLatin1String("QtGadgetHelper"))
+ node->location().warning(
+ QStringLiteral("No documentation for '%1'").arg(node->plainSignature()));
+ }
+ } else if (!node->isSharingComment()) {
+ // Reimplements clause and type alias info precede body text
+ if (fn && !fn->overridesThis().isEmpty())
+ generateReimplementsClause(fn, marker);
+ else if (node->isProperty()) {
+ if (static_cast<const PropertyNode *>(node)->propertyType() != PropertyNode::PropertyType::StandardProperty)
+ generateAddendum(node, BindableProperty, marker);
+ }
+
+ if (!generateText(node->doc().body(), node, marker)) {
+ if (node->isMarkedReimp())
+ return;
+ }
+
+ if (fn) {
+ if (fn->isQmlSignal())
+ generateAddendum(node, QmlSignalHandler, marker);
+ if (fn->isPrivateSignal())
+ generateAddendum(node, PrivateSignal, marker);
+ if (fn->isInvokable())
+ generateAddendum(node, Invokable, marker);
+ if (fn->hasAssociatedProperties())
+ generateAddendum(node, AssociatedProperties, marker);
+ }
+
+ // Generate warnings
+ if (node->isEnumType()) {
+ const auto *enume = static_cast<const EnumNode *>(node);
+
+ QSet<QString> definedItems;
+ const QList<EnumItem> &items = enume->items();
+ for (const auto &item : items)
+ definedItems.insert(item.name());
+
+ const auto &documentedItemList = enume->doc().enumItemNames();
+ QSet<QString> documentedItems(documentedItemList.cbegin(), documentedItemList.cend());
+ const QSet<QString> allItems = definedItems + documentedItems;
+ if (allItems.size() > definedItems.size()
+ || allItems.size() > documentedItems.size()) {
+ for (const auto &it : allItems) {
+ if (!definedItems.contains(it)) {
+ QString details;
+ QString best = nearestName(it, definedItems);
+ if (!best.isEmpty() && !documentedItems.contains(best))
+ details = QStringLiteral("Maybe you meant '%1'?").arg(best);
+
+ node->doc().location().warning(
+ QStringLiteral("No such enum item '%1' in %2")
+ .arg(it, node->plainFullName()),
+ details);
+ } else if (!documentedItems.contains(it)) {
+ node->doc().location().warning(
+ QStringLiteral("Undocumented enum item '%1' in %2")
+ .arg(it, node->plainFullName()));
+ }
+ }
+ }
+ } else if (fn) {
+ const QSet<QString> declaredNames = fn->parameters().getNames();
+ const QSet<QString> documentedNames = fn->doc().parameterNames();
+ if (declaredNames != documentedNames) {
+ for (const auto &name : declaredNames) {
+ if (!documentedNames.contains(name)) {
+ if (fn->isActive() || fn->isPreliminary()) {
+ // Require no parameter documentation for overrides and overloads,
+ // and only require it for non-overloaded constructors.
+ if (!fn->isMarkedReimp() && !fn->isOverload() &&
+ !(fn->isSomeCtor() && fn->hasOverloads())) {
+ fn->doc().location().warning(
+ QStringLiteral("Undocumented parameter '%1' in %2")
+ .arg(name, node->plainFullName()));
+ }
+ }
+ }
+ }
+ for (const auto &name : documentedNames) {
+ if (!declaredNames.contains(name)) {
+ QString best = nearestName(name, declaredNames);
+ QString details;
+ if (!best.isEmpty())
+ details = QStringLiteral("Maybe you meant '%1'?").arg(best);
+ fn->doc().location().warning(QStringLiteral("No such parameter '%1' in %2")
+ .arg(name, fn->plainFullName()),
+ details);
+ }
+ }
+ }
+ /*
+ This return value check should be implemented
+ for all functions with a return type.
+ mws 13/12/2018
+ */
+ if (!fn->isDeprecated() && fn->returnsBool() && !fn->isMarkedReimp()
+ && !fn->isOverload()) {
+ if (!fn->doc().body().contains("return"))
+ node->doc().location().warning(
+ QStringLiteral("Undocumented return value "
+ "(hint: use 'return' or 'returns' in the text"));
+ }
+ }
+ }
+ generateEnumValuesForQmlProperty(node, marker);
+ generateRequiredLinks(node, marker);
+}
+
+/*!
+ Generates either a link to the project folder for example \a node, or a list
+ of links files/images if 'url.examples config' variable is not defined.
+
+ Does nothing for non-example nodes.
+*/
+void Generator::generateRequiredLinks(const Node *node, CodeMarker *marker)
+{
+ if (!node->isExample())
+ return;
+
+ const auto *en = static_cast<const ExampleNode *>(node);
+ QString exampleUrl{Config::instance().get(CONFIG_URL + Config::dot + CONFIG_EXAMPLES).asString()};
+
+ if (exampleUrl.isEmpty()) {
+ if (!en->noAutoList()) {
+ generateFileList(en, marker, false); // files
+ generateFileList(en, marker, true); // images
+ }
+ } else {
+ generateLinkToExample(en, marker, exampleUrl);
+ }
+}
+
+/*!
+ Generates an external link to the project folder for example \a node.
+ The path to the example replaces a placeholder '\1' character if
+ one is found in the \a baseUrl string. If no such placeholder is found,
+ the path is appended to \a baseUrl, after a '/' character if \a baseUrl did
+ not already end in one.
+*/
+void Generator::generateLinkToExample(const ExampleNode *en, CodeMarker *marker,
+ const QString &baseUrl)
+{
+ QString exampleUrl(baseUrl);
+ QString link;
+#ifndef QT_BOOTSTRAPPED
+ link = QUrl(exampleUrl).host();
+#endif
+ if (!link.isEmpty())
+ link.prepend(" @ ");
+ link.prepend("Example project");
+
+ const QLatin1Char separator('/');
+ const QLatin1Char placeholder('\1');
+ if (!exampleUrl.contains(placeholder)) {
+ if (!exampleUrl.endsWith(separator))
+ exampleUrl += separator;
+ exampleUrl += placeholder;
+ }
+
+ // Construct a path to the example; <install path>/<example name>
+ QString pathRoot;
+ QStringMultiMap *metaTagMap = en->doc().metaTagMap();
+ if (metaTagMap)
+ pathRoot = metaTagMap->value(QLatin1String("installpath"));
+ if (pathRoot.isEmpty())
+ pathRoot = Config::instance().get(CONFIG_EXAMPLESINSTALLPATH).asString();
+ QStringList path = QStringList() << pathRoot << en->name();
+ path.removeAll(QString());
+
+ Text text;
+ text << Atom::ParaLeft
+ << Atom(Atom::Link, exampleUrl.replace(placeholder, path.join(separator)))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, link)
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom::ParaRight;
+
+ generateText(text, nullptr, marker);
+}
+
+void Generator::addImageToCopy(const ExampleNode *en, const ResolvedFile& resolved_file)
+{
+ QDir dirInfo;
+ // TODO: [uncentralized-output-directory-structure]
+ const QString prefix("/images/used-in-examples");
+
+ // TODO: Generators probably should not need to keep track of which files were generated.
+ // Understand if we really need this information and where it should
+ // belong, considering that it should be part of whichever system
+ // would actually store the file itself.
+ s_outFileNames << prefix.mid(1) + "/" + resolved_file.get_query();
+
+
+ // TODO: [uncentralized-output-directory-structure]
+ QString imgOutDir = s_outDir + prefix + "/" + QFileInfo{resolved_file.get_query()}.path();
+ if (!dirInfo.mkpath(imgOutDir))
+ en->location().fatal(QStringLiteral("Cannot create output directory '%1'").arg(imgOutDir));
+ Config::copyFile(en->location(), resolved_file.get_path(), QFileInfo{resolved_file.get_query()}.fileName(), imgOutDir);
+}
+
+// TODO: [multi-purpose-function-with-flag][generate-file-list]
+// Avoid the use of a boolean flag to dispatch to the correct
+// implementation trough branching.
+// We always have to process both images and files, such that we
+// should consider to remove the branching altogheter, performing both
+// operations in a single call.
+// Otherwise, if this turns out to be infeasible, complex or
+// possibly-confusing, consider extracting the processing code outside
+// the function and provide two higer-level dispathing functions for
+// files and images.
+
+/*!
+ This function is called when the documentation for an example is
+ being formatted. It outputs a list of files for the example, which
+ can be the example's source files or the list of images used by the
+ example. The images are copied into a subtree of
+ \c{...doc/html/images/used-in-examples/...}
+*/
+void Generator::generateFileList(const ExampleNode *en, CodeMarker *marker, bool images)
+{
+ Text text;
+ OpenedList openedList(OpenedList::Bullet);
+ QString tag;
+ QStringList paths;
+ Atom::AtomType atomType = Atom::ExampleFileLink;
+
+ if (images) {
+ paths = en->images();
+ tag = "Images:";
+ atomType = Atom::ExampleImageLink;
+ } else { // files
+ paths = en->files();
+ tag = "Files:";
+ }
+ std::sort(paths.begin(), paths.end(), Generator::comparePaths);
+
+ text << Atom::ParaLeft << tag << Atom::ParaRight;
+ text << Atom(Atom::ListLeft, openedList.styleString());
+
+ for (const auto &path : std::as_const(paths)) {
+ auto maybe_resolved_file{file_resolver.resolve(path)};
+ if (!maybe_resolved_file) {
+ // TODO: [uncentralized-admonition][failed-resolve-file]
+ QString details = std::transform_reduce(
+ file_resolver.get_search_directories().cbegin(),
+ file_resolver.get_search_directories().cend(),
+ u"Searched directories:"_s,
+ std::plus(),
+ [](const DirectoryPath &directory_path) -> QString { return u' ' + directory_path.value(); }
+ );
+
+ en->location().warning(u"(Generator)Cannot find file to quote from: %1"_s.arg(path), details);
+
+ continue;
+ }
+
+ auto file{*maybe_resolved_file};
+ if (images) addImageToCopy(en, file);
+ else generateExampleFilePage(en, file, marker);
+
+ openedList.next();
+ text << Atom(Atom::ListItemNumber, openedList.numberString())
+ << Atom(Atom::ListItemLeft, openedList.styleString()) << Atom::ParaLeft
+ << Atom(atomType, file.get_query()) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << file.get_query()
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom::ParaRight
+ << Atom(Atom::ListItemRight, openedList.styleString());
+ }
+ text << Atom(Atom::ListRight, openedList.styleString());
+ if (!paths.isEmpty())
+ generateText(text, en, marker);
+}
+
+/*!
+ Recursive writing of HTML files from the root \a node.
+ */
+void Generator::generateDocumentation(Node *node)
+{
+ if (!node->url().isNull())
+ return;
+ if (node->isIndexNode())
+ return;
+ if (node->isInternal() && !m_showInternal)
+ return;
+ if (node->isExternalPage())
+ return;
+
+ /*
+ Obtain a code marker for the source file.
+ */
+ CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath());
+
+ if (node->parent() != nullptr) {
+ if (node->isCollectionNode()) {
+ /*
+ A collection node collects: groups, C++ modules, or QML
+ modules. Testing for a CollectionNode must be done
+ before testing for a TextPageNode because a
+ CollectionNode is a PageNode at this point.
+
+ Don't output an HTML page for the collection node unless
+ the \group, \module, or \qmlmodule command was actually
+ seen by qdoc in the qdoc comment for the node.
+
+ A key prerequisite in this case is the call to
+ mergeCollections(cn). We must determine whether this
+ group, module or QML module has members in other
+ modules. We know at this point that cn's members list
+ contains only members in the current module. Therefore,
+ before outputting the page for cn, we must search for
+ members of cn in the other modules and add them to the
+ members list.
+ */
+ auto *cn = static_cast<CollectionNode *>(node);
+ if (cn->wasSeen()) {
+ m_qdb->mergeCollections(cn);
+ beginSubPage(node, fileName(node));
+ generateCollectionNode(cn, marker);
+ endSubPage();
+ } else if (cn->isGenericCollection()) {
+ // Currently used only for the module's related orphans page
+ // but can be generalized for other kinds of collections if
+ // other use cases pop up.
+ QString name = cn->name().toLower();
+ name.replace(QChar(' '), QString("-"));
+ QString filename =
+ cn->tree()->physicalModuleName() + "-" + name + "." + fileExtension();
+ beginSubPage(node, filename);
+ generateGenericCollectionPage(cn, marker);
+ endSubPage();
+ }
+ } else if (node->isTextPageNode()) {
+ beginSubPage(node, fileName(node));
+ generatePageNode(static_cast<PageNode *>(node), marker);
+ endSubPage();
+ } else if (node->isAggregate()) {
+ if ((node->isClassNode() || node->isHeader() || node->isNamespace())
+ && node->docMustBeGenerated()) {
+ beginSubPage(node, fileName(node));
+ generateCppReferencePage(static_cast<Aggregate *>(node), marker);
+ endSubPage();
+ } else if (node->isQmlType()) {
+ beginSubPage(node, fileName(node));
+ auto *qcn = static_cast<QmlTypeNode *>(node);
+ generateQmlTypePage(qcn, marker);
+ endSubPage();
+ } else if (node->isProxyNode()) {
+ beginSubPage(node, fileName(node));
+ generateProxyPage(static_cast<Aggregate *>(node), marker);
+ endSubPage();
+ }
+ }
+ }
+
+ if (node->isAggregate()) {
+ auto *aggregate = static_cast<Aggregate *>(node);
+ const NodeList &children = aggregate->childNodes();
+ for (auto *child : children) {
+ if (child->isPageNode() && !child->isPrivate()) {
+ generateDocumentation(child);
+ } else if (!node->parent() && child->isInAPI() && !child->isRelatedNonmember()) {
+ // Warn if there are documented non-page-generating nodes in the root namespace
+ child->location().warning(u"No documentation generated for %1 '%2' in global scope."_s
+ .arg(typeString(child), child->name()),
+ u"Maybe you forgot to use the '\\relates' command?"_s);
+ child->setStatus(Node::DontDocument);
+ } else if (child->isQmlModule() && !child->wasSeen()) {
+ // An undocumented QML module that was constructed as a placeholder
+ auto *qmlModule = static_cast<CollectionNode *>(child);
+ for (const auto *member : qmlModule->members()) {
+ member->location().warning(
+ u"Undocumented QML module '%1' referred by type '%2' or its members"_s
+ .arg(qmlModule->name(), member->name()),
+ u"Maybe you forgot to document '\\qmlmodule %1'?"_s
+ .arg(qmlModule->name()));
+ }
+ } else if (child->isQmlType() && !child->hasDoc()) {
+ // A placeholder QML type with incorrect module identifier
+ auto *qmlType = static_cast<QmlTypeNode *>(child);
+ if (auto qmid = qmlType->logicalModuleName(); !qmid.isEmpty())
+ qmlType->location().warning(u"No such type '%1' in QML module '%2'"_s
+ .arg(qmlType->name(), qmid));
+ }
+ }
+ }
+}
+
+void Generator::generateReimplementsClause(const FunctionNode *fn, CodeMarker *marker)
+{
+ if (fn->overridesThis().isEmpty() || !fn->parent()->isClassNode())
+ return;
+
+ auto *cn = static_cast<ClassNode *>(fn->parent());
+ const FunctionNode *overrides = cn->findOverriddenFunction(fn);
+ if (overrides && !overrides->isPrivate() && !overrides->parent()->isPrivate()) {
+ if (overrides->hasDoc()) {
+ Text text;
+ text << Atom::ParaLeft << "Reimplements: ";
+ QString fullName =
+ overrides->parent()->name()
+ + "::" + overrides->signature(Node::SignaturePlain);
+ appendFullName(text, overrides->parent(), fullName, overrides);
+ text << "." << Atom::ParaRight;
+ generateText(text, fn, marker);
+ } else {
+ fn->doc().location().warning(
+ QStringLiteral("Illegal \\reimp; no documented virtual function for %1")
+ .arg(overrides->plainSignature()));
+ }
+ return;
+ }
+ const PropertyNode *sameName = cn->findOverriddenProperty(fn);
+ if (sameName && sameName->hasDoc()) {
+ Text text;
+ text << Atom::ParaLeft << "Reimplements an access function for property: ";
+ QString fullName = sameName->parent()->name() + "::" + sameName->name();
+ appendFullName(text, sameName->parent(), fullName, sameName);
+ text << "." << Atom::ParaRight;
+ generateText(text, fn, marker);
+ }
+}
+
+QString Generator::formatSince(const Node *node)
+{
+ QStringList since = node->since().split(QLatin1Char(' '));
+
+ // If there is only one argument, assume it is the Qt version number.
+ if (since.size() == 1)
+ return "Qt " + since[0];
+
+ // Otherwise, use the original <project> <version> string.
+ return node->since();
+}
+
+/*!
+ \internal
+ Returns a string representing status information of a \a node.
+
+ If a status description is returned, it is one of:
+ \list
+ \li Custom status set explicitly in node's documentation using
+ \c {\meta {status} {<description>}},
+ \li 'Deprecated [since <version>]' (\\deprecated [<version>]),
+ \li 'Until <version>',
+ \li 'Preliminary' (\\preliminary), or
+ \li The description adopted from associated module's state:
+ \c {\modulestate {<description>}}.
+ \endlist
+
+ Otherwise, returns \c std::nullopt.
+*/
+std::optional<QString> formatStatus(const Node *node, QDocDatabase *qdb)
+{
+ QString status;
+
+ if (const auto metaMap = node->doc().metaTagMap(); metaMap) {
+ status = metaMap->value("status");
+ if (!status.isEmpty())
+ return {status};
+ }
+ const auto since = node->deprecatedSince();
+ if (node->status() == Node::Deprecated) {
+ status = u"Deprecated"_s;
+ if (!since.isEmpty())
+ status += " since %1"_L1.arg(since);
+ } else if (!since.isEmpty()) {
+ status = "Until %1"_L1.arg(since);
+ } else if (node->status() == Node::Preliminary) {
+ status = u"Preliminary"_s;
+ } else if (const auto collection = qdb->getModuleNode(node); collection) {
+ status = collection->state();
+ }
+
+ return status.isEmpty() ? std::nullopt : std::optional(status);
+}
+
+void Generator::generateSince(const Node *node, CodeMarker *marker)
+{
+ if (!node->since().isEmpty()) {
+ Text text;
+ text << Atom::ParaLeft << "This " << typeString(node) << " was introduced in "
+ << formatSince(node) << "." << Atom::ParaRight;
+ generateText(text, node, marker);
+ }
+}
+
+void Generator::generateNoexceptNote(const Node* node, CodeMarker* marker) {
+ std::vector<const Node*> nodes;
+ if (node->isSharedCommentNode()) {
+ auto shared_node = static_cast<const SharedCommentNode*>(node);
+ nodes.reserve(shared_node->collective().size());
+ nodes.insert(nodes.begin(), shared_node->collective().begin(), shared_node->collective().end());
+ } else nodes.push_back(node);
+
+ std::size_t counter{1};
+ for (const Node* node : nodes) {
+ if (node->isFunction(Node::CPP)) {
+ if (auto exception_info = static_cast<const FunctionNode*>(node)->getNoexcept(); exception_info && !(*exception_info).isEmpty()) {
+ Text text;
+ text << Atom::NoteLeft
+ << (nodes.size() > 1 ? QString::fromStdString(" ("s + std::to_string(counter) + ")"s) : QString::fromStdString("This ") + typeString(node))
+ << " does not throw any exception when " << "\"" << *exception_info << "\"" << " is true."
+ << Atom::NoteRight;
+ generateText(text, node, marker);
+ }
+ }
+
+ ++counter;
+ }
+}
+
+void Generator::generateStatus(const Node *node, CodeMarker *marker)
+{
+ Text text;
+
+ switch (node->status()) {
+ case Node::Active:
+ // Output the module 'state' description if set.
+ if (node->isModule() || node->isQmlModule()) {
+ const QString &state = static_cast<const CollectionNode*>(node)->state();
+ if (!state.isEmpty()) {
+ text << Atom::ParaLeft << "This " << typeString(node) << " is in "
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_ITALIC) << state
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_ITALIC) << " state."
+ << Atom::ParaRight;
+ break;
+ }
+ }
+ if (const auto version = node->deprecatedSince(); !version.isEmpty()) {
+ text << Atom::ParaLeft << "This " << typeString(node)
+ << " is scheduled for deprecation in version "
+ << version << "." << Atom::ParaRight;
+ }
+ break;
+ case Node::Preliminary:
+ text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) << "This "
+ << typeString(node) << " is under development and is subject to change."
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << Atom::ParaRight;
+ break;
+ case Node::Deprecated:
+ text << Atom::ParaLeft;
+ if (node->isAggregate())
+ text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD);
+ text << "This " << typeString(node) << " is deprecated";
+ if (const QString &version = node->deprecatedSince(); !version.isEmpty()) {
+ text << " since ";
+ if (node->isQmlNode() && !node->logicalModuleName().isEmpty())
+ text << node->logicalModuleName() << " ";
+ text << version;
+ }
+
+ text << ". We strongly advise against using it in new code.";
+ if (node->isAggregate())
+ text << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
+ text << Atom::ParaRight;
+ break;
+ case Node::Internal:
+ default:
+ break;
+ }
+ generateText(text, node, marker);
+}
+
+/*!
+ Generates an addendum note of type \a type for \a node, using \a marker
+ as the code marker.
+*/
+void Generator::generateAddendum(const Node *node, Addendum type, CodeMarker *marker,
+ bool generateNote)
+{
+ Q_ASSERT(node && !node->name().isEmpty());
+ Text text;
+ text << Atom(Atom::DivLeft,
+ "class=\"admonition %1\""_L1.arg(generateNote ? u"note"_s : u"auto"_s));
+ text << Atom::ParaLeft;
+
+ if (generateNote) {
+ text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
+ << "Note: " << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD);
+ }
+
+ switch (type) {
+ case Invokable:
+ text << "This function can be invoked via the meta-object system and from QML. See "
+ << Atom(Atom::AutoLink, "Q_INVOKABLE")
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << ".";
+ break;
+ case PrivateSignal:
+ text << "This is a private signal. It can be used in signal connections "
+ "but cannot be emitted by the user.";
+ break;
+ case QmlSignalHandler:
+ {
+ QString handler(node->name());
+ qsizetype prefixLocation = handler.lastIndexOf('.', -2) + 1;
+ handler[prefixLocation] = handler[prefixLocation].toTitleCase();
+ handler.insert(prefixLocation, QLatin1String("on"));
+ text << "The corresponding handler is "
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_TELETYPE) << handler
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_TELETYPE) << ".";
+ break;
+ }
+ case AssociatedProperties:
+ {
+ if (!node->isFunction())
+ return;
+ const auto *fn = static_cast<const FunctionNode *>(node);
+ auto nodes = fn->associatedProperties();
+ if (nodes.isEmpty())
+ return;
+ std::sort(nodes.begin(), nodes.end(), Node::nodeNameLessThan);
+ for (const auto *n : std::as_const(nodes)) {
+ QString msg;
+ const auto *pn = static_cast<const PropertyNode *>(n);
+ switch (pn->role(fn)) {
+ case PropertyNode::FunctionRole::Getter:
+ msg = QStringLiteral("Getter function");
+ break;
+ case PropertyNode::FunctionRole::Setter:
+ msg = QStringLiteral("Setter function");
+ break;
+ case PropertyNode::FunctionRole::Resetter:
+ msg = QStringLiteral("Resetter function");
+ break;
+ case PropertyNode::FunctionRole::Notifier:
+ msg = QStringLiteral("Notifier signal");
+ break;
+ default:
+ continue;
+ }
+ text << msg << " for property " << Atom(Atom::Link, pn->name())
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << pn->name()
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << ". ";
+ }
+ break;
+ }
+ case BindableProperty:
+ {
+ text << "This property supports "
+ << Atom(Atom::Link, "QProperty")
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << "QProperty"
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+ text << " bindings.";
+ break;
+ }
+ default:
+ return;
+ }
+
+ text << Atom::ParaRight
+ << Atom::DivRight;
+ generateText(text, node, marker);
+}
+
+/*!
+ Generate the documentation for \a relative. i.e. \a relative
+ is the node that represents the entity where a qdoc comment
+ was found, and \a text represents the qdoc comment.
+ */
+bool Generator::generateText(const Text &text, const Node *relative, CodeMarker *marker)
+{
+ bool result = false;
+ if (text.firstAtom() != nullptr) {
+ int numAtoms = 0;
+ initializeTextOutput();
+ generateAtomList(text.firstAtom(), relative, marker, true, numAtoms);
+ result = true;
+ }
+ return result;
+}
+
+/*
+ The node is an aggregate, typically a class node, which has
+ a threadsafeness level. This function checks all the children
+ of the node to see if they are exceptions to the node's
+ threadsafeness. If there are any exceptions, the exceptions
+ are added to the appropriate set (reentrant, threadsafe, and
+ nonreentrant, and true is returned. If there are no exceptions,
+ the three node lists remain empty and false is returned.
+ */
+bool Generator::hasExceptions(const Node *node, NodeList &reentrant, NodeList &threadsafe,
+ NodeList &nonreentrant)
+{
+ bool result = false;
+ Node::ThreadSafeness ts = node->threadSafeness();
+ const NodeList &children = static_cast<const Aggregate *>(node)->childNodes();
+ for (auto child : children) {
+ if (!child->isDeprecated()) {
+ switch (child->threadSafeness()) {
+ case Node::Reentrant:
+ reentrant.append(child);
+ if (ts == Node::ThreadSafe)
+ result = true;
+ break;
+ case Node::ThreadSafe:
+ threadsafe.append(child);
+ if (ts == Node::Reentrant)
+ result = true;
+ break;
+ case Node::NonReentrant:
+ nonreentrant.append(child);
+ result = true;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+/*!
+ Returns \c true if a trademark symbol should be appended to the
+ output as determined by \a atom. Trademarks are tracked via the
+ use of the \\tm formatting command.
+
+ Returns true if:
+
+ \list
+ \li \a atom is of type Atom::FormattingRight containing
+ ATOM_FORMATTING_TRADEMARK, and
+ \li The trademarked string is the first appearance on the
+ current sub-page.
+ \endlist
+*/
+bool Generator::appendTrademark(const Atom *atom)
+{
+ if (atom->type() != Atom::FormattingRight)
+ return false;
+ if (atom->string() != ATOM_FORMATTING_TRADEMARK)
+ return false;
+
+ if (atom->count() > 1) {
+ if (s_trademarks.contains(atom->string(1)))
+ return false;
+ s_trademarks << atom->string(1);
+ }
+
+ return true;
+}
+
+static void startNote(Text &text)
+{
+ text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
+ << "Note:" << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << " ";
+}
+
+/*!
+ Generates text that explains how threadsafe and/or reentrant
+ \a node is.
+ */
+void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker)
+{
+ Text text, rlink, tlink;
+ NodeList reentrant;
+ NodeList threadsafe;
+ NodeList nonreentrant;
+ Node::ThreadSafeness ts = node->threadSafeness();
+ bool exceptions = false;
+
+ rlink << Atom(Atom::Link, "reentrant") << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << "reentrant" << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+
+ tlink << Atom(Atom::Link, "thread-safe") << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << "thread-safe" << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+
+ switch (ts) {
+ case Node::UnspecifiedSafeness:
+ break;
+ case Node::NonReentrant:
+ text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
+ << "Warning:" << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << " This "
+ << typeString(node) << " is not " << rlink << "." << Atom::ParaRight;
+ break;
+ case Node::Reentrant:
+ case Node::ThreadSafe:
+ startNote(text);
+ if (node->isAggregate()) {
+ exceptions = hasExceptions(node, reentrant, threadsafe, nonreentrant);
+ text << "All functions in this " << typeString(node) << " are ";
+ if (ts == Node::ThreadSafe)
+ text << tlink;
+ else
+ text << rlink;
+
+ if (!exceptions || (ts == Node::Reentrant && !threadsafe.isEmpty()))
+ text << ".";
+ else
+ text << " with the following exceptions:";
+ } else {
+ text << "This " << typeString(node) << " is ";
+ if (ts == Node::ThreadSafe)
+ text << tlink;
+ else
+ text << rlink;
+ text << ".";
+ }
+ text << Atom::ParaRight;
+ break;
+ default:
+ break;
+ }
+ generateText(text, node, marker);
+
+ if (exceptions) {
+ text.clear();
+ if (ts == Node::Reentrant) {
+ if (!nonreentrant.isEmpty()) {
+ startNote(text);
+ text << "These functions are not " << rlink << ":" << Atom::ParaRight;
+ signatureList(nonreentrant, node, marker);
+ }
+ if (!threadsafe.isEmpty()) {
+ text.clear();
+ startNote(text);
+ text << "These functions are also " << tlink << ":" << Atom::ParaRight;
+ generateText(text, node, marker);
+ signatureList(threadsafe, node, marker);
+ }
+ } else { // thread-safe
+ if (!reentrant.isEmpty()) {
+ startNote(text);
+ text << "These functions are only " << rlink << ":" << Atom::ParaRight;
+ signatureList(reentrant, node, marker);
+ }
+ if (!nonreentrant.isEmpty()) {
+ text.clear();
+ startNote(text);
+ text << "These functions are not " << rlink << ":" << Atom::ParaRight;
+ signatureList(nonreentrant, node, marker);
+ }
+ }
+ }
+}
+
+/*!
+ \internal
+
+ Generates text that describes the comparison category of \a node.
+ The CodeMarker \a marker is passed along to generateText().
+ */
+bool Generator::generateComparisonCategory(const Node *node, CodeMarker *marker)
+{
+ auto category{node->comparisonCategory()};
+ if (category == ComparisonCategory::None)
+ return false;
+
+ Text text;
+ text << Atom::ParaLeft << "This %1 is "_L1.arg(typeString(node))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_ITALIC)
+ << QString::fromStdString(comparisonCategoryAsString(category))
+ << ((category == ComparisonCategory::Equality) ? "-"_L1 : "ly "_L1)
+ << Atom(Atom::String, "comparable"_L1)
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_ITALIC)
+ << "."_L1 << Atom::ParaRight;
+ generateText(text, node, marker);
+ return true;
+}
+
+/*!
+ Generates a list of types that compare to \a node with the comparison
+ category that applies for the relationship, followed by (an optional)
+ descriptive text.
+
+ Returns \c true if text was generated, \c false otherwise.
+ */
+bool Generator::generateComparisonList(const Node *node)
+{
+ Q_ASSERT(node);
+ if (!node->doc().comparesWithMap())
+ return false;
+
+ Text relationshipText;
+ for (auto [key, description] : node->doc().comparesWithMap()->asKeyValueRange()) {
+ const QString &category = QString::fromStdString(comparisonCategoryAsString(key));
+
+ relationshipText << Atom::ParaLeft << "This %1 is "_L1.arg(typeString(node))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD) << category
+ << ((key == ComparisonCategory::Equality) ? "-"_L1 : "ly "_L1)
+ << "comparable"_L1
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD)
+ << " with "_L1;
+
+ const QStringList types{description.firstAtom()->string().split(';'_L1)};
+ for (const auto &name : types)
+ relationshipText << Atom(Atom::AutoLink, name)
+ << Utilities::separator(types.indexOf(name), types.size());
+
+ relationshipText << Atom(Atom::ParaRight) << description;
+ }
+
+ generateText(relationshipText, node, nullptr);
+ return !relationshipText.isEmpty();
+}
+
+/*!
+ Returns the string containing an example code of the input node,
+ if it is an overloaded signal. Otherwise, returns an empty string.
+ */
+QString Generator::getOverloadedSignalCode(const Node *node)
+{
+ if (!node->isFunction())
+ return QString();
+ const auto func = static_cast<const FunctionNode *>(node);
+ if (!func->isSignal() || !func->hasOverloads())
+ return QString();
+
+ // Compute a friendly name for the object of that instance.
+ // e.g: "QAbstractSocket" -> "abstractSocket"
+ QString objectName = node->parent()->name();
+ if (objectName.size() >= 2) {
+ if (objectName[0] == 'Q')
+ objectName = objectName.mid(1);
+ objectName[0] = objectName[0].toLower();
+ }
+
+ // We have an overloaded signal, show an example. Note, for const
+ // overloaded signals, one should use Q{Const,NonConst}Overload, but
+ // it is very unlikely that we will ever have public API overloading
+ // signals by const.
+ QString code = "connect(" + objectName + ", QOverload<";
+ code += func->parameters().generateTypeList();
+ code += ">::of(&" + func->parent()->name() + "::" + func->name() + "),\n [=](";
+ code += func->parameters().generateTypeAndNameList();
+ code += "){ /* ... */ });";
+
+ return code;
+}
+
+/*!
+ If the node is an overloaded signal, add a node with an example on how to connect to it
+ */
+void Generator::generateOverloadedSignal(const Node *node, CodeMarker *marker)
+{
+ QString code = getOverloadedSignalCode(node);
+ if (code.isEmpty())
+ return;
+
+ Text text;
+ text << Atom::ParaLeft << Atom(Atom::FormattingLeft, ATOM_FORMATTING_BOLD)
+ << "Note:" << Atom(Atom::FormattingRight, ATOM_FORMATTING_BOLD) << " Signal "
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_ITALIC) << node->name()
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_ITALIC)
+ << " is overloaded in this class. "
+ "To connect to this signal by using the function pointer syntax, Qt "
+ "provides a convenient helper for obtaining the function pointer as "
+ "shown in this example:"
+ << Atom(Atom::Code, marker->markedUpCode(code, node, node->location()));
+
+ generateText(text, node, marker);
+}
+
+/*!
+ Traverses the database recursively to generate all the documentation.
+ */
+void Generator::generateDocs()
+{
+ s_currentGenerator = this;
+ generateDocumentation(m_qdb->primaryTreeRoot());
+}
+
+Generator *Generator::generatorForFormat(const QString &format)
+{
+ for (const auto &generator : std::as_const(s_generators)) {
+ if (generator->format() == format)
+ return generator;
+ }
+ return nullptr;
+}
+
+QString Generator::indent(int level, const QString &markedCode)
+{
+ if (level == 0)
+ return markedCode;
+
+ QString t;
+ int column = 0;
+
+ int i = 0;
+ while (i < markedCode.size()) {
+ if (markedCode.at(i) == QLatin1Char('\n')) {
+ column = 0;
+ } else {
+ if (column == 0) {
+ for (int j = 0; j < level; j++)
+ t += QLatin1Char(' ');
+ }
+ column++;
+ }
+ t += markedCode.at(i++);
+ }
+ return t;
+}
+
+void Generator::initialize()
+{
+ Config &config = Config::instance();
+ s_outputFormats = config.getOutputFormats();
+ s_redirectDocumentationToDevNull = config.get(CONFIG_REDIRECTDOCUMENTATIONTODEVNULL).asBool();
+
+ for (auto &g : s_generators) {
+ if (s_outputFormats.contains(g->format())) {
+ s_currentGenerator = g;
+ g->initializeGenerator();
+ }
+ }
+
+ const auto &configFormatting = config.subVars(CONFIG_FORMATTING);
+ for (const auto &n : configFormatting) {
+ QString formattingDotName = CONFIG_FORMATTING + Config::dot + n;
+ const auto &formattingDotNames = config.subVars(formattingDotName);
+ for (const auto &f : formattingDotNames) {
+ const auto &configVar = config.get(formattingDotName + Config::dot + f);
+ QString def{configVar.asString()};
+ if (!def.isEmpty()) {
+ int numParams = Config::numParams(def);
+ int numOccs = def.count("\1");
+ if (numParams != 1) {
+ configVar.location().warning(QStringLiteral("Formatting '%1' must "
+ "have exactly one "
+ "parameter (found %2)")
+ .arg(n, numParams));
+ } else if (numOccs > 1) {
+ configVar.location().fatal(QStringLiteral("Formatting '%1' must "
+ "contain exactly one "
+ "occurrence of '\\1' "
+ "(found %2)")
+ .arg(n, numOccs));
+ } else {
+ int paramPos = def.indexOf("\1");
+ s_fmtLeftMaps[f].insert(n, def.left(paramPos));
+ s_fmtRightMaps[f].insert(n, def.mid(paramPos + 1));
+ }
+ }
+ }
+ }
+
+ s_project = config.get(CONFIG_PROJECT).asString();
+ s_outDir = config.getOutputDir();
+ s_outSubdir = s_outDir.mid(s_outDir.lastIndexOf('/') + 1);
+
+ s_outputPrefixes.clear();
+ QStringList items{config.get(CONFIG_OUTPUTPREFIXES).asStringList()};
+ if (!items.isEmpty()) {
+ for (const auto &prefix : items)
+ s_outputPrefixes[prefix] =
+ config.get(CONFIG_OUTPUTPREFIXES + Config::dot + prefix).asString();
+ }
+ if (!items.contains(u"QML"_s))
+ s_outputPrefixes[u"QML"_s] = u"qml-"_s;
+
+ s_outputSuffixes.clear();
+ for (const auto &suffix : config.get(CONFIG_OUTPUTSUFFIXES).asStringList())
+ s_outputSuffixes[suffix] = config.get(CONFIG_OUTPUTSUFFIXES
+ + Config::dot + suffix).asString();
+
+ s_noLinkErrors = config.get(CONFIG_NOLINKERRORS).asBool();
+ s_autolinkErrors = config.get(CONFIG_AUTOLINKERRORS).asBool();
+}
+
+/*!
+ Creates template-specific subdirs (e.g. /styles and /scripts for HTML)
+ and copies the files to them.
+ */
+void Generator::copyTemplateFiles(const QString &configVar, const QString &subDir)
+{
+ // TODO: [resolving-files-unlinked-to-doc]
+ // This is another case of resolving files, albeit it doesn't use Doc::resolveFile.
+ // While it may be left out of a first iteration of the file
+ // resolution logic, it should later be integrated into it.
+ // This should come naturally when the output directory logic is
+ // extracted and copying a file should require a validated
+ // intermediate format.
+ // Do note that what is done here is a bit different from the
+ // resolve file routine that is done for other user-given paths.
+ // Thas is, the paths will always be absolute and not relative as
+ // they are resolved from the configuration.
+ // Ideally, this could be solved in the configuration already,
+ // together with the other configuration resolution processes that
+ // do not abide by the same constraints that, for example, snippet
+ // resolution uses.
+ Config &config = Config::instance();
+ QStringList files = config.getCanonicalPathList(configVar, Config::Validate);
+ const auto &loc = config.get(configVar).location();
+ if (!files.isEmpty()) {
+ QDir dirInfo;
+ // TODO: [uncentralized-output-directory-structure]
+ // As with other places in the generation pass, the details of
+ // where something is saved in the output directory are spread
+ // to whichever part of the generation does the saving.
+ // It is hence complex to build a model of how an output
+ // directory looks like, as the knowledge has no specific
+ // entry point or chain-path that can be followed in full.
+ // Each of those operations should be centralized in a system
+ // that uniquely knows what the format of the output-directory
+ // is and how to perform operations on it.
+ // Later, move this operation to that centralized system.
+ QString templateDir = s_outDir + QLatin1Char('/') + subDir;
+ if (!dirInfo.exists(templateDir) && !dirInfo.mkdir(templateDir)) {
+ // TODO: [uncentralized-admonition]
+ loc.fatal(QStringLiteral("Cannot create %1 directory '%2'").arg(subDir, templateDir));
+ } else {
+ for (const auto &file : files) {
+ if (!file.isEmpty())
+ Config::copyFile(loc, file, file, templateDir);
+ }
+ }
+ }
+}
+
+/*!
+ Reads format-specific variables from config, sets output
+ (sub)directories, creates them on the filesystem and copies the
+ template-specific files.
+ */
+void Generator::initializeFormat()
+{
+ Config &config = Config::instance();
+ s_outFileNames.clear();
+ s_useOutputSubdirs = true;
+ if (config.get(format() + Config::dot + "nosubdirs").asBool())
+ resetUseOutputSubdirs();
+
+ if (s_outputFormats.isEmpty())
+ return;
+
+ s_outDir = config.getOutputDir(format());
+ if (s_outDir.isEmpty()) {
+ Location().fatal(QStringLiteral("No output directory specified in "
+ "configuration file or on the command line"));
+ } else {
+ s_outSubdir = s_outDir.mid(s_outDir.lastIndexOf('/') + 1);
+ }
+
+ QDir outputDir(s_outDir);
+ if (outputDir.exists()) {
+ if (!config.generating() && Generator::useOutputSubdirs()) {
+ if (!outputDir.isEmpty())
+ Location().error(QStringLiteral("Output directory '%1' exists but is not empty")
+ .arg(s_outDir));
+ }
+ } else if (!outputDir.mkpath(QStringLiteral("."))) {
+ Location().fatal(QStringLiteral("Cannot create output directory '%1'").arg(s_outDir));
+ }
+
+ // Output directory exists, which is enough for prepare phase.
+ if (config.preparing())
+ return;
+
+ const QLatin1String imagesDir("images");
+ if (!outputDir.exists(imagesDir) && !outputDir.mkdir(imagesDir))
+ Location().fatal(QStringLiteral("Cannot create images directory '%1'").arg(outputDir.filePath(imagesDir)));
+
+ copyTemplateFiles(format() + Config::dot + CONFIG_STYLESHEETS, "style");
+ copyTemplateFiles(format() + Config::dot + CONFIG_SCRIPTS, "scripts");
+ copyTemplateFiles(format() + Config::dot + CONFIG_EXTRAIMAGES, "images");
+
+ // Use a format-specific .quotinginformation if defined, otherwise a global value
+ if (config.subVars(format()).contains(CONFIG_QUOTINGINFORMATION))
+ m_quoting = config.get(format() + Config::dot + CONFIG_QUOTINGINFORMATION).asBool();
+ else
+ m_quoting = config.get(CONFIG_QUOTINGINFORMATION).asBool();
+}
+
+/*!
+ Updates the generator's m_showInternal from the Config.
+ */
+void Generator::initializeGenerator()
+{
+ m_showInternal = Config::instance().showInternal();
+}
+
+bool Generator::matchAhead(const Atom *atom, Atom::AtomType expectedAtomType)
+{
+ return atom->next() && atom->next()->type() == expectedAtomType;
+}
+
+/*!
+ Used for writing to the current output stream. Returns a
+ reference to the current output stream, which is then used
+ with the \c {<<} operator for writing.
+ */
+QTextStream &Generator::out()
+{
+ return *outStreamStack.top();
+}
+
+QString Generator::outFileName()
+{
+ return QFileInfo(static_cast<QFile *>(out().device())->fileName()).fileName();
+}
+
+QString Generator::outputPrefix(const Node *node)
+{
+ // Omit prefix for module pages
+ if (node->isPageNode() && !node->isCollectionNode()) {
+ switch (node->genus()) {
+ case Node::QML:
+ return s_outputPrefixes[u"QML"_s];
+ case Node::CPP:
+ return s_outputPrefixes[u"CPP"_s];
+ default:
+ break;
+ }
+ }
+ return QString();
+}
+
+QString Generator::outputSuffix(const Node *node)
+{
+ if (node->isPageNode()) {
+ switch (node->genus()) {
+ case Node::QML:
+ return s_outputSuffixes[u"QML"_s];
+ case Node::CPP:
+ return s_outputSuffixes[u"CPP"_s];
+ default:
+ break;
+ }
+ }
+
+ return QString();
+}
+
+bool Generator::parseArg(const QString &src, const QString &tag, int *pos, int n,
+ QStringView *contents, QStringView *par1)
+{
+#define SKIP_CHAR(c) \
+ if (i >= n || src[i] != c) \
+ return false; \
+ ++i;
+
+#define SKIP_SPACE \
+ while (i < n && src[i] == ' ') \
+ ++i;
+
+ qsizetype i = *pos;
+ qsizetype j {};
+
+ // assume "<@" has been parsed outside
+ // SKIP_CHAR('<');
+ // SKIP_CHAR('@');
+
+ if (tag != QStringView(src).mid(i, tag.size())) {
+ return false;
+ }
+
+ // skip tag
+ i += tag.size();
+
+ // parse stuff like: linkTag("(<@link node=\"([^\"]+)\">).*(</@link>)");
+ if (par1) {
+ SKIP_SPACE;
+ // read parameter name
+ j = i;
+ while (i < n && src[i].isLetter())
+ ++i;
+ if (src[i] == '=') {
+ SKIP_CHAR('=');
+ SKIP_CHAR('"');
+ // skip parameter name
+ j = i;
+ while (i < n && src[i] != '"')
+ ++i;
+ *par1 = QStringView(src).mid(j, i - j);
+ SKIP_CHAR('"');
+ SKIP_SPACE;
+ }
+ }
+ SKIP_SPACE;
+ SKIP_CHAR('>');
+
+ // find contents up to closing "</@tag>
+ j = i;
+ for (; true; ++i) {
+ if (i + 4 + tag.size() > n)
+ return false;
+ if (src[i] != '<')
+ continue;
+ if (src[i + 1] != '/')
+ continue;
+ if (src[i + 2] != '@')
+ continue;
+ if (tag != QStringView(src).mid(i + 3, tag.size()))
+ continue;
+ if (src[i + 3 + tag.size()] != '>')
+ continue;
+ break;
+ }
+
+ *contents = QStringView(src).mid(j, i - j);
+
+ i += tag.size() + 4;
+
+ *pos = i;
+ return true;
+#undef SKIP_CHAR
+#undef SKIP_SPACE
+}
+
+QString Generator::plainCode(const QString &markedCode)
+{
+ QString t = markedCode;
+ t.replace(tag, QString());
+ t.replace(quot, QLatin1String("\""));
+ t.replace(gt, QLatin1String(">"));
+ t.replace(lt, QLatin1String("<"));
+ t.replace(amp, QLatin1String("&"));
+ return t;
+}
+
+int Generator::skipAtoms(const Atom *atom, Atom::AtomType type) const
+{
+ int skipAhead = 0;
+ atom = atom->next();
+ while (atom && atom->type() != type) {
+ skipAhead++;
+ atom = atom->next();
+ }
+ return skipAhead;
+}
+
+/*!
+ Resets the variables used during text output.
+ */
+void Generator::initializeTextOutput()
+{
+ m_inLink = false;
+ m_inContents = false;
+ m_inSectionHeading = false;
+ m_inTableHeader = false;
+ m_numTableRows = 0;
+ m_threeColumnEnumValueTable = true;
+ m_link.clear();
+ m_sectionNumber.clear();
+}
+
+void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList)
+{
+ if (node->isFunction() && !node->isMacro()) {
+ const auto fn = static_cast<const FunctionNode *>(node);
+ if (fn->overloadNumber() == 0) {
+ QString alternateName;
+ const FunctionNode *alternateFunc = nullptr;
+
+ if (fn->name().startsWith("set") && fn->name().size() >= 4) {
+ alternateName = fn->name()[3].toLower();
+ alternateName += fn->name().mid(4);
+ alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
+
+ if (!alternateFunc) {
+ alternateName = "is" + fn->name().mid(3);
+ alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
+ if (!alternateFunc) {
+ alternateName = "has" + fn->name().mid(3);
+ alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
+ }
+ }
+ } else if (!fn->name().isEmpty()) {
+ alternateName = "set";
+ alternateName += fn->name()[0].toUpper();
+ alternateName += fn->name().mid(1);
+ alternateFunc = fn->parent()->findFunctionChild(alternateName, QString());
+ }
+
+ if (alternateFunc && alternateFunc->access() != Access::Private) {
+ int i;
+ for (i = 0; i < alsoList.size(); ++i) {
+ if (alsoList.at(i).toString().contains(alternateName))
+ break;
+ }
+
+ if (i == alsoList.size()) {
+ if (alternateFunc->isDeprecated() && !fn->isDeprecated())
+ return;
+ alternateName += "()";
+
+ Text also;
+ also << Atom(Atom::Link, alternateName)
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << alternateName
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+ alsoList.prepend(also);
+ }
+ }
+ }
+ }
+}
+
+void Generator::generateEnumValuesForQmlProperty(const Node *node, CodeMarker *marker)
+{
+ if (!node->isQmlProperty())
+ return;
+
+ const auto *qpn = static_cast<const QmlPropertyNode*>(node);
+
+ if (!qpn->enumNode())
+ return;
+
+ // Retrieve atoms from C++ enum \value list
+ const auto body{qpn->enumNode()->doc().body()};
+ const auto *start{body.firstAtom()};
+ Text text;
+
+ while ((start = start->find(Atom::ListLeft, ATOM_LIST_VALUE))) {
+ const auto end = start->find(Atom::ListRight, ATOM_LIST_VALUE);
+ // Skip subsequent ListLeft atoms, collating multiple lists into one
+ text << body.subText(text.isEmpty() ? start : start->next(), end);
+ start = end;
+ }
+ if (text.isEmpty())
+ return;
+
+ text << Atom(Atom::ListRight, ATOM_LIST_VALUE);
+ if (marker)
+ generateText(text, qpn, marker);
+ else
+ generateText(text, qpn);
+}
+
+void Generator::terminate()
+{
+ for (const auto &generator : std::as_const(s_generators)) {
+ if (s_outputFormats.contains(generator->format()))
+ generator->terminateGenerator();
+ }
+
+ // REMARK: Generators currently, due to recent changes and the
+ // transitive nature of the current codebase, receive some of
+ // their dependencies in the constructor and some of them in their
+ // initialize-terminate lifetime.
+ // This means that generators need to be constructed and
+ // destructed between usages such that if multiple usages are
+ // required, the generators present in the list will have been
+ // destroyed by then such that accessing them would be an error.
+ // The current codebase calls initialize and the correspective
+ // terminate with the same scope as the lifetime of the
+ // generators.
+ // Then, clearing the list ensures that, if another generator
+ // execution is needed, the stale generators will not be removed
+ // as to be replaced by newly constructed ones.
+ // Do note that it is not clear that this will happen for any call
+ // in Qt's documentation and this should work only because of the
+ // form of the current codebase and the scoping of the
+ // initialize-terminate calls. As such, this should be considered
+ // a patchwork that may or may not be doing anything and that may
+ // break due to changes in other parts of the codebase.
+ //
+ // This is still to be considered temporary as the whole
+ // initialize-terminate idiom must be removed from the codebase.
+ s_generators.clear();
+
+ s_fmtLeftMaps.clear();
+ s_fmtRightMaps.clear();
+ s_outDir.clear();
+}
+
+void Generator::terminateGenerator() {}
+
+/*!
+ Trims trailing whitespace off the \a string and returns
+ the trimmed string.
+ */
+QString Generator::trimmedTrailing(const QString &string, const QString &prefix,
+ const QString &suffix)
+{
+ QString trimmed = string;
+ while (trimmed.size() > 0 && trimmed[trimmed.size() - 1].isSpace())
+ trimmed.truncate(trimmed.size() - 1);
+
+ trimmed.append(suffix);
+ trimmed.prepend(prefix);
+ return trimmed;
+}
+
+QString Generator::typeString(const Node *node)
+{
+ switch (node->nodeType()) {
+ case Node::Namespace:
+ return "namespace";
+ case Node::Class:
+ return "class";
+ case Node::Struct:
+ return "struct";
+ case Node::Union:
+ return "union";
+ case Node::QmlType:
+ case Node::QmlValueType:
+ return "type";
+ case Node::Page:
+ return "documentation";
+ case Node::Enum:
+ return "enum";
+ case Node::Typedef:
+ case Node::TypeAlias:
+ return "typedef";
+ case Node::Function: {
+ const auto fn = static_cast<const FunctionNode *>(node);
+ switch (fn->metaness()) {
+ case FunctionNode::QmlSignal:
+ return "signal";
+ case FunctionNode::QmlSignalHandler:
+ return "signal handler";
+ case FunctionNode::QmlMethod:
+ return "method";
+ case FunctionNode::MacroWithParams:
+ case FunctionNode::MacroWithoutParams:
+ return "macro";
+ default:
+ break;
+ }
+ return "function";
+ }
+ case Node::Property:
+ case Node::QmlProperty:
+ return "property";
+ case Node::Module:
+ case Node::QmlModule:
+ return "module";
+ case Node::SharedComment: {
+ const auto &collective = static_cast<const SharedCommentNode *>(node)->collective();
+ return collective.first()->nodeTypeString();
+ }
+ default:
+ return "documentation";
+ }
+}
+
+void Generator::unknownAtom(const Atom *atom)
+{
+ Location::internalError(QStringLiteral("unknown atom type '%1' in %2 generator")
+ .arg(atom->typeString(), format()));
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/generator.h b/src/qdoc/qdoc/src/qdoc/generator.h
new file mode 100644
index 000000000..faddc8eb7
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/generator.h
@@ -0,0 +1,207 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef GENERATOR_H
+#define GENERATOR_H
+
+#include "text.h"
+#include "utilities.h"
+#include "filesystem/fileresolver.h"
+
+#include <QtCore/qlist.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qtextstream.h>
+#include <optional>
+
+QT_BEGIN_NAMESPACE
+
+typedef QMultiMap<QString, Node *> NodeMultiMap;
+
+class Aggregate;
+class CodeMarker;
+class ExampleNode;
+class FunctionNode;
+class Location;
+class Node;
+class QDocDatabase;
+
+class Generator
+{
+public:
+ enum ListType { Generic, Obsolete };
+
+ enum Addendum {
+ Invokable,
+ PrivateSignal,
+ QmlSignalHandler,
+ AssociatedProperties,
+ BindableProperty
+ };
+
+ Generator(FileResolver& file_resolver);
+ virtual ~Generator();
+
+ virtual bool canHandleFormat(const QString &format) { return format == this->format(); }
+ virtual QString format() = 0;
+ virtual void generateDocs();
+ virtual void initializeGenerator();
+ virtual void initializeFormat();
+ virtual void terminateGenerator();
+ virtual QString typeString(const Node *node);
+
+ QString fullDocumentLocation(const Node *node);
+ QString linkForExampleFile(const QString &path, const QString &fileExt = QString());
+ static QString exampleFileTitle(const ExampleNode *relative, const QString &fileName);
+ static Generator *currentGenerator() { return s_currentGenerator; }
+ static Generator *generatorForFormat(const QString &format);
+ static void initialize();
+ static const QString &outputDir() { return s_outDir; }
+ static const QString &outputSubdir() { return s_outSubdir; }
+ static void terminate();
+ static const QStringList &outputFileNames() { return s_outFileNames; }
+ static bool noLinkErrors() { return s_noLinkErrors; }
+ static bool autolinkErrors() { return s_autolinkErrors; }
+ static QString defaultModuleName() { return s_project; }
+ static void resetUseOutputSubdirs() { s_useOutputSubdirs = false; }
+ static bool useOutputSubdirs() { return s_useOutputSubdirs; }
+ static void setQmlTypeContext(QmlTypeNode *t) { s_qmlTypeContext = t; }
+ static QmlTypeNode *qmlTypeContext() { return s_qmlTypeContext; }
+ static QString cleanRef(const QString &ref, bool xmlCompliant = false);
+ static QString plainCode(const QString &markedCode);
+ virtual QString fileBase(const Node *node) const;
+
+protected:
+ static QFile *openSubPageFile(const Node *node, const QString &fileName);
+ void beginSubPage(const Node *node, const QString &fileName);
+ void endSubPage();
+ [[nodiscard]] virtual QString fileExtension() const = 0;
+ virtual void generateExampleFilePage(const Node *, ResolvedFile, CodeMarker * = nullptr) {}
+ virtual void generateAlsoList(const Node *node, CodeMarker *marker);
+ virtual void generateAlsoList(const Node *node) { generateAlsoList(node, nullptr); }
+ virtual qsizetype generateAtom(const Atom *, const Node *, CodeMarker *) { return 0; }
+ virtual void generateBody(const Node *node, CodeMarker *marker);
+ virtual void generateCppReferencePage(Aggregate *, CodeMarker *) {}
+ virtual void generateProxyPage(Aggregate *, CodeMarker *) {}
+ virtual void generateQmlTypePage(QmlTypeNode *, CodeMarker *) {}
+ virtual void generatePageNode(PageNode *, CodeMarker *) {}
+ virtual void generateCollectionNode(CollectionNode *, CodeMarker *) {}
+ virtual void generateGenericCollectionPage(CollectionNode *, CodeMarker *) {}
+ virtual void generateDocumentation(Node *node);
+ virtual bool generateText(const Text &text, const Node *relative, CodeMarker *marker);
+ virtual bool generateText(const Text &text, const Node *relative)
+ {
+ return generateText(text, relative, nullptr);
+ };
+ virtual int skipAtoms(const Atom *atom, Atom::AtomType type) const;
+
+ static bool matchAhead(const Atom *atom, Atom::AtomType expectedAtomType);
+ static QString outputPrefix(const Node *node);
+ static QString outputSuffix(const Node *node);
+ static void supplementAlsoList(const Node *node, QList<Text> &alsoList);
+ static QString trimmedTrailing(const QString &string, const QString &prefix,
+ const QString &suffix);
+ void initializeTextOutput();
+ QString fileName(const Node *node, const QString &extension = QString()) const;
+ QMap<QString, QString> &formattingLeftMap();
+ QMap<QString, QString> &formattingRightMap();
+ const Atom *generateAtomList(const Atom *atom, const Node *relative, CodeMarker *marker,
+ bool generate, int &numGeneratedAtoms);
+ void generateEnumValuesForQmlProperty(const Node *node, CodeMarker *marker);
+ void generateRequiredLinks(const Node *node, CodeMarker *marker);
+ void generateLinkToExample(const ExampleNode *en, CodeMarker *marker,
+ const QString &exampleUrl);
+ virtual void generateFileList(const ExampleNode *en, CodeMarker *marker, bool images);
+ static QString formatSince(const Node *node);
+ void generateSince(const Node *node, CodeMarker *marker);
+ void generateNoexceptNote(const Node *node, CodeMarker *marker);
+ void generateStatus(const Node *node, CodeMarker *marker);
+ virtual void generateAddendum(const Node *node, Addendum type, CodeMarker *marker,
+ bool generateNote);
+ virtual void generateAddendum(const Node *node, Addendum type, CodeMarker *marker)
+ {
+ generateAddendum(node, type, marker, true);
+ };
+ void generateThreadSafeness(const Node *node, CodeMarker *marker);
+ bool generateComparisonCategory(const Node *node, CodeMarker *marker = nullptr);
+ bool generateComparisonList(const Node *node);
+
+ void generateOverloadedSignal(const Node *node, CodeMarker *marker);
+ static QString getOverloadedSignalCode(const Node *node);
+ QString indent(int level, const QString &markedCode);
+ QTextStream &out();
+ QString outFileName();
+ bool parseArg(const QString &src, const QString &tag, int *pos, int n, QStringView *contents,
+ QStringView *par1 = nullptr);
+ void unknownAtom(const Atom *atom);
+ int appendSortedQmlNames(Text &text, const Node *base, const NodeList &subs);
+
+ static bool hasExceptions(const Node *node, NodeList &reentrant, NodeList &threadsafe,
+ NodeList &nonreentrant);
+
+ QString naturalLanguage;
+ QString tagFile_;
+ QStack<QTextStream *> outStreamStack;
+
+ void appendFullName(Text &text, const Node *apparentNode, const Node *relative,
+ const Node *actualNode = nullptr);
+ void appendFullName(Text &text, const Node *apparentNode, const QString &fullName,
+ const Node *actualNode);
+ int appendSortedNames(Text &text, const ClassNode *classe,
+ const QList<RelatedClass> &classes);
+ void appendSignature(Text &text, const Node *node);
+ void signatureList(const NodeList &nodes, const Node *relative, CodeMarker *marker);
+
+ void addImageToCopy(const ExampleNode *en, const ResolvedFile& resolved_file);
+ // TODO: This seems to be used as the predicate in std::sort calls.
+ // Remove it as it is unneeded.
+ // Indeed, it could be replaced by std::less and, furthermore,
+ // std::sort already defaults to operator< when no predicate is
+ // provided.
+ static bool comparePaths(const QString &a, const QString &b) { return (a < b); }
+ static bool appendTrademark(const Atom *atom);
+
+private:
+ static Generator *s_currentGenerator;
+ static QMap<QString, QMap<QString, QString>> s_fmtLeftMaps;
+ static QMap<QString, QMap<QString, QString>> s_fmtRightMaps;
+ static QList<Generator *> s_generators;
+ static QString s_project;
+ static QString s_outDir;
+ static QString s_outSubdir;
+ static QStringList s_outFileNames;
+ static QSet<QString> s_outputFormats;
+ static QSet<QString> s_trademarks;
+ static QHash<QString, QString> s_outputPrefixes;
+ static QHash<QString, QString> s_outputSuffixes;
+ static bool s_noLinkErrors;
+ static bool s_autolinkErrors;
+ static bool s_redirectDocumentationToDevNull;
+ static bool s_useOutputSubdirs;
+ static QmlTypeNode *s_qmlTypeContext;
+
+ void generateReimplementsClause(const FunctionNode *fn, CodeMarker *marker);
+ static void copyTemplateFiles(const QString &configVar, const QString &subDir);
+
+protected:
+ FileResolver& file_resolver;
+
+ QDocDatabase *m_qdb { nullptr };
+ bool m_inLink { false };
+ bool m_inContents { false };
+ bool m_inSectionHeading { false };
+ bool m_inTableHeader { false };
+ bool m_threeColumnEnumValueTable { true };
+ bool m_showInternal { false };
+ bool m_quoting { false };
+ int m_numTableRows { 0 };
+ QString m_link {};
+ QString m_sectionNumber {};
+};
+
+std::optional<QString> formatStatus(const Node *node, QDocDatabase *qdb);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/headernode.cpp b/src/qdoc/qdoc/src/qdoc/headernode.cpp
new file mode 100644
index 000000000..ab576fbd6
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/headernode.cpp
@@ -0,0 +1,43 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "headernode.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class Headernode
+ \brief This class represents a C++ header file.
+ */
+
+HeaderNode::HeaderNode(Aggregate *parent, const QString &name) : Aggregate(HeaderFile, parent, name)
+{
+ // Set the include file with enclosing angle brackets removed
+ if (name.startsWith(QChar('<')) && name.size() > 2)
+ Aggregate::setIncludeFile(name.mid(1).chopped(1));
+ else
+ Aggregate::setIncludeFile(name);
+}
+
+/*!
+ Returns true if this header file node is not private and
+ contains at least one public child node with documentation.
+ */
+bool HeaderNode::docMustBeGenerated() const
+{
+ if (isInAPI())
+ return true;
+ return hasDocumentedChildren();
+}
+
+/*!
+ Returns true if this header file node contains at least one
+ child that has documentation and is not private or internal.
+ */
+bool HeaderNode::hasDocumentedChildren() const
+{
+ return std::any_of(m_children.cbegin(), m_children.cend(),
+ [](Node *child) { return child->isInAPI(); });
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/headernode.h b/src/qdoc/qdoc/src/qdoc/headernode.h
new file mode 100644
index 000000000..b20ff8fdb
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/headernode.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef HEADERNODE_H
+#define HEADERNODE_H
+
+#include "aggregate.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class HeaderNode : public Aggregate
+{
+public:
+ HeaderNode(Aggregate *parent, const QString &name);
+ [[nodiscard]] bool docMustBeGenerated() const override;
+ [[nodiscard]] bool isFirstClassAggregate() const override { return true; }
+ [[nodiscard]] bool isRelatableType() const override { return true; }
+ [[nodiscard]] QString title() const override { return (m_title.isEmpty() ? name() : m_title); }
+ [[nodiscard]] QString subtitle() const override { return m_subtitle; }
+ [[nodiscard]] QString fullTitle() const override
+ {
+ return (m_title.isEmpty() ? name() : name() + " - " + m_title);
+ }
+ bool setTitle(const QString &title) override
+ {
+ m_title = title;
+ return true;
+ }
+ bool setSubtitle(const QString &subtitle) override
+ {
+ m_subtitle = subtitle;
+ return true;
+ }
+ [[nodiscard]] bool hasDocumentedChildren() const;
+
+private:
+ QString m_title {};
+ QString m_subtitle {};
+};
+
+QT_END_NAMESPACE
+
+#endif // HEADERNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/helpprojectwriter.cpp b/src/qdoc/qdoc/src/qdoc/helpprojectwriter.cpp
new file mode 100644
index 000000000..968bb7b25
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/helpprojectwriter.cpp
@@ -0,0 +1,769 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "helpprojectwriter.h"
+
+#include "access.h"
+#include "aggregate.h"
+#include "atom.h"
+#include "classnode.h"
+#include "collectionnode.h"
+#include "config.h"
+#include "enumnode.h"
+#include "functionnode.h"
+#include "htmlgenerator.h"
+#include "node.h"
+#include "qdocdatabase.h"
+#include "typedefnode.h"
+
+#include <QtCore/qhash.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+HelpProjectWriter::HelpProjectWriter(const QString &defaultFileName, Generator *g)
+{
+ reset(defaultFileName, g);
+}
+
+void HelpProjectWriter::reset(const QString &defaultFileName, Generator *g)
+{
+ m_projects.clear();
+ m_gen = g;
+ /*
+ Get the pointer to the singleton for the qdoc database and
+ store it locally. This replaces all the local accesses to
+ the node tree, which are now private.
+ */
+ m_qdb = QDocDatabase::qdocDB();
+
+ // The output directory should already have been checked by the calling
+ // generator.
+ Config &config = Config::instance();
+ m_outputDir = config.getOutputDir();
+
+ const QStringList names{config.get(CONFIG_QHP + Config::dot + "projects").asStringList()};
+
+ for (const auto &projectName : names) {
+ HelpProject project;
+ project.m_name = projectName;
+
+ QString prefix = CONFIG_QHP + Config::dot + projectName + Config::dot;
+ project.m_helpNamespace = config.get(prefix + "namespace").asString();
+ project.m_virtualFolder = config.get(prefix + "virtualFolder").asString();
+ project.m_version = config.get(CONFIG_VERSION).asString();
+ project.m_fileName = config.get(prefix + "file").asString();
+ if (project.m_fileName.isEmpty())
+ project.m_fileName = defaultFileName;
+ project.m_extraFiles = config.get(prefix + "extraFiles").asStringSet();
+ project.m_extraFiles += config.get(CONFIG_QHP + Config::dot + "extraFiles").asStringSet();
+ project.m_indexTitle = config.get(prefix + "indexTitle").asString();
+ project.m_indexRoot = config.get(prefix + "indexRoot").asString();
+ project.m_filterAttributes = config.get(prefix + "filterAttributes").asStringSet();
+ project.m_includeIndexNodes = config.get(prefix + "includeIndexNodes").asBool();
+ const QSet<QString> customFilterNames = config.subVars(prefix + "customFilters");
+ for (const auto &filterName : customFilterNames) {
+ QString name{config.get(prefix + "customFilters" + Config::dot + filterName
+ + Config::dot + "name").asString()};
+ project.m_customFilters[name] =
+ config.get(prefix + "customFilters" + Config::dot + filterName
+ + Config::dot + "filterAttributes").asStringSet();
+ }
+
+ const auto excludedPrefixes = config.get(prefix + "excluded").asStringSet();
+ for (auto name : excludedPrefixes)
+ project.m_excluded.insert(name.replace(QLatin1Char('\\'), QLatin1Char('/')));
+
+ const auto subprojectPrefixes{config.get(prefix + "subprojects").asStringList()};
+ for (const auto &name : subprojectPrefixes) {
+ SubProject subproject;
+ QString subprefix = prefix + "subprojects" + Config::dot + name + Config::dot;
+ subproject.m_title = config.get(subprefix + "title").asString();
+ if (subproject.m_title.isEmpty())
+ continue;
+ subproject.m_indexTitle = config.get(subprefix + "indexTitle").asString();
+ subproject.m_sortPages = config.get(subprefix + "sortPages").asBool();
+ subproject.m_type = config.get(subprefix + "type").asString();
+ readSelectors(subproject, config.get(subprefix + "selectors").asStringList());
+ project.m_subprojects.append(subproject);
+ }
+
+ if (project.m_subprojects.isEmpty()) {
+ SubProject subproject;
+ readSelectors(subproject, config.get(prefix + "selectors").asStringList());
+ project.m_subprojects.insert(0, subproject);
+ }
+
+ m_projects.append(project);
+ }
+}
+
+void HelpProjectWriter::readSelectors(SubProject &subproject, const QStringList &selectors)
+{
+ QHash<QString, Node::NodeType> typeHash;
+ typeHash["namespace"] = Node::Namespace;
+ typeHash["class"] = Node::Class;
+ typeHash["struct"] = Node::Struct;
+ typeHash["union"] = Node::Union;
+ typeHash["header"] = Node::HeaderFile;
+ typeHash["headerfile"] = Node::HeaderFile;
+ typeHash["doc"] = Node::Page; // Unused (supported but ignored as a prefix)
+ typeHash["fake"] = Node::Page; // Unused (supported but ignored as a prefix)
+ typeHash["page"] = Node::Page;
+ typeHash["enum"] = Node::Enum;
+ typeHash["example"] = Node::Example;
+ typeHash["externalpage"] = Node::ExternalPage;
+ typeHash["typedef"] = Node::Typedef;
+ typeHash["typealias"] = Node::TypeAlias;
+ typeHash["function"] = Node::Function;
+ typeHash["property"] = Node::Property;
+ typeHash["variable"] = Node::Variable;
+ typeHash["group"] = Node::Group;
+ typeHash["module"] = Node::Module;
+ typeHash["qmlmodule"] = Node::QmlModule;
+ typeHash["qmlproperty"] = Node::QmlProperty;
+ typeHash["qmlclass"] = Node::QmlType; // Legacy alias for 'qmltype'
+ typeHash["qmltype"] = Node::QmlType;
+ typeHash["qmlbasictype"] = Node::QmlValueType; // Legacy alias for 'qmlvaluetype'
+ typeHash["qmlvaluetype"] = Node::QmlValueType;
+
+ for (const QString &selector : selectors) {
+ QStringList pieces = selector.split(QLatin1Char(':'));
+ // Remove doc: or fake: prefix
+ if (pieces.size() > 1 && typeHash.value(pieces[0].toLower()) == Node::Page)
+ pieces.takeFirst();
+
+ QString typeName = pieces.takeFirst().toLower();
+ if (!typeHash.contains(typeName))
+ continue;
+
+ subproject.m_selectors << typeHash.value(typeName);
+ if (!pieces.isEmpty()) {
+ pieces = pieces[0].split(QLatin1Char(','));
+ for (const auto &piece : std::as_const(pieces)) {
+ if (typeHash[typeName] == Node::Group
+ || typeHash[typeName] == Node::Module
+ || typeHash[typeName] == Node::QmlModule) {
+ subproject.m_groups << piece.toLower();
+ }
+ }
+ }
+ }
+}
+
+void HelpProjectWriter::addExtraFile(const QString &file)
+{
+ for (HelpProject &project : m_projects)
+ project.m_extraFiles.insert(file);
+}
+
+Keyword HelpProjectWriter::keywordDetails(const Node *node) const
+{
+ QString ref = m_gen->fullDocumentLocation(node);
+
+ if (node->parent() && !node->parent()->name().isEmpty()) {
+ QString name = (node->isEnumType() || node->isTypedef())
+ ? node->parent()->name()+"::"+node->name()
+ : node->name();
+ QString id = (!node->isRelatedNonmember())
+ ? node->parent()->name()+"::"+node->name()
+ : node->name();
+ return Keyword(name, id, ref);
+ } else if (node->isQmlType()) {
+ const QString &name = node->name();
+ QString moduleName = node->logicalModuleName();
+ QStringList ids("QML." + name);
+ if (!moduleName.isEmpty()) {
+ QString majorVersion = node->logicalModule()
+ ? node->logicalModule()->logicalModuleVersion().split('.')[0]
+ : QString();
+ ids << "QML." + moduleName + majorVersion + "." + name;
+ }
+ return Keyword(name, ids, ref);
+ } else if (node->isQmlModule()) {
+ const QLatin1Char delim('.');
+ QStringList parts = node->logicalModuleName().split(delim) << "QML";
+ std::reverse(parts.begin(), parts.end());
+ return Keyword(node->logicalModuleName(), parts.join(delim), ref);
+ } else if (node->isTextPageNode()) {
+ const auto *pageNode = static_cast<const PageNode *>(node);
+ return Keyword(pageNode->fullTitle(), pageNode->fullTitle(), ref);
+ } else {
+ return Keyword(node->name(), node->name(), ref);
+ }
+}
+
+bool HelpProjectWriter::generateSection(HelpProject &project, QXmlStreamWriter & /* writer */,
+ const Node *node)
+{
+ if (!node->url().isEmpty() && !(project.m_includeIndexNodes && !node->url().startsWith("http")))
+ return false;
+
+ if (node->isPrivate() || node->isInternal() || node->isDontDocument())
+ return false;
+
+ if (node->name().isEmpty())
+ return true;
+
+ QString docPath = node->doc().location().filePath();
+ if (!docPath.isEmpty() && project.m_excluded.contains(docPath))
+ return false;
+
+ QString objName = node->isTextPageNode() ? node->fullTitle() : node->fullDocumentName();
+ // Only add nodes to the set for each subproject if they match a selector.
+ // Those that match will be listed in the table of contents.
+
+ for (int i = 0; i < project.m_subprojects.size(); i++) {
+ SubProject subproject = project.m_subprojects[i];
+ // No selectors: accept all nodes.
+ if (subproject.m_selectors.isEmpty()) {
+ project.m_subprojects[i].m_nodes[objName] = node;
+ } else if (subproject.m_selectors.contains(node->nodeType())) {
+ // Add all group members for '[group|module|qmlmodule]:name' selector
+ if (node->isCollectionNode()) {
+ if (project.m_subprojects[i].m_groups.contains(node->name().toLower())) {
+ const auto *cn = static_cast<const CollectionNode *>(node);
+ const auto members = cn->members();
+ for (const Node *m : members) {
+ if (!m->isInAPI())
+ continue;
+ QString memberName =
+ m->isTextPageNode() ? m->fullTitle() : m->fullDocumentName();
+ project.m_subprojects[i].m_nodes[memberName] = m;
+ }
+ continue;
+ } else if (!project.m_subprojects[i].m_groups.isEmpty()) {
+ continue; // Node does not represent specified group(s)
+ }
+ } else if (node->isTextPageNode()) {
+ if (node->isExternalPage() || node->fullTitle().isEmpty())
+ continue;
+ }
+ project.m_subprojects[i].m_nodes[objName] = node;
+ }
+ }
+
+ switch (node->nodeType()) {
+
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ project.m_keywords.append(keywordDetails(node));
+ break;
+ case Node::QmlType:
+ case Node::QmlValueType:
+ if (node->doc().hasKeywords()) {
+ const auto keywords = node->doc().keywords();
+ for (const Atom *keyword : keywords) {
+ if (!keyword->string().isEmpty()) {
+ project.m_keywords.append(Keyword(keyword->string(), keyword->string(),
+ m_gen->fullDocumentLocation(node)));
+ }
+ else
+ node->doc().location().warning(
+ QStringLiteral("Bad keyword in %1")
+ .arg(m_gen->fullDocumentLocation(node)));
+ }
+ }
+ project.m_keywords.append(keywordDetails(node));
+ break;
+
+ case Node::Namespace:
+ project.m_keywords.append(keywordDetails(node));
+ break;
+
+ case Node::Enum:
+ project.m_keywords.append(keywordDetails(node));
+ {
+ const auto *enumNode = static_cast<const EnumNode *>(node);
+ const auto items = enumNode->items();
+ for (const auto &item : items) {
+ if (enumNode->itemAccess(item.name()) == Access::Private)
+ continue;
+
+ QString name;
+ QString id;
+ if (!node->parent()->name().isEmpty()) {
+ name = id = node->parent()->name() + "::" + item.name();
+ } else {
+ name = id = item.name();
+ }
+ QString ref = m_gen->fullDocumentLocation(node);
+ project.m_keywords.append(Keyword(name, id, ref));
+ }
+ }
+ break;
+
+ case Node::Group:
+ case Node::Module:
+ case Node::QmlModule: {
+ const auto *cn = static_cast<const CollectionNode *>(node);
+ if (!cn->fullTitle().isEmpty()) {
+ if (cn->doc().hasKeywords()) {
+ const auto keywords = cn->doc().keywords();
+ for (const Atom *keyword : keywords) {
+ if (!keyword->string().isEmpty()) {
+ project.m_keywords.append(
+ Keyword(keyword->string(), keyword->string(),
+ m_gen->fullDocumentLocation(node)));
+ } else
+ cn->doc().location().warning(
+ QStringLiteral("Bad keyword in %1")
+ .arg(m_gen->fullDocumentLocation(node)));
+ }
+ }
+ project.m_keywords.append(keywordDetails(node));
+ }
+ } break;
+
+ case Node::Property:
+ case Node::QmlProperty:
+ project.m_keywords.append(keywordDetails(node));
+ break;
+
+ case Node::Function: {
+ const auto *funcNode = static_cast<const FunctionNode *>(node);
+
+ /*
+ QML methods, signals, and signal handlers used to be node types,
+ but now they are Function nodes with a Metaness value that specifies
+ what kind of function they are, QmlSignal, QmlMethod, etc.
+ */
+ if (funcNode->isQmlNode()) {
+ project.m_keywords.append(keywordDetails(node));
+ break;
+ }
+ // Only insert keywords for non-constructors. Constructors are covered
+ // by the classes themselves.
+
+ if (!funcNode->isSomeCtor())
+ project.m_keywords.append(keywordDetails(node));
+
+ // Insert member status flags into the entries for the parent
+ // node of the function, or the node it is related to.
+ // Since parent nodes should have already been inserted into
+ // the set of files, we only need to ensure that related nodes
+ // are inserted.
+
+ if (node->parent())
+ project.m_memberStatus[node->parent()].insert(node->status());
+ } break;
+ case Node::TypeAlias:
+ case Node::Typedef: {
+ const auto *typedefNode = static_cast<const TypedefNode *>(node);
+ Keyword typedefDetails = keywordDetails(node);
+ const EnumNode *enumNode = typedefNode->associatedEnum();
+ // Use the location of any associated enum node in preference
+ // to that of the typedef.
+ if (enumNode)
+ typedefDetails.m_ref = m_gen->fullDocumentLocation(enumNode);
+
+ project.m_keywords.append(typedefDetails);
+ } break;
+
+ case Node::Variable: {
+ project.m_keywords.append(keywordDetails(node));
+ } break;
+
+ // Page nodes (such as manual pages) contain subtypes, titles and other
+ // attributes.
+ case Node::Page: {
+ const auto *pn = static_cast<const PageNode *>(node);
+ if (!pn->fullTitle().isEmpty()) {
+ if (pn->doc().hasKeywords()) {
+ const auto keywords = pn->doc().keywords();
+ for (const Atom *keyword : keywords) {
+ if (!keyword->string().isEmpty()) {
+ project.m_keywords.append(
+ Keyword(keyword->string(), keyword->string(),
+ m_gen->fullDocumentLocation(node)));
+ } else {
+ QString loc = m_gen->fullDocumentLocation(node);
+ pn->doc().location().warning(QStringLiteral("Bad keyword in %1").arg(loc));
+ }
+ }
+ }
+ project.m_keywords.append(keywordDetails(node));
+ }
+ break;
+ }
+ default:;
+ }
+
+ // Add all images referenced in the page to the set of files to include.
+ const Atom *atom = node->doc().body().firstAtom();
+ while (atom) {
+ if (atom->type() == Atom::Image || atom->type() == Atom::InlineImage) {
+ // Images are all placed within a single directory regardless of
+ // whether the source images are in a nested directory structure.
+ QStringList pieces = atom->string().split(QLatin1Char('/'));
+ project.m_files.insert("images/" + pieces.last());
+ }
+ atom = atom->next();
+ }
+
+ return true;
+}
+
+void HelpProjectWriter::generateSections(HelpProject &project, QXmlStreamWriter &writer,
+ const Node *node)
+{
+ /*
+ Don't include index nodes in the help file.
+ */
+ if (node->isIndexNode())
+ return;
+ if (!generateSection(project, writer, node))
+ return;
+
+ if (node->isAggregate()) {
+ const auto *aggregate = static_cast<const Aggregate *>(node);
+
+ // Ensure that we don't visit nodes more than once.
+ NodeList childSet;
+ NodeList children = aggregate->childNodes();
+ std::sort(children.begin(), children.end(), Node::nodeNameLessThan);
+ for (auto *child : children) {
+ // Skip related non-members adopted by some other aggregate
+ if (child->parent() != aggregate)
+ continue;
+ if (child->isIndexNode() || child->isPrivate())
+ continue;
+ if (child->isTextPageNode()) {
+ if (!childSet.contains(child))
+ childSet << child;
+ } else {
+ // Store member status of children
+ project.m_memberStatus[node].insert(child->status());
+ if (child->isFunction() && static_cast<const FunctionNode *>(child)->isOverload())
+ continue;
+ if (!childSet.contains(child))
+ childSet << child;
+ }
+ }
+ for (const auto *child : std::as_const(childSet))
+ generateSections(project, writer, child);
+ }
+}
+
+void HelpProjectWriter::generate()
+{
+ // Warn if .qhp configuration was expected but not provided
+ if (auto &config = Config::instance(); m_projects.isEmpty() && config.get(CONFIG_QHP).asBool()) {
+ config.location().warning(u"Documentation configuration for '%1' doesn't define a help project (qhp)"_s
+ .arg(config.get(CONFIG_PROJECT).asString()));
+ }
+ for (HelpProject &project : m_projects)
+ generateProject(project);
+}
+
+void HelpProjectWriter::writeSection(QXmlStreamWriter &writer, const QString &path,
+ const QString &value)
+{
+ writer.writeStartElement(QStringLiteral("section"));
+ writer.writeAttribute(QStringLiteral("ref"), path);
+ writer.writeAttribute(QStringLiteral("title"), value);
+ writer.writeEndElement(); // section
+}
+
+/*!
+ Write subsections for all members, compatibility members and obsolete members.
+ */
+void HelpProjectWriter::addMembers(HelpProject &project, QXmlStreamWriter &writer, const Node *node)
+{
+ QString href = m_gen->fullDocumentLocation(node);
+ href = href.left(href.size() - 5);
+ if (href.isEmpty())
+ return;
+
+ bool derivedClass = false;
+ if (node->isClassNode())
+ derivedClass = !(static_cast<const ClassNode *>(node)->baseClasses().isEmpty());
+
+ // Do not generate a 'List of all members' for namespaces or header files,
+ // but always generate it for derived classes and QML types (but not QML value types)
+ if (!node->isNamespace() && !node->isHeader() && !node->isQmlBasicType()
+ && (derivedClass || node->isQmlType() || !project.m_memberStatus[node].isEmpty())) {
+ QString membersPath = href + QStringLiteral("-members.html");
+ writeSection(writer, membersPath, QStringLiteral("List of all members"));
+ }
+ if (project.m_memberStatus[node].contains(Node::Deprecated)) {
+ QString obsoletePath = href + QStringLiteral("-obsolete.html");
+ writeSection(writer, obsoletePath, QStringLiteral("Obsolete members"));
+ }
+}
+
+void HelpProjectWriter::writeNode(HelpProject &project, QXmlStreamWriter &writer, const Node *node)
+{
+ QString href = m_gen->fullDocumentLocation(node);
+ QString objName = node->name();
+
+ switch (node->nodeType()) {
+
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ case Node::QmlType:
+ case Node::QmlValueType: {
+ QString typeStr = m_gen->typeString(node);
+ if (!typeStr.isEmpty())
+ typeStr[0] = typeStr[0].toTitleCase();
+ writer.writeStartElement("section");
+ writer.writeAttribute("ref", href);
+ if (node->parent() && !node->parent()->name().isEmpty())
+ writer.writeAttribute("title",
+ QStringLiteral("%1::%2 %3 Reference")
+ .arg(node->parent()->name(), objName, typeStr));
+ else
+ writer.writeAttribute("title", QStringLiteral("%1 %2 Reference").arg(objName, typeStr));
+
+ addMembers(project, writer, node);
+ writer.writeEndElement(); // section
+ } break;
+
+ case Node::Namespace:
+ writeSection(writer, href, objName);
+ break;
+
+ case Node::Example:
+ case Node::HeaderFile:
+ case Node::Page:
+ case Node::Group:
+ case Node::Module:
+ case Node::QmlModule: {
+ writer.writeStartElement("section");
+ writer.writeAttribute("ref", href);
+ writer.writeAttribute("title", node->fullTitle());
+ if (node->nodeType() == Node::HeaderFile)
+ addMembers(project, writer, node);
+ writer.writeEndElement(); // section
+ } break;
+ default:;
+ }
+}
+
+void HelpProjectWriter::generateProject(HelpProject &project)
+{
+ const Node *rootNode;
+
+ // Restrict searching only to the local (primary) tree
+ QList<Tree *> searchOrder = m_qdb->searchOrder();
+ m_qdb->setLocalSearch();
+
+ if (!project.m_indexRoot.isEmpty())
+ rootNode = m_qdb->findPageNodeByTitle(project.m_indexRoot);
+ else
+ rootNode = m_qdb->primaryTreeRoot();
+
+ if (rootNode == nullptr)
+ return;
+
+ project.m_files.clear();
+ project.m_keywords.clear();
+
+ QFile file(m_outputDir + QDir::separator() + project.m_fileName);
+ if (!file.open(QFile::WriteOnly))
+ return;
+
+ QXmlStreamWriter writer(&file);
+ writer.setAutoFormatting(true);
+ writer.writeStartDocument();
+ writer.writeStartElement("QtHelpProject");
+ writer.writeAttribute("version", "1.0");
+
+ // Write metaData, virtualFolder and namespace elements.
+ writer.writeTextElement("namespace", project.m_helpNamespace);
+ writer.writeTextElement("virtualFolder", project.m_virtualFolder);
+ writer.writeStartElement("metaData");
+ writer.writeAttribute("name", "version");
+ writer.writeAttribute("value", project.m_version);
+ writer.writeEndElement();
+
+ // Write customFilter elements.
+ for (auto it = project.m_customFilters.constBegin(); it != project.m_customFilters.constEnd();
+ ++it) {
+ writer.writeStartElement("customFilter");
+ writer.writeAttribute("name", it.key());
+ QStringList sortedAttributes = it.value().values();
+ sortedAttributes.sort();
+ for (const auto &filter : std::as_const(sortedAttributes))
+ writer.writeTextElement("filterAttribute", filter);
+ writer.writeEndElement(); // customFilter
+ }
+
+ // Start the filterSection.
+ writer.writeStartElement("filterSection");
+
+ // Write filterAttribute elements.
+ QStringList sortedFilterAttributes = project.m_filterAttributes.values();
+ sortedFilterAttributes.sort();
+ for (const auto &filterName : std::as_const(sortedFilterAttributes))
+ writer.writeTextElement("filterAttribute", filterName);
+
+ writer.writeStartElement("toc");
+ writer.writeStartElement("section");
+ const Node *node = m_qdb->findPageNodeByTitle(project.m_indexTitle);
+ if (!node)
+ node = m_qdb->findNodeByNameAndType(QStringList(project.m_indexTitle), &Node::isPageNode);
+ if (!node)
+ node = m_qdb->findNodeByNameAndType(QStringList("index.html"), &Node::isPageNode);
+ QString indexPath;
+ if (node)
+ indexPath = m_gen->fullDocumentLocation(node);
+ else
+ indexPath = "index.html";
+ writer.writeAttribute("ref", indexPath);
+ writer.writeAttribute("title", project.m_indexTitle);
+
+ generateSections(project, writer, rootNode);
+
+ for (int i = 0; i < project.m_subprojects.size(); i++) {
+ SubProject subproject = project.m_subprojects[i];
+
+ if (subproject.m_type == QLatin1String("manual")) {
+
+ const Node *indexPage = m_qdb->findNodeForTarget(subproject.m_indexTitle, nullptr);
+ if (indexPage) {
+ Text indexBody = indexPage->doc().body();
+ const Atom *atom = indexBody.firstAtom();
+ QStack<int> sectionStack;
+ bool inItem = false;
+
+ while (atom) {
+ switch (atom->type()) {
+ case Atom::ListLeft:
+ sectionStack.push(0);
+ break;
+ case Atom::ListRight:
+ if (sectionStack.pop() > 0)
+ writer.writeEndElement(); // section
+ break;
+ case Atom::ListItemLeft:
+ inItem = true;
+ break;
+ case Atom::ListItemRight:
+ inItem = false;
+ break;
+ case Atom::Link:
+ if (inItem) {
+ if (sectionStack.top() > 0)
+ writer.writeEndElement(); // section
+
+ const Node *page = m_qdb->findNodeForTarget(atom->string(), nullptr);
+ writer.writeStartElement("section");
+ QString indexPath = m_gen->fullDocumentLocation(page);
+ writer.writeAttribute("ref", indexPath);
+ writer.writeAttribute("title", atom->linkText());
+
+ sectionStack.top() += 1;
+ }
+ break;
+ default:;
+ }
+
+ if (atom == indexBody.lastAtom())
+ break;
+ atom = atom->next();
+ }
+ } else
+ rootNode->doc().location().warning(
+ QStringLiteral("Failed to find index: %1").arg(subproject.m_indexTitle));
+
+ } else {
+
+ writer.writeStartElement("section");
+ QString indexPath = m_gen->fullDocumentLocation(
+ m_qdb->findNodeForTarget(subproject.m_indexTitle, nullptr));
+ writer.writeAttribute("ref", indexPath);
+ writer.writeAttribute("title", subproject.m_title);
+
+ if (subproject.m_sortPages) {
+ QStringList titles = subproject.m_nodes.keys();
+ titles.sort();
+ for (const auto &title : std::as_const(titles)) {
+ writeNode(project, writer, subproject.m_nodes[title]);
+ }
+ } else {
+ // Find a contents node and navigate from there, using the NextLink values.
+ QSet<QString> visited;
+ bool contentsFound = false;
+ for (const auto *node : std::as_const(subproject.m_nodes)) {
+ QString nextTitle = node->links().value(Node::NextLink).first;
+ if (!nextTitle.isEmpty()
+ && node->links().value(Node::ContentsLink).first.isEmpty()) {
+
+ const Node *nextPage = m_qdb->findNodeForTarget(nextTitle, nullptr);
+
+ // Write the contents node.
+ writeNode(project, writer, node);
+ contentsFound = true;
+
+ while (nextPage) {
+ writeNode(project, writer, nextPage);
+ nextTitle = nextPage->links().value(Node::NextLink).first;
+ if (nextTitle.isEmpty() || visited.contains(nextTitle))
+ break;
+ nextPage = m_qdb->findNodeForTarget(nextTitle, nullptr);
+ visited.insert(nextTitle);
+ }
+ break;
+ }
+ }
+ // No contents/nextpage links found, write all nodes unsorted
+ if (!contentsFound) {
+ QList<const Node *> subnodes = subproject.m_nodes.values();
+
+ std::sort(subnodes.begin(), subnodes.end(), Node::nodeNameLessThan);
+
+ for (const auto *node : std::as_const(subnodes))
+ writeNode(project, writer, node);
+ }
+ }
+
+ writer.writeEndElement(); // section
+ }
+ }
+
+ // Restore original search order
+ m_qdb->setSearchOrder(searchOrder);
+
+ writer.writeEndElement(); // section
+ writer.writeEndElement(); // toc
+
+ writer.writeStartElement("keywords");
+ std::sort(project.m_keywords.begin(), project.m_keywords.end());
+ for (const auto &k : std::as_const(project.m_keywords)) {
+ for (const auto &id : std::as_const(k.m_ids)) {
+ writer.writeStartElement("keyword");
+ writer.writeAttribute("name", k.m_name);
+ writer.writeAttribute("id", id);
+ writer.writeAttribute("ref", k.m_ref);
+ writer.writeEndElement(); //keyword
+ }
+ }
+ writer.writeEndElement(); // keywords
+
+ writer.writeStartElement("files");
+
+ // The list of files to write is the union of generated files and
+ // other files (images and extras) included in the project
+ QSet<QString> files =
+ QSet<QString>(m_gen->outputFileNames().cbegin(), m_gen->outputFileNames().cend());
+ files.unite(project.m_files);
+ files.unite(project.m_extraFiles);
+ QStringList sortedFiles = files.values();
+ sortedFiles.sort();
+ for (const auto &usedFile : std::as_const(sortedFiles)) {
+ if (!usedFile.isEmpty())
+ writer.writeTextElement("file", usedFile);
+ }
+ writer.writeEndElement(); // files
+
+ writer.writeEndElement(); // filterSection
+ writer.writeEndElement(); // QtHelpProject
+ writer.writeEndDocument();
+ file.close();
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/helpprojectwriter.h b/src/qdoc/qdoc/src/qdoc/helpprojectwriter.h
new file mode 100644
index 000000000..11dd67fb1
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/helpprojectwriter.h
@@ -0,0 +1,106 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef HELPPROJECTWRITER_H
+#define HELPPROJECTWRITER_H
+
+#include "node.h"
+
+#include <QtCore/qstring.h>
+#include <QtCore/qxmlstream.h>
+
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+class QDocDatabase;
+class Generator;
+
+using NodeTypeSet = QSet<unsigned char>;
+
+struct SubProject
+{
+ QString m_title {};
+ QString m_indexTitle {};
+ NodeTypeSet m_selectors {};
+ bool m_sortPages {};
+ QString m_type {};
+ QHash<QString, const Node *> m_nodes {};
+ QStringList m_groups {};
+};
+
+/*
+ * Name is the human-readable name to be shown in Assistant.
+ * Ids is a list of unique identifiers.
+ * Ref is the location of the documentation for the keyword.
+ */
+struct Keyword {
+ QString m_name {};
+ QStringList m_ids {};
+ QString m_ref {};
+ Keyword(QString name, const QString &id, QString ref)
+ : m_name(std::move(name)), m_ids(QStringList(id)), m_ref(std::move(ref))
+ {
+ }
+ Keyword(QString name, QStringList ids, QString ref)
+ : m_name(std::move(name)), m_ids(std::move(ids)), m_ref(std::move(ref))
+ {
+ }
+ bool operator<(const Keyword &o) const
+ {
+ // Order by name; use ref as a secondary sort key
+ return (m_name == o.m_name) ? m_ref < o.m_ref : m_name < o.m_name;
+ }
+};
+
+struct HelpProject
+{
+ using NodeStatusSet = QSet<unsigned char>;
+
+ QString m_name {};
+ QString m_helpNamespace {};
+ QString m_virtualFolder {};
+ QString m_version {};
+ QString m_fileName {};
+ QString m_indexRoot {};
+ QString m_indexTitle {};
+ QList<Keyword> m_keywords {};
+ QSet<QString> m_files {};
+ QSet<QString> m_extraFiles {};
+ QSet<QString> m_filterAttributes {};
+ QHash<QString, QSet<QString>> m_customFilters {};
+ QSet<QString> m_excluded {};
+ QList<SubProject> m_subprojects {};
+ QHash<const Node *, NodeStatusSet> m_memberStatus {};
+ bool m_includeIndexNodes {};
+};
+
+
+class HelpProjectWriter
+{
+public:
+ HelpProjectWriter(const QString &defaultFileName, Generator *g);
+ void reset(const QString &defaultFileName, Generator *g);
+ void addExtraFile(const QString &file);
+ void generate();
+
+private:
+ void generateProject(HelpProject &project);
+ void generateSections(HelpProject &project, QXmlStreamWriter &writer, const Node *node);
+ bool generateSection(HelpProject &project, QXmlStreamWriter &writer, const Node *node);
+ Keyword keywordDetails(const Node *node) const;
+ void writeNode(HelpProject &project, QXmlStreamWriter &writer, const Node *node);
+ void readSelectors(SubProject &subproject, const QStringList &selectors);
+ void addMembers(HelpProject &project, QXmlStreamWriter &writer, const Node *node);
+ void writeSection(QXmlStreamWriter &writer, const QString &path, const QString &value);
+
+ QDocDatabase *m_qdb {};
+ Generator *m_gen {};
+
+ QString m_outputDir {};
+ QList<HelpProject> m_projects {};
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/htmlgenerator.cpp b/src/qdoc/qdoc/src/qdoc/htmlgenerator.cpp
new file mode 100644
index 000000000..d0f9fb2b4
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/htmlgenerator.cpp
@@ -0,0 +1,3722 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "htmlgenerator.h"
+
+#include "access.h"
+#include "aggregate.h"
+#include "classnode.h"
+#include "collectionnode.h"
+#include "config.h"
+#include "codemarker.h"
+#include "codeparser.h"
+#include "enumnode.h"
+#include "functionnode.h"
+#include "helpprojectwriter.h"
+#include "manifestwriter.h"
+#include "node.h"
+#include "propertynode.h"
+#include "qdocdatabase.h"
+#include "qmlpropertynode.h"
+#include "sharedcommentnode.h"
+#include "tagfilewriter.h"
+#include "tree.h"
+#include "quoter.h"
+#include "utilities.h"
+
+#include <QtCore/qlist.h>
+#include <QtCore/qmap.h>
+#include <QtCore/quuid.h>
+#include <QtCore/qversionnumber.h>
+#include <QtCore/qregularexpression.h>
+
+#include <cctype>
+#include <deque>
+#include <string>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+bool HtmlGenerator::s_inUnorderedList { false };
+
+HtmlGenerator::HtmlGenerator(FileResolver& file_resolver) : XmlGenerator(file_resolver) {}
+
+static void addLink(const QString &linkTarget, QStringView nestedStuff, QString *res)
+{
+ if (!linkTarget.isEmpty()) {
+ *res += QLatin1String("<a href=\"");
+ *res += linkTarget;
+ *res += QLatin1String("\" translate=\"no\">");
+ *res += nestedStuff;
+ *res += QLatin1String("</a>");
+ } else {
+ *res += nestedStuff;
+ }
+}
+
+/*!
+ \internal
+ Convenience method that starts an unordered list if not in one.
+ */
+inline void HtmlGenerator::openUnorderedList()
+{
+ if (!s_inUnorderedList) {
+ out() << "<ul>\n";
+ s_inUnorderedList = true;
+ }
+}
+
+/*!
+ \internal
+ Convenience method that closes an unordered list if in one.
+ */
+inline void HtmlGenerator::closeUnorderedList()
+{
+ if (s_inUnorderedList) {
+ out() << "</ul>\n";
+ s_inUnorderedList = false;
+ }
+}
+
+/*!
+ Destroys the HTML output generator. Deletes the singleton
+ instance of HelpProjectWriter and the ManifestWriter instance.
+ */
+HtmlGenerator::~HtmlGenerator()
+{
+ if (m_helpProjectWriter) {
+ delete m_helpProjectWriter;
+ m_helpProjectWriter = nullptr;
+ }
+
+ if (m_manifestWriter) {
+ delete m_manifestWriter;
+ m_manifestWriter = nullptr;
+ }
+}
+
+/*!
+ Initializes the HTML output generator's data structures
+ from the configuration (Config) singleton.
+ */
+void HtmlGenerator::initializeGenerator()
+{
+ static const struct
+ {
+ const char *key;
+ const char *left;
+ const char *right;
+ } defaults[] = { { ATOM_FORMATTING_BOLD, "<b>", "</b>" },
+ { ATOM_FORMATTING_INDEX, "<!--", "-->" },
+ { ATOM_FORMATTING_ITALIC, "<i>", "</i>" },
+ { ATOM_FORMATTING_PARAMETER, "<i translate=\"no\">", "</i>" },
+ { ATOM_FORMATTING_SUBSCRIPT, "<sub>", "</sub>" },
+ { ATOM_FORMATTING_SUPERSCRIPT, "<sup>", "</sup>" },
+ { ATOM_FORMATTING_TELETYPE, "<code translate=\"no\">",
+ "</code>" }, // <tt> tag is not supported in HTML5
+ { ATOM_FORMATTING_TRADEMARK, "<span translate=\"no\">", "&#8482;" },
+ { ATOM_FORMATTING_UICONTROL, "<b translate=\"no\">", "</b>" },
+ { ATOM_FORMATTING_UNDERLINE, "<u>", "</u>" },
+ { nullptr, nullptr, nullptr } };
+
+ Generator::initializeGenerator();
+ config = &Config::instance();
+
+ /*
+ The formatting maps are owned by Generator. They are cleared in
+ Generator::terminate().
+ */
+ for (int i = 0; defaults[i].key; ++i) {
+ formattingLeftMap().insert(QLatin1String(defaults[i].key), QLatin1String(defaults[i].left));
+ formattingRightMap().insert(QLatin1String(defaults[i].key),
+ QLatin1String(defaults[i].right));
+ }
+
+ QString formatDot{HtmlGenerator::format() + Config::dot};
+ m_endHeader = config->get(formatDot + CONFIG_ENDHEADER).asString();
+ m_postHeader = config->get(formatDot + HTMLGENERATOR_POSTHEADER).asString();
+ m_postPostHeader = config->get(formatDot + HTMLGENERATOR_POSTPOSTHEADER).asString();
+ m_prologue = config->get(formatDot + HTMLGENERATOR_PROLOGUE).asString();
+
+ m_footer = config->get(formatDot + HTMLGENERATOR_FOOTER).asString();
+ m_address = config->get(formatDot + HTMLGENERATOR_ADDRESS).asString();
+ m_noNavigationBar = config->get(formatDot + HTMLGENERATOR_NONAVIGATIONBAR).asBool();
+ m_navigationSeparator = config->get(formatDot + HTMLGENERATOR_NAVIGATIONSEPARATOR).asString();
+ tocDepth = config->get(formatDot + HTMLGENERATOR_TOCDEPTH).asInt();
+
+ m_project = config->get(CONFIG_PROJECT).asString();
+ m_projectDescription = config->get(CONFIG_DESCRIPTION)
+ .asString(m_project + QLatin1String(" Reference Documentation"));
+
+ m_projectUrl = config->get(CONFIG_URL).asString();
+ tagFile_ = config->get(CONFIG_TAGFILE).asString();
+ naturalLanguage = config->get(CONFIG_NATURALLANGUAGE).asString(QLatin1String("en"));
+
+ m_codeIndent = config->get(CONFIG_CODEINDENT).asInt();
+ m_codePrefix = config->get(CONFIG_CODEPREFIX).asString();
+ m_codeSuffix = config->get(CONFIG_CODESUFFIX).asString();
+
+ /*
+ The help file write should be allocated once and only once
+ per qdoc execution.
+ */
+ if (m_helpProjectWriter)
+ m_helpProjectWriter->reset(m_project.toLower() + ".qhp", this);
+ else
+ m_helpProjectWriter = new HelpProjectWriter(m_project.toLower() + ".qhp", this);
+
+ if (!m_manifestWriter)
+ m_manifestWriter = new ManifestWriter();
+
+ // Documentation template handling
+ m_headerScripts = config->get(formatDot + CONFIG_HEADERSCRIPTS).asString();
+ m_headerStyles = config->get(formatDot + CONFIG_HEADERSTYLES).asString();
+
+ // Retrieve the config for the navigation bar
+ m_homepage = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_HOMEPAGE).asString();
+
+ m_hometitle = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_HOMETITLE)
+ .asString(m_homepage);
+
+ m_landingpage = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_LANDINGPAGE).asString();
+
+ m_landingtitle = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_LANDINGTITLE)
+ .asString(m_landingpage);
+
+ m_cppclassespage = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_CPPCLASSESPAGE).asString();
+
+ m_cppclassestitle = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_CPPCLASSESTITLE)
+ .asString(QLatin1String("C++ Classes"));
+
+ m_qmltypespage = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_QMLTYPESPAGE).asString();
+
+ m_qmltypestitle = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_QMLTYPESTITLE)
+ .asString(QLatin1String("QML Types"));
+
+ m_trademarkspage = config->get(CONFIG_NAVIGATION
+ + Config::dot + CONFIG_TRADEMARKSPAGE).asString();
+
+ m_buildversion = config->get(CONFIG_BUILDVERSION).asString();
+}
+
+/*!
+ Gracefully terminates the HTML output generator.
+ */
+void HtmlGenerator::terminateGenerator()
+{
+ Generator::terminateGenerator();
+}
+
+QString HtmlGenerator::format()
+{
+ return "HTML";
+}
+
+/*!
+ If qdoc is in the \c {-prepare} phase, traverse the primary
+ tree to generate the index file for the current module.
+
+ If qdoc is in the \c {-generate} phase, traverse the primary
+ tree to generate all the HTML documentation for the current
+ module. Then generate the help file and the tag file.
+ */
+void HtmlGenerator::generateDocs()
+{
+ Node *qflags = m_qdb->findClassNode(QStringList("QFlags"));
+ if (qflags)
+ m_qflagsHref = linkForNode(qflags, nullptr);
+ if (!config->preparing())
+ Generator::generateDocs();
+
+ if (!config->generating()) {
+ QString fileBase =
+ m_project.toLower().simplified().replace(QLatin1Char(' '), QLatin1Char('-'));
+ m_qdb->generateIndex(outputDir() + QLatin1Char('/') + fileBase + ".index", m_projectUrl,
+ m_projectDescription, this);
+ }
+
+ if (!config->preparing()) {
+ m_helpProjectWriter->generate();
+ m_manifestWriter->generateManifestFiles();
+ /*
+ Generate the XML tag file, if it was requested.
+ */
+ if (!tagFile_.isEmpty()) {
+ TagFileWriter tagFileWriter;
+ tagFileWriter.generateTagFile(tagFile_, this);
+ }
+ }
+}
+
+/*!
+ Generate an html file with the contents of a C++ or QML source file.
+ */
+void HtmlGenerator::generateExampleFilePage(const Node *en, ResolvedFile resolved_file, CodeMarker *marker)
+{
+ SubTitleSize subTitleSize = LargeSubTitle;
+ QString fullTitle = en->fullTitle();
+
+ beginSubPage(en, linkForExampleFile(resolved_file.get_query()));
+ generateHeader(fullTitle, en, marker);
+ generateTitle(fullTitle, Text() << en->subtitle(), subTitleSize, en, marker);
+
+ Text text;
+ Quoter quoter;
+ Doc::quoteFromFile(en->doc().location(), quoter, resolved_file);
+ QString code = quoter.quoteTo(en->location(), QString(), QString());
+ CodeMarker *codeMarker = CodeMarker::markerForFileName(resolved_file.get_path());
+ text << Atom(codeMarker->atomType(), code);
+ Atom a(codeMarker->atomType(), code);
+
+ generateText(text, en, codeMarker);
+ endSubPage();
+}
+
+/*!
+ Generate html from an instance of Atom.
+ */
+qsizetype HtmlGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker)
+{
+ qsizetype idx, skipAhead = 0;
+ static bool in_para = false;
+ Node::Genus genus = Node::DontCare;
+
+ switch (atom->type()) {
+ case Atom::AutoLink: {
+ QString name = atom->string();
+ if (relative && relative->name() == name.replace(QLatin1String("()"), QLatin1String())) {
+ out() << protectEnc(atom->string());
+ break;
+ }
+ // Allow auto-linking to nodes in API reference
+ genus = Node::API;
+ }
+ Q_FALLTHROUGH();
+ case Atom::NavAutoLink:
+ if (!m_inLink && !m_inContents && !m_inSectionHeading) {
+ const Node *node = nullptr;
+ QString link = getAutoLink(atom, relative, &node, genus);
+ if (link.isEmpty()) {
+ if (autolinkErrors() && relative)
+ relative->doc().location().warning(
+ QStringLiteral("Can't autolink to '%1'").arg(atom->string()));
+ } else if (node && node->isDeprecated()) {
+ if (relative && (relative->parent() != node) && !relative->isDeprecated())
+ link.clear();
+ }
+ if (link.isEmpty()) {
+ out() << protectEnc(atom->string());
+ } else {
+ beginLink(link, node, relative);
+ generateLink(atom);
+ endLink();
+ }
+ } else {
+ out() << protectEnc(atom->string());
+ }
+ break;
+ case Atom::BaseName:
+ break;
+ case Atom::BriefLeft:
+ if (!hasBrief(relative)) {
+ skipAhead = skipAtoms(atom, Atom::BriefRight);
+ break;
+ }
+ out() << "<p>";
+ rewritePropertyBrief(atom, relative);
+ break;
+ case Atom::BriefRight:
+ if (hasBrief(relative))
+ out() << "</p>\n";
+ break;
+ case Atom::C:
+ // This may at one time have been used to mark up C++ code but it is
+ // now widely used to write teletype text. As a result, text marked
+ // with the \c command is not passed to a code marker.
+ out() << formattingLeftMap()[ATOM_FORMATTING_TELETYPE];
+ out() << protectEnc(plainCode(atom->string()));
+ out() << formattingRightMap()[ATOM_FORMATTING_TELETYPE];
+ break;
+ case Atom::CaptionLeft:
+ out() << "<p class=\"figCaption\">";
+ in_para = true;
+ break;
+ case Atom::CaptionRight:
+ endLink();
+ if (in_para) {
+ out() << "</p>\n";
+ in_para = false;
+ }
+ break;
+ case Atom::Qml:
+ out() << "<pre class=\"qml\" translate=\"no\">"
+ << trimmedTrailing(highlightedCode(indent(m_codeIndent, atom->string()), relative,
+ false, Node::QML),
+ m_codePrefix, m_codeSuffix)
+ << "</pre>\n";
+ break;
+ case Atom::Code:
+ out() << "<pre class=\"cpp\" translate=\"no\">"
+ << trimmedTrailing(highlightedCode(indent(m_codeIndent, atom->string()), relative),
+ m_codePrefix, m_codeSuffix)
+ << "</pre>\n";
+ break;
+ case Atom::CodeBad:
+ out() << "<pre class=\"cpp plain\" translate=\"no\">"
+ << trimmedTrailing(protectEnc(plainCode(indent(m_codeIndent, atom->string()))),
+ m_codePrefix, m_codeSuffix)
+ << "</pre>\n";
+ break;
+ case Atom::DetailsLeft:
+ out() << "<details>\n";
+ if (!atom->string().isEmpty())
+ out() << "<summary>" << protectEnc(atom->string()) << "</summary>\n";
+ else
+ out() << "<summary>...</summary>\n";
+ break;
+ case Atom::DetailsRight:
+ out() << "</details>\n";
+ break;
+ case Atom::DivLeft:
+ out() << "<div";
+ if (!atom->string().isEmpty())
+ out() << ' ' << atom->string();
+ out() << '>';
+ break;
+ case Atom::DivRight:
+ out() << "</div>";
+ break;
+ case Atom::FootnoteLeft:
+ // ### For now
+ if (in_para) {
+ out() << "</p>\n";
+ in_para = false;
+ }
+ out() << "<!-- ";
+ break;
+ case Atom::FootnoteRight:
+ // ### For now
+ out() << "-->\n";
+ break;
+ case Atom::FormatElse:
+ case Atom::FormatEndif:
+ case Atom::FormatIf:
+ break;
+ case Atom::FormattingLeft:
+ if (atom->string().startsWith("span "))
+ out() << '<' + atom->string() << '>';
+ else
+ out() << formattingLeftMap()[atom->string()];
+ break;
+ case Atom::FormattingRight:
+ if (atom->string() == ATOM_FORMATTING_LINK) {
+ endLink();
+ } else if (atom->string() == ATOM_FORMATTING_TRADEMARK) {
+ if (appendTrademark(atom)) {
+ // Make the trademark symbol a link to navigation.trademarkspage (if set)
+ const Node *node{nullptr};
+ const Atom tm_link(Atom::NavLink, m_trademarkspage);
+ if (const auto &link = getLink(&tm_link, relative, &node);
+ !link.isEmpty() && node != relative)
+ out() << "<a href=\"%1\">%2</a>"_L1.arg(link, formattingRightMap()[atom->string()]);
+ else
+ out() << formattingRightMap()[atom->string()];
+ }
+ out() << "</span>";
+ } else if (atom->string().startsWith("span ")) {
+ out() << "</span>";
+ } else {
+ out() << formattingRightMap()[atom->string()];
+ }
+ break;
+ case Atom::AnnotatedList: {
+ const CollectionNode *cn = m_qdb->getCollectionNode(atom->string(), Node::Group);
+ if (cn)
+ generateList(cn, marker, atom->string());
+ } break;
+ case Atom::GeneratedList:
+ if (atom->string() == QLatin1String("annotatedclasses")) {
+ generateAnnotatedList(relative, marker, m_qdb->getCppClasses().values());
+ } else if (atom->string() == QLatin1String("annotatedexamples")) {
+ generateAnnotatedLists(relative, marker, m_qdb->getExamples());
+ } else if (atom->string() == QLatin1String("annotatedattributions")) {
+ generateAnnotatedLists(relative, marker, m_qdb->getAttributions());
+ } else if (atom->string() == QLatin1String("classes")) {
+ generateCompactList(Generic, relative, m_qdb->getCppClasses(), true,
+ QStringLiteral(""));
+ } else if (atom->string().contains("classes ")) {
+ QString rootName = atom->string().mid(atom->string().indexOf("classes") + 7).trimmed();
+ generateCompactList(Generic, relative, m_qdb->getCppClasses(), true, rootName);
+ } else if (atom->string() == QLatin1String("qmlvaluetypes")
+ || atom->string() == QLatin1String("qmlbasictypes")) {
+ generateCompactList(Generic, relative, m_qdb->getQmlValueTypes(), true,
+ QStringLiteral(""));
+ } else if (atom->string() == QLatin1String("qmltypes")) {
+ generateCompactList(Generic, relative, m_qdb->getQmlTypes(), true, QStringLiteral(""));
+ } else if ((idx = atom->string().indexOf(QStringLiteral("bymodule"))) != -1) {
+ QDocDatabase *qdb = QDocDatabase::qdocDB();
+ QString moduleName = atom->string().mid(idx + 8).trimmed();
+ Node::NodeType moduleType = typeFromString(atom);
+ if (const auto *cn = qdb->getCollectionNode(moduleName, moduleType)) {
+ NodeMap map;
+ switch (moduleType) {
+ case Node::Module:
+ // classesbymodule <module_name>
+ map = cn->getMembers([](const Node *n) { return n->isClassNode(); });
+ generateAnnotatedList(relative, marker, map.values());
+ break;
+ case Node::QmlModule:
+ if (atom->string().contains(QLatin1String("qmlvaluetypes")))
+ map = cn->getMembers(Node::QmlValueType); // qmlvaluetypesbymodule <module_name>
+ else
+ map = cn->getMembers(Node::QmlType); // qmltypesbymodule <module_name>
+ generateAnnotatedList(relative, marker, map.values());
+ break;
+ default: // fall back to listing all members
+ generateAnnotatedList(relative, marker, cn->members());
+ break;
+ }
+ }
+ } else if (atom->string() == QLatin1String("classhierarchy")) {
+ generateClassHierarchy(relative, m_qdb->getCppClasses());
+ } else if (atom->string() == QLatin1String("obsoleteclasses")) {
+ generateCompactList(Generic, relative, m_qdb->getObsoleteClasses(), false,
+ QStringLiteral("Q"));
+ } else if (atom->string() == QLatin1String("obsoleteqmltypes")) {
+ generateCompactList(Generic, relative, m_qdb->getObsoleteQmlTypes(), false,
+ QStringLiteral(""));
+ } else if (atom->string() == QLatin1String("obsoletecppmembers")) {
+ generateCompactList(Obsolete, relative, m_qdb->getClassesWithObsoleteMembers(), false,
+ QStringLiteral("Q"));
+ } else if (atom->string() == QLatin1String("obsoleteqmlmembers")) {
+ generateCompactList(Obsolete, relative, m_qdb->getQmlTypesWithObsoleteMembers(), false,
+ QStringLiteral(""));
+ } else if (atom->string() == QLatin1String("functionindex")) {
+ generateFunctionIndex(relative);
+ } else if (atom->string() == QLatin1String("attributions")) {
+ generateAnnotatedList(relative, marker, m_qdb->getAttributions().values());
+ } else if (atom->string() == QLatin1String("legalese")) {
+ generateLegaleseList(relative, marker);
+ } else if (atom->string() == QLatin1String("overviews")) {
+ generateList(relative, marker, "overviews");
+ } else if (atom->string() == QLatin1String("cpp-modules")) {
+ generateList(relative, marker, "cpp-modules");
+ } else if (atom->string() == QLatin1String("qml-modules")) {
+ generateList(relative, marker, "qml-modules");
+ } else if (atom->string() == QLatin1String("namespaces")) {
+ generateAnnotatedList(relative, marker, m_qdb->getNamespaces().values());
+ } else if (atom->string() == QLatin1String("related")) {
+ generateList(relative, marker, "related");
+ } else {
+ const CollectionNode *cn = m_qdb->getCollectionNode(atom->string(), Node::Group);
+ if (cn) {
+ if (!generateGroupList(const_cast<CollectionNode *>(cn)))
+ relative->location().warning(
+ QString("'\\generatelist %1' group is empty").arg(atom->string()));
+ } else {
+ relative->location().warning(
+ QString("'\\generatelist %1' no such group").arg(atom->string()));
+ }
+ }
+ break;
+ case Atom::SinceList: {
+ const NodeMultiMap &nsmap = m_qdb->getSinceMap(atom->string());
+ if (nsmap.isEmpty())
+ break;
+
+ const NodeMultiMap &ncmap = m_qdb->getClassMap(atom->string());
+ const NodeMultiMap &nqcmap = m_qdb->getQmlTypeMap(atom->string());
+
+ Sections sections(nsmap);
+ out() << "<ul>\n";
+ const QList<Section> sinceSections = sections.sinceSections();
+ for (const auto &section : sinceSections) {
+ if (!section.members().isEmpty()) {
+ out() << "<li>"
+ << "<a href=\"#" << Utilities::asAsciiPrintable(section.title()) << "\">"
+ << section.title() << "</a></li>\n";
+ }
+ }
+ out() << "</ul>\n";
+
+ int index = 0;
+ for (const auto &section : sinceSections) {
+ if (!section.members().isEmpty()) {
+ out() << "<h3 id=\"" << Utilities::asAsciiPrintable(section.title()) << "\">"
+ << protectEnc(section.title()) << "</h3>\n";
+ if (index == Sections::SinceClasses)
+ generateCompactList(Generic, relative, ncmap, false, QStringLiteral("Q"));
+ else if (index == Sections::SinceQmlTypes)
+ generateCompactList(Generic, relative, nqcmap, false, QStringLiteral(""));
+ else if (index == Sections::SinceMemberFunctions
+ || index == Sections::SinceQmlMethods
+ || index == Sections::SinceQmlProperties) {
+
+ QMap<QString, NodeMultiMap> parentmaps;
+
+ const QList<Node *> &members = section.members();
+ for (const auto &member : members) {
+ QString parent_full_name = (*member).parent()->fullName();
+
+ auto parent_entry = parentmaps.find(parent_full_name);
+ if (parent_entry == parentmaps.end())
+ parent_entry = parentmaps.insert(parent_full_name, NodeMultiMap());
+ parent_entry->insert(member->name(), member);
+ }
+
+ for (auto map = parentmaps.begin(); map != parentmaps.end(); ++map) {
+ NodeVector nv = map->values().toVector();
+ auto parent = nv.front()->parent();
+
+ out() << ((index == Sections::SinceMemberFunctions) ? "<p>Class " : "<p>QML Type ");
+
+ out() << "<a href=\"" << linkForNode(parent, relative) << "\" translate=\"no\">";
+ QStringList pieces = parent->fullName().split("::");
+ out() << protectEnc(pieces.last());
+ out() << "</a>"
+ << ":</p>\n";
+
+ generateSection(nv, relative, marker);
+ out() << "<br/>";
+ }
+ } else if (index == Sections::SinceEnumValues) {
+ out() << "<div class=\"table\"><table class=\"alignedsummary\" translate=\"no\">\n";
+ const auto map_it = m_qdb->newEnumValueMaps().constFind(atom->string());
+ for (auto it = map_it->cbegin(); it != map_it->cend(); ++it) {
+ out() << "<tr><td class=\"memItemLeft\"> enum value </td><td class=\"memItemRight\">"
+ << "<b><a href=\"" << linkForNode(it.value(), nullptr) << "\">"
+ << it.key() << "</a></b></td></tr>\n";
+ }
+ out() << "</table></div>\n";
+ } else {
+ generateSection(section.members(), relative, marker);
+ }
+ }
+ ++index;
+ }
+ } break;
+ case Atom::BR:
+ out() << "<br />\n";
+ break;
+ case Atom::HR:
+ out() << "<hr />\n";
+ break;
+ case Atom::Image:
+ case Atom::InlineImage: {
+ QString text;
+ if (atom->next() && atom->next()->type() == Atom::ImageText)
+ text = atom->next()->string();
+ if (atom->type() == Atom::Image)
+ out() << "<p class=\"centerAlign\">";
+
+ auto maybe_resolved_file{file_resolver.resolve(atom->string())};
+ if (!maybe_resolved_file) {
+ // TODO: [uncentralized-admonition]
+ relative->location().warning(
+ QStringLiteral("Missing image: %1").arg(protectEnc(atom->string())));
+ out() << "<font color=\"red\">[Missing image " << protectEnc(atom->string())
+ << "]</font>";
+ } else {
+ ResolvedFile file{*maybe_resolved_file};
+ QString file_name{QFileInfo{file.get_path()}.fileName()};
+
+ // TODO: [operation-can-fail-making-the-output-incorrect]
+ // The operation of copying the file can fail, making the
+ // output refer to an image that does not exist.
+ // This should be fine as HTML will take care of managing
+ // the rendering of a missing image, but what html will
+ // render is in stark contrast with what we do when the
+ // image does not exist at all.
+ // It may be more correct to unify the behavior between
+ // the two either by considering images that cannot be
+ // copied as missing or letting the HTML renderer
+ // always taking care of the two cases.
+ // Do notice that effectively doing this might be
+ // unnecessary as extracting the output directory logic
+ // should ensure that a safe assumption for copy should be
+ // made at the API boundary.
+
+ // TODO: [uncentralized-output-directory-structure]
+ Config::copyFile(relative->doc().location(), file.get_path(), file_name, outputDir() + QLatin1String("/images"));
+
+ // TODO: [uncentralized-output-directory-structure]
+ out() << "<img src=\"" << "images/" + protectEnc(file_name) << '"';
+
+ // TODO: [same-result-branching]
+ // If text is empty protectEnc should return the empty
+ // string itself, such that the two branches would still
+ // result in the same output.
+ // Ensure that this is the case and then flatten the branch if so.
+ if (!text.isEmpty())
+ out() << " alt=\"" << protectEnc(text) << '"';
+ else
+ out() << " alt=\"\"";
+
+ out() << " />";
+
+ // TODO: [uncentralized-output-directory-structure]
+ m_helpProjectWriter->addExtraFile("images/" + file_name);
+ setImageFileName(relative, "images/" + file_name);
+ }
+
+ if (atom->type() == Atom::Image)
+ out() << "</p>";
+ } break;
+ case Atom::ImageText:
+ break;
+ // Admonitions
+ case Atom::ImportantLeft:
+ case Atom::NoteLeft:
+ case Atom::WarningLeft: {
+ QString admonType = atom->typeString();
+ // Remove 'Left' from atom type to get the admonition type
+ admonType.chop(4);
+ out() << "<div class=\"admonition " << admonType.toLower() << "\">\n"
+ << "<p>";
+ out() << formattingLeftMap()[ATOM_FORMATTING_BOLD];
+ out() << admonType << ": ";
+ out() << formattingRightMap()[ATOM_FORMATTING_BOLD];
+ } break;
+ case Atom::ImportantRight:
+ case Atom::NoteRight:
+ case Atom::WarningRight:
+ out() << "</p>\n"
+ << "</div>\n";
+ break;
+ case Atom::LegaleseLeft:
+ out() << "<div class=\"LegaleseLeft\">";
+ break;
+ case Atom::LegaleseRight:
+ out() << "</div>";
+ break;
+ case Atom::LineBreak:
+ out() << "<br/>";
+ break;
+ case Atom::Link:
+ // Prevent nested links in table of contents
+ if (m_inContents)
+ break;
+ Q_FALLTHROUGH();
+ case Atom::NavLink: {
+ const Node *node = nullptr;
+ QString link = getLink(atom, relative, &node);
+ if (link.isEmpty() && (node != relative) && !noLinkErrors()) {
+ Location location = atom->isLinkAtom() ? static_cast<const LinkAtom*>(atom)->location
+ : relative->doc().location();
+ location.warning(
+ QStringLiteral("Can't link to '%1'").arg(atom->string()));
+ }
+ beginLink(link, node, relative);
+ skipAhead = 1;
+ } break;
+ case Atom::ExampleFileLink: {
+ QString link = linkForExampleFile(atom->string());
+ beginLink(link);
+ skipAhead = 1;
+ } break;
+ case Atom::ExampleImageLink: {
+ QString link = atom->string();
+ link = "images/used-in-examples/" + link;
+ beginLink(link);
+ skipAhead = 1;
+ } break;
+ case Atom::LinkNode: {
+ const Node *node = CodeMarker::nodeForString(atom->string());
+ beginLink(linkForNode(node, relative), node, relative);
+ skipAhead = 1;
+ } break;
+ case Atom::ListLeft:
+ if (in_para) {
+ out() << "</p>\n";
+ in_para = false;
+ }
+ if (atom->string() == ATOM_LIST_BULLET) {
+ out() << "<ul>\n";
+ } else if (atom->string() == ATOM_LIST_TAG) {
+ out() << "<dl>\n";
+ } else if (atom->string() == ATOM_LIST_VALUE) {
+ out() << R"(<div class="table"><table class="valuelist">)";
+ m_threeColumnEnumValueTable = isThreeColumnEnumValueTable(atom);
+ if (m_threeColumnEnumValueTable) {
+ if (++m_numTableRows % 2 == 1)
+ out() << R"(<tr valign="top" class="odd">)";
+ else
+ out() << R"(<tr valign="top" class="even">)";
+
+ out() << "<th class=\"tblConst\">Constant</th>";
+
+ // If not in \enum topic, skip the value column
+ if (relative->isEnumType())
+ out() << "<th class=\"tblval\">Value</th>";
+
+ out() << "<th class=\"tbldscr\">Description</th></tr>\n";
+ } else {
+ out() << "<tr><th class=\"tblConst\">Constant</th><th "
+ "class=\"tblVal\">Value</th></tr>\n";
+ }
+ } else {
+ QString olType;
+ if (atom->string() == ATOM_LIST_UPPERALPHA) {
+ olType = "A";
+ } else if (atom->string() == ATOM_LIST_LOWERALPHA) {
+ olType = "a";
+ } else if (atom->string() == ATOM_LIST_UPPERROMAN) {
+ olType = "I";
+ } else if (atom->string() == ATOM_LIST_LOWERROMAN) {
+ olType = "i";
+ } else { // (atom->string() == ATOM_LIST_NUMERIC)
+ olType = "1";
+ }
+
+ if (atom->next() != nullptr && atom->next()->string().toInt() > 1) {
+ out() << QString(R"(<ol class="%1" type="%1" start="%2">)")
+ .arg(olType, atom->next()->string());
+ } else
+ out() << QString(R"(<ol class="%1" type="%1">)").arg(olType);
+ }
+ break;
+ case Atom::ListItemNumber:
+ break;
+ case Atom::ListTagLeft:
+ if (atom->string() == ATOM_LIST_TAG) {
+ out() << "<dt>";
+ } else { // (atom->string() == ATOM_LIST_VALUE)
+ std::pair<QString, int> pair = getAtomListValue(atom);
+ skipAhead = pair.second;
+ QString t = protectEnc(plainCode(marker->markedUpEnumValue(pair.first, relative)));
+ out() << "<tr><td class=\"topAlign\"><code translate=\"no\">" << t << "</code>";
+
+ if (relative->isEnumType()) {
+ out() << "</td><td class=\"topAlign tblval\">";
+ const auto *enume = static_cast<const EnumNode *>(relative);
+ QString itemValue = enume->itemValue(atom->next()->string());
+ if (itemValue.isEmpty())
+ out() << '?';
+ else
+ out() << "<code translate=\"no\">" << protectEnc(itemValue) << "</code>";
+ }
+ }
+ break;
+ case Atom::SinceTagRight:
+ case Atom::ListTagRight:
+ if (atom->string() == ATOM_LIST_TAG)
+ out() << "</dt>\n";
+ break;
+ case Atom::ListItemLeft:
+ if (atom->string() == ATOM_LIST_TAG) {
+ out() << "<dd>";
+ } else if (atom->string() == ATOM_LIST_VALUE) {
+ if (m_threeColumnEnumValueTable) {
+ out() << "</td><td class=\"topAlign\">";
+ if (matchAhead(atom, Atom::ListItemRight))
+ out() << "&nbsp;";
+ }
+ } else {
+ out() << "<li>";
+ }
+ if (matchAhead(atom, Atom::ParaLeft))
+ skipAhead = 1;
+ break;
+ case Atom::ListItemRight:
+ if (atom->string() == ATOM_LIST_TAG) {
+ out() << "</dd>\n";
+ } else if (atom->string() == ATOM_LIST_VALUE) {
+ out() << "</td></tr>\n";
+ } else {
+ out() << "</li>\n";
+ }
+ break;
+ case Atom::ListRight:
+ if (atom->string() == ATOM_LIST_BULLET) {
+ out() << "</ul>\n";
+ } else if (atom->string() == ATOM_LIST_TAG) {
+ out() << "</dl>\n";
+ } else if (atom->string() == ATOM_LIST_VALUE) {
+ out() << "</table></div>\n";
+ } else {
+ out() << "</ol>\n";
+ }
+ break;
+ case Atom::Nop:
+ break;
+ case Atom::ParaLeft:
+ out() << "<p>";
+ in_para = true;
+ break;
+ case Atom::ParaRight:
+ endLink();
+ if (in_para) {
+ out() << "</p>\n";
+ in_para = false;
+ }
+ // if (!matchAhead(atom, Atom::ListItemRight) && !matchAhead(atom, Atom::TableItemRight))
+ // out() << "</p>\n";
+ break;
+ case Atom::QuotationLeft:
+ out() << "<blockquote>";
+ break;
+ case Atom::QuotationRight:
+ out() << "</blockquote>\n";
+ break;
+ case Atom::RawString:
+ out() << atom->string();
+ break;
+ case Atom::SectionLeft:
+ case Atom::SectionRight:
+ break;
+ case Atom::SectionHeadingLeft: {
+ int unit = atom->string().toInt() + hOffset(relative);
+ out() << "<h" + QString::number(unit) + QLatin1Char(' ') << "id=\""
+ << Tree::refForAtom(atom) << "\">";
+ m_inSectionHeading = true;
+ break;
+ }
+ case Atom::SectionHeadingRight:
+ out() << "</h" + QString::number(atom->string().toInt() + hOffset(relative)) + ">\n";
+ m_inSectionHeading = false;
+ break;
+ case Atom::SidebarLeft:
+ Q_FALLTHROUGH();
+ case Atom::SidebarRight:
+ break;
+ case Atom::String:
+ if (m_inLink && !m_inContents && !m_inSectionHeading) {
+ generateLink(atom);
+ } else {
+ out() << protectEnc(atom->string());
+ }
+ break;
+ case Atom::TableLeft: {
+ std::pair<QString, QString> pair = getTableWidthAttr(atom);
+ QString attr = pair.second;
+ QString width = pair.first;
+
+ if (in_para) {
+ out() << "</p>\n";
+ in_para = false;
+ }
+
+ out() << R"(<div class="table"><table class=")" << attr << '"';
+ if (!width.isEmpty())
+ out() << " width=\"" << width << '"';
+ out() << ">\n ";
+ m_numTableRows = 0;
+ } break;
+ case Atom::TableRight:
+ out() << "</table></div>\n";
+ break;
+ case Atom::TableHeaderLeft:
+ out() << "<thead><tr class=\"qt-style\">";
+ m_inTableHeader = true;
+ break;
+ case Atom::TableHeaderRight:
+ out() << "</tr>";
+ if (matchAhead(atom, Atom::TableHeaderLeft)) {
+ skipAhead = 1;
+ out() << "\n<tr class=\"qt-style\">";
+ } else {
+ out() << "</thead>\n";
+ m_inTableHeader = false;
+ }
+ break;
+ case Atom::TableRowLeft:
+ if (!atom->string().isEmpty())
+ out() << "<tr " << atom->string() << '>';
+ else if (++m_numTableRows % 2 == 1)
+ out() << R"(<tr valign="top" class="odd">)";
+ else
+ out() << R"(<tr valign="top" class="even">)";
+ break;
+ case Atom::TableRowRight:
+ out() << "</tr>\n";
+ break;
+ case Atom::TableItemLeft: {
+ if (m_inTableHeader)
+ out() << "<th ";
+ else
+ out() << "<td ";
+
+ for (int i = 0; i < atom->count(); ++i) {
+ if (i > 0)
+ out() << ' ';
+ const QString &p = atom->string(i);
+ if (p.contains('=')) {
+ out() << p;
+ } else {
+ QStringList spans = p.split(QLatin1Char(','));
+ if (spans.size() == 2) {
+ if (spans.at(0) != "1")
+ out() << " colspan=\"" << spans.at(0) << '"';
+ if (spans.at(1) != "1")
+ out() << " rowspan=\"" << spans.at(1) << '"';
+ }
+ }
+ }
+ out() << '>';
+ if (matchAhead(atom, Atom::ParaLeft))
+ skipAhead = 1;
+ } break;
+ case Atom::TableItemRight:
+ if (m_inTableHeader)
+ out() << "</th>";
+ else {
+ out() << "</td>";
+ }
+ if (matchAhead(atom, Atom::ParaLeft))
+ skipAhead = 1;
+ break;
+ case Atom::TableOfContents:
+ Q_FALLTHROUGH();
+ case Atom::Keyword:
+ break;
+ case Atom::Target:
+ out() << "<span id=\"" << Utilities::asAsciiPrintable(atom->string()) << "\"></span>";
+ break;
+ case Atom::UnhandledFormat:
+ out() << "<b class=\"redFont\">&lt;Missing HTML&gt;</b>";
+ break;
+ case Atom::UnknownCommand:
+ out() << R"(<b class="redFont"><code translate=\"no\">\)" << protectEnc(atom->string()) << "</code></b>";
+ break;
+ case Atom::CodeQuoteArgument:
+ case Atom::CodeQuoteCommand:
+ case Atom::ComparesLeft:
+ case Atom::ComparesRight:
+ case Atom::SnippetCommand:
+ case Atom::SnippetIdentifier:
+ case Atom::SnippetLocation:
+ // no HTML output (ignore)
+ break;
+ default:
+ unknownAtom(atom);
+ }
+ return skipAhead;
+}
+
+/*!
+ * Return a string representing a text that exposes information about
+ * the user-visible groups that the \a node is part of. A user-visible
+ * group is a group that generates an output page, that is, a \\group
+ * topic exists for the group and can be linked to.
+ *
+ * The returned string is composed of comma separated links to the
+ * groups, with their title as the user-facing text, surrounded by
+ * some introductory text.
+ *
+ * For example, if a node named N is part of the groups with title A
+ * and B, the line rendered form of the line will be "N is part of the
+ * A, B groups", where A and B are clickable links that target the
+ * respective page of each group.
+ *
+ * If a node has a single group, the comma is removed for readability
+ * pusposes and "groups" is expressed as a singular noun.
+ * For example, "N is part of the A group".
+ *
+ * The returned string is empty when the node is not linked to any
+ * group that has a valid link target.
+ *
+ * This string is used in the summary of c++ classes or qml types to
+ * link them to some of the overview documentation that is generated
+ * through the "\group" command.
+ *
+ * Note that this is currently, incorrectly, a member of
+ * HtmlGenerator as it requires access to some protected/private
+ * members for escaping and linking.
+ */
+QString HtmlGenerator::groupReferenceText(PageNode* node) {
+ auto link_for_group = [this](const CollectionNode *group) -> QString {
+ QString target{linkForNode(group, nullptr)};
+ return (target.isEmpty()) ? protectEnc(group->name()) : "<a href=\"" + target + "\">" + protectEnc(group->fullTitle()) + "</a>";
+ };
+
+ QString text{};
+
+ const QStringList &groups_names{node->groupNames()};
+ if (groups_names.isEmpty())
+ return text;
+
+ std::vector<CollectionNode *> groups_nodes(groups_names.size(), nullptr);
+ std::transform(groups_names.cbegin(), groups_names.cend(), groups_nodes.begin(),
+ [this](const QString &group_name) -> CollectionNode* {
+ CollectionNode *group{m_qdb->groups()[group_name]};
+ m_qdb->mergeCollections(group);
+ return (group && group->wasSeen()) ? group : nullptr;
+ });
+ groups_nodes.erase(std::remove(groups_nodes.begin(), groups_nodes.end(), nullptr), groups_nodes.end());
+
+ if (!groups_nodes.empty()) {
+ text += node->name() + " is part of ";
+
+ for (std::vector<CollectionNode *>::size_type index{0}; index < groups_nodes.size(); ++index) {
+ text += link_for_group(groups_nodes[index]) + Utilities::separator(index, groups_nodes.size());
+ }
+ }
+ return text;
+}
+
+/*!
+ Generate a reference page for the C++ class, namespace, or
+ header file documented in \a node using the code \a marker
+ provided.
+ */
+void HtmlGenerator::generateCppReferencePage(Aggregate *aggregate, CodeMarker *marker)
+{
+ QString title;
+ QString rawTitle;
+ QString fullTitle;
+ NamespaceNode *ns = nullptr;
+ SectionVector *summarySections = nullptr;
+ SectionVector *detailsSections = nullptr;
+
+ Sections sections(aggregate);
+ QString word = aggregate->typeWord(true);
+ auto templateDecl = aggregate->templateDecl();
+ if (aggregate->isNamespace()) {
+ rawTitle = aggregate->plainName();
+ fullTitle = aggregate->plainFullName();
+ title = rawTitle + " Namespace";
+ ns = static_cast<NamespaceNode *>(aggregate);
+ summarySections = &sections.stdSummarySections();
+ detailsSections = &sections.stdDetailsSections();
+ } else if (aggregate->isClassNode()) {
+ rawTitle = aggregate->plainName();
+ fullTitle = aggregate->plainFullName();
+ title = rawTitle + QLatin1Char(' ') + word;
+ summarySections = &sections.stdCppClassSummarySections();
+ detailsSections = &sections.stdCppClassDetailsSections();
+ } else if (aggregate->isHeader()) {
+ title = fullTitle = rawTitle = aggregate->fullTitle();
+ summarySections = &sections.stdSummarySections();
+ detailsSections = &sections.stdDetailsSections();
+ }
+
+ Text subtitleText;
+ if (rawTitle != fullTitle || templateDecl) {
+ if (aggregate->isClassNode()) {
+ if (templateDecl)
+ subtitleText << (*templateDecl).to_qstring() + QLatin1Char(' ');
+ subtitleText << aggregate->typeWord(false) + QLatin1Char(' ');
+ const QStringList ancestors = fullTitle.split(QLatin1String("::"));
+ for (const auto &a : ancestors) {
+ if (a == rawTitle) {
+ subtitleText << a;
+ break;
+ } else {
+ subtitleText << Atom(Atom::AutoLink, a) << "::";
+ }
+ }
+ } else {
+ subtitleText << fullTitle;
+ }
+ }
+
+ generateHeader(title, aggregate, marker);
+ generateTableOfContents(aggregate, marker, summarySections);
+ generateTitle(title, subtitleText, SmallSubTitle, aggregate, marker);
+ if (ns && !ns->hasDoc() && ns->docNode()) {
+ NamespaceNode *NS = ns->docNode();
+ Text brief;
+ brief << "The " << ns->name() << " namespace includes the following elements from module "
+ << ns->tree()->camelCaseModuleName() << ". The full namespace is "
+ << "documented in module " << NS->tree()->camelCaseModuleName()
+ << Atom(Atom::LinkNode, CodeMarker::stringForNode(NS))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, " here.")
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+ out() << "<p>";
+ generateText(brief, ns, marker);
+ out() << "</p>\n";
+ } else
+ generateBrief(aggregate, marker);
+
+ const auto parentIsClass = aggregate->parent()->isClassNode();
+
+ if (!parentIsClass)
+ generateRequisites(aggregate, marker);
+ generateStatus(aggregate, marker);
+ if (parentIsClass)
+ generateSince(aggregate, marker);
+
+ QString membersLink = generateAllMembersFile(Sections::allMembersSection(), marker);
+ if (!membersLink.isEmpty()) {
+ openUnorderedList();
+ out() << "<li><a href=\"" << membersLink << "\">"
+ << "List of all members, including inherited members</a></li>\n";
+ }
+ QString obsoleteLink = generateObsoleteMembersFile(sections, marker);
+ if (!obsoleteLink.isEmpty()) {
+ openUnorderedList();
+ out() << "<li><a href=\"" << obsoleteLink << "\">"
+ << "Deprecated members</a></li>\n";
+ }
+
+ if (QString groups_text{groupReferenceText(aggregate)}; !groups_text.isEmpty()) {
+ openUnorderedList();
+
+ out() << "<li>" << groups_text << "</li>\n";
+ }
+
+ closeUnorderedList();
+ generateComparisonCategory(aggregate, marker);
+ generateComparisonList(aggregate);
+
+ generateThreadSafeness(aggregate, marker);
+
+ bool needOtherSection = false;
+
+ for (const auto &section : std::as_const(*summarySections)) {
+ if (section.members().isEmpty() && section.reimplementedMembers().isEmpty()) {
+ if (!section.inheritedMembers().isEmpty())
+ needOtherSection = true;
+ } else {
+ if (!section.members().isEmpty()) {
+ QString ref = registerRef(section.title().toLower());
+ out() << "<h2 id=\"" << ref << "\">" << protectEnc(section.title()) << "</h2>\n";
+ generateSection(section.members(), aggregate, marker);
+ }
+ if (!section.reimplementedMembers().isEmpty()) {
+ QString name = QString("Reimplemented ") + section.title();
+ QString ref = registerRef(name.toLower());
+ out() << "<h2 id=\"" << ref << "\">" << protectEnc(name) << "</h2>\n";
+ generateSection(section.reimplementedMembers(), aggregate, marker);
+ }
+
+ if (!section.inheritedMembers().isEmpty()) {
+ out() << "<ul>\n";
+ generateSectionInheritedList(section, aggregate);
+ out() << "</ul>\n";
+ }
+ }
+ }
+
+ if (needOtherSection) {
+ out() << "<h3>Additional Inherited Members</h3>\n"
+ "<ul>\n";
+
+ for (const auto &section : std::as_const(*summarySections)) {
+ if (section.members().isEmpty() && !section.inheritedMembers().isEmpty())
+ generateSectionInheritedList(section, aggregate);
+ }
+ out() << "</ul>\n";
+ }
+
+ if (aggregate->doc().isEmpty()) {
+ QString command = "documentation";
+ if (aggregate->isClassNode())
+ command = R"('\class' comment)";
+ if (!ns || ns->isDocumentedHere()) {
+ aggregate->location().warning(
+ QStringLiteral("No %1 for '%2'").arg(command, aggregate->plainSignature()));
+ }
+ } else {
+ generateExtractionMark(aggregate, DetailedDescriptionMark);
+ out() << "<div class=\"descr\">\n"
+ << "<h2 id=\"" << registerRef("details") << "\">"
+ << "Detailed Description"
+ << "</h2>\n";
+ generateBody(aggregate, marker);
+ out() << "</div>\n";
+ generateAlsoList(aggregate, marker);
+ generateExtractionMark(aggregate, EndMark);
+ }
+
+ for (const auto &section : std::as_const(*detailsSections)) {
+ bool headerGenerated = false;
+ if (section.isEmpty())
+ continue;
+
+ const QList<Node *> &members = section.members();
+ for (const auto &member : members) {
+ if (member->access() == Access::Private) // ### check necessary?
+ continue;
+ if (!headerGenerated) {
+ if (!section.divClass().isEmpty())
+ out() << "<div class=\"" << section.divClass() << "\">\n";
+ out() << "<h2>" << protectEnc(section.title()) << "</h2>\n";
+ headerGenerated = true;
+ }
+ if (!member->isClassNode())
+ generateDetailedMember(member, aggregate, marker);
+ else {
+ out() << "<h3> class ";
+ generateFullName(member, aggregate);
+ out() << "</h3>";
+ generateBrief(member, marker, aggregate);
+ }
+
+ QStringList names;
+ names << member->name();
+ if (member->isFunction()) {
+ const auto *func = reinterpret_cast<const FunctionNode *>(member);
+ if (func->isSomeCtor() || func->isDtor() || func->overloadNumber() != 0)
+ names.clear();
+ } else if (member->isProperty()) {
+ const auto *prop = reinterpret_cast<const PropertyNode *>(member);
+ if (!prop->getters().isEmpty() && !names.contains(prop->getters().first()->name()))
+ names << prop->getters().first()->name();
+ if (!prop->setters().isEmpty())
+ names << prop->setters().first()->name();
+ if (!prop->resetters().isEmpty())
+ names << prop->resetters().first()->name();
+ if (!prop->notifiers().isEmpty())
+ names << prop->notifiers().first()->name();
+ } else if (member->isEnumType()) {
+ const auto *enume = reinterpret_cast<const EnumNode *>(member);
+ if (enume->flagsType())
+ names << enume->flagsType()->name();
+ const auto &enumItemNameList = enume->doc().enumItemNames();
+ const auto &omitEnumItemNameList = enume->doc().omitEnumItemNames();
+ const auto items = QSet<QString>(enumItemNameList.cbegin(), enumItemNameList.cend())
+ - QSet<QString>(omitEnumItemNameList.cbegin(), omitEnumItemNameList.cend());
+ for (const QString &enumName : items) {
+ names << plainCode(marker->markedUpEnumValue(enumName, enume));
+ }
+ }
+ }
+ if (headerGenerated && !section.divClass().isEmpty())
+ out() << "</div>\n";
+ }
+ generateFooter(aggregate);
+}
+
+void HtmlGenerator::generateProxyPage(Aggregate *aggregate, CodeMarker *marker)
+{
+ Q_ASSERT(aggregate->isProxyNode());
+
+ QString title;
+ QString rawTitle;
+ QString fullTitle;
+ Text subtitleText;
+ SectionVector *summarySections = nullptr;
+ SectionVector *detailsSections = nullptr;
+
+ Sections sections(aggregate);
+ rawTitle = aggregate->plainName();
+ fullTitle = aggregate->plainFullName();
+ title = rawTitle + " Proxy Page";
+ summarySections = &sections.stdSummarySections();
+ detailsSections = &sections.stdDetailsSections();
+ generateHeader(title, aggregate, marker);
+ generateTitle(title, subtitleText, SmallSubTitle, aggregate, marker);
+ generateBrief(aggregate, marker);
+ for (auto it = summarySections->constBegin(); it != summarySections->constEnd(); ++it) {
+ if (!it->members().isEmpty()) {
+ QString ref = registerRef(it->title().toLower());
+ out() << "<h2 id=\"" << ref << "\">" << protectEnc(it->title()) << "</h2>\n";
+ generateSection(it->members(), aggregate, marker);
+ }
+ }
+
+ if (!aggregate->doc().isEmpty()) {
+ generateExtractionMark(aggregate, DetailedDescriptionMark);
+ out() << "<div class=\"descr\">\n"
+ << "<h2 id=\"" << registerRef("details") << "\">"
+ << "Detailed Description"
+ << "</h2>\n";
+ generateBody(aggregate, marker);
+ out() << "</div>\n";
+ generateAlsoList(aggregate, marker);
+ generateExtractionMark(aggregate, EndMark);
+ }
+
+ for (const auto &section : std::as_const(*detailsSections)) {
+ if (section.isEmpty())
+ continue;
+
+ if (!section.divClass().isEmpty())
+ out() << "<div class=\"" << section.divClass() << "\">\n";
+ out() << "<h2>" << protectEnc(section.title()) << "</h2>\n";
+
+ const QList<Node *> &members = section.members();
+ for (const auto &member : members) {
+ if (!member->isPrivate()) { // ### check necessary?
+ if (!member->isClassNode())
+ generateDetailedMember(member, aggregate, marker);
+ else {
+ out() << "<h3> class ";
+ generateFullName(member, aggregate);
+ out() << "</h3>";
+ generateBrief(member, marker, aggregate);
+ }
+
+ QStringList names;
+ names << member->name();
+ if (member->isFunction()) {
+ const auto *func = reinterpret_cast<const FunctionNode *>(member);
+ if (func->isSomeCtor() || func->isDtor() || func->overloadNumber() != 0)
+ names.clear();
+ } else if (member->isEnumType()) {
+ const auto *enume = reinterpret_cast<const EnumNode *>(member);
+ if (enume->flagsType())
+ names << enume->flagsType()->name();
+ const auto &enumItemNameList = enume->doc().enumItemNames();
+ const auto &omitEnumItemNameList = enume->doc().omitEnumItemNames();
+ const auto items =
+ QSet<QString>(enumItemNameList.cbegin(), enumItemNameList.cend())
+ - QSet<QString>(omitEnumItemNameList.cbegin(),
+ omitEnumItemNameList.cend());
+ for (const QString &enumName : items)
+ names << plainCode(marker->markedUpEnumValue(enumName, enume));
+ }
+ }
+ }
+ if (!section.divClass().isEmpty())
+ out() << "</div>\n";
+ }
+ generateFooter(aggregate);
+}
+
+/*!
+ Generate the HTML page for a QML type. \qcn is the QML type.
+ \marker is the code markeup object.
+ */
+void HtmlGenerator::generateQmlTypePage(QmlTypeNode *qcn, CodeMarker *marker)
+{
+ Generator::setQmlTypeContext(qcn);
+ SubTitleSize subTitleSize = LargeSubTitle;
+ QString htmlTitle = qcn->fullTitle();
+ if (qcn->isQmlBasicType())
+ htmlTitle.append(" QML Value Type");
+ else
+ htmlTitle.append(" QML Type");
+
+
+ generateHeader(htmlTitle, qcn, marker);
+ Sections sections(qcn);
+ generateTableOfContents(qcn, marker, &sections.stdQmlTypeSummarySections());
+ marker = CodeMarker::markerForLanguage(QLatin1String("QML"));
+ generateTitle(htmlTitle, Text() << qcn->subtitle(), subTitleSize, qcn, marker);
+ generateBrief(qcn, marker);
+ generateQmlRequisites(qcn, marker);
+ generateStatus(qcn, marker);
+
+ QString allQmlMembersLink;
+
+ // No 'All Members' file for QML value types
+ if (!qcn->isQmlBasicType())
+ allQmlMembersLink = generateAllQmlMembersFile(sections, marker);
+ QString obsoleteLink = generateObsoleteQmlMembersFile(sections, marker);
+ if (!allQmlMembersLink.isEmpty() || !obsoleteLink.isEmpty()) {
+ openUnorderedList();
+
+ if (!allQmlMembersLink.isEmpty()) {
+ out() << "<li><a href=\"" << allQmlMembersLink << "\">"
+ << "List of all members, including inherited members</a></li>\n";
+ }
+ if (!obsoleteLink.isEmpty()) {
+ out() << "<li><a href=\"" << obsoleteLink << "\">"
+ << "Deprecated members</a></li>\n";
+ }
+ }
+
+ if (QString groups_text{groupReferenceText(qcn)}; !groups_text.isEmpty()) {
+ openUnorderedList();
+
+ out() << "<li>" << groups_text << "</li>\n";
+ }
+
+ closeUnorderedList();
+
+ const QList<Section> &stdQmlTypeSummarySections = sections.stdQmlTypeSummarySections();
+ for (const auto &section : stdQmlTypeSummarySections) {
+ if (!section.isEmpty()) {
+ QString ref = registerRef(section.title().toLower());
+ out() << "<h2 id=\"" << ref << "\">" << protectEnc(section.title()) << "</h2>\n";
+ generateQmlSummary(section.members(), qcn, marker);
+ }
+ }
+
+ generateExtractionMark(qcn, DetailedDescriptionMark);
+ out() << "<h2 id=\"" << registerRef("details") << "\">"
+ << "Detailed Description"
+ << "</h2>\n";
+ generateBody(qcn, marker);
+ generateAlsoList(qcn, marker);
+ generateExtractionMark(qcn, EndMark);
+
+ const QList<Section> &stdQmlTypeDetailsSections = sections.stdQmlTypeDetailsSections();
+ for (const auto &section : stdQmlTypeDetailsSections) {
+ if (!section.isEmpty()) {
+ out() << "<h2>" << protectEnc(section.title()) << "</h2>\n";
+ const QList<Node *> &members = section.members();
+ for (const auto member : members) {
+ generateDetailedQmlMember(member, qcn, marker);
+ out() << "<br/>\n";
+ }
+ }
+ }
+ generateFooter(qcn);
+ Generator::setQmlTypeContext(nullptr);
+}
+
+/*!
+ Generate the HTML page for an entity that doesn't map
+ to any underlying parsable C++ or QML element.
+ */
+void HtmlGenerator::generatePageNode(PageNode *pn, CodeMarker *marker)
+{
+ SubTitleSize subTitleSize = LargeSubTitle;
+ QString fullTitle = pn->fullTitle();
+
+ generateHeader(fullTitle, pn, marker);
+ /*
+ Generate the TOC for the new doc format.
+ Don't generate a TOC for the home page.
+ */
+ if ((pn->name() != QLatin1String("index.html")))
+ generateTableOfContents(pn, marker, nullptr);
+
+ generateTitle(fullTitle, Text() << pn->subtitle(), subTitleSize, pn, marker);
+ if (pn->isExample()) {
+ generateBrief(pn, marker, nullptr, false);
+ }
+
+ generateExtractionMark(pn, DetailedDescriptionMark);
+ out() << R"(<div class="descr" id=")" << registerRef("details")
+ << "\">\n";
+
+ generateBody(pn, marker);
+ out() << "</div>\n";
+ generateAlsoList(pn, marker);
+ generateExtractionMark(pn, EndMark);
+
+ generateFooter(pn);
+}
+
+/*!
+ Generate the HTML page for a group, module, or QML module.
+ */
+void HtmlGenerator::generateCollectionNode(CollectionNode *cn, CodeMarker *marker)
+{
+ SubTitleSize subTitleSize = LargeSubTitle;
+ QString fullTitle = cn->fullTitle();
+ QString ref;
+
+ generateHeader(fullTitle, cn, marker);
+ generateTableOfContents(cn, marker, nullptr);
+ generateTitle(fullTitle, Text() << cn->subtitle(), subTitleSize, cn, marker);
+
+ // Generate brief for C++ modules, status for all modules.
+ if (cn->genus() != Node::DOC && cn->genus() != Node::DontCare) {
+ if (cn->isModule())
+ generateBrief(cn, marker);
+ generateStatus(cn, marker);
+ generateSince(cn, marker);
+ }
+
+ if (cn->isModule()) {
+ if (!cn->noAutoList()) {
+ NodeMap nmm{cn->getMembers(Node::Namespace)};
+ if (!nmm.isEmpty()) {
+ ref = registerRef("namespaces");
+ out() << "<h2 id=\"" << ref << "\">Namespaces</h2>\n";
+ generateAnnotatedList(cn, marker, nmm.values());
+ }
+ nmm = cn->getMembers([](const Node *n){ return n->isClassNode(); });
+ if (!nmm.isEmpty()) {
+ ref = registerRef("classes");
+ out() << "<h2 id=\"" << ref << "\">Classes</h2>\n";
+ generateAnnotatedList(cn, marker, nmm.values());
+ }
+ }
+ }
+
+ if (cn->isModule() && !cn->doc().briefText().isEmpty()) {
+ generateExtractionMark(cn, DetailedDescriptionMark);
+ ref = registerRef("details");
+ out() << "<div class=\"descr\">\n";
+ out() << "<h2 id=\"" << ref << "\">"
+ << "Detailed Description"
+ << "</h2>\n";
+ } else {
+ generateExtractionMark(cn, DetailedDescriptionMark);
+ out() << R"(<div class="descr" id=")" << registerRef("details")
+ << "\">\n";
+ }
+
+ generateBody(cn, marker);
+ out() << "</div>\n";
+ generateAlsoList(cn, marker);
+ generateExtractionMark(cn, EndMark);
+
+ if (!cn->noAutoList()) {
+ if (cn->isGroup() || cn->isQmlModule())
+ generateAnnotatedList(cn, marker, cn->members());
+ }
+ generateFooter(cn);
+}
+
+/*!
+ Generate the HTML page for a generic collection. This is usually
+ a collection of C++ elements that are related to an element in
+ a different module.
+ */
+void HtmlGenerator::generateGenericCollectionPage(CollectionNode *cn, CodeMarker *marker)
+{
+ SubTitleSize subTitleSize = LargeSubTitle;
+ QString fullTitle = cn->name();
+
+ generateHeader(fullTitle, cn, marker);
+ generateTitle(fullTitle, Text() << cn->subtitle(), subTitleSize, cn, marker);
+
+ Text brief;
+ brief << "Each function or type documented here is related to a class or "
+ << "namespace that is documented in a different module. The reference "
+ << "page for that class or namespace will link to the function or type "
+ << "on this page.";
+ out() << "<p>";
+ generateText(brief, cn, marker);
+ out() << "</p>\n";
+
+ const QList<Node *> members = cn->members();
+ for (const auto &member : members)
+ generateDetailedMember(member, cn, marker);
+
+ generateFooter(cn);
+}
+
+/*!
+ Returns "html" for this subclass of Generator.
+ */
+QString HtmlGenerator::fileExtension() const
+{
+ return "html";
+}
+
+/*!
+ Output a navigation bar (breadcrumbs) for the html file.
+ For API reference pages, items for the navigation bar are (in order):
+ \table
+ \header \li Item \li Related configuration variable \li Notes
+ \row \li home \li navigation.homepage \li e.g. 'Qt 6.2'
+ \row \li landing \li navigation.landingpage \li Module landing page
+ \row \li types \li navigation.cppclassespage (C++)\br
+ navigation.qmltypespage (QML) \li Types only
+ \row \li module \li n/a (automatic) \li Module page if different
+ from previous item
+ \row \li page \li n/a \li Current page title
+ \endtable
+
+ For other page types (page nodes) the navigation bar is constructed from home
+ page, landing page, and the chain of PageNode::navigationParent() items (if one exists).
+ This chain is constructed from the \\list structure on a page or pages defined in
+ \c navigation.toctitles configuration variable.
+
+ Finally, if no other navigation data exists for a page but it is a member of a
+ single group (using \\ingroup), add that group page to the navigation bar.
+ */
+void HtmlGenerator::generateNavigationBar(const QString &title, const Node *node,
+ CodeMarker *marker, const QString &buildversion,
+ bool tableItems)
+{
+ if (m_noNavigationBar || node == nullptr)
+ return;
+
+ Text navigationbar;
+
+ // Set list item types based on the navigation bar type
+ // TODO: Do we still need table items?
+ Atom::AtomType itemLeft = tableItems ? Atom::TableItemLeft : Atom::ListItemLeft;
+ Atom::AtomType itemRight = tableItems ? Atom::TableItemRight : Atom::ListItemRight;
+
+ // Helper to add an item to navigation bar based on a string link target
+ auto addNavItem = [&](const QString &link, const QString &title) {
+ navigationbar << Atom(itemLeft) << Atom(Atom::NavLink, link)
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << Atom(Atom::String, title)
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(itemRight);
+ };
+
+ // Helper to add an item to navigation bar based on a target node
+ auto addNavItemNode = [&](const Node *node, const QString &title) {
+ navigationbar << Atom(itemLeft) << Atom(Atom::LinkNode, CodeMarker::stringForNode(node))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << Atom(Atom::String, title)
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom(itemRight);
+ };
+
+ // Resolve the associated module (collection) node and its 'state' description
+ const auto *moduleNode = m_qdb->getModuleNode(node);
+ QString moduleState;
+ if (moduleNode && !moduleNode->state().isEmpty())
+ moduleState = QStringLiteral(" (%1)").arg(moduleNode->state());
+
+ if (m_hometitle == title)
+ return;
+ if (!m_homepage.isEmpty())
+ addNavItem(m_homepage, m_hometitle);
+ if (!m_landingpage.isEmpty() && m_landingtitle != title)
+ addNavItem(m_landingpage, m_landingtitle);
+
+ if (node->isClassNode()) {
+ if (!m_cppclassespage.isEmpty() && !m_cppclassestitle.isEmpty())
+ addNavItem(m_cppclassespage, m_cppclassestitle);
+ if (!node->physicalModuleName().isEmpty()) {
+ // Add explicit link to the \module page if:
+ // - It's not the C++ classes page that's already added, OR
+ // - It has a \modulestate associated with it
+ if (moduleNode && (!moduleState.isEmpty() || moduleNode->title() != m_cppclassespage))
+ addNavItemNode(moduleNode, moduleNode->name() + moduleState);
+ }
+ navigationbar << Atom(itemLeft) << Atom(Atom::String, node->name()) << Atom(itemRight);
+ } else if (node->isQmlType()) {
+ if (!m_qmltypespage.isEmpty() && !m_qmltypestitle.isEmpty())
+ addNavItem(m_qmltypespage, m_qmltypestitle);
+ // Add explicit link to the \qmlmodule page if:
+ // - It's not the QML types page that's already added, OR
+ // - It has a \modulestate associated with it
+ if (moduleNode && (!moduleState.isEmpty() || moduleNode->title() != m_qmltypespage)) {
+ addNavItemNode(moduleNode, moduleNode->name() + moduleState);
+ }
+ navigationbar << Atom(itemLeft) << Atom(Atom::String, node->name()) << Atom(itemRight);
+ } else {
+ if (node->isPageNode()) {
+ auto currentNode{static_cast<const PageNode*>(node)};
+ std::deque<const Node *> navNodes;
+ // Cutoff at 16 items in case there's a circular dependency
+ qsizetype navItems = 0;
+ while (currentNode->navigationParent() && ++navItems < 16) {
+ if (std::find(navNodes.cbegin(), navNodes.cend(),
+ currentNode->navigationParent()) == navNodes.cend())
+ navNodes.push_front(currentNode->navigationParent());
+ currentNode = currentNode->navigationParent();
+ }
+ // If no nav. parent was found but the page is a \group member, add a link to the
+ // (first) group page.
+ if (navNodes.empty()) {
+ const QStringList groups = static_cast<const PageNode *>(node)->groupNames();
+ for (const auto &groupName : groups) {
+ const auto *groupNode = m_qdb->findNodeByNameAndType(QStringList{groupName}, &Node::isGroup);
+ if (groupNode && !groupNode->title().isEmpty()) {
+ navNodes.push_front(groupNode);
+ break;
+ }
+ }
+ }
+ while (!navNodes.empty()) {
+ if (navNodes.front()->isPageNode())
+ addNavItemNode(navNodes.front(), navNodes.front()->title());
+ navNodes.pop_front();
+ }
+ }
+ if (!navigationbar.isEmpty()) {
+ navigationbar << Atom(itemLeft) << Atom(Atom::String, title) << Atom(itemRight);
+ }
+ }
+
+ generateText(navigationbar, node, marker);
+
+ if (buildversion.isEmpty())
+ return;
+
+ navigationbar.clear();
+
+ if (tableItems) {
+ out() << "</tr></table><table class=\"buildversion\"><tr>\n"
+ << R"(<td id="buildversion" width="100%" align="right">)";
+ } else {
+ out() << "<li id=\"buildversion\">";
+ }
+
+ // Link buildversion string to navigation.landingpage
+ if (!m_landingpage.isEmpty() && m_landingtitle != title) {
+ navigationbar << Atom(Atom::NavLink, m_landingpage)
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << Atom(Atom::String, buildversion)
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+ generateText(navigationbar, node, marker);
+ } else {
+ out() << buildversion;
+ }
+ if (tableItems)
+ out() << "</td>\n";
+ else
+ out() << "</li>\n";
+}
+
+void HtmlGenerator::generateHeader(const QString &title, const Node *node, CodeMarker *marker)
+{
+ out() << "<!DOCTYPE html>\n";
+ out() << QString("<html lang=\"%1\">\n").arg(naturalLanguage);
+ out() << "<head>\n";
+ out() << " <meta charset=\"utf-8\">\n";
+ if (node && !node->doc().location().isEmpty())
+ out() << "<!-- " << node->doc().location().fileName() << " -->\n";
+
+ if (node && !node->doc().briefText().isEmpty()) {
+ out() << " <meta name=\"description\" content=\""
+ << protectEnc(node->doc().briefText().toString())
+ << "\">\n";
+ }
+
+ // determine the rest of the <title> element content: "title | titleSuffix version"
+ QString titleSuffix;
+ if (!m_landingtitle.isEmpty()) {
+ // for normal pages: "title | landingtitle version"
+ titleSuffix = m_landingtitle;
+ } else if (!m_hometitle.isEmpty()) {
+ // for pages that set the homepage title but not landing page title:
+ // "title | hometitle version"
+ if (title != m_hometitle)
+ titleSuffix = m_hometitle;
+ } else if (!m_project.isEmpty()) {
+ // for projects outside of Qt or Qt 5: "title | project version"
+ if (title != m_project)
+ titleSuffix = m_project;
+ } else
+ // default: "title | Qt version"
+ titleSuffix = QLatin1String("Qt ");
+
+ if (title == titleSuffix)
+ titleSuffix.clear();
+
+ QString divider;
+ if (!titleSuffix.isEmpty() && !title.isEmpty())
+ divider = QLatin1String(" | ");
+
+ // Generating page title
+ out() << " <title>" << protectEnc(title) << divider << titleSuffix;
+
+ // append a full version to the suffix if neither suffix nor title
+ // include (a prefix of) version information
+ QVersionNumber projectVersion = QVersionNumber::fromString(m_qdb->version());
+ if (!projectVersion.isNull()) {
+ QVersionNumber titleVersion;
+ static const QRegularExpression re(QLatin1String(R"(\d+\.\d+)"));
+ const QString &versionedTitle = titleSuffix.isEmpty() ? title : titleSuffix;
+ auto match = re.match(versionedTitle);
+ if (match.hasMatch())
+ titleVersion = QVersionNumber::fromString(match.captured());
+ if (titleVersion.isNull() || !titleVersion.isPrefixOf(projectVersion))
+ out() << QLatin1Char(' ') << projectVersion.toString();
+ }
+ out() << "</title>\n";
+
+ // Include style sheet and script links.
+ out() << m_headerStyles;
+ out() << m_headerScripts;
+ if (m_endHeader.isEmpty())
+ out() << "</head>\n<body>\n";
+ else
+ out() << m_endHeader;
+
+ out() << QString(m_postHeader).replace("\\" + COMMAND_VERSION, m_qdb->version());
+ bool usingTable = m_postHeader.trimmed().endsWith(QLatin1String("<tr>"));
+ generateNavigationBar(title, node, marker, m_buildversion, usingTable);
+ out() << QString(m_postPostHeader).replace("\\" + COMMAND_VERSION, m_qdb->version());
+
+ m_navigationLinks.clear();
+ refMap.clear();
+
+ if (node && !node->links().empty()) {
+ std::pair<QString, QString> linkPair;
+ std::pair<QString, QString> anchorPair;
+ const Node *linkNode;
+ bool useSeparator = false;
+
+ if (node->links().contains(Node::PreviousLink)) {
+ linkPair = node->links()[Node::PreviousLink];
+ linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
+ if (linkNode == nullptr && !noLinkErrors())
+ node->doc().location().warning(
+ QStringLiteral("Cannot link to '%1'").arg(linkPair.first));
+ if (linkNode == nullptr || linkNode == node)
+ anchorPair = linkPair;
+ else
+ anchorPair = anchorForNode(linkNode);
+
+ out() << R"( <link rel="prev" href=")" << anchorPair.first << "\" />\n";
+
+ m_navigationLinks += R"(<a class="prevPage" href=")" + anchorPair.first + "\">";
+ if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
+ m_navigationLinks += protect(anchorPair.second);
+ else
+ m_navigationLinks += protect(linkPair.second);
+ m_navigationLinks += "</a>\n";
+ useSeparator = !m_navigationSeparator.isEmpty();
+ }
+ if (node->links().contains(Node::NextLink)) {
+ linkPair = node->links()[Node::NextLink];
+ linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
+ if (linkNode == nullptr && !noLinkErrors())
+ node->doc().location().warning(
+ QStringLiteral("Cannot link to '%1'").arg(linkPair.first));
+ if (linkNode == nullptr || linkNode == node)
+ anchorPair = linkPair;
+ else
+ anchorPair = anchorForNode(linkNode);
+
+ out() << R"( <link rel="next" href=")" << anchorPair.first << "\" />\n";
+
+ if (useSeparator)
+ m_navigationLinks += m_navigationSeparator;
+
+ m_navigationLinks += R"(<a class="nextPage" href=")" + anchorPair.first + "\">";
+ if (linkPair.first == linkPair.second && !anchorPair.second.isEmpty())
+ m_navigationLinks += protect(anchorPair.second);
+ else
+ m_navigationLinks += protect(linkPair.second);
+ m_navigationLinks += "</a>\n";
+ }
+ if (node->links().contains(Node::StartLink)) {
+ linkPair = node->links()[Node::StartLink];
+ linkNode = m_qdb->findNodeForTarget(linkPair.first, node);
+ if (linkNode == nullptr && !noLinkErrors())
+ node->doc().location().warning(
+ QStringLiteral("Cannot link to '%1'").arg(linkPair.first));
+ if (linkNode == nullptr || linkNode == node)
+ anchorPair = linkPair;
+ else
+ anchorPair = anchorForNode(linkNode);
+ out() << R"( <link rel="start" href=")" << anchorPair.first << "\" />\n";
+ }
+ }
+
+ if (node && !node->links().empty())
+ out() << "<p class=\"naviNextPrevious headerNavi\">\n" << m_navigationLinks << "</p>\n";
+}
+
+void HtmlGenerator::generateTitle(const QString &title, const Text &subtitle,
+ SubTitleSize subTitleSize, const Node *relative,
+ CodeMarker *marker)
+{
+ out() << QString(m_prologue).replace("\\" + COMMAND_VERSION, m_qdb->version());
+ QString attribute;
+ if (relative->genus() & Node::API)
+ attribute = R"( translate="no")";
+
+ if (!title.isEmpty())
+ out() << "<h1 class=\"title\"" << attribute << ">" << protectEnc(title) << "</h1>\n";
+ if (!subtitle.isEmpty()) {
+ out() << "<span";
+ if (subTitleSize == SmallSubTitle)
+ out() << " class=\"small-subtitle\"" << attribute << ">";
+ else
+ out() << " class=\"subtitle\"" << attribute << ">";
+ generateText(subtitle, relative, marker);
+ out() << "</span>\n";
+ }
+}
+
+void HtmlGenerator::generateFooter(const Node *node)
+{
+ if (node && !node->links().empty())
+ out() << "<p class=\"naviNextPrevious footerNavi\">\n" << m_navigationLinks << "</p>\n";
+
+ out() << QString(m_footer).replace("\\" + COMMAND_VERSION, m_qdb->version())
+ << QString(m_address).replace("\\" + COMMAND_VERSION, m_qdb->version());
+
+ out() << "</body>\n";
+ out() << "</html>\n";
+}
+
+/*!
+Lists the required imports and includes in a table.
+The number of rows is known.
+*/
+void HtmlGenerator::generateRequisites(Aggregate *aggregate, CodeMarker *marker)
+{
+ QMap<QString, Text> requisites;
+ Text text;
+
+ const QString headerText = "Header";
+ const QString sinceText = "Since";
+ const QString inheritedBytext = "Inherited By";
+ const QString inheritsText = "Inherits";
+ const QString nativeTypeText = "In QML";
+ const QString qtVariableText = "qmake";
+ const QString cmakeText = "CMake";
+ const QString statusText = "Status";
+
+ // The order of the requisites matter
+ const QStringList requisiteorder { headerText, cmakeText, qtVariableText, sinceText,
+ nativeTypeText, inheritsText, inheritedBytext, statusText };
+
+ addIncludeFileToMap(aggregate, marker, requisites, text, headerText);
+ addSinceToMap(aggregate, requisites, &text, sinceText);
+
+ if (aggregate->isClassNode() || aggregate->isNamespace()) {
+ addCMakeInfoToMap(aggregate, requisites, &text, cmakeText);
+ addQtVariableToMap(aggregate, requisites, &text, qtVariableText);
+ }
+
+ if (aggregate->isClassNode()) {
+ auto *classe = dynamic_cast<ClassNode *>(aggregate);
+ if (classe->isQmlNativeType() && !classe->isInternal())
+ addQmlNativeTypesToMap(requisites, &text, nativeTypeText, classe);
+
+ addInheritsToMap(requisites, &text, inheritsText, classe);
+ addInheritedByToMap(requisites, &text, inheritedBytext, classe);
+ }
+
+ // Add the state description (if any) to the map
+ addStatusToMap(aggregate, requisites, text, statusText);
+
+ if (!requisites.isEmpty()) {
+ // generate the table
+ generateTheTable(requisiteorder, requisites, headerText, aggregate, marker);
+ }
+}
+
+/*!
+ * \internal
+ */
+void HtmlGenerator::generateTheTable(const QStringList &requisiteOrder,
+ const QMap<QString, Text> &requisites,
+ const QString &headerText, const Aggregate *aggregate,
+ CodeMarker *marker)
+{
+ out() << "<div class=\"table\"><table class=\"alignedsummary\" translate=\"no\">\n";
+
+ for (auto it = requisiteOrder.constBegin(); it != requisiteOrder.constEnd(); ++it) {
+
+ if (requisites.contains(*it)) {
+ out() << "<tr>"
+ << "<td class=\"memItemLeft rightAlign topAlign\"> " << *it
+ << ":"
+ "</td><td class=\"memItemRight bottomAlign\"> ";
+
+ if (*it == headerText)
+ out() << requisites.value(*it).toString();
+ else
+ generateText(requisites.value(*it), aggregate, marker);
+ out() << "</td></tr>\n";
+ }
+ }
+ out() << "</table></div>\n";
+}
+
+/*!
+ * \internal
+ * Adds inherited by information to the map.
+ */
+void HtmlGenerator::addInheritedByToMap(QMap<QString, Text> &requisites, Text *text,
+ const QString &inheritedBytext, ClassNode *classe)
+{
+ if (!classe->derivedClasses().isEmpty()) {
+ text->clear();
+ *text << Atom::ParaLeft;
+ int count = appendSortedNames(*text, classe, classe->derivedClasses());
+ *text << Atom::ParaRight;
+ if (count > 0)
+ requisites.insert(inheritedBytext, *text);
+ }
+}
+
+/*!
+ * \internal
+ * Adds base classes to the map.
+ */
+void HtmlGenerator::addInheritsToMap(QMap<QString, Text> &requisites, Text *text,
+ const QString &inheritsText, ClassNode *classe)
+{
+ if (!classe->baseClasses().isEmpty()) {
+ int index = 0;
+ text->clear();
+ const auto baseClasses = classe->baseClasses();
+ for (const auto &cls : baseClasses) {
+ if (cls.m_node) {
+ appendFullName(*text, cls.m_node, classe);
+
+ if (cls.m_access == Access::Protected) {
+ *text << " (protected)";
+ } else if (cls.m_access == Access::Private) {
+ *text << " (private)";
+ }
+ *text << Utilities::comma(index++, classe->baseClasses().size());
+ }
+ }
+ *text << Atom::ParaRight;
+ if (index > 0)
+ requisites.insert(inheritsText, *text);
+ }
+}
+
+/*!
+ \internal
+ Add the QML/C++ native type information to the map.
+ */
+ void HtmlGenerator::addQmlNativeTypesToMap(QMap<QString, Text> &requisites, Text *text,
+ const QString &nativeTypeText, ClassNode *classe) const
+{
+ if (!text)
+ return;
+
+ text->clear();
+
+ QList<QmlTypeNode *> nativeTypes { classe->qmlNativeTypes().cbegin(), classe->qmlNativeTypes().cend()};
+ std::sort(nativeTypes.begin(), nativeTypes.end(), Node::nodeNameLessThan);
+ qsizetype index { 0 };
+
+ for (const auto &item : std::as_const(nativeTypes)) {
+ *text << Atom(Atom::LinkNode, CodeMarker::stringForNode(item))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK)
+ << Atom(Atom::String, item->name())
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+ *text << Utilities::comma(index++, nativeTypes.size());
+ }
+ requisites.insert(nativeTypeText, *text);
+}
+
+/*!
+ * \internal
+ * Adds the CMake package and link library information to the map.
+ */
+void HtmlGenerator::addCMakeInfoToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites,
+ Text *text, const QString &CMakeInfo) const
+{
+ if (!aggregate->physicalModuleName().isEmpty() && text != nullptr) {
+ const CollectionNode *cn =
+ m_qdb->getCollectionNode(aggregate->physicalModuleName(), Node::Module);
+ if (!cn || cn->qtCMakeComponent().isEmpty())
+ return;
+
+ text->clear();
+ const QString qtComponent = "Qt" + QString::number(QT_VERSION_MAJOR);
+ const QString findPackageText = "find_package(" + qtComponent + " REQUIRED COMPONENTS "
+ + cn->qtCMakeComponent() + ")";
+ const QString targetText = cn->qtCMakeTargetItem().isEmpty() ? cn->qtCMakeComponent() : cn->qtCMakeTargetItem();
+ const QString targetLinkLibrariesText = "target_link_libraries(mytarget PRIVATE "
+ + qtComponent + "::" + targetText + ")";
+ const Atom lineBreak = Atom(Atom::RawString, " <br/>\n");
+ *text << findPackageText << lineBreak << targetLinkLibrariesText;
+ requisites.insert(CMakeInfo, *text);
+ }
+}
+
+/*!
+ * \internal
+ * Adds the Qt variable (from the \\qtvariable command) to the map.
+ */
+void HtmlGenerator::addQtVariableToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites,
+ Text *text, const QString &qtVariableText) const
+{
+ if (!aggregate->physicalModuleName().isEmpty()) {
+ const CollectionNode *cn =
+ m_qdb->getCollectionNode(aggregate->physicalModuleName(), Node::Module);
+
+ if (cn && !cn->qtVariable().isEmpty()) {
+ text->clear();
+ *text << "QT += " + cn->qtVariable();
+ requisites.insert(qtVariableText, *text);
+ }
+ }
+}
+
+/*!
+ * \internal
+ * Adds the since information (from the \\since command) to the map.
+ *
+ */
+void HtmlGenerator::addSinceToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites,
+ Text *text, const QString &sinceText) const
+{
+ if (!aggregate->since().isEmpty() && text != nullptr) {
+ text->clear();
+ *text << formatSince(aggregate) << Atom::ParaRight;
+ requisites.insert(sinceText, *text);
+ }
+}
+
+/*!
+ * \internal
+ * Adds the status description for \a aggregate, together with a <span> element, to the \a
+ * requisites map.
+ *
+ * The span element can be used for adding CSS styling/icon associated with a specific status.
+ * The span class name is constructed by converting the description (sans \\deprecated
+ * version info) to lowercase and replacing all non-alphanum characters with hyphens. In
+ * addition, the span has a class \c status. For example,
+ * 'Tech Preview' -> class="status tech-preview"
+*/
+void HtmlGenerator::addStatusToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites,
+ Text &text, const QString &statusText) const
+{
+ auto status{formatStatus(aggregate, m_qdb)};
+ if (!status)
+ return;
+
+ QString spanClass;
+ if (aggregate->status() == Node::Deprecated)
+ spanClass = u"deprecated"_s; // Disregard any version info
+ else
+ spanClass = Utilities::asAsciiPrintable(status.value());
+
+ text.clear();
+ text << Atom(Atom::String, status.value())
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_SPAN +
+ "class=\"status %1\""_L1.arg(spanClass))
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_SPAN);
+ requisites.insert(statusText, text);
+}
+
+/*!
+ * \internal
+ * Adds the includes (from the \\includefile command) to the map.
+ */
+void HtmlGenerator::addIncludeFileToMap(const Aggregate *aggregate, CodeMarker *marker,
+ QMap<QString, Text> &requisites, Text& text,
+ const QString &headerText)
+{
+ if (aggregate->includeFile()) {
+ text.clear();
+ text << highlightedCode(
+ indent(m_codeIndent, marker->markedUpInclude(*aggregate->includeFile())),
+ aggregate
+ );
+
+ requisites.insert(headerText, text);
+ }
+}
+
+/*!
+Lists the required imports and includes in a table.
+The number of rows is known.
+*/
+void HtmlGenerator::generateQmlRequisites(QmlTypeNode *qcn, CodeMarker *marker)
+{
+ if (qcn == nullptr)
+ return;
+ QMap<QString, Text> requisites;
+ Text text;
+
+ const QString importText = "Import Statement:";
+ const QString sinceText = "Since:";
+ const QString inheritedBytext = "Inherited By:";
+ const QString inheritsText = "Inherits:";
+ const QString nativeTypeText = "In C++:";
+ const QString statusText = "Status:";
+
+ // add the module name and version to the map
+ QString logicalModuleVersion;
+ const CollectionNode *collection = qcn->logicalModule();
+
+ // skip import statement of \internal collections
+ if (!qcn->logicalModuleName().isEmpty() && (!collection || !collection->isInternal() || m_showInternal)) {
+ QStringList parts = QStringList() << "import" << qcn->logicalModuleName() << qcn->logicalModuleVersion();
+ text.clear();
+ text << parts.join(' ').trimmed();
+ requisites.insert(importText, text);
+ } else if (!qcn->isQmlBasicType() && qcn->logicalModuleName().isEmpty()) {
+ qcn->doc().location().warning(QStringLiteral("Could not resolve QML import statement for type '%1'").arg(qcn->name()),
+ QStringLiteral("Maybe you forgot to use the '\\%1' command?").arg(COMMAND_INQMLMODULE));
+ }
+
+ // add the since and project into the map
+ if (!qcn->since().isEmpty()) {
+ text.clear();
+ text << formatSince(qcn) << Atom::ParaRight;
+ requisites.insert(sinceText, text);
+ }
+
+ // add the native type to the map
+ ClassNode *cn = qcn->classNode();
+ if (cn && cn->isQmlNativeType() && !cn->isInternal()) {
+ text.clear();
+ text << Atom(Atom::LinkNode, CodeMarker::stringForNode(cn));
+ text << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK);
+ text << Atom(Atom::String, cn->name());
+ text << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK);
+ requisites.insert(nativeTypeText, text);
+ }
+
+ // add the inherits to the map
+ QmlTypeNode *base = qcn->qmlBaseNode();
+ while (base && base->isInternal()) {
+ base = base->qmlBaseNode();
+ }
+ if (base) {
+ text.clear();
+ text << Atom::ParaLeft << Atom(Atom::LinkNode, CodeMarker::stringForNode(base))
+ << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << Atom(Atom::String, base->name())
+ << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom::ParaRight;
+ requisites.insert(inheritsText, text);
+ }
+
+ // add the inherited-by to the map
+ NodeList subs;
+ QmlTypeNode::subclasses(qcn, subs);
+ if (!subs.isEmpty()) {
+ text.clear();
+ text << Atom::ParaLeft;
+ int count = appendSortedQmlNames(text, qcn, subs);
+ text << Atom::ParaRight;
+ if (count > 0)
+ requisites.insert(inheritedBytext, text);
+ }
+
+ // Add the state description (if any) to the map
+ addStatusToMap(qcn, requisites, text, statusText);
+
+ // The order of the requisites matter
+ const QStringList requisiteorder { importText, sinceText, nativeTypeText, inheritsText,
+ inheritedBytext, statusText };
+
+ if (!requisites.isEmpty()) {
+ // generate the table
+ out() << "<div class=\"table\"><table class=\"alignedsummary\" translate=\"no\">\n";
+ for (const auto &requisite : requisiteorder) {
+
+ if (requisites.contains(requisite)) {
+ out() << "<tr>"
+ << "<td class=\"memItemLeft rightAlign topAlign\"> " << requisite
+ << "</td><td class=\"memItemRight bottomAlign\"> ";
+
+ if (requisite == importText)
+ out() << requisites.value(requisite).toString();
+ else
+ generateText(requisites.value(requisite), qcn, marker);
+ out() << "</td></tr>";
+ }
+ }
+ out() << "</table></div>";
+ }
+}
+
+void HtmlGenerator::generateBrief(const Node *node, CodeMarker *marker, const Node *relative,
+ bool addLink)
+{
+ Text brief = node->doc().briefText();
+
+ if (!brief.isEmpty()) {
+ if (!brief.lastAtom()->string().endsWith('.')) {
+ brief << Atom(Atom::String, ".");
+ node->doc().location().warning(
+ QStringLiteral("'\\brief' statement does not end with a full stop."));
+ }
+ generateExtractionMark(node, BriefMark);
+ out() << "<p>";
+ generateText(brief, node, marker);
+
+ if (addLink) {
+ if (!relative || node == relative)
+ out() << " <a href=\"#";
+ else
+ out() << " <a href=\"" << linkForNode(node, relative) << '#';
+ out() << registerRef("details") << "\">More...</a>";
+ }
+
+ out() << "</p>\n";
+ generateExtractionMark(node, EndMark);
+ }
+}
+
+/*!
+ Revised for the new doc format.
+ Generates a table of contents beginning at \a node.
+ */
+void HtmlGenerator::generateTableOfContents(const Node *node, CodeMarker *marker,
+ QList<Section> *sections)
+{
+ QList<Atom *> toc;
+ if (node->doc().hasTableOfContents())
+ toc = node->doc().tableOfContents();
+ if (tocDepth == 0 || (toc.isEmpty() && !sections && !node->isModule())) {
+ generateSidebar();
+ return;
+ }
+
+ int sectionNumber = 1;
+ int detailsBase = 0;
+
+ // disable nested links in table of contents
+ m_inContents = true;
+
+ out() << "<div class=\"sidebar\">\n";
+ out() << "<div class=\"toc\">\n";
+ out() << "<h3 id=\"toc\">Contents</h3>\n";
+
+ if (node->isModule()) {
+ openUnorderedList();
+ if (!static_cast<const CollectionNode *>(node)->noAutoList()) {
+ if (node->hasNamespaces()) {
+ out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
+ << registerRef("namespaces") << "\">Namespaces</a></li>\n";
+ }
+ if (node->hasClasses()) {
+ out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
+ << registerRef("classes") << "\">Classes</a></li>\n";
+ }
+ }
+ out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#" << registerRef("details")
+ << "\">Detailed Description</a></li>\n";
+ for (const auto &entry : std::as_const(toc)) {
+ if (entry->string().toInt() == 1) {
+ detailsBase = 1;
+ break;
+ }
+ }
+ } else if (sections && (node->isClassNode() || node->isNamespace() || node->isQmlType())) {
+ for (const auto &section : std::as_const(*sections)) {
+ if (!section.members().isEmpty()) {
+ openUnorderedList();
+ out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
+ << registerRef(section.plural()) << "\">" << section.title() << "</a></li>\n";
+ }
+ if (!section.reimplementedMembers().isEmpty()) {
+ openUnorderedList();
+ QString ref = QString("Reimplemented ") + section.plural();
+ out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
+ << registerRef(ref.toLower()) << "\">"
+ << QString("Reimplemented ") + section.title() << "</a></li>\n";
+ }
+ }
+ if (!node->isNamespace() || node->hasDoc()) {
+ openUnorderedList();
+ out() << "<li class=\"level" << sectionNumber << "\"><a href=\"#"
+ << registerRef("details") << "\">Detailed Description</a></li>\n";
+ }
+ for (const auto &entry : toc) {
+ if (entry->string().toInt() == 1) {
+ detailsBase = 1;
+ break;
+ }
+ }
+ }
+
+ for (const auto &atom : toc) {
+ sectionNumber = atom->string().toInt() + detailsBase;
+ // restrict the ToC depth to the one set by the HTML.tocdepth variable or
+ // print all levels if tocDepth is not set.
+ if (sectionNumber <= tocDepth || tocDepth < 0) {
+ openUnorderedList();
+ int numAtoms;
+ Text headingText = Text::sectionHeading(atom);
+ out() << "<li class=\"level" << sectionNumber << "\">";
+ out() << "<a href=\"" << '#' << Tree::refForAtom(atom) << "\">";
+ generateAtomList(headingText.firstAtom(), node, marker, true, numAtoms);
+ out() << "</a></li>\n";
+ }
+ }
+ closeUnorderedList();
+ out() << "</div>\n";
+ out() << R"(<div class="sidebar-content" id="sidebar-content"></div>)";
+ out() << "</div>\n";
+ m_inContents = false;
+ m_inLink = false;
+}
+
+/*!
+ Outputs a placeholder div where the style can add customized sidebar content.
+ */
+void HtmlGenerator::generateSidebar()
+{
+ out() << "<div class=\"sidebar\">";
+ out() << R"(<div class="sidebar-content" id="sidebar-content"></div>)";
+ out() << "</div>\n";
+}
+
+QString HtmlGenerator::generateAllMembersFile(const Section &section, CodeMarker *marker)
+{
+ if (section.isEmpty())
+ return QString();
+
+ const Aggregate *aggregate = section.aggregate();
+ QString fileName = fileBase(aggregate) + "-members." + fileExtension();
+ beginSubPage(aggregate, fileName);
+ QString title = "List of All Members for " + aggregate->name();
+ generateHeader(title, aggregate, marker);
+ generateSidebar();
+ generateTitle(title, Text(), SmallSubTitle, aggregate, marker);
+ out() << "<p>This is the complete list of members for ";
+ generateFullName(aggregate, nullptr);
+ out() << ", including inherited members.</p>\n";
+
+ generateSectionList(section, aggregate, marker);
+
+ generateFooter();
+ endSubPage();
+ return fileName;
+}
+
+/*!
+ This function creates an html page on which are listed all
+ the members of the QML class used to generte the \a sections,
+ including the inherited members. The \a marker is used for
+ formatting stuff.
+ */
+QString HtmlGenerator::generateAllQmlMembersFile(const Sections &sections, CodeMarker *marker)
+{
+
+ if (sections.allMembersSection().isEmpty())
+ return QString();
+
+ const Aggregate *aggregate = sections.aggregate();
+ QString fileName = fileBase(aggregate) + "-members." + fileExtension();
+ beginSubPage(aggregate, fileName);
+ QString title = "List of All Members for " + aggregate->name();
+ generateHeader(title, aggregate, marker);
+ generateSidebar();
+ generateTitle(title, Text(), SmallSubTitle, aggregate, marker);
+ out() << "<p>This is the complete list of members for ";
+ generateFullName(aggregate, nullptr);
+ out() << ", including inherited members.</p>\n";
+
+ ClassNodesList &cknl = sections.allMembersSection().classNodesList();
+ for (int i = 0; i < cknl.size(); i++) {
+ ClassNodes ckn = cknl[i];
+ const QmlTypeNode *qcn = ckn.first;
+ NodeVector &nodes = ckn.second;
+ if (nodes.isEmpty())
+ continue;
+ if (i != 0) {
+ out() << "<p>The following members are inherited from ";
+ generateFullName(qcn, nullptr);
+ out() << ".</p>\n";
+ }
+ openUnorderedList();
+ for (int j = 0; j < nodes.size(); j++) {
+ Node *node = nodes[j];
+ if (node->access() == Access::Private || node->isInternal())
+ continue;
+ if (node->isSharingComment() && node->sharedCommentNode()->isPropertyGroup())
+ continue;
+
+ std::function<void(Node *)> generate = [&](Node *n) {
+ out() << "<li class=\"fn\" translate=\"no\">";
+ generateQmlItem(n, aggregate, marker, true);
+ if (n->isDefault())
+ out() << " [default]";
+ else if (n->isAttached())
+ out() << " [attached]";
+ // Indent property group members
+ if (n->isPropertyGroup()) {
+ out() << "<ul>\n";
+ const QList<Node *> &collective =
+ static_cast<SharedCommentNode *>(n)->collective();
+ std::for_each(collective.begin(), collective.end(), generate);
+ out() << "</ul>\n";
+ }
+ out() << "</li>\n";
+ };
+ generate(node);
+ }
+ closeUnorderedList();
+ }
+
+
+ generateFooter();
+ endSubPage();
+ return fileName;
+}
+
+QString HtmlGenerator::generateObsoleteMembersFile(const Sections &sections, CodeMarker *marker)
+{
+ SectionPtrVector summary_spv;
+ SectionPtrVector details_spv;
+ if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
+ return QString();
+
+ Aggregate *aggregate = sections.aggregate();
+ QString title = "Obsolete Members for " + aggregate->name();
+ QString fileName = fileBase(aggregate) + "-obsolete." + fileExtension();
+
+ beginSubPage(aggregate, fileName);
+ generateHeader(title, aggregate, marker);
+ generateSidebar();
+ generateTitle(title, Text(), SmallSubTitle, aggregate, marker);
+
+ out() << "<p><b>The following members of class "
+ << "<a href=\"" << linkForNode(aggregate, nullptr) << "\" translate=\"no\">"
+ << protectEnc(aggregate->name()) << "</a>"
+ << " are deprecated.</b> "
+ << "They are provided to keep old source code working. "
+ << "We strongly advise against using them in new code.</p>\n";
+
+ for (const auto &section : summary_spv) {
+ out() << "<h2>" << protectEnc(section->title()) << "</h2>\n";
+ generateSectionList(*section, aggregate, marker, true);
+ }
+
+ for (const auto &section : details_spv) {
+ out() << "<h2>" << protectEnc(section->title()) << "</h2>\n";
+
+ const NodeVector &members = section->obsoleteMembers();
+ for (const auto &member : members) {
+ if (member->access() != Access::Private)
+ generateDetailedMember(member, aggregate, marker);
+ }
+ }
+
+ generateFooter();
+ endSubPage();
+ return fileName;
+}
+
+/*!
+ Generates a separate file where deprecated members of the QML
+ type \a qcn are listed. The \a marker is used to generate
+ the section lists, which are then traversed and output here.
+ */
+QString HtmlGenerator::generateObsoleteQmlMembersFile(const Sections &sections, CodeMarker *marker)
+{
+ SectionPtrVector summary_spv;
+ SectionPtrVector details_spv;
+ if (!sections.hasObsoleteMembers(&summary_spv, &details_spv))
+ return QString();
+
+ Aggregate *aggregate = sections.aggregate();
+ QString title = "Obsolete Members for " + aggregate->name();
+ QString fileName = fileBase(aggregate) + "-obsolete." + fileExtension();
+
+ beginSubPage(aggregate, fileName);
+ generateHeader(title, aggregate, marker);
+ generateSidebar();
+ generateTitle(title, Text(), SmallSubTitle, aggregate, marker);
+
+ out() << "<p><b>The following members of QML type "
+ << "<a href=\"" << linkForNode(aggregate, nullptr) << "\">"
+ << protectEnc(aggregate->name()) << "</a>"
+ << " are deprecated.</b> "
+ << "They are provided to keep old source code working. "
+ << "We strongly advise against using them in new code.</p>\n";
+
+ for (const auto &section : summary_spv) {
+ QString ref = registerRef(section->title().toLower());
+ out() << "<h2 id=\"" << ref << "\">" << protectEnc(section->title()) << "</h2>\n";
+ generateQmlSummary(section->obsoleteMembers(), aggregate, marker);
+ }
+
+ for (const auto &section : details_spv) {
+ out() << "<h2>" << protectEnc(section->title()) << "</h2>\n";
+ const NodeVector &members = section->obsoleteMembers();
+ for (const auto &member : members) {
+ generateDetailedQmlMember(member, aggregate, marker);
+ out() << "<br/>\n";
+ }
+ }
+
+ generateFooter();
+ endSubPage();
+ return fileName;
+}
+
+void HtmlGenerator::generateClassHierarchy(const Node *relative, NodeMultiMap &classMap)
+{
+ if (classMap.isEmpty())
+ return;
+
+ NodeMap topLevel;
+ for (const auto &it : classMap) {
+ auto *classe = static_cast<ClassNode *>(it);
+ if (classe->baseClasses().isEmpty())
+ topLevel.insert(classe->name(), classe);
+ }
+
+ QStack<NodeMap> stack;
+ stack.push(topLevel);
+
+ out() << "<ul>\n";
+ while (!stack.isEmpty()) {
+ if (stack.top().isEmpty()) {
+ stack.pop();
+ out() << "</ul>\n";
+ } else {
+ ClassNode *child = static_cast<ClassNode *>(*stack.top().begin());
+ out() << "<li>";
+ generateFullName(child, relative);
+ out() << "</li>\n";
+ stack.top().erase(stack.top().begin());
+
+ NodeMap newTop;
+ const auto derivedClasses = child->derivedClasses();
+ for (const RelatedClass &d : derivedClasses) {
+ if (d.m_node && d.m_node->isInAPI())
+ newTop.insert(d.m_node->name(), d.m_node);
+ }
+ if (!newTop.isEmpty()) {
+ stack.push(newTop);
+ out() << "<ul>\n";
+ }
+ }
+ }
+}
+
+/*!
+ Outputs an annotated list of the nodes in \a unsortedNodes.
+ A two-column table is output.
+ */
+void HtmlGenerator::generateAnnotatedList(const Node *relative, CodeMarker *marker,
+ const NodeList &unsortedNodes)
+{
+ if (unsortedNodes.isEmpty() || relative == nullptr)
+ return;
+
+ NodeMultiMap nmm;
+ bool allInternal = true;
+ for (auto *node : unsortedNodes) {
+ if (!node->isInternal() && !node->isDeprecated()) {
+ allInternal = false;
+ nmm.insert(node->fullName(relative), node);
+ }
+ }
+ if (allInternal)
+ return;
+ out() << "<div class=\"table\"><table class=\"annotated\">\n";
+ int row = 0;
+ NodeList nodes = nmm.values();
+ std::sort(nodes.begin(), nodes.end(), Node::nodeNameLessThan);
+
+ for (const auto *node : std::as_const(nodes)) {
+ if (++row % 2 == 1)
+ out() << "<tr class=\"odd topAlign\">";
+ else
+ out() << "<tr class=\"even topAlign\">";
+ out() << "<td class=\"tblName\" translate=\"no\"><p>";
+ generateFullName(node, relative);
+ out() << "</p></td>";
+
+ if (!node->isTextPageNode()) {
+ Text brief = node->doc().trimmedBriefText(node->name());
+ if (!brief.isEmpty()) {
+ out() << "<td class=\"tblDescr\"><p>";
+ generateText(brief, node, marker);
+ out() << "</p></td>";
+ } else if (!node->reconstitutedBrief().isEmpty()) {
+ out() << "<td class=\"tblDescr\"><p>";
+ out() << node->reconstitutedBrief();
+ out() << "</p></td>";
+ }
+ } else {
+ out() << "<td class=\"tblDescr\"><p>";
+ if (!node->reconstitutedBrief().isEmpty()) {
+ out() << node->reconstitutedBrief();
+ } else
+ out() << protectEnc(node->doc().briefText().toString());
+ out() << "</p></td>";
+ }
+ out() << "</tr>\n";
+ }
+ out() << "</table></div>\n";
+}
+
+/*!
+ Outputs a series of annotated lists from the nodes in \a nmm,
+ divided into sections based by the key names in the multimap.
+ */
+void HtmlGenerator::generateAnnotatedLists(const Node *relative, CodeMarker *marker,
+ const NodeMultiMap &nmm)
+{
+ const auto &uniqueKeys = nmm.uniqueKeys();
+ for (const QString &name : uniqueKeys) {
+ if (!name.isEmpty()) {
+ out() << "<h2 id=\"" << registerRef(name.toLower()) << "\">" << protectEnc(name)
+ << "</h2>\n";
+ }
+ generateAnnotatedList(relative, marker, nmm.values(name));
+ }
+}
+
+/*!
+ This function finds the common prefix of the names of all
+ the classes in the class map \a nmm and then generates a
+ compact list of the class names alphabetized on the part
+ of the name not including the common prefix. You can tell
+ the function to use \a commonPrefix as the common prefix,
+ but normally you let it figure it out itself by looking at
+ the name of the first and last classes in the class map
+ \a nmm.
+ */
+void HtmlGenerator::generateCompactList(ListType listType, const Node *relative,
+ const NodeMultiMap &nmm, bool includeAlphabet,
+ const QString &commonPrefix)
+{
+ if (nmm.isEmpty())
+ return;
+
+ const int NumParagraphs = 37; // '0' to '9', 'A' to 'Z', '_'
+ qsizetype commonPrefixLen = commonPrefix.size();
+
+ /*
+ Divide the data into 37 paragraphs: 0, ..., 9, A, ..., Z,
+ underscore (_). QAccel will fall in paragraph 10 (A) and
+ QXtWidget in paragraph 33 (X). This is the only place where we
+ assume that NumParagraphs is 37. Each paragraph is a NodeMultiMap.
+ */
+ NodeMultiMap paragraph[NumParagraphs + 1];
+ QString paragraphName[NumParagraphs + 1];
+ QSet<char> usedParagraphNames;
+
+ for (auto c = nmm.constBegin(); c != nmm.constEnd(); ++c) {
+ QStringList pieces = c.key().split("::");
+ int idx = commonPrefixLen;
+ if (idx > 0 && !pieces.last().startsWith(commonPrefix, Qt::CaseInsensitive))
+ idx = 0;
+ QString last = pieces.last().toLower();
+ QString key = last.mid(idx);
+
+ int paragraphNr = NumParagraphs - 1;
+
+ if (key[0].digitValue() != -1) {
+ paragraphNr = key[0].digitValue();
+ } else if (key[0] >= QLatin1Char('a') && key[0] <= QLatin1Char('z')) {
+ paragraphNr = 10 + key[0].unicode() - 'a';
+ }
+
+ paragraphName[paragraphNr] = key[0].toUpper();
+ usedParagraphNames.insert(key[0].toLower().cell());
+ paragraph[paragraphNr].insert(last, c.value());
+ }
+
+ /*
+ Each paragraph j has a size: paragraph[j].count(). In the
+ discussion, we will assume paragraphs 0 to 5 will have sizes
+ 3, 1, 4, 1, 5, 9.
+
+ We now want to compute the paragraph offset. Paragraphs 0 to 6
+ start at offsets 0, 3, 4, 8, 9, 14, 23.
+ */
+ qsizetype paragraphOffset[NumParagraphs + 1]; // 37 + 1
+ paragraphOffset[0] = 0;
+ for (int i = 0; i < NumParagraphs; i++) // i = 0..36
+ paragraphOffset[i + 1] = paragraphOffset[i] + paragraph[i].size();
+
+ /*
+ Output the alphabet as a row of links.
+ */
+ if (includeAlphabet) {
+ out() << "<p class=\"centerAlign functionIndex\" translate=\"no\"><b>";
+ for (int i = 0; i < 26; i++) {
+ QChar ch('a' + i);
+ if (usedParagraphNames.contains(char('a' + i)))
+ out() << QString("<a href=\"#%1\">%2</a>&nbsp;").arg(ch).arg(ch.toUpper());
+ }
+ out() << "</b></p>\n";
+ }
+
+ /*
+ Output a <div> element to contain all the <dl> elements.
+ */
+ out() << "<div class=\"flowListDiv\" translate=\"no\">\n";
+ m_numTableRows = 0;
+
+ int curParNr = 0;
+ int curParOffset = 0;
+ QString previousName;
+ bool multipleOccurrences = false;
+
+ for (int i = 0; i < nmm.size(); i++) {
+ while ((curParNr < NumParagraphs) && (curParOffset == paragraph[curParNr].size())) {
+ ++curParNr;
+ curParOffset = 0;
+ }
+
+ /*
+ Starting a new paragraph means starting a new <dl>.
+ */
+ if (curParOffset == 0) {
+ if (i > 0)
+ out() << "</dl>\n";
+ if (++m_numTableRows % 2 == 1)
+ out() << "<dl class=\"flowList odd\">";
+ else
+ out() << "<dl class=\"flowList even\">";
+ out() << "<dt class=\"alphaChar\"";
+ if (includeAlphabet)
+ out() << QString(" id=\"%1\"").arg(paragraphName[curParNr][0].toLower());
+ out() << "><b>" << paragraphName[curParNr] << "</b></dt>\n";
+ }
+
+ /*
+ Output a <dd> for the current offset in the current paragraph.
+ */
+ out() << "<dd>";
+ if ((curParNr < NumParagraphs) && !paragraphName[curParNr].isEmpty()) {
+ NodeMultiMap::Iterator it;
+ NodeMultiMap::Iterator next;
+ it = paragraph[curParNr].begin();
+ for (int j = 0; j < curParOffset; j++)
+ ++it;
+
+ if (listType == Generic) {
+ /*
+ Previously, we used generateFullName() for this, but we
+ require some special formatting.
+ */
+ out() << "<a href=\"" << linkForNode(it.value(), relative) << "\">";
+ } else if (listType == Obsolete) {
+ QString fileName = fileBase(it.value()) + "-obsolete." + fileExtension();
+ QString link;
+ if (useOutputSubdirs())
+ link = "../%1/"_L1.arg(it.value()->tree()->physicalModuleName());
+ link += fileName;
+ out() << "<a href=\"" << link << "\">";
+ }
+
+ QStringList pieces{it.value()->fullName(relative).split("::"_L1)};
+ const auto &name{pieces.last()};
+ next = it;
+ ++next;
+ if (name != previousName)
+ multipleOccurrences = false;
+ if ((next != paragraph[curParNr].end()) && (name == next.value()->name())) {
+ multipleOccurrences = true;
+ previousName = name;
+ }
+ if (multipleOccurrences && pieces.size() == 1)
+ pieces.last().append(": %1"_L1.arg(it.value()->tree()->camelCaseModuleName()));
+
+ out() << protectEnc(pieces.last());
+ out() << "</a>";
+ if (pieces.size() > 1) {
+ out() << " (";
+ generateFullName(it.value()->parent(), relative);
+ out() << ')';
+ }
+ }
+ out() << "</dd>\n";
+ curParOffset++;
+ }
+ if (nmm.size() > 0)
+ out() << "</dl>\n";
+
+ out() << "</div>\n";
+}
+
+void HtmlGenerator::generateFunctionIndex(const Node *relative)
+{
+ out() << "<p class=\"centerAlign functionIndex\" translate=\"no\"><b>";
+ for (int i = 0; i < 26; i++) {
+ QChar ch('a' + i);
+ out() << QString("<a href=\"#%1\">%2</a>&nbsp;").arg(ch).arg(ch.toUpper());
+ }
+ out() << "</b></p>\n";
+
+ char nextLetter = 'a';
+
+ out() << "<ul translate=\"no\">\n";
+ NodeMapMap &funcIndex = m_qdb->getFunctionIndex();
+ for (auto fnMap = funcIndex.constBegin(); fnMap != funcIndex.constEnd(); ++fnMap) {
+ const QString &key = fnMap.key();
+ const QChar firstLetter = key.isEmpty() ? QChar('A') : key.front();
+ Q_ASSERT_X(firstLetter.unicode() < 256, "generateFunctionIndex",
+ "Only valid C++ identifiers were expected");
+ const char currentLetter = firstLetter.isLower() ? firstLetter.unicode() : nextLetter - 1;
+
+ if (currentLetter < nextLetter) {
+ out() << "<li>";
+ } else {
+ // TODO: This is not covered by our tests
+ while (nextLetter < currentLetter)
+ out() << QStringLiteral("<li id=\"%1\"></li>").arg(nextLetter++);
+ Q_ASSERT(nextLetter == currentLetter);
+ out() << QStringLiteral("<li id=\"%1\">").arg(nextLetter++);
+ }
+ out() << protectEnc(key) << ':';
+
+ for (auto it = (*fnMap).constBegin(); it != (*fnMap).constEnd(); ++it) {
+ out() << ' ';
+ generateFullName((*it)->parent(), relative, *it);
+ }
+ out() << "</li>\n";
+ }
+ while (nextLetter <= 'z')
+ out() << QStringLiteral("<li id=\"%1\"></li>").arg(nextLetter++);
+ out() << "</ul>\n";
+}
+
+void HtmlGenerator::generateLegaleseList(const Node *relative, CodeMarker *marker)
+{
+ TextToNodeMap &legaleseTexts = m_qdb->getLegaleseTexts();
+ for (auto it = legaleseTexts.cbegin(), end = legaleseTexts.cend(); it != end; ++it) {
+ Text text = it.key();
+ generateText(text, relative, marker);
+ out() << "<ul>\n";
+ do {
+ out() << "<li>";
+ generateFullName(it.value(), relative);
+ out() << "</li>\n";
+ ++it;
+ } while (it != legaleseTexts.constEnd() && it.key() == text);
+ out() << "</ul>\n";
+ }
+}
+
+void HtmlGenerator::generateQmlItem(const Node *node, const Node *relative, CodeMarker *marker,
+ bool summary)
+{
+ QString marked = marker->markedUpQmlItem(node, summary);
+ marked.replace("@param>", "i>");
+
+ marked.replace("<@extra>", "<code class=\"%1 extra\" translate=\"no\">"_L1
+ .arg(summary ? "summary"_L1 : "details"_L1));
+ marked.replace("</@extra>", "</code>");
+
+
+ if (summary) {
+ marked.remove("<@name>");
+ marked.remove("</@name>");
+ marked.remove("<@type>");
+ marked.remove("</@type>");
+ }
+ out() << highlightedCode(marked, relative, false, Node::QML);
+}
+
+/*!
+ This function generates a simple unordered list for the members
+ of collection node \a {cn}. Returns \c true if the list was
+ generated (collection has members), \c false otherwise.
+ */
+bool HtmlGenerator::generateGroupList(CollectionNode *cn)
+{
+ m_qdb->mergeCollections(cn);
+ if (cn->members().isEmpty())
+ return false;
+
+ NodeList members{cn->members()};
+ std::sort(members.begin(), members.end(), Node::nodeNameLessThan);
+ out() << "<ul>\n";
+ for (const auto *node : std::as_const(members)) {
+ out() << "<li translate=\"no\">";
+ generateFullName(node, nullptr);
+ out() << "</li>\n";
+ }
+ out() << "</ul>\n";
+ return true;
+}
+
+void HtmlGenerator::generateList(const Node *relative, CodeMarker *marker, const QString &selector)
+{
+ CNMap cnm;
+ Node::NodeType type = Node::NoType;
+ if (selector == QLatin1String("overviews"))
+ type = Node::Group;
+ else if (selector == QLatin1String("cpp-modules"))
+ type = Node::Module;
+ else if (selector == QLatin1String("qml-modules"))
+ type = Node::QmlModule;
+ if (type != Node::NoType) {
+ NodeList nodeList;
+ m_qdb->mergeCollections(type, cnm, relative);
+ const auto collectionList = cnm.values();
+ nodeList.reserve(collectionList.size());
+ for (auto *collectionNode : collectionList)
+ nodeList.append(collectionNode);
+ generateAnnotatedList(relative, marker, nodeList);
+ } else {
+ /*
+ \generatelist {selector} is only allowed in a
+ comment where the topic is \group, \module, or
+ \qmlmodule.
+ */
+ if (relative && !relative->isCollectionNode()) {
+ relative->doc().location().warning(
+ QStringLiteral("\\generatelist {%1} is only allowed in \\group, "
+ "\\module and \\qmlmodule comments.")
+ .arg(selector));
+ return;
+ }
+ auto *node = const_cast<Node *>(relative);
+ auto *collectionNode = static_cast<CollectionNode *>(node);
+ m_qdb->mergeCollections(collectionNode);
+ generateAnnotatedList(collectionNode, marker, collectionNode->members());
+ }
+}
+
+void HtmlGenerator::generateSection(const NodeVector &nv, const Node *relative, CodeMarker *marker)
+{
+ bool alignNames = true;
+ if (!nv.isEmpty()) {
+ bool twoColumn = false;
+ if (nv.first()->isProperty()) {
+ twoColumn = (nv.size() >= 5);
+ alignNames = false;
+ }
+ if (alignNames) {
+ out() << "<div class=\"table\"><table class=\"alignedsummary\" translate=\"no\">\n";
+ } else {
+ if (twoColumn)
+ out() << "<div class=\"table\"><table class=\"propsummary\" translate=\"no\">\n"
+ << "<tr><td class=\"topAlign\">";
+ out() << "<ul>\n";
+ }
+
+ int i = 0;
+ for (const auto &member : nv) {
+ if (member->access() == Access::Private)
+ continue;
+
+ if (alignNames) {
+ out() << "<tr><td class=\"memItemLeft rightAlign topAlign\"> ";
+ } else {
+ if (twoColumn && i == (nv.size() + 1) / 2)
+ out() << "</ul></td><td class=\"topAlign\"><ul>\n";
+ out() << "<li class=\"fn\" translate=\"no\">";
+ }
+
+ generateSynopsis(member, relative, marker, Section::Summary, alignNames);
+ if (alignNames)
+ out() << "</td></tr>\n";
+ else
+ out() << "</li>\n";
+ i++;
+ }
+ if (alignNames)
+ out() << "</table></div>\n";
+ else {
+ out() << "</ul>\n";
+ if (twoColumn)
+ out() << "</td></tr>\n</table></div>\n";
+ }
+ }
+}
+
+void HtmlGenerator::generateSectionList(const Section &section, const Node *relative,
+ CodeMarker *marker, bool useObsoleteMembers)
+{
+ bool alignNames = true;
+ const NodeVector &members =
+ (useObsoleteMembers ? section.obsoleteMembers() : section.members());
+ if (!members.isEmpty()) {
+ bool hasPrivateSignals = false;
+ bool isInvokable = false;
+ bool twoColumn = false;
+ if (section.style() == Section::AllMembers) {
+ alignNames = false;
+ twoColumn = (members.size() >= 16);
+ } else if (members.first()->isProperty()) {
+ twoColumn = (members.size() >= 5);
+ alignNames = false;
+ }
+ if (alignNames) {
+ out() << "<div class=\"table\"><table class=\"alignedsummary\" translate=\"no\">\n";
+ } else {
+ if (twoColumn)
+ out() << "<div class=\"table\"><table class=\"propsummary\" translate=\"no\">\n"
+ << "<tr><td class=\"topAlign\">";
+ out() << "<ul>\n";
+ }
+
+ int i = 0;
+ for (const auto &member : members) {
+ if (member->access() == Access::Private)
+ continue;
+
+ if (alignNames) {
+ out() << "<tr><td class=\"memItemLeft topAlign rightAlign\"> ";
+ } else {
+ if (twoColumn && i == (members.size() + 1) / 2)
+ out() << "</ul></td><td class=\"topAlign\"><ul>\n";
+ out() << "<li class=\"fn\" translate=\"no\">";
+ }
+
+ generateSynopsis(member, relative, marker, section.style(), alignNames);
+ if (member->isFunction()) {
+ const auto *fn = static_cast<const FunctionNode *>(member);
+ if (fn->isPrivateSignal()) {
+ hasPrivateSignals = true;
+ if (alignNames)
+ out() << "</td><td class=\"memItemRight bottomAlign\">[see note below]";
+ } else if (fn->isInvokable()) {
+ isInvokable = true;
+ if (alignNames)
+ out() << "</td><td class=\"memItemRight bottomAlign\">[see note below]";
+ }
+ }
+ if (alignNames)
+ out() << "</td></tr>\n";
+ else
+ out() << "</li>\n";
+ i++;
+ }
+ if (alignNames)
+ out() << "</table></div>\n";
+ else {
+ out() << "</ul>\n";
+ if (twoColumn)
+ out() << "</td></tr>\n</table></div>\n";
+ }
+ if (alignNames) {
+ if (hasPrivateSignals)
+ generateAddendum(relative, Generator::PrivateSignal, marker);
+ if (isInvokable)
+ generateAddendum(relative, Generator::Invokable, marker);
+ }
+ }
+
+ if (!useObsoleteMembers && section.style() == Section::Summary
+ && !section.inheritedMembers().isEmpty()) {
+ out() << "<ul>\n";
+ generateSectionInheritedList(section, relative);
+ out() << "</ul>\n";
+ }
+}
+
+void HtmlGenerator::generateSectionInheritedList(const Section &section, const Node *relative)
+{
+ const QList<std::pair<Aggregate *, int>> &inheritedMembers = section.inheritedMembers();
+ for (const auto &member : inheritedMembers) {
+ out() << "<li class=\"fn\" translate=\"no\">";
+ out() << member.second << ' ';
+ if (member.second == 1) {
+ out() << section.singular();
+ } else {
+ out() << section.plural();
+ }
+ out() << " inherited from <a href=\"" << fileName(member.first) << '#'
+ << Generator::cleanRef(section.title().toLower()) << "\">"
+ << protectEnc(member.first->plainFullName(relative)) << "</a></li>\n";
+ }
+}
+
+void HtmlGenerator::generateSynopsis(const Node *node, const Node *relative, CodeMarker *marker,
+ Section::Style style, bool alignNames)
+{
+ QString marked = marker->markedUpSynopsis(node, relative, style);
+ marked.replace("@param>", "i>");
+
+ if (style == Section::Summary) {
+ marked.remove("<@name>");
+ marked.remove("</@name>");
+ }
+
+ if (style == Section::AllMembers) {
+ static const QRegularExpression extraRegExp("<@extra>.*</@extra>",
+ QRegularExpression::InvertedGreedinessOption);
+ marked.remove(extraRegExp);
+ } else {
+ marked.replace("<@extra>", "<code class=\"%1 extra\" translate=\"no\">"_L1
+ .arg(style == Section::Summary ? "summary"_L1 : "details"_L1));
+ marked.replace("</@extra>", "</code>");
+ }
+
+ if (style != Section::Details) {
+ marked.remove("<@type>");
+ marked.remove("</@type>");
+ }
+
+ out() << highlightedCode(marked, relative, alignNames);
+}
+
+QString HtmlGenerator::highlightedCode(const QString &markedCode, const Node *relative,
+ bool alignNames, Node::Genus genus)
+{
+ QString src = markedCode;
+ QString html;
+ html.reserve(src.size());
+ QStringView arg;
+ QStringView par1;
+
+ const QChar charLangle = '<';
+ const QChar charAt = '@';
+
+ static const QString typeTag("type");
+ static const QString headerTag("headerfile");
+ static const QString funcTag("func");
+ static const QString linkTag("link");
+
+ // replace all <@link> tags: "(<@link node=\"([^\"]+)\">).*(</@link>)"
+ // replace all <@func> tags: "(<@func target=\"([^\"]*)\">)(.*)(</@func>)"
+ // replace all "(<@(type|headerfile)(?: +[^>]*)?>)(.*)(</@\\2>)" tags
+ bool done = false;
+ for (int i = 0, srcSize = src.size(); i < srcSize;) {
+ if (src.at(i) == charLangle && src.at(i + 1) == charAt) {
+ if (alignNames && !done) {
+ html += QLatin1String("</td><td class=\"memItemRight bottomAlign\">");
+ done = true;
+ }
+ i += 2;
+ if (parseArg(src, linkTag, &i, srcSize, &arg, &par1)) {
+ html += QLatin1String("<b>");
+ const Node *n = CodeMarker::nodeForString(par1.toString());
+ QString link = linkForNode(n, relative);
+ addLink(link, arg, &html);
+ html += QLatin1String("</b>");
+ } else if (parseArg(src, funcTag, &i, srcSize, &arg, &par1)) {
+ const FunctionNode *fn = m_qdb->findFunctionNode(par1.toString(), relative, genus);
+ QString link = linkForNode(fn, relative);
+ addLink(link, arg, &html);
+ par1 = QStringView();
+ } else if (parseArg(src, typeTag, &i, srcSize, &arg, &par1)) {
+ par1 = QStringView();
+ const Node *n = m_qdb->findTypeNode(arg.toString(), relative, genus);
+ html += QLatin1String("<span class=\"type\">");
+ if (n && (n->isQmlBasicType())) {
+ if (relative && (relative->genus() == n->genus() || genus == n->genus()))
+ addLink(linkForNode(n, relative), arg, &html);
+ else
+ html += arg;
+ } else
+ addLink(linkForNode(n, relative), arg, &html);
+ html += QLatin1String("</span>");
+ } else if (parseArg(src, headerTag, &i, srcSize, &arg, &par1)) {
+ par1 = QStringView();
+ if (arg.startsWith(QLatin1Char('&')))
+ html += arg;
+ else {
+ const Node *n = m_qdb->findNodeForInclude(QStringList(arg.toString()));
+ if (n && n != relative)
+ addLink(linkForNode(n, relative), arg, &html);
+ else
+ html += arg;
+ }
+ } else {
+ html += charLangle;
+ html += charAt;
+ }
+ } else {
+ html += src.at(i++);
+ }
+ }
+
+ // replace all
+ // "<@comment>" -> "<span class=\"comment\">";
+ // "<@preprocessor>" -> "<span class=\"preprocessor\">";
+ // "<@string>" -> "<span class=\"string\">";
+ // "<@char>" -> "<span class=\"char\">";
+ // "<@number>" -> "<span class=\"number\">";
+ // "<@op>" -> "<span class=\"operator\">";
+ // "<@type>" -> "<span class=\"type\">";
+ // "<@name>" -> "<span class=\"name\">";
+ // "<@keyword>" -> "<span class=\"keyword\">";
+ // "</@(?:comment|preprocessor|string|char|number|op|type|name|keyword)>" -> "</span>"
+ src = html;
+ html = QString();
+ html.reserve(src.size());
+ static const QLatin1String spanTags[] = {
+ QLatin1String("comment>"), QLatin1String("<span class=\"comment\">"),
+ QLatin1String("preprocessor>"), QLatin1String("<span class=\"preprocessor\">"),
+ QLatin1String("string>"), QLatin1String("<span class=\"string\">"),
+ QLatin1String("char>"), QLatin1String("<span class=\"char\">"),
+ QLatin1String("number>"), QLatin1String("<span class=\"number\">"),
+ QLatin1String("op>"), QLatin1String("<span class=\"operator\">"),
+ QLatin1String("type>"), QLatin1String("<span class=\"type\">"),
+ QLatin1String("name>"), QLatin1String("<span class=\"name\">"),
+ QLatin1String("keyword>"), QLatin1String("<span class=\"keyword\">")
+ };
+ int nTags = 9;
+ // Update the upper bound of k in the following code to match the length
+ // of the above array.
+ for (int i = 0, n = src.size(); i < n;) {
+ if (src.at(i) == QLatin1Char('<')) {
+ if (src.at(i + 1) == QLatin1Char('@')) {
+ i += 2;
+ bool handled = false;
+ for (int k = 0; k != nTags; ++k) {
+ const QLatin1String &tag = spanTags[2 * k];
+ if (i + tag.size() <= src.size() && tag == QStringView(src).mid(i, tag.size())) {
+ html += spanTags[2 * k + 1];
+ i += tag.size();
+ handled = true;
+ break;
+ }
+ }
+ if (!handled) {
+ // drop 'our' unknown tags (the ones still containing '@')
+ while (i < n && src.at(i) != QLatin1Char('>'))
+ ++i;
+ ++i;
+ }
+ continue;
+ } else if (src.at(i + 1) == QLatin1Char('/') && src.at(i + 2) == QLatin1Char('@')) {
+ i += 3;
+ bool handled = false;
+ for (int k = 0; k != nTags; ++k) {
+ const QLatin1String &tag = spanTags[2 * k];
+ if (i + tag.size() <= src.size() && tag == QStringView(src).mid(i, tag.size())) {
+ html += QLatin1String("</span>");
+ i += tag.size();
+ handled = true;
+ break;
+ }
+ }
+ if (!handled) {
+ // drop 'our' unknown tags (the ones still containing '@')
+ while (i < n && src.at(i) != QLatin1Char('>'))
+ ++i;
+ ++i;
+ }
+ continue;
+ }
+ }
+ html += src.at(i);
+ ++i;
+ }
+ return html;
+}
+
+void HtmlGenerator::generateLink(const Atom *atom)
+{
+ Q_ASSERT(m_inLink);
+
+ if (m_linkNode && m_linkNode->isFunction()) {
+ auto match = XmlGenerator::m_funcLeftParen.match(atom->string());
+ if (match.hasMatch()) {
+ // C++: move () outside of link
+ qsizetype leftParenLoc = match.capturedStart(1);
+ out() << protectEnc(atom->string().left(leftParenLoc));
+ endLink();
+ out() << protectEnc(atom->string().mid(leftParenLoc));
+ return;
+ }
+ }
+ out() << protectEnc(atom->string());
+}
+
+QString HtmlGenerator::protectEnc(const QString &string)
+{
+ return protect(string);
+}
+
+QString HtmlGenerator::protect(const QString &string)
+{
+#define APPEND(x) \
+ if (html.isEmpty()) { \
+ html = string; \
+ html.truncate(i); \
+ } \
+ html += (x);
+
+ QString html;
+ qsizetype n = string.size();
+
+ for (int i = 0; i < n; ++i) {
+ QChar ch = string.at(i);
+
+ if (ch == QLatin1Char('&')) {
+ APPEND("&amp;");
+ } else if (ch == QLatin1Char('<')) {
+ APPEND("&lt;");
+ } else if (ch == QLatin1Char('>')) {
+ APPEND("&gt;");
+ } else if (ch == QChar(8211)) {
+ APPEND("&ndash;");
+ } else if (ch == QChar(8212)) {
+ APPEND("&mdash;");
+ } else if (ch == QLatin1Char('"')) {
+ APPEND("&quot;");
+ } else {
+ if (!html.isEmpty())
+ html += ch;
+ }
+ }
+
+ if (!html.isEmpty())
+ return html;
+ return string;
+
+#undef APPEND
+}
+
+QString HtmlGenerator::fileBase(const Node *node) const
+{
+ QString result = Generator::fileBase(node);
+ if (!node->isAggregate() && node->isDeprecated())
+ result += QLatin1String("-obsolete");
+ return result;
+}
+
+QString HtmlGenerator::fileName(const Node *node)
+{
+ if (node->isExternalPage())
+ return node->name();
+ return Generator::fileName(node);
+}
+
+void HtmlGenerator::generateFullName(const Node *apparentNode, const Node *relative,
+ const Node *actualNode)
+{
+ if (actualNode == nullptr)
+ actualNode = apparentNode;
+ bool link = !linkForNode(actualNode, relative).isEmpty();
+ if (link) {
+ out() << "<a href=\"" << linkForNode(actualNode, relative);
+ if (actualNode->isDeprecated())
+ out() << "\" class=\"obsolete";
+ out() << "\">";
+ }
+ out() << protectEnc(apparentNode->fullName(relative));
+ if (link)
+ out() << "</a>";
+}
+
+void HtmlGenerator::generateDetailedMember(const Node *node, const PageNode *relative,
+ CodeMarker *marker)
+{
+ const EnumNode *etn;
+ generateExtractionMark(node, MemberMark);
+ QString nodeRef = nullptr;
+ if (node->isSharedCommentNode()) {
+ const auto *scn = reinterpret_cast<const SharedCommentNode *>(node);
+ const QList<Node *> &collective = scn->collective();
+ if (collective.size() > 1)
+ out() << "<div class=\"fngroup\">\n";
+ for (const auto *sharedNode : collective) {
+ nodeRef = refForNode(sharedNode);
+ out() << R"(<h3 class="fn fngroupitem" translate="no" id=")" << nodeRef << "\">";
+ generateSynopsis(sharedNode, relative, marker, Section::Details);
+ out() << "</h3>";
+ }
+ if (collective.size() > 1)
+ out() << "</div>";
+ out() << '\n';
+ } else {
+ nodeRef = refForNode(node);
+ if (node->isEnumType() && (etn = static_cast<const EnumNode *>(node))->flagsType()) {
+ out() << R"(<h3 class="flags" id=")" << nodeRef << "\">";
+ generateSynopsis(etn, relative, marker, Section::Details);
+ out() << "<br/>";
+ generateSynopsis(etn->flagsType(), relative, marker, Section::Details);
+ out() << "</h3>\n";
+ } else {
+ out() << R"(<h3 class="fn" translate="no" id=")" << nodeRef << "\">";
+ generateSynopsis(node, relative, marker, Section::Details);
+ out() << "</h3>" << '\n';
+ }
+ }
+
+ generateStatus(node, marker);
+ generateBody(node, marker);
+ generateOverloadedSignal(node, marker);
+ generateComparisonCategory(node, marker);
+ generateThreadSafeness(node, marker);
+ generateSince(node, marker);
+ generateNoexceptNote(node, marker);
+
+ if (node->isProperty()) {
+ const auto property = static_cast<const PropertyNode *>(node);
+ if (property->propertyType() == PropertyNode::PropertyType::StandardProperty) {
+ Section section("", "", "", "", Section::Accessors);
+
+ section.appendMembers(property->getters().toVector());
+ section.appendMembers(property->setters().toVector());
+ section.appendMembers(property->resetters().toVector());
+
+ if (!section.members().isEmpty()) {
+ out() << "<p><b>Access functions:</b></p>\n";
+ generateSectionList(section, node, marker);
+ }
+
+ Section notifiers("", "", "", "", Section::Accessors);
+ notifiers.appendMembers(property->notifiers().toVector());
+
+ if (!notifiers.members().isEmpty()) {
+ out() << "<p><b>Notifier signal:</b></p>\n";
+ generateSectionList(notifiers, node, marker);
+ }
+ }
+ } else if (node->isEnumType()) {
+ const auto *enumTypeNode = static_cast<const EnumNode *>(node);
+ if (enumTypeNode->flagsType()) {
+ out() << "<p>The " << protectEnc(enumTypeNode->flagsType()->name())
+ << " type is a typedef for "
+ << "<a href=\"" << m_qflagsHref << "\">QFlags</a>&lt;"
+ << protectEnc(enumTypeNode->name()) << "&gt;. It stores an OR combination of "
+ << protectEnc(enumTypeNode->name()) << " values.</p>\n";
+ }
+ }
+ generateAlsoList(node, marker);
+ generateExtractionMark(node, EndMark);
+}
+
+/*!
+ This version of the function is called when outputting the link
+ to an example file or example image, where the \a link is known
+ to be correct.
+ */
+void HtmlGenerator::beginLink(const QString &link)
+{
+ m_link = link;
+ m_inLink = true;
+ m_linkNode = nullptr;
+
+ if (!m_link.isEmpty())
+ out() << "<a href=\"" << m_link << "\" translate=\"no\">";
+}
+
+void HtmlGenerator::beginLink(const QString &link, const Node *node, const Node *relative)
+{
+ m_link = link;
+ m_inLink = true;
+ m_linkNode = node;
+ if (m_link.isEmpty())
+ return;
+
+ const QString &translate_attr =
+ (node && node->genus() & Node::API) ? " translate=\"no\""_L1 : ""_L1;
+
+ if (node == nullptr || (relative != nullptr && node->status() == relative->status()))
+ out() << "<a href=\"" << m_link << "\"%1>"_L1.arg(translate_attr);
+ else if (node->isDeprecated())
+ out() << "<a href=\"" << m_link << "\" class=\"obsolete\"%1>"_L1.arg(translate_attr);
+ else
+ out() << "<a href=\"" << m_link << "\"%1>"_L1.arg(translate_attr);
+}
+
+void HtmlGenerator::endLink()
+{
+ if (!m_inLink)
+ return;
+
+ m_inLink = false;
+ m_linkNode = nullptr;
+
+ if (!m_link.isEmpty())
+ out() << "</a>";
+}
+
+/*!
+ Generates the summary list for the \a members. Only used for
+ sections of QML element documentation.
+ */
+void HtmlGenerator::generateQmlSummary(const NodeVector &members, const Node *relative,
+ CodeMarker *marker)
+{
+ if (!members.isEmpty()) {
+ out() << "<ul>\n";
+ for (const auto &member : members) {
+ out() << "<li class=\"fn\" translate=\"no\">";
+ generateQmlItem(member, relative, marker, true);
+ if (member->isPropertyGroup()) {
+ const auto *scn = static_cast<const SharedCommentNode *>(member);
+ if (scn->count() > 0) {
+ out() << "<ul>\n";
+ const QList<Node *> &sharedNodes = scn->collective();
+ for (const auto &node : sharedNodes) {
+ if (node->isQmlProperty()) {
+ out() << "<li class=\"fn\" translate=\"no\">";
+ generateQmlItem(node, relative, marker, true);
+ out() << "</li>\n";
+ }
+ }
+ out() << "</ul>\n";
+ }
+ }
+ out() << "</li>\n";
+ }
+ out() << "</ul>\n";
+ }
+}
+
+/*!
+ Outputs the html detailed documentation for a section
+ on a QML element reference page.
+ */
+void HtmlGenerator::generateDetailedQmlMember(Node *node, const Aggregate *relative,
+ CodeMarker *marker)
+{
+ generateExtractionMark(node, MemberMark);
+
+ QString qmlItemHeader("<div class=\"qmlproto\" translate=\"no\">\n"
+ "<div class=\"table\"><table class=\"qmlname\">\n");
+
+ QString qmlItemStart("<tr valign=\"top\" class=\"odd\" id=\"%1\">\n"
+ "<td class=\"%2\"><p>\n");
+ QString qmlItemEnd("</p></td></tr>\n");
+
+ QString qmlItemFooter("</table></div></div>\n");
+
+ auto generateQmlProperty = [&](Node *n) {
+ out() << qmlItemStart.arg(refForNode(n), "tblQmlPropNode");
+ generateQmlItem(n, relative, marker, false);
+ out() << qmlItemEnd;
+ };
+
+ auto generateQmlMethod = [&](Node *n) {
+ out() << qmlItemStart.arg(refForNode(n), "tblQmlFuncNode");
+ generateSynopsis(n, relative, marker, Section::Details, false);
+ out() << qmlItemEnd;
+ };
+
+ out() << "<div class=\"qmlitem\">";
+ if (node->isPropertyGroup()) {
+ const auto *scn = static_cast<const SharedCommentNode *>(node);
+ out() << qmlItemHeader;
+ if (!scn->name().isEmpty()) {
+ const QString nodeRef = refForNode(scn);
+ out() << R"(<tr valign="top" class="even" id=")" << nodeRef << "\">";
+ out() << "<th class=\"centerAlign\"><p>";
+ out() << "<b>" << scn->name() << " group</b>";
+ out() << "</p></th></tr>\n";
+ }
+ const QList<Node *> sharedNodes = scn->collective();
+ for (const auto &sharedNode : sharedNodes) {
+ if (sharedNode->isQmlProperty())
+ generateQmlProperty(sharedNode);
+ }
+ out() << qmlItemFooter;
+ } else if (node->isQmlProperty()) {
+ out() << qmlItemHeader;
+ generateQmlProperty(node);
+ out() << qmlItemFooter;
+ } else if (node->isSharedCommentNode()) {
+ const auto *scn = reinterpret_cast<const SharedCommentNode *>(node);
+ const QList<Node *> &sharedNodes = scn->collective();
+ if (sharedNodes.size() > 1)
+ out() << "<div class=\"fngroup\">\n";
+ out() << qmlItemHeader;
+ for (const auto &sharedNode : sharedNodes) {
+ // Generate the node only if it's a QML method
+ if (sharedNode->isFunction(Node::QML))
+ generateQmlMethod(sharedNode);
+ else if (sharedNode->isQmlProperty())
+ generateQmlProperty(sharedNode);
+ }
+ out() << qmlItemFooter;
+ if (sharedNodes.size() > 1)
+ out() << "</div>"; // fngroup
+ } else { // assume the node is a method/signal handler
+ out() << qmlItemHeader;
+ generateQmlMethod(node);
+ out() << qmlItemFooter;
+ }
+
+ out() << "<div class=\"qmldoc\">";
+ generateStatus(node, marker);
+ generateBody(node, marker);
+ generateThreadSafeness(node, marker);
+ generateSince(node, marker);
+ generateAlsoList(node, marker);
+ out() << "</div></div>";
+ generateExtractionMark(node, EndMark);
+}
+
+void HtmlGenerator::generateExtractionMark(const Node *node, ExtractionMarkType markType)
+{
+ if (markType != EndMark) {
+ out() << "<!-- $$$" + node->name();
+ if (markType == MemberMark) {
+ if (node->isFunction()) {
+ const auto *func = static_cast<const FunctionNode *>(node);
+ if (!func->hasAssociatedProperties()) {
+ if (func->overloadNumber() == 0)
+ out() << "[overload1]";
+ out() << "$$$" + func->name() + func->parameters().rawSignature().remove(' ');
+ }
+ } else if (node->isProperty()) {
+ out() << "-prop";
+ const auto *prop = static_cast<const PropertyNode *>(node);
+ const NodeList &list = prop->functions();
+ for (const auto *propFuncNode : list) {
+ if (propFuncNode->isFunction()) {
+ const auto *func = static_cast<const FunctionNode *>(propFuncNode);
+ out() << "$$$" + func->name()
+ + func->parameters().rawSignature().remove(' ');
+ }
+ }
+ } else if (node->isEnumType()) {
+ const auto *enumNode = static_cast<const EnumNode *>(node);
+ const auto &items = enumNode->items();
+ for (const auto &item : items)
+ out() << "$$$" + item.name();
+ }
+ } else if (markType == BriefMark) {
+ out() << "-brief";
+ } else if (markType == DetailedDescriptionMark) {
+ out() << "-description";
+ }
+ out() << " -->\n";
+ } else {
+ out() << "<!-- @@@" + node->name() + " -->\n";
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/htmlgenerator.h b/src/qdoc/qdoc/src/qdoc/htmlgenerator.h
new file mode 100644
index 000000000..62fe7df38
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/htmlgenerator.h
@@ -0,0 +1,179 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef HTMLGENERATOR_H
+#define HTMLGENERATOR_H
+
+#include "codemarker.h"
+#include "xmlgenerator.h"
+#include "filesystem/fileresolver.h"
+
+#include <QtCore/qhash.h>
+#include <QtCore/qregularexpression.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+class Config;
+class ExampleNode;
+class HelpProjectWriter;
+class ManifestWriter;
+
+class HtmlGenerator : public XmlGenerator
+{
+public:
+ HtmlGenerator(FileResolver& file_resolver);
+ ~HtmlGenerator() override;
+
+ void initializeGenerator() override;
+ void terminateGenerator() override;
+ QString format() override;
+ void generateDocs() override;
+
+ QString protectEnc(const QString &string);
+ static QString protect(const QString &string);
+
+protected:
+ void generateExampleFilePage(const Node *en, ResolvedFile resolved_file, CodeMarker *marker) override;
+ qsizetype generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker) override;
+ void generateCppReferencePage(Aggregate *aggregate, CodeMarker *marker) override;
+ void generateProxyPage(Aggregate *aggregate, CodeMarker *marker) override;
+ void generateQmlTypePage(QmlTypeNode *qcn, CodeMarker *marker) override;
+ void generatePageNode(PageNode *pn, CodeMarker *marker) override;
+ void generateCollectionNode(CollectionNode *cn, CodeMarker *marker) override;
+ void generateGenericCollectionPage(CollectionNode *cn, CodeMarker *marker) override;
+ [[nodiscard]] QString fileExtension() const override;
+
+private:
+ enum SubTitleSize { SmallSubTitle, LargeSubTitle };
+ enum ExtractionMarkType { BriefMark, DetailedDescriptionMark, MemberMark, EndMark };
+
+ void generateNavigationBar(const QString &title, const Node *node, CodeMarker *marker,
+ const QString &buildversion, bool tableItems = false);
+ void generateHeader(const QString &title, const Node *node = nullptr,
+ CodeMarker *marker = nullptr);
+ void generateTitle(const QString &title, const Text &subTitle, SubTitleSize subTitleSize,
+ const Node *relative, CodeMarker *marker);
+ void generateFooter(const Node *node = nullptr);
+ void generateRequisites(Aggregate *inner, CodeMarker *marker);
+ void generateQmlRequisites(QmlTypeNode *qcn, CodeMarker *marker);
+ void generateBrief(const Node *node, CodeMarker *marker, const Node *relative = nullptr,
+ bool addLink = true);
+ void generateTableOfContents(const Node *node, CodeMarker *marker,
+ QList<Section> *sections = nullptr);
+ void generateSidebar();
+ QString generateAllMembersFile(const Section &section, CodeMarker *marker);
+ QString generateAllQmlMembersFile(const Sections &sections, CodeMarker *marker);
+ QString generateObsoleteMembersFile(const Sections &sections, CodeMarker *marker);
+ QString generateObsoleteQmlMembersFile(const Sections &sections, CodeMarker *marker);
+ void generateClassHierarchy(const Node *relative, NodeMultiMap &classMap);
+ void generateAnnotatedLists(const Node *relative, CodeMarker *marker,
+ const NodeMultiMap &nodeMap);
+ void generateAnnotatedList(const Node *relative, CodeMarker *marker, const NodeList &nodes);
+ void generateCompactList(ListType listType, const Node *relative, const NodeMultiMap &classMap,
+ bool includeAlphabet, const QString &commonPrefix);
+ void generateFunctionIndex(const Node *relative);
+ void generateLegaleseList(const Node *relative, CodeMarker *marker);
+ bool generateGroupList(CollectionNode *cn);
+ void generateList(const Node *relative, CodeMarker *marker, const QString &selector);
+ void generateSectionList(const Section &section, const Node *relative, CodeMarker *marker,
+ bool useObsoloteMembers = false);
+ void generateQmlSummary(const NodeVector &members, const Node *relative, CodeMarker *marker);
+ void generateQmlItem(const Node *node, const Node *relative, CodeMarker *marker, bool summary);
+ void generateDetailedQmlMember(Node *node, const Aggregate *relative, CodeMarker *marker);
+
+ void generateSection(const NodeVector &nv, const Node *relative, CodeMarker *marker);
+ void generateSynopsis(const Node *node, const Node *relative, CodeMarker *marker,
+ Section::Style style, bool alignNames = false);
+ void generateSectionInheritedList(const Section &section, const Node *relative);
+ QString highlightedCode(const QString &markedCode, const Node *relative,
+ bool alignNames = false, Node::Genus genus = Node::DontCare);
+
+ void generateFullName(const Node *apparentNode, const Node *relative,
+ const Node *actualNode = nullptr);
+ void generateDetailedMember(const Node *node, const PageNode *relative, CodeMarker *marker);
+ void generateLink(const Atom *atom);
+
+ QString fileBase(const Node *node) const override;
+ QString fileName(const Node *node);
+
+ void beginLink(const QString &link);
+ void beginLink(const QString &link, const Node *node, const Node *relative);
+ void endLink();
+ void generateExtractionMark(const Node *node, ExtractionMarkType markType);
+ void addIncludeFileToMap(const Aggregate *aggregate, CodeMarker *marker,
+ QMap<QString, Text> &requisites, Text& text,
+ const QString &headerText);
+ void addSinceToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites, Text *text,
+ const QString &sinceText) const;
+ void addStatusToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites, Text &text,
+ const QString &statusText) const;
+ void addCMakeInfoToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites, Text *text,
+ const QString &CMakeInfo) const;
+ void addQtVariableToMap(const Aggregate *aggregate, QMap<QString, Text> &requisites, Text *text,
+ const QString &qtVariableText) const;
+ void addQmlNativeTypesToMap(QMap<QString, Text> &requisites, Text *text,
+ const QString &nativeTypeText,
+ ClassNode *classe) const;
+ void addInheritsToMap(QMap<QString, Text> &requisites, Text *text, const QString &inheritsText,
+ ClassNode *classe);
+ void addInheritedByToMap(QMap<QString, Text> &requisites, Text *text,
+ const QString &inheritedBytext, ClassNode *classe);
+ void generateTheTable(const QStringList &requisiteOrder, const QMap<QString, Text> &requisites,
+ const QString &headerText, const Aggregate *aggregate,
+ CodeMarker *marker);
+ inline void openUnorderedList();
+ inline void closeUnorderedList();
+
+ QString groupReferenceText(PageNode* node);
+
+ static bool s_inUnorderedList;
+
+ int m_codeIndent { 0 };
+ QString m_codePrefix {};
+ QString m_codeSuffix {};
+ HelpProjectWriter *m_helpProjectWriter { nullptr };
+ ManifestWriter *m_manifestWriter { nullptr };
+ QString m_headerScripts {};
+ QString m_headerStyles {};
+ QString m_endHeader {};
+ QString m_postHeader {};
+ QString m_postPostHeader {};
+ QString m_prologue {};
+ QString m_footer {};
+ QString m_address {};
+ bool m_noNavigationBar { false };
+ QString m_project {};
+ QString m_projectDescription {};
+ QString m_projectUrl {};
+ QString m_navigationLinks {};
+ QString m_navigationSeparator {};
+ QString m_homepage {};
+ QString m_hometitle {};
+ QString m_landingpage {};
+ QString m_landingtitle {};
+ QString m_cppclassespage {};
+ QString m_cppclassestitle {};
+ QString m_qmltypespage {};
+ QString m_qmltypestitle {};
+ QString m_trademarkspage {};
+ QString m_buildversion {};
+ QString m_qflagsHref {};
+ int tocDepth {};
+
+ Config *config { nullptr };
+
+};
+
+#define HTMLGENERATOR_ADDRESS "address"
+#define HTMLGENERATOR_FOOTER "footer"
+#define HTMLGENERATOR_POSTHEADER "postheader"
+#define HTMLGENERATOR_POSTPOSTHEADER "postpostheader"
+#define HTMLGENERATOR_PROLOGUE "prologue"
+#define HTMLGENERATOR_NONAVIGATIONBAR "nonavigationbar"
+#define HTMLGENERATOR_NAVIGATIONSEPARATOR "navigationseparator"
+#define HTMLGENERATOR_TOCDEPTH "tocdepth"
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/importrec.h b/src/qdoc/qdoc/src/qdoc/importrec.h
new file mode 100644
index 000000000..84f8f35ac
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/importrec.h
@@ -0,0 +1,33 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef IMPORTREC_H
+#define IMPORTREC_H
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+struct ImportRec
+{
+ QString m_moduleName {};
+ QString m_majorMinorVersion {};
+ QString m_importUri {}; // subdirectory of module directory
+
+ ImportRec(QString name, QString version, QString importUri)
+ : m_moduleName(std::move(name)),
+ m_majorMinorVersion(std::move(version)),
+ m_importUri(std::move(importUri))
+ {
+ }
+ QString &name() { return m_moduleName; }
+ QString &version() { return m_majorMinorVersion; }
+ [[nodiscard]] bool isEmpty() const { return m_moduleName.isEmpty(); }
+};
+
+QT_END_NAMESPACE
+
+#endif // IMPORTREC_H
diff --git a/src/qdoc/qdoc/src/qdoc/location.cpp b/src/qdoc/qdoc/src/qdoc/location.cpp
new file mode 100644
index 000000000..714e232d7
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/location.cpp
@@ -0,0 +1,423 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "location.h"
+
+#include "config.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qregularexpression.h>
+
+#include <climits>
+#include <cstdio>
+#include <cstdlib>
+
+QT_BEGIN_NAMESPACE
+
+int Location::s_tabSize;
+int Location::s_warningCount = 0;
+int Location::s_warningLimit = -1;
+QString Location::s_programName;
+QString Location::s_project;
+QRegularExpression *Location::s_spuriousRegExp = nullptr;
+
+/*!
+ \class Location
+
+ \brief The Location class provides a way to mark a location in a file.
+
+ It maintains a stack of file positions. A file position
+ consists of the file path, line number, and column number.
+ The location is used for printing error messages that are
+ tied to a location in a file.
+ */
+
+/*!
+ Constructs an empty location.
+ */
+Location::Location() : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
+{
+ // nothing.
+}
+
+/*!
+ Constructs a location with (fileName, 1, 1) on its file
+ position stack.
+ */
+Location::Location(const QString &fileName)
+ : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
+{
+ push(fileName);
+}
+
+/*!
+ The copy constructor copies the contents of \a other into
+ this Location using the assignment operator.
+ */
+Location::Location(const Location &other)
+ : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
+{
+ *this = other;
+}
+
+/*!
+ The assignment operator does a deep copy of the entire
+ state of \a other into this Location.
+ */
+Location &Location::operator=(const Location &other)
+{
+ if (this == &other)
+ return *this;
+
+ QStack<StackEntry> *oldStk = m_stk;
+
+ m_stkBottom = other.m_stkBottom;
+ if (other.m_stk == nullptr) {
+ m_stk = nullptr;
+ m_stkTop = &m_stkBottom;
+ } else {
+ m_stk = new QStack<StackEntry>(*other.m_stk);
+ m_stkTop = &m_stk->top();
+ }
+ m_stkDepth = other.m_stkDepth;
+ m_etc = other.m_etc;
+ delete oldStk;
+ return *this;
+}
+
+/*!
+ If the file position on top of the stack has a line number
+ less than 1, set its line number to 1 and its column number
+ to 1. Otherwise, do nothing.
+ */
+void Location::start()
+{
+ if (m_stkTop->m_lineNo < 1) {
+ m_stkTop->m_lineNo = 1;
+ m_stkTop->m_columnNo = 1;
+ }
+}
+
+/*!
+ Advance the current file position, using \a ch to decide how to do
+ that. If \a ch is a \c{'\\n'}, increment the current line number and
+ set the column number to 1. If \ch is a \c{'\\t'}, increment to the
+ next tab column. Otherwise, increment the column number by 1.
+
+ The current file position is the one on top of the position stack.
+ */
+void Location::advance(QChar ch)
+{
+ if (ch == QLatin1Char('\n')) {
+ m_stkTop->m_lineNo++;
+ m_stkTop->m_columnNo = 1;
+ } else if (ch == QLatin1Char('\t')) {
+ m_stkTop->m_columnNo = 1 + s_tabSize * (m_stkTop->m_columnNo + s_tabSize - 1) / s_tabSize;
+ } else {
+ m_stkTop->m_columnNo++;
+ }
+}
+
+/*!
+ Pushes \a filePath onto the file position stack. The current
+ file position becomes (\a filePath, 1, 1).
+
+ \sa pop()
+*/
+void Location::push(const QString &filePath)
+{
+ if (m_stkDepth++ >= 1) {
+ if (m_stk == nullptr)
+ m_stk = new QStack<StackEntry>;
+ m_stk->push(StackEntry());
+ m_stkTop = &m_stk->top();
+ }
+
+ m_stkTop->m_filePath = filePath;
+ m_stkTop->m_lineNo = INT_MIN;
+ m_stkTop->m_columnNo = 1;
+}
+
+/*!
+ Pops the top of the internal stack. The current file position
+ becomes the next one in the new top of stack.
+
+ \sa push()
+*/
+void Location::pop()
+{
+ if (--m_stkDepth == 0) {
+ m_stkBottom = StackEntry();
+ } else {
+ if (!m_stk)
+ return;
+ m_stk->pop();
+ if (m_stk->isEmpty()) {
+ delete m_stk;
+ m_stk = nullptr;
+ m_stkTop = &m_stkBottom;
+ } else {
+ m_stkTop = &m_stk->top();
+ }
+ }
+}
+
+/*! \fn bool Location::isEmpty() const
+
+ Returns \c true if there is no file name set yet; returns \c false
+ otherwise. The functions filePath(), lineNo() and columnNo()
+ must not be called on an empty Location object.
+ */
+
+/*! \fn const QString &Location::filePath() const
+ Returns the current path and file name. If the Location is
+ empty, the returned string is null.
+
+ \sa lineNo(), columnNo()
+ */
+
+/*!
+ Returns the file name part of the file path, ie the current
+ file. Returns an empty string if the file path is empty.
+ */
+QString Location::fileName() const
+{
+ QFileInfo fi(filePath());
+ return fi.fileName();
+}
+
+/*!
+ Returns the suffix of the file name. Returns an empty string
+ if the file path is empty.
+ */
+QString Location::fileSuffix() const
+{
+ QString fp = filePath();
+ return (fp.isEmpty() ? fp : fp.mid(fp.lastIndexOf('.') + 1));
+}
+
+/*! \fn int Location::lineNo() const
+ Returns the current line number.
+ Must not be called on an empty Location object.
+
+ \sa filePath(), columnNo()
+*/
+
+/*! \fn int Location::columnNo() const
+ Returns the current column number.
+ Must not be called on an empty Location object.
+
+ \sa filePath(), lineNo()
+*/
+
+/*!
+ Writes \a message and \a details to stderr as a formatted
+ warning message. Does not write the message if qdoc is in
+ the Prepare phase.
+ */
+void Location::warning(const QString &message, const QString &details) const
+{
+ const auto &config = Config::instance();
+ if (!config.preparing() || config.singleExec())
+ emitMessage(Warning, message, details);
+}
+
+/*!
+ Writes \a message and \a details to stderr as a formatted
+ error message. Does not write the message if qdoc is in
+ the Prepare phase.
+ */
+void Location::error(const QString &message, const QString &details) const
+{
+ const auto &config = Config::instance();
+ if (!config.preparing() || config.singleExec())
+ emitMessage(Error, message, details);
+}
+
+/*!
+ Returns the error code QDoc should exit with; EXIT_SUCCESS
+ or the number of documentation warnings if they exceeded
+ the limit set by warninglimit configuration variable.
+ */
+int Location::exitCode()
+{
+ if (s_warningLimit < 0 || s_warningCount <= s_warningLimit)
+ return EXIT_SUCCESS;
+
+ Location().emitMessage(
+ Error,
+ QStringLiteral("Documentation warnings (%1) exceeded the limit (%2) for '%3'.")
+ .arg(QString::number(s_warningCount), QString::number(s_warningLimit),
+ s_project),
+ QString());
+ return s_warningCount;
+}
+
+/*!
+ Writes \a message and \a details to stderr as a formatted
+ error message and then exits the program. qdoc prints fatal
+ errors in either phase (Prepare or Generate).
+ */
+void Location::fatal(const QString &message, const QString &details) const
+{
+ emitMessage(Error, message, details);
+ information(message);
+ information(details);
+ information("Aborting");
+ exit(EXIT_FAILURE);
+}
+
+/*!
+ Writes \a message and \a details to stderr as a formatted
+ report message.
+ */
+void Location::report(const QString &message, const QString &details) const
+{
+ emitMessage(Report, message, details);
+}
+
+/*!
+ Gets several parameters from the config, including
+ tab size, program name, and a regular expression that
+ appears to be used for matching certain error messages
+ so that emitMessage() can avoid printing them.
+ */
+void Location::initialize()
+{
+ Config &config = Config::instance();
+ s_tabSize = config.get(CONFIG_TABSIZE).asInt();
+ s_programName = config.programName();
+ s_project = config.get(CONFIG_PROJECT).asString();
+ if (!config.singleExec())
+ s_warningCount = 0;
+ if (qEnvironmentVariableIsSet("QDOC_ENABLE_WARNINGLIMIT")
+ || config.get(CONFIG_WARNINGLIMIT + Config::dot + "enabled").asBool())
+ s_warningLimit = config.get(CONFIG_WARNINGLIMIT).asInt();
+
+ QRegularExpression regExp = config.getRegExp(CONFIG_SPURIOUS);
+ if (regExp.isValid()) {
+ s_spuriousRegExp = new QRegularExpression(regExp);
+ } else {
+ config.get(CONFIG_SPURIOUS).location()
+ .warning(QStringLiteral("Invalid regular expression '%1'")
+ .arg(regExp.pattern()));
+ }
+}
+
+/*!
+ Apparently, all this does is delete the regular expression
+ used for intercepting certain error messages that should
+ not be emitted by emitMessage().
+ */
+void Location::terminate()
+{
+ delete s_spuriousRegExp;
+ s_spuriousRegExp = nullptr;
+}
+
+/*!
+ Prints \a message to \c stdout followed by a \c{'\n'}.
+ */
+void Location::information(const QString &message)
+{
+ printf("%s\n", message.toLatin1().data());
+ fflush(stdout);
+}
+
+/*!
+ Report a program bug, including the \a hint.
+ */
+void Location::internalError(const QString &hint)
+{
+ Location().fatal(QStringLiteral("Internal error (%1)").arg(hint),
+ QStringLiteral("There is a bug in %1. Seek advice from your local"
+ " %2 guru.")
+ .arg(s_programName, s_programName));
+}
+
+/*!
+ Formats \a message and \a details into a single string
+ and outputs that string to \c stderr. \a type specifies
+ whether the \a message is an error or a warning.
+ */
+void Location::emitMessage(MessageType type, const QString &message, const QString &details) const
+{
+ if (type == Warning && s_spuriousRegExp != nullptr) {
+ auto match = s_spuriousRegExp->match(message, 0, QRegularExpression::NormalMatch,
+ QRegularExpression::AnchorAtOffsetMatchOption);
+ if (match.hasMatch() && match.capturedLength() == message.size())
+ return;
+ }
+
+ QString result = message;
+ if (!details.isEmpty())
+ result += "\n[" + details + QLatin1Char(']');
+ result.replace("\n", "\n ");
+ if (isEmpty()) {
+ if (type == Error)
+ result.prepend(QStringLiteral(": error: "));
+ else if (type == Warning) {
+ result.prepend(QStringLiteral(": warning: "));
+ ++s_warningCount;
+ }
+ } else {
+ if (type == Error)
+ result.prepend(QStringLiteral(": (qdoc) error: "));
+ else if (type == Warning) {
+ result.prepend(QStringLiteral(": (qdoc) warning: "));
+ ++s_warningCount;
+ }
+ }
+ if (type != Report)
+ result.prepend(toString());
+ fprintf(stderr, "%s\n", result.toLatin1().data());
+ fflush(stderr);
+}
+
+/*!
+ Converts the location to a string to be prepended to error
+ messages.
+ */
+QString Location::toString() const
+{
+ QString str;
+
+ if (isEmpty()) {
+ str = s_programName;
+ } else {
+ Location loc2 = *this;
+ loc2.setEtc(false);
+ loc2.pop();
+ if (!loc2.isEmpty()) {
+ QString blah = QStringLiteral("In file included from ");
+ for (;;) {
+ str += blah;
+ str += loc2.top();
+ loc2.pop();
+ if (loc2.isEmpty())
+ break;
+ str += QStringLiteral(",\n");
+ blah.fill(' ');
+ }
+ str += QStringLiteral(":\n");
+ }
+ str += top();
+ }
+ return str;
+}
+
+QString Location::top() const
+{
+ QDir path(filePath());
+ QString str = path.absolutePath();
+ if (lineNo() >= 1) {
+ str += QLatin1Char(':');
+ str += QString::number(lineNo());
+ }
+ if (etc())
+ str += QLatin1String(" (etc.)");
+ return str;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/location.h b/src/qdoc/qdoc/src/qdoc/location.h
new file mode 100644
index 000000000..8427bc917
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/location.h
@@ -0,0 +1,92 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef LOCATION_H
+#define LOCATION_H
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qstack.h>
+
+QT_BEGIN_NAMESPACE
+
+class QRegularExpression;
+
+class Location
+{
+public:
+ Location();
+ explicit Location(const QString &filePath);
+ Location(const Location &other);
+ ~Location() { delete m_stk; }
+
+ Location &operator=(const Location &other);
+
+ void start();
+ void advance(QChar ch);
+ void advanceLines(int n)
+ {
+ m_stkTop->m_lineNo += n;
+ m_stkTop->m_columnNo = 1;
+ }
+
+ void push(const QString &filePath);
+ void pop();
+ void setEtc(bool etc) { m_etc = etc; }
+ void setLineNo(int no) { m_stkTop->m_lineNo = no; }
+ void setColumnNo(int no) { m_stkTop->m_columnNo = no; }
+
+ [[nodiscard]] bool isEmpty() const { return m_stkDepth == 0; }
+ [[nodiscard]] int depth() const { return m_stkDepth; }
+ [[nodiscard]] const QString &filePath() const { return m_stkTop->m_filePath; }
+ [[nodiscard]] QString fileName() const;
+ [[nodiscard]] QString fileSuffix() const;
+ [[nodiscard]] int lineNo() const { return m_stkTop->m_lineNo; }
+ [[nodiscard]] int columnNo() const { return m_stkTop->m_columnNo; }
+ [[nodiscard]] bool etc() const { return m_etc; }
+ [[nodiscard]] QString toString() const;
+ void warning(const QString &message, const QString &details = QString()) const;
+ void error(const QString &message, const QString &details = QString()) const;
+ void fatal(const QString &message, const QString &details = QString()) const;
+ void report(const QString &message, const QString &details = QString()) const;
+
+ static void initialize();
+
+ static void terminate();
+ static void information(const QString &message);
+ static void internalError(const QString &hint);
+ static int exitCode();
+
+private:
+ enum MessageType { Warning, Error, Report };
+
+ struct StackEntry
+ {
+ QString m_filePath {};
+ int m_lineNo {};
+ int m_columnNo {};
+ };
+ friend class QTypeInfo<StackEntry>;
+
+ void emitMessage(MessageType type, const QString &message, const QString &details) const;
+ [[nodiscard]] QString top() const;
+
+private:
+ StackEntry m_stkBottom {};
+ QStack<StackEntry> *m_stk {};
+ StackEntry *m_stkTop {};
+ int m_stkDepth {};
+ bool m_etc {};
+
+ static int s_tabSize;
+ static int s_warningCount;
+ static int s_warningLimit;
+ static QString s_programName;
+ static QString s_project;
+ static QRegularExpression *s_spuriousRegExp;
+};
+Q_DECLARE_TYPEINFO(Location::StackEntry, Q_RELOCATABLE_TYPE);
+Q_DECLARE_TYPEINFO(Location, Q_COMPLEX_TYPE); // stkTop = &stkBottom
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/macro.h b/src/qdoc/qdoc/src/qdoc/macro.h
new file mode 100644
index 000000000..11e77fa29
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/macro.h
@@ -0,0 +1,27 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#ifndef MACRO_H
+#define MACRO_H
+
+#include "location.h"
+
+#include <QtCore/qmap.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ * Simple structure used by the Doc and DocParser classes.
+ */
+struct Macro
+{
+public:
+ QString m_defaultDef {};
+ Location m_defaultDefLocation {};
+ QMap<QString, QString> m_otherDefs {};
+ int numParams {};
+};
+
+QT_END_NAMESPACE
+
+#endif // MACRO_H
diff --git a/src/qdoc/qdoc/src/qdoc/main.cpp b/src/qdoc/qdoc/src/qdoc/main.cpp
new file mode 100644
index 000000000..5e48a6a8a
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/main.cpp
@@ -0,0 +1,720 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "clangcodeparser.h"
+#include "codemarker.h"
+#include "codeparser.h"
+#include "config.h"
+#include "cppcodemarker.h"
+#include "doc.h"
+#include "docbookgenerator.h"
+#include "htmlgenerator.h"
+#include "location.h"
+#include "puredocparser.h"
+#include "qdocdatabase.h"
+#include "qmlcodemarker.h"
+#include "qmlcodeparser.h"
+#include "sourcefileparser.h"
+#include "utilities.h"
+#include "tokenizer.h"
+#include "tree.h"
+#include "webxmlgenerator.h"
+
+#include "filesystem/fileresolver.h"
+#include "boundaries/filesystem/directorypath.h"
+
+#include <QtCore/qcompilerdetection.h>
+#include <QtCore/qdatetime.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qglobal.h>
+#include <QtCore/qhashfunctions.h>
+
+#include <set>
+
+#ifndef QT_BOOTSTRAPPED
+# include <QtCore/qcoreapplication.h>
+#endif
+
+#include <algorithm>
+#include <cstdlib>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+bool creationTimeBefore(const QFileInfo &fi1, const QFileInfo &fi2)
+{
+ return fi1.lastModified() < fi2.lastModified();
+}
+
+/*!
+ \internal
+ Inspects each file path in \a sources. File paths with a known
+ source file type are parsed to extract user-provided
+ documentation and information about source code level elements.
+
+ \note Unknown source file types are silently ignored.
+
+ The validity or availability of the file paths may or may not cause QDoc
+ to generate warnings; this depends on the implementation of
+ parseSourceFile() for the relevant parser.
+
+ \sa CodeParser::parserForSourceFile, CodeParser::sourceFileNameFilter
+*/
+static void parseSourceFiles(
+ std::vector<QString>&& sources,
+ SourceFileParser& source_file_parser,
+ CppCodeParser& cpp_code_parser
+) {
+ ParserErrorHandler error_handler{};
+ std::stable_sort(sources.begin(), sources.end());
+
+ sources.erase (
+ std::unique(sources.begin(), sources.end()),
+ sources.end()
+ );
+
+ auto qml_sources =
+ std::stable_partition(sources.begin(), sources.end(), [](const QString& source){
+ return CodeParser::parserForSourceFile(source) == CodeParser::parserForLanguage("QML");
+ });
+
+
+ std::for_each(qml_sources, sources.end(),
+ [&source_file_parser, &cpp_code_parser, &error_handler](const QString& source){
+ qCDebug(lcQdoc, "Parsing %s", qPrintable(source));
+
+ auto [untied_documentation, tied_documentation] = source_file_parser(tag_source_file(source));
+ std::vector<FnMatchError> errors{};
+
+ for (auto untied : untied_documentation) {
+ auto result = cpp_code_parser.processTopicArgs(untied);
+ tied_documentation.insert(tied_documentation.end(), result.first.begin(), result.first.end());
+ };
+
+ cpp_code_parser.processMetaCommands(tied_documentation);
+
+ // Process errors that occurred during parsing
+ for (const auto &e : errors)
+ error_handler(e);
+ });
+
+ std::for_each(sources.begin(), qml_sources, [&cpp_code_parser](const QString& source){
+ auto *codeParser = CodeParser::parserForSourceFile(source);
+ if (!codeParser) return;
+
+ qCDebug(lcQdoc, "Parsing %s", qPrintable(source));
+ codeParser->parseSourceFile(Config::instance().location(), source, cpp_code_parser);
+ });
+
+}
+
+/*!
+ Read some XML indexes containing definitions from other
+ documentation sets. \a config contains a variable that
+ lists directories where index files can be found. It also
+ contains the \c depends variable, which lists the modules
+ that the current module depends on. \a formats contains
+ a list of output formats; each format may have a different
+ output subdirectory where index files are located.
+*/
+static void loadIndexFiles(const QSet<QString> &formats)
+{
+ Config &config = Config::instance();
+ QDocDatabase *qdb = QDocDatabase::qdocDB();
+ QStringList indexFiles;
+ const QStringList configIndexes{config.get(CONFIG_INDEXES).asStringList()};
+ bool warn = !config.get(CONFIG_NOLINKERRORS).asBool();
+
+ for (const auto &index : configIndexes) {
+ QFileInfo fi(index);
+ if (fi.exists() && fi.isFile())
+ indexFiles << index;
+ else if (warn)
+ Location().warning(QString("Index file not found: %1").arg(index));
+ }
+
+ config.dependModules() += config.get(CONFIG_DEPENDS).asStringList();
+ config.dependModules().removeDuplicates();
+ bool useNoSubDirs = false;
+ QSet<QString> subDirs;
+
+ // Add format-specific output subdirectories to the set of
+ // subdirectories where we look for index files
+ for (const auto &format : formats) {
+ if (config.get(format + Config::dot + "nosubdirs").asBool()) {
+ useNoSubDirs = true;
+ const auto singleOutputSubdir{QDir(config.getOutputDir(format)).dirName()};
+ if (!singleOutputSubdir.isEmpty())
+ subDirs << singleOutputSubdir;
+ }
+ }
+
+ if (!config.dependModules().empty()) {
+ if (!config.indexDirs().empty()) {
+ for (auto &dir : config.indexDirs()) {
+ if (dir.startsWith("..")) {
+ const QString prefix(QDir(config.currentDir())
+ .relativeFilePath(config.previousCurrentDir()));
+ if (!prefix.isEmpty())
+ dir.prepend(prefix + QLatin1Char('/'));
+ }
+ }
+ /*
+ Load all dependencies:
+ Either add all subdirectories of the indexdirs as dependModules,
+ when an asterisk is used in the 'depends' list, or
+ when <format>.nosubdirs is set, we need to look for all .index files
+ in the output subdirectory instead.
+ */
+ bool asteriskUsed = false;
+ if (config.dependModules().contains("*")) {
+ config.dependModules().removeOne("*");
+ asteriskUsed = true;
+ if (useNoSubDirs) {
+ std::for_each(formats.begin(), formats.end(), [&](const QString &format) {
+ QDir scanDir(config.getOutputDir(format));
+ QStringList foundModules =
+ scanDir.entryList(QStringList("*.index"), QDir::Files);
+ std::transform(
+ foundModules.begin(), foundModules.end(), foundModules.begin(),
+ [](const QString &index) { return QFileInfo(index).baseName(); });
+ config.dependModules() << foundModules;
+ });
+ } else {
+ for (const auto &indexDir : config.indexDirs()) {
+ QDir scanDir = QDir(indexDir);
+ scanDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
+ QFileInfoList dirList = scanDir.entryInfoList();
+ for (const auto &dir : dirList)
+ config.dependModules().append(dir.fileName());
+ }
+ }
+ // Remove self-dependencies and possible duplicates
+ QString project{config.get(CONFIG_PROJECT).asString()};
+ config.dependModules().removeAll(project.toLower());
+ config.dependModules().removeDuplicates();
+ qCCritical(lcQdoc) << "Configuration file for"
+ << project << "has depends = *; loading all"
+ << config.dependModules().size()
+ << "index files found";
+ }
+ for (const auto &module : config.dependModules()) {
+ QList<QFileInfo> foundIndices;
+ // Always look in module-specific subdir, even with *.nosubdirs config
+ bool useModuleSubDir = !subDirs.contains(module);
+ subDirs << module;
+
+ for (const auto &dir : config.indexDirs()) {
+ for (const auto &subDir : std::as_const(subDirs)) {
+ QString fileToLookFor = dir + QLatin1Char('/') + subDir + QLatin1Char('/')
+ + module + ".index";
+ if (QFile::exists(fileToLookFor)) {
+ QFileInfo tempFileInfo(fileToLookFor);
+ if (!foundIndices.contains(tempFileInfo))
+ foundIndices.append(tempFileInfo);
+ }
+ }
+ }
+ // Clear the temporary module-specific subdir
+ if (useModuleSubDir)
+ subDirs.remove(module);
+ std::sort(foundIndices.begin(), foundIndices.end(), creationTimeBefore);
+ QString indexToAdd;
+ if (foundIndices.size() > 1) {
+ /*
+ QDoc should always use the last entry in the multimap when there are
+ multiple index files for a module, since the last modified file has the
+ highest UNIX timestamp.
+ */
+ QStringList indexPaths;
+ indexPaths.reserve(foundIndices.size());
+ for (const auto &found : std::as_const(foundIndices))
+ indexPaths << found.absoluteFilePath();
+ if (warn) {
+ Location().warning(
+ QString("Multiple index files found for dependency \"%1\":\n%2")
+ .arg(module, indexPaths.join('\n')));
+ Location().warning(
+ QString("Using %1 as index file for dependency \"%2\"")
+ .arg(foundIndices[foundIndices.size() - 1].absoluteFilePath(),
+ module));
+ }
+ indexToAdd = foundIndices[foundIndices.size() - 1].absoluteFilePath();
+ } else if (foundIndices.size() == 1) {
+ indexToAdd = foundIndices[0].absoluteFilePath();
+ }
+ if (!indexToAdd.isEmpty()) {
+ if (!indexFiles.contains(indexToAdd))
+ indexFiles << indexToAdd;
+ } else if (!asteriskUsed && warn) {
+ Location().warning(
+ QString(R"("%1" Cannot locate index file for dependency "%2")")
+ .arg(config.get(CONFIG_PROJECT).asString(), module));
+ }
+ }
+ } else if (warn) {
+ Location().warning(
+ QLatin1String("Dependent modules specified, but no index directories were set. "
+ "There will probably be errors for missing links."));
+ }
+ }
+ qdb->readIndexes(indexFiles);
+}
+
+/*!
+ \internal
+ Prints to stderr the name of the project that QDoc is running for,
+ in which mode and which phase.
+
+ If QDoc is not running in debug mode or --log-progress command line
+ option is not set, do nothing.
+ */
+void logStartEndMessage(const QLatin1String &startStop, Config &config)
+{
+ if (!config.get(CONFIG_LOGPROGRESS).asBool())
+ return;
+
+ const QString runName = " qdoc for "
+ + config.get(CONFIG_PROJECT).asString()
+ + QLatin1String(" in ")
+ + QLatin1String(config.singleExec() ? "single" : "dual")
+ + QLatin1String(" process mode: ")
+ + QLatin1String(config.preparing() ? "prepare" : "generate")
+ + QLatin1String(" phase.");
+
+ const QString msg = startStop + runName;
+ qCInfo(lcQdoc) << msg.toUtf8().data();
+}
+
+/*!
+ Processes the qdoc config file \a fileName. This is the controller for all
+ of QDoc. The \a config instance represents the configuration data for QDoc.
+ All other classes are initialized with the same config.
+ */
+static void processQdocconfFile(const QString &fileName)
+{
+ Config &config = Config::instance();
+ config.setPreviousCurrentDir(QDir::currentPath());
+
+ /*
+ With the default configuration values in place, load
+ the qdoc configuration file. Note that the configuration
+ file may include other configuration files.
+
+ The Location class keeps track of the current location
+ in the file being processed, mainly for error reporting
+ purposes.
+ */
+ Location::initialize();
+ config.load(fileName);
+ QString project{config.get(CONFIG_PROJECT).asString()};
+ if (project.isEmpty()) {
+ qCCritical(lcQdoc) << QLatin1String("qdoc can't run; no project set in qdocconf file");
+ exit(1);
+ }
+ Location::terminate();
+
+ config.setCurrentDir(QFileInfo(fileName).path());
+ if (!config.currentDir().isEmpty())
+ QDir::setCurrent(config.currentDir());
+
+ logStartEndMessage(QLatin1String("Start"), config);
+
+ if (config.getDebug()) {
+ Utilities::startDebugging(QString("command line"));
+ qCDebug(lcQdoc).noquote() << "Arguments:" << QCoreApplication::arguments();
+ }
+
+ // <<TODO: [cleanup-temporary-kludges]
+ // The underlying validation should be performed at the
+ // configuration level during parsing.
+ // This cannot be done straightforwardly with how the Config class
+ // is implemented.
+ // When the Config class will be deprived of logic and
+ // restructured, the compiler will notify us of this kludge, but
+ // remember to reevaluate the code itself considering the new
+ // data-flow and the possibility for optimizations as this is not
+ // done for temporary code. Indeed some of the code is visibly wasteful.
+ // Similarly, ensure that the loose definition that we use here is
+ // not preserved.
+
+ QStringList search_directories{config.getCanonicalPathList(CONFIG_EXAMPLEDIRS)};
+ QStringList image_search_directories{config.getCanonicalPathList(CONFIG_IMAGEDIRS)};
+
+ const auto& [excludedDirs, excludedFiles] = config.getExcludedPaths();
+
+ qCDebug(lcQdoc, "Adding doc/image dirs found in exampledirs to imagedirs");
+ QSet<QString> exampleImageDirs;
+ QStringList exampleImageList = config.getExampleImageFiles(excludedDirs, excludedFiles);
+ for (const auto &image : exampleImageList) {
+ if (image.contains("doc/images")) {
+ QString t = image.left(image.lastIndexOf("doc/images") + 10);
+ if (!exampleImageDirs.contains(t))
+ exampleImageDirs.insert(t);
+ }
+ }
+
+ // REMARK: The previous system discerned between search directories based on the kind of file that was searched for.
+ // For example, an image search was bounded to some directories
+ // that may or may not be the same as the ones where examples are
+ // searched for.
+ // The current Qt documentation does not use this feature. That
+ // is, the output of QDoc when a unified search list is used is
+ // the same as the output for that of separated lists.
+ // For this reason, we currently simplify the process, albeit this
+ // may at some point change, by joining the various lists into a
+ // single search list and a unified interface.
+ // Do note that the configuration still allows for those
+ // parameters to be user defined in a split-way as this will not
+ // be able to be changed until Config itself is looked upon.
+ // Hence, we join the various directory sources into one list for the time being.
+ // Do note that this means that the amount of searched directories for a file is now increased.
+ // This shouldn't matter as the amount of directories is expected
+ // to be generally small and the search routine complexity is
+ // linear in the amount of directories.
+ // There are some complications that may arise in very specific
+ // cases by this choice (some of which where there before under
+ // possibly different circumstances), making some files
+ // unreachable.
+ // See the remarks in FileResolver for more infomration.
+ std::copy(image_search_directories.begin(), image_search_directories.end(), std::back_inserter(search_directories));
+ std::copy(exampleImageDirs.begin(), exampleImageDirs.end(), std::back_inserter(search_directories));
+
+ std::vector<DirectoryPath> validated_search_directories{};
+ for (const QString& path : search_directories) {
+ auto maybe_validated_path{DirectoryPath::refine(path)};
+ if (!maybe_validated_path)
+ // TODO: [uncentralized-admonition]
+ qCDebug(lcQdoc).noquote() << u"%1 is not a valid path, it will be ignored when resolving a file"_s.arg(path);
+ else validated_search_directories.push_back(*maybe_validated_path);
+ }
+
+ // TODO>>
+
+ FileResolver file_resolver{std::move(validated_search_directories)};
+
+ // REMARK: The constructor for generators doesn't actually perform
+ // initialization of their content.
+ // Indeed, Generators use the general antipattern of the static
+ // initialize-terminate non-scoped mutable state that we see in
+ // many parts of QDoc.
+ // In their constructor, Generators mainly register themselves into a static list.
+ // Previously, this was done at the start of main.
+ // To be able to pass a correct FileResolver or other systems, we
+ // need to construct them after the configuration has been read
+ // and has been destructured.
+ // For this reason, their construction was moved here.
+ // This function may be called more than once for some of QDoc's
+ // call, albeit this should not actually happen in Qt's
+ // documentation.
+ // Then, constructing the generators here might provide for some
+ // unexpected behavior as new generators are appended to the list
+ // and never used, considering that the list is searched in a
+ // linearly fashion and each generator of some type T, in the
+ // current codebase, will always be found if another instance of
+ // that same type would have been found.
+ // Furthermore, those instances would be destroyed by then, such
+ // that accessing them would be erroneous.
+ // To avoid this, the static list was made to be cleared in
+ // Generator::terminate, which, in theory, will be called before
+ // the generators will be constructed again.
+ // We could have used the initialize method for this, but this
+ // would force us into a limited and more complex semantic, see an
+ // example of this in DocParser, and would restrain us further to
+ // the initialize-terminate idiom which is expect to be purged in
+ // the future.
+ HtmlGenerator htmlGenerator{file_resolver};
+ WebXMLGenerator webXMLGenerator{file_resolver};
+ DocBookGenerator docBookGenerator{file_resolver};
+
+ Generator::initialize();
+
+ /*
+ Initialize the qdoc database, where all the parsed source files
+ will be stored. The database includes a tree of nodes, which gets
+ built as the source files are parsed. The documentation output is
+ generated by traversing that tree.
+
+ Note: qdocDB() allocates a new instance only if no instance exists.
+ So it is safe to call qdocDB() any time.
+ */
+ QDocDatabase *qdb = QDocDatabase::qdocDB();
+ qdb->setVersion(config.get(CONFIG_VERSION).asString());
+ /*
+ By default, the only output format is HTML.
+ */
+ const QSet<QString> outputFormats = config.getOutputFormats();
+
+ qdb->clearSearchOrder();
+ if (!config.singleExec()) {
+ if (!config.preparing()) {
+ qCDebug(lcQdoc, " loading index files");
+ loadIndexFiles(outputFormats);
+ qCDebug(lcQdoc, " done loading index files");
+ }
+ qdb->newPrimaryTree(project);
+ } else if (config.preparing())
+ qdb->newPrimaryTree(project);
+ else
+ qdb->setPrimaryTree(project);
+
+ // Retrieve the dependencies if loadIndexFiles() was not called
+ if (config.dependModules().isEmpty()) {
+ config.dependModules() = config.get(CONFIG_DEPENDS).asStringList();
+ config.dependModules().removeDuplicates();
+ }
+ qdb->setSearchOrder(config.dependModules());
+
+ // Store the title of the index (landing) page
+ NamespaceNode *root = qdb->primaryTreeRoot();
+ if (root) {
+ QString title{config.get(CONFIG_NAVIGATION + Config::dot + CONFIG_LANDINGPAGE).asString()};
+ root->tree()->setIndexTitle(
+ config.get(CONFIG_NAVIGATION + Config::dot + CONFIG_LANDINGTITLE).asString(title));
+ }
+
+
+ std::vector<QByteArray> include_paths{};
+ {
+ auto args = config.getCanonicalPathList(CONFIG_INCLUDEPATHS,
+ Config::IncludePaths);
+#ifdef Q_OS_MACOS
+ args.append(Utilities::getInternalIncludePaths(QStringLiteral("clang++")));
+#elif defined(Q_OS_LINUX)
+ args.append(Utilities::getInternalIncludePaths(QStringLiteral("g++")));
+#endif
+
+ for (const auto &path : std::as_const(args)) {
+ if (!path.isEmpty())
+ include_paths.push_back(path.toUtf8());
+ }
+
+ include_paths.erase(std::unique(include_paths.begin(), include_paths.end()),
+ include_paths.end());
+ }
+
+ QList<QByteArray> clang_defines{};
+ {
+ const QStringList config_defines{config.get(CONFIG_DEFINES).asStringList()};
+ for (const QString &def : config_defines) {
+ if (!def.contains(QChar('*'))) {
+ QByteArray tmp("-D");
+ tmp.append(def.toUtf8());
+ clang_defines.append(tmp.constData());
+ }
+ }
+ }
+
+ std::optional<PCHFile> pch = std::nullopt;
+ if (config.dualExec() || config.preparing()) {
+ const QString moduleHeader = config.get(CONFIG_MODULEHEADER).asString();
+ pch = buildPCH(
+ QDocDatabase::qdocDB(),
+ moduleHeader.isNull() ? project : moduleHeader,
+ Config::instance().getHeaderFiles(),
+ include_paths,
+ clang_defines
+ );
+ }
+
+ ClangCodeParser clangParser(QDocDatabase::qdocDB(), Config::instance(), include_paths, clang_defines, pch);
+ PureDocParser docParser{config.location()};
+
+ /*
+ Initialize all the classes and data structures with the
+ qdoc configuration. This is safe to do for each qdocconf
+ file processed, because all the data structures created
+ are either cleared after they have been used, or they
+ are cleared in the terminate() functions below.
+ */
+ Location::initialize();
+ Tokenizer::initialize();
+ CodeMarker::initialize();
+ CodeParser::initialize();
+ Doc::initialize(file_resolver);
+
+ if (config.dualExec() || config.preparing()) {
+ QStringList sourceList;
+
+ qCDebug(lcQdoc, "Reading sourcedirs");
+ sourceList =
+ config.getAllFiles(CONFIG_SOURCES, CONFIG_SOURCEDIRS, excludedDirs, excludedFiles);
+
+ std::vector<QString> sources{};
+ for (const auto &source : sourceList) {
+ if (source.contains(QLatin1String("doc/snippets")))
+ continue;
+ sources.emplace_back(source);
+ }
+ /*
+ Find all the qdoc files in the example dirs, and add
+ them to the source files to be parsed.
+ */
+ qCDebug(lcQdoc, "Reading exampledirs");
+ QStringList exampleQdocList = config.getExampleQdocFiles(excludedDirs, excludedFiles);
+ for (const auto &example : exampleQdocList) {
+ sources.emplace_back(example);
+ }
+
+ /*
+ Parse each source text file in the set using the appropriate parser and
+ add it to the big tree.
+ */
+ if (config.get(CONFIG_LOGPROGRESS).asBool())
+ qCInfo(lcQdoc) << "Parse source files for" << project;
+
+
+ auto headers = config.getHeaderFiles();
+ CppCodeParser cpp_code_parser(FnCommandParser(qdb, headers, clang_defines, pch));
+
+ SourceFileParser source_file_parser{clangParser, docParser};
+ parseSourceFiles(std::move(sources), source_file_parser, cpp_code_parser);
+
+ if (config.get(CONFIG_LOGPROGRESS).asBool())
+ qCInfo(lcQdoc) << "Source files parsed for" << project;
+ }
+ /*
+ Now the primary tree has been built from all the header and
+ source files. Resolve all the class names, function names,
+ targets, URLs, links, and other stuff that needs resolving.
+ */
+ qCDebug(lcQdoc, "Resolving stuff prior to generating docs");
+ qdb->resolveStuff();
+
+ /*
+ The primary tree is built and all the stuff that needed
+ resolving has been resolved. Now traverse the tree and
+ generate the documentation output. More than one output
+ format can be requested. The tree is traversed for each
+ one.
+ */
+ qCDebug(lcQdoc, "Generating docs");
+ for (const auto &format : outputFormats) {
+ auto *generator = Generator::generatorForFormat(format);
+ if (generator) {
+ generator->initializeFormat();
+ generator->generateDocs();
+ } else {
+ config.get(CONFIG_OUTPUTFORMATS)
+ .location()
+ .fatal(QStringLiteral("QDoc: Unknown output format '%1'").arg(format));
+ }
+ }
+
+ qCDebug(lcQdoc, "Terminating qdoc classes");
+ if (Utilities::debugging())
+ Utilities::stopDebugging(project);
+
+ logStartEndMessage(QLatin1String("End"), config);
+ QDocDatabase::qdocDB()->setVersion(QString());
+ Generator::terminate();
+ CodeParser::terminate();
+ CodeMarker::terminate();
+ Doc::terminate();
+ Tokenizer::terminate();
+ Location::terminate();
+ QDir::setCurrent(config.previousCurrentDir());
+
+ qCDebug(lcQdoc, "qdoc classes terminated");
+}
+
+/*!
+ \internal
+ For each file in \a qdocFiles, first clear the configured module
+ dependencies and then pass the file to processQdocconfFile().
+
+ \sa processQdocconfFile(), singleExecutionMode(), dualExecutionMode()
+*/
+static void clearModuleDependenciesAndProcessQdocconfFile(const QStringList &qdocFiles)
+{
+ for (const auto &file : std::as_const(qdocFiles)) {
+ Config::instance().dependModules().clear();
+ processQdocconfFile(file);
+ }
+}
+
+/*!
+ \internal
+
+ A single QDoc process for prepare and generate phases.
+ The purpose is to first generate all index files for all documentation
+ projects that combined make out the documentation set being generated.
+ This allows QDoc to link to all content contained in all projects, e.g.
+ user-defined types or overview documentation, regardless of the project
+ that content belongs to when generating the final output.
+*/
+static void singleExecutionMode()
+{
+ const QStringList qdocFiles = Config::loadMaster(Config::instance().qdocFiles().at(0));
+
+ Config::instance().setQDocPass(Config::Prepare);
+ clearModuleDependenciesAndProcessQdocconfFile(qdocFiles);
+
+ Config::instance().setQDocPass(Config::Generate);
+ QDocDatabase::qdocDB()->processForest();
+ clearModuleDependenciesAndProcessQdocconfFile(qdocFiles);
+}
+
+/*!
+ \internal
+
+ Process each .qdocconf-file passed as command line argument(s).
+*/
+static void dualExecutionMode()
+{
+ const QStringList qdocFiles = Config::instance().qdocFiles();
+ clearModuleDependenciesAndProcessQdocconfFile(qdocFiles);
+}
+
+QT_END_NAMESPACE
+
+int main(int argc, char **argv)
+{
+ QT_USE_NAMESPACE
+
+ // Initialize Qt:
+#ifndef QT_BOOTSTRAPPED
+ // use deterministic hash seed
+ QHashSeed::setDeterministicGlobalSeed();
+#endif
+ QCoreApplication app(argc, argv);
+ app.setApplicationVersion(QLatin1String(QT_VERSION_STR));
+
+ // Instantiate various singletons (used via static methods):
+ /*
+ Create code parsers for the languages to be parsed,
+ and create a tree for C++.
+ */
+ QmlCodeParser qmlParser;
+
+ /*
+ Create code markers for plain text, C++,
+ and QML.
+
+ The plain CodeMarker must be instantiated first because it is used as
+ fallback when the other markers cannot be used.
+
+ Each marker instance is prepended to the CodeMarker::markers list by the
+ base class constructor.
+ */
+ CodeMarker fallbackMarker;
+ CppCodeMarker cppMarker;
+ QmlCodeMarker qmlMarker;
+
+ Config::instance().init("QDoc", app.arguments());
+
+ if (Config::instance().qdocFiles().isEmpty())
+ Config::instance().showHelp();
+
+ if (Config::instance().singleExec()) {
+ singleExecutionMode();
+ } else {
+ dualExecutionMode();
+ }
+
+ // Tidy everything away:
+ QmlTypeNode::terminate();
+ QDocDatabase::destroyQdocDB();
+ return Location::exitCode();
+}
diff --git a/src/qdoc/qdoc/src/qdoc/manifestwriter.cpp b/src/qdoc/qdoc/src/qdoc/manifestwriter.cpp
new file mode 100644
index 000000000..97bf7f190
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/manifestwriter.cpp
@@ -0,0 +1,405 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include "manifestwriter.h"
+
+#include "config.h"
+#include "examplenode.h"
+#include "generator.h"
+#include "qdocdatabase.h"
+
+#include <QtCore/qmap.h>
+#include <QtCore/qset.h>
+#include <QtCore/qxmlstream.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \internal
+
+ For each attribute in a map of attributes, checks if the attribute is
+ found in \a usedAttributes. If it is not found, issues a warning specific
+ to the attribute.
+ */
+void warnAboutUnusedAttributes(const QStringList &usedAttributes, const ExampleNode *example)
+{
+ QMap<QString, QString> attributesToWarnFor;
+ attributesToWarnFor.insert(QStringLiteral("imageUrl"),
+ QStringLiteral("Example documentation should have at least one '\\image'"));
+ attributesToWarnFor.insert(QStringLiteral("projectPath"),
+ QStringLiteral("Example has no project file"));
+
+ for (auto it = attributesToWarnFor.cbegin(); it != attributesToWarnFor.cend(); ++it) {
+ if (!usedAttributes.contains(it.key()))
+ example->doc().location().warning(example->name() + ": " + it.value());
+ }
+}
+
+/*!
+ \internal
+
+ Write the description element. The description for an example is set
+ with the \brief command. If no brief is available, the element is set
+ to "No description available".
+ */
+
+void writeDescription(QXmlStreamWriter *writer, const ExampleNode *example)
+{
+ Q_ASSERT(writer && example);
+ writer->writeStartElement("description");
+ const Text brief = example->doc().briefText();
+ if (!brief.isEmpty())
+ writer->writeCDATA(brief.toString());
+ else
+ writer->writeCDATA(QString("No description available"));
+ writer->writeEndElement(); // description
+}
+
+/*!
+ \internal
+
+ Returns a list of \a files that Qt Creator should open for the \a exampleName.
+ */
+QMap<int, QString> getFilesToOpen(const QStringList &files, const QString &exampleName)
+{
+ QMap<int, QString> filesToOpen;
+ for (const QString &file : files) {
+ QFileInfo fileInfo(file);
+ QString fileName = fileInfo.fileName().toLower();
+ // open .qml, .cpp and .h files with a
+ // basename matching the example (project) name
+ // QMap key indicates the priority -
+ // the lowest value will be the top-most file
+ if ((fileInfo.baseName().compare(exampleName, Qt::CaseInsensitive) == 0)) {
+ if (fileName.endsWith(".qml"))
+ filesToOpen.insert(0, file);
+ else if (fileName.endsWith(".cpp"))
+ filesToOpen.insert(1, file);
+ else if (fileName.endsWith(".h"))
+ filesToOpen.insert(2, file);
+ }
+ // main.qml takes precedence over main.cpp
+ else if (fileName.endsWith("main.qml")) {
+ filesToOpen.insert(3, file);
+ } else if (fileName.endsWith("main.cpp")) {
+ filesToOpen.insert(4, file);
+ }
+ }
+
+ return filesToOpen;
+}
+
+/*!
+ \internal
+ \brief Writes the lists of files to open for the example.
+
+ Writes out the \a filesToOpen and the full \a installPath through \a writer.
+ */
+void writeFilesToOpen(QXmlStreamWriter &writer, const QString &installPath,
+ const QMap<int, QString> &filesToOpen)
+{
+ for (auto it = filesToOpen.constEnd(); it != filesToOpen.constBegin();) {
+ writer.writeStartElement("fileToOpen");
+ if (--it == filesToOpen.constBegin()) {
+ writer.writeAttribute(QStringLiteral("mainFile"), QStringLiteral("true"));
+ }
+ writer.writeCharacters(installPath + it.value());
+ writer.writeEndElement();
+ }
+}
+
+/*!
+ \internal
+ \brief Writes example metadata into \a writer.
+
+ For instance,
+
+
+ \ meta category {Application Example}
+
+ becomes
+
+ <meta>
+ <entry name="category">Application Example</entry>
+ <meta>
+*/
+static void writeMetaInformation(QXmlStreamWriter &writer, const QStringMultiMap &map)
+{
+ if (map.isEmpty())
+ return;
+
+ writer.writeStartElement("meta");
+ for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
+ writer.writeStartElement("entry");
+ writer.writeAttribute(QStringLiteral("name"), it.key());
+ writer.writeCharacters(it.value());
+ writer.writeEndElement(); // tag
+ }
+ writer.writeEndElement(); // meta
+}
+
+/*!
+ \class ManifestWriter
+ \internal
+ \brief The ManifestWriter is responsible for writing manifest files.
+ */
+ManifestWriter::ManifestWriter()
+{
+ Config &config = Config::instance();
+ m_project = config.get(CONFIG_PROJECT).asString();
+ m_outputDirectory = config.getOutputDir();
+ m_qdb = QDocDatabase::qdocDB();
+
+ const QString prefix = CONFIG_QHP + Config::dot + m_project + Config::dot;
+ m_manifestDir =
+ QLatin1String("qthelp://") + config.get(prefix + QLatin1String("namespace")).asString();
+ m_manifestDir +=
+ QLatin1Char('/') + config.get(prefix + QLatin1String("virtualFolder")).asString()
+ + QLatin1Char('/');
+ readManifestMetaContent();
+ m_examplesPath = config.get(CONFIG_EXAMPLESINSTALLPATH).asString();
+ if (!m_examplesPath.isEmpty())
+ m_examplesPath += QLatin1Char('/');
+}
+
+template <typename F>
+void ManifestWriter::processManifestMetaContent(const QString &fullName, F matchFunc)
+{
+ for (const auto &index : m_manifestMetaContent) {
+ const auto &names = index.m_names;
+ for (const QString &name : names) {
+ bool match;
+ qsizetype wildcard = name.indexOf(QChar('*'));
+ switch (wildcard) {
+ case -1: // no wildcard used, exact match required
+ match = (fullName == name);
+ break;
+ case 0: // '*' matches all examples
+ match = true;
+ break;
+ default: // match with wildcard at the end
+ match = fullName.startsWith(name.left(wildcard));
+ }
+ if (match)
+ matchFunc(index);
+ }
+ }
+}
+
+/*!
+ This function outputs one or more manifest files in XML.
+ They are used by Creator.
+ */
+void ManifestWriter::generateManifestFiles()
+{
+ generateExampleManifestFile();
+ m_qdb->exampleNodeMap().clear();
+ m_manifestMetaContent.clear();
+}
+
+/*
+ Returns Qt module name as lower case tag, stripping Qt prefix:
+ QtQuickControls -> quickcontrols
+ QtOpenGL -> opengl
+ QtQuick3D -> quick3d
+ */
+static QString moduleNameAsTag(const QString &module)
+{
+ QString moduleName = module;
+ if (moduleName.startsWith("Qt"))
+ moduleName = moduleName.mid(2);
+ // Some examples are in QtDoc module, but 'doc' as tag makes little sense
+ if (moduleName == "Doc")
+ return QString();
+ return moduleName.toLower();
+}
+
+/*
+ Return tags that were added with
+ \ meta {tag} {tag1[,tag2,...]}
+ or
+ \ meta {tags} {tag1[,tag2,...]}
+ from example metadata
+ */
+static QSet<QString> tagsAddedWithMetaCommand(const ExampleNode *example)
+{
+ Q_ASSERT(example);
+
+ QSet<QString> tags;
+ const QStringMultiMap *metaTagMap = example->doc().metaTagMap();
+ if (metaTagMap) {
+ QStringList originalTags = metaTagMap->values("tag");
+ originalTags << metaTagMap->values("tags");
+ for (const auto &tag : originalTags) {
+ const auto &tagList = tag.toLower().split(QLatin1Char(','), Qt::SkipEmptyParts);
+ tags += QSet<QString>(tagList.constBegin(), tagList.constEnd());
+ }
+ }
+ return tags;
+}
+
+/*
+ Writes the contents of tags into writer, formatted as
+ <tags>tag1,tag2..</tags>
+ */
+static void writeTagsElement(QXmlStreamWriter *writer, const QSet<QString> &tags)
+{
+ Q_ASSERT(writer);
+ if (tags.isEmpty())
+ return;
+
+ writer->writeStartElement("tags");
+ QStringList sortedTags = tags.values();
+ sortedTags.sort();
+ writer->writeCharacters(sortedTags.join(","));
+ writer->writeEndElement(); // tags
+}
+
+/*!
+ This function is called by generateExampleManifestFiles(), once
+ for each manifest file to be generated.
+ */
+void ManifestWriter::generateExampleManifestFile()
+{
+ const ExampleNodeMap &exampleNodeMap = m_qdb->exampleNodeMap();
+ if (exampleNodeMap.isEmpty())
+ return;
+
+ const QString outputFileName = "examples-manifest.xml";
+ QFile outputFile(m_outputDirectory + QLatin1Char('/') + outputFileName);
+ if (!outputFile.open(QFile::WriteOnly | QFile::Text))
+ return;
+
+ QXmlStreamWriter writer(&outputFile);
+ writer.setAutoFormatting(true);
+ writer.writeStartDocument();
+ writer.writeStartElement("instructionals");
+ writer.writeAttribute("module", m_project);
+ writer.writeStartElement("examples");
+
+ for (const auto &example : exampleNodeMap.values()) {
+ QMap<QString, QString> usedAttributes;
+ QSet<QString> tags;
+ const QString installPath = retrieveExampleInstallationPath(example);
+ const QString fullName = m_project + QLatin1Char('/') + example->title();
+
+ processManifestMetaContent(
+ fullName, [&](const ManifestMetaFilter &filter) { tags += filter.m_tags; });
+ tags += tagsAddedWithMetaCommand(example);
+ // omit from the manifest if explicitly marked broken
+ if (tags.contains("broken"))
+ continue;
+
+ // attributes that are always written for the element
+ usedAttributes.insert("name", example->title());
+ usedAttributes.insert("docUrl", m_manifestDir + Generator::currentGenerator()->fileBase(example) + ".html");
+
+ if (!example->projectFile().isEmpty())
+ usedAttributes.insert("projectPath", installPath + example->projectFile());
+ if (!example->imageFileName().isEmpty())
+ usedAttributes.insert("imageUrl", m_manifestDir + example->imageFileName());
+
+ processManifestMetaContent(fullName, [&](const ManifestMetaFilter &filter) {
+ const auto attributes = filter.m_attributes;
+ for (const auto &attribute : attributes) {
+ const QLatin1Char div(':');
+ QStringList attrList = attribute.split(div);
+ if (attrList.size() == 1)
+ attrList.append(QStringLiteral("true"));
+ QString attrName = attrList.takeFirst();
+ if (!usedAttributes.contains(attrName))
+ usedAttributes.insert(attrName, attrList.join(div));
+ }
+ });
+
+ writer.writeStartElement("example");
+ for (auto it = usedAttributes.cbegin(); it != usedAttributes.cend(); ++it)
+ writer.writeAttribute(it.key(), it.value());
+
+ warnAboutUnusedAttributes(usedAttributes.keys(), example);
+ writeDescription(&writer, example);
+
+ const QString moduleNameTag = moduleNameAsTag(m_project);
+ if (!moduleNameTag.isEmpty())
+ tags << moduleNameTag;
+ writeTagsElement(&writer, tags);
+
+ const QString exampleName = example->name().mid(example->name().lastIndexOf('/') + 1);
+ const auto files = example->files();
+ const QMap<int, QString> filesToOpen = getFilesToOpen(files, exampleName);
+ writeFilesToOpen(writer, installPath, filesToOpen);
+
+ if (const QStringMultiMap *metaTagMapP = example->doc().metaTagMap()) {
+ // Write \meta elements into the XML, except for 'tag', 'installpath',
+ // as they are handled separately
+ QStringMultiMap map = *metaTagMapP;
+ erase_if(map, [](QStringMultiMap::iterator iter) {
+ return iter.key() == "tag" || iter.key() == "tags" || iter.key() == "installpath";
+ });
+ writeMetaInformation(writer, map);
+ }
+
+ writer.writeEndElement(); // example
+ }
+
+ writer.writeEndElement(); // examples
+
+ if (!m_exampleCategories.isEmpty()) {
+ writer.writeStartElement("categories");
+ for (const auto &examplecategory : m_exampleCategories) {
+ writer.writeStartElement("category");
+ writer.writeCharacters(examplecategory);
+ writer.writeEndElement();
+ }
+ writer.writeEndElement(); // categories
+ }
+
+ writer.writeEndElement(); // instructionals
+ writer.writeEndDocument();
+ outputFile.close();
+}
+
+/*!
+ Reads metacontent - additional attributes and tags to apply
+ when generating manifest files, read from config.
+
+ The manifest metacontent map is cleared immediately after
+ the manifest files have been generated.
+ */
+void ManifestWriter::readManifestMetaContent()
+{
+ Config &config = Config::instance();
+ const QStringList names{config.get(CONFIG_MANIFESTMETA
+ + Config::dot
+ + QStringLiteral("filters")).asStringList()};
+
+ for (const auto &manifest : names) {
+ ManifestMetaFilter filter;
+ QString prefix = CONFIG_MANIFESTMETA + Config::dot + manifest + Config::dot;
+ filter.m_names = config.get(prefix + QStringLiteral("names")).asStringSet();
+ filter.m_attributes = config.get(prefix + QStringLiteral("attributes")).asStringSet();
+ filter.m_tags = config.get(prefix + QStringLiteral("tags")).asStringSet();
+ m_manifestMetaContent.append(filter);
+ }
+
+ m_exampleCategories = config.get(CONFIG_MANIFESTMETA
+ + QStringLiteral(".examplecategories")).asStringList();
+}
+
+/*!
+ Retrieve the install path for the \a example as specified with
+ the \\meta command, or fall back to the one defined in .qdocconf.
+ */
+QString ManifestWriter::retrieveExampleInstallationPath(const ExampleNode *example) const
+{
+ QString installPath;
+ if (example->doc().metaTagMap())
+ installPath = example->doc().metaTagMap()->value(QLatin1String("installpath"));
+ if (installPath.isEmpty())
+ installPath = m_examplesPath;
+ if (!installPath.isEmpty() && !installPath.endsWith(QLatin1Char('/')))
+ installPath += QLatin1Char('/');
+
+ return installPath;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/manifestwriter.h b/src/qdoc/qdoc/src/qdoc/manifestwriter.h
new file mode 100644
index 000000000..730835b9e
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/manifestwriter.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#ifndef MANIFESTWRITER_H
+#define MANIFESTWRITER_H
+
+#include <QtCore/qlist.h>
+#include <QtCore/qset.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class ExampleNode;
+class QDocDatabase;
+class QXmlStreamWriter;
+class ManifestWriter
+{
+ struct ManifestMetaFilter
+ {
+ QSet<QString> m_names {};
+ QSet<QString> m_attributes {};
+ QSet<QString> m_tags {};
+ };
+
+public:
+ ManifestWriter();
+ void generateManifestFiles();
+ void generateExampleManifestFile();
+ void readManifestMetaContent();
+ QString retrieveExampleInstallationPath(const ExampleNode *example) const;
+
+private:
+ QString m_manifestDir {};
+ QString m_examplesPath {};
+ QString m_outputDirectory {};
+ QString m_project {};
+ QDocDatabase *m_qdb { nullptr };
+ QList<ManifestMetaFilter> m_manifestMetaContent {};
+ QStringList m_exampleCategories {};
+
+ template <typename F>
+ void processManifestMetaContent(const QString &fullName, F matchFunc);
+};
+
+QT_END_NAMESPACE
+
+#endif // MANIFESTWRITER_H
diff --git a/src/qdoc/qdoc/src/qdoc/namespacenode.cpp b/src/qdoc/qdoc/src/qdoc/namespacenode.cpp
new file mode 100644
index 000000000..22686c050
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/namespacenode.cpp
@@ -0,0 +1,190 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "namespacenode.h"
+
+#include "codeparser.h"
+#include "tree.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class NamespaceNode
+ \brief This class represents a C++ namespace.
+
+ A namespace can be used in multiple C++ modules, so there
+ can be a NamespaceNode for namespace Xxx in more than one
+ Node tree.
+ */
+
+/*! \fn NamespaceNode(Aggregate *parent, const QString &name)
+ Constructs a NamespaceNode with the specified \a parent and \a name.
+ The node type is Node::Namespace.
+ */
+
+/*!
+ Returns true if this namespace is to be documented in the
+ current module. There can be elements declared in this
+ namespace spread over multiple modules. Those elements are
+ documented in the modules where they are declared, but they
+ are linked to from the namespace page in the module where
+ the namespace itself is documented.
+ */
+bool NamespaceNode::isDocumentedHere() const
+{
+ return m_whereDocumented == tree()->camelCaseModuleName();
+}
+
+/*!
+ Returns true if this namespace node contains at least one
+ child that has documentation and is not private or internal.
+ */
+bool NamespaceNode::hasDocumentedChildren() const
+{
+ return std::any_of(m_children.cbegin(), m_children.cend(),
+ [](Node *child) { return child->isInAPI(); });
+}
+
+/*!
+ Report qdoc warning for each documented child in a namespace
+ that is not documented. This function should only be called
+ when the namespace is not documented.
+ */
+void NamespaceNode::reportDocumentedChildrenInUndocumentedNamespace() const
+{
+ for (const auto *node : std::as_const(m_children)) {
+ if (node->isInAPI()) {
+ QString msg1 = node->name();
+ if (node->isFunction())
+ msg1 += "()";
+ msg1 += QStringLiteral(
+ " is documented, but namespace %1 is not documented in any module.")
+ .arg(name());
+ QString msg2 =
+ QStringLiteral(
+ "Add /*! '\\%1 %2' ... */ or remove the qdoc comment marker (!) at "
+ "that line number.")
+ .arg(COMMAND_NAMESPACE, name());
+
+ node->doc().location().warning(msg1, msg2);
+ }
+ }
+}
+
+/*!
+ Returns true if this namespace node is not private and
+ contains at least one public child node with documentation.
+ */
+bool NamespaceNode::docMustBeGenerated() const
+{
+ if (isInAPI())
+ return true;
+ return hasDocumentedChildren();
+}
+
+/*!
+ Returns a const reference to the namespace node's list of
+ included children, which contains pointers to all the child
+ nodes of other namespace nodes that have the same name as
+ this namespace node. The list is built after the prepare
+ phase has been run but just before the generate phase. It
+ is buils by QDocDatabase::resolveNamespaces().
+
+ \sa QDocDatabase::resolveNamespaces()
+ */
+const NodeList &NamespaceNode::includedChildren() const
+{
+ return m_includedChildren;
+}
+
+/*!
+ This function is only called from QDocDatabase::resolveNamespaces().
+
+ \sa includedChildren(), QDocDatabase::resolveNamespaces()
+ */
+void NamespaceNode::includeChild(Node *child)
+{
+ m_includedChildren.append(child);
+}
+
+/*! \fn Tree* NamespaceNode::tree() const
+ Returns a pointer to the Tree that contains this NamespaceNode.
+ This requires traversing the parent() pointers to the root of
+ the Tree, which is the unnamed NamespaceNode.
+ */
+
+/*! \fn bool NamespaceNode::isFirstClassAggregate() const
+ Returns \c true.
+ */
+
+/*! \fn bool NamespaceNode::isRelatableType() const
+ Returns \c true.
+ */
+
+/*! \fn bool NamespaceNode::wasSeen() const
+ Returns \c true if the \c {\\namespace} command that this NamespaceNode
+ represents has been parsed by qdoc. When \c false is returned, it means
+ that only \c {\\relates} commands have been seen that relate elements to
+ this namespace.
+ */
+
+/*! \fn void NamespaceNode::markSeen()
+ Sets the data member that indicates that the \c {\\namespace} command this
+ NamespaceNode represents has been parsed by qdoc.
+ */
+
+/*! \fn void NamespaceNode::markNotSeen()
+ Clears the data member that indicates that the \c {\\namespace} command this
+ NamespaceNode represents has been parsed by qdoc.
+ */
+
+/*! \fn void NamespaceNode::setTree(Tree* t)
+ Sets the Tree pointer to \a t, which means this NamespaceNode is in the Tree \a t.
+ */
+
+/*! \fn QString NamespaceNode::whereDocumented() const
+ Returns the camel case name of the module where this namespace is documented.
+
+ \sa setWhereDocumented()
+ */
+
+/*! \fn void NamespaceNode::setWhereDocumented(const QString &t)
+ Sets the camel case name of the module where this namespace is documented to
+ the module named \a t.
+
+ This function is called when the \c {\\namespace} command is processed to let
+ qdoc know that this namespace is documented in the current module, so that
+ when something in another module is marked as related to this namespace, it
+ can be documented there with a ProxyNode for this namespace.
+
+ \sa whereDocumented()
+ */
+
+/*! \fn void NamespaceNode::setDocumented()
+ Sets the flag indicating that the \c {\\namespace} command for this
+ namespace was seen.
+ */
+
+/*! \fn bool NamespaceNode::wasDocumented() const
+ Returns \c true if a \c {\\namespace} command for this namespace was seen.
+ Otherwise returns \c false.
+ */
+
+/*! \fn void NamespaceNode::setDocNode(NamespaceNode *ns)
+ Called in QDocDatabase::resolveNamespaces() to set the pointer to the
+ NamespaceNode in which this namespace is documented.
+
+ \sa QDocDatabase::resolveNamespaces()
+ */
+
+/*! \fn NamespaceNode *NamespaceNode::docNode() const
+ Returns a pointer to the NamespaceNode that represents where the namespace
+ documentation is actually generated. API elements in many different modules
+ can be included in a single namespace. That namespace is only documented in
+ one module. The namespace is documented in the module where the \c {\\namespace}
+ command for the namespace appears.
+
+ \sa QDocDatabase::resolveNamespaces()
+ */
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/namespacenode.h b/src/qdoc/qdoc/src/qdoc/namespacenode.h
new file mode 100644
index 000000000..80d068838
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/namespacenode.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef NAMESPACENODE_H
+#define NAMESPACENODE_H
+
+#include "aggregate.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class Tree;
+
+class NamespaceNode : public Aggregate
+{
+public:
+ NamespaceNode(Aggregate *parent, const QString &name) : Aggregate(Namespace, parent, name) {}
+ ~NamespaceNode() override = default;
+ [[nodiscard]] Tree *tree() const override { return (parent() ? parent()->tree() : m_tree); }
+
+ [[nodiscard]] bool isFirstClassAggregate() const override { return true; }
+ [[nodiscard]] bool isRelatableType() const override { return true; }
+ [[nodiscard]] bool wasSeen() const override { return m_seen; }
+ void markSeen() { m_seen = true; }
+ void setTree(Tree *t) { m_tree = t; }
+ [[nodiscard]] const NodeList &includedChildren() const;
+ void includeChild(Node *child);
+ void setWhereDocumented(const QString &t) { m_whereDocumented = t; }
+ [[nodiscard]] bool isDocumentedHere() const;
+ [[nodiscard]] bool hasDocumentedChildren() const;
+ void reportDocumentedChildrenInUndocumentedNamespace() const;
+ [[nodiscard]] bool docMustBeGenerated() const override;
+ void setDocNode(NamespaceNode *ns) { m_docNode = ns; }
+ [[nodiscard]] NamespaceNode *docNode() const { return m_docNode; }
+
+private:
+ bool m_seen { false };
+ Tree *m_tree { nullptr };
+ QString m_whereDocumented {};
+ NamespaceNode *m_docNode { nullptr };
+ NodeList m_includedChildren {};
+};
+
+QT_END_NAMESPACE
+
+#endif // NAMESPACENODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/node.cpp b/src/qdoc/qdoc/src/qdoc/node.cpp
new file mode 100644
index 000000000..1aadbdeb1
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/node.cpp
@@ -0,0 +1,1383 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "node.h"
+
+#include "aggregate.h"
+#include "codemarker.h"
+#include "config.h"
+#include "enumnode.h"
+#include "functionnode.h"
+#include "generator.h"
+#include "qdocdatabase.h"
+#include "qmltypenode.h"
+#include "qmlpropertynode.h"
+#include "relatedclass.h"
+#include "sharedcommentnode.h"
+#include "tokenizer.h"
+#include "tree.h"
+
+#include <QtCore/quuid.h>
+#include <QtCore/qversionnumber.h>
+
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class Node
+ \brief The Node class is the base class for all the nodes in QDoc's parse tree.
+
+ Class Node is the base class of all the node subclasses. There is a subclass of Node
+ for each type of entity that QDoc can document. The types of entities that QDoc can
+ document are listed in the enum type NodeType.
+
+ After ClangCodeParser has parsed all the header files to build its precompiled header,
+ it then visits the clang Abstract Syntax Tree (AST). For each node in the AST that it
+ determines is in the public API and must be documented, it creates an instance of one
+ of the Node subclasses and adds it to the QDoc Tree.
+
+ Each instance of a sublass of Node has a parent pointer to link it into the Tree. The
+ parent pointer is obtained by calling \l {parent()}, which returns a pointer to an
+ instance of the Node subclass, Aggregate, which is never instantiated directly, but
+ as the base class for certain subclasses of Node that can have children. For example,
+ ClassNode and QmlTypeNode can have children, so they both inherit Aggregate, while
+ PropertyNode and QmlPropertyNode can not have children, so they both inherit Node.
+
+ \sa Aggregate, ClassNode, PropertyNode
+ */
+
+/*!
+ Returns \c true if the node \a n1 is less than node \a n2. The
+ comparison is performed by comparing properties of the nodes
+ in order of increasing complexity.
+ */
+bool Node::nodeNameLessThan(const Node *n1, const Node *n2)
+{
+#define LT_RETURN_IF_NOT_EQUAL(a, b) \
+ if ((a) != (b)) \
+ return (a) < (b);
+
+ if (n1->isPageNode() && n2->isPageNode()) {
+ LT_RETURN_IF_NOT_EQUAL(n1->fullName(), n2->fullName());
+ LT_RETURN_IF_NOT_EQUAL(n1->fullTitle(), n2->fullTitle());
+ }
+
+ if (n1->isFunction() && n2->isFunction()) {
+ const auto *f1 = static_cast<const FunctionNode *>(n1);
+ const auto *f2 = static_cast<const FunctionNode *>(n2);
+
+ LT_RETURN_IF_NOT_EQUAL(f1->isConst(), f2->isConst());
+ LT_RETURN_IF_NOT_EQUAL(f1->signature(Node::SignatureReturnType),
+ f2->signature(Node::SignatureReturnType));
+ }
+
+ LT_RETURN_IF_NOT_EQUAL(n1->nodeType(), n2->nodeType());
+ LT_RETURN_IF_NOT_EQUAL(n1->name(), n2->name());
+ LT_RETURN_IF_NOT_EQUAL(n1->access(), n2->access());
+ LT_RETURN_IF_NOT_EQUAL(n1->location().filePath(), n2->location().filePath());
+
+ return false;
+}
+
+/*!
+ \enum Node::NodeType
+
+ An unsigned char value that identifies an object as a
+ particular subclass of Node.
+
+ \value NoType The node type has not been set yet.
+ \value Namespace The Node subclass is NamespaceNode, which represents a C++
+ namespace.
+ \value Class The Node subclass is ClassNode, which represents a C++ class.
+ \value Struct The Node subclass is ClassNode, which represents a C struct.
+ \value Union The Node subclass is ClassNode, which represents a C union
+ (currently no known uses).
+ \value HeaderFile The Node subclass is HeaderNode, which represents a header
+ file.
+ \value Page The Node subclass is PageNode, which represents a text page from
+ a .qdoc file.
+ \value Enum The Node subclass is EnumNode, which represents an enum type or
+ enum class.
+ \value Example The Node subclass is ExampleNode, which represents an example
+ subdirectory.
+ \value ExternalPage The Node subclass is ExternalPageNode, which is for
+ linking to an external page.
+ \value Function The Node subclass is FunctionNode, which can represent C++,
+ and QML functions.
+ \value Typedef The Node subclass is TypedefNode, which represents a C++
+ typedef.
+ \value Property The Node subclass is PropertyNode, which represents a use of
+ Q_Property.
+ \value Variable The Node subclass is VariableNode, which represents a global
+ variable or class data member.
+ \value Group The Node subclass is CollectionNode, which represents a group of
+ documents.
+ \value Module The Node subclass is CollectionNode, which represents a C++
+ module.
+ \value QmlType The Node subclass is QmlTypeNode, which represents a QML type.
+ \value QmlModule The Node subclass is CollectionNode, which represents a QML
+ module.
+ \value QmlProperty The Node subclass is QmlPropertyNode, which represents a
+ property in a QML type.
+ \value QmlBasicType The Node subclass is QmlTypeNode, which represents a
+ value type like int, etc.
+ \value SharedComment The Node subclass is SharedCommentNode, which represents
+ a collection of nodes that share the same documentation comment.
+ \omitvalue Collection
+ \value Proxy The Node subclass is ProxyNode, which represents one or more
+ entities that are documented in the current module but which actually
+ reside in a different module.
+ \omitvalue LastType
+*/
+
+/*!
+ \enum Node::Genus
+
+ An unsigned char value that specifies whether the Node represents a
+ C++ element, a QML element, or a text document.
+ The Genus values are also passed to search functions to specify the
+ Genus of Tree Node that can satisfy the search.
+
+ \value DontCare The Genus is not specified. Used when calling Tree search functions to indicate
+ the search can accept any Genus of Node.
+ \value CPP The Node represents a C++ element.
+ \value QML The Node represents a QML element.
+ \value DOC The Node represents a text document.
+*/
+
+/*!
+ \internal
+ \fn setComparisonCategory(const ComparisonCategory category)
+
+ Sets the comparison category of this node to \a category.
+
+ \sa ComparisonCategory, comparisonCategory()
+ */
+
+/*!
+ \internal
+ \fn ComparisonCategory comparisonCategory()
+
+ Returns the comparison category of this node.
+
+ \sa ComparisonCategory, setComparisonCategory()
+ */
+
+/*!
+ \enum Access
+
+ An unsigned char value that indicates the C++ access level.
+
+ \value Public The element has public access.
+ \value Protected The element has protected access.
+ \value Private The element has private access.
+*/
+
+/*!
+ \enum Node::Status
+
+ An unsigned char that specifies the status of the documentation element in
+ the documentation set.
+
+ \value Deprecated The element has been deprecated.
+ \value Preliminary The element is new; the documentation is preliminary.
+ \value Active The element is current.
+ \value Internal The element is for internal use only, not to be published.
+ \value DontDocument The element is not to be documented.
+*/
+
+/*!
+ \enum Node::ThreadSafeness
+
+ An unsigned char that specifies the degree of thread-safeness of the element.
+
+ \value UnspecifiedSafeness The thread-safeness is not specified.
+ \value NonReentrant The element is not reentrant.
+ \value Reentrant The element is reentrant.
+ \value ThreadSafe The element is threadsafe.
+*/
+
+/*!
+ \enum Node::LinkType
+
+ An unsigned char value that probably should be moved out of the Node base class.
+
+ \value StartLink
+ \value NextLink
+ \value PreviousLink
+ \value ContentsLink
+ */
+
+/*!
+ \enum Node::FlagValue
+
+ A value used in PropertyNode and QmlPropertyNode that can be -1, 0, or +1.
+ Properties and QML properties have flags, which can be 0 or 1, false or true,
+ or not set. FlagValueDefault is the not set value. In other words, if a flag
+ is set to FlagValueDefault, the meaning is the flag has not been set.
+
+ \value FlagValueDefault -1 Not set.
+ \value FlagValueFalse 0 False.
+ \value FlagValueTrue 1 True.
+*/
+
+/*!
+ \fn Node::~Node()
+
+ The default destructor is virtual so any subclass of Node can be
+ deleted by deleting a pointer to Node.
+ */
+
+/*! \fn bool Node::isActive() const
+ Returns true if this node's status is \c Active.
+ */
+
+/*! \fn bool Node::isClass() const
+ Returns true if the node type is \c Class.
+ */
+
+/*! \fn bool Node::isCppNode() const
+ Returns true if this node's Genus value is \c CPP.
+ */
+
+/*! \fn bool Node::isDeprecated() const
+ Returns true if this node's status is \c Deprecated.
+ */
+
+/*! \fn bool Node::isDontDocument() const
+ Returns true if this node's status is \c DontDocument.
+ */
+
+/*! \fn bool Node::isEnumType() const
+ Returns true if the node type is \c Enum.
+ */
+
+/*! \fn bool Node::isExample() const
+ Returns true if the node type is \c Example.
+ */
+
+/*! \fn bool Node::isExternalPage() const
+ Returns true if the node type is \c ExternalPage.
+ */
+
+/*! \fn bool Node::isFunction(Genus g = DontCare) const
+ Returns true if this is a FunctionNode and its Genus is set to \a g.
+ */
+
+/*! \fn bool Node::isGroup() const
+ Returns true if the node type is \c Group.
+ */
+
+/*! \fn bool Node::isHeader() const
+ Returns true if the node type is \c HeaderFile.
+ */
+
+/*! \fn bool Node::isIndexNode() const
+ Returns true if this node was created from something in an index file.
+ */
+
+/*! \fn bool Node::isModule() const
+ Returns true if the node type is \c Module.
+ */
+
+/*! \fn bool Node::isNamespace() const
+ Returns true if the node type is \c Namespace.
+ */
+
+/*! \fn bool Node::isPage() const
+ Returns true if the node type is \c Page.
+ */
+
+/*! \fn bool Node::isPreliminary() const
+ Returns true if this node's status is \c Preliminary.
+ */
+
+/*! \fn bool Node::isPrivate() const
+ Returns true if this node's access is \c Private.
+ */
+
+/*! \fn bool Node::isProperty() const
+ Returns true if the node type is \c Property.
+ */
+
+/*! \fn bool Node::isProxyNode() const
+ Returns true if the node type is \c Proxy.
+ */
+
+/*! \fn bool Node::isPublic() const
+ Returns true if this node's access is \c Public.
+ */
+
+/*! \fn bool Node::isProtected() const
+ Returns true if this node's access is \c Protected.
+ */
+
+/*! \fn bool Node::isQmlBasicType() const
+ Returns true if the node type is \c QmlBasicType.
+ */
+
+/*! \fn bool Node::isQmlModule() const
+ Returns true if the node type is \c QmlModule.
+ */
+
+/*! \fn bool Node::isQmlNode() const
+ Returns true if this node's Genus value is \c QML.
+ */
+
+/*! \fn bool Node::isQmlProperty() const
+ Returns true if the node type is \c QmlProperty.
+ */
+
+/*! \fn bool Node::isQmlType() const
+ Returns true if the node type is \c QmlType or \c QmlValueType.
+ */
+
+/*! \fn bool Node::isRelatedNonmember() const
+ Returns true if this is a related nonmember of something.
+ */
+
+/*! \fn bool Node::isStruct() const
+ Returns true if the node type is \c Struct.
+ */
+
+/*! \fn bool Node::isSharedCommentNode() const
+ Returns true if the node type is \c SharedComment.
+ */
+
+/*! \fn bool Node::isTypeAlias() const
+ Returns true if the node type is \c Typedef.
+ */
+
+/*! \fn bool Node::isTypedef() const
+ Returns true if the node type is \c Typedef.
+ */
+
+/*! \fn bool Node::isUnion() const
+ Returns true if the node type is \c Union.
+ */
+
+/*! \fn bool Node::isVariable() const
+ Returns true if the node type is \c Variable.
+ */
+
+/*! \fn bool Node::isGenericCollection() const
+ Returns true if the node type is \c Collection.
+ */
+
+/*! \fn bool Node::isAbstract() const
+ Returns true if the ClassNode or QmlTypeNode is marked abstract.
+*/
+
+/*! \fn bool Node::isAggregate() const
+ Returns true if this node is an aggregate, which means it
+ inherits Aggregate and can therefore have children.
+*/
+
+/*! \fn bool Node::isFirstClassAggregate() const
+ Returns true if this Node is an Aggregate but not a ProxyNode.
+*/
+
+/*! \fn bool Node::isAlias() const
+ Returns true if this QML property is marked as an alias.
+*/
+
+/*! \fn bool Node::isAttached() const
+ Returns true if the QML property or QML method node is marked as attached.
+*/
+
+/*! \fn bool Node::isClassNode() const
+ Returns true if this is an instance of ClassNode.
+*/
+
+/*! \fn bool Node::isCollectionNode() const
+ Returns true if this is an instance of CollectionNode.
+*/
+
+/*! \fn bool Node::isDefault() const
+ Returns true if the QML property node is marked as default.
+*/
+
+/*! \fn bool Node::isMacro() const
+ returns true if either FunctionNode::isMacroWithParams() or
+ FunctionNode::isMacroWithoutParams() returns true.
+*/
+
+/*! \fn bool Node::isPageNode() const
+ Returns true if this node represents something that generates a documentation
+ page. In other words, if this Node's subclass inherits PageNode, then this
+ function will return \e true.
+*/
+
+/*! \fn bool Node::isRelatableType() const
+ Returns true if this node is something you can relate things to with
+ the \e relates command. NamespaceNode, ClassNode, HeaderNode, and
+ ProxyNode are relatable types.
+*/
+
+/*! \fn bool Node::isMarkedReimp() const
+ Returns true if the FunctionNode is marked as a reimplemented function.
+ That means it is virtual in the base class and it is reimplemented in
+ the subclass.
+*/
+
+/*! \fn bool Node::isPropertyGroup() const
+ Returns true if the node is a SharedCommentNode for documenting
+ multiple C++ properties or multiple QML properties.
+*/
+
+/*! \fn bool Node::isStatic() const
+ Returns true if the FunctionNode represents a static function.
+*/
+
+/*! \fn bool Node::isTextPageNode() const
+ Returns true if the node is a PageNode but not an Aggregate.
+*/
+
+/*!
+ Returns this node's name member. Appends "()" to the returned
+ name if this node is a function node, but not if it is a macro
+ because macro names normally appear without parentheses.
+ */
+QString Node::plainName() const
+{
+ if (isFunction() && !isMacro())
+ return m_name + QLatin1String("()");
+ return m_name;
+}
+
+/*!
+ Constructs and returns the node's fully qualified name by
+ recursively ascending the parent links and prepending each
+ parent name + "::". Breaks out when reaching a HeaderNode,
+ or when the parent pointer is \a relative. Typically, calls
+ to this function pass \c nullptr for \a relative.
+ */
+QString Node::plainFullName(const Node *relative) const
+{
+ if (m_name.isEmpty())
+ return QLatin1String("global");
+ if (isHeader())
+ return plainName();
+
+ QStringList parts;
+ const Node *node = this;
+ while (node && !node->isHeader()) {
+ parts.prepend(node->plainName());
+ if (node->parent() == relative || node->parent()->name().isEmpty())
+ break;
+ node = node->parent();
+ }
+ return parts.join(QLatin1String("::"));
+}
+
+/*!
+ Constructs and returns the node's fully qualified signature
+ by recursively ascending the parent links and prepending each
+ parent name + "::" to the plain signature. The return type is
+ not included.
+ */
+QString Node::plainSignature() const
+{
+ if (m_name.isEmpty())
+ return QLatin1String("global");
+
+ QString fullName;
+ const Node *node = this;
+ while (node) {
+ fullName.prepend(node->signature(Node::SignaturePlain));
+ if (node->parent()->name().isEmpty())
+ break;
+ fullName.prepend(QLatin1String("::"));
+ node = node->parent();
+ }
+ return fullName;
+}
+
+/*!
+ Constructs and returns this node's full name. The full name is
+ often just the title(). When it is not the title, it is the
+ plainFullName().
+ */
+QString Node::fullName(const Node *relative) const
+{
+ if ((isTextPageNode() || isGroup()) && !title().isEmpty())
+ return title();
+ return plainFullName(relative);
+}
+
+/*!
+ Sets this Node's Doc to \a doc. If \a replace is false and
+ this Node already has a Doc, and if this doc is not marked
+ with the \\reimp command, a warning is reported that the
+ existing Doc is being overridden, and it reports where the
+ previous Doc was found. If \a replace is true, the Doc is
+ replaced silently.
+ */
+void Node::setDoc(const Doc &doc, bool replace)
+{
+ if (!m_doc.isEmpty() && !replace && !doc.isMarkedReimp()) {
+ doc.location().warning(QStringLiteral("Overrides a previous doc"),
+ QStringLiteral("from here: %1").arg(m_doc.location().toString()));
+ }
+ m_doc = doc;
+}
+
+/*!
+ Sets the node's status to \a t.
+
+ \sa Status
+*/
+void Node::setStatus(Status t)
+{
+ m_status = t;
+
+ // Set non-null, empty URL to nodes that are ignored as
+ // link targets
+ switch (t) {
+ case Internal:
+ if (Config::instance().showInternal())
+ break;
+ Q_FALLTHROUGH();
+ case DontDocument:
+ m_url = QStringLiteral("");
+ break;
+ default:
+ break;
+ }
+}
+
+/*!
+ Construct a node with the given \a type and having the
+ given \a parent and \a name. The new node is added to the
+ parent's child list.
+ */
+Node::Node(NodeType type, Aggregate *parent, QString name)
+ : m_nodeType(type),
+ m_indexNodeFlag(false),
+ m_relatedNonmember(false),
+ m_hadDoc(false),
+ m_parent(parent),
+ m_name(std::move(name))
+{
+ if (m_parent)
+ m_parent->addChild(this);
+
+ setGenus(getGenus(type));
+}
+
+/*!
+ Determines the appropriate Genus value for the NodeType
+ value \a t and returns that Genus value. Note that this
+ function is called in the Node() constructor. It always
+ returns Node::CPP when \a t is Node::Function, which
+ means the FunctionNode() constructor must determine its
+ own Genus value separately, because class FunctionNode
+ is a subclass of Node.
+ */
+Node::Genus Node::getGenus(Node::NodeType t)
+{
+ switch (t) {
+ case Node::Enum:
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ case Node::Module:
+ case Node::TypeAlias:
+ case Node::Typedef:
+ case Node::Property:
+ case Node::Variable:
+ case Node::Function:
+ case Node::Namespace:
+ case Node::HeaderFile:
+ return Node::CPP;
+ case Node::QmlType:
+ case Node::QmlModule:
+ case Node::QmlProperty:
+ case Node::QmlValueType:
+ return Node::QML;
+ case Node::Page:
+ case Node::Group:
+ case Node::Example:
+ case Node::ExternalPage:
+ return Node::DOC;
+ case Node::Collection:
+ case Node::SharedComment:
+ case Node::Proxy:
+ default:
+ return Node::DontCare;
+ }
+}
+
+/*! \fn QString Node::url() const
+ Returns the node's URL, which is the url of the documentation page
+ created for the node or the url of an external page if the node is
+ an ExternalPageNode. The url is used for generating a link to the
+ page the node represents.
+
+ \sa Node::setUrl()
+ */
+
+/*! \fn void Node::setUrl(const QString &url)
+ Sets the node's URL to \a url, which is the url to the page that the
+ node represents. This function is only called when an index file is
+ read. In other words, a node's url is set when qdoc decides where its
+ page will be and what its name will be, which happens when qdoc writes
+ the index file for the module being documented.
+
+ \sa QDocIndexFiles
+ */
+
+/*!
+ Returns this node's type as a string for use as an
+ attribute value in XML or HTML.
+ */
+QString Node::nodeTypeString() const
+{
+ if (isFunction()) {
+ const auto *fn = static_cast<const FunctionNode *>(this);
+ return fn->kindString();
+ }
+ return nodeTypeString(nodeType());
+}
+
+/*!
+ Returns the node type \a t as a string for use as an
+ attribute value in XML or HTML.
+ */
+QString Node::nodeTypeString(NodeType t)
+{
+ switch (t) {
+ case Namespace:
+ return QLatin1String("namespace");
+ case Class:
+ return QLatin1String("class");
+ case Struct:
+ return QLatin1String("struct");
+ case Union:
+ return QLatin1String("union");
+ case HeaderFile:
+ return QLatin1String("header");
+ case Page:
+ return QLatin1String("page");
+ case Enum:
+ return QLatin1String("enum");
+ case Example:
+ return QLatin1String("example");
+ case ExternalPage:
+ return QLatin1String("external page");
+ case TypeAlias:
+ case Typedef:
+ return QLatin1String("typedef");
+ case Function:
+ return QLatin1String("function");
+ case Property:
+ return QLatin1String("property");
+ case Proxy:
+ return QLatin1String("proxy");
+ case Variable:
+ return QLatin1String("variable");
+ case Group:
+ return QLatin1String("group");
+ case Module:
+ return QLatin1String("module");
+
+ case QmlType:
+ return QLatin1String("QML type");
+ case QmlValueType:
+ return QLatin1String("QML value type");
+ case QmlModule:
+ return QLatin1String("QML module");
+ case QmlProperty:
+ return QLatin1String("QML property");
+
+ case SharedComment:
+ return QLatin1String("shared comment");
+ case Collection:
+ return QLatin1String("collection");
+ default:
+ break;
+ }
+ return QString();
+}
+
+/*! Converts the boolean value \a b to an enum representation
+ of the boolean type, which includes an enum value for the
+ \e {default value} of the item, i.e. true, false, or default.
+ */
+Node::FlagValue Node::toFlagValue(bool b)
+{
+ return b ? FlagValueTrue : FlagValueFalse;
+}
+
+/*!
+ Converts the enum \a fv back to a boolean value.
+ If \a fv is neither the true enum value nor the
+ false enum value, the boolean value returned is
+ \a defaultValue.
+ */
+bool Node::fromFlagValue(FlagValue fv, bool defaultValue)
+{
+ switch (fv) {
+ case FlagValueTrue:
+ return true;
+ case FlagValueFalse:
+ return false;
+ default:
+ return defaultValue;
+ }
+}
+
+/*!
+ This function creates a pair that describes a link.
+ The pair is composed from \a link and \a desc. The
+ \a linkType is the map index the pair is filed under.
+ */
+void Node::setLink(LinkType linkType, const QString &link, const QString &desc)
+{
+ std::pair<QString, QString> linkPair;
+ linkPair.first = link;
+ linkPair.second = desc;
+ m_linkMap[linkType] = linkPair;
+}
+
+/*!
+ Sets the information about the project and version a node was introduced
+ in, unless the version is lower than the 'ignoresince.<project>'
+ configuration variable.
+ */
+void Node::setSince(const QString &since)
+{
+ QStringList parts = since.split(QLatin1Char(' '));
+ QString project;
+ if (parts.size() > 1)
+ project = Config::dot + parts.first();
+
+ QVersionNumber cutoff =
+ QVersionNumber::fromString(Config::instance().get(CONFIG_IGNORESINCE + project).asString())
+ .normalized();
+
+ if (!cutoff.isNull() && QVersionNumber::fromString(parts.last()).normalized() < cutoff)
+ return;
+
+ m_since = parts.join(QLatin1Char(' '));
+}
+
+/*!
+ Extract a class name from the type \a string and return it.
+ */
+QString Node::extractClassName(const QString &string) const
+{
+ QString result;
+ for (int i = 0; i <= string.size(); ++i) {
+ QChar ch;
+ if (i != string.size())
+ ch = string.at(i);
+
+ QChar lower = ch.toLower();
+ if ((lower >= QLatin1Char('a') && lower <= QLatin1Char('z')) || ch.digitValue() >= 0
+ || ch == QLatin1Char('_') || ch == QLatin1Char(':')) {
+ result += ch;
+ } else if (!result.isEmpty()) {
+ if (result != QLatin1String("const"))
+ return result;
+ result.clear();
+ }
+ }
+ return result;
+}
+
+/*!
+ Returns the thread safeness value for whatever this node
+ represents. But if this node has a parent and the thread
+ safeness value of the parent is the same as the thread
+ safeness value of this node, what is returned is the
+ value \c{UnspecifiedSafeness}. Why?
+ */
+Node::ThreadSafeness Node::threadSafeness() const
+{
+ if (m_parent && m_safeness == m_parent->inheritedThreadSafeness())
+ return UnspecifiedSafeness;
+ return m_safeness;
+}
+
+/*!
+ If this node has a parent, the parent's thread safeness
+ value is returned. Otherwise, this node's thread safeness
+ value is returned. Why?
+ */
+Node::ThreadSafeness Node::inheritedThreadSafeness() const
+{
+ if (m_parent && m_safeness == UnspecifiedSafeness)
+ return m_parent->inheritedThreadSafeness();
+ return m_safeness;
+}
+
+/*!
+ Returns \c true if the node's status is \c Internal, or if
+ its parent is a class with \c Internal status.
+ */
+bool Node::isInternal() const
+{
+ if (status() == Internal)
+ return true;
+ return parent() && parent()->status() == Internal && !parent()->isAbstract();
+}
+
+/*! \fn void Node::markInternal()
+ Sets the node's access to Private and its status to Internal.
+ */
+
+/*!
+ Returns a pointer to the root of the Tree this node is in.
+ */
+Aggregate *Node::root() const
+{
+ if (parent() == nullptr)
+ return (this->isAggregate() ? static_cast<Aggregate *>(const_cast<Node *>(this)) : nullptr);
+ Aggregate *t = parent();
+ while (t->parent() != nullptr)
+ t = t->parent();
+ return t;
+}
+
+/*!
+ Returns a pointer to the Tree this node is in.
+ */
+Tree *Node::tree() const
+{
+ return root()->tree();
+}
+
+/*!
+ Sets the node's declaration location, its definition
+ location, or both, depending on the suffix of the file
+ name from the file path in location \a t.
+ */
+void Node::setLocation(const Location &t)
+{
+ QString suffix = t.fileSuffix();
+ if (suffix == "h")
+ m_declLocation = t;
+ else if (suffix == "cpp")
+ m_defLocation = t;
+ else {
+ m_declLocation = t;
+ m_defLocation = t;
+ }
+}
+
+/*!
+ Returns \c true if this node is documented, or it represents
+ a documented node read from the index ('had doc'), or this
+ node is sharing a non-empty doc with other nodes.
+
+ \sa Doc
+ */
+bool Node::hasDoc() const
+{
+ if (m_hadDoc)
+ return true;
+
+ if (!m_doc.isEmpty())
+ return true;
+
+ return (m_sharedCommentNode && m_sharedCommentNode->hasDoc());
+}
+
+/*!
+ Returns the CPP node's qualified name by prepending the
+ namespaces name + "::" if there isw a namespace.
+ */
+QString Node::qualifyCppName()
+{
+ if (m_parent && m_parent->isNamespace() && !m_parent->name().isEmpty())
+ return m_parent->name() + "::" + m_name;
+ return m_name;
+}
+
+/*!
+ Return the name of this node qualified with the parent name
+ and "::" if there is a parent name.
+ */
+QString Node::qualifyWithParentName()
+{
+ if (m_parent && !m_parent->name().isEmpty())
+ return m_parent->name() + "::" + m_name;
+ return m_name;
+}
+
+/*!
+ Returns the QML node's qualified name by prepending the logical
+ module name.
+ */
+QString Node::qualifyQmlName()
+{
+ return logicalModuleName() + "::" + m_name;
+}
+
+/*!
+ Returns \c true if the node is a class node or a QML type node
+ that is marked as being a wrapper class or wrapper QML type,
+ or if it is a member of a wrapper class or type.
+ */
+bool Node::isWrapper() const
+{
+ return m_parent != nullptr && m_parent->isWrapper();
+}
+
+/*!
+ Construct the full document name for this node and return it.
+ */
+QString Node::fullDocumentName() const
+{
+ QStringList pieces;
+ const Node *n = this;
+
+ do {
+ if (!n->name().isEmpty())
+ pieces.insert(0, n->name());
+
+ if (n->isQmlType() && !n->logicalModuleName().isEmpty()) {
+ pieces.insert(0, n->logicalModuleName());
+ break;
+ }
+
+ if (n->isTextPageNode())
+ break;
+
+ // Examine the parent if the node is a member
+ if (!n->parent() || n->isRelatedNonmember())
+ break;
+
+ n = n->parent();
+ } while (true);
+
+ // Create a name based on the type of the ancestor node.
+ QString concatenator = "::";
+ if (n->isQmlType())
+ concatenator = QLatin1Char('.');
+
+ if (n->isTextPageNode())
+ concatenator = QLatin1Char('#');
+
+ return pieces.join(concatenator);
+}
+
+/*!
+ Sets the Node status to Node::Deprecated, unless \a sinceVersion represents
+ a future version.
+
+ Stores \a sinceVersion representing the version in which the deprecation
+ took (or will take) place.
+
+ Fetches the current version from the config ('version' variable) as a
+ string, and compared to \a sinceVersion. If both string represent a valid
+ version and \a sinceVersion is greater than the currect version, do not
+ mark the node as deprecated; leave it active.
+*/
+void Node::setDeprecated(const QString &sinceVersion)
+{
+
+ if (!m_deprecatedSince.isEmpty())
+ qCWarning(lcQdoc) << QStringLiteral(
+ "Setting deprecated since version for %1 to %2 even though it "
+ "was already set to %3. This is very unexpected.")
+ .arg(this->m_name, sinceVersion, this->m_deprecatedSince);
+ m_deprecatedSince = sinceVersion;
+
+ if (!sinceVersion.isEmpty()) {
+ QVersionNumber since = QVersionNumber::fromString(sinceVersion).normalized();
+ QVersionNumber current = QVersionNumber::fromString(
+ Config::instance().get(CONFIG_VERSION).asString())
+ .normalized();
+ if (!current.isNull() && !since.isNull()) {
+ if (current < since)
+ return;
+ }
+ }
+ setStatus(Deprecated);
+}
+
+/*! \fn Node *Node::clone(Aggregate *parent)
+
+ When reimplemented in a subclass, this function creates a
+ clone of this node on the heap and makes the clone a child
+ of \a parent. A pointer to the clone is returned.
+
+ Here in the base class, this function does nothing and returns
+ nullptr.
+ */
+
+/*! \fn NodeType Node::nodeType() const
+ Returns this node's type.
+
+ \sa NodeType
+*/
+
+/*! \fn Genus Node::genus() const
+ Returns this node's Genus.
+
+ \sa Genus
+*/
+
+/*! void Node::setGenus(Genus t)
+ Sets this node's Genus to \a t.
+*/
+
+/*! \fn QString Node::signature(Node::SignatureOptions options) const
+
+ Specific parts of the signature are included according to flags in
+ \a options.
+
+ If this node is not a FunctionNode, this function returns plainName().
+
+ \sa FunctionNode::signature()
+*/
+
+/*! \fn const QString &Node::fileNameBase() const
+ Returns the node's file name base string, which is built once, when
+ Generator::fileBase() is called and stored in the Node.
+*/
+
+/*! \fn bool Node::hasFileNameBase() const
+ Returns true if the node's file name base has been set.
+
+ \sa Node::fileNameBase()
+*/
+
+/*! \fn void Node::setFileNameBase(const QString &t)
+ Sets the node's file name base to \a t. Only called by
+ Generator::fileBase().
+*/
+
+/*! \fn void Node::setAccess(Access t)
+ Sets the node's access type to \a t.
+
+ \sa Access
+*/
+
+/*! \fn void Node::setThreadSafeness(ThreadSafeness t)
+ Sets the node's thread safeness to \a t.
+
+ \sa ThreadSafeness
+*/
+
+/*! \fn void Node::setPhysicalModuleName(const QString &name)
+ Sets the node's physical module \a name.
+*/
+
+/*! \fn void Node::setReconstitutedBrief(const QString &t)
+ When reading an index file, this function is called with the
+ reconstituted brief clause \a t to set the node's brief clause.
+ I think this is needed for linking to something in the brief clause.
+*/
+
+/*! \fn void Node::setParent(Aggregate *n)
+ Sets the node's parent pointer to \a n. Such a thing
+ is not lightly done. All the calls to this function
+ are in other member functions of Node subclasses. See
+ the code in the subclass implementations to understand
+ when this function can be called safely and why it is called.
+*/
+
+/*! \fn void Node::setIndexNodeFlag(bool isIndexNode = true)
+ Sets a flag in this Node that indicates the node was created
+ for something in an index file. This is important to know
+ because an index node is not to be documented in the current
+ module. When the index flag is set, it means the Node
+ represents something in another module, and it will be
+ documented in that module's documentation.
+*/
+
+/*! \fn void Node::setRelatedNonmember(bool b)
+ Sets a flag in the node indicating whether this node is a related nonmember
+ of something. This function is called when the \c relates command is seen.
+ */
+
+/*! \fn void Node::addMember(Node *node)
+ In a CollectionNode, this function adds \a node to the collection
+ node's members list. It does nothing if this node is not a CollectionNode.
+ */
+
+/*! \fn bool Node::hasNamespaces() const
+ Returns \c true if this is a CollectionNode and its members list
+ contains namespace nodes. Otherwise it returns \c false.
+ */
+
+/*! \fn bool Node::hasClasses() const
+ Returns \c true if this is a CollectionNode and its members list
+ contains class nodes. Otherwise it returns \c false.
+ */
+
+/*! \fn void Node::setAbstract(bool b)
+ If this node is a ClassNode or a QmlTypeNode, the node's abstract flag
+ data member is set to \a b.
+ */
+
+/*! \fn void Node::setWrapper()
+ If this node is a ClassNode or a QmlTypeNode, the node's wrapper flag
+ data member is set to \c true.
+ */
+
+/*! \fn void Node::setDataType(const QString &dataType)
+ If this node is a PropertyNode or a QmlPropertyNode, its
+ data type data member is set to \a dataType. Otherwise,
+ this function does nothing.
+ */
+
+/*! \fn bool Node::wasSeen() const
+ Returns the \c seen flag data member of this node if it is a NamespaceNode
+ or a CollectionNode. Otherwise it returns \c false. If \c true is returned,
+ it means that the location where the namespace or collection is to be
+ documented has been found.
+ */
+
+/*! \fn void appendGroupName(const QString &t)
+ If this node is a PageNode, the group name \a t is appended to the node's
+ list of group names. It is not clear to me what this list of group names
+ is used for, but it is written to the index file, and it is used in the
+ navigation bar.
+ */
+
+/*! \fn QString Node::element() const
+ If this node is a QmlPropertyNode or a FunctionNode, this function
+ returns the name of the parent node. Otherwise it returns an empty
+ string.
+ */
+
+/*! \fn bool Node::docMustBeGenerated() const
+ This function is called to perform a test to decide if the node must have
+ documentation generated. In the Node base class, it always returns \c false.
+
+ In the ProxyNode class it always returns \c true. There aren't many proxy
+ nodes, but when one appears, it must generate documentation. In the overrides
+ in NamespaceNode and ClassNode, a meaningful test is performed to decide if
+ documentation must be generated.
+ */
+
+/*! \fn QString Node::title() const
+ Returns a string that can be used to print a title in the documentation for
+ whatever this Node is. In the Node base class, the node's name() is returned.
+ In a PageNode, the function returns the title data member. In a HeaderNode,
+ if the title() is empty, the name() is returned.
+ */
+
+/*! \fn QString Node::subtitle() const { return QString(); }
+ Returns a string that can be used to print a subtitle in the documentation for
+ whatever this Node is. In the Node base class, the empty string is returned.
+ In a PageNode, the function returns the subtitle data member. In a HeaderNode,
+ the subtitle data member is returned.
+ */
+
+/*! \fn QString Node::fullTitle() const
+ Returns a string that can be used as the full title for the documentation of
+ this node. In this base class, the name() is returned. In a PageNode, title()
+ is returned. In a HeaderNode, if the title() is empty, the name() is returned.
+ If the title() is not empty then name-title is returned. In a CollectionNode,
+ the title() is returned.
+ */
+
+/*! \fn bool Node::setTitle(const QString &title)
+ Sets the node's \a title, which is used for the title of
+ the documentation page, if one is generated for this node.
+ Returns \c true if the title is set. In this base class,
+ there is no title string stored, so in the base class,
+ nothing happens and \c false is returned. The override in
+ the PageNode class is where the title is set.
+ */
+
+/*! \fn bool Node::setSubtitle(const QString &subtitle)
+ Sets the node's \a subtitle, which is used for the subtitle
+ of the documentation page, if one is generated for this node.
+ Returns \c true if the subtitle is set. In this base class,
+ there is no subtitle string stored, so in the base class,
+ nothing happens and \c false is returned. The override in
+ the PageNode and HeaderNode classes is where the subtitle is
+ set.
+ */
+
+/*! \fn void Node::markDefault()
+ If this node is a QmlPropertyNode, it is marked as the default property.
+ Otherwise the function does nothing.
+ */
+
+/*! \fn void Node::markReadOnly(bool flag)
+ If this node is a QmlPropertyNode, then the property's read-only
+ flag is set to \a flag.
+ */
+
+/*! \fn Aggregate *Node::parent() const
+ Returns the node's parent pointer.
+*/
+
+/*! \fn const QString &Node::name() const
+ Returns the node's name data member.
+*/
+
+/*! \fn void Node::setQtVariable(const QString &v)
+ If this node is a CollectionNode, its QT variable is set to \a v.
+ Otherwise the function does nothing. I don't know what the QT variable
+ is used for.
+ */
+
+/*! \fn QString Node::qtVariable() const
+ If this node is a CollectionNode, its QT variable is returned.
+ Otherwise an empty string is returned. I don't know what the QT
+ variable is used for.
+ */
+
+/*! \fn bool Node::hasTag(const QString &t) const
+ If this node is a FunctionNode, the function returns \c true if
+ the function has the tag \a t. Otherwise the function returns
+ \c false. I don't know what the tag is used for.
+ */
+
+/*! \fn const QMap<LinkType, std::pair<QString, QString> > &Node::links() const
+ Returns a reference to this node's link map. The link map should
+ probably be moved to the PageNode, because it contains links to the
+ start page, next page, previous page, and contents page, and these
+ are only used in PageNode, I think.
+ */
+
+/*! \fn Access Node::access() const
+ Returns the node's Access setting, which can be \c Public,
+ \c Protected, or \c Private.
+ */
+
+/*! \fn const Location& Node::declLocation() const
+ Returns the Location where this node's declaration was seen.
+ Normally the declaration location is in an \e include file.
+ The declaration location is used in qdoc error/warning messages
+ about the declaration.
+ */
+
+/*! \fn const Location& Node::defLocation() const
+ Returns the Location where this node's dedefinition was seen.
+ Normally the definition location is in a \e .cpp file.
+ The definition location is used in qdoc error/warning messages
+ when the error is discovered at the location of the definition,
+ although the way to correct the problem often requires changing
+ the declaration.
+ */
+
+/*! \fn const Location& Node::location() const
+ If this node's definition location is empty, this function
+ returns this node's declaration location. Otherwise it
+ returns the definition location.
+
+ \sa Location
+ */
+
+/*! \fn const Doc &Node::doc() const
+ Returns a reference to the node's Doc data member.
+
+ \sa Doc
+ */
+
+/*! \fn Status Node::status() const
+ Returns the node's status value.
+
+ \sa Status
+ */
+
+/*! \fn QString Node::since() const
+ Returns the node's since string, which can be empty.
+ */
+
+/*! \fn QString Node::templateStuff() const
+ Returns the node's template parameters string, if this node
+ represents a templated element.
+ */
+
+/*! \fn bool Node::isSharingComment() const
+ This function returns \c true if the node is sharing a comment
+ with other nodes. For example, multiple functions can be documented
+ with a single qdoc comment by listing the \c {\\fn} signatures for
+ all the functions in the single qdoc comment.
+ */
+
+/*! \fn QString Node::qmlTypeName() const
+ If this is a QmlPropertyNode or a FunctionNode representing a QML
+ method, this function returns the qmlTypeName() of
+ the parent() node. Otherwise it returns the name data member.
+ */
+
+/*! \fn QString Node::qmlFullBaseName() const
+ If this is a QmlTypeNode, this function returns the QML full
+ base name. Otherwise it returns an empty string.
+ */
+
+/*! \fn QString Node::logicalModuleName() const
+ If this is a CollectionNode, this function returns the logical
+ module name. Otherwise it returns an empty string.
+ */
+
+/*! \fn QString Node::logicalModuleVersion() const
+ If this is a CollectionNode, this function returns the logical
+ module version number. Otherwise it returns an empty string.
+ */
+
+/*! \fn QString Node::logicalModuleIdentifier() const
+ If this is a CollectionNode, this function returns the logical
+ module identifier. Otherwise it returns an empty string.
+ */
+
+/*! \fn void Node::setLogicalModuleInfo(const QString &arg)
+ If this node is a CollectionNode, this function splits \a arg
+ on the blank character to get a logical module name and version
+ number. If the version number is present, it splits the version
+ number on the '.' character to get a major version number and a
+ minor version number. If the version number is present, both the
+ major and minor version numbers should be there, but the minor
+ version number is not absolutely necessary.
+
+ The strings are stored in the appropriate data members for use
+ when the QML module page is generated.
+ */
+
+/*! \fn void Node::setLogicalModuleInfo(const QStringList &info)
+ If this node is a CollectionNode, this function accepts the
+ logical module \a info as a string list. If the logical module
+ info contains the version number, it splits the version number
+ on the '.' character to get the major and minor version numbers.
+ Both major and minor version numbers should be provided, but
+ the minor version number is not strictly necessary.
+
+ The strings are stored in the appropriate data members for use
+ when the QML module page is generated. This overload
+ of the function is called when qdoc is reading an index file.
+ */
+
+/*! \fn CollectionNode *Node::logicalModule() const
+ If this is a QmlTypeNode, a pointer to its QML module is returned,
+ which is a pointer to a CollectionNode. Otherwise the \c nullptr
+ is returned.
+ */
+
+/*! \fn void Node::setQmlModule(CollectionNode *t)
+ If this is a QmlTypeNode, this function sets the QML type's QML module
+ pointer to the CollectionNode \a t. Otherwise the function does nothing.
+ */
+
+/*! \fn ClassNode *Node::classNode()
+ If this is a QmlTypeNode, this function returns the pointer to
+ the C++ ClassNode that this QML type represents. Otherwise the
+ \c nullptr is returned.
+ */
+
+/*! \fn void Node::setClassNode(ClassNode *cn)
+ If this is a QmlTypeNode, this function sets the C++ class node
+ to \a cn. The C++ ClassNode is the C++ implementation of the QML
+ type.
+ */
+
+/*! \fn NodeType Node::goal(const QString &t)
+ When a square-bracket parameter is used in a qdoc command, this
+ function might be called to convert the text string \a t obtained
+ from inside the square brackets to be a Goal value, which is returned.
+
+ \sa Goal
+ */
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/node.h b/src/qdoc/qdoc/src/qdoc/node.h
new file mode 100644
index 000000000..3b5eb56bd
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/node.h
@@ -0,0 +1,343 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef NODE_H
+#define NODE_H
+
+#include "access.h"
+#include "comparisoncategory.h"
+#include "doc.h"
+#include "enumitem.h"
+#include "importrec.h"
+#include "parameters.h"
+#include "relatedclass.h"
+#include "template_declaration.h"
+
+#include <QtCore/qdir.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qstringlist.h>
+
+#include <optional>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+class ClassNode;
+class CollectionNode;
+class EnumNode;
+class ExampleNode;
+class FunctionNode;
+class Node;
+class QDocDatabase;
+class QmlTypeNode;
+class PageNode;
+class PropertyNode;
+class QmlPropertyNode;
+class SharedCommentNode;
+class Tree;
+class TypedefNode;
+
+typedef QList<Node *> NodeList;
+typedef QList<ClassNode *> ClassList;
+typedef QList<Node *> NodeVector;
+typedef QMap<QString, Node *> NodeMap;
+typedef QMap<QString, NodeMap> NodeMapMap;
+typedef QMultiMap<QString, Node *> NodeMultiMap;
+typedef QMap<QString, NodeMultiMap> NodeMultiMapMap;
+typedef QMap<QString, CollectionNode *> CNMap;
+typedef QMultiMap<QString, CollectionNode *> CNMultiMap;
+
+class Node
+{
+public:
+ enum NodeType : unsigned char {
+ NoType,
+ Namespace,
+ Class,
+ Struct,
+ Union,
+ HeaderFile,
+ Page,
+ Enum,
+ Example,
+ ExternalPage,
+ Function,
+ Typedef,
+ TypeAlias,
+ Property,
+ Variable,
+ Group,
+ Module,
+ QmlType,
+ QmlModule,
+ QmlProperty,
+ QmlValueType,
+ SharedComment,
+ Collection,
+ Proxy
+ };
+
+ enum Genus : unsigned char {
+ DontCare = 0x0,
+ CPP = 0x1,
+ QML = 0x4,
+ DOC = 0x8,
+ API = CPP | QML
+ };
+
+ enum Status : unsigned char {
+ Deprecated,
+ Preliminary,
+ Active,
+ Internal,
+ DontDocument
+ }; // don't reorder this enum
+
+ enum ThreadSafeness : unsigned char {
+ UnspecifiedSafeness,
+ NonReentrant,
+ Reentrant,
+ ThreadSafe
+ };
+
+ enum SignatureOption : unsigned char {
+ SignaturePlain = 0x0,
+ SignatureDefaultValues = 0x1,
+ SignatureReturnType = 0x2,
+ SignatureTemplateParams = 0x4
+ };
+ Q_DECLARE_FLAGS(SignatureOptions, SignatureOption)
+
+ enum LinkType : unsigned char { StartLink, NextLink, PreviousLink, ContentsLink };
+
+ enum FlagValue { FlagValueDefault = -1, FlagValueFalse = 0, FlagValueTrue = 1 };
+
+ virtual ~Node() = default;
+ virtual Node *clone(Aggregate *) { return nullptr; } // currently only FunctionNode
+ [[nodiscard]] virtual Tree *tree() const;
+ [[nodiscard]] Aggregate *root() const;
+
+ [[nodiscard]] NodeType nodeType() const { return m_nodeType; }
+ [[nodiscard]] QString nodeTypeString() const;
+
+ [[nodiscard]] Genus genus() const { return m_genus; }
+ void setGenus(Genus t) { m_genus = t; }
+ static Genus getGenus(NodeType t);
+
+ [[nodiscard]] bool isActive() const { return m_status == Active; }
+ [[nodiscard]] bool isClass() const { return m_nodeType == Class; }
+ [[nodiscard]] bool isCppNode() const { return genus() == CPP; }
+ [[nodiscard]] bool isDontDocument() const { return (m_status == DontDocument); }
+ [[nodiscard]] bool isEnumType() const { return m_nodeType == Enum; }
+ [[nodiscard]] bool isExample() const { return m_nodeType == Example; }
+ [[nodiscard]] bool isExternalPage() const { return m_nodeType == ExternalPage; }
+ [[nodiscard]] bool isFunction(Genus g = DontCare) const
+ {
+ return m_nodeType == Function && (genus() == g || g == DontCare);
+ }
+ [[nodiscard]] bool isGroup() const { return m_nodeType == Group; }
+ [[nodiscard]] bool isHeader() const { return m_nodeType == HeaderFile; }
+ [[nodiscard]] bool isIndexNode() const { return m_indexNodeFlag; }
+ [[nodiscard]] bool isModule() const { return m_nodeType == Module; }
+ [[nodiscard]] bool isNamespace() const { return m_nodeType == Namespace; }
+ [[nodiscard]] bool isPage() const { return m_nodeType == Page; }
+ [[nodiscard]] bool isPreliminary() const { return (m_status == Preliminary); }
+ [[nodiscard]] bool isPrivate() const { return m_access == Access::Private; }
+ [[nodiscard]] bool isProperty() const { return m_nodeType == Property; }
+ [[nodiscard]] bool isProxyNode() const { return m_nodeType == Proxy; }
+ [[nodiscard]] bool isPublic() const { return m_access == Access::Public; }
+ [[nodiscard]] bool isProtected() const { return m_access == Access::Protected; }
+ [[nodiscard]] bool isQmlBasicType() const { return m_nodeType == QmlValueType; }
+ [[nodiscard]] bool isQmlModule() const { return m_nodeType == QmlModule; }
+ [[nodiscard]] bool isQmlNode() const { return genus() == QML; }
+ [[nodiscard]] bool isQmlProperty() const { return m_nodeType == QmlProperty; }
+ [[nodiscard]] bool isQmlType() const { return m_nodeType == QmlType || m_nodeType == QmlValueType; }
+ [[nodiscard]] bool isRelatedNonmember() const { return m_relatedNonmember; }
+ [[nodiscard]] bool isStruct() const { return m_nodeType == Struct; }
+ [[nodiscard]] bool isSharedCommentNode() const { return m_nodeType == SharedComment; }
+ [[nodiscard]] bool isTypeAlias() const { return m_nodeType == TypeAlias; }
+ [[nodiscard]] bool isTypedef() const
+ {
+ return m_nodeType == Typedef || m_nodeType == TypeAlias;
+ }
+ [[nodiscard]] bool isUnion() const { return m_nodeType == Union; }
+ [[nodiscard]] bool isVariable() const { return m_nodeType == Variable; }
+ [[nodiscard]] bool isGenericCollection() const { return (m_nodeType == Node::Collection); }
+
+ [[nodiscard]] virtual bool isDeprecated() const { return (m_status == Deprecated); }
+ [[nodiscard]] virtual bool isAbstract() const { return false; }
+ [[nodiscard]] virtual bool isAggregate() const { return false; } // means "can have children"
+ [[nodiscard]] virtual bool isFirstClassAggregate() const
+ {
+ return false;
+ } // Aggregate but not proxy or prop group"
+ [[nodiscard]] virtual bool isAlias() const { return false; }
+ [[nodiscard]] virtual bool isAttached() const { return false; }
+ [[nodiscard]] virtual bool isClassNode() const { return false; }
+ [[nodiscard]] virtual bool isCollectionNode() const { return false; }
+ [[nodiscard]] virtual bool isDefault() const { return false; }
+ [[nodiscard]] virtual bool isInternal() const;
+ [[nodiscard]] virtual bool isMacro() const { return false; }
+ [[nodiscard]] virtual bool isPageNode() const { return false; } // means "generates a doc page"
+ [[nodiscard]] virtual bool isRelatableType() const { return false; }
+ [[nodiscard]] virtual bool isMarkedReimp() const { return false; }
+ [[nodiscard]] virtual bool isPropertyGroup() const { return false; }
+ [[nodiscard]] virtual bool isStatic() const { return false; }
+ [[nodiscard]] virtual bool isTextPageNode() const
+ {
+ return false;
+ } // means PageNode but not Aggregate
+ [[nodiscard]] virtual bool isWrapper() const;
+
+ [[nodiscard]] QString plainName() const;
+ QString plainFullName(const Node *relative = nullptr) const;
+ [[nodiscard]] QString plainSignature() const;
+ QString fullName(const Node *relative = nullptr) const;
+ [[nodiscard]] virtual QString signature(Node::SignatureOptions) const { return plainName(); }
+
+ [[nodiscard]] const QString &fileNameBase() const { return m_fileNameBase; }
+ [[nodiscard]] bool hasFileNameBase() const { return !m_fileNameBase.isEmpty(); }
+ void setFileNameBase(const QString &t) { m_fileNameBase = t; }
+
+ void setAccess(Access t) { m_access = t; }
+ void setLocation(const Location &t);
+ void setDoc(const Doc &doc, bool replace = false);
+ void setStatus(Status t);
+ void setThreadSafeness(ThreadSafeness t) { m_safeness = t; }
+ void setSince(const QString &since);
+ void setPhysicalModuleName(const QString &name) { m_physicalModuleName = name; }
+ void setUrl(const QString &url) { m_url = url; }
+ void setTemplateDecl(std::optional<RelaxedTemplateDeclaration> t) { m_templateDecl = t; }
+ void setReconstitutedBrief(const QString &t) { m_reconstitutedBrief = t; }
+ void setParent(Aggregate *n) { m_parent = n; }
+ void setIndexNodeFlag(bool isIndexNode = true) { m_indexNodeFlag = isIndexNode; }
+ void setHadDoc() { m_hadDoc = true; }
+ void setComparisonCategory(const ComparisonCategory &category) { m_comparisonCategory = category; }
+ [[nodiscard]] ComparisonCategory comparisonCategory() const { return m_comparisonCategory; }
+ virtual void setRelatedNonmember(bool b) { m_relatedNonmember = b; }
+ virtual void addMember(Node *) {}
+ [[nodiscard]] virtual bool hasNamespaces() const { return false; }
+ [[nodiscard]] virtual bool hasClasses() const { return false; }
+ virtual void setAbstract(bool) {}
+ virtual void setWrapper() {}
+ virtual void setDataType(const QString &) {}
+ [[nodiscard]] virtual bool wasSeen() const { return false; }
+ virtual void appendGroupName(const QString &) {}
+ [[nodiscard]] virtual QString element() const { return QString(); }
+ [[nodiscard]] virtual bool docMustBeGenerated() const { return false; }
+
+ [[nodiscard]] virtual QString title() const { return name(); }
+ [[nodiscard]] virtual QString subtitle() const { return QString(); }
+ [[nodiscard]] virtual QString fullTitle() const { return name(); }
+ virtual bool setTitle(const QString &) { return false; }
+ virtual bool setSubtitle(const QString &) { return false; }
+
+ void markInternal()
+ {
+ setAccess(Access::Private);
+ setStatus(Internal);
+ }
+ virtual void markDefault() {}
+ virtual void markReadOnly(bool) {}
+
+ [[nodiscard]] Aggregate *parent() const { return m_parent; }
+ [[nodiscard]] const QString &name() const { return m_name; }
+ [[nodiscard]] QString physicalModuleName() const { return m_physicalModuleName; }
+ [[nodiscard]] QString url() const { return m_url; }
+ virtual void setQtVariable(const QString &) {}
+ [[nodiscard]] virtual QString qtVariable() const { return QString(); }
+ virtual void setQtCMakeComponent(const QString &) {}
+ virtual void setQtCMakeTargetItem(const QString &) {}
+ [[nodiscard]] virtual QString qtCMakeComponent() const { return QString(); }
+ [[nodiscard]] virtual QString qtCMakeTargetItem() const { return QString(); }
+ [[nodiscard]] virtual bool hasTag(const QString &) const { return false; }
+
+ void setDeprecated(const QString &sinceVersion);
+ [[nodiscard]] const QString &deprecatedSince() const { return m_deprecatedSince; }
+
+ [[nodiscard]] const QMap<LinkType, std::pair<QString, QString>> &links() const { return m_linkMap; }
+ void setLink(LinkType linkType, const QString &link, const QString &desc);
+
+ [[nodiscard]] Access access() const { return m_access; }
+ [[nodiscard]] const Location &declLocation() const { return m_declLocation; }
+ [[nodiscard]] const Location &defLocation() const { return m_defLocation; }
+ [[nodiscard]] const Location &location() const
+ {
+ return (m_defLocation.isEmpty() ? m_declLocation : m_defLocation);
+ }
+ [[nodiscard]] const Doc &doc() const { return m_doc; }
+ [[nodiscard]] bool isInAPI() const
+ {
+ return !isPrivate() && !isInternal() && !isDontDocument() && hasDoc();
+ }
+ [[nodiscard]] bool hasDoc() const;
+ [[nodiscard]] bool hadDoc() const { return m_hadDoc; }
+ [[nodiscard]] Status status() const { return m_status; }
+ [[nodiscard]] ThreadSafeness threadSafeness() const;
+ [[nodiscard]] ThreadSafeness inheritedThreadSafeness() const;
+ [[nodiscard]] QString since() const { return m_since; }
+ [[nodiscard]] const std::optional<RelaxedTemplateDeclaration>& templateDecl() const { return m_templateDecl; }
+ [[nodiscard]] const QString &reconstitutedBrief() const { return m_reconstitutedBrief; }
+
+ [[nodiscard]] bool isSharingComment() const { return (m_sharedCommentNode != nullptr); }
+ void setSharedCommentNode(SharedCommentNode *t) { m_sharedCommentNode = t; }
+ SharedCommentNode *sharedCommentNode() { return m_sharedCommentNode; }
+
+ [[nodiscard]] QString extractClassName(const QString &string) const;
+ [[nodiscard]] virtual QString qmlTypeName() const { return m_name; }
+ [[nodiscard]] virtual QString qmlFullBaseName() const { return QString(); }
+ [[nodiscard]] virtual QString logicalModuleName() const { return QString(); }
+ [[nodiscard]] virtual QString logicalModuleVersion() const { return QString(); }
+ [[nodiscard]] virtual QString logicalModuleIdentifier() const { return QString(); }
+
+ virtual void setLogicalModuleInfo(const QStringList &) {}
+ [[nodiscard]] virtual CollectionNode *logicalModule() const { return nullptr; }
+ virtual void setQmlModule(CollectionNode *) {}
+ virtual ClassNode *classNode() { return nullptr; }
+ virtual void setClassNode(ClassNode *) {}
+ [[nodiscard]] QString fullDocumentName() const;
+ QString qualifyCppName();
+ QString qualifyQmlName();
+ QString qualifyWithParentName();
+
+ static FlagValue toFlagValue(bool b);
+ static bool fromFlagValue(FlagValue fv, bool defaultValue);
+ static QString nodeTypeString(NodeType t);
+ static bool nodeNameLessThan(const Node *first, const Node *second);
+
+protected:
+ Node(NodeType type, Aggregate *parent, QString name);
+
+private:
+ NodeType m_nodeType {};
+ Genus m_genus {};
+ Access m_access { Access::Public };
+ ThreadSafeness m_safeness { UnspecifiedSafeness };
+ Status m_status { Active };
+ ComparisonCategory m_comparisonCategory { ComparisonCategory::None };
+ bool m_indexNodeFlag : 1;
+ bool m_relatedNonmember : 1;
+ bool m_hadDoc : 1;
+
+ Aggregate *m_parent { nullptr };
+ SharedCommentNode *m_sharedCommentNode { nullptr };
+ QString m_name {};
+ Location m_declLocation {};
+ Location m_defLocation {};
+ Doc m_doc {};
+ QMap<LinkType, std::pair<QString, QString>> m_linkMap {};
+ QString m_fileNameBase {};
+ QString m_physicalModuleName {};
+ QString m_url {};
+ QString m_since {};
+ std::optional<RelaxedTemplateDeclaration> m_templateDecl{std::nullopt};
+ QString m_reconstitutedBrief {};
+ QString m_deprecatedSince {};
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(Node::SignatureOptions)
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/openedlist.cpp b/src/qdoc/qdoc/src/qdoc/openedlist.cpp
new file mode 100644
index 000000000..a85e45ec4
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/openedlist.cpp
@@ -0,0 +1,172 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "openedlist.h"
+
+#include "atom.h"
+
+#include <QtCore/qregularexpression.h>
+
+QT_BEGIN_NAMESPACE
+
+static const char roman[] = "m\2d\5c\2l\5x\2v\5i";
+
+OpenedList::OpenedList(ListStyle style) : sty(style), ini(1), nex(0) {}
+
+OpenedList::OpenedList(const Location &location, const QString &hint) : sty(Bullet), ini(1)
+{
+ static const QRegularExpression hintSyntax("^(\\W*)([0-9]+|[A-Z]+|[a-z]+)(\\W*)$");
+
+ auto match = hintSyntax.match(hint);
+ if (match.hasMatch()) {
+ bool ok;
+ int asNumeric = hint.toInt(&ok);
+ int asRoman = fromRoman(match.captured(2));
+ int asAlpha = fromAlpha(match.captured(2));
+
+ if (ok) {
+ sty = Numeric;
+ ini = asNumeric;
+ } else if (asRoman > 0 && asRoman != 100 && asRoman != 500) {
+ sty = (hint == hint.toLower()) ? LowerRoman : UpperRoman;
+ ini = asRoman;
+ } else {
+ sty = (hint == hint.toLower()) ? LowerAlpha : UpperAlpha;
+ ini = asAlpha;
+ }
+ pref = match.captured(1);
+ suff = match.captured(3);
+ } else if (!hint.isEmpty()) {
+ location.warning(QStringLiteral("Unrecognized list style '%1'").arg(hint));
+ }
+ nex = ini - 1;
+}
+
+QString OpenedList::styleString() const
+{
+ switch (style()) {
+ case Bullet:
+ default:
+ return ATOM_LIST_BULLET;
+ case Tag:
+ return ATOM_LIST_TAG;
+ case Value:
+ return ATOM_LIST_VALUE;
+ case Numeric:
+ return ATOM_LIST_NUMERIC;
+ case UpperAlpha:
+ return ATOM_LIST_UPPERALPHA;
+ case LowerAlpha:
+ return ATOM_LIST_LOWERALPHA;
+ case UpperRoman:
+ return ATOM_LIST_UPPERROMAN;
+ case LowerRoman:
+ return ATOM_LIST_LOWERROMAN;
+ }
+}
+
+QString OpenedList::numberString() const
+{
+ return QString::number(number());
+ /*
+ switch ( style() ) {
+ case Numeric:
+ return QString::number( number() );
+ case UpperAlpha:
+ return toAlpha( number() ).toUpper();
+ case LowerAlpha:
+ return toAlpha( number() );
+ case UpperRoman:
+ return toRoman( number() ).toUpper();
+ case LowerRoman:
+ return toRoman( number() );
+ case Bullet:
+ default:
+ return "*";
+ }*/
+}
+
+int OpenedList::fromAlpha(const QString &str)
+{
+ int n = 0;
+ int u;
+
+ for (const QChar &character : str) {
+ u = character.toLower().unicode();
+ if (u >= 'a' && u <= 'z') {
+ n *= 26;
+ n += u - 'a' + 1;
+ } else {
+ return 0;
+ }
+ }
+ return n;
+}
+
+QString OpenedList::toRoman(int n)
+{
+ /*
+ See p. 30 of Donald E. Knuth's "TeX: The Program".
+ */
+ QString str;
+ int j = 0;
+ int k;
+ int u;
+ int v = 1000;
+
+ for (;;) {
+ while (n >= v) {
+ str += roman[j];
+ n -= v;
+ }
+
+ if (n <= 0)
+ break;
+
+ k = j + 2;
+ u = v / roman[k - 1];
+ if (roman[k - 1] == 2) {
+ k += 2;
+ u /= 5;
+ }
+ if (n + u >= v) {
+ str += roman[k];
+ n += u;
+ } else {
+ j += 2;
+ v /= roman[j - 1];
+ }
+ }
+ return str;
+}
+
+int OpenedList::fromRoman(const QString &str)
+{
+ int n = 0;
+ int j;
+ int u;
+ int v = 0;
+
+ for (const QChar &character : str) {
+ j = 0;
+ u = 1000;
+ while (roman[j] != 'i' && roman[j] != character.toLower()) {
+ j += 2;
+ u /= roman[j - 1];
+ }
+ if (u < v) {
+ n -= u;
+ } else {
+ n += u;
+ }
+ v = u;
+ }
+
+ if (str.toLower() == toRoman(n)) {
+ return n;
+ } else {
+ return 0;
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/openedlist.h b/src/qdoc/qdoc/src/qdoc/openedlist.h
new file mode 100644
index 000000000..cd0c54e40
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/openedlist.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef OPENEDLIST_H
+#define OPENEDLIST_H
+
+#include "location.h"
+
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class OpenedList
+{
+public:
+ enum ListStyle { Bullet, Tag, Value, Numeric, UpperAlpha, LowerAlpha, UpperRoman, LowerRoman };
+
+ OpenedList() : sty(Bullet), ini(1), nex(0) {}
+ explicit OpenedList(ListStyle style);
+ OpenedList(const Location &location, const QString &hint);
+
+ void next() { nex++; }
+
+ [[nodiscard]] bool isStarted() const { return nex >= ini; }
+ [[nodiscard]] ListStyle style() const { return sty; }
+ [[nodiscard]] QString styleString() const;
+ [[nodiscard]] int number() const { return nex; }
+ [[nodiscard]] QString numberString() const;
+ [[nodiscard]] QString prefix() const { return pref; }
+ [[nodiscard]] QString suffix() const { return suff; }
+
+private:
+ static int fromAlpha(const QString &str);
+ static QString toRoman(int n);
+ static int fromRoman(const QString &str);
+
+ ListStyle sty;
+ int ini;
+ int nex;
+ QString pref;
+ QString suff;
+};
+Q_DECLARE_TYPEINFO(OpenedList, Q_RELOCATABLE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/pagenode.cpp b/src/qdoc/qdoc/src/qdoc/pagenode.cpp
new file mode 100644
index 000000000..5f01bc24f
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/pagenode.cpp
@@ -0,0 +1,117 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "pagenode.h"
+
+#include "aggregate.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class PageNode
+ \brief A PageNode is a Node that generates a documentation page.
+
+ Not all subclasses of Node produce documentation pages. FunctionNode,
+ PropertyNode, and EnumNode are all examples of subclasses of Node that
+ don't produce documentation pages but add documentation to a page.
+ They are always child nodes of an Aggregate, and Aggregate inherits
+ PageNode.
+
+ Not every subclass of PageNode inherits Aggregate. ExternalPageNode,
+ ExampleNode, and CollectionNode are subclasses of PageNode that are
+ not subclasses of Aggregate. Because they are not subclasses of
+ Aggregate, they can't have children. But they still generate, or
+ link to, a documentation page.
+ */
+
+/*! \fn QString PageNode::title() const
+ Returns the node's title, which is used for the page title.
+ */
+
+/*! \fn QString PageNode::subtitle() const
+ Returns the node's subtitle, which may be empty.
+ */
+
+/*!
+ Returns the node's full title.
+ */
+QString PageNode::fullTitle() const
+{
+ return title();
+}
+
+/*!
+ Sets the node's \a title, which is used for the page title.
+ Returns true. Adds the node to the parent() nonfunction map
+ using the \a title as the key.
+ */
+bool PageNode::setTitle(const QString &title)
+{
+ m_title = title;
+ parent()->addChildByTitle(this, title);
+ return true;
+}
+
+/*!
+ \fn bool PageNode::setSubtitle(const QString &subtitle)
+ Sets the node's \a subtitle. Returns true;
+ */
+
+/*! \fn PageNode::PageNode(Aggregate *parent, const QString &name)
+ This constructor sets the PageNode's \a parent and the \a name is the
+ argument of the \c {\\page} command. The node type is set to Node::Page.
+ */
+
+/*! \fn PageNode::PageNode(NodeType type, Aggregate *parent, const QString &name)
+ This constructor is not called directly. It is called by the constructors of
+ subclasses of PageNode, usually Aggregate. The node type is set to \a type,
+ and the parent pointer is set to \a parent. \a name is the argument of the topic
+ command that resulted in the PageNode being created. This could be \c {\\class}
+ or \c {\\namespace}, for example.
+ */
+
+/*! \fn PageNode::~PageNode()
+ The destructor is virtual, and it does nothing.
+ */
+
+/*! \fn bool PageNode::isPageNode() const
+ Always returns \c true because this is a PageNode.
+ */
+
+/*! \fn bool PageNode::isTextPageNode() const
+ Returns \c true if this instance of PageNode is not an Aggregate.
+ The significance of a \c true return value is that this PageNode
+ doesn't have children, because it is not an Aggregate.
+
+ \sa Aggregate.
+ */
+
+/*! \fn QString PageNode::imageFileName() const
+ If this PageNode is an ExampleNode, the image file name
+ data member is returned. Otherwise an empty string is
+ returned.
+ */
+
+/*! \fn void PageNode::setImageFileName(const QString &ifn)
+ If this PageNode is an ExampleNode, the image file name
+ data member is set to \a ifn. Otherwise the function does
+ nothing.
+ */
+
+/*! \fn bool PageNode::noAutoList() const
+ Returns the value of the no auto-list flag.
+ */
+
+/*! \fn void PageNode::setNoAutoList(bool b)
+ Sets the no auto-list flag to \a b.
+ */
+
+/*! \fn const QStringList &PageNode::groupNames() const
+ Returns a const reference to the string list containing all the group names.
+ */
+
+/*! \fn void PageNode::appendGroupName(const QString &t)
+ Appends \a t to the list of group names.
+ */
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/pagenode.h b/src/qdoc/qdoc/src/qdoc/pagenode.h
new file mode 100644
index 000000000..14231bccd
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/pagenode.h
@@ -0,0 +1,82 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef PAGENODE_H
+#define PAGENODE_H
+
+#include "node.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+
+class PageNode : public Node
+{
+public:
+ PageNode(Aggregate *parent, const QString &name) : Node(Page, parent, name) {}
+ PageNode(NodeType type, Aggregate *parent, const QString &name) : Node(type, parent, name) {}
+
+ [[nodiscard]] bool isPageNode() const override { return true; }
+ [[nodiscard]] bool isTextPageNode() const override
+ {
+ return !isAggregate();
+ } // PageNode but not Aggregate
+
+ [[nodiscard]] QString title() const override { return m_title; }
+ [[nodiscard]] QString subtitle() const override { return m_subtitle; }
+ [[nodiscard]] QString fullTitle() const override;
+ bool setTitle(const QString &title) override;
+ bool setSubtitle(const QString &subtitle) override
+ {
+ m_subtitle = subtitle;
+ return true;
+ }
+ [[nodiscard]] virtual QString imageFileName() const { return QString(); }
+ virtual void setImageFileName(const QString &) {}
+
+ [[nodiscard]] bool noAutoList() const { return m_noAutoList; }
+ void setNoAutoList(bool b) { m_noAutoList = b; }
+ [[nodiscard]] const QStringList &groupNames() const { return m_groupNames; }
+ void appendGroupName(const QString &t) override { m_groupNames.append(t); }
+
+ [[nodiscard]] const PageNode *navigationParent() const { return m_navParent; }
+ void setNavigationParent(const PageNode *parent) { m_navParent = parent; }
+
+ void markAttribution() { is_attribution = true; }
+ [[nodiscard]] bool isAttribution() const { return is_attribution; }
+
+protected:
+ friend class Node;
+
+protected:
+ bool m_noAutoList { false };
+ QString m_title {};
+ QString m_subtitle {};
+ QStringList m_groupNames {};
+
+ // Marks the PageNode as being or not being an attribution.
+ // A PageNode that is an attribution represents a page that serves
+ // to present the third party software that a project uses,
+ // together with its license, link to the website of the project
+ // and so on.
+ // PageNode that are attribution are expected to be generate only
+ // for the Qt project by the QAttributionScanner, as part of the
+ // built of Qt's documentation.
+ //
+ // PageNodes that are attribution are marked primarily so that
+ // QDoc is able to generate a specialized list of attributions for
+ // a specific module through the use of the "\generatedlist"
+ // command, and behave like any other PageNode otherwise.
+ bool is_attribution{ false };
+
+private:
+ const PageNode *m_navParent { nullptr };
+};
+
+QT_END_NAMESPACE
+
+#endif // PAGENODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/parameters.cpp b/src/qdoc/qdoc/src/qdoc/parameters.cpp
new file mode 100644
index 000000000..39f88b48f
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/parameters.cpp
@@ -0,0 +1,542 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "parameters.h"
+
+#include "codechunk.h"
+#include "generator.h"
+#include "tokenizer.h"
+
+QT_BEGIN_NAMESPACE
+
+QRegularExpression Parameters::s_varComment(R"(^/\*\s*([a-zA-Z_0-9]+)\s*\*/$)");
+
+/*!
+ \class Parameter
+ \brief The Parameter class describes one function parameter.
+
+ A parameter can be a function parameter or a macro parameter.
+ It has a name, a data type, and an optional default value.
+ These are all stored as strings so they can be compared with
+ a parameter in a function signature to find a match.
+ */
+
+/*!
+ \fn Parameter::Parameter(const QString &type, const QString &name, const QString &defaultValue)
+
+ Constructs the parameter from the \a type, the optional \a name,
+ and the optional \a defaultValue.
+ */
+
+/*!
+ Reconstructs the text signature for the parameter and returns
+ it. If \a includeValue is true and there is a default value,
+ the default value is appended with '='.
+ */
+QString Parameter::signature(bool includeValue) const
+{
+ QString p = m_type;
+ if (!p.isEmpty() && !p.endsWith(QChar('*')) && !p.endsWith(QChar('&')) &&
+ !p.endsWith(QChar(' ')) && !m_name.isEmpty()) {
+ p += QLatin1Char(' ');
+ }
+ p += m_name;
+ if (includeValue && !m_defaultValue.isEmpty())
+ p += " = " + m_defaultValue;
+ return p;
+}
+
+/*!
+ \class Parameters
+
+ \brief A class for parsing and managing a function parameter list
+
+ The constructor is passed a string that is the text inside the
+ parentheses of a function declaration. The constructor parses
+ the parameter list into a vector of class Parameter.
+
+ The Parameters object is then used in function searches to find
+ the correct function node given the function name and the signature
+ of its parameters.
+ */
+
+Parameters::Parameters() : m_valid(true), m_privateSignal(false), m_tok(0), m_tokenizer(nullptr)
+{
+ // nothing.
+}
+
+Parameters::Parameters(const QString &signature)
+ : m_valid(true), m_privateSignal(false), m_tok(0), m_tokenizer(nullptr)
+{
+ if (!signature.isEmpty()) {
+ if (!parse(signature)) {
+ m_parameters.clear();
+ m_valid = false;
+ }
+ }
+}
+
+/*!
+ Get the next token from the string being parsed and store
+ it in the token variable.
+ */
+void Parameters::readToken()
+{
+ m_tok = m_tokenizer->getToken();
+}
+
+/*!
+ Return the current lexeme from the string being parsed.
+ */
+QString Parameters::lexeme()
+{
+ return m_tokenizer->lexeme();
+}
+
+/*!
+ Return the previous lexeme read from the string being parsed.
+ */
+QString Parameters::previousLexeme()
+{
+ return m_tokenizer->previousLexeme();
+}
+
+/*!
+ If the current token is \a target, read the next token and
+ return \c true. Otherwise, return false without reading the
+ next token.
+ */
+bool Parameters::match(int target)
+{
+ if (m_tok == target) {
+ readToken();
+ return true;
+ }
+ return false;
+}
+
+/*!
+ Match a template clause in angle brackets, append it to the
+ \a type, and return \c true. If there is no template clause,
+ or if an error is detected, return \c false.
+ */
+void Parameters::matchTemplateAngles(CodeChunk &type)
+{
+ if (m_tok == Tok_LeftAngle) {
+ int leftAngleDepth = 0;
+ int parenAndBraceDepth = 0;
+ do {
+ if (m_tok == Tok_LeftAngle) {
+ leftAngleDepth++;
+ } else if (m_tok == Tok_RightAngle) {
+ leftAngleDepth--;
+ } else if (m_tok == Tok_LeftParen || m_tok == Tok_LeftBrace) {
+ ++parenAndBraceDepth;
+ } else if (m_tok == Tok_RightParen || m_tok == Tok_RightBrace) {
+ if (--parenAndBraceDepth < 0)
+ return;
+ }
+ type.append(lexeme());
+ readToken();
+ } while (leftAngleDepth > 0 && m_tok != Tok_Eoi);
+ }
+}
+
+/*!
+ Uses the current tokenizer to parse the \a name and \a type
+ of the parameter.
+ */
+bool Parameters::matchTypeAndName(CodeChunk &type, QString &name)
+{
+ /*
+ This code is really hard to follow... sorry. The loop is there to match
+ Alpha::Beta::Gamma::...::Omega.
+ */
+ for (;;) {
+ bool virgin = true;
+
+ if (m_tok != Tok_Ident) {
+ /*
+ There is special processing for 'Foo::operator int()'
+ and such elsewhere. This is the only case where we
+ return something with a trailing gulbrandsen ('Foo::').
+ */
+ if (m_tok == Tok_operator)
+ return true;
+
+ /*
+ People may write 'const unsigned short' or
+ 'short unsigned const' or any other permutation.
+ */
+ while (match(Tok_const) || match(Tok_volatile))
+ type.append(previousLexeme());
+ QString pending;
+ while (m_tok == Tok_signed || m_tok == Tok_int || m_tok == Tok_unsigned
+ || m_tok == Tok_short || m_tok == Tok_long || m_tok == Tok_int64) {
+ if (m_tok == Tok_signed)
+ pending = lexeme();
+ else {
+ if (m_tok == Tok_unsigned && !pending.isEmpty())
+ type.append(pending);
+ pending.clear();
+ type.append(lexeme());
+ }
+ readToken();
+ virgin = false;
+ }
+ if (!pending.isEmpty()) {
+ type.append(pending);
+ pending.clear();
+ }
+ while (match(Tok_const) || match(Tok_volatile))
+ type.append(previousLexeme());
+
+ if (match(Tok_Tilde))
+ type.append(previousLexeme());
+ }
+
+ if (virgin) {
+ if (match(Tok_Ident)) {
+ /*
+ This is a hack until we replace this "parser"
+ with the real one used in Qt Creator.
+ Is it still needed? mws 11/12/2018
+ */
+ if (lexeme() == "("
+ && ((previousLexeme() == "QT_PREPEND_NAMESPACE")
+ || (previousLexeme() == "NS"))) {
+ readToken();
+ readToken();
+ type.append(previousLexeme());
+ readToken();
+ } else
+ type.append(previousLexeme());
+ } else if (match(Tok_void) || match(Tok_int) || match(Tok_char) || match(Tok_double)
+ || match(Tok_Ellipsis)) {
+ type.append(previousLexeme());
+ } else {
+ return false;
+ }
+ } else if (match(Tok_int) || match(Tok_char) || match(Tok_double)) {
+ type.append(previousLexeme());
+ }
+
+ matchTemplateAngles(type);
+
+ while (match(Tok_const) || match(Tok_volatile))
+ type.append(previousLexeme());
+
+ if (match(Tok_Gulbrandsen))
+ type.append(previousLexeme());
+ else
+ break;
+ }
+
+ while (match(Tok_Ampersand) || match(Tok_Aster) || match(Tok_const) || match(Tok_Caret)
+ || match(Tok_Ellipsis))
+ type.append(previousLexeme());
+
+ if (match(Tok_LeftParenAster)) {
+ /*
+ A function pointer. This would be rather hard to handle without a
+ tokenizer hack, because a type can be followed with a left parenthesis
+ in some cases (e.g., 'operator int()'). The tokenizer recognizes '(*'
+ as a single token.
+ */
+ type.append(" "); // force a space after the type
+ type.append(previousLexeme());
+ type.appendHotspot();
+ if (match(Tok_Ident))
+ name = previousLexeme();
+ if (!match(Tok_RightParen))
+ return false;
+ type.append(previousLexeme());
+ if (!match(Tok_LeftParen))
+ return false;
+ type.append(previousLexeme());
+
+ /* parse the parameters. Ignore the parameter name from the type */
+ while (m_tok != Tok_RightParen && m_tok != Tok_Eoi) {
+ QString dummy;
+ if (!matchTypeAndName(type, dummy))
+ return false;
+ if (match(Tok_Comma))
+ type.append(previousLexeme());
+ }
+ if (!match(Tok_RightParen))
+ return false;
+ type.append(previousLexeme());
+ } else {
+ /*
+ The common case: Look for an optional identifier, then for
+ some array brackets.
+ */
+ type.appendHotspot();
+
+ if (match(Tok_Ident)) {
+ name = previousLexeme();
+ } else if (match(Tok_Comment)) {
+ /*
+ A neat hack: Commented-out parameter names are
+ recognized by qdoc. It's impossible to illustrate
+ here inside a C-style comment, because it requires
+ an asterslash. It's also impossible to illustrate
+ inside a C++-style comment, because the explanation
+ does not fit on one line.
+ */
+ auto match = s_varComment.match(previousLexeme());
+ if (match.hasMatch())
+ name = match.captured(1);
+ } else if (match(Tok_LeftParen)) {
+ name = "(";
+ while (m_tok != Tok_RightParen && m_tok != Tok_Eoi) {
+ name.append(lexeme());
+ readToken();
+ }
+ name.append(")");
+ readToken();
+ if (match(Tok_LeftBracket)) {
+ name.append("[");
+ while (m_tok != Tok_RightBracket && m_tok != Tok_Eoi) {
+ name.append(lexeme());
+ readToken();
+ }
+ name.append("]");
+ readToken();
+ }
+ }
+
+ if (m_tok == Tok_LeftBracket) {
+ int bracketDepth0 = m_tokenizer->bracketDepth();
+ while ((m_tokenizer->bracketDepth() >= bracketDepth0 && m_tok != Tok_Eoi)
+ || m_tok == Tok_RightBracket) {
+ type.append(lexeme());
+ readToken();
+ }
+ }
+ }
+ return true;
+}
+
+/*!
+ Parse the next function parameter, if there is one, and
+ append it to the internal parameter vector. Return true
+ if a parameter is parsed correctly. Otherwise return false.
+ */
+bool Parameters::matchParameter()
+{
+ if (match(Tok_QPrivateSignal)) {
+ m_privateSignal = true;
+ return true;
+ }
+
+ CodeChunk chunk;
+ QString name;
+ if (!matchTypeAndName(chunk, name))
+ return false;
+ QString type = chunk.toString();
+ QString defaultValue;
+ match(Tok_Comment);
+ if (match(Tok_Equal)) {
+ chunk.clear();
+ int pdepth = m_tokenizer->parenDepth();
+ while (m_tokenizer->parenDepth() >= pdepth
+ && (m_tok != Tok_Comma || (m_tokenizer->parenDepth() > pdepth))
+ && m_tok != Tok_Eoi) {
+ chunk.append(lexeme());
+ readToken();
+ }
+ defaultValue = chunk.toString();
+ }
+ append(type, name, defaultValue);
+ return true;
+}
+
+/*!
+ This function uses a Tokenizer to parse the \a signature,
+ which is a comma-separated list of parameter declarations.
+ If an error is detected, the Parameters object is cleared
+ and \c false is returned. Otherwise \c true is returned.
+ */
+bool Parameters::parse(const QString &signature)
+{
+ Tokenizer *outerTokenizer = m_tokenizer;
+ int outerTok = m_tok;
+
+ QByteArray latin1 = signature.toLatin1();
+ Tokenizer stringTokenizer(Location(), latin1);
+ stringTokenizer.setParsingFnOrMacro(true);
+ m_tokenizer = &stringTokenizer;
+
+ readToken();
+ do {
+ if (!matchParameter()) {
+ m_parameters.clear();
+ m_valid = false;
+ break;
+ }
+ } while (match(Tok_Comma));
+
+ m_tokenizer = outerTokenizer;
+ m_tok = outerTok;
+ return m_valid;
+}
+
+/*!
+ Append a Parameter constructed from \a type, \a name, and \a value
+ to the parameter vector.
+ */
+void Parameters::append(const QString &type, const QString &name, const QString &value)
+{
+ m_parameters.append(Parameter(type, name, value));
+}
+
+/*!
+ Returns the list of reconstructed parameters. If \a includeValues
+ is true, the default values are included, if any are present.
+ */
+QString Parameters::signature(bool includeValues) const
+{
+ QString result;
+ if (!m_parameters.empty()) {
+ for (int i = 0; i < m_parameters.size(); i++) {
+ if (i > 0)
+ result += ", ";
+ result += m_parameters.at(i).signature(includeValues);
+ }
+ }
+ return result;
+}
+
+/*!
+ Returns the signature of all the parameters with all the
+ spaces and commas removed. It is unintelligible, but that
+ is what the caller wants.
+
+ If \a names is true, the parameter names are included. If
+ \a values is true, the default values are included.
+ */
+QString Parameters::rawSignature(bool names, bool values) const
+{
+ QString raw;
+ const auto params = m_parameters;
+ for (const auto &parameter : params) {
+ raw += parameter.type();
+ if (names)
+ raw += parameter.name();
+ if (values)
+ raw += parameter.defaultValue();
+ }
+ return raw;
+}
+
+/*!
+ Parse the parameter \a signature by splitting the string,
+ and store the individual parameters in the parameter vector.
+
+ This method of parsing is naive but sufficient for QML methods
+ and macros.
+ */
+void Parameters::set(const QString &signature)
+{
+ clear();
+ if (!signature.isEmpty()) {
+ QStringList commaSplit = signature.split(',');
+ m_parameters.resize(commaSplit.size());
+ int i = 0;
+ for (const auto &item : std::as_const(commaSplit)) {
+ QStringList blankSplit = item.split(' ', Qt::SkipEmptyParts);
+ QString pDefault;
+ qsizetype defaultIdx = blankSplit.indexOf(QStringLiteral("="));
+ if (defaultIdx != -1) {
+ if (++defaultIdx < blankSplit.size())
+ pDefault = blankSplit.mid(defaultIdx).join(' ');
+ blankSplit = blankSplit.mid(0, defaultIdx - 1);
+ }
+ QString pName = blankSplit.takeLast();
+ QString pType = blankSplit.join(' ');
+ if (pType.isEmpty() && pName == QLatin1String("..."))
+ qSwap(pType, pName);
+ else {
+ int j = 0;
+ while (j < pName.size() && !pName.at(j).isLetter())
+ j++;
+ if (j > 0) {
+ pType += QChar(' ') + pName.left(j);
+ pName = pName.mid(j);
+ }
+ }
+ m_parameters[i++].set(pType, pName, pDefault);
+ }
+ }
+}
+
+/*!
+ Insert all the parameter names into names.
+ */
+QSet<QString> Parameters::getNames() const
+{
+ QSet<QString> names;
+ const auto params = m_parameters;
+ for (const auto &parameter : params) {
+ if (!parameter.name().isEmpty())
+ names.insert(parameter.name());
+ }
+ return names;
+}
+
+/*!
+ Construct a list of the parameter types and return it.
+ */
+QString Parameters::generateTypeList() const
+{
+ QString out;
+ if (count() > 0) {
+ for (int i = 0; i < count(); ++i) {
+ if (i > 0)
+ out += ", ";
+ out += m_parameters.at(i).type();
+ }
+ }
+ return out;
+}
+
+/*!
+ Construct a list of the parameter type/name pairs and
+ return it.
+*/
+QString Parameters::generateTypeAndNameList() const
+{
+ QString out;
+ if (count() > 0) {
+ for (int i = 0; i < count(); ++i) {
+ if (i != 0)
+ out += ", ";
+ const Parameter &p = m_parameters.at(i);
+ out += p.type();
+ if (out[out.size() - 1].isLetterOrNumber())
+ out += QLatin1Char(' ');
+ out += p.name();
+ }
+ }
+ return out;
+}
+
+/*!
+ Returns true if \a parameters contains the same parameter
+ signature as this.
+ */
+bool Parameters::match(const Parameters &parameters) const
+{
+ if (count() != parameters.count())
+ return false;
+ if (count() == 0)
+ return true;
+ for (int i = 0; i < count(); i++) {
+ if (parameters.at(i).type() != m_parameters.at(i).type())
+ return false;
+ }
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/parameters.h b/src/qdoc/qdoc/src/qdoc/parameters.h
new file mode 100644
index 000000000..1417b0958
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/parameters.h
@@ -0,0 +1,113 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef PARAMETERS_H
+#define PARAMETERS_H
+
+#include <QtCore/qlist.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qset.h>
+
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+class Location;
+class Tokenizer;
+class CodeChunk;
+
+class Parameter
+{
+public:
+ Parameter() = default;
+ explicit Parameter(QString type, QString name = QString(), QString defaultValue = QString())
+ : m_type(std::move(type)), m_name(std::move(name)), m_defaultValue(std::move(defaultValue))
+ {
+ }
+
+ void setName(const QString &name) { m_name = name; }
+ [[nodiscard]] bool hasType() const { return !m_type.isEmpty(); }
+ [[nodiscard]] const QString &type() const { return m_type; }
+ [[nodiscard]] const QString &name() const { return m_name; }
+ [[nodiscard]] const QString &defaultValue() const { return m_defaultValue; }
+ void setDefaultValue(const QString &t) { m_defaultValue = t; }
+
+ void set(const QString &type, const QString &name, const QString &defaultValue = QString())
+ {
+ m_type = type;
+ m_name = name;
+ m_defaultValue = defaultValue;
+ }
+
+ [[nodiscard]] QString signature(bool includeValue = false) const;
+
+ [[nodiscard]] const QString &canonicalType() const { return m_canonicalType; }
+ void setCanonicalType(const QString &t) { m_canonicalType = t; }
+
+public:
+ QString m_canonicalType {};
+ QString m_type {};
+ QString m_name {};
+ QString m_defaultValue {};
+};
+
+typedef QList<Parameter> ParameterVector;
+
+class Parameters
+{
+public:
+ Parameters();
+ Parameters(const QString &signature); // TODO: Making this explicit breaks QDoc
+
+ void clear()
+ {
+ m_parameters.clear();
+ m_privateSignal = false;
+ m_valid = true;
+ }
+ [[nodiscard]] const ParameterVector &parameters() const { return m_parameters; }
+ [[nodiscard]] bool isPrivateSignal() const { return m_privateSignal; }
+ [[nodiscard]] bool isEmpty() const { return m_parameters.isEmpty(); }
+ [[nodiscard]] bool isValid() const { return m_valid; }
+ [[nodiscard]] int count() const { return m_parameters.size(); }
+ void reserve(int count) { m_parameters.reserve(count); }
+ [[nodiscard]] const Parameter &at(int i) const { return m_parameters.at(i); }
+ Parameter &last() { return m_parameters.last(); }
+ [[nodiscard]] const Parameter &last() const { return m_parameters.last(); }
+ inline Parameter &operator[](int index) { return m_parameters[index]; }
+ void append(const QString &type, const QString &name, const QString &value);
+ void append(const QString &type, const QString &name) { append(type, name, QString()); }
+ void append(const QString &type) { append(type, QString(), QString()); }
+ void pop_back() { m_parameters.pop_back(); }
+ void setPrivateSignal() { m_privateSignal = true; }
+ [[nodiscard]] QString signature(bool includeValues = false) const;
+ [[nodiscard]] QString rawSignature(bool names = false, bool values = false) const;
+ void set(const QString &signature);
+ [[nodiscard]] QSet<QString> getNames() const;
+ [[nodiscard]] QString generateTypeList() const;
+ [[nodiscard]] QString generateTypeAndNameList() const;
+ [[nodiscard]] bool match(const Parameters &parameters) const;
+
+private:
+ void readToken();
+ QString lexeme();
+ QString previousLexeme();
+ bool match(int target);
+ void matchTemplateAngles(CodeChunk &type);
+ bool matchTypeAndName(CodeChunk &type, QString &name);
+ bool matchParameter();
+ bool parse(const QString &signature);
+
+private:
+ static QRegularExpression s_varComment;
+
+ bool m_valid {};
+ bool m_privateSignal {};
+ int m_tok {};
+ Tokenizer *m_tokenizer { nullptr };
+ ParameterVector m_parameters;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/parsererror.cpp b/src/qdoc/qdoc/src/qdoc/parsererror.cpp
new file mode 100644
index 000000000..b55660572
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/parsererror.cpp
@@ -0,0 +1,90 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "parsererror.h"
+#include "node.h"
+#include "qdocdatabase.h"
+#include "config.h"
+#include "utilities.h"
+
+#include <QtCore/qregularexpression.h>
+
+using namespace Qt::Literals::StringLiterals;
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class FnMatchError
+ \brief Encapsulates information about \fn match error during parsing.
+*/
+
+/*!
+ \variable FnMatchError::signature
+
+ Signature for the \fn topic that failed to match.
+*/
+
+/*!
+ \relates FnMatchError
+
+ Returns \c true if any parent of a C++ function represented by
+ \a signature is documented as \\internal.
+*/
+bool isParentInternal(const QString &signature)
+{
+ const QRegularExpression scoped_fn{R"((?:\w+(?:<[^>]+>)?::)+~?\w\S*\()"};
+ auto match = scoped_fn.match(signature);
+ if (!match.isValid())
+ return false;
+
+ auto scope = match.captured().split("::"_L1);
+ scope.removeLast(); // Drop function name
+
+ for (auto &s : scope)
+ if (qsizetype pos = s.indexOf('<'); pos >= 0)
+ s.truncate(pos);
+
+ auto parent = QDocDatabase::qdocDB()->findNodeByNameAndType(scope, &Node::isCppNode);
+ if (parent && !(parent->isClassNode() || parent->isNamespace())) {
+ qCDebug(lcQdoc).noquote()
+ << "Invalid scope:" << qPrintable(parent->nodeTypeString())
+ << qPrintable(parent->fullName())
+ << "for \\fn" << qPrintable(signature);
+ return false;
+ }
+
+ while (parent) {
+ if (parent->isInternal())
+ return true;
+ parent = parent->parent();
+ }
+
+ return false;
+}
+
+/*!
+ \class ParserErrorHandler
+ \brief Processes parser errors and outputs warnings for them.
+*/
+
+/*!
+ Generates a warning specific to FnMatchError.
+
+ Warnings for internal documentation are omitted. Specifically, this
+ (omission) happens if:
+
+ \list
+ \li \c {--showinternal} command line option is \b not
+ used, and
+ \li The warning is for an \\fn that is declared
+ under a namespace/class that is documented as
+ \\internal.
+ \endlist
+*/
+void ParserErrorHandler::operator()(const FnMatchError &e) const
+{
+ if (Config::instance().showInternal() || !isParentInternal(e.signature))
+ e.location.warning("Failed to find function when parsing \\fn %1"_L1.arg(e.signature));
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/parsererror.h b/src/qdoc/qdoc/src/qdoc/parsererror.h
new file mode 100644
index 000000000..85435366f
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/parsererror.h
@@ -0,0 +1,26 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef PARSERERROR_H
+#define PARSERERROR_H
+
+#include "location.h"
+
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+struct FnMatchError {
+ QString signature {};
+ Location location {};
+
+};
+
+struct ParserErrorHandler
+{
+ void operator()(const FnMatchError &e) const;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/propertynode.cpp b/src/qdoc/qdoc/src/qdoc/propertynode.cpp
new file mode 100644
index 000000000..6607af5bd
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/propertynode.cpp
@@ -0,0 +1,135 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "propertynode.h"
+
+#include "aggregate.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class PropertyNode
+
+ This class describes one instance of using the Q_PROPERTY macro.
+ */
+
+/*!
+ The constructor sets the \a parent and the \a name, but
+ everything else is left to default values.
+ */
+PropertyNode::PropertyNode(Aggregate *parent, const QString &name) : Node(Property, parent, name)
+{
+ // nothing
+}
+
+
+/*!
+ Returns a string representing an access function \a role.
+*/
+QString PropertyNode::roleName(FunctionRole role)
+{
+ switch (role) {
+ case FunctionRole::Getter:
+ return "getter";
+ case FunctionRole::Setter:
+ return "setter";
+ case FunctionRole::Resetter:
+ return "resetter";
+ case FunctionRole::Notifier:
+ return "notifier";
+ case FunctionRole::Bindable:
+ return "bindable";
+ default:
+ break;
+ }
+ return QString();
+}
+
+/*!
+ Sets this property's \e {overridden from} property to
+ \a baseProperty, which indicates that this property
+ overrides \a baseProperty. To begin with, all the values
+ in this property are set to the corresponding values in
+ \a baseProperty.
+
+ We probably should ensure that the constant and final
+ attributes are not being overridden improperly.
+ */
+void PropertyNode::setOverriddenFrom(const PropertyNode *baseProperty)
+{
+ for (qsizetype i{0}; i < (qsizetype)FunctionRole::NumFunctionRoles; ++i) {
+ if (m_functions[i].isEmpty())
+ m_functions[i] = baseProperty->m_functions[i];
+ }
+ if (m_stored == FlagValueDefault)
+ m_stored = baseProperty->m_stored;
+ if (m_writable == FlagValueDefault)
+ m_writable = baseProperty->m_writable;
+ if (m_user == FlagValueDefault)
+ m_user = baseProperty->m_user;
+ m_overrides = baseProperty;
+}
+
+/*!
+ Returns a string containing the data type qualified with "const" either
+ prepended to the data type or appended to it, or without the const
+ qualification, depending circumstances in the PropertyNode internal state.
+ */
+QString PropertyNode::qualifiedDataType() const
+{
+ if (m_propertyType != PropertyType::StandardProperty || m_type.startsWith(QLatin1String("const ")))
+ return m_type;
+
+ if (setters().isEmpty() && resetters().isEmpty()) {
+ if (m_type.contains(QLatin1Char('*')) || m_type.contains(QLatin1Char('&'))) {
+ // 'QWidget *' becomes 'QWidget *' const
+ return m_type + " const";
+ } else {
+ /*
+ 'int' becomes 'const int' ('int const' is
+ correct C++, but looks wrong)
+ */
+ return "const " + m_type;
+ }
+ } else {
+ return m_type;
+ }
+}
+
+/*!
+ Returns true if this property has an access function named \a name.
+ */
+bool PropertyNode::hasAccessFunction(const QString &name) const
+{
+ for (const auto &getter : getters()) {
+ if (getter->name() == name)
+ return true;
+ }
+ for (const auto &setter : setters()) {
+ if (setter->name() == name)
+ return true;
+ }
+ for (const auto &resetter : resetters()) {
+ if (resetter->name() == name)
+ return true;
+ }
+ for (const auto &notifier : notifiers()) {
+ if (notifier->name() == name)
+ return true;
+ }
+ return false;
+}
+
+/*!
+ Returns the role of \a functionNode for this property.
+ */
+PropertyNode::FunctionRole PropertyNode::role(const FunctionNode *functionNode) const
+{
+ for (qsizetype i{0}; i < (qsizetype)FunctionRole::NumFunctionRoles; i++) {
+ if (m_functions[i].contains(const_cast<FunctionNode *>(functionNode)))
+ return (FunctionRole)i;
+ }
+ return FunctionRole::Notifier; // TODO: Figure out a better way to handle 'not found'.
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/propertynode.h b/src/qdoc/qdoc/src/qdoc/propertynode.h
new file mode 100644
index 000000000..9ae59932b
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/propertynode.h
@@ -0,0 +1,93 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef PROPERTYNODE_H
+#define PROPERTYNODE_H
+
+#include "functionnode.h"
+#include "node.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+
+class PropertyNode : public Node
+{
+public:
+ enum class PropertyType { StandardProperty, BindableProperty };
+ enum class FunctionRole { Getter, Setter, Resetter, Notifier, Bindable, NumFunctionRoles };
+ static QString roleName(FunctionRole role);
+
+ PropertyNode(Aggregate *parent, const QString &name);
+
+ void setDataType(const QString &dataType) override { m_type = dataType; }
+ void addFunction(FunctionNode *function, FunctionRole role);
+ void addSignal(FunctionNode *function, FunctionRole role);
+ void setStored(bool stored) { m_stored = toFlagValue(stored); }
+ void setWritable(bool writable) { m_writable = toFlagValue(writable); }
+ void setOverriddenFrom(const PropertyNode *baseProperty);
+ void setConstant() { m_const = true; }
+ void setRequired() { m_required = true; }
+ void setPropertyType(PropertyType type) { m_propertyType = type; }
+
+ [[nodiscard]] const QString &dataType() const { return m_type; }
+ [[nodiscard]] QString qualifiedDataType() const;
+ [[nodiscard]] NodeList functions() const;
+ [[nodiscard]] const NodeList &functions(FunctionRole role) const
+ {
+ return m_functions[(int)role];
+ }
+ [[nodiscard]] const NodeList &getters() const { return functions(FunctionRole::Getter); }
+ [[nodiscard]] const NodeList &setters() const { return functions(FunctionRole::Setter); }
+ [[nodiscard]] const NodeList &resetters() const { return functions(FunctionRole::Resetter); }
+ [[nodiscard]] const NodeList &notifiers() const { return functions(FunctionRole::Notifier); }
+ [[nodiscard]] bool hasAccessFunction(const QString &name) const;
+ FunctionRole role(const FunctionNode *functionNode) const;
+ [[nodiscard]] bool isStored() const { return fromFlagValue(m_stored, storedDefault()); }
+ [[nodiscard]] bool isWritable() const { return fromFlagValue(m_writable, writableDefault()); }
+ [[nodiscard]] bool isConstant() const { return m_const; }
+ [[nodiscard]] bool isRequired() const { return m_required; }
+ [[nodiscard]] PropertyType propertyType() const { return m_propertyType; }
+ [[nodiscard]] const PropertyNode *overriddenFrom() const { return m_overrides; }
+
+ [[nodiscard]] bool storedDefault() const { return true; }
+ [[nodiscard]] bool writableDefault() const { return !setters().isEmpty(); }
+
+private:
+ QString m_type {};
+ PropertyType m_propertyType { PropertyType::StandardProperty };
+ NodeList m_functions[(qsizetype)FunctionRole::NumFunctionRoles] {};
+ FlagValue m_stored { FlagValueDefault };
+ FlagValue m_writable { FlagValueDefault };
+ FlagValue m_user { FlagValueDefault };
+ bool m_const { false };
+ bool m_required { false };
+ const PropertyNode *m_overrides { nullptr };
+};
+
+inline void PropertyNode::addFunction(FunctionNode *function, FunctionRole role)
+{
+ m_functions[(int)role].append(function);
+ function->addAssociatedProperty(this);
+}
+
+inline void PropertyNode::addSignal(FunctionNode *function, FunctionRole role)
+{
+ m_functions[(int)role].append(function);
+ function->addAssociatedProperty(this);
+}
+
+inline NodeList PropertyNode::functions() const
+{
+ NodeList list;
+ for (qsizetype i{0}; i < (qsizetype)FunctionRole::NumFunctionRoles; ++i)
+ list += m_functions[i];
+ return list;
+}
+
+QT_END_NAMESPACE
+
+#endif // PROPERTYNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/proxynode.cpp b/src/qdoc/qdoc/src/qdoc/proxynode.cpp
new file mode 100644
index 000000000..49e4be34e
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/proxynode.cpp
@@ -0,0 +1,54 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "proxynode.h"
+
+#include "tree.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class ProxyNode
+ \brief A class for representing an Aggregate that is documented in a different module.
+
+ This class is used to represent an Aggregate (usually a class)
+ that is located and documented in a different module. In the
+ current module, a ProxyNode holds child nodes that are related
+ to the class in the other module.
+
+ For example, class QHash is located and documented in QtCore.
+ There are many global functions named qHash() in QtCore that
+ are all related to class QHash using the \c relates command.
+ There are also a few qHash() function in QtNetwork that are
+ related to QHash. These functions must be documented when the
+ documentation for QtNetwork is generated, but the reference
+ page for QHash must link to that documentation in its related
+ nonmembers list.
+
+ The ProxyNode allows qdoc to construct links to the related
+ functions (or other things?) in QtNetwork from the reference
+ page in QtCore.
+ */
+
+/*!
+ Constructs the ProxyNode, which at this point looks like any
+ other Aggregate, and then finds the Tree this node is in and
+ appends this node to that Tree's proxy list so it will be
+ easy to find later.
+ */
+ProxyNode::ProxyNode(Aggregate *parent, const QString &name) : Aggregate(Node::Proxy, parent, name)
+{
+ tree()->appendProxy(this);
+}
+
+/*! \fn bool ProxyNode::docMustBeGenerated() const
+ Returns true because a ProxyNode always means some documentation
+ must be generated.
+*/
+
+/*! \fn bool ProxyNode::isRelatableType() const
+ Returns true because the ProxyNode exists so that elements
+ can be related to it with the \c {\\relates} command.
+*/
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/proxynode.h b/src/qdoc/qdoc/src/qdoc/proxynode.h
new file mode 100644
index 000000000..cced34892
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/proxynode.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef PROXYNODE_H
+#define PROXYNODE_H
+
+#include "aggregate.h"
+
+#include <QtCore/qglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class ProxyNode : public Aggregate
+{
+public:
+ ProxyNode(Aggregate *parent, const QString &name);
+ [[nodiscard]] bool docMustBeGenerated() const override { return true; }
+ [[nodiscard]] bool isRelatableType() const override { return true; }
+};
+
+QT_END_NAMESPACE
+
+#endif // PROXYNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/puredocparser.cpp b/src/qdoc/qdoc/src/qdoc/puredocparser.cpp
new file mode 100644
index 000000000..c6de06a9c
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/puredocparser.cpp
@@ -0,0 +1,74 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "puredocparser.h"
+
+#include "qdocdatabase.h"
+#include "tokenizer.h"
+
+#include <cerrno>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ Parses the source file identified by \a filePath and adds its
+ parsed contents to the database. The \a location is used for
+ reporting errors.
+ */
+std::vector<UntiedDocumentation> PureDocParser::parse_qdoc_file(const QString &filePath)
+{
+ QFile in(filePath);
+ if (!in.open(QIODevice::ReadOnly)) {
+ location.error(
+ QStringLiteral("Can't open source file '%1' (%2)").arg(filePath, strerror(errno)));
+ return {};
+ }
+
+ return processQdocComments(in);
+}
+
+/*!
+ This is called by parseSourceFile() to do the actual parsing
+ and tree building. It only processes qdoc comments. It skips
+ everything else.
+ */
+std::vector<UntiedDocumentation> PureDocParser::processQdocComments(QFile& input_file)
+{
+ std::vector<UntiedDocumentation> untied{};
+
+ Tokenizer tokenizer(Location{input_file.fileName()}, input_file);
+
+ const QSet<QString> &commands = CppCodeParser::topic_commands + CppCodeParser::meta_commands;
+
+ int token = tokenizer.getToken();
+ while (token != Tok_Eoi) {
+ if (token != Tok_Doc) {
+ token = tokenizer.getToken();
+ continue;
+ }
+ QString comment = tokenizer.lexeme(); // returns an entire qdoc comment.
+ Location start_loc(tokenizer.location());
+ token = tokenizer.getToken();
+
+ Doc::trimCStyleComment(start_loc, comment);
+ Location end_loc(tokenizer.location());
+
+ // Doc constructor parses the comment.
+ Doc doc(start_loc, end_loc, comment, commands, CppCodeParser::topic_commands);
+ if (doc.topicsUsed().isEmpty()) {
+ doc.location().warning(QStringLiteral("This qdoc comment contains no topic command "
+ "(e.g., '\\%1', '\\%2').")
+ .arg(COMMAND_MODULE, COMMAND_PAGE));
+ continue;
+ }
+
+ if (hasTooManyTopics(doc))
+ continue;
+
+ untied.emplace_back(UntiedDocumentation{doc, QStringList()});
+ }
+
+ return untied;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/puredocparser.h b/src/qdoc/qdoc/src/qdoc/puredocparser.h
new file mode 100644
index 000000000..d3467ed92
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/puredocparser.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef PUREDOCPARSER_H
+#define PUREDOCPARSER_H
+
+#include "cppcodeparser.h"
+
+#include <QtCore/QFile>
+
+QT_BEGIN_NAMESPACE
+
+class Location;
+
+class PureDocParser
+{
+public:
+ PureDocParser(const Location& location) : location{location} {}
+
+ std::vector<UntiedDocumentation> parse_qdoc_file(const QString& filePath);
+
+private:
+ std::vector<UntiedDocumentation> processQdocComments(QFile& input_file);
+
+private:
+ const Location& location;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.cpp b/src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.cpp
new file mode 100644
index 000000000..6586056a4
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.cpp
@@ -0,0 +1,177 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qdoccommandlineparser.h"
+
+#include "utilities.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qfile.h>
+
+QDocCommandLineParser::QDocCommandLineParser()
+ : QCommandLineParser(),
+ defineOption(QStringList() << QStringLiteral("D")),
+ dependsOption(QStringList() << QStringLiteral("depends")),
+ highlightingOption(QStringList() << QStringLiteral("highlighting")),
+ showInternalOption(QStringList() << QStringLiteral("showinternal")),
+ redirectDocumentationToDevNullOption(QStringList()
+ << QStringLiteral("redirect-documentation-to-dev-null")),
+ noExamplesOption(QStringList() << QStringLiteral("no-examples")),
+ indexDirOption(QStringList() << QStringLiteral("indexdir")),
+ installDirOption(QStringList() << QStringLiteral("installdir")),
+ outputDirOption(QStringList() << QStringLiteral("outputdir")),
+ outputFormatOption(QStringList() << QStringLiteral("outputformat")),
+ noLinkErrorsOption(QStringList() << QStringLiteral("no-link-errors")),
+ autoLinkErrorsOption(QStringList() << QStringLiteral("autolink-errors")),
+ debugOption(QStringList() << QStringLiteral("debug")),
+ atomsDumpOption("atoms-dump"),
+ prepareOption(QStringList() << QStringLiteral("prepare")),
+ generateOption(QStringList() << QStringLiteral("generate")),
+ logProgressOption(QStringList() << QStringLiteral("log-progress")),
+ singleExecOption(QStringList() << QStringLiteral("single-exec")),
+ includePathOption("I", "Add dir to the include path for header files.", "path"),
+ includePathSystemOption("isystem", "Add dir to the system include path for header files.",
+ "path"),
+ frameworkOption("F", "Add macOS framework to the include path for header files.",
+ "framework"),
+ timestampsOption(QStringList() << QStringLiteral("timestamps")),
+ useDocBookExtensions(QStringList() << QStringLiteral("docbook-extensions"))
+{
+ setApplicationDescription(QStringLiteral("Qt documentation generator"));
+ addHelpOption();
+ addVersionOption();
+
+ setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
+
+ addPositionalArgument("file1.qdocconf ...", QStringLiteral("Input files"));
+
+ defineOption.setDescription(
+ QStringLiteral("Define the argument as a macro while parsing sources"));
+ defineOption.setValueName(QStringLiteral("macro[=def]"));
+ addOption(defineOption);
+
+ dependsOption.setDescription(QStringLiteral("Specify dependent modules"));
+ dependsOption.setValueName(QStringLiteral("module"));
+ addOption(dependsOption);
+
+ highlightingOption.setDescription(
+ QStringLiteral("Turn on syntax highlighting (makes qdoc run slower)"));
+ addOption(highlightingOption);
+
+ showInternalOption.setDescription(QStringLiteral("Include content marked internal"));
+ addOption(showInternalOption);
+
+ redirectDocumentationToDevNullOption.setDescription(
+ QStringLiteral("Save all documentation content to /dev/null. "
+ " Useful if someone is interested in qdoc errors only."));
+ addOption(redirectDocumentationToDevNullOption);
+
+ noExamplesOption.setDescription(QStringLiteral("Do not generate documentation for examples"));
+ addOption(noExamplesOption);
+
+ indexDirOption.setDescription(
+ QStringLiteral("Specify a directory where QDoc should search for index files to load"));
+ indexDirOption.setValueName(QStringLiteral("dir"));
+ addOption(indexDirOption);
+
+ installDirOption.setDescription(QStringLiteral(
+ "Specify the directory where the output will be after running \"make install\""));
+ installDirOption.setValueName(QStringLiteral("dir"));
+ addOption(installDirOption);
+
+ outputDirOption.setDescription(
+ QStringLiteral("Specify output directory, overrides setting in qdocconf file"));
+ outputDirOption.setValueName(QStringLiteral("dir"));
+ addOption(outputDirOption);
+
+ outputFormatOption.setDescription(
+ QStringLiteral("Specify output format, overrides setting in qdocconf file"));
+ outputFormatOption.setValueName(QStringLiteral("format"));
+ addOption(outputFormatOption);
+
+ noLinkErrorsOption.setDescription(
+ QStringLiteral("Do not print link errors (i.e. missing targets)"));
+ addOption(noLinkErrorsOption);
+
+ autoLinkErrorsOption.setDescription(QStringLiteral("Show errors when automatic linking fails"));
+ addOption(autoLinkErrorsOption);
+
+ debugOption.setDescription(QStringLiteral("Enable debug output"));
+ addOption(debugOption);
+
+ atomsDumpOption.setDescription(QStringLiteral(
+ "Shows a human-readable form of the intermediate result of parsing a block-comment."));
+ addOption(atomsDumpOption);
+
+ prepareOption.setDescription(
+ QStringLiteral("Run qdoc only to generate an index file, not the docs"));
+ addOption(prepareOption);
+
+ generateOption.setDescription(
+ QStringLiteral("Run qdoc to read the index files and generate the docs"));
+ addOption(generateOption);
+
+ logProgressOption.setDescription(QStringLiteral("Log progress on stderr."));
+ addOption(logProgressOption);
+
+ singleExecOption.setDescription(QStringLiteral("Run qdoc once over all the qdoc conf files."));
+ addOption(singleExecOption);
+
+ includePathOption.setFlags(QCommandLineOption::ShortOptionStyle);
+ addOption(includePathOption);
+
+ addOption(includePathSystemOption);
+
+ frameworkOption.setFlags(QCommandLineOption::ShortOptionStyle);
+ addOption(frameworkOption);
+
+ timestampsOption.setDescription(QStringLiteral("Timestamp each qdoc log line."));
+ addOption(timestampsOption);
+
+ useDocBookExtensions.setDescription(
+ QStringLiteral("Use the DocBook Library extensions for metadata."));
+ addOption(useDocBookExtensions);
+}
+
+/*!
+ * \internal
+ *
+ * Create a list of arguments from the command line and/or file(s).
+ * This lets QDoc accept arguments contained in a file provided as a
+ * command-line argument prepended by '@'.
+ */
+static QStringList argumentsFromCommandLineAndFile(const QStringList &arguments)
+{
+ QStringList allArguments;
+ allArguments.reserve(arguments.size());
+ for (const QString &argument : arguments) {
+ // "@file" doesn't start with a '-' so we can't use QCommandLineParser for it
+ if (argument.startsWith(QLatin1Char('@'))) {
+ QString optionsFile = argument;
+ optionsFile.remove(0, 1);
+ if (optionsFile.isEmpty())
+ qFatal("The @ option requires an input file");
+ QFile f(optionsFile);
+ if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
+ qFatal("Cannot open options file specified with @: %ls",
+ qUtf16Printable(optionsFile));
+ while (!f.atEnd()) {
+ QString line = QString::fromLocal8Bit(f.readLine().trimmed());
+ if (!line.isEmpty())
+ allArguments << line;
+ }
+ } else {
+ allArguments << argument;
+ }
+ }
+ return allArguments;
+}
+
+void QDocCommandLineParser::process(const QStringList &arguments)
+{
+ auto allArguments = argumentsFromCommandLineAndFile(arguments);
+ QCommandLineParser::process(allArguments);
+
+ if (isSet(singleExecOption) && isSet(indexDirOption))
+ qCWarning(lcQdoc) << "Warning: -indexdir option ignored: Index files are not used in single-exec mode.";
+}
diff --git a/src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.h b/src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.h
new file mode 100644
index 000000000..57b36b582
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qdoccommandlineparser.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QDOCCOMMANDLINEPARSER_H
+#define QDOCCOMMANDLINEPARSER_H
+
+#include <QtCore/qcommandlineparser.h>
+
+QT_BEGIN_NAMESPACE
+
+struct QDocCommandLineParser : public QCommandLineParser
+{
+ QDocCommandLineParser();
+ void process(const QStringList &arguments);
+
+ QCommandLineOption defineOption, dependsOption, highlightingOption;
+ QCommandLineOption showInternalOption, redirectDocumentationToDevNullOption;
+ QCommandLineOption noExamplesOption, indexDirOption, installDirOption;
+ QCommandLineOption outputDirOption, outputFormatOption;
+ QCommandLineOption noLinkErrorsOption, autoLinkErrorsOption, debugOption, atomsDumpOption;
+ QCommandLineOption prepareOption, generateOption, logProgressOption, singleExecOption;
+ QCommandLineOption includePathOption, includePathSystemOption, frameworkOption;
+ QCommandLineOption timestampsOption, useDocBookExtensions;
+};
+
+QT_END_NAMESPACE
+
+#endif // QDOCCOMMANDLINEPARSER_H
diff --git a/src/qdoc/qdoc/src/qdoc/qdocdatabase.cpp b/src/qdoc/qdoc/src/qdoc/qdocdatabase.cpp
new file mode 100644
index 000000000..57e88fbde
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qdocdatabase.cpp
@@ -0,0 +1,1685 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qdocdatabase.h"
+
+#include "atom.h"
+#include "collectionnode.h"
+#include "functionnode.h"
+#include "generator.h"
+#include "qdocindexfiles.h"
+#include "tree.h"
+
+#include <QtCore/qregularexpression.h>
+#include <stack>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+static NodeMultiMap emptyNodeMultiMap_;
+
+/*!
+ \class QDocForest
+
+ A class representing a forest of Tree objects.
+
+ This private class manages a collection of Tree objects (a
+ forest) for the singleton QDocDatabase object. It is only
+ accessed by that singleton QDocDatabase object, which is a
+ friend. Each tree in the forest is an instance of class
+ Tree, which is a mostly private class. Both QDocForest and
+ QDocDatabase are friends of Tree and have full access.
+
+ There are two kinds of trees in the forest, differing not
+ in structure but in use. One Tree is the primary tree. It
+ is the tree representing the module being documented. All
+ the other trees in the forest are called index trees. Each
+ one represents the contents of the index file for one of
+ the modules the current module must be able to link to.
+
+ The instances of subclasses of Node in the primary tree
+ will contain documentation in an instance of Doc. The
+ index trees contain no documentation, and each Node in
+ an index tree is marked as an index node.
+
+ Each tree is named with the name of its module.
+
+ The search order is created by searchOrder(), if it has
+ not already been created. The search order and module
+ names arrays have parallel structure, i.e. modulNames_[i]
+ is the module name of the Tree at searchOrder_[i].
+
+ The primary tree is always the first tree in the search
+ order. i.e., when the database is searched, the primary
+ tree is always searched first, unless a specific tree is
+ being searched.
+ */
+
+/*!
+ Destroys the qdoc forest. This requires deleting
+ each Tree in the forest. Note that the forest has
+ been transferred into the search order array, so
+ what is really being used to destroy the forest
+ is the search order array.
+ */
+QDocForest::~QDocForest()
+{
+ for (auto *entry : m_searchOrder)
+ delete entry;
+ m_forest.clear();
+ m_searchOrder.clear();
+ m_indexSearchOrder.clear();
+ m_moduleNames.clear();
+ m_primaryTree = nullptr;
+}
+
+/*!
+ Initializes the forest prior to a traversal and
+ returns a pointer to the primary tree. If the
+ forest is empty, it returns \nullptr.
+ */
+Tree *QDocForest::firstTree()
+{
+ m_currentIndex = 0;
+ return (!searchOrder().isEmpty() ? searchOrder()[0] : nullptr);
+}
+
+/*!
+ Increments the forest's current tree index. If the current
+ tree index is still within the forest, the function returns
+ the pointer to the current tree. Otherwise it returns \nullptr.
+ */
+Tree *QDocForest::nextTree()
+{
+ ++m_currentIndex;
+ return (m_currentIndex < searchOrder().size() ? searchOrder()[m_currentIndex] : nullptr);
+}
+
+/*!
+ \fn Tree *QDocForest::primaryTree()
+
+ Returns the pointer to the primary tree.
+ */
+
+/*!
+ Finds the tree for module \a t in the forest and
+ sets the primary tree to be that tree. After the
+ primary tree is set, that tree is removed from the
+ forest.
+
+ \node It gets re-inserted into the forest after the
+ search order is built.
+ */
+void QDocForest::setPrimaryTree(const QString &t)
+{
+ QString T = t.toLower();
+ m_primaryTree = findTree(T);
+ m_forest.remove(T);
+ if (m_primaryTree == nullptr)
+ qCCritical(lcQdoc) << "Error: Could not set primary tree to" << t;
+}
+
+/*!
+ If the search order array is empty, create the search order.
+ If the search order array is not empty, do nothing.
+ */
+void QDocForest::setSearchOrder(const QStringList &t)
+{
+ if (!m_searchOrder.isEmpty())
+ return;
+
+ /* Allocate space for the search order. */
+ m_searchOrder.reserve(m_forest.size() + 1);
+ m_searchOrder.clear();
+ m_moduleNames.reserve(m_forest.size() + 1);
+ m_moduleNames.clear();
+
+ /* The primary tree is always first in the search order. */
+ QString primaryName = primaryTree()->physicalModuleName();
+ m_searchOrder.append(m_primaryTree);
+ m_moduleNames.append(primaryName);
+ m_forest.remove(primaryName);
+
+ for (const QString &m : t) {
+ if (primaryName != m) {
+ auto it = m_forest.find(m);
+ if (it != m_forest.end()) {
+ m_searchOrder.append(it.value());
+ m_moduleNames.append(m);
+ m_forest.remove(m);
+ }
+ }
+ }
+ /*
+ If any trees remain in the forest, just add them
+ to the search order sequentially, because we don't
+ know any better at this point.
+ */
+ if (!m_forest.isEmpty()) {
+ for (auto it = m_forest.begin(); it != m_forest.end(); ++it) {
+ m_searchOrder.append(it.value());
+ m_moduleNames.append(it.key());
+ }
+ m_forest.clear();
+ }
+
+ /*
+ Rebuild the forest after constructing the search order.
+ It was destroyed during construction of the search order,
+ but it is needed for module-specific searches.
+
+ Note that this loop also inserts the primary tree into the
+ forrest. That is a requirement.
+ */
+ for (int i = 0; i < m_searchOrder.size(); ++i) {
+ if (!m_forest.contains(m_moduleNames.at(i))) {
+ m_forest.insert(m_moduleNames.at(i), m_searchOrder.at(i));
+ }
+ }
+}
+
+/*!
+ Returns an ordered array of Tree pointers that represents
+ the order in which the trees should be searched. The first
+ Tree in the array is the tree for the current module, i.e.
+ the module for which qdoc is generating documentation.
+
+ The other Tree pointers in the array represent the index
+ files that were loaded in preparation for generating this
+ module's documentation. Each Tree pointer represents one
+ index file. The index file Tree points have been ordered
+ heuristically to, hopefully, minimize searching. Thr order
+ will probably be changed.
+
+ If the search order array is empty, this function calls
+ indexSearchOrder(). The search order array is empty while
+ the index files are being loaded, but some searches must
+ be performed during this time, notably searches for base
+ class nodes. These searches require a temporary search
+ order. The temporary order changes throughout the loading
+ of the index files, but it is always the tree for the
+ current index file first, followed by the trees for the
+ index files that have already been loaded. The only
+ ordering required in this temporary search order is that
+ the current tree must be searched first.
+ */
+const QList<Tree *> &QDocForest::searchOrder()
+{
+ if (m_searchOrder.isEmpty())
+ return indexSearchOrder();
+ return m_searchOrder;
+}
+
+/*!
+ There are two search orders used by qdoc when searching for
+ things. The normal search order is returned by searchOrder(),
+ but this normal search order is not known until all the index
+ files have been read. At that point, setSearchOrder() is
+ called.
+
+ During the reading of the index files, the vector holding
+ the normal search order remains empty. Whenever the search
+ order is requested, if that vector is empty, this function
+ is called to return a temporary search order, which includes
+ all the index files that have been read so far, plus the
+ one being read now. That one is prepended to the front of
+ the vector.
+ */
+const QList<Tree *> &QDocForest::indexSearchOrder()
+{
+ if (m_forest.size() > m_indexSearchOrder.size())
+ m_indexSearchOrder.prepend(m_primaryTree);
+ return m_indexSearchOrder;
+}
+
+/*!
+ Create a new Tree for the index file for the specified
+ \a module and add it to the forest. Return the pointer
+ to its root.
+ */
+NamespaceNode *QDocForest::newIndexTree(const QString &module)
+{
+ m_primaryTree = new Tree(module, m_qdb);
+ m_forest.insert(module.toLower(), m_primaryTree);
+ return m_primaryTree->root();
+}
+
+/*!
+ Create a new Tree for use as the primary tree. This tree
+ will represent the primary module. \a module is camel case.
+ */
+void QDocForest::newPrimaryTree(const QString &module)
+{
+ m_primaryTree = new Tree(module, m_qdb);
+}
+
+/*!
+ Searches through the forest for a node named \a targetPath
+ and returns a pointer to it if found. The \a relative node
+ is the starting point. It only makes sense for the primary
+ tree, which is searched first. After the primary tree has
+ been searched, \a relative is set to 0 for searching the
+ other trees, which are all index trees. With relative set
+ to 0, the starting point for each index tree is the root
+ of the index tree.
+
+ If \a targetPath is resolved successfully but it refers to
+ a \\section title, continue the search, keeping the section
+ title as a fallback if no higher-priority targets are found.
+ */
+const Node *QDocForest::findNodeForTarget(QStringList &targetPath, const Node *relative,
+ Node::Genus genus, QString &ref)
+{
+ int flags = SearchBaseClasses | SearchEnumValues;
+
+ QString entity = targetPath.takeFirst();
+ QStringList entityPath = entity.split("::");
+
+ QString target;
+ if (!targetPath.isEmpty())
+ target = targetPath.takeFirst();
+
+ TargetRec::TargetType type = TargetRec::Unknown;
+ const Node *tocNode = nullptr;
+ for (const auto *tree : searchOrder()) {
+ const Node *n = tree->findNodeForTarget(entityPath, target, relative, flags, genus, ref, &type);
+ if (n) {
+ // Targets referring to non-section titles are returned immediately
+ if (type != TargetRec::Contents)
+ return n;
+ if (!tocNode)
+ tocNode = n;
+ }
+ relative = nullptr;
+ }
+ return tocNode;
+}
+
+/*!
+ Finds the FunctionNode for the qualified function name
+ in \a path, that also has the specified \a parameters.
+ Returns a pointer to the first matching function.
+
+ \a relative is a node in the primary tree where the search
+ should begin. It is only used when searching the primary
+ tree. \a genus can be used to force the search to find a
+ C++ function or a QML function.
+ */
+const FunctionNode *QDocForest::findFunctionNode(const QStringList &path,
+ const Parameters &parameters, const Node *relative,
+ Node::Genus genus)
+{
+ for (const auto *tree : searchOrder()) {
+ const FunctionNode *fn = tree->findFunctionNode(path, parameters, relative, genus);
+ if (fn)
+ return fn;
+ relative = nullptr;
+ }
+ return nullptr;
+}
+
+/*! \class QDocDatabase
+ This class provides exclusive access to the qdoc database,
+ which consists of a forrest of trees and a lot of maps and
+ other useful data structures.
+ */
+
+QDocDatabase *QDocDatabase::s_qdocDB = nullptr;
+NodeMap QDocDatabase::s_typeNodeMap;
+NodeMultiMap QDocDatabase::s_obsoleteClasses;
+NodeMultiMap QDocDatabase::s_classesWithObsoleteMembers;
+NodeMultiMap QDocDatabase::s_obsoleteQmlTypes;
+NodeMultiMap QDocDatabase::s_qmlTypesWithObsoleteMembers;
+NodeMultiMap QDocDatabase::s_cppClasses;
+NodeMultiMap QDocDatabase::s_qmlBasicTypes;
+NodeMultiMap QDocDatabase::s_qmlTypes;
+NodeMultiMap QDocDatabase::s_examples;
+NodeMultiMapMap QDocDatabase::s_newClassMaps;
+NodeMultiMapMap QDocDatabase::s_newQmlTypeMaps;
+NodeMultiMapMap QDocDatabase::s_newEnumValueMaps;
+NodeMultiMapMap QDocDatabase::s_newSinceMaps;
+
+/*!
+ Constructs the singleton qdoc database object. The singleton
+ constructs the \a forest_ object, which is also a singleton.
+ \a m_showInternal is normally false. If it is true, qdoc will
+ write documentation for nodes marked \c internal.
+
+ \a singleExec_ is false when qdoc is being used in the standard
+ way of running qdoc twices for each module, first with -prepare
+ and then with -generate. First the -prepare phase is run for
+ each module, then the -generate phase is run for each module.
+
+ When \a singleExec_ is true, qdoc is run only once. During the
+ single execution, qdoc processes the qdocconf files for all the
+ modules sequentially in a loop. Each source file for each module
+ is read exactly once.
+ */
+QDocDatabase::QDocDatabase() : m_forest(this)
+{
+ // nothing
+}
+
+/*!
+ Creates the singleton. Allows only one instance of the class
+ to be created. Returns a pointer to the singleton.
+*/
+QDocDatabase *QDocDatabase::qdocDB()
+{
+ if (s_qdocDB == nullptr) {
+ s_qdocDB = new QDocDatabase;
+ initializeDB();
+ }
+ return s_qdocDB;
+}
+
+/*!
+ Destroys the singleton.
+ */
+void QDocDatabase::destroyQdocDB()
+{
+ if (s_qdocDB != nullptr) {
+ delete s_qdocDB;
+ s_qdocDB = nullptr;
+ }
+}
+
+/*!
+ Initialize data structures in the singleton qdoc database.
+
+ In particular, the type node map is initialized with a lot
+ type names that don't refer to documented types. For example,
+ many C++ standard types are included. These might be documented
+ here at some point, but for now they are not. Other examples
+ include \c array and \c data, which are just generic names
+ used as place holders in function signatures that appear in
+ the documentation.
+
+ \note Do not add QML basic types into this list as it will
+ break linking to those types.
+ */
+void QDocDatabase::initializeDB()
+{
+ s_typeNodeMap.insert("accepted", nullptr);
+ s_typeNodeMap.insert("actionPerformed", nullptr);
+ s_typeNodeMap.insert("activated", nullptr);
+ s_typeNodeMap.insert("alias", nullptr);
+ s_typeNodeMap.insert("anchors", nullptr);
+ s_typeNodeMap.insert("any", nullptr);
+ s_typeNodeMap.insert("array", nullptr);
+ s_typeNodeMap.insert("autoSearch", nullptr);
+ s_typeNodeMap.insert("axis", nullptr);
+ s_typeNodeMap.insert("backClicked", nullptr);
+ s_typeNodeMap.insert("boomTime", nullptr);
+ s_typeNodeMap.insert("border", nullptr);
+ s_typeNodeMap.insert("buttonClicked", nullptr);
+ s_typeNodeMap.insert("callback", nullptr);
+ s_typeNodeMap.insert("char", nullptr);
+ s_typeNodeMap.insert("clicked", nullptr);
+ s_typeNodeMap.insert("close", nullptr);
+ s_typeNodeMap.insert("closed", nullptr);
+ s_typeNodeMap.insert("cond", nullptr);
+ s_typeNodeMap.insert("data", nullptr);
+ s_typeNodeMap.insert("dataReady", nullptr);
+ s_typeNodeMap.insert("dateString", nullptr);
+ s_typeNodeMap.insert("dateTimeString", nullptr);
+ s_typeNodeMap.insert("datetime", nullptr);
+ s_typeNodeMap.insert("day", nullptr);
+ s_typeNodeMap.insert("deactivated", nullptr);
+ s_typeNodeMap.insert("drag", nullptr);
+ s_typeNodeMap.insert("easing", nullptr);
+ s_typeNodeMap.insert("error", nullptr);
+ s_typeNodeMap.insert("exposure", nullptr);
+ s_typeNodeMap.insert("fatalError", nullptr);
+ s_typeNodeMap.insert("fileSelected", nullptr);
+ s_typeNodeMap.insert("flags", nullptr);
+ s_typeNodeMap.insert("float", nullptr);
+ s_typeNodeMap.insert("focus", nullptr);
+ s_typeNodeMap.insert("focusZone", nullptr);
+ s_typeNodeMap.insert("format", nullptr);
+ s_typeNodeMap.insert("framePainted", nullptr);
+ s_typeNodeMap.insert("from", nullptr);
+ s_typeNodeMap.insert("frontClicked", nullptr);
+ s_typeNodeMap.insert("function", nullptr);
+ s_typeNodeMap.insert("hasOpened", nullptr);
+ s_typeNodeMap.insert("hovered", nullptr);
+ s_typeNodeMap.insert("hoveredTitle", nullptr);
+ s_typeNodeMap.insert("hoveredUrl", nullptr);
+ s_typeNodeMap.insert("imageCapture", nullptr);
+ s_typeNodeMap.insert("imageProcessing", nullptr);
+ s_typeNodeMap.insert("index", nullptr);
+ s_typeNodeMap.insert("initialized", nullptr);
+ s_typeNodeMap.insert("isLoaded", nullptr);
+ s_typeNodeMap.insert("item", nullptr);
+ s_typeNodeMap.insert("key", nullptr);
+ s_typeNodeMap.insert("keysequence", nullptr);
+ s_typeNodeMap.insert("listViewClicked", nullptr);
+ s_typeNodeMap.insert("loadRequest", nullptr);
+ s_typeNodeMap.insert("locale", nullptr);
+ s_typeNodeMap.insert("location", nullptr);
+ s_typeNodeMap.insert("long", nullptr);
+ s_typeNodeMap.insert("message", nullptr);
+ s_typeNodeMap.insert("messageReceived", nullptr);
+ s_typeNodeMap.insert("mode", nullptr);
+ s_typeNodeMap.insert("month", nullptr);
+ s_typeNodeMap.insert("name", nullptr);
+ s_typeNodeMap.insert("number", nullptr);
+ s_typeNodeMap.insert("object", nullptr);
+ s_typeNodeMap.insert("offset", nullptr);
+ s_typeNodeMap.insert("ok", nullptr);
+ s_typeNodeMap.insert("openCamera", nullptr);
+ s_typeNodeMap.insert("openImage", nullptr);
+ s_typeNodeMap.insert("openVideo", nullptr);
+ s_typeNodeMap.insert("padding", nullptr);
+ s_typeNodeMap.insert("parent", nullptr);
+ s_typeNodeMap.insert("path", nullptr);
+ s_typeNodeMap.insert("photoModeSelected", nullptr);
+ s_typeNodeMap.insert("position", nullptr);
+ s_typeNodeMap.insert("precision", nullptr);
+ s_typeNodeMap.insert("presetClicked", nullptr);
+ s_typeNodeMap.insert("preview", nullptr);
+ s_typeNodeMap.insert("previewSelected", nullptr);
+ s_typeNodeMap.insert("progress", nullptr);
+ s_typeNodeMap.insert("puzzleLost", nullptr);
+ s_typeNodeMap.insert("qmlSignal", nullptr);
+ s_typeNodeMap.insert("rectangle", nullptr);
+ s_typeNodeMap.insert("request", nullptr);
+ s_typeNodeMap.insert("requestId", nullptr);
+ s_typeNodeMap.insert("section", nullptr);
+ s_typeNodeMap.insert("selected", nullptr);
+ s_typeNodeMap.insert("send", nullptr);
+ s_typeNodeMap.insert("settingsClicked", nullptr);
+ s_typeNodeMap.insert("shoe", nullptr);
+ s_typeNodeMap.insert("short", nullptr);
+ s_typeNodeMap.insert("signed", nullptr);
+ s_typeNodeMap.insert("sizeChanged", nullptr);
+ s_typeNodeMap.insert("size_t", nullptr);
+ s_typeNodeMap.insert("sockaddr", nullptr);
+ s_typeNodeMap.insert("someOtherSignal", nullptr);
+ s_typeNodeMap.insert("sourceSize", nullptr);
+ s_typeNodeMap.insert("startButtonClicked", nullptr);
+ s_typeNodeMap.insert("state", nullptr);
+ s_typeNodeMap.insert("std::initializer_list", nullptr);
+ s_typeNodeMap.insert("std::list", nullptr);
+ s_typeNodeMap.insert("std::map", nullptr);
+ s_typeNodeMap.insert("std::pair", nullptr);
+ s_typeNodeMap.insert("std::string", nullptr);
+ s_typeNodeMap.insert("std::vector", nullptr);
+ s_typeNodeMap.insert("stringlist", nullptr);
+ s_typeNodeMap.insert("swapPlayers", nullptr);
+ s_typeNodeMap.insert("symbol", nullptr);
+ s_typeNodeMap.insert("t", nullptr);
+ s_typeNodeMap.insert("T", nullptr);
+ s_typeNodeMap.insert("tagChanged", nullptr);
+ s_typeNodeMap.insert("timeString", nullptr);
+ s_typeNodeMap.insert("timeout", nullptr);
+ s_typeNodeMap.insert("to", nullptr);
+ s_typeNodeMap.insert("toggled", nullptr);
+ s_typeNodeMap.insert("type", nullptr);
+ s_typeNodeMap.insert("unsigned", nullptr);
+ s_typeNodeMap.insert("urllist", nullptr);
+ s_typeNodeMap.insert("va_list", nullptr);
+ s_typeNodeMap.insert("value", nullptr);
+ s_typeNodeMap.insert("valueEmitted", nullptr);
+ s_typeNodeMap.insert("videoFramePainted", nullptr);
+ s_typeNodeMap.insert("videoModeSelected", nullptr);
+ s_typeNodeMap.insert("videoRecorder", nullptr);
+ s_typeNodeMap.insert("void", nullptr);
+ s_typeNodeMap.insert("volatile", nullptr);
+ s_typeNodeMap.insert("wchar_t", nullptr);
+ s_typeNodeMap.insert("x", nullptr);
+ s_typeNodeMap.insert("y", nullptr);
+ s_typeNodeMap.insert("zoom", nullptr);
+ s_typeNodeMap.insert("zoomTo", nullptr);
+}
+
+/*! \fn NamespaceNode *QDocDatabase::primaryTreeRoot()
+ Returns a pointer to the root node of the primary tree.
+ */
+
+/*!
+ \fn const CNMap &QDocDatabase::groups()
+ Returns a const reference to the collection of all
+ group nodes in the primary tree.
+*/
+
+/*!
+ \fn const CNMap &QDocDatabase::modules()
+ Returns a const reference to the collection of all
+ module nodes in the primary tree.
+*/
+
+/*!
+ \fn const CNMap &QDocDatabase::qmlModules()
+ Returns a const reference to the collection of all
+ QML module nodes in the primary tree.
+*/
+
+/*! \fn CollectionNode *QDocDatabase::findGroup(const QString &name)
+ Find the group node named \a name and return a pointer
+ to it. If a matching node is not found, add a new group
+ node named \a name and return a pointer to that one.
+
+ If a new group node is added, its parent is the tree root,
+ and the new group node is marked \e{not seen}.
+ */
+
+/*! \fn CollectionNode *QDocDatabase::findModule(const QString &name)
+ Find the module node named \a name and return a pointer
+ to it. If a matching node is not found, add a new module
+ node named \a name and return a pointer to that one.
+
+ If a new module node is added, its parent is the tree root,
+ and the new module node is marked \e{not seen}.
+ */
+
+/*! \fn CollectionNode *QDocDatabase::addGroup(const QString &name)
+ Looks up the group named \a name in the primary tree. If
+ a match is found, a pointer to the node is returned.
+ Otherwise, a new group node named \a name is created and
+ inserted into the collection, and the pointer to that node
+ is returned.
+ */
+
+/*! \fn CollectionNode *QDocDatabase::addModule(const QString &name)
+ Looks up the module named \a name in the primary tree. If
+ a match is found, a pointer to the node is returned.
+ Otherwise, a new module node named \a name is created and
+ inserted into the collection, and the pointer to that node
+ is returned.
+ */
+
+/*! \fn CollectionNode *QDocDatabase::addQmlModule(const QString &name)
+ Looks up the QML module named \a name in the primary tree.
+ If a match is found, a pointer to the node is returned.
+ Otherwise, a new QML module node named \a name is created
+ and inserted into the collection, and the pointer to that
+ node is returned.
+ */
+
+/*! \fn CollectionNode *QDocDatabase::addToGroup(const QString &name, Node *node)
+ Looks up the group node named \a name in the collection
+ of all group nodes. If a match is not found, a new group
+ node named \a name is created and inserted into the collection.
+ Then append \a node to the group's members list, and append the
+ group node to the member list of the \a node. The parent of the
+ \a node is not changed by this function. Returns a pointer to
+ the group node.
+ */
+
+/*! \fn CollectionNode *QDocDatabase::addToModule(const QString &name, Node *node)
+ Looks up the module node named \a name in the collection
+ of all module nodes. If a match is not found, a new module
+ node named \a name is created and inserted into the collection.
+ Then append \a node to the module's members list. The parent of
+ \a node is not changed by this function. Returns the module node.
+ */
+
+/*! \fn Collection *QDocDatabase::addToQmlModule(const QString &name, Node *node)
+ Looks up the QML module named \a name. If it isn't there,
+ create it. Then append \a node to the QML module's member
+ list. The parent of \a node is not changed by this function.
+ */
+
+/*! \fn QmlTypeNode *QDocDatabase::findQmlType(const QString &name)
+ Returns the QML type node identified by the qualified
+ QML type \a name, or \c nullptr if no type was found.
+ */
+
+/*!
+ Returns the QML type node identified by the QML module id
+ \a qmid and QML type \a name, or \c nullptr if no type
+ was found.
+
+ If the QML module id is empty, looks up the QML type by
+ \a name only.
+ */
+QmlTypeNode *QDocDatabase::findQmlType(const QString &qmid, const QString &name)
+{
+ if (!qmid.isEmpty()) {
+ if (auto *qcn = m_forest.lookupQmlType(qmid + u"::"_s + name); qcn)
+ return qcn;
+ }
+
+ QStringList path(name);
+ return static_cast<QmlTypeNode *>(m_forest.findNodeByNameAndType(path, &Node::isQmlType));
+}
+
+/*!
+ Returns the QML type node identified by the QML module id
+ constructed from the strings in the import \a record and the
+ QML type \a name. Returns \c nullptr if no type was not found.
+ */
+QmlTypeNode *QDocDatabase::findQmlType(const ImportRec &record, const QString &name)
+{
+ if (!record.isEmpty()) {
+ const QString qmName = record.m_importUri.isEmpty() ?
+ record.m_moduleName : record.m_importUri;
+ const QStringList dotSplit{name.split(QLatin1Char('.'))};
+ for (const auto &namePart : dotSplit) {
+ if (auto *qcn = m_forest.lookupQmlType(qmName + u"::"_s + namePart); qcn)
+ return qcn;
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ Returns the QML node identified by the QML module id \a qmid
+ and \a name, searching in the primary tree only. If \a qmid
+ is an empty string, searches for the node using name only.
+
+ Returns \c nullptr if no node was found.
+*/
+QmlTypeNode *QDocDatabase::findQmlTypeInPrimaryTree(const QString &qmid, const QString &name)
+{
+ if (!qmid.isEmpty())
+ return primaryTree()->lookupQmlType(qmid + u"::"_s + name);
+ return static_cast<QmlTypeNode *>(primaryTreeRoot()->findChildNode(name, Node::QML, TypesOnly));
+}
+
+/*!
+ This function calls a set of functions for each tree in the
+ forest that has not already been analyzed. In this way, when
+ running qdoc in \e singleExec mode, each tree is analyzed in
+ turn, and its classes and types are added to the appropriate
+ node maps.
+ */
+void QDocDatabase::processForest()
+{
+ processForest(&QDocDatabase::findAllClasses);
+ processForest(&QDocDatabase::findAllFunctions);
+ processForest(&QDocDatabase::findAllObsoleteThings);
+ processForest(&QDocDatabase::findAllLegaleseTexts);
+ processForest(&QDocDatabase::findAllSince);
+ processForest(&QDocDatabase::findAllAttributions);
+ resolveNamespaces();
+}
+
+/*!
+ This function calls \a func for each tree in the forest,
+ ensuring that \a func is called only once per tree.
+
+ \sa processForest()
+ */
+void QDocDatabase::processForest(FindFunctionPtr func)
+{
+ Tree *t = m_forest.firstTree();
+ while (t) {
+ if (!m_completedFindFunctions.values(t).contains(func)) {
+ (this->*(func))(t->root());
+ m_completedFindFunctions.insert(t, func);
+ }
+ t = m_forest.nextTree();
+ }
+}
+
+/*!
+ Returns a reference to the collection of legalese texts.
+ */
+TextToNodeMap &QDocDatabase::getLegaleseTexts()
+{
+ processForest(&QDocDatabase::findAllLegaleseTexts);
+ return m_legaleseTexts;
+}
+
+/*!
+ Returns a reference to the map of C++ classes with obsolete members.
+ */
+NodeMultiMap &QDocDatabase::getClassesWithObsoleteMembers()
+{
+ processForest(&QDocDatabase::findAllObsoleteThings);
+ return s_classesWithObsoleteMembers;
+}
+
+/*!
+ Returns a reference to the map of obsolete QML types.
+ */
+NodeMultiMap &QDocDatabase::getObsoleteQmlTypes()
+{
+ processForest(&QDocDatabase::findAllObsoleteThings);
+ return s_obsoleteQmlTypes;
+}
+
+/*!
+ Returns a reference to the map of QML types with obsolete members.
+ */
+NodeMultiMap &QDocDatabase::getQmlTypesWithObsoleteMembers()
+{
+ processForest(&QDocDatabase::findAllObsoleteThings);
+ return s_qmlTypesWithObsoleteMembers;
+}
+
+/*!
+ Returns a reference to the map of QML basic types.
+ */
+NodeMultiMap &QDocDatabase::getQmlValueTypes()
+{
+ processForest(&QDocDatabase::findAllClasses);
+ return s_qmlBasicTypes;
+}
+
+/*!
+ Returns a reference to the multimap of QML types.
+ */
+NodeMultiMap &QDocDatabase::getQmlTypes()
+{
+ processForest(&QDocDatabase::findAllClasses);
+ return s_qmlTypes;
+}
+
+/*!
+ Returns a reference to the multimap of example nodes.
+ */
+NodeMultiMap &QDocDatabase::getExamples()
+{
+ processForest(&QDocDatabase::findAllClasses);
+ return s_examples;
+}
+
+/*!
+ Returns a reference to the multimap of attribution nodes.
+ */
+NodeMultiMap &QDocDatabase::getAttributions()
+{
+ processForest(&QDocDatabase::findAllAttributions);
+ return m_attributions;
+}
+
+/*!
+ Returns a reference to the map of obsolete C++ clases.
+ */
+NodeMultiMap &QDocDatabase::getObsoleteClasses()
+{
+ processForest(&QDocDatabase::findAllObsoleteThings);
+ return s_obsoleteClasses;
+}
+
+/*!
+ Returns a reference to the map of all C++ classes.
+ */
+NodeMultiMap &QDocDatabase::getCppClasses()
+{
+ processForest(&QDocDatabase::findAllClasses);
+ return s_cppClasses;
+}
+
+/*!
+ Returns the function index. This data structure is used to
+ output the function index page.
+ */
+NodeMapMap &QDocDatabase::getFunctionIndex()
+{
+ processForest(&QDocDatabase::findAllFunctions);
+ return m_functionIndex;
+}
+
+/*!
+ Finds all the nodes containing legalese text and puts them
+ in a map.
+ */
+void QDocDatabase::findAllLegaleseTexts(Aggregate *node)
+{
+ for (const auto &childNode : node->childNodes()) {
+ if (childNode->isPrivate())
+ continue;
+ if (!childNode->doc().legaleseText().isEmpty())
+ m_legaleseTexts.insert(childNode->doc().legaleseText(), childNode);
+ if (childNode->isAggregate())
+ findAllLegaleseTexts(static_cast<Aggregate *>(childNode));
+ }
+}
+
+/*!
+ \fn void QDocDatabase::findAllObsoleteThings(Aggregate *node)
+
+ Finds all nodes with status = Deprecated and sorts them into
+ maps. They can be C++ classes, QML types, or they can be
+ functions, enum types, typedefs, methods, etc.
+ */
+
+/*!
+ \fn void QDocDatabase::findAllSince(Aggregate *node)
+
+ Finds all the nodes in \a node where a \e{since} command appeared
+ in the qdoc comment and sorts them into maps according to the kind
+ of node.
+
+ This function is used for generating the "New Classes... in x.y"
+ section on the \e{What's New in Qt x.y} page.
+ */
+
+/*!
+ Find the \a key in the map of new class maps, and return a
+ reference to the value, which is a NodeMap. If \a key is not
+ found, return a reference to an empty NodeMap.
+ */
+const NodeMultiMap &QDocDatabase::getClassMap(const QString &key)
+{
+ processForest(&QDocDatabase::findAllSince);
+ auto it = s_newClassMaps.constFind(key);
+ return (it != s_newClassMaps.constEnd()) ? it.value() : emptyNodeMultiMap_;
+}
+
+/*!
+ Find the \a key in the map of new QML type maps, and return a
+ reference to the value, which is a NodeMap. If the \a key is not
+ found, return a reference to an empty NodeMap.
+ */
+const NodeMultiMap &QDocDatabase::getQmlTypeMap(const QString &key)
+{
+ processForest(&QDocDatabase::findAllSince);
+ auto it = s_newQmlTypeMaps.constFind(key);
+ return (it != s_newQmlTypeMaps.constEnd()) ? it.value() : emptyNodeMultiMap_;
+}
+
+/*!
+ Find the \a key in the map of new \e {since} maps, and return
+ a reference to the value, which is a NodeMultiMap. If \a key
+ is not found, return a reference to an empty NodeMultiMap.
+ */
+const NodeMultiMap &QDocDatabase::getSinceMap(const QString &key)
+{
+ processForest(&QDocDatabase::findAllSince);
+ auto it = s_newSinceMaps.constFind(key);
+ return (it != s_newSinceMaps.constEnd()) ? it.value() : emptyNodeMultiMap_;
+}
+
+/*!
+ Performs several housekeeping tasks prior to generating the
+ documentation. These tasks create required data structures
+ and resolve links.
+ */
+void QDocDatabase::resolveStuff()
+{
+ const auto &config = Config::instance();
+ if (config.dualExec() || config.preparing()) {
+ // order matters
+ primaryTree()->resolveBaseClasses(primaryTreeRoot());
+ primaryTree()->resolvePropertyOverriddenFromPtrs(primaryTreeRoot());
+ primaryTreeRoot()->resolveRelates();
+ primaryTreeRoot()->normalizeOverloads();
+ primaryTree()->markDontDocumentNodes();
+ primaryTree()->removePrivateAndInternalBases(primaryTreeRoot());
+ primaryTree()->resolveProperties();
+ primaryTreeRoot()->markUndocumentedChildrenInternal();
+ primaryTreeRoot()->resolveQmlInheritance();
+ primaryTree()->resolveTargets(primaryTreeRoot());
+ primaryTree()->resolveCppToQmlLinks();
+ primaryTree()->resolveSince(*primaryTreeRoot());
+ }
+ if (config.singleExec() && config.generating()) {
+ primaryTree()->resolveBaseClasses(primaryTreeRoot());
+ primaryTree()->resolvePropertyOverriddenFromPtrs(primaryTreeRoot());
+ primaryTreeRoot()->resolveQmlInheritance();
+ primaryTree()->resolveCppToQmlLinks();
+ primaryTree()->resolveSince(*primaryTreeRoot());
+ }
+ if (!config.preparing()) {
+ resolveNamespaces();
+ resolveProxies();
+ resolveBaseClasses();
+ updateNavigation();
+ }
+ if (config.dualExec())
+ QDocIndexFiles::destroyQDocIndexFiles();
+}
+
+void QDocDatabase::resolveBaseClasses()
+{
+ Tree *t = m_forest.firstTree();
+ while (t) {
+ t->resolveBaseClasses(t->root());
+ t = m_forest.nextTree();
+ }
+}
+
+/*!
+ Returns a reference to the namespace map. Constructs the
+ namespace map if it hasn't been constructed yet.
+
+ \note This function must not be called in the prepare phase.
+ */
+NodeMultiMap &QDocDatabase::getNamespaces()
+{
+ resolveNamespaces();
+ return m_namespaceIndex;
+}
+
+/*!
+ Multiple namespace nodes for namespace X can exist in the
+ qdoc database in different trees. This function first finds
+ all namespace nodes in all the trees and inserts them into
+ a multimap. Then it combines all the namespace nodes that
+ have the same name into a single namespace node of that
+ name and inserts that combined namespace node into an index.
+ */
+void QDocDatabase::resolveNamespaces()
+{
+ if (!m_namespaceIndex.isEmpty())
+ return;
+
+ bool linkErrors = !Config::instance().get(CONFIG_NOLINKERRORS).asBool();
+ NodeMultiMap namespaceMultimap;
+ Tree *t = m_forest.firstTree();
+ while (t) {
+ t->root()->findAllNamespaces(namespaceMultimap);
+ t = m_forest.nextTree();
+ }
+ const QList<QString> keys = namespaceMultimap.uniqueKeys();
+ for (const QString &key : keys) {
+ NamespaceNode *ns = nullptr;
+ NamespaceNode *indexNamespace = nullptr;
+ const NodeList namespaces = namespaceMultimap.values(key);
+ qsizetype count = namespaceMultimap.remove(key);
+ if (count > 0) {
+ for (auto *node : namespaces) {
+ ns = static_cast<NamespaceNode *>(node);
+ if (ns->isDocumentedHere())
+ break;
+ else if (ns->hadDoc())
+ indexNamespace = ns; // namespace was documented but in another tree
+ ns = nullptr;
+ }
+ if (ns) {
+ for (auto *node : namespaces) {
+ auto *nsNode = static_cast<NamespaceNode *>(node);
+ if (nsNode->hadDoc() && nsNode != ns) {
+ ns->doc().location().warning(
+ QStringLiteral("Namespace %1 documented more than once")
+ .arg(nsNode->name()), QStringLiteral("also seen here: %1")
+ .arg(nsNode->doc().location().toString()));
+ }
+ }
+ } else if (!indexNamespace) {
+ // Warn about documented children in undocumented namespaces.
+ // As the namespace can be documented outside this project,
+ // skip the warning if -no-link-errors is set
+ if (linkErrors) {
+ for (auto *node : namespaces) {
+ if (!node->isIndexNode())
+ static_cast<NamespaceNode *>(node)->reportDocumentedChildrenInUndocumentedNamespace();
+ }
+ }
+ } else {
+ for (auto *node : namespaces) {
+ auto *nsNode = static_cast<NamespaceNode *>(node);
+ if (nsNode != indexNamespace)
+ nsNode->setDocNode(indexNamespace);
+ }
+ }
+ }
+ /*
+ If there are multiple namespace nodes with the same
+ name where one of them will be the main reference page
+ for the namespace, include all nodes in the public
+ API of the namespace.
+ */
+ if (ns && count > 1) {
+ for (auto *node : namespaces) {
+ auto *nameSpaceNode = static_cast<NamespaceNode *>(node);
+ if (nameSpaceNode != ns) {
+ for (auto it = nameSpaceNode->constBegin(); it != nameSpaceNode->constEnd();
+ ++it) {
+ Node *anotherNs = *it;
+ if (anotherNs && anotherNs->isPublic() && !anotherNs->isInternal())
+ ns->includeChild(anotherNs);
+ }
+ }
+ }
+ }
+ /*
+ Add the main namespace reference node to index, or the last seen
+ namespace if the main one was not found.
+ */
+ if (!ns)
+ ns = indexNamespace ? indexNamespace : static_cast<NamespaceNode *>(namespaces.last());
+ m_namespaceIndex.insert(ns->name(), ns);
+ }
+}
+
+/*!
+ Each instance of class Tree that represents an index file
+ must be traversed to find all instances of class ProxyNode.
+ For each ProxyNode found, look up the ProxyNode's name in
+ the primary Tree. If it is found, it means that the proxy
+ node contains elements (normally just functions) that are
+ documented in the module represented by the Tree containing
+ the proxy node but that are related to the node we found in
+ the primary tree.
+ */
+void QDocDatabase::resolveProxies()
+{
+ // The first tree is the primary tree.
+ // Skip the primary tree.
+ Tree *t = m_forest.firstTree();
+ t = m_forest.nextTree();
+ while (t) {
+ const NodeList &proxies = t->proxies();
+ if (!proxies.isEmpty()) {
+ for (auto *node : proxies) {
+ const auto *pn = static_cast<ProxyNode *>(node);
+ if (pn->count() > 0) {
+ Aggregate *aggregate = primaryTree()->findAggregate(pn->name());
+ if (aggregate != nullptr)
+ aggregate->appendToRelatedByProxy(pn->childNodes());
+ }
+ }
+ }
+ t = m_forest.nextTree();
+ }
+}
+
+/*!
+ Finds the function node for the qualified function path in
+ \a target and returns a pointer to it. The \a target is a
+ function signature with or without parameters but without
+ the return type.
+
+ \a relative is the node in the primary tree where the search
+ begins. It is not used in the other trees, if the node is not
+ found in the primary tree. \a genus can be used to force the
+ search to find a C++ function or a QML function.
+
+ The entire forest is searched, but the first match is accepted.
+ */
+const FunctionNode *QDocDatabase::findFunctionNode(const QString &target, const Node *relative,
+ Node::Genus genus)
+{
+ QString signature;
+ QString function = target;
+ qsizetype length = target.size();
+ if (function.endsWith("()"))
+ function.chop(2);
+ if (function.endsWith(QChar(')'))) {
+ qsizetype position = function.lastIndexOf(QChar('('));
+ signature = function.mid(position + 1, length - position - 2);
+ function = function.left(position);
+ }
+ QStringList path = function.split("::");
+ return m_forest.findFunctionNode(path, Parameters(signature), relative, genus);
+}
+
+/*!
+ This function is called for autolinking to a \a type,
+ which could be a function return type or a parameter
+ type. The tree node that represents the \a type is
+ returned. All the trees are searched until a match is
+ found. When searching the primary tree, the search
+ begins at \a relative and proceeds up the parent chain.
+ When searching the index trees, the search begins at the
+ root.
+ */
+const Node *QDocDatabase::findTypeNode(const QString &type, const Node *relative, Node::Genus genus)
+{
+ QStringList path = type.split("::");
+ if ((path.size() == 1) && (path.at(0)[0].isLower() || path.at(0) == QString("T"))) {
+ auto it = s_typeNodeMap.find(path.at(0));
+ if (it != s_typeNodeMap.end())
+ return it.value();
+ }
+ return m_forest.findTypeNode(path, relative, genus);
+}
+
+/*!
+ Finds the node that will generate the documentation that
+ contains the \a target and returns a pointer to it.
+
+ Can this be improved by using the target map in Tree?
+ */
+const Node *QDocDatabase::findNodeForTarget(const QString &target, const Node *relative)
+{
+ const Node *node = nullptr;
+ if (target.isEmpty())
+ node = relative;
+ else if (target.endsWith(".html"))
+ node = findNodeByNameAndType(QStringList(target), &Node::isPageNode);
+ else {
+ QStringList path = target.split("::");
+ int flags = SearchBaseClasses | SearchEnumValues;
+ for (const auto *tree : searchOrder()) {
+ const Node *n = tree->findNode(path, relative, flags, Node::DontCare);
+ if (n)
+ return n;
+ relative = nullptr;
+ }
+ node = findPageNodeByTitle(target);
+ }
+ return node;
+}
+
+QStringList QDocDatabase::groupNamesForNode(Node *node)
+{
+ QStringList result;
+ CNMap *m = primaryTree()->getCollectionMap(Node::Group);
+
+ if (!m)
+ return result;
+
+ for (auto it = m->cbegin(); it != m->cend(); ++it)
+ if (it.value()->members().contains(node))
+ result << it.key();
+
+ return result;
+}
+
+/*!
+ Reads and parses the qdoc index files listed in \a indexFiles.
+ */
+void QDocDatabase::readIndexes(const QStringList &indexFiles)
+{
+ QStringList filesToRead;
+ for (const QString &file : indexFiles) {
+ QString fn = file.mid(file.lastIndexOf(QChar('/')) + 1);
+ if (!isLoaded(fn))
+ filesToRead << file;
+ else
+ qCCritical(lcQdoc) << "Index file" << file << "is already in memory.";
+ }
+ QDocIndexFiles::qdocIndexFiles()->readIndexes(filesToRead);
+}
+
+/*!
+ Generates a qdoc index file and write it to \a fileName. The
+ index file is generated with the parameters \a url and \a title,
+ using the generator \a g.
+ */
+void QDocDatabase::generateIndex(const QString &fileName, const QString &url, const QString &title,
+ Generator *g)
+{
+ QString t = fileName.mid(fileName.lastIndexOf(QChar('/')) + 1);
+ primaryTree()->setIndexFileName(t);
+ QDocIndexFiles::qdocIndexFiles()->generateIndex(fileName, url, title, g);
+ QDocIndexFiles::destroyQDocIndexFiles();
+}
+
+/*!
+ Returns the collection node representing the module that \a relative
+ node belongs to, or \c nullptr if there is no such module in the
+ primary tree.
+*/
+const CollectionNode *QDocDatabase::getModuleNode(const Node *relative)
+{
+ Node::NodeType moduleType{Node::Module};
+ QString moduleName;
+ switch (relative->genus())
+ {
+ case Node::CPP:
+ moduleType = Node::Module;
+ moduleName = relative->physicalModuleName();
+ break;
+ case Node::QML:
+ moduleType = Node::QmlModule;
+ moduleName = relative->logicalModuleName();
+ break;
+ default:
+ return nullptr;
+ }
+ if (moduleName.isEmpty())
+ return nullptr;
+
+ return primaryTree()->getCollection(moduleName, moduleType);
+}
+
+/*!
+ Finds all the collection nodes of the specified \a type
+ and merges them into the collection node map \a cnm. Nodes
+ that match the \a relative node are not included.
+ */
+void QDocDatabase::mergeCollections(Node::NodeType type, CNMap &cnm, const Node *relative)
+{
+ cnm.clear();
+ CNMultiMap cnmm;
+ for (auto *tree : searchOrder()) {
+ CNMap *m = tree->getCollectionMap(type);
+ if (m && !m->isEmpty()) {
+ for (auto it = m->cbegin(); it != m->cend(); ++it) {
+ if (!it.value()->isInternal())
+ cnmm.insert(it.key(), it.value());
+ }
+ }
+ }
+ if (cnmm.isEmpty())
+ return;
+ static const QRegularExpression singleDigit("\\b([0-9])\\b");
+ const QStringList keys = cnmm.uniqueKeys();
+ for (const auto &key : keys) {
+ const QList<CollectionNode *> values = cnmm.values(key);
+ CollectionNode *n = nullptr;
+ for (auto *value : values) {
+ if (value && value->wasSeen() && value != relative) {
+ n = value;
+ break;
+ }
+ }
+ if (n) {
+ if (values.size() > 1) {
+ for (CollectionNode *value : values) {
+ if (value != n) {
+ // Allow multiple (major) versions of QML modules
+ if ((n->isQmlModule())
+ && n->logicalModuleIdentifier() != value->logicalModuleIdentifier()) {
+ if (value->wasSeen() && value != relative
+ && !value->members().isEmpty())
+ cnm.insert(value->fullTitle().toLower(), value);
+ continue;
+ }
+ for (Node *t : value->members())
+ n->addMember(t);
+ }
+ }
+ }
+ QString sortKey = n->fullTitle().toLower();
+ if (sortKey.startsWith("the "))
+ sortKey.remove(0, 4);
+ sortKey.replace(singleDigit, "0\\1");
+ cnm.insert(sortKey, n);
+ }
+ }
+}
+
+/*!
+ Finds all the collection nodes with the same name
+ and type as \a c and merges their members into the
+ members list of \a c.
+
+ For QML modules, only nodes with matching
+ module identifiers are merged to avoid merging
+ modules with different (major) versions.
+ */
+void QDocDatabase::mergeCollections(CollectionNode *c)
+{
+ if (c == nullptr)
+ return;
+
+ // REMARK: This form of merging is usually called during the
+ // generation phase om-the-fly when a source-of-truth collection
+ // is required.
+ // In practice, this means a collection could be merged many, many
+ // times during the lifetime of a generation.
+ // To avoid repeating the merging process each time, which could
+ // be time consuming, we use a small flag that is set directly on
+ // the collection to bail-out early.
+ //
+ // The merging process is only meaningful for collections when the
+ // collection references are spread troughout multiple projects.
+ // The part of information that exists in other project is read
+ // before the generation phase, such that when the generation
+ // phase comes, we already have all the information we need for
+ // merging such that we can consider all version of a certain
+ // collection node immutable, making the caching inherently
+ // correct at any point of the generation.
+ //
+ // This implies that this operation is unsafe if it is performed
+ // before all the index files are loaded.
+ // Indeed, this is a prerequisite, with the current structure, to
+ // perform this optmization.
+ //
+ // At the current time, this is true and is expected not to
+ // change.
+ //
+ // Do note that this is not applied to the other overload of
+ // mergeCollections as we cannot as safely ensure its consistency
+ // and, as the result of the merging depends on multiple
+ // parameters, it would require an actual memoization of the call.
+ //
+ // Note that this is a defensive optimization and we are assuming
+ // that it is effective based on heuristical data. As this is
+ // expected to disappear, at least in its current form, in the
+ // future, a more thorough analysis was not performed.
+ if (c->isMerged()) {
+ return;
+ }
+
+ for (auto *tree : searchOrder()) {
+ CollectionNode *cn = tree->getCollection(c->name(), c->nodeType());
+ if (cn && cn != c) {
+ if ((cn->isQmlModule())
+ && cn->logicalModuleIdentifier() != c->logicalModuleIdentifier())
+ continue;
+
+ for (auto *node : cn->members())
+ c->addMember(node);
+
+ // REMARK: The merging process is performed to ensure that
+ // references to the collection in external projects are
+ // taken into account before consuming the collection.
+ //
+ // This works by having QDoc construct empty collections
+ // as soon as a reference to a collection is encountered
+ // and filling details later on when its definition is
+ // found.
+ //
+ // This initially-empty collection is always saved to the
+ // primaryTree and it is the collection that is directly
+ // accessible to consumers during the generation process.
+ //
+ // Nonetheless, when the definition for the collection is
+ // not in the same project as the one that is being
+ // compiled, its details will never be filled in.
+ //
+ // Indeed, the details will live in the index file for the
+ // project where the collection is defined, if any, and
+ // the node for it, which has complete information, will
+ // live in some non-primaryTree.
+ //
+ // The merging process itself is used by consumers during
+ // the generation process because they access the
+ // primaryTree version of the collection expecting a
+ // source-of-truth.
+ // To ensure that this is the case for usages that
+ // requires linking, we need to merge not only the members
+ // of the collection that reside in external versions of
+ // the collection; but some of the data that reside in the
+ // definition of the collection intself, namely the title
+ // and the url.
+ //
+ // A collection that contains the data of a definition is
+ // always marked as seen, hence we use that to discern
+ // whether we are working with a placeholder node or not,
+ // and fill in the data if we encounter a node that
+ // represents a definition.
+ //
+ // The way in which QDoc works implies that collection are
+ // globally scoped between projects.
+ // The repetition of the definition for the same
+ // collection is warned for as a duplicate documentation,
+ // such that we can expect a single valid source of truth
+ // for a given collection in each project.
+ // It is currently unknown if this warning is applicable
+ // when the repeated collection is defined in two
+ // different projects.
+ //
+ // As QDoc implicitly would not correctly support this
+ // case, we assume that only one declaration exists for
+ // each collection, such that the first encoutered one
+ // must be the source of truth and that there is no need
+ // to copy any data after the first copy is performed.
+ // KLUDGE: Note that this process is done as a hackish
+ // solution to QTBUG-104237 and should not be considered
+ // final or dependable.
+ if (!c->wasSeen() && cn->wasSeen()) {
+ c->markSeen();
+ c->setTitle(cn->title());
+ c->setUrl(cn->url());
+ }
+ }
+ }
+
+ c->markMerged();
+}
+
+/*!
+ Searches for the node that matches the path in \a atom and the
+ specified \a genus. The \a relative node is used if the first
+ leg of the path is empty, i.e. if the path begins with '#'.
+ The function also sets \a ref if there remains an unused leg
+ in the path after the node is found. The node is returned as
+ well as the \a ref. If the returned node pointer is null,
+ \a ref is also not valid.
+ */
+const Node *QDocDatabase::findNodeForAtom(const Atom *a, const Node *relative, QString &ref,
+ Node::Genus genus)
+{
+ const Node *node = nullptr;
+
+ Atom *atom = const_cast<Atom *>(a);
+ QStringList targetPath = atom->string().split(QLatin1Char('#'));
+ QString first = targetPath.first().trimmed();
+
+ Tree *domain = nullptr;
+
+ if (atom->isLinkAtom()) {
+ domain = atom->domain();
+ genus = atom->genus();
+ }
+
+ if (first.isEmpty())
+ node = relative; // search for a target on the current page.
+ else if (domain) {
+ if (first.endsWith(".html"))
+ node = domain->findNodeByNameAndType(QStringList(first), &Node::isPageNode);
+ else if (first.endsWith(QChar(')'))) {
+ QString signature;
+ QString function = first;
+ qsizetype length = first.size();
+ if (function.endsWith("()"))
+ function.chop(2);
+ if (function.endsWith(QChar(')'))) {
+ qsizetype position = function.lastIndexOf(QChar('('));
+ signature = function.mid(position + 1, length - position - 2);
+ function = function.left(position);
+ }
+ QStringList path = function.split("::");
+ node = domain->findFunctionNode(path, Parameters(signature), nullptr, genus);
+ }
+ if (node == nullptr) {
+ int flags = SearchBaseClasses | SearchEnumValues;
+ QStringList nodePath = first.split("::");
+ QString target;
+ targetPath.removeFirst();
+ if (!targetPath.isEmpty())
+ target = targetPath.takeFirst();
+ if (relative && relative->tree()->physicalModuleName() != domain->physicalModuleName())
+ relative = nullptr;
+ return domain->findNodeForTarget(nodePath, target, relative, flags, genus, ref);
+ }
+ } else {
+ if (first.endsWith(".html"))
+ node = findNodeByNameAndType(QStringList(first), &Node::isPageNode);
+ else if (first.endsWith(QChar(')')))
+ node = findFunctionNode(first, relative, genus);
+ if (node == nullptr)
+ return findNodeForTarget(targetPath, relative, genus, ref);
+ }
+
+ if (node != nullptr && ref.isEmpty()) {
+ if (!node->url().isEmpty())
+ return node;
+ targetPath.removeFirst();
+ if (!targetPath.isEmpty()) {
+ ref = node->root()->tree()->getRef(targetPath.first(), node);
+ if (ref.isEmpty())
+ node = nullptr;
+ }
+ }
+ return node;
+}
+
+/*!
+ Updates navigation (previous/next page links and the navigation parent)
+ for pages listed in the TOC, specified by the \c navigation.toctitles
+ configuration variable.
+
+ if \c navigation.toctitles.inclusive is \c true, include also the TOC
+ page(s) themselves as a 'root' item in the navigation bar (breadcrumbs)
+ that are generated for HTML output.
+*/
+void QDocDatabase::updateNavigation()
+{
+ // Restrict searching only to the local (primary) tree
+ QList<Tree *> searchOrder = this->searchOrder();
+ setLocalSearch();
+
+ const QString configVar = CONFIG_NAVIGATION +
+ Config::dot +
+ CONFIG_TOCTITLES;
+
+ // TODO: [direct-configuration-access]
+ // The configuration is currently a singleton with some generally
+ // global mutable state.
+ //
+ // Accessing the data in this form complicates testing and
+ // requires tests that inhibit any test parallelization, as the
+ // tests are not self contained.
+ //
+ // This should be generally avoived. Possibly, we should strive
+ // for Config to be a POD type that generally is scoped to
+ // main and whose data is destructured into dependencies when
+ // the dependencies are constructed.
+ bool inclusive{Config::instance().get(
+ configVar + Config::dot + CONFIG_INCLUSIVE).asBool()};
+
+ // TODO: [direct-configuration-access]
+ const auto tocTitles{Config::instance().get(configVar).asStringList()};
+
+ for (const auto &tocTitle : tocTitles) {
+ if (const auto candidateTarget = findNodeForTarget(tocTitle, nullptr); candidateTarget && candidateTarget->isPageNode()) {
+ auto tocPage{static_cast<const PageNode*>(candidateTarget)};
+
+ Text body = tocPage->doc().body();
+
+ auto *atom = body.firstAtom();
+
+ std::pair<PageNode *, Atom *> prev { nullptr, nullptr };
+
+ std::stack<const PageNode *> tocStack;
+ tocStack.push(inclusive ? tocPage : nullptr);
+
+ bool inItem = false;
+
+ // TODO: Understand how much we use this form of looping over atoms.
+ // If it is used a few times we might consider providing
+ // an iterator for Text to make use of a simpler
+ // range-for loop.
+ while (atom) {
+ switch (atom->type()) {
+ case Atom::ListItemLeft:
+ // Not known if we're going to have a link, push a temporary
+ tocStack.push(nullptr);
+ inItem = true;
+ break;
+ case Atom::ListItemRight:
+ tocStack.pop();
+ inItem = false;
+ break;
+ case Atom::Link: {
+ if (!inItem)
+ break;
+
+ // TODO: [unnecessary-output-parameter]
+ // We currently need an lvalue string to
+ // pass to findNodeForAtom, as the
+ // outparameter ref.
+ //
+ // Apart from the general problems with output
+ // parameters, we shouldn't be forced to
+ // instanciate an unnecessary object at call
+ // site.
+ //
+ // Understand what the correct way to avoid this is.
+ // This requires changes to findNodeForAtom
+ // and should be addressed in the context of
+ // revising that method.
+ QString unused{};
+ // TODO: Having to const cast is really a code
+ // smell and could result in undefined
+ // behavior in some specific cases (e.g point
+ // to something that is actually const).
+ //
+ // We should understand how to sequence the
+ // code so that we have access to mutable data
+ // when we need it and "freeze" the data
+ // afterwards.
+ //
+ // If it we expect this form of mutability at
+ // this point we should expose a non-const API
+ // for the database, possibly limited to a
+ // very specific scope of execution.
+ //
+ // Understand what the correct sequencing for
+ // this processing is and revise this part.
+ auto candidatePage = const_cast<Node *>(findNodeForAtom(atom, nullptr, unused));
+ if (!candidatePage || !candidatePage->isPageNode()) break;
+
+ auto page{static_cast<PageNode*>(candidatePage)};
+
+ // ignore self-references
+ if (page == prev.first) break;
+
+ if (prev.first) {
+ prev.first->setLink(
+ Node::NextLink,
+ page->title(),
+ // TODO: [possible-assertion-failure][imprecise-types][atoms-link]
+ // As with other structures in QDoc we
+ // are able to call methods that are
+ // valid only on very specific states.
+ //
+ // For some of those calls we have
+ // some defensive programming measures
+ // that allow us to at least identify
+ // the error during debugging, while
+ // for others this may currently hide
+ // some logic error.
+ //
+ // To avoid those cases, we should
+ // strive to move those cases to a
+ // compilation error, requiring a
+ // statically analyzable state that
+ // represents the current model.
+ //
+ // This would ensure that those
+ // lingering class of bugs are
+ // eliminated completely, forces a
+ // more explicit codebase where the
+ // current capabilities do not depend
+ // on runtime values might not be
+ // generally visible, and does not
+ // require us to incur into the
+ // required state, which may be rare,
+ // simplifying our abilities to
+ // evaluate all possible states.
+ //
+ // For linking atoms, LinkAtom is
+ // available and might be a good
+ // enough solution to move linkText
+ // to.
+ atom->linkText()
+ );
+ page->setLink(
+ Node::PreviousLink,
+ prev.first->title(),
+ // TODO: [possible-assertion-failure][imprecise-types][atoms-link]
+ prev.second->linkText()
+ );
+ }
+
+ if (page == tocPage)
+ break;
+
+ // Find the navigation parent from the stack; we may have null pointers
+ // for non-link list items, so skip those.
+ qsizetype popped = 0;
+ while (tocStack.size() > 1 && !tocStack.top()) {
+ tocStack.pop();
+ ++popped;
+ }
+
+ page->setNavigationParent(tocStack.empty() ? nullptr : tocStack.top());
+
+ while (--popped > 0)
+ tocStack.push(nullptr);
+
+ tocStack.push(page);
+ prev = { page, atom };
+ }
+ break;
+ default:
+ break;
+ }
+
+ atom = atom->next();
+ }
+ } else {
+ Config::instance().get(configVar).location()
+ .warning(QStringLiteral("Failed to find table of contents with title '%1'")
+ .arg(tocTitle));
+ }
+ }
+
+ // Restore search order
+ setSearchOrder(searchOrder);
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/qdocdatabase.h b/src/qdoc/qdoc/src/qdoc/qdocdatabase.h
new file mode 100644
index 000000000..df2b4135c
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qdocdatabase.h
@@ -0,0 +1,395 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QDOCDATABASE_H
+#define QDOCDATABASE_H
+
+#include "config.h"
+#include "examplenode.h"
+#include "propertynode.h"
+#include "text.h"
+#include "tree.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+typedef QMultiMap<Text, const Node *> TextToNodeMap;
+
+class Atom;
+class FunctionNode;
+class Generator;
+class QDocDatabase;
+
+enum FindFlag {
+ SearchBaseClasses = 0x1,
+ SearchEnumValues = 0x2,
+ TypesOnly = 0x4,
+ IgnoreModules = 0x8
+};
+
+class QDocForest
+{
+private:
+ friend class QDocDatabase;
+ explicit QDocForest(QDocDatabase *qdb) : m_qdb(qdb), m_primaryTree(nullptr), m_currentIndex(0)
+ {
+ }
+ ~QDocForest();
+
+ Tree *firstTree();
+ Tree *nextTree();
+ Tree *primaryTree() { return m_primaryTree; }
+ Tree *findTree(const QString &t) { return m_forest.value(t); }
+ QStringList keys() { return m_forest.keys(); }
+ NamespaceNode *primaryTreeRoot() { return (m_primaryTree ? m_primaryTree->root() : nullptr); }
+ bool isEmpty() { return searchOrder().isEmpty(); }
+ bool done() { return (m_currentIndex >= searchOrder().size()); }
+ const QList<Tree *> &searchOrder();
+ const QList<Tree *> &indexSearchOrder();
+ void setSearchOrder(const QStringList &t);
+ bool isLoaded(const QString &fn)
+ {
+ return std::any_of(searchOrder().constBegin(), searchOrder().constEnd(),
+ [fn](Tree *tree) { return fn == tree->indexFileName(); });
+ }
+
+ const Node *findNode(const QStringList &path, const Node *relative, int findFlags,
+ Node::Genus genus)
+ {
+ for (const auto *tree : searchOrder()) {
+ const Node *n = tree->findNode(path, relative, findFlags, genus);
+ if (n)
+ return n;
+ relative = nullptr;
+ }
+ return nullptr;
+ }
+
+ Node *findNodeByNameAndType(const QStringList &path, bool (Node::*isMatch)() const)
+ {
+ for (const auto *tree : searchOrder()) {
+ Node *n = tree->findNodeByNameAndType(path, isMatch);
+ if (n)
+ return n;
+ }
+ return nullptr;
+ }
+
+ ClassNode *findClassNode(const QStringList &path)
+ {
+ for (const auto *tree : searchOrder()) {
+ ClassNode *n = tree->findClassNode(path);
+ if (n)
+ return n;
+ }
+ return nullptr;
+ }
+
+ Node *findNodeForInclude(const QStringList &path)
+ {
+ for (const auto *tree : searchOrder()) {
+ Node *n = tree->findNodeForInclude(path);
+ if (n)
+ return n;
+ }
+ return nullptr;
+ }
+
+ const FunctionNode *findFunctionNode(const QStringList &path, const Parameters &parameters,
+ const Node *relative, Node::Genus genus);
+ const Node *findNodeForTarget(QStringList &targetPath, const Node *relative, Node::Genus genus,
+ QString &ref);
+
+ const Node *findTypeNode(const QStringList &path, const Node *relative, Node::Genus genus)
+ {
+ int flags = SearchBaseClasses | SearchEnumValues | TypesOnly;
+ if (relative && genus == Node::DontCare && relative->genus() != Node::DOC)
+ genus = relative->genus();
+ for (const auto *tree : searchOrder()) {
+ const Node *n = tree->findNode(path, relative, flags, genus);
+ if (n)
+ return n;
+ relative = nullptr;
+ }
+ return nullptr;
+ }
+
+ const PageNode *findPageNodeByTitle(const QString &title)
+ {
+ for (const auto *tree : searchOrder()) {
+ const PageNode *n = tree->findPageNodeByTitle(title);
+ if (n)
+ return n;
+ }
+ return nullptr;
+ }
+
+ const CollectionNode *getCollectionNode(const QString &name, Node::NodeType type)
+ {
+ for (auto *tree : searchOrder()) {
+ const CollectionNode *cn = tree->getCollection(name, type);
+ if (cn)
+ return cn;
+ }
+ return nullptr;
+ }
+
+ QmlTypeNode *lookupQmlType(const QString &name)
+ {
+ for (const auto *tree : searchOrder()) {
+ QmlTypeNode *qcn = tree->lookupQmlType(name);
+ if (qcn)
+ return qcn;
+ }
+ return nullptr;
+ }
+
+ void clearSearchOrder() { m_searchOrder.clear(); }
+ void newPrimaryTree(const QString &module);
+ void setPrimaryTree(const QString &t);
+ NamespaceNode *newIndexTree(const QString &module);
+
+private:
+ QDocDatabase *m_qdb;
+ Tree *m_primaryTree;
+ int m_currentIndex;
+ QMap<QString, Tree *> m_forest;
+ QList<Tree *> m_searchOrder;
+ QList<Tree *> m_indexSearchOrder;
+ QList<QString> m_moduleNames;
+};
+
+class QDocDatabase
+{
+public:
+ static QDocDatabase *qdocDB();
+ static void destroyQdocDB();
+ ~QDocDatabase() = default;
+
+ using FindFunctionPtr = void (QDocDatabase::*)(Aggregate *);
+
+ Tree *findTree(const QString &t) { return m_forest.findTree(t); }
+
+ const CNMap &groups() { return primaryTree()->groups(); }
+ const CNMap &modules() { return primaryTree()->modules(); }
+ const CNMap &qmlModules() { return primaryTree()->qmlModules(); }
+
+ CollectionNode *addGroup(const QString &name) { return primaryTree()->addGroup(name); }
+ CollectionNode *addModule(const QString &name) { return primaryTree()->addModule(name); }
+ CollectionNode *addQmlModule(const QString &name) { return primaryTree()->addQmlModule(name); }
+
+ CollectionNode *addToGroup(const QString &name, Node *node)
+ {
+ return primaryTree()->addToGroup(name, node);
+ }
+ CollectionNode *addToModule(const QString &name, Node *node)
+ {
+ return primaryTree()->addToModule(name, node);
+ }
+ CollectionNode *addToQmlModule(const QString &name, Node *node)
+ {
+ return primaryTree()->addToQmlModule(name, node);
+ }
+
+ void addExampleNode(ExampleNode *n) { primaryTree()->addExampleNode(n); }
+ ExampleNodeMap &exampleNodeMap() { return primaryTree()->exampleNodeMap(); }
+
+ QmlTypeNode *findQmlType(const QString &name)
+ {
+ return m_forest.lookupQmlType(name);
+ }
+ QmlTypeNode *findQmlType(const QString &qmid, const QString &name);
+ QmlTypeNode *findQmlType(const ImportRec &import, const QString &name);
+ QmlTypeNode *findQmlTypeInPrimaryTree(const QString &qmid, const QString &name);
+
+ static NodeMultiMap &obsoleteClasses() { return s_obsoleteClasses; }
+ static NodeMultiMap &obsoleteQmlTypes() { return s_obsoleteQmlTypes; }
+ static NodeMultiMap &classesWithObsoleteMembers() { return s_classesWithObsoleteMembers; }
+ static NodeMultiMap &qmlTypesWithObsoleteMembers() { return s_qmlTypesWithObsoleteMembers; }
+ static NodeMultiMap &cppClasses() { return s_cppClasses; }
+ static NodeMultiMap &qmlBasicTypes() { return s_qmlBasicTypes; }
+ static NodeMultiMap &qmlTypes() { return s_qmlTypes; }
+ static NodeMultiMap &examples() { return s_examples; }
+ static NodeMultiMapMap &newClassMaps() { return s_newClassMaps; }
+ static NodeMultiMapMap &newQmlTypeMaps() { return s_newQmlTypeMaps; }
+ static NodeMultiMapMap &newEnumValueMaps() { return s_newEnumValueMaps; }
+ static NodeMultiMapMap &newSinceMaps() { return s_newSinceMaps; }
+
+private:
+ void findAllClasses(Aggregate *node) { node->findAllClasses(); }
+ void findAllFunctions(Aggregate *node) { node->findAllFunctions(m_functionIndex); }
+ void findAllAttributions(Aggregate *node) { node->findAllAttributions(m_attributions); }
+ void findAllLegaleseTexts(Aggregate *node);
+ void findAllObsoleteThings(Aggregate *node) { node->findAllObsoleteThings(); }
+ void findAllSince(Aggregate *node) { node->findAllSince(); }
+
+public:
+ /*******************************************************************
+ special collection access functions
+ ********************************************************************/
+ NodeMultiMap &getCppClasses();
+ NodeMultiMap &getObsoleteClasses();
+ NodeMultiMap &getClassesWithObsoleteMembers();
+ NodeMultiMap &getObsoleteQmlTypes();
+ NodeMultiMap &getQmlTypesWithObsoleteMembers();
+ NodeMultiMap &getNamespaces();
+ NodeMultiMap &getQmlValueTypes();
+ NodeMultiMap &getQmlTypes();
+ NodeMultiMap &getExamples();
+ NodeMultiMap &getAttributions();
+ NodeMapMap &getFunctionIndex();
+ TextToNodeMap &getLegaleseTexts();
+ const NodeMultiMap &getClassMap(const QString &key);
+ const NodeMultiMap &getQmlTypeMap(const QString &key);
+ const NodeMultiMap &getSinceMap(const QString &key);
+
+ /*******************************************************************
+ Many of these will be either eliminated or replaced.
+ ********************************************************************/
+ void resolveStuff();
+ void insertTarget(const QString &name, const QString &title, TargetRec::TargetType type,
+ Node *node, int priority)
+ {
+ primaryTree()->insertTarget(name, title, type, node, priority);
+ }
+
+ /*******************************************************************
+ The functions declared below are called for the current tree only.
+ ********************************************************************/
+ Aggregate *findRelatesNode(const QStringList &path)
+ {
+ return primaryTree()->findRelatesNode(path);
+ }
+ /*******************************************************************/
+
+ /*****************************************************************************
+ This function can handle parameters enclosed in '[' ']' (domain and genus).
+ ******************************************************************************/
+ const Node *findNodeForAtom(const Atom *atom, const Node *relative, QString &ref,
+ Node::Genus genus = Node::DontCare);
+ /*******************************************************************/
+
+ /*******************************************************************
+ The functions declared below are called for all trees.
+ ********************************************************************/
+ ClassNode *findClassNode(const QStringList &path) { return m_forest.findClassNode(path); }
+ Node *findNodeForInclude(const QStringList &path) { return m_forest.findNodeForInclude(path); }
+ const FunctionNode *findFunctionNode(const QString &target, const Node *relative,
+ Node::Genus genus);
+ const Node *findTypeNode(const QString &type, const Node *relative, Node::Genus genus);
+ const Node *findNodeForTarget(const QString &target, const Node *relative);
+ const PageNode *findPageNodeByTitle(const QString &title)
+ {
+ return m_forest.findPageNodeByTitle(title);
+ }
+ Node *findNodeByNameAndType(const QStringList &path, bool (Node::*isMatch)() const)
+ {
+ return m_forest.findNodeByNameAndType(path, isMatch);
+ }
+ const CollectionNode *getCollectionNode(const QString &name, Node::NodeType type)
+ {
+ return m_forest.getCollectionNode(name, type);
+ }
+ const CollectionNode *getModuleNode(const Node *relative);
+
+ FunctionNode *findFunctionNodeForTag(const QString &tag)
+ {
+ return primaryTree()->findFunctionNodeForTag(tag);
+ }
+ FunctionNode *findMacroNode(const QString &t) { return primaryTree()->findMacroNode(t); }
+
+ QStringList groupNamesForNode(Node *node);
+
+private:
+ const Node *findNodeForTarget(QStringList &targetPath, const Node *relative, Node::Genus genus,
+ QString &ref)
+ {
+ return m_forest.findNodeForTarget(targetPath, relative, genus, ref);
+ }
+ const FunctionNode *findFunctionNode(const QStringList &path, const Parameters &parameters,
+ const Node *relative, Node::Genus genus)
+ {
+ return m_forest.findFunctionNode(path, parameters, relative, genus);
+ }
+
+ /*******************************************************************/
+public:
+ void addPropertyFunction(PropertyNode *property, const QString &funcName,
+ PropertyNode::FunctionRole funcRole)
+ {
+ primaryTree()->addPropertyFunction(property, funcName, funcRole);
+ }
+
+ void setVersion(const QString &v) { m_version = v; }
+ [[nodiscard]] QString version() const { return m_version; }
+
+ void readIndexes(const QStringList &indexFiles);
+ void generateIndex(const QString &fileName, const QString &url, const QString &title,
+ Generator *g);
+
+ void processForest();
+
+ NamespaceNode *primaryTreeRoot() { return m_forest.primaryTreeRoot(); }
+ void newPrimaryTree(const QString &module) { m_forest.newPrimaryTree(module); }
+ void setPrimaryTree(const QString &t) { m_forest.setPrimaryTree(t); }
+ NamespaceNode *newIndexTree(const QString &module) { return m_forest.newIndexTree(module); }
+ const QList<Tree *> &searchOrder() { return m_forest.searchOrder(); }
+ void setLocalSearch() { m_forest.m_searchOrder = QList<Tree *>(1, primaryTree()); }
+ void setSearchOrder(const QList<Tree *> &searchOrder) { m_forest.m_searchOrder = searchOrder; }
+ void setSearchOrder(QStringList &t) { m_forest.setSearchOrder(t); }
+ void mergeCollections(Node::NodeType type, CNMap &cnm, const Node *relative);
+ void mergeCollections(CollectionNode *c);
+ void clearSearchOrder() { m_forest.clearSearchOrder(); }
+ QStringList keys() { return m_forest.keys(); }
+ void resolveNamespaces();
+ void resolveProxies();
+ void resolveBaseClasses();
+ void updateNavigation();
+
+private:
+ friend class Tree;
+
+ void processForest(FindFunctionPtr func);
+ bool isLoaded(const QString &t) { return m_forest.isLoaded(t); }
+ static void initializeDB();
+
+private:
+ QDocDatabase();
+ QDocDatabase(QDocDatabase const &) : m_forest(this) { }
+ QDocDatabase &operator=(QDocDatabase const &);
+
+public:
+ Tree *primaryTree() { return m_forest.primaryTree(); }
+
+private:
+ static QDocDatabase *s_qdocDB;
+ static NodeMap s_typeNodeMap;
+ static NodeMultiMap s_obsoleteClasses;
+ static NodeMultiMap s_classesWithObsoleteMembers;
+ static NodeMultiMap s_obsoleteQmlTypes;
+ static NodeMultiMap s_qmlTypesWithObsoleteMembers;
+ static NodeMultiMap s_cppClasses;
+ static NodeMultiMap s_qmlBasicTypes;
+ static NodeMultiMap s_qmlTypes;
+ static NodeMultiMap s_examples;
+ static NodeMultiMapMap s_newClassMaps;
+ static NodeMultiMapMap s_newQmlTypeMaps;
+ static NodeMultiMapMap s_newEnumValueMaps;
+ static NodeMultiMapMap s_newSinceMaps;
+
+ QString m_version {};
+ QDocForest m_forest;
+
+ NodeMultiMap m_namespaceIndex {};
+ NodeMultiMap m_attributions {};
+ NodeMapMap m_functionIndex {};
+ TextToNodeMap m_legaleseTexts {};
+ QMultiHash<Tree*, FindFunctionPtr> m_completedFindFunctions {};
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/qdocindexfiles.cpp b/src/qdoc/qdoc/src/qdoc/qdocindexfiles.cpp
new file mode 100644
index 000000000..74d27ed3c
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qdocindexfiles.cpp
@@ -0,0 +1,1449 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qdocindexfiles.h"
+
+#include "access.h"
+#include "atom.h"
+#include "classnode.h"
+#include "collectionnode.h"
+#include "comparisoncategory.h"
+#include "config.h"
+#include "enumnode.h"
+#include "examplenode.h"
+#include "externalpagenode.h"
+#include "functionnode.h"
+#include "generator.h"
+#include "headernode.h"
+#include "location.h"
+#include "utilities.h"
+#include "propertynode.h"
+#include "qdocdatabase.h"
+#include "qmlpropertynode.h"
+#include "typedefnode.h"
+#include "variablenode.h"
+
+#include <QtCore/qxmlstream.h>
+
+#include <algorithm>
+
+QT_BEGIN_NAMESPACE
+
+enum QDocAttr {
+ QDocAttrNone,
+ QDocAttrExample,
+ QDocAttrFile,
+ QDocAttrImage,
+ QDocAttrDocument,
+ QDocAttrExternalPage,
+ QDocAttrAttribution
+};
+
+static Node *root_ = nullptr;
+static IndexSectionWriter *post_ = nullptr;
+
+/*!
+ \class QDocIndexFiles
+
+ This class handles qdoc index files.
+ */
+
+QDocIndexFiles *QDocIndexFiles::s_qdocIndexFiles = nullptr;
+
+/*!
+ Constructs the singleton QDocIndexFiles.
+ */
+QDocIndexFiles::QDocIndexFiles() : m_gen(nullptr)
+{
+ m_qdb = QDocDatabase::qdocDB();
+ m_storeLocationInfo = Config::instance().get(CONFIG_LOCATIONINFO).asBool();
+}
+
+/*!
+ Destroys the singleton QDocIndexFiles.
+ */
+QDocIndexFiles::~QDocIndexFiles()
+{
+ m_qdb = nullptr;
+ m_gen = nullptr;
+}
+
+/*!
+ Creates the singleton. Allows only one instance of the class
+ to be created. Returns a pointer to the singleton.
+ */
+QDocIndexFiles *QDocIndexFiles::qdocIndexFiles()
+{
+ if (s_qdocIndexFiles == nullptr)
+ s_qdocIndexFiles = new QDocIndexFiles;
+ return s_qdocIndexFiles;
+}
+
+/*!
+ Destroys the singleton.
+ */
+void QDocIndexFiles::destroyQDocIndexFiles()
+{
+ if (s_qdocIndexFiles != nullptr) {
+ delete s_qdocIndexFiles;
+ s_qdocIndexFiles = nullptr;
+ }
+}
+
+/*!
+ Reads and parses the list of index files in \a indexFiles.
+ */
+void QDocIndexFiles::readIndexes(const QStringList &indexFiles)
+{
+ for (const QString &file : indexFiles) {
+ qCDebug(lcQdoc) << "Loading index file: " << file;
+ readIndexFile(file);
+ }
+}
+
+/*!
+ Reads and parses the index file at \a path.
+ */
+void QDocIndexFiles::readIndexFile(const QString &path)
+{
+ QFile file(path);
+ if (!file.open(QFile::ReadOnly)) {
+ qWarning() << "Could not read index file" << path;
+ return;
+ }
+
+ QXmlStreamReader reader(&file);
+ reader.setNamespaceProcessing(false);
+
+ if (!reader.readNextStartElement())
+ return;
+
+ if (reader.name() != QLatin1String("INDEX"))
+ return;
+
+ QXmlStreamAttributes attrs = reader.attributes();
+
+ QString indexUrl {attrs.value(QLatin1String("url")).toString()};
+
+ // Decide how we link to nodes loaded from this index file:
+ // If building a set that will be installed AND the URL of
+ // the dependency is identical to ours, assume that also
+ // the dependent html files are available under the same
+ // directory tree. Otherwise, link using the full index URL.
+ if (!Config::installDir.isEmpty() && indexUrl == Config::instance().get(CONFIG_URL).asString()) {
+ // Generate a relative URL between the install dir and the index file
+ // when the -installdir command line option is set.
+ QDir installDir(path.section('/', 0, -3) + '/' + Generator::outputSubdir());
+ indexUrl = installDir.relativeFilePath(path).section('/', 0, -2);
+ }
+ m_project = attrs.value(QLatin1String("project")).toString();
+ QString indexTitle = attrs.value(QLatin1String("indexTitle")).toString();
+ m_basesList.clear();
+ m_relatedNodes.clear();
+
+ NamespaceNode *root = m_qdb->newIndexTree(m_project);
+ if (!root) {
+ qWarning() << "Issue parsing index tree" << path;
+ return;
+ }
+
+ root->tree()->setIndexTitle(indexTitle);
+
+ // Scan all elements in the XML file, constructing a map that contains
+ // base classes for each class found.
+ while (reader.readNextStartElement()) {
+ readIndexSection(reader, root, indexUrl);
+ }
+
+ // Now that all the base classes have been found for this index,
+ // arrange them into an inheritance hierarchy.
+ resolveIndex();
+}
+
+/*!
+ Read a <section> element from the index file and create the
+ appropriate node(s).
+ */
+void QDocIndexFiles::readIndexSection(QXmlStreamReader &reader, Node *current,
+ const QString &indexUrl)
+{
+ QXmlStreamAttributes attributes = reader.attributes();
+ QStringView elementName = reader.name();
+
+ QString name = attributes.value(QLatin1String("name")).toString();
+ QString href = attributes.value(QLatin1String("href")).toString();
+ Node *node;
+ Location location;
+ Aggregate *parent = nullptr;
+ bool hasReadChildren = false;
+
+ if (current->isAggregate())
+ parent = static_cast<Aggregate *>(current);
+
+ if (attributes.hasAttribute(QLatin1String("related"))) {
+ bool isIntTypeRelatedValue = false;
+ int relatedIndex = attributes.value(QLatin1String("related")).toInt(&isIntTypeRelatedValue);
+ if (isIntTypeRelatedValue) {
+ if (adoptRelatedNode(parent, relatedIndex)) {
+ reader.skipCurrentElement();
+ return;
+ }
+ } else {
+ QList<Node *>::iterator nodeIterator =
+ std::find_if(m_relatedNodes.begin(), m_relatedNodes.end(), [&](const Node *relatedNode) {
+ return (name == relatedNode->name() && href == relatedNode->url().section(QLatin1Char('/'), -1));
+ });
+
+ if (nodeIterator != m_relatedNodes.end() && parent) {
+ parent->adoptChild(*nodeIterator);
+ reader.skipCurrentElement();
+ return;
+ }
+ }
+ }
+
+ QString filePath;
+ int lineNo = 0;
+ if (attributes.hasAttribute(QLatin1String("filepath"))) {
+ filePath = attributes.value(QLatin1String("filepath")).toString();
+ lineNo = attributes.value("lineno").toInt();
+ }
+ if (elementName == QLatin1String("namespace")) {
+ auto *namespaceNode = new NamespaceNode(parent, name);
+ node = namespaceNode;
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + name.toLower() + ".html");
+ else if (!indexUrl.isNull())
+ location = Location(name.toLower() + ".html");
+ } else if (elementName == QLatin1String("class") || elementName == QLatin1String("struct")
+ || elementName == QLatin1String("union")) {
+ Node::NodeType type = Node::Class;
+ if (elementName == QLatin1String("class"))
+ type = Node::Class;
+ else if (elementName == QLatin1String("struct"))
+ type = Node::Struct;
+ else if (elementName == QLatin1String("union"))
+ type = Node::Union;
+ node = new ClassNode(type, parent, name);
+ if (attributes.hasAttribute(QLatin1String("bases"))) {
+ QString bases = attributes.value(QLatin1String("bases")).toString();
+ if (!bases.isEmpty())
+ m_basesList.append(
+ std::pair<ClassNode *, QString>(static_cast<ClassNode *>(node), bases));
+ }
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + name.toLower() + ".html");
+ else if (!indexUrl.isNull())
+ location = Location(name.toLower() + ".html");
+ bool abstract = false;
+ if (attributes.value(QLatin1String("abstract")) == QLatin1String("true"))
+ abstract = true;
+ node->setAbstract(abstract);
+ } else if (elementName == QLatin1String("header")) {
+ node = new HeaderNode(parent, name);
+
+ if (attributes.hasAttribute(QLatin1String("location")))
+ name = attributes.value(QLatin1String("location")).toString();
+
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + name);
+ else if (!indexUrl.isNull())
+ location = Location(name);
+ } else if (elementName == QLatin1String("qmlclass") || elementName == QLatin1String("qmlvaluetype")
+ || elementName == QLatin1String("qmlbasictype")) {
+ auto *qmlTypeNode = new QmlTypeNode(parent, name,
+ elementName == QLatin1String("qmlclass") ? Node::QmlType : Node::QmlValueType);
+ qmlTypeNode->setTitle(attributes.value(QLatin1String("title")).toString());
+ QString logicalModuleName = attributes.value(QLatin1String("qml-module-name")).toString();
+ if (!logicalModuleName.isEmpty())
+ m_qdb->addToQmlModule(logicalModuleName, qmlTypeNode);
+ bool abstract = false;
+ if (attributes.value(QLatin1String("abstract")) == QLatin1String("true"))
+ abstract = true;
+ qmlTypeNode->setAbstract(abstract);
+ QString qmlFullBaseName = attributes.value(QLatin1String("qml-base-type")).toString();
+ if (!qmlFullBaseName.isEmpty()) {
+ qmlTypeNode->setQmlBaseName(qmlFullBaseName);
+ }
+ if (attributes.hasAttribute(QLatin1String("location")))
+ name = attributes.value("location").toString();
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + name);
+ else if (!indexUrl.isNull())
+ location = Location(name);
+ node = qmlTypeNode;
+ } else if (elementName == QLatin1String("qmlproperty")) {
+ QString type = attributes.value(QLatin1String("type")).toString();
+ bool attached = false;
+ if (attributes.value(QLatin1String("attached")) == QLatin1String("true"))
+ attached = true;
+ bool readonly = false;
+ if (attributes.value(QLatin1String("writable")) == QLatin1String("false"))
+ readonly = true;
+ auto *qmlPropertyNode = new QmlPropertyNode(parent, name, type, attached);
+ qmlPropertyNode->markReadOnly(readonly);
+ if (attributes.value(QLatin1String("required")) == QLatin1String("true"))
+ qmlPropertyNode->setRequired();
+ node = qmlPropertyNode;
+ } else if (elementName == QLatin1String("group")) {
+ auto *collectionNode = m_qdb->addGroup(name);
+ collectionNode->setTitle(attributes.value(QLatin1String("title")).toString());
+ collectionNode->setSubtitle(attributes.value(QLatin1String("subtitle")).toString());
+ if (attributes.value(QLatin1String("seen")) == QLatin1String("true"))
+ collectionNode->markSeen();
+ node = collectionNode;
+ } else if (elementName == QLatin1String("module")) {
+ auto *collectionNode = m_qdb->addModule(name);
+ collectionNode->setTitle(attributes.value(QLatin1String("title")).toString());
+ collectionNode->setSubtitle(attributes.value(QLatin1String("subtitle")).toString());
+ if (attributes.value(QLatin1String("seen")) == QLatin1String("true"))
+ collectionNode->markSeen();
+ node = collectionNode;
+ } else if (elementName == QLatin1String("qmlmodule")) {
+ auto *collectionNode = m_qdb->addQmlModule(name);
+ const QStringList info = QStringList()
+ << name
+ << QString(attributes.value(QLatin1String("qml-module-version")).toString());
+ collectionNode->setLogicalModuleInfo(info);
+ collectionNode->setTitle(attributes.value(QLatin1String("title")).toString());
+ collectionNode->setSubtitle(attributes.value(QLatin1String("subtitle")).toString());
+ if (attributes.value(QLatin1String("seen")) == QLatin1String("true"))
+ collectionNode->markSeen();
+ node = collectionNode;
+ } else if (elementName == QLatin1String("page")) {
+ QDocAttr subtype = QDocAttrNone;
+ QString attr = attributes.value(QLatin1String("subtype")).toString();
+ if (attr == QLatin1String("attribution")) {
+ subtype = QDocAttrAttribution;
+ } else if (attr == QLatin1String("example")) {
+ subtype = QDocAttrExample;
+ } else if (attr == QLatin1String("file")) {
+ subtype = QDocAttrFile;
+ } else if (attr == QLatin1String("image")) {
+ subtype = QDocAttrImage;
+ } else if (attr == QLatin1String("page")) {
+ subtype = QDocAttrDocument;
+ } else if (attr == QLatin1String("externalpage")) {
+ subtype = QDocAttrExternalPage;
+ } else
+ goto done;
+
+ if (current->isExample()) {
+ auto *exampleNode = static_cast<ExampleNode *>(current);
+ if (subtype == QDocAttrFile) {
+ exampleNode->appendFile(name);
+ goto done;
+ } else if (subtype == QDocAttrImage) {
+ exampleNode->appendImage(name);
+ goto done;
+ }
+ }
+ PageNode *pageNode = nullptr;
+ if (subtype == QDocAttrExample)
+ pageNode = new ExampleNode(parent, name);
+ else if (subtype == QDocAttrExternalPage)
+ pageNode = new ExternalPageNode(parent, name);
+ else {
+ pageNode = new PageNode(parent, name);
+ if (subtype == QDocAttrAttribution) pageNode->markAttribution();
+ }
+
+ pageNode->setTitle(attributes.value(QLatin1String("title")).toString());
+
+ if (attributes.hasAttribute(QLatin1String("location")))
+ name = attributes.value(QLatin1String("location")).toString();
+
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + name);
+ else if (!indexUrl.isNull())
+ location = Location(name);
+
+ node = pageNode;
+
+ } else if (elementName == QLatin1String("enum")) {
+ auto *enumNode = new EnumNode(parent, name, attributes.hasAttribute("scoped"));
+
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html");
+ else if (!indexUrl.isNull())
+ location = Location(parent->name().toLower() + ".html");
+
+ while (reader.readNextStartElement()) {
+ QXmlStreamAttributes childAttributes = reader.attributes();
+ if (reader.name() == QLatin1String("value")) {
+ EnumItem item(childAttributes.value(QLatin1String("name")).toString(),
+ childAttributes.value(QLatin1String("value")).toString(),
+ childAttributes.value(QLatin1String("since")).toString()
+ );
+ enumNode->addItem(item);
+ } else if (reader.name() == QLatin1String("keyword")) {
+ insertTarget(TargetRec::Keyword, childAttributes, enumNode);
+ } else if (reader.name() == QLatin1String("target")) {
+ insertTarget(TargetRec::Target, childAttributes, enumNode);
+ }
+ reader.skipCurrentElement();
+ }
+
+ node = enumNode;
+
+ hasReadChildren = true;
+ } else if (elementName == QLatin1String("typedef")) {
+ if (attributes.hasAttribute("aliasedtype"))
+ node = new TypeAliasNode(parent, name, attributes.value(QLatin1String("aliasedtype")).toString());
+ else
+ node = new TypedefNode(parent, name);
+
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html");
+ else if (!indexUrl.isNull())
+ location = Location(parent->name().toLower() + ".html");
+ } else if (elementName == QLatin1String("property")) {
+ auto *propNode = new PropertyNode(parent, name);
+ node = propNode;
+ if (attributes.value(QLatin1String("bindable")) == QLatin1String("true"))
+ propNode->setPropertyType(PropertyNode::PropertyType::BindableProperty);
+
+ propNode->setWritable(attributes.value(QLatin1String("writable")) != QLatin1String("false"));
+
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html");
+ else if (!indexUrl.isNull())
+ location = Location(parent->name().toLower() + ".html");
+
+ } else if (elementName == QLatin1String("function")) {
+ QString t = attributes.value(QLatin1String("meta")).toString();
+ bool attached = false;
+ FunctionNode::Metaness metaness = FunctionNode::Plain;
+ if (!t.isEmpty())
+ metaness = FunctionNode::getMetaness(t);
+ if (attributes.value(QLatin1String("attached")) == QLatin1String("true"))
+ attached = true;
+ auto *fn = new FunctionNode(metaness, parent, name, attached);
+
+ fn->setReturnType(attributes.value(QLatin1String("type")).toString());
+
+ if (fn->isCppNode()) {
+ fn->setVirtualness(attributes.value(QLatin1String("virtual")).toString());
+ fn->setConst(attributes.value(QLatin1String("const")) == QLatin1String("true"));
+ fn->setStatic(attributes.value(QLatin1String("static")) == QLatin1String("true"));
+ fn->setFinal(attributes.value(QLatin1String("final")) == QLatin1String("true"));
+ fn->setOverride(attributes.value(QLatin1String("override")) == QLatin1String("true"));
+
+ if (attributes.value(QLatin1String("explicit")) == QLatin1String("true"))
+ fn->markExplicit();
+
+ if (attributes.value(QLatin1String("constexpr")) == QLatin1String("true"))
+ fn->markConstexpr();
+
+ if (attributes.value(QLatin1String("noexcept")) == QLatin1String("true")) {
+ fn->markNoexcept(attributes.value("noexcept_expression").toString());
+ }
+
+ qsizetype refness = attributes.value(QLatin1String("refness")).toUInt();
+ if (refness == 1)
+ fn->setRef(true);
+ else if (refness == 2)
+ fn->setRefRef(true);
+ /*
+ Theoretically, this should ensure that each function
+ node receives the same overload number and overload
+ flag it was written with, and it should be unnecessary
+ to call normalizeOverloads() for index nodes.
+ */
+ if (attributes.value(QLatin1String("overload")) == QLatin1String("true"))
+ fn->setOverloadNumber(attributes.value(QLatin1String("overload-number")).toUInt());
+ else
+ fn->setOverloadNumber(0);
+ }
+
+ /*
+ Note: The "signature" attribute was written to the
+ index file, but it is not read back in. That is ok
+ because we reconstruct the parameter list and the
+ return type, from which the signature was built in
+ the first place and from which it can be rebuilt.
+ */
+ while (reader.readNextStartElement()) {
+ QXmlStreamAttributes childAttributes = reader.attributes();
+ if (reader.name() == QLatin1String("parameter")) {
+ // Do not use the default value for the parameter; it is not
+ // required, and has been known to cause problems.
+ QString type = childAttributes.value(QLatin1String("type")).toString();
+ QString name = childAttributes.value(QLatin1String("name")).toString();
+ fn->parameters().append(type, name);
+ } else if (reader.name() == QLatin1String("keyword")) {
+ insertTarget(TargetRec::Keyword, childAttributes, fn);
+ } else if (reader.name() == QLatin1String("target")) {
+ insertTarget(TargetRec::Target, childAttributes, fn);
+ }
+ reader.skipCurrentElement();
+ }
+
+ node = fn;
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html");
+ else if (!indexUrl.isNull())
+ location = Location(parent->name().toLower() + ".html");
+
+ hasReadChildren = true;
+ } else if (elementName == QLatin1String("variable")) {
+ node = new VariableNode(parent, name);
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + parent->name().toLower() + ".html");
+ else if (!indexUrl.isNull())
+ location = Location(parent->name().toLower() + ".html");
+ } else if (elementName == QLatin1String("keyword")) {
+ insertTarget(TargetRec::Keyword, attributes, current);
+ goto done;
+ } else if (elementName == QLatin1String("target")) {
+ insertTarget(TargetRec::Target, attributes, current);
+ goto done;
+ } else if (elementName == QLatin1String("contents")) {
+ insertTarget(TargetRec::Contents, attributes, current);
+ goto done;
+ } else if (elementName == QLatin1String("proxy")) {
+ node = new ProxyNode(parent, name);
+ if (!indexUrl.isEmpty())
+ location = Location(indexUrl + QLatin1Char('/') + name.toLower() + ".html");
+ else if (!indexUrl.isNull())
+ location = Location(name.toLower() + ".html");
+ } else {
+ goto done;
+ }
+
+ {
+ if (!href.isEmpty()) {
+ node->setUrl(href);
+ // Include the index URL if it exists
+ if (!node->isExternalPage() && !indexUrl.isEmpty())
+ node->setUrl(indexUrl + QLatin1Char('/') + href);
+ }
+
+ const QString access = attributes.value(QLatin1String("access")).toString();
+ if (access == "protected")
+ node->setAccess(Access::Protected);
+ else if ((access == "private") || (access == "internal"))
+ node->setAccess(Access::Private);
+ else
+ node->setAccess(Access::Public);
+
+ if (attributes.hasAttribute(QLatin1String("related"))) {
+ node->setRelatedNonmember(true);
+ m_relatedNodes << node;
+ }
+
+ if (attributes.hasAttribute(QLatin1String("threadsafety"))) {
+ QString threadSafety = attributes.value(QLatin1String("threadsafety")).toString();
+ if (threadSafety == QLatin1String("non-reentrant"))
+ node->setThreadSafeness(Node::NonReentrant);
+ else if (threadSafety == QLatin1String("reentrant"))
+ node->setThreadSafeness(Node::Reentrant);
+ else if (threadSafety == QLatin1String("thread safe"))
+ node->setThreadSafeness(Node::ThreadSafe);
+ else
+ node->setThreadSafeness(Node::UnspecifiedSafeness);
+ } else
+ node->setThreadSafeness(Node::UnspecifiedSafeness);
+
+ const QString category = attributes.value(QLatin1String("comparison_category")).toString();
+ node->setComparisonCategory(comparisonCategoryFromString(category.toStdString()));
+
+ QString status = attributes.value(QLatin1String("status")).toString();
+ // TODO: "obsolete" is kept for backward compatibility, remove in the near future
+ if (status == QLatin1String("obsolete") || status == QLatin1String("deprecated"))
+ node->setStatus(Node::Deprecated);
+ else if (status == QLatin1String("preliminary"))
+ node->setStatus(Node::Preliminary);
+ else if (status == QLatin1String("internal"))
+ node->setStatus(Node::Internal);
+ else if (status == QLatin1String("ignored"))
+ node->setStatus(Node::DontDocument);
+ else
+ node->setStatus(Node::Active);
+
+ QString physicalModuleName = attributes.value(QLatin1String("module")).toString();
+ if (!physicalModuleName.isEmpty())
+ m_qdb->addToModule(physicalModuleName, node);
+
+ QString since = attributes.value(QLatin1String("since")).toString();
+ if (!since.isEmpty()) {
+ node->setSince(since);
+ }
+
+ if (attributes.hasAttribute(QLatin1String("documented"))) {
+ if (attributes.value(QLatin1String("documented")) == QLatin1String("true"))
+ node->setHadDoc();
+ }
+
+ QString groupsAttr = attributes.value(QLatin1String("groups")).toString();
+ if (!groupsAttr.isEmpty()) {
+ const QStringList groupNames = groupsAttr.split(QLatin1Char(','));
+ for (const auto &group : groupNames) {
+ m_qdb->addToGroup(group, node);
+ }
+ }
+
+ // Create some content for the node.
+ QSet<QString> emptySet;
+ Location t(filePath);
+ if (!filePath.isEmpty()) {
+ t.setLineNo(lineNo);
+ node->setLocation(t);
+ location = t;
+ }
+ Doc doc(location, location, QString(), emptySet, emptySet); // placeholder
+ node->setDoc(doc);
+ node->setIndexNodeFlag(); // Important: This node came from an index file.
+ QString briefAttr = attributes.value(QLatin1String("brief")).toString();
+ if (!briefAttr.isEmpty()) {
+ node->setReconstitutedBrief(briefAttr);
+ }
+
+ if (!hasReadChildren) {
+ bool useParent = (elementName == QLatin1String("namespace") && name.isEmpty());
+ while (reader.readNextStartElement()) {
+ if (useParent)
+ readIndexSection(reader, parent, indexUrl);
+ else
+ readIndexSection(reader, node, indexUrl);
+ }
+ }
+ }
+
+done:
+ while (!reader.isEndElement()) {
+ if (reader.readNext() == QXmlStreamReader::Invalid) {
+ break;
+ }
+ }
+}
+
+void QDocIndexFiles::insertTarget(TargetRec::TargetType type,
+ const QXmlStreamAttributes &attributes, Node *node)
+{
+ int priority;
+ switch (type) {
+ case TargetRec::Keyword:
+ priority = 1;
+ break;
+ case TargetRec::Target:
+ priority = 2;
+ break;
+ case TargetRec::Contents:
+ priority = 3;
+ break;
+ default:
+ return;
+ }
+
+ QString name = attributes.value(QLatin1String("name")).toString();
+ QString title = attributes.value(QLatin1String("title")).toString();
+ m_qdb->insertTarget(name, title, type, node, priority);
+}
+
+/*!
+ This function tries to resolve class inheritance immediately
+ after the index file is read. It is not always possible to
+ resolve a class inheritance at this point, because the base
+ class might be in an index file that hasn't been read yet, or
+ it might be in one of the header files that will be read for
+ the current module. These cases will be resolved after all
+ the index files and header and source files have been read,
+ just prior to beginning the generate phase for the current
+ module.
+
+ I don't think this is completely correct because it always
+ sets the access to public.
+ */
+void QDocIndexFiles::resolveIndex()
+{
+ for (const auto &pair : std::as_const(m_basesList)) {
+ const QStringList bases = pair.second.split(QLatin1Char(','));
+ for (const auto &base : bases) {
+ QStringList basePath = base.split(QString("::"));
+ Node *n = m_qdb->findClassNode(basePath);
+ if (n)
+ pair.first->addResolvedBaseClass(Access::Public, static_cast<ClassNode *>(n));
+ else
+ pair.first->addUnresolvedBaseClass(Access::Public, basePath);
+ }
+ }
+ // No longer needed.
+ m_basesList.clear();
+}
+
+static QString getAccessString(Access t)
+{
+
+ switch (t) {
+ case Access::Public:
+ return QLatin1String("public");
+ case Access::Protected:
+ return QLatin1String("protected");
+ case Access::Private:
+ return QLatin1String("private");
+ default:
+ break;
+ }
+ return QLatin1String("public");
+}
+
+static QString getStatusString(Node::Status t)
+{
+ switch (t) {
+ case Node::Deprecated:
+ return QLatin1String("deprecated");
+ case Node::Preliminary:
+ return QLatin1String("preliminary");
+ case Node::Active:
+ return QLatin1String("active");
+ case Node::Internal:
+ return QLatin1String("internal");
+ case Node::DontDocument:
+ return QLatin1String("ignored");
+ default:
+ break;
+ }
+ return QLatin1String("active");
+}
+
+static QString getThreadSafenessString(Node::ThreadSafeness t)
+{
+ switch (t) {
+ case Node::NonReentrant:
+ return QLatin1String("non-reentrant");
+ case Node::Reentrant:
+ return QLatin1String("reentrant");
+ case Node::ThreadSafe:
+ return QLatin1String("thread safe");
+ case Node::UnspecifiedSafeness:
+ default:
+ break;
+ }
+ return QLatin1String("unspecified");
+}
+
+/*!
+ Returns the index of \a node in the list of related non-member nodes.
+*/
+int QDocIndexFiles::indexForNode(Node *node)
+{
+ qsizetype i = m_relatedNodes.indexOf(node);
+ if (i == -1) {
+ i = m_relatedNodes.size();
+ m_relatedNodes << node;
+ }
+ return i;
+}
+
+/*!
+ Adopts the related non-member node identified by \a index to the
+ parent \a adoptiveParent. Returns \c true if successful.
+*/
+bool QDocIndexFiles::adoptRelatedNode(Aggregate *adoptiveParent, int index)
+{
+ Node *related = m_relatedNodes.value(index);
+
+ if (adoptiveParent && related) {
+ adoptiveParent->adoptChild(related);
+ return true;
+ }
+
+ return false;
+}
+
+/*!
+ Write canonicalized versions of \\target and \\keyword identifiers
+ that appear in the documentation of \a node into the index using
+ \a writer, so that they can be used as link targets in external
+ documentation sets.
+*/
+void QDocIndexFiles::writeTargets(QXmlStreamWriter &writer, Node *node)
+{
+ if (node->doc().hasTargets()) {
+ for (const Atom *target : std::as_const(node->doc().targets())) {
+ const QString &title = target->string();
+ const QString &name{Utilities::asAsciiPrintable(title)};
+ writer.writeStartElement("target");
+ writer.writeAttribute("name", node->isExternalPage() ? title : name);
+ if (name != title)
+ writer.writeAttribute("title", title);
+ writer.writeEndElement(); // target
+ }
+ }
+ if (node->doc().hasKeywords()) {
+ for (const Atom *keyword : std::as_const(node->doc().keywords())) {
+ const QString &title = keyword->string();
+ const QString &name{Utilities::asAsciiPrintable(title)};
+ writer.writeStartElement("keyword");
+ writer.writeAttribute("name", name);
+ if (name != title)
+ writer.writeAttribute("title", title);
+ writer.writeEndElement(); // keyword
+ }
+ }
+}
+
+/*!
+ Generate the index section with the given \a writer for the \a node
+ specified, returning true if an element was written, and returning
+ false if an element is not written.
+
+ \note Function nodes are processed in generateFunctionSection()
+ */
+bool QDocIndexFiles::generateIndexSection(QXmlStreamWriter &writer, Node *node,
+ IndexSectionWriter *post)
+{
+ if (m_gen == nullptr)
+ m_gen = Generator::currentGenerator();
+
+ Q_ASSERT(m_gen);
+
+ post_ = nullptr;
+ /*
+ Don't include index nodes in a new index file.
+ */
+ if (node->isIndexNode())
+ return false;
+
+ QString nodeName;
+ QString logicalModuleName;
+ QString logicalModuleVersion;
+ QString qmlFullBaseName;
+ QString baseNameAttr;
+ QString moduleNameAttr;
+ QString moduleVerAttr;
+
+ switch (node->nodeType()) {
+ case Node::Namespace:
+ nodeName = "namespace";
+ break;
+ case Node::Class:
+ nodeName = "class";
+ break;
+ case Node::Struct:
+ nodeName = "struct";
+ break;
+ case Node::Union:
+ nodeName = "union";
+ break;
+ case Node::HeaderFile:
+ nodeName = "header";
+ break;
+ case Node::QmlType:
+ case Node::QmlValueType:
+ nodeName = (node->nodeType() == Node::QmlType) ? "qmlclass" : "qmlvaluetype";
+ logicalModuleName = node->logicalModuleName();
+ baseNameAttr = "qml-base-type";
+ moduleNameAttr = "qml-module-name";
+ moduleVerAttr = "qml-module-version";
+ qmlFullBaseName = node->qmlFullBaseName();
+ break;
+ case Node::Page:
+ case Node::Example:
+ case Node::ExternalPage:
+ nodeName = "page";
+ break;
+ case Node::Group:
+ nodeName = "group";
+ break;
+ case Node::Module:
+ nodeName = "module";
+ break;
+ case Node::QmlModule:
+ nodeName = "qmlmodule";
+ moduleNameAttr = "qml-module-name";
+ moduleVerAttr = "qml-module-version";
+ logicalModuleName = node->logicalModuleName();
+ logicalModuleVersion = node->logicalModuleVersion();
+ break;
+ case Node::Enum:
+ nodeName = "enum";
+ break;
+ case Node::TypeAlias:
+ case Node::Typedef:
+ nodeName = "typedef";
+ break;
+ case Node::Property:
+ nodeName = "property";
+ break;
+ case Node::Variable:
+ nodeName = "variable";
+ break;
+ case Node::SharedComment:
+ if (!node->isPropertyGroup())
+ return false;
+ // Add an entry for property groups so that they can be linked to
+ nodeName = "qmlproperty";
+ break;
+ case Node::QmlProperty:
+ nodeName = "qmlproperty";
+ break;
+ case Node::Proxy:
+ nodeName = "proxy";
+ break;
+ case Node::Function: // Now processed in generateFunctionSection()
+ default:
+ return false;
+ }
+
+ QString objName = node->name();
+ // Special case: only the root node should have an empty name.
+ if (objName.isEmpty() && node != m_qdb->primaryTreeRoot())
+ return false;
+
+ writer.writeStartElement(nodeName);
+
+ if (!node->isTextPageNode() && !node->isCollectionNode() && !node->isHeader()) {
+ if (node->threadSafeness() != Node::UnspecifiedSafeness)
+ writer.writeAttribute("threadsafety", getThreadSafenessString(node->threadSafeness()));
+ }
+
+ writer.writeAttribute("name", objName);
+
+ // Write module and base type info for QML types
+ if (!moduleNameAttr.isEmpty()) {
+ if (!logicalModuleName.isEmpty())
+ writer.writeAttribute(moduleNameAttr, logicalModuleName);
+ if (!logicalModuleVersion.isEmpty())
+ writer.writeAttribute(moduleVerAttr, logicalModuleVersion);
+ }
+ if (!baseNameAttr.isEmpty() && !qmlFullBaseName.isEmpty())
+ writer.writeAttribute(baseNameAttr, qmlFullBaseName);
+
+ QString href;
+ if (!node->isExternalPage()) {
+ QString fullName = node->fullDocumentName();
+ if (fullName != objName)
+ writer.writeAttribute("fullname", fullName);
+ href = m_gen->fullDocumentLocation(node);
+ } else
+ href = node->name();
+ if (node->isQmlNode()) {
+ Aggregate *p = node->parent();
+ if (p && p->isQmlType() && p->isAbstract())
+ href.clear();
+ }
+ if (!href.isEmpty())
+ writer.writeAttribute("href", href);
+
+ writer.writeAttribute("status", getStatusString(node->status()));
+ if (!node->isTextPageNode() && !node->isCollectionNode() && !node->isHeader()) {
+ writer.writeAttribute("access", getAccessString(node->access()));
+ if (node->isAbstract())
+ writer.writeAttribute("abstract", "true");
+ }
+ const Location &declLocation = node->declLocation();
+ if (!declLocation.fileName().isEmpty())
+ writer.writeAttribute("location", declLocation.fileName());
+ if (m_storeLocationInfo && !declLocation.filePath().isEmpty()) {
+ writer.writeAttribute("filepath", declLocation.filePath());
+ writer.writeAttribute("lineno", QString("%1").arg(declLocation.lineNo()));
+ }
+
+ if (node->isRelatedNonmember())
+ writer.writeAttribute("related", QString::number(indexForNode(node)));
+
+ if (!node->since().isEmpty())
+ writer.writeAttribute("since", node->since());
+
+ if (node->hasDoc())
+ writer.writeAttribute("documented", "true");
+
+ QStringList groups = m_qdb->groupNamesForNode(node);
+ if (!groups.isEmpty())
+ writer.writeAttribute("groups", groups.join(QLatin1Char(',')));
+
+ QString brief = node->doc().trimmedBriefText(node->name()).toString();
+ switch (node->nodeType()) {
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union: {
+ // Classes contain information about their base classes.
+ const auto *classNode = static_cast<const ClassNode *>(node);
+ const QList<RelatedClass> &bases = classNode->baseClasses();
+ QSet<QString> baseStrings;
+ for (const auto &related : bases) {
+ ClassNode *n = related.m_node;
+ if (n)
+ baseStrings.insert(n->fullName());
+ else if (!related.m_path.isEmpty())
+ baseStrings.insert(related.m_path.join(QLatin1String("::")));
+ }
+ if (!baseStrings.isEmpty()) {
+ QStringList baseStringsAsList = baseStrings.values();
+ baseStringsAsList.sort();
+ writer.writeAttribute("bases", baseStringsAsList.join(QLatin1Char(',')));
+ }
+ if (!node->physicalModuleName().isEmpty())
+ writer.writeAttribute("module", node->physicalModuleName());
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ if (auto category = node->comparisonCategory(); category != ComparisonCategory::None)
+ writer.writeAttribute("comparison_category", comparisonCategoryAsString(category));
+ } break;
+ case Node::HeaderFile: {
+ const auto *headerNode = static_cast<const HeaderNode *>(node);
+ if (!headerNode->physicalModuleName().isEmpty())
+ writer.writeAttribute("module", headerNode->physicalModuleName());
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ writer.writeAttribute("title", headerNode->title());
+ writer.writeAttribute("fulltitle", headerNode->fullTitle());
+ writer.writeAttribute("subtitle", headerNode->subtitle());
+ } break;
+ case Node::Namespace: {
+ const auto *namespaceNode = static_cast<const NamespaceNode *>(node);
+ if (!namespaceNode->physicalModuleName().isEmpty())
+ writer.writeAttribute("module", namespaceNode->physicalModuleName());
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ } break;
+ case Node::QmlValueType:
+ case Node::QmlType: {
+ const auto *qmlTypeNode = static_cast<const QmlTypeNode *>(node);
+ writer.writeAttribute("title", qmlTypeNode->title());
+ writer.writeAttribute("fulltitle", qmlTypeNode->fullTitle());
+ writer.writeAttribute("subtitle", qmlTypeNode->subtitle());
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ } break;
+ case Node::Page:
+ case Node::Example:
+ case Node::ExternalPage: {
+ if (node->isExample())
+ writer.writeAttribute("subtype", "example");
+ else if (node->isExternalPage())
+ writer.writeAttribute("subtype", "externalpage");
+ else
+ writer.writeAttribute("subtype", (static_cast<PageNode*>(node)->isAttribution() ? "attribution" : "page"));
+
+ const auto *pageNode = static_cast<const PageNode *>(node);
+ writer.writeAttribute("title", pageNode->title());
+ writer.writeAttribute("fulltitle", pageNode->fullTitle());
+ writer.writeAttribute("subtitle", pageNode->subtitle());
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ } break;
+ case Node::Group:
+ case Node::Module:
+ case Node::QmlModule: {
+ const auto *collectionNode = static_cast<const CollectionNode *>(node);
+ writer.writeAttribute("seen", collectionNode->wasSeen() ? "true" : "false");
+ writer.writeAttribute("title", collectionNode->title());
+ if (!collectionNode->subtitle().isEmpty())
+ writer.writeAttribute("subtitle", collectionNode->subtitle());
+ if (!collectionNode->physicalModuleName().isEmpty())
+ writer.writeAttribute("module", collectionNode->physicalModuleName());
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ } break;
+ case Node::QmlProperty: {
+ auto *qmlPropertyNode = static_cast<QmlPropertyNode *>(node);
+ writer.writeAttribute("type", qmlPropertyNode->dataType());
+ writer.writeAttribute("attached", qmlPropertyNode->isAttached() ? "true" : "false");
+ writer.writeAttribute("writable", qmlPropertyNode->isReadOnly() ? "false" : "true");
+ if (qmlPropertyNode->isRequired())
+ writer.writeAttribute("required", "true");
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ } break;
+ case Node::Property: {
+ const auto *propertyNode = static_cast<const PropertyNode *>(node);
+
+ if (propertyNode->propertyType() == PropertyNode::PropertyType::BindableProperty)
+ writer.writeAttribute("bindable", "true");
+
+ if (!propertyNode->isWritable())
+ writer.writeAttribute("writable", "false");
+
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ // Property access function names
+ for (qsizetype i{0}; i < (qsizetype)PropertyNode::FunctionRole::NumFunctionRoles; ++i) {
+ auto role{(PropertyNode::FunctionRole)i};
+ for (const auto *fnNode : propertyNode->functions(role)) {
+ writer.writeStartElement(PropertyNode::roleName(role));
+ writer.writeAttribute("name", fnNode->name());
+ writer.writeEndElement();
+ }
+ }
+ } break;
+ case Node::Variable: {
+ const auto *variableNode = static_cast<const VariableNode *>(node);
+ writer.writeAttribute("type", variableNode->dataType());
+ writer.writeAttribute("static", variableNode->isStatic() ? "true" : "false");
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+ } break;
+ case Node::Enum: {
+ const auto *enumNode = static_cast<const EnumNode *>(node);
+ if (enumNode->isScoped())
+ writer.writeAttribute("scoped", "true");
+ if (enumNode->flagsType())
+ writer.writeAttribute("typedef", enumNode->flagsType()->fullDocumentName());
+ const auto &items = enumNode->items();
+ for (const auto &item : items) {
+ writer.writeStartElement("value");
+ writer.writeAttribute("name", item.name());
+ writer.writeAttribute("value", item.value());
+ if (!item.since().isEmpty())
+ writer.writeAttribute("since", item.since());
+ writer.writeEndElement(); // value
+ }
+ } break;
+ case Node::Typedef: {
+ const auto *typedefNode = static_cast<const TypedefNode *>(node);
+ if (typedefNode->associatedEnum())
+ writer.writeAttribute("enum", typedefNode->associatedEnum()->fullDocumentName());
+ } break;
+ case Node::TypeAlias:
+ writer.writeAttribute("aliasedtype", static_cast<const TypeAliasNode *>(node)->aliasedType());
+ break;
+ case Node::Function: // Now processed in generateFunctionSection()
+ default:
+ break;
+ }
+
+ writeTargets(writer, node);
+
+ /*
+ Some nodes have a table of contents. For these, we close
+ the opening tag, create sub-elements for the items in the
+ table of contents, and then add a closing tag for the
+ element. Elements for all other nodes are closed in the
+ opening tag.
+ */
+ if (node->isPageNode() || node->isCollectionNode()) {
+ if (node->doc().hasTableOfContents()) {
+ for (int i = 0; i < node->doc().tableOfContents().size(); ++i) {
+ Atom *item = node->doc().tableOfContents()[i];
+ int level = node->doc().tableOfContentsLevels()[i];
+ QString title = Text::sectionHeading(item).toString();
+ writer.writeStartElement("contents");
+ writer.writeAttribute("name", Tree::refForAtom(item));
+ writer.writeAttribute("title", title);
+ writer.writeAttribute("level", QString::number(level));
+ writer.writeEndElement(); // contents
+ }
+ }
+ }
+ // WebXMLGenerator - skip the nested <page> elements for example
+ // files/images, as the generator produces them separately
+ if (node->isExample() && m_gen->format() != QLatin1String("WebXML")) {
+ const auto *exampleNode = static_cast<const ExampleNode *>(node);
+ const auto &files = exampleNode->files();
+ for (const QString &file : files) {
+ writer.writeStartElement("page");
+ writer.writeAttribute("name", file);
+ QString href = m_gen->linkForExampleFile(file);
+ writer.writeAttribute("href", href);
+ writer.writeAttribute("status", "active");
+ writer.writeAttribute("subtype", "file");
+ writer.writeAttribute("title", "");
+ writer.writeAttribute("fulltitle", Generator::exampleFileTitle(exampleNode, file));
+ writer.writeAttribute("subtitle", file);
+ writer.writeEndElement(); // page
+ }
+ const auto &images = exampleNode->images();
+ for (const QString &file : images) {
+ writer.writeStartElement("page");
+ writer.writeAttribute("name", file);
+ QString href = m_gen->linkForExampleFile(file);
+ writer.writeAttribute("href", href);
+ writer.writeAttribute("status", "active");
+ writer.writeAttribute("subtype", "image");
+ writer.writeAttribute("title", "");
+ writer.writeAttribute("fulltitle", Generator::exampleFileTitle(exampleNode, file));
+ writer.writeAttribute("subtitle", file);
+ writer.writeEndElement(); // page
+ }
+ }
+ // Append to the section if the callback object was set
+ if (post)
+ post->append(writer, node);
+
+ post_ = post;
+ return true;
+}
+
+/*!
+ This function writes a <function> element for \a fn to the
+ index file using \a writer.
+ */
+void QDocIndexFiles::generateFunctionSection(QXmlStreamWriter &writer, FunctionNode *fn)
+{
+ if (fn->isInternal() && !Config::instance().showInternal())
+ return;
+
+ const QString objName = fn->name();
+ writer.writeStartElement("function");
+ writer.writeAttribute("name", objName);
+
+ const QString fullName = fn->fullDocumentName();
+ if (fullName != objName)
+ writer.writeAttribute("fullname", fullName);
+ const QString href = m_gen->fullDocumentLocation(fn);
+ if (!href.isEmpty())
+ writer.writeAttribute("href", href);
+ if (fn->threadSafeness() != Node::UnspecifiedSafeness)
+ writer.writeAttribute("threadsafety", getThreadSafenessString(fn->threadSafeness()));
+ writer.writeAttribute("status", getStatusString(fn->status()));
+ writer.writeAttribute("access", getAccessString(fn->access()));
+
+ const Location &declLocation = fn->declLocation();
+ if (!declLocation.fileName().isEmpty())
+ writer.writeAttribute("location", declLocation.fileName());
+ if (m_storeLocationInfo && !declLocation.filePath().isEmpty()) {
+ writer.writeAttribute("filepath", declLocation.filePath());
+ writer.writeAttribute("lineno", QString("%1").arg(declLocation.lineNo()));
+ }
+
+ if (fn->hasDoc())
+ writer.writeAttribute("documented", "true");
+ if (fn->isRelatedNonmember())
+ writer.writeAttribute("related", QString::number(indexForNode(fn)));
+ if (!fn->since().isEmpty())
+ writer.writeAttribute("since", fn->since());
+
+ const QString brief = fn->doc().trimmedBriefText(fn->name()).toString();
+ writer.writeAttribute("meta", fn->metanessString());
+ if (fn->isCppNode()) {
+ if (!fn->isNonvirtual())
+ writer.writeAttribute("virtual", fn->virtualness());
+
+ if (fn->isConst())
+ writer.writeAttribute("const", "true");
+ if (fn->isStatic())
+ writer.writeAttribute("static", "true");
+ if (fn->isFinal())
+ writer.writeAttribute("final", "true");
+ if (fn->isOverride())
+ writer.writeAttribute("override", "true");
+ if (fn->isExplicit())
+ writer.writeAttribute("explicit", "true");
+ if (fn->isConstexpr())
+ writer.writeAttribute("constexpr", "true");
+
+ if (auto noexcept_info = fn->getNoexcept()) {
+ writer.writeAttribute("noexcept", "true");
+ if (!(*noexcept_info).isEmpty()) writer.writeAttribute("noexcept_expression", *noexcept_info);
+ }
+
+ /*
+ This ensures that for functions that have overloads,
+ the first function written is the one that is not an
+ overload, and the overloads follow it immediately in
+ the index file numbered from 1 to n.
+ */
+ if (fn->isOverload() && (fn->overloadNumber() > 0)) {
+ writer.writeAttribute("overload", "true");
+ writer.writeAttribute("overload-number", QString::number(fn->overloadNumber()));
+ }
+ if (fn->isRef())
+ writer.writeAttribute("refness", QString::number(1));
+ else if (fn->isRefRef())
+ writer.writeAttribute("refness", QString::number(2));
+ if (fn->hasAssociatedProperties()) {
+ QStringList associatedProperties;
+ for (const auto *node : fn->associatedProperties()) {
+ associatedProperties << node->name();
+ }
+ associatedProperties.sort();
+ writer.writeAttribute("associated-property",
+ associatedProperties.join(QLatin1Char(',')));
+ }
+ }
+
+ const auto return_type = fn->returnType();
+ if (!return_type.isEmpty())
+ writer.writeAttribute("type", return_type);
+
+ if (fn->isCppNode()) {
+ if (!brief.isEmpty())
+ writer.writeAttribute("brief", brief);
+
+ /*
+ Note: The "signature" attribute is written to the
+ index file, but it is not read back in by qdoc. However,
+ we need it for the webxml generator.
+ */
+ const QString signature = appendAttributesToSignature(fn);
+ writer.writeAttribute("signature", signature);
+
+ QStringList groups = m_qdb->groupNamesForNode(fn);
+ if (!groups.isEmpty())
+ writer.writeAttribute("groups", groups.join(QLatin1Char(',')));
+ }
+
+ for (int i = 0; i < fn->parameters().count(); ++i) {
+ const Parameter &parameter = fn->parameters().at(i);
+ writer.writeStartElement("parameter");
+ writer.writeAttribute("type", parameter.type());
+ writer.writeAttribute("name", parameter.name());
+ writer.writeAttribute("default", parameter.defaultValue());
+ writer.writeEndElement(); // parameter
+ }
+
+ writeTargets(writer, fn);
+
+ // Append to the section if the callback object was set
+ if (post_)
+ post_->append(writer, fn);
+
+ writer.writeEndElement(); // function
+}
+
+/*!
+ \internal
+
+ Constructs the signature to be written to an index file for the function
+ represented by FunctionNode \a fn.
+
+ 'const' is already part of FunctionNode::signature(), which forms the basis
+ for the signature returned by this method. The method adds, where
+ applicable, the C++ keywords "final", "override", or "= 0", to the
+ signature carried by the FunctionNode itself.
+ */
+QString QDocIndexFiles::appendAttributesToSignature(const FunctionNode *fn) const noexcept
+{
+ QString signature = fn->signature(Node::SignatureReturnType);
+
+ if (fn->isFinal())
+ signature += " final";
+ if (fn->isOverride())
+ signature += " override";
+ if (fn->isPureVirtual())
+ signature += " = 0";
+
+ return signature;
+}
+
+/*!
+ Outputs a <function> element to the index for each FunctionNode in
+ an \a aggregate, using \a writer.
+ The \a aggregate has a function map that contains all the
+ function nodes (a vector of overloads) indexed by function
+ name.
+
+ If a function element represents an overload, it has an
+ \c overload attribute set to \c true and an \c {overload-number}
+ attribute set to the function's overload number.
+ */
+void QDocIndexFiles::generateFunctionSections(QXmlStreamWriter &writer, Aggregate *aggregate)
+{
+ for (auto functions : std::as_const(aggregate->functionMap())) {
+ std::for_each(functions.begin(), functions.end(),
+ [this,&writer](FunctionNode *fn) {
+ generateFunctionSection(writer, fn);
+ }
+ );
+ }
+}
+
+/*!
+ Generate index sections for the child nodes of the given \a node
+ using the \a writer specified.
+*/
+void QDocIndexFiles::generateIndexSections(QXmlStreamWriter &writer, Node *node,
+ IndexSectionWriter *post)
+{
+ /*
+ Note that groups, modules, and QML modules are written
+ after all the other nodes.
+ */
+ if (node->isCollectionNode() || node->isGroup() || node->isModule() || node->isQmlModule())
+ return;
+
+ if (node->isInternal() && !Config::instance().showInternal())
+ return;
+
+ if (generateIndexSection(writer, node, post)) {
+ if (node->isAggregate()) {
+ auto *aggregate = static_cast<Aggregate *>(node);
+ // First write the function children, then write the nonfunction children.
+ generateFunctionSections(writer, aggregate);
+ const auto &nonFunctionList = aggregate->nonfunctionList();
+ for (auto *node : nonFunctionList)
+ generateIndexSections(writer, node, post);
+ }
+
+ if (node == root_) {
+ /*
+ We wait until the end of the index file to output the group, module,
+ and QML module elements. By outputting them at the end, when we read
+ the index file back in, all the group, module, and QML module member
+ elements will have already been created. It is then only necessary to
+ create the group, module, or QML module element and add each member to
+ its member list.
+ */
+ const CNMap &groups = m_qdb->groups();
+ if (!groups.isEmpty()) {
+ for (auto it = groups.constBegin(); it != groups.constEnd(); ++it) {
+ if (generateIndexSection(writer, it.value(), post))
+ writer.writeEndElement();
+ }
+ }
+
+ const CNMap &modules = m_qdb->modules();
+ if (!modules.isEmpty()) {
+ for (auto it = modules.constBegin(); it != modules.constEnd(); ++it) {
+ if (generateIndexSection(writer, it.value(), post))
+ writer.writeEndElement();
+ }
+ }
+
+ const CNMap &qmlModules = m_qdb->qmlModules();
+ if (!qmlModules.isEmpty()) {
+ for (auto it = qmlModules.constBegin(); it != qmlModules.constEnd(); ++it) {
+ if (generateIndexSection(writer, it.value(), post))
+ writer.writeEndElement();
+ }
+ }
+ }
+
+ writer.writeEndElement();
+ }
+}
+
+/*!
+ Writes a qdoc module index in XML to a file named \a fileName.
+ \a url is the \c url attribute of the <INDEX> element.
+ \a title is the \c title attribute of the <INDEX> element.
+ \a g is a pointer to the current Generator in use, stored for later use.
+ */
+void QDocIndexFiles::generateIndex(const QString &fileName, const QString &url,
+ const QString &title, Generator *g)
+{
+ QFile file(fileName);
+ if (!file.open(QFile::WriteOnly | QFile::Text))
+ return;
+
+ qCDebug(lcQdoc) << "Writing index file:" << fileName;
+
+ m_gen = g;
+ m_relatedNodes.clear();
+ QXmlStreamWriter writer(&file);
+ writer.setAutoFormatting(true);
+ writer.writeStartDocument();
+ writer.writeDTD("<!DOCTYPE QDOCINDEX>");
+
+ writer.writeStartElement("INDEX");
+ writer.writeAttribute("url", url);
+ writer.writeAttribute("title", title);
+ writer.writeAttribute("version", m_qdb->version());
+ writer.writeAttribute("project", Config::instance().get(CONFIG_PROJECT).asString());
+
+ root_ = m_qdb->primaryTreeRoot();
+ if (!root_->tree()->indexTitle().isEmpty())
+ writer.writeAttribute("indexTitle", root_->tree()->indexTitle());
+
+ generateIndexSections(writer, root_, nullptr);
+
+ writer.writeEndElement(); // INDEX
+ writer.writeEndElement(); // QDOCINDEX
+ writer.writeEndDocument();
+ file.close();
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/qdocindexfiles.h b/src/qdoc/qdoc/src/qdoc/qdocindexfiles.h
new file mode 100644
index 000000000..2225aa576
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qdocindexfiles.h
@@ -0,0 +1,73 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QDOCINDEXFILES_H
+#define QDOCINDEXFILES_H
+
+#include "node.h"
+#include "tree.h"
+
+QT_BEGIN_NAMESPACE
+
+class Atom;
+class FunctionNode;
+class Generator;
+class QDocDatabase;
+class WebXMLGenerator;
+class QXmlStreamReader;
+class QXmlStreamWriter;
+class QXmlStreamAttributes;
+
+// A callback interface for extending index sections
+class IndexSectionWriter
+{
+public:
+ virtual ~IndexSectionWriter() = default;
+ virtual void append(QXmlStreamWriter &writer, Node *node) = 0;
+};
+
+class QDocIndexFiles
+{
+ friend class QDocDatabase;
+ friend class WebXMLGenerator; // for using generateIndexSections()
+
+private:
+ static QDocIndexFiles *qdocIndexFiles();
+ static void destroyQDocIndexFiles();
+
+ QDocIndexFiles();
+ ~QDocIndexFiles();
+
+ void readIndexes(const QStringList &indexFiles);
+ void readIndexFile(const QString &path);
+ void readIndexSection(QXmlStreamReader &reader, Node *current, const QString &indexUrl);
+ void insertTarget(TargetRec::TargetType type, const QXmlStreamAttributes &attributes,
+ Node *node);
+ void resolveIndex();
+ int indexForNode(Node *node);
+ bool adoptRelatedNode(Aggregate *adoptiveParent, int index);
+ void writeTargets(QXmlStreamWriter &writer, Node *node);
+
+ void generateIndex(const QString &fileName, const QString &url, const QString &title,
+ Generator *g);
+ void generateFunctionSection(QXmlStreamWriter &writer, FunctionNode *fn);
+ void generateFunctionSections(QXmlStreamWriter &writer, Aggregate *aggregate);
+ bool generateIndexSection(QXmlStreamWriter &writer, Node *node,
+ IndexSectionWriter *post = nullptr);
+ void generateIndexSections(QXmlStreamWriter &writer, Node *node,
+ IndexSectionWriter *post = nullptr);
+ QString appendAttributesToSignature(const FunctionNode *fn) const noexcept;
+
+private:
+ static QDocIndexFiles *s_qdocIndexFiles;
+ QDocDatabase *m_qdb {};
+ Generator *m_gen {};
+ QString m_project;
+ QList<std::pair<ClassNode *, QString>> m_basesList;
+ NodeList m_relatedNodes;
+ bool m_storeLocationInfo;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/qmlcodemarker.cpp b/src/qdoc/qdoc/src/qdoc/qmlcodemarker.cpp
new file mode 100644
index 000000000..30dec979e
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlcodemarker.cpp
@@ -0,0 +1,175 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qmlcodemarker.h"
+
+#include <QtCore/qregularexpression.h>
+
+#include "atom.h"
+#include "node.h"
+#include "qmlmarkupvisitor.h"
+#include "text.h"
+
+#include <private/qqmljsast_p.h>
+#include <private/qqmljsastfwd_p.h>
+#include <private/qqmljsengine_p.h>
+#include <private/qqmljslexer_p.h>
+#include <private/qqmljsparser_p.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ Returns \c true if the \a code is recognized by the parser.
+ */
+bool QmlCodeMarker::recognizeCode(const QString &code)
+{
+ // Naive pre-check; starts with an import statement or 'CamelCase {'
+ static const QRegularExpression regExp(QStringLiteral("^\\s*(import |([A-Z][a-z0-9]*)+\\s?{)"));
+ if (!regExp.match(code).hasMatch())
+ return false;
+
+ QQmlJS::Engine engine;
+ QQmlJS::Lexer lexer(&engine);
+ QQmlJS::Parser parser(&engine);
+
+ QString newCode = code;
+ extractPragmas(newCode);
+ lexer.setCode(newCode, 1);
+
+ return parser.parse();
+}
+
+/*!
+ Returns \c true if \a ext is any of a list of file extensions
+ for the QML language.
+ */
+bool QmlCodeMarker::recognizeExtension(const QString &ext)
+{
+ return ext == "qml";
+}
+
+/*!
+ Returns \c true if the \a language is recognized. Only "QML" is
+ recognized by this marker.
+ */
+bool QmlCodeMarker::recognizeLanguage(const QString &language)
+{
+ return language == "QML";
+}
+
+/*!
+ Returns the type of atom used to represent QML code in the documentation.
+*/
+Atom::AtomType QmlCodeMarker::atomType() const
+{
+ return Atom::Qml;
+}
+
+QString QmlCodeMarker::markedUpCode(const QString &code, const Node *relative,
+ const Location &location)
+{
+ return addMarkUp(code, relative, location);
+}
+
+/*!
+ Constructs and returns the marked up name for the \a node.
+ If the node is any kind of QML function (a method,
+ signal, or handler), "()" is appended to the marked up name.
+ */
+QString QmlCodeMarker::markedUpName(const Node *node)
+{
+ QString name = linkTag(node, taggedNode(node));
+ if (node->isFunction())
+ name += "()";
+ return name;
+}
+
+QString QmlCodeMarker::markedUpInclude(const QString &include)
+{
+ return addMarkUp("import " + include, nullptr, Location{});
+}
+
+QString QmlCodeMarker::addMarkUp(const QString &code, const Node * /* relative */,
+ const Location &location)
+{
+ QQmlJS::Engine engine;
+ QQmlJS::Lexer lexer(&engine);
+
+ QString newCode = code;
+ QList<QQmlJS::SourceLocation> pragmas = extractPragmas(newCode);
+ lexer.setCode(newCode, 1);
+
+ QQmlJS::Parser parser(&engine);
+ QString output;
+
+ if (parser.parse()) {
+ QQmlJS::AST::UiProgram *ast = parser.ast();
+ // Pass the unmodified code to the visitor so that pragmas and other
+ // unhandled source text can be output.
+ QmlMarkupVisitor visitor(code, pragmas, &engine);
+ QQmlJS::AST::Node::accept(ast, &visitor);
+ if (visitor.hasError()) {
+ location.warning(
+ location.fileName()
+ + QStringLiteral("Unable to analyze QML snippet. The output is incomplete."));
+ }
+ output = visitor.markedUpCode();
+ } else {
+ location.warning(QStringLiteral("Unable to parse QML snippet: \"%1\" at line %2, column %3")
+ .arg(parser.errorMessage())
+ .arg(parser.errorLineNumber())
+ .arg(parser.errorColumnNumber()));
+ output = protect(code);
+ }
+
+ return output;
+}
+
+/*
+ Copied and pasted from
+ src/declarative/qml/qqmlscriptparser.cpp.
+*/
+void replaceWithSpace(QString &str, int idx, int n); // qmlcodeparser.cpp
+
+/*
+ Copied and pasted from
+ src/declarative/qml/qqmlscriptparser.cpp then modified to
+ return a list of removed pragmas.
+
+ Searches for ".pragma <value>" or ".import <stuff>" declarations
+ in \a script. Currently supported pragmas are: library
+*/
+QList<QQmlJS::SourceLocation> QmlCodeMarker::extractPragmas(QString &script)
+{
+ QList<QQmlJS::SourceLocation> removed;
+
+ QQmlJS::Lexer l(nullptr);
+ l.setCode(script, 0);
+
+ int token = l.lex();
+
+ while (true) {
+ if (token != QQmlJSGrammar::T_DOT)
+ break;
+
+ int startOffset = l.tokenOffset();
+ int startLine = l.tokenStartLine();
+ int startColumn = l.tokenStartColumn();
+
+ token = l.lex();
+
+ if (token != QQmlJSGrammar::T_PRAGMA && token != QQmlJSGrammar::T_IMPORT)
+ break;
+ int endOffset = 0;
+ while (startLine == l.tokenStartLine()) {
+ endOffset = l.tokenLength() + l.tokenOffset();
+ token = l.lex();
+ }
+ replaceWithSpace(script, startOffset, endOffset - startOffset);
+ removed.append(QQmlJS::SourceLocation(startOffset, endOffset - startOffset, startLine,
+ startColumn));
+ }
+ return removed;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/qmlcodemarker.h b/src/qdoc/qdoc/src/qdoc/qmlcodemarker.h
new file mode 100644
index 000000000..64a1f7c9f
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlcodemarker.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QMLCODEMARKER_H
+#define QMLCODEMARKER_H
+
+#include "cppcodemarker.h"
+
+#include <private/qqmljsastfwd_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QmlCodeMarker : public CppCodeMarker
+{
+public:
+ QmlCodeMarker() = default;
+ ~QmlCodeMarker() override = default;
+
+ bool recognizeCode(const QString &code) override;
+ bool recognizeExtension(const QString &ext) override;
+ bool recognizeLanguage(const QString &language) override;
+ [[nodiscard]] Atom::AtomType atomType() const override;
+ QString markedUpCode(const QString &code, const Node *relative,
+ const Location &location) override;
+
+ QString markedUpName(const Node *node) override;
+ QString markedUpInclude(const QString &include) override;
+
+ /* Copied from src/declarative/qml/qdeclarativescriptparser.cpp */
+ QList<QQmlJS::SourceLocation> extractPragmas(QString &script);
+
+private:
+ QString addMarkUp(const QString &code, const Node *relative, const Location &location);
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/qmlcodeparser.cpp b/src/qdoc/qdoc/src/qdoc/qmlcodeparser.cpp
new file mode 100644
index 000000000..fadd7c307
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlcodeparser.cpp
@@ -0,0 +1,143 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qmlcodeparser.h"
+
+#include "node.h"
+#include "qmlvisitor.h"
+#include "utilities.h"
+
+#include <private/qqmljsast_p.h>
+
+#include <qdebug.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ Returns "QML".
+ */
+QString QmlCodeParser::language()
+{
+ return "QML";
+}
+
+/*!
+ Returns a string list containing "*.qml". This is the only
+ file type parsed by the QMLN parser.
+ */
+QStringList QmlCodeParser::sourceFileNameFilter()
+{
+ return QStringList() << "*.qml";
+}
+
+/*!
+ Parses the source file at \a filePath and inserts the contents
+ into the database. The \a location is used for error reporting.
+
+ If it can't open the file at \a filePath, it reports an error
+ and returns without doing anything.
+ */
+void QmlCodeParser::parseSourceFile(const Location &location, const QString &filePath, CppCodeParser&)
+{
+ static const QSet<QString> topic_commands{
+ COMMAND_VARIABLE, COMMAND_QMLCLASS, COMMAND_QMLTYPE, COMMAND_QMLPROPERTY,
+ COMMAND_QMLPROPERTYGROUP, COMMAND_QMLATTACHEDPROPERTY, COMMAND_QMLSIGNAL,
+ COMMAND_QMLATTACHEDSIGNAL, COMMAND_QMLMETHOD, COMMAND_QMLATTACHEDMETHOD,
+ COMMAND_QMLVALUETYPE, COMMAND_QMLBASICTYPE,
+ };
+
+ QFile in(filePath);
+ if (!in.open(QIODevice::ReadOnly)) {
+ location.error(QStringLiteral("Cannot open QML file '%1'").arg(filePath));
+ return;
+ }
+
+ QString document = in.readAll();
+ in.close();
+
+ QString newCode = document;
+ extractPragmas(newCode);
+
+ QQmlJS::Engine engine{};
+ QQmlJS::Lexer lexer{&engine};
+ lexer.setCode(newCode, 1);
+
+ QQmlJS::Parser parser{&engine};
+
+ if (parser.parse()) {
+ QQmlJS::AST::UiProgram *ast = parser.ast();
+ QmlDocVisitor visitor(filePath, newCode, &engine, topic_commands + CodeParser::common_meta_commands,
+ topic_commands);
+ QQmlJS::AST::Node::accept(ast, &visitor);
+ if (visitor.hasError())
+ Location(filePath).warning("Could not analyze QML file, output is incomplete.");
+ }
+ const auto &messages = parser.diagnosticMessages();
+ for (const auto &msg : messages) {
+ qCDebug(lcQdoc, "%s: %d: %d: QML syntax error: %s", qUtf8Printable(filePath),
+ msg.loc.startLine, msg.loc.startColumn, qUtf8Printable(msg.message));
+ }
+}
+
+/*!
+ Copy and paste from src/declarative/qml/qdeclarativescriptparser.cpp.
+ This function blanks out the section of the \a str beginning at \a idx
+ and running for \a n characters.
+*/
+void replaceWithSpace(QString &str, int idx, int n) // Also used in qmlcodemarker.cpp.
+{
+ QChar *data = str.data() + idx;
+ const QChar space(QLatin1Char(' '));
+ for (int ii = 0; ii < n; ++ii)
+ *data++ = space;
+}
+
+/*!
+ Copy & paste from src/declarative/qml/qdeclarativescriptparser.cpp,
+ then modified to return no values.
+
+ Searches for ".pragma <value>" declarations within \a script.
+ Currently supported pragmas are: library
+*/
+void QmlCodeParser::extractPragmas(QString &script)
+{
+ const QString pragma(QLatin1String("pragma"));
+
+ QQmlJS::Lexer l(nullptr);
+ l.setCode(script, 0);
+
+ int token = l.lex();
+
+ while (true) {
+ if (token != QQmlJSGrammar::T_DOT)
+ return;
+
+ int startOffset = l.tokenOffset();
+ int startLine = l.tokenStartLine();
+
+ token = l.lex();
+
+ if (token != QQmlJSGrammar::T_IDENTIFIER || l.tokenStartLine() != startLine
+ || script.mid(l.tokenOffset(), l.tokenLength()) != pragma)
+ return;
+
+ token = l.lex();
+
+ if (token != QQmlJSGrammar::T_IDENTIFIER || l.tokenStartLine() != startLine)
+ return;
+
+ QString pragmaValue = script.mid(l.tokenOffset(), l.tokenLength());
+ int endOffset = l.tokenLength() + l.tokenOffset();
+
+ token = l.lex();
+ if (l.tokenStartLine() == startLine)
+ return;
+
+ if (pragmaValue == QLatin1String("library"))
+ replaceWithSpace(script, startOffset, endOffset - startOffset);
+ else
+ return;
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/qmlcodeparser.h b/src/qdoc/qdoc/src/qdoc/qmlcodeparser.h
new file mode 100644
index 000000000..dff493be4
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlcodeparser.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QMLCODEPARSER_H
+#define QMLCODEPARSER_H
+
+#include "codeparser.h"
+
+#include <QtCore/qset.h>
+
+#include <private/qqmljsengine_p.h>
+#include <private/qqmljslexer_p.h>
+#include <private/qqmljsparser_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Node;
+class QString;
+
+class QmlCodeParser : public CodeParser
+{
+public:
+ QmlCodeParser() = default;
+ ~QmlCodeParser() override = default;
+
+ void initializeParser() override {}
+ void terminateParser() override {}
+ QString language() override;
+ QStringList sourceFileNameFilter() override;
+ void parseSourceFile(const Location &location, const QString &filePath, CppCodeParser&) override;
+
+ /* Copied from src/declarative/qml/qdeclarativescriptparser.cpp */
+ void extractPragmas(QString &script);
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.cpp b/src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.cpp
new file mode 100644
index 000000000..31adb838d
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.cpp
@@ -0,0 +1,794 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qmlmarkupvisitor.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstringlist.h>
+
+#include <private/qqmljsast_p.h>
+#include <private/qqmljsengine_p.h>
+
+QT_BEGIN_NAMESPACE
+
+QmlMarkupVisitor::QmlMarkupVisitor(const QString &source,
+ const QList<QQmlJS::SourceLocation> &pragmas,
+ QQmlJS::Engine *engine)
+{
+ this->m_source = source;
+ this->m_engine = engine;
+
+ m_cursor = 0;
+ m_extraIndex = 0;
+
+ // Merge the lists of locations of pragmas and comments in the source code.
+ int i = 0;
+ int j = 0;
+ const QList<QQmlJS::SourceLocation> comments = engine->comments();
+ while (i < comments.size() && j < pragmas.size()) {
+ if (comments[i].offset < pragmas[j].offset) {
+ m_extraTypes.append(Comment);
+ m_extraLocations.append(comments[i]);
+ ++i;
+ } else {
+ m_extraTypes.append(Pragma);
+ m_extraLocations.append(comments[j]);
+ ++j;
+ }
+ }
+
+ while (i < comments.size()) {
+ m_extraTypes.append(Comment);
+ m_extraLocations.append(comments[i]);
+ ++i;
+ }
+
+ while (j < pragmas.size()) {
+ m_extraTypes.append(Pragma);
+ m_extraLocations.append(pragmas[j]);
+ ++j;
+ }
+}
+
+// The protect() function is a copy of the one from CppCodeMarker.
+
+static const QString samp = QLatin1String("&amp;");
+static const QString slt = QLatin1String("&lt;");
+static const QString sgt = QLatin1String("&gt;");
+static const QString squot = QLatin1String("&quot;");
+
+QString QmlMarkupVisitor::protect(const QString &str)
+{
+ qsizetype n = str.size();
+ QString marked;
+ marked.reserve(n * 2 + 30);
+ const QChar *data = str.constData();
+ for (int i = 0; i != n; ++i) {
+ switch (data[i].unicode()) {
+ case '&':
+ marked += samp;
+ break;
+ case '<':
+ marked += slt;
+ break;
+ case '>':
+ marked += sgt;
+ break;
+ case '"':
+ marked += squot;
+ break;
+ default:
+ marked += data[i];
+ }
+ }
+ return marked;
+}
+
+QString QmlMarkupVisitor::markedUpCode()
+{
+ if (int(m_cursor) < m_source.size())
+ addExtra(m_cursor, m_source.size());
+
+ return m_output;
+}
+
+bool QmlMarkupVisitor::hasError() const
+{
+ return m_hasRecursionDepthError;
+}
+
+void QmlMarkupVisitor::addExtra(quint32 start, quint32 finish)
+{
+ if (m_extraIndex >= m_extraLocations.size()) {
+ QString extra = m_source.mid(start, finish - start);
+ if (extra.trimmed().isEmpty())
+ m_output += extra;
+ else
+ m_output += protect(extra); // text that should probably have been caught by the parser
+
+ m_cursor = finish;
+ return;
+ }
+
+ while (m_extraIndex < m_extraLocations.size()) {
+ if (m_extraTypes[m_extraIndex] == Comment) {
+ if (m_extraLocations[m_extraIndex].offset - 2 >= start)
+ break;
+ } else {
+ if (m_extraLocations[m_extraIndex].offset >= start)
+ break;
+ }
+ m_extraIndex++;
+ }
+
+ quint32 i = start;
+ while (i < finish && m_extraIndex < m_extraLocations.size()) {
+ quint32 j = m_extraLocations[m_extraIndex].offset - 2;
+ if (i <= j && j < finish) {
+ if (i < j)
+ m_output += protect(m_source.mid(i, j - i));
+
+ quint32 l = m_extraLocations[m_extraIndex].length;
+ if (m_extraTypes[m_extraIndex] == Comment) {
+ if (m_source.mid(j, 2) == QLatin1String("/*"))
+ l += 4;
+ else
+ l += 2;
+ m_output += QLatin1String("<@comment>");
+ m_output += protect(m_source.mid(j, l));
+ m_output += QLatin1String("</@comment>");
+ } else
+ m_output += protect(m_source.mid(j, l));
+
+ m_extraIndex++;
+ i = j + l;
+ } else
+ break;
+ }
+
+ QString extra = m_source.mid(i, finish - i);
+ if (extra.trimmed().isEmpty())
+ m_output += extra;
+ else
+ m_output += protect(extra); // text that should probably have been caught by the parser
+
+ m_cursor = finish;
+}
+
+void QmlMarkupVisitor::addMarkedUpToken(QQmlJS::SourceLocation &location,
+ const QString &tagName,
+ const QHash<QString, QString> &attributes)
+{
+ if (!location.isValid())
+ return;
+
+ if (m_cursor < location.offset)
+ addExtra(m_cursor, location.offset);
+ else if (m_cursor > location.offset)
+ return;
+
+ m_output += QString(QLatin1String("<@%1")).arg(tagName);
+ for (const auto &key : attributes)
+ m_output += QString(QLatin1String(" %1=\"%2\"")).arg(key, attributes[key]);
+ m_output += QString(QLatin1String(">%2</@%3>")).arg(protect(sourceText(location)), tagName);
+ m_cursor += location.length;
+}
+
+QString QmlMarkupVisitor::sourceText(QQmlJS::SourceLocation &location)
+{
+ return m_source.mid(location.offset, location.length);
+}
+
+void QmlMarkupVisitor::addVerbatim(QQmlJS::SourceLocation first,
+ QQmlJS::SourceLocation last)
+{
+ if (!first.isValid())
+ return;
+
+ quint32 start = first.begin();
+ quint32 finish;
+ if (last.isValid())
+ finish = last.end();
+ else
+ finish = first.end();
+
+ if (m_cursor < start)
+ addExtra(m_cursor, start);
+ else if (m_cursor > start)
+ return;
+
+ QString text = m_source.mid(start, finish - start);
+ m_output += protect(text);
+ m_cursor = finish;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiImport *uiimport)
+{
+ addVerbatim(uiimport->importToken);
+ if (!uiimport->importUri)
+ addMarkedUpToken(uiimport->fileNameToken, QLatin1String("headerfile"));
+ return false;
+}
+
+void QmlMarkupVisitor::endVisit(QQmlJS::AST::UiImport *uiimport)
+{
+ if (uiimport->version)
+ addVerbatim(uiimport->version->firstSourceLocation(),
+ uiimport->version->lastSourceLocation());
+ addVerbatim(uiimport->asToken);
+ addMarkedUpToken(uiimport->importIdToken, QLatin1String("headerfile"));
+ addVerbatim(uiimport->semicolonToken);
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiPublicMember *member)
+{
+ if (member->type == QQmlJS::AST::UiPublicMember::Property) {
+ addVerbatim(member->defaultToken());
+ addVerbatim(member->readonlyToken());
+ addVerbatim(member->propertyToken());
+ addVerbatim(member->typeModifierToken);
+ addMarkedUpToken(member->typeToken, QLatin1String("type"));
+ addMarkedUpToken(member->identifierToken, QLatin1String("name"));
+ addVerbatim(member->colonToken);
+ if (member->binding)
+ QQmlJS::AST::Node::accept(member->binding, this);
+ else if (member->statement)
+ QQmlJS::AST::Node::accept(member->statement, this);
+ } else {
+ addVerbatim(member->propertyToken());
+ addVerbatim(member->typeModifierToken);
+ addMarkedUpToken(member->typeToken, QLatin1String("type"));
+ // addVerbatim(member->identifierToken());
+ QQmlJS::AST::Node::accept(member->parameters, this);
+ }
+ addVerbatim(member->semicolonToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiObjectInitializer *initializer)
+{
+ addVerbatim(initializer->lbraceToken, initializer->lbraceToken);
+ return true;
+}
+
+void QmlMarkupVisitor::endVisit(QQmlJS::AST::UiObjectInitializer *initializer)
+{
+ addVerbatim(initializer->rbraceToken, initializer->rbraceToken);
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiObjectBinding *binding)
+{
+ QQmlJS::AST::Node::accept(binding->qualifiedId, this);
+ addVerbatim(binding->colonToken);
+ QQmlJS::AST::Node::accept(binding->qualifiedTypeNameId, this);
+ QQmlJS::AST::Node::accept(binding->initializer, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiScriptBinding *binding)
+{
+ QQmlJS::AST::Node::accept(binding->qualifiedId, this);
+ addVerbatim(binding->colonToken);
+ QQmlJS::AST::Node::accept(binding->statement, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiArrayBinding *binding)
+{
+ QQmlJS::AST::Node::accept(binding->qualifiedId, this);
+ addVerbatim(binding->colonToken);
+ addVerbatim(binding->lbracketToken);
+ QQmlJS::AST::Node::accept(binding->members, this);
+ addVerbatim(binding->rbracketToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiArrayMemberList *list)
+{
+ for (QQmlJS::AST::UiArrayMemberList *it = list; it; it = it->next) {
+ QQmlJS::AST::Node::accept(it->member, this);
+ // addVerbatim(it->commaToken);
+ }
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiQualifiedId *id)
+{
+ addMarkedUpToken(id->identifierToken, QLatin1String("name"));
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ThisExpression *expression)
+{
+ addVerbatim(expression->thisToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::IdentifierExpression *identifier)
+{
+ addMarkedUpToken(identifier->identifierToken, QLatin1String("name"));
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::NullExpression *null)
+{
+ addMarkedUpToken(null->nullToken, QLatin1String("number"));
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::TrueLiteral *literal)
+{
+ addMarkedUpToken(literal->trueToken, QLatin1String("number"));
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::FalseLiteral *literal)
+{
+ addMarkedUpToken(literal->falseToken, QLatin1String("number"));
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::NumericLiteral *literal)
+{
+ addMarkedUpToken(literal->literalToken, QLatin1String("number"));
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::StringLiteral *literal)
+{
+ addMarkedUpToken(literal->literalToken, QLatin1String("string"));
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::RegExpLiteral *literal)
+{
+ addVerbatim(literal->literalToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ArrayPattern *literal)
+{
+ addVerbatim(literal->lbracketToken);
+ QQmlJS::AST::Node::accept(literal->elements, this);
+ addVerbatim(literal->rbracketToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ObjectPattern *literal)
+{
+ addVerbatim(literal->lbraceToken);
+ return true;
+}
+
+void QmlMarkupVisitor::endVisit(QQmlJS::AST::ObjectPattern *literal)
+{
+ addVerbatim(literal->rbraceToken);
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::PatternElementList *list)
+{
+ for (QQmlJS::AST::PatternElementList *it = list; it; it = it->next) {
+ QQmlJS::AST::Node::accept(it->element, this);
+ // addVerbatim(it->commaToken);
+ }
+ QQmlJS::AST::Node::accept(list->elision, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::Elision *elision)
+{
+ addVerbatim(elision->commaToken, elision->commaToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::PatternProperty *list)
+{
+ QQmlJS::AST::Node::accept(list->name, this);
+ addVerbatim(list->colonToken, list->colonToken);
+ QQmlJS::AST::Node::accept(list->initializer, this);
+ // addVerbatim(list->commaToken, list->commaToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ArrayMemberExpression *expression)
+{
+ QQmlJS::AST::Node::accept(expression->base, this);
+ addVerbatim(expression->lbracketToken);
+ QQmlJS::AST::Node::accept(expression->expression, this);
+ addVerbatim(expression->rbracketToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::FieldMemberExpression *expression)
+{
+ QQmlJS::AST::Node::accept(expression->base, this);
+ addVerbatim(expression->dotToken);
+ addMarkedUpToken(expression->identifierToken, QLatin1String("name"));
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::NewMemberExpression *expression)
+{
+ addVerbatim(expression->newToken);
+ QQmlJS::AST::Node::accept(expression->base, this);
+ addVerbatim(expression->lparenToken);
+ QQmlJS::AST::Node::accept(expression->arguments, this);
+ addVerbatim(expression->rparenToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::NewExpression *expression)
+{
+ addVerbatim(expression->newToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ArgumentList *list)
+{
+ addVerbatim(list->commaToken, list->commaToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::PostIncrementExpression *expression)
+{
+ addVerbatim(expression->incrementToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::PostDecrementExpression *expression)
+{
+ addVerbatim(expression->decrementToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::DeleteExpression *expression)
+{
+ addVerbatim(expression->deleteToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::VoidExpression *expression)
+{
+ addVerbatim(expression->voidToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::TypeOfExpression *expression)
+{
+ addVerbatim(expression->typeofToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::PreIncrementExpression *expression)
+{
+ addVerbatim(expression->incrementToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::PreDecrementExpression *expression)
+{
+ addVerbatim(expression->decrementToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UnaryPlusExpression *expression)
+{
+ addVerbatim(expression->plusToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UnaryMinusExpression *expression)
+{
+ addVerbatim(expression->minusToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::TildeExpression *expression)
+{
+ addVerbatim(expression->tildeToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::NotExpression *expression)
+{
+ addVerbatim(expression->notToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::BinaryExpression *expression)
+{
+ QQmlJS::AST::Node::accept(expression->left, this);
+ addMarkedUpToken(expression->operatorToken, QLatin1String("op"));
+ QQmlJS::AST::Node::accept(expression->right, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ConditionalExpression *expression)
+{
+ QQmlJS::AST::Node::accept(expression->expression, this);
+ addVerbatim(expression->questionToken);
+ QQmlJS::AST::Node::accept(expression->ok, this);
+ addVerbatim(expression->colonToken);
+ QQmlJS::AST::Node::accept(expression->ko, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::Expression *expression)
+{
+ QQmlJS::AST::Node::accept(expression->left, this);
+ addVerbatim(expression->commaToken);
+ QQmlJS::AST::Node::accept(expression->right, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::Block *block)
+{
+ addVerbatim(block->lbraceToken);
+ return true;
+}
+
+void QmlMarkupVisitor::endVisit(QQmlJS::AST::Block *block)
+{
+ addVerbatim(block->rbraceToken);
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::VariableStatement *statement)
+{
+ addVerbatim(statement->declarationKindToken);
+ QQmlJS::AST::Node::accept(statement->declarations, this);
+ // addVerbatim(statement->semicolonToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::VariableDeclarationList *list)
+{
+ for (QQmlJS::AST::VariableDeclarationList *it = list; it; it = it->next) {
+ QQmlJS::AST::Node::accept(it->declaration, this);
+ addVerbatim(it->commaToken);
+ }
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::EmptyStatement *statement)
+{
+ addVerbatim(statement->semicolonToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ExpressionStatement *statement)
+{
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->semicolonToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::IfStatement *statement)
+{
+ addMarkedUpToken(statement->ifToken, QLatin1String("keyword"));
+ addVerbatim(statement->lparenToken);
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->rparenToken);
+ QQmlJS::AST::Node::accept(statement->ok, this);
+ if (statement->ko) {
+ addMarkedUpToken(statement->elseToken, QLatin1String("keyword"));
+ QQmlJS::AST::Node::accept(statement->ko, this);
+ }
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::DoWhileStatement *statement)
+{
+ addMarkedUpToken(statement->doToken, QLatin1String("keyword"));
+ QQmlJS::AST::Node::accept(statement->statement, this);
+ addMarkedUpToken(statement->whileToken, QLatin1String("keyword"));
+ addVerbatim(statement->lparenToken);
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->rparenToken);
+ addVerbatim(statement->semicolonToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::WhileStatement *statement)
+{
+ addMarkedUpToken(statement->whileToken, QLatin1String("keyword"));
+ addVerbatim(statement->lparenToken);
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->rparenToken);
+ QQmlJS::AST::Node::accept(statement->statement, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ForStatement *statement)
+{
+ addMarkedUpToken(statement->forToken, QLatin1String("keyword"));
+ addVerbatim(statement->lparenToken);
+ QQmlJS::AST::Node::accept(statement->initialiser, this);
+ addVerbatim(statement->firstSemicolonToken);
+ QQmlJS::AST::Node::accept(statement->condition, this);
+ addVerbatim(statement->secondSemicolonToken);
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->rparenToken);
+ QQmlJS::AST::Node::accept(statement->statement, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ForEachStatement *statement)
+{
+ addMarkedUpToken(statement->forToken, QLatin1String("keyword"));
+ addVerbatim(statement->lparenToken);
+ QQmlJS::AST::Node::accept(statement->lhs, this);
+ addVerbatim(statement->inOfToken);
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->rparenToken);
+ QQmlJS::AST::Node::accept(statement->statement, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ContinueStatement *statement)
+{
+ addMarkedUpToken(statement->continueToken, QLatin1String("keyword"));
+ addMarkedUpToken(statement->identifierToken, QLatin1String("name"));
+ addVerbatim(statement->semicolonToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::BreakStatement *statement)
+{
+ addMarkedUpToken(statement->breakToken, QLatin1String("keyword"));
+ addMarkedUpToken(statement->identifierToken, QLatin1String("name"));
+ addVerbatim(statement->semicolonToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ReturnStatement *statement)
+{
+ addMarkedUpToken(statement->returnToken, QLatin1String("keyword"));
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->semicolonToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::WithStatement *statement)
+{
+ addMarkedUpToken(statement->withToken, QLatin1String("keyword"));
+ addVerbatim(statement->lparenToken);
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->rparenToken);
+ QQmlJS::AST::Node::accept(statement->statement, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::CaseBlock *block)
+{
+ addVerbatim(block->lbraceToken);
+ return true;
+}
+
+void QmlMarkupVisitor::endVisit(QQmlJS::AST::CaseBlock *block)
+{
+ addVerbatim(block->rbraceToken, block->rbraceToken);
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::SwitchStatement *statement)
+{
+ addMarkedUpToken(statement->switchToken, QLatin1String("keyword"));
+ addVerbatim(statement->lparenToken);
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->rparenToken);
+ QQmlJS::AST::Node::accept(statement->block, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::CaseClause *clause)
+{
+ addMarkedUpToken(clause->caseToken, QLatin1String("keyword"));
+ QQmlJS::AST::Node::accept(clause->expression, this);
+ addVerbatim(clause->colonToken);
+ QQmlJS::AST::Node::accept(clause->statements, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::DefaultClause *clause)
+{
+ addMarkedUpToken(clause->defaultToken, QLatin1String("keyword"));
+ addVerbatim(clause->colonToken, clause->colonToken);
+ return true;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::LabelledStatement *statement)
+{
+ addMarkedUpToken(statement->identifierToken, QLatin1String("name"));
+ addVerbatim(statement->colonToken);
+ QQmlJS::AST::Node::accept(statement->statement, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::ThrowStatement *statement)
+{
+ addMarkedUpToken(statement->throwToken, QLatin1String("keyword"));
+ QQmlJS::AST::Node::accept(statement->expression, this);
+ addVerbatim(statement->semicolonToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::Catch *c)
+{
+ addMarkedUpToken(c->catchToken, QLatin1String("keyword"));
+ addVerbatim(c->lparenToken);
+ addMarkedUpToken(c->identifierToken, QLatin1String("name"));
+ addVerbatim(c->rparenToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::Finally *f)
+{
+ addMarkedUpToken(f->finallyToken, QLatin1String("keyword"));
+ QQmlJS::AST::Node::accept(f->statement, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::TryStatement *statement)
+{
+ addMarkedUpToken(statement->tryToken, QLatin1String("keyword"));
+ QQmlJS::AST::Node::accept(statement->statement, this);
+ QQmlJS::AST::Node::accept(statement->catchExpression, this);
+ QQmlJS::AST::Node::accept(statement->finallyExpression, this);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::FunctionExpression *expression)
+{
+ addMarkedUpToken(expression->functionToken, QLatin1String("keyword"));
+ addMarkedUpToken(expression->identifierToken, QLatin1String("name"));
+ addVerbatim(expression->lparenToken);
+ QQmlJS::AST::Node::accept(expression->formals, this);
+ addVerbatim(expression->rparenToken);
+ addVerbatim(expression->lbraceToken);
+ QQmlJS::AST::Node::accept(expression->body, this);
+ addVerbatim(expression->rbraceToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::FunctionDeclaration *declaration)
+{
+ addMarkedUpToken(declaration->functionToken, QLatin1String("keyword"));
+ addMarkedUpToken(declaration->identifierToken, QLatin1String("name"));
+ addVerbatim(declaration->lparenToken);
+ QQmlJS::AST::Node::accept(declaration->formals, this);
+ addVerbatim(declaration->rparenToken);
+ addVerbatim(declaration->lbraceToken);
+ QQmlJS::AST::Node::accept(declaration->body, this);
+ addVerbatim(declaration->rbraceToken);
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::FormalParameterList *list)
+{
+ // addVerbatim(list->commaToken);
+ QQmlJS::AST::Node::accept(list->element, this);
+ // addMarkedUpToken(list->identifierToken, QLatin1String("name"));
+ return false;
+}
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::DebuggerStatement *statement)
+{
+ addVerbatim(statement->debuggerToken);
+ addVerbatim(statement->semicolonToken);
+ return true;
+}
+
+// Elements and items are represented by UiObjectDefinition nodes.
+
+bool QmlMarkupVisitor::visit(QQmlJS::AST::UiObjectDefinition *definition)
+{
+ addMarkedUpToken(definition->qualifiedTypeNameId->identifierToken, QLatin1String("type"));
+ QQmlJS::AST::Node::accept(definition->initializer, this);
+ return false;
+}
+
+void QmlMarkupVisitor::throwRecursionDepthError()
+{
+ m_hasRecursionDepthError = true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.h b/src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.h
new file mode 100644
index 000000000..a19636a67
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlmarkupvisitor.h
@@ -0,0 +1,139 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QMLMARKUPVISITOR_H
+#define QMLMARKUPVISITOR_H
+
+#include "node.h"
+#include "tree.h"
+
+#include <QtCore/qstring.h>
+
+#include <private/qqmljsastvisitor_p.h>
+#include <private/qqmljsengine_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QmlMarkupVisitor : public QQmlJS::AST::Visitor
+{
+public:
+ enum ExtraType { Comment, Pragma };
+
+ QmlMarkupVisitor(const QString &code, const QList<QQmlJS::SourceLocation> &pragmas,
+ QQmlJS::Engine *engine);
+ ~QmlMarkupVisitor() override = default;
+
+ QString markedUpCode();
+ [[nodiscard]] bool hasError() const;
+
+ bool visit(QQmlJS::AST::UiImport *) override;
+ void endVisit(QQmlJS::AST::UiImport *) override;
+
+ bool visit(QQmlJS::AST::UiPublicMember *) override;
+ bool visit(QQmlJS::AST::UiObjectDefinition *) override;
+
+ bool visit(QQmlJS::AST::UiObjectInitializer *) override;
+ void endVisit(QQmlJS::AST::UiObjectInitializer *) override;
+
+ bool visit(QQmlJS::AST::UiObjectBinding *) override;
+ bool visit(QQmlJS::AST::UiScriptBinding *) override;
+ bool visit(QQmlJS::AST::UiArrayBinding *) override;
+ bool visit(QQmlJS::AST::UiArrayMemberList *) override;
+ bool visit(QQmlJS::AST::UiQualifiedId *) override;
+
+ bool visit(QQmlJS::AST::ThisExpression *) override;
+ bool visit(QQmlJS::AST::IdentifierExpression *) override;
+ bool visit(QQmlJS::AST::NullExpression *) override;
+ bool visit(QQmlJS::AST::TrueLiteral *) override;
+ bool visit(QQmlJS::AST::FalseLiteral *) override;
+ bool visit(QQmlJS::AST::NumericLiteral *) override;
+ bool visit(QQmlJS::AST::StringLiteral *) override;
+ bool visit(QQmlJS::AST::RegExpLiteral *) override;
+ bool visit(QQmlJS::AST::ArrayPattern *) override;
+
+ bool visit(QQmlJS::AST::ObjectPattern *) override;
+ void endVisit(QQmlJS::AST::ObjectPattern *) override;
+
+ bool visit(QQmlJS::AST::PatternElementList *) override;
+ bool visit(QQmlJS::AST::Elision *) override;
+ bool visit(QQmlJS::AST::PatternProperty *) override;
+ bool visit(QQmlJS::AST::ArrayMemberExpression *) override;
+ bool visit(QQmlJS::AST::FieldMemberExpression *) override;
+ bool visit(QQmlJS::AST::NewMemberExpression *) override;
+ bool visit(QQmlJS::AST::NewExpression *) override;
+ bool visit(QQmlJS::AST::ArgumentList *) override;
+ bool visit(QQmlJS::AST::PostIncrementExpression *) override;
+ bool visit(QQmlJS::AST::PostDecrementExpression *) override;
+ bool visit(QQmlJS::AST::DeleteExpression *) override;
+ bool visit(QQmlJS::AST::VoidExpression *) override;
+ bool visit(QQmlJS::AST::TypeOfExpression *) override;
+ bool visit(QQmlJS::AST::PreIncrementExpression *) override;
+ bool visit(QQmlJS::AST::PreDecrementExpression *) override;
+ bool visit(QQmlJS::AST::UnaryPlusExpression *) override;
+ bool visit(QQmlJS::AST::UnaryMinusExpression *) override;
+ bool visit(QQmlJS::AST::TildeExpression *) override;
+ bool visit(QQmlJS::AST::NotExpression *) override;
+ bool visit(QQmlJS::AST::BinaryExpression *) override;
+ bool visit(QQmlJS::AST::ConditionalExpression *) override;
+ bool visit(QQmlJS::AST::Expression *) override;
+
+ bool visit(QQmlJS::AST::Block *) override;
+ void endVisit(QQmlJS::AST::Block *) override;
+
+ bool visit(QQmlJS::AST::VariableStatement *) override;
+ bool visit(QQmlJS::AST::VariableDeclarationList *) override;
+ bool visit(QQmlJS::AST::EmptyStatement *) override;
+ bool visit(QQmlJS::AST::ExpressionStatement *) override;
+ bool visit(QQmlJS::AST::IfStatement *) override;
+ bool visit(QQmlJS::AST::DoWhileStatement *) override;
+ bool visit(QQmlJS::AST::WhileStatement *) override;
+ bool visit(QQmlJS::AST::ForStatement *) override;
+ bool visit(QQmlJS::AST::ForEachStatement *) override;
+ bool visit(QQmlJS::AST::ContinueStatement *) override;
+ bool visit(QQmlJS::AST::BreakStatement *) override;
+ bool visit(QQmlJS::AST::ReturnStatement *) override;
+ bool visit(QQmlJS::AST::WithStatement *) override;
+
+ bool visit(QQmlJS::AST::CaseBlock *) override;
+ void endVisit(QQmlJS::AST::CaseBlock *) override;
+
+ bool visit(QQmlJS::AST::SwitchStatement *) override;
+ bool visit(QQmlJS::AST::CaseClause *) override;
+ bool visit(QQmlJS::AST::DefaultClause *) override;
+ bool visit(QQmlJS::AST::LabelledStatement *) override;
+ bool visit(QQmlJS::AST::ThrowStatement *) override;
+ bool visit(QQmlJS::AST::TryStatement *) override;
+ bool visit(QQmlJS::AST::Catch *) override;
+ bool visit(QQmlJS::AST::Finally *) override;
+ bool visit(QQmlJS::AST::FunctionDeclaration *) override;
+ bool visit(QQmlJS::AST::FunctionExpression *) override;
+ bool visit(QQmlJS::AST::FormalParameterList *) override;
+ bool visit(QQmlJS::AST::DebuggerStatement *) override;
+
+protected:
+ QString protect(const QString &string);
+
+private:
+ typedef QHash<QString, QString> StringHash;
+ void addExtra(quint32 start, quint32 finish);
+ void addMarkedUpToken(QQmlJS::SourceLocation &location, const QString &text,
+ const StringHash &attributes = StringHash());
+ void addVerbatim(QQmlJS::SourceLocation first,
+ QQmlJS::SourceLocation last = QQmlJS::SourceLocation());
+ QString sourceText(QQmlJS::SourceLocation &location);
+ void throwRecursionDepthError() final;
+
+ QQmlJS::Engine *m_engine { nullptr };
+ QList<ExtraType> m_extraTypes {};
+ QList<QQmlJS::SourceLocation> m_extraLocations {};
+ QString m_source {};
+ QString m_output {};
+ quint32 m_cursor {};
+ int m_extraIndex {};
+ bool m_hasRecursionDepthError { false };
+};
+Q_DECLARE_TYPEINFO(QmlMarkupVisitor::ExtraType, Q_PRIMITIVE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/qmlpropertynode.cpp b/src/qdoc/qdoc/src/qdoc/qmlpropertynode.cpp
new file mode 100644
index 000000000..335b7d870
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlpropertynode.cpp
@@ -0,0 +1,175 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qmlpropertynode.h"
+
+#include "classnode.h"
+#include "propertynode.h"
+#include "enumnode.h"
+
+#include <utility>
+#include "qdocdatabase.h"
+#include "utilities.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ Constructor for the QML property node.
+ */
+QmlPropertyNode::QmlPropertyNode(Aggregate *parent, const QString &name, QString type,
+ bool attached)
+ : Node(QmlProperty, parent, name),
+ m_type(std::move(type)),
+ m_attached(attached)
+{
+ if (m_type == "alias")
+ m_isAlias = true;
+ if (name.startsWith("__"))
+ setStatus(Internal);
+}
+
+/*!
+ \fn bool QmlPropertyNode::isReadOnly() const
+
+ Returns \c true if this QML property node is marked as a
+ read-only property.
+*/
+
+/*!
+ \fn const EnumNode *QmlPropertyNode::enumNode() const
+
+ Returns the node representing the C++ enumeration associated
+ with this property, or \nullptr.
+*/
+
+/*!
+ Returns the prefix to use for documentated enumerators from
+ the associated C++ enum for this property.
+*/
+const QString &QmlPropertyNode::enumPrefix() const
+{
+ return !m_enumNode.second.isEmpty() ?
+ m_enumNode.second : parent()->name();
+}
+
+/*!
+ Locates the node specified by \a path and sets it as the C++ enumeration
+ associated with this property.
+
+ \a registeredQmlName is used as the prefix in the generated enum value
+ documentation.
+
+ \note The target EnumNode is searched under the primary tree only.
+
+ Returns \c true on success.
+*/
+bool QmlPropertyNode::setEnumNode(const QString &path, const QString &registeredQmlName)
+{
+ m_enumNode.first = static_cast<EnumNode*>(
+ QDocDatabase::qdocDB()->primaryTree()->findNodeByNameAndType(path.split("::"), &Node::isEnumType)
+ );
+ m_enumNode.second = registeredQmlName;
+ return m_enumNode.first != nullptr;
+}
+
+/*!
+ Returns \c true if this QML property or attached property is
+ read-only. If the read-only status is not set explicitly
+ using the \\readonly command, QDoc attempts to resolve it
+ from the associated C++ class instantiated by the QML type
+ that this property belongs to.
+
+ \note Depending on how the QML type is implemented, this
+ information may not be available to QDoc. If so, add a debug
+ line but do not treat it as a warning.
+ */
+bool QmlPropertyNode::isReadOnly()
+{
+ if (m_readOnly != FlagValueDefault)
+ return fromFlagValue(m_readOnly, false);
+
+ // Find the parent QML type node
+ auto *parent{this->parent()};
+ while (parent && !(parent->isQmlType()))
+ parent = parent->parent();
+
+ bool readonly{false};
+ if (auto qcn = static_cast<QmlTypeNode *>(parent); qcn && qcn->classNode()) {
+ if (auto propertyNode = findCorrespondingCppProperty(); propertyNode)
+ readonly = !propertyNode->isWritable();
+ else
+ qCDebug(lcQdoc).nospace()
+ << qPrintable(defLocation().toString())
+ << ": Automatic resolution of QML property attributes failed for "
+ << name()
+ << " (Q_PROPERTY not found in the C++ class hierarchy known to QDoc. "
+ << "Likely, the type is replaced with a private implementation.)";
+ }
+ markReadOnly(readonly);
+ return readonly;
+}
+
+/*!
+ Returns \c true if this QML property is marked with \required or the
+ corresponding C++ property uses the REQUIRED keyword.
+*/
+bool QmlPropertyNode::isRequired()
+{
+ if (m_required != FlagValueDefault)
+ return fromFlagValue(m_required, false);
+
+ PropertyNode *pn = findCorrespondingCppProperty();
+ return pn != nullptr && pn->isRequired();
+}
+
+/*!
+ Returns a pointer this QML property's corresponding C++
+ property, if it has one.
+ */
+PropertyNode *QmlPropertyNode::findCorrespondingCppProperty()
+{
+ PropertyNode *pn;
+ Node *n = parent();
+ while (n && !(n->isQmlType()))
+ n = n->parent();
+ if (n) {
+ auto *qcn = static_cast<QmlTypeNode *>(n);
+ ClassNode *cn = qcn->classNode();
+ if (cn) {
+ /*
+ If there is a dot in the property name, first
+ find the C++ property corresponding to the QML
+ property group.
+ */
+ QStringList dotSplit = name().split(QChar('.'));
+ pn = cn->findPropertyNode(dotSplit[0]);
+ if (pn) {
+ /*
+ Now find the C++ property corresponding to
+ the QML property in the QML property group,
+ <group>.<property>.
+ */
+ if (dotSplit.size() > 1) {
+ QStringList path(extractClassName(pn->qualifiedDataType()));
+ Node *nn = QDocDatabase::qdocDB()->findClassNode(path);
+ if (nn) {
+ auto *cn = static_cast<ClassNode *>(nn);
+ PropertyNode *pn2 = cn->findPropertyNode(dotSplit[1]);
+ /*
+ If found, return the C++ property
+ corresponding to the QML property.
+ Otherwise, return the C++ property
+ corresponding to the QML property
+ group.
+ */
+ return (pn2 ? pn2 : pn);
+ }
+ } else
+ return pn;
+ }
+ }
+ }
+ return nullptr;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/qmlpropertynode.h b/src/qdoc/qdoc/src/qdoc/qmlpropertynode.h
new file mode 100644
index 000000000..f966949c1
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlpropertynode.h
@@ -0,0 +1,72 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QMLPROPERTYNODE_H
+#define QMLPROPERTYNODE_H
+
+#include "aggregate.h"
+#include "node.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class QmlPropertyNode : public Node
+{
+public:
+ QmlPropertyNode(Aggregate *parent, const QString &name, QString type, bool attached);
+
+ void setDataType(const QString &dataType) override { m_type = dataType; }
+ void setStored(bool stored) { m_stored = toFlagValue(stored); }
+ void setDefaultValue(const QString &value) { m_defaultValue = value; }
+ void setRequired() { m_required = toFlagValue(true); }
+ bool setEnumNode(const QString &path, const QString &registeredQmlName);
+
+ [[nodiscard]] const QString &dataType() const { return m_type; }
+ [[nodiscard]] const QString &defaultValue() const { return m_defaultValue; }
+ [[nodiscard]] bool isStored() const { return fromFlagValue(m_stored, true); }
+ bool isRequired();
+ [[nodiscard]] bool isDefault() const override { return m_isDefault; }
+ [[nodiscard]] bool isReadOnly() const { return fromFlagValue(m_readOnly, false); }
+ [[nodiscard]] bool isReadOnly();
+ [[nodiscard]] bool isAlias() const override { return m_isAlias; }
+ [[nodiscard]] bool isAttached() const override { return m_attached; }
+ [[nodiscard]] QString qmlTypeName() const override { return parent()->qmlTypeName(); }
+ [[nodiscard]] QString logicalModuleName() const override
+ {
+ return parent()->logicalModuleName();
+ }
+ [[nodiscard]] QString logicalModuleVersion() const override
+ {
+ return parent()->logicalModuleVersion();
+ }
+ [[nodiscard]] QString logicalModuleIdentifier() const override
+ {
+ return parent()->logicalModuleIdentifier();
+ }
+ [[nodiscard]] QString element() const override { return parent()->name(); }
+ [[nodiscard]] const EnumNode *enumNode() const { return m_enumNode.first; }
+ [[nodiscard]] const QString &enumPrefix() const;
+
+ void markDefault() override { m_isDefault = true; }
+ void markReadOnly(bool flag) override { m_readOnly = toFlagValue(flag); }
+
+private:
+ PropertyNode *findCorrespondingCppProperty();
+
+private:
+ QString m_type {};
+ QString m_defaultValue {};
+ FlagValue m_stored { FlagValueDefault };
+ bool m_isAlias { false };
+ bool m_isDefault { false };
+ bool m_attached {};
+ FlagValue m_readOnly { FlagValueDefault };
+ FlagValue m_required { FlagValueDefault };
+ std::pair<EnumNode *, QString> m_enumNode { nullptr, {} };
+};
+
+QT_END_NAMESPACE
+
+#endif // QMLPROPERTYNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/qmltypenode.cpp b/src/qdoc/qdoc/src/qdoc/qmltypenode.cpp
new file mode 100644
index 000000000..4285f9b6e
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmltypenode.cpp
@@ -0,0 +1,153 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qmltypenode.h"
+#include "collectionnode.h"
+#include "qdocdatabase.h"
+
+#include <QtCore/qdebug.h>
+
+QT_BEGIN_NAMESPACE
+
+QMultiMap<const Node *, Node *> QmlTypeNode::s_inheritedBy;
+
+/*!
+ Constructs a Qml type.
+
+ The new node has the given \a parent, name \a name, and a specific node
+ \a type. Valid types are Node::QmlType and Node::QmlValueType.
+ */
+QmlTypeNode::QmlTypeNode(Aggregate *parent, const QString &name, Node::NodeType type)
+ : Aggregate(type, parent, name)
+{
+ Q_ASSERT(type == Node::QmlType || type == Node::QmlValueType);
+ setTitle(name);
+}
+
+/*!
+ Clear the static maps so that subsequent runs don't try to use
+ contents from a previous run.
+ */
+void QmlTypeNode::terminate()
+{
+ s_inheritedBy.clear();
+}
+
+/*!
+ Record the fact that QML class \a base is inherited by
+ QML class \a sub.
+ */
+void QmlTypeNode::addInheritedBy(const Node *base, Node *sub)
+{
+ if (sub->isInternal())
+ return;
+ if (!s_inheritedBy.contains(base, sub))
+ s_inheritedBy.insert(base, sub);
+}
+
+/*!
+ Loads the list \a subs with the nodes of all the subclasses of \a base.
+ */
+void QmlTypeNode::subclasses(const Node *base, NodeList &subs)
+{
+ subs.clear();
+ if (s_inheritedBy.count(base) > 0) {
+ subs = s_inheritedBy.values(base);
+ }
+}
+
+/*!
+ If this QML type node has a base type node,
+ return the fully qualified name of that QML
+ type, i.e. <QML-module-name>::<QML-type-name>.
+ */
+QString QmlTypeNode::qmlFullBaseName() const
+{
+ QString result;
+ if (m_qmlBaseNode) {
+ result = m_qmlBaseNode->logicalModuleName() + "::" + m_qmlBaseNode->name();
+ }
+ return result;
+}
+
+/*!
+ If the QML type's QML module pointer is set, return the QML
+ module name from the QML module node. Otherwise, return the
+ empty string.
+ */
+QString QmlTypeNode::logicalModuleName() const
+{
+ return (m_logicalModule ? m_logicalModule->logicalModuleName() : QString());
+}
+
+/*!
+ If the QML type's QML module pointer is set, return the QML
+ module version from the QML module node. Otherwise, return
+ the empty string.
+ */
+QString QmlTypeNode::logicalModuleVersion() const
+{
+ return (m_logicalModule ? m_logicalModule->logicalModuleVersion() : QString());
+}
+
+/*!
+ If the QML type's QML module pointer is set, return the QML
+ module identifier from the QML module node. Otherwise, return
+ the empty string.
+ */
+QString QmlTypeNode::logicalModuleIdentifier() const
+{
+ return (m_logicalModule ? m_logicalModule->logicalModuleIdentifier() : QString());
+}
+
+/*!
+ Returns true if this QML type inherits \a type.
+ */
+bool QmlTypeNode::inherits(Aggregate *type)
+{
+ QmlTypeNode *qtn = qmlBaseNode();
+ while (qtn != nullptr) {
+ if (qtn == type)
+ return true;
+ qtn = qtn->qmlBaseNode();
+ }
+ return false;
+}
+
+/*!
+ Recursively resolves the base node for this QML type when only the name of
+ the base type is known.
+
+ \a previousSearches is used for speeding up the process.
+*/
+void QmlTypeNode::resolveInheritance(NodeMap &previousSearches)
+{
+ if (m_qmlBaseNode || m_qmlBaseName.isEmpty())
+ return;
+
+ auto *base = static_cast<QmlTypeNode *>(previousSearches.value(m_qmlBaseName));
+ if (!previousSearches.contains(m_qmlBaseName)) {
+ for (const auto &imp : std::as_const(m_importList)) {
+ base = QDocDatabase::qdocDB()->findQmlType(imp, m_qmlBaseName);
+ if (base)
+ break;
+ }
+ if (!base) {
+ if (m_qmlBaseName.contains(':'))
+ base = QDocDatabase::qdocDB()->findQmlType(m_qmlBaseName);
+ else
+ base = QDocDatabase::qdocDB()->findQmlType(QString(), m_qmlBaseName);
+ }
+ previousSearches.insert(m_qmlBaseName, base);
+ }
+
+ if (base && base != this) {
+ m_qmlBaseNode = base;
+ QmlTypeNode::addInheritedBy(base, this);
+ // Base types read from the index need resolving as they only have the name set
+ if (base->isIndexNode())
+ base->resolveInheritance(previousSearches);
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/qmltypenode.h b/src/qdoc/qdoc/src/qdoc/qmltypenode.h
new file mode 100644
index 000000000..d7cd5ff2b
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmltypenode.h
@@ -0,0 +1,65 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QMLTYPENODE_H
+#define QMLTYPENODE_H
+
+#include "importrec.h"
+#include "aggregate.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class ClassNode;
+class CollectionNode;
+
+typedef QList<ImportRec> ImportList;
+
+class QmlTypeNode : public Aggregate
+{
+public:
+ QmlTypeNode(Aggregate *parent, const QString &name, Node::NodeType type);
+ [[nodiscard]] bool isFirstClassAggregate() const override { return true; }
+ ClassNode *classNode() override { return m_classNode; }
+ void setClassNode(ClassNode *cn) override { m_classNode = cn; }
+ [[nodiscard]] bool isAbstract() const override { return m_abstract; }
+ [[nodiscard]] bool isWrapper() const override { return m_wrapper; }
+ void setAbstract(bool b) override { m_abstract = b; }
+ void setWrapper() override { m_wrapper = true; }
+ [[nodiscard]] bool isInternal() const override { return (status() == Internal); }
+ [[nodiscard]] QString qmlFullBaseName() const override;
+ [[nodiscard]] QString logicalModuleName() const override;
+ [[nodiscard]] QString logicalModuleVersion() const override;
+ [[nodiscard]] QString logicalModuleIdentifier() const override;
+ [[nodiscard]] CollectionNode *logicalModule() const override { return m_logicalModule; }
+ void setQmlModule(CollectionNode *t) override { m_logicalModule = t; }
+
+ void setImportList(const ImportList &il) { m_importList = il; }
+ [[nodiscard]] const QString &qmlBaseName() const { return m_qmlBaseName; }
+ void setQmlBaseName(const QString &name) { m_qmlBaseName = name; }
+ [[nodiscard]] QmlTypeNode *qmlBaseNode() const override { return m_qmlBaseNode; }
+ void resolveInheritance(NodeMap &previousSearches);
+ static void addInheritedBy(const Node *base, Node *sub);
+ static void subclasses(const Node *base, NodeList &subs);
+ static void terminate();
+ bool inherits(Aggregate *type);
+
+public:
+ static QMultiMap<const Node *, Node *> s_inheritedBy;
+
+private:
+ bool m_abstract { false };
+ bool m_wrapper { false };
+ ClassNode *m_classNode { nullptr };
+ QString m_qmlBaseName {};
+ CollectionNode *m_logicalModule { nullptr };
+ QmlTypeNode *m_qmlBaseNode { nullptr };
+ ImportList m_importList {};
+};
+
+QT_END_NAMESPACE
+
+#endif // QMLTYPENODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/qmlvisitor.cpp b/src/qdoc/qdoc/src/qdoc/qmlvisitor.cpp
new file mode 100644
index 000000000..d6ecf1986
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlvisitor.cpp
@@ -0,0 +1,729 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qmlvisitor.h"
+
+#include "aggregate.h"
+#include "codechunk.h"
+#include "codeparser.h"
+#include "functionnode.h"
+#include "node.h"
+#include "qdocdatabase.h"
+#include "qmlpropertynode.h"
+#include "tokenizer.h"
+#include "utilities.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qglobal.h>
+
+#include <private/qqmljsast_p.h>
+#include <private/qqmljsengine_p.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+/*!
+ The constructor stores all the parameters in local data members.
+ */
+QmlDocVisitor::QmlDocVisitor(const QString &filePath, const QString &code, QQmlJS::Engine *engine,
+ const QSet<QString> &commands, const QSet<QString> &topics)
+ : m_nestingLevel(0)
+{
+ m_lastEndOffset = 0;
+ this->m_filePath = filePath;
+ this->m_name = QFileInfo(filePath).baseName();
+ m_document = code;
+ this->m_engine = engine;
+ this->m_commands = commands;
+ this->m_topics = topics;
+ m_current = QDocDatabase::qdocDB()->primaryTreeRoot();
+}
+
+/*!
+ Returns the location of the nearest comment above the \a offset.
+ */
+QQmlJS::SourceLocation QmlDocVisitor::precedingComment(quint32 offset) const
+{
+ const auto comments = m_engine->comments();
+ for (auto it = comments.rbegin(); it != comments.rend(); ++it) {
+ QQmlJS::SourceLocation loc = *it;
+
+ if (loc.begin() <= m_lastEndOffset) {
+ // Return if we reach the end of the preceding structure.
+ break;
+ } else if (m_usedComments.contains(loc.begin())) {
+ // Return if we encounter a previously used comment.
+ break;
+ } else if (loc.begin() > m_lastEndOffset && loc.end() < offset) {
+ // Only examine multiline comments in order to avoid snippet markers.
+ if (m_document.at(loc.offset - 1) == QLatin1Char('*')) {
+ QString comment = m_document.mid(loc.offset, loc.length);
+ if (comment.startsWith(QLatin1Char('!')) || comment.startsWith(QLatin1Char('*'))) {
+ return loc;
+ }
+ }
+ }
+ }
+
+ return QQmlJS::SourceLocation();
+}
+
+class QmlSignatureParser
+{
+public:
+ QmlSignatureParser(FunctionNode *func, const QString &signature, const Location &loc);
+ void readToken() { tok_ = tokenizer_->getToken(); }
+ QString lexeme() { return tokenizer_->lexeme(); }
+ QString previousLexeme() { return tokenizer_->previousLexeme(); }
+
+ bool match(int target);
+ bool matchTypeAndName(CodeChunk *type, QString *var);
+ bool matchParameter();
+ bool matchFunctionDecl();
+
+private:
+ QString signature_;
+ QStringList names_;
+ Tokenizer *tokenizer_;
+ int tok_;
+ FunctionNode *func_;
+ const Location &location_;
+};
+
+/*!
+ Finds the nearest unused qdoc comment above the QML entity
+ represented by a \a node and processes the qdoc commands
+ in that comment. The processed documentation is stored in
+ \a node.
+
+ If \a node is a \c nullptr and there is a valid comment block,
+ the QML module identifier (\inqmlmodule argument) is used
+ for searching an existing QML type node. If an existing node
+ is not found, constructs a new QmlTypeNode instance.
+
+ Returns a pointer to the QmlTypeNode instance if one was
+ found or constructed. Otherwise, returns a pointer to the \a
+ node that was passed as an argument.
+ */
+Node *QmlDocVisitor::applyDocumentation(QQmlJS::SourceLocation location, Node *node)
+{
+ QQmlJS::SourceLocation loc = precedingComment(location.begin());
+ Location comment_loc(m_filePath);
+
+ // No preceding comment; construct a new QML type if
+ // needed.
+ if (!loc.isValid()) {
+ if (!node)
+ node = new QmlTypeNode(m_current, m_name, Node::QmlType);
+ comment_loc.setLineNo(location.startLine);
+ node->setLocation(comment_loc);
+ return node;
+ }
+
+ QString source = m_document.mid(loc.offset + 1, loc.length - 1);
+ comment_loc.setLineNo(loc.startLine);
+ comment_loc.setColumnNo(loc.startColumn);
+
+ Doc doc(comment_loc, comment_loc, source, m_commands, m_topics);
+ const TopicList &topicsUsed = doc.topicsUsed();
+ NodeList nodes;
+ if (!node) {
+ QString qmid;
+ if (auto args = doc.metaCommandArgs(COMMAND_INQMLMODULE); !args.isEmpty())
+ qmid = args.first().first;
+ node = QDocDatabase::qdocDB()->findQmlTypeInPrimaryTree(qmid, m_name);
+ if (!node) {
+ node = new QmlTypeNode(m_current, m_name, Node::QmlType);
+ node->setLocation(comment_loc);
+ }
+ }
+
+ auto *parent{node->parent()};
+ node->setDoc(doc);
+ nodes.append(node);
+ if (!topicsUsed.empty()) {
+ for (int i = 0; i < topicsUsed.size(); ++i) {
+ QString topic = topicsUsed.at(i).m_topic;
+ QString args = topicsUsed.at(i).m_args;
+ if (topic.endsWith(QLatin1String("property"))) {
+ auto *qmlProperty = static_cast<QmlPropertyNode *>(node);
+ QmlPropArgs qpa;
+ if (splitQmlPropertyArg(doc, args, qpa)) {
+ if (qpa.m_name == node->name()) {
+ if (qmlProperty->isAlias())
+ qmlProperty->setDataType(qpa.m_type);
+ } else {
+ bool isAttached = topic.contains(QLatin1String("attached"));
+ QmlPropertyNode *n = parent->hasQmlProperty(qpa.m_name, isAttached);
+ if (n == nullptr)
+ n = new QmlPropertyNode(parent, qpa.m_name, qpa.m_type, isAttached);
+ n->setLocation(doc.location());
+ n->setDoc(doc);
+ // Use the const-overload of QmlPropertyNode::isReadOnly() as there's
+ // no associated C++ property to resolve the read-only status from
+ n->markReadOnly(const_cast<const QmlPropertyNode *>(qmlProperty)->isReadOnly()
+ && !isAttached);
+ if (qmlProperty->isDefault())
+ n->markDefault();
+ nodes.append(n);
+ }
+ } else
+ qCDebug(lcQdoc) << "Failed to parse QML property:" << topic << args;
+ } else if (topic.endsWith(QLatin1String("method")) || topic == COMMAND_QMLSIGNAL) {
+ if (node->isFunction()) {
+ auto *fn = static_cast<FunctionNode *>(node);
+ QmlSignatureParser qsp(fn, args, doc.location());
+ }
+ }
+ }
+ }
+ for (const auto &node : nodes)
+ applyMetacommands(loc, node, doc);
+
+ m_usedComments.insert(loc.offset);
+ return node;
+}
+
+QmlSignatureParser::QmlSignatureParser(FunctionNode *func, const QString &signature,
+ const Location &loc)
+ : signature_(signature), func_(func), location_(loc)
+{
+ QByteArray latin1 = signature.toLatin1();
+ Tokenizer stringTokenizer(location_, latin1);
+ stringTokenizer.setParsingFnOrMacro(true);
+ tokenizer_ = &stringTokenizer;
+ readToken();
+ matchFunctionDecl();
+}
+
+/*!
+ If the current token matches \a target, read the next
+ token and return true. Otherwise, don't read the next
+ token, and return false.
+ */
+bool QmlSignatureParser::match(int target)
+{
+ if (tok_ == target) {
+ readToken();
+ return true;
+ }
+ return false;
+}
+
+/*!
+ Parse a QML data type into \a type and an optional
+ variable name into \a var.
+ */
+bool QmlSignatureParser::matchTypeAndName(CodeChunk *type, QString *var)
+{
+ /*
+ This code is really hard to follow... sorry. The loop is there to match
+ Alpha::Beta::Gamma::...::Omega.
+ */
+ for (;;) {
+ bool virgin = true;
+
+ if (tok_ != Tok_Ident) {
+ while (match(Tok_signed) || match(Tok_unsigned) || match(Tok_short) || match(Tok_long)
+ || match(Tok_int64)) {
+ type->append(previousLexeme());
+ virgin = false;
+ }
+ }
+
+ if (virgin) {
+ if (match(Tok_Ident)) {
+ type->append(previousLexeme());
+ } else if (match(Tok_void) || match(Tok_int) || match(Tok_char) || match(Tok_double)
+ || match(Tok_Ellipsis))
+ type->append(previousLexeme());
+ else
+ return false;
+ } else if (match(Tok_int) || match(Tok_char) || match(Tok_double)) {
+ type->append(previousLexeme());
+ }
+
+ if (match(Tok_Gulbrandsen))
+ type->append(previousLexeme());
+ else
+ break;
+ }
+
+ while (match(Tok_Ampersand) || match(Tok_Aster) || match(Tok_const) || match(Tok_Caret))
+ type->append(previousLexeme());
+
+ /*
+ The usual case: Look for an optional identifier, then for
+ some array brackets.
+ */
+ type->appendHotspot();
+
+ if ((var != nullptr) && match(Tok_Ident))
+ *var = previousLexeme();
+
+ if (tok_ == Tok_LeftBracket) {
+ int bracketDepth0 = tokenizer_->bracketDepth();
+ while ((tokenizer_->bracketDepth() >= bracketDepth0 && tok_ != Tok_Eoi)
+ || tok_ == Tok_RightBracket) {
+ type->append(lexeme());
+ readToken();
+ }
+ }
+ return true;
+}
+
+bool QmlSignatureParser::matchParameter()
+{
+ QString name;
+ CodeChunk type;
+ CodeChunk defaultValue;
+
+ bool result = matchTypeAndName(&type, &name);
+ if (name.isEmpty()) {
+ name = type.toString();
+ type.clear();
+ }
+
+ if (!result)
+ return false;
+ if (match(Tok_Equal)) {
+ int parenDepth0 = tokenizer_->parenDepth();
+ while (tokenizer_->parenDepth() >= parenDepth0
+ && (tok_ != Tok_Comma || tokenizer_->parenDepth() > parenDepth0)
+ && tok_ != Tok_Eoi) {
+ defaultValue.append(lexeme());
+ readToken();
+ }
+ }
+ func_->parameters().append(type.toString(), name, defaultValue.toString());
+ return true;
+}
+
+bool QmlSignatureParser::matchFunctionDecl()
+{
+ CodeChunk returnType;
+
+ qsizetype firstBlank = signature_.indexOf(QChar(' '));
+ qsizetype leftParen = signature_.indexOf(QChar('('));
+ if ((firstBlank > 0) && (leftParen - firstBlank) > 1) {
+ if (!matchTypeAndName(&returnType, nullptr))
+ return false;
+ }
+
+ while (match(Tok_Ident)) {
+ names_.append(previousLexeme());
+ if (!match(Tok_Gulbrandsen)) {
+ previousLexeme();
+ names_.pop_back();
+ break;
+ }
+ }
+
+ if (tok_ != Tok_LeftParen)
+ return false;
+ /*
+ Parsing the parameters should be moved into class Parameters,
+ but it can wait. mws 14/12/2018
+ */
+ readToken();
+
+ func_->setLocation(location_);
+ func_->setReturnType(returnType.toString());
+
+ if (tok_ != Tok_RightParen) {
+ func_->parameters().clear();
+ do {
+ if (!matchParameter())
+ return false;
+ } while (match(Tok_Comma));
+ }
+ if (!match(Tok_RightParen))
+ return false;
+ return true;
+}
+
+/*!
+ A QML property argument has the form...
+
+ <type> <component>::<name>
+ <type> <QML-module>::<component>::<name>
+
+ This function splits the argument into one of those
+ two forms. The three part form is the old form, which
+ was used before the creation of QtQuick 2 and Qt
+ Components. A <QML-module> is the QML equivalent of a
+ C++ namespace. So this function splits \a arg on "::"
+ and stores the parts in the \e {type}, \e {module},
+ \e {component}, and \a {name}, fields of \a qpa. If it
+ is successful, it returns \c true. If not enough parts
+ are found, a qdoc warning is emitted and false is
+ returned.
+ */
+bool QmlDocVisitor::splitQmlPropertyArg(const Doc &doc, const QString &arg, QmlPropArgs &qpa)
+{
+ qpa.clear();
+ QStringList blankSplit = arg.split(QLatin1Char(' '));
+ if (blankSplit.size() > 1) {
+ qpa.m_type = blankSplit[0];
+ QStringList colonSplit(blankSplit[1].split("::"));
+ if (colonSplit.size() == 3) {
+ qpa.m_module = colonSplit[0];
+ qpa.m_component = colonSplit[1];
+ qpa.m_name = colonSplit[2];
+ return true;
+ } else if (colonSplit.size() == 2) {
+ qpa.m_component = colonSplit[0];
+ qpa.m_name = colonSplit[1];
+ return true;
+ } else if (colonSplit.size() == 1) {
+ qpa.m_name = colonSplit[0];
+ return true;
+ }
+ doc.location().warning(
+ QStringLiteral("Unrecognizable QML module/component qualifier for %1.").arg(arg));
+ } else {
+ doc.location().warning(QStringLiteral("Missing property type for %1.").arg(arg));
+ }
+ return false;
+}
+
+/*!
+ Applies the metacommands found in the comment.
+ */
+void QmlDocVisitor::applyMetacommands(QQmlJS::SourceLocation, Node *node, Doc &doc)
+{
+ QDocDatabase *qdb = QDocDatabase::qdocDB();
+ QSet<QString> metacommands = doc.metaCommandsUsed();
+ if (metacommands.size() > 0) {
+ metacommands.subtract(m_topics);
+ for (const auto &command : std::as_const(metacommands)) {
+ const ArgList args = doc.metaCommandArgs(command);
+ if ((command == COMMAND_QMLABSTRACT) || (command == COMMAND_ABSTRACT)) {
+ if (node->isQmlType()) {
+ node->setAbstract(true);
+ }
+ } else if (command == COMMAND_DEPRECATED) {
+ node->setDeprecated(args[0].second);
+ } else if (command == COMMAND_INQMLMODULE) {
+ qdb->addToQmlModule(args[0].first, node);
+ } else if (command == COMMAND_QMLINHERITS) {
+ if (node->name() == args[0].first)
+ doc.location().warning(
+ QStringLiteral("%1 tries to inherit itself").arg(args[0].first));
+ else if (node->isQmlType()) {
+ auto *qmlType = static_cast<QmlTypeNode *>(node);
+ qmlType->setQmlBaseName(args[0].first);
+ }
+ } else if (command == COMMAND_DEFAULT) {
+ if (!node->isQmlProperty()) {
+ doc.location().warning(QStringLiteral("Ignored '\\%1', applies only to '\\%2'")
+ .arg(command, COMMAND_QMLPROPERTY));
+ } else if (args.isEmpty() || args[0].first.isEmpty()) {
+ doc.location().warning(QStringLiteral("Expected an argument for '\\%1' (maybe you meant '\\%2'?)")
+ .arg(command, COMMAND_QMLDEFAULT));
+ } else {
+ static_cast<QmlPropertyNode *>(node)->setDefaultValue(args[0].first);
+ }
+ } else if (command == COMMAND_QMLDEFAULT) {
+ node->markDefault();
+ } else if (command == COMMAND_QMLENUMERATORSFROM) {
+ if (!node->isQmlProperty()) {
+ doc.location().warning("Ignored '\\%1', applies only to '\\%2'"_L1
+ .arg(command, COMMAND_QMLPROPERTY));
+ } else if (!static_cast<QmlPropertyNode*>(node)->setEnumNode(args[0].first, args[0].second)) {
+ doc.location().warning("Failed to find C++ enumeration '%2' passed to \\%1"_L1
+ .arg(command, args[0].first), "Use \\value commands instead"_L1);
+ }
+ } else if (command == COMMAND_QMLREADONLY) {
+ node->markReadOnly(1);
+ } else if (command == COMMAND_QMLREQUIRED) {
+ if (node->isQmlProperty())
+ static_cast<QmlPropertyNode *>(node)->setRequired();
+ } else if ((command == COMMAND_INGROUP) && !args.isEmpty()) {
+ for (const auto &argument : args)
+ QDocDatabase::qdocDB()->addToGroup(argument.first, node);
+ } else if (command == COMMAND_INTERNAL) {
+ node->setStatus(Node::Internal);
+ } else if (command == COMMAND_OBSOLETE) {
+ node->setStatus(Node::Deprecated);
+ } else if (command == COMMAND_PRELIMINARY) {
+ node->setStatus(Node::Preliminary);
+ } else if (command == COMMAND_SINCE) {
+ QString arg = args[0].first; //.join(' ');
+ node->setSince(arg);
+ } else if (command == COMMAND_WRAPPER) {
+ node->setWrapper();
+ } else {
+ doc.location().warning(
+ QStringLiteral("The \\%1 command is ignored in QML files").arg(command));
+ }
+ }
+ }
+}
+
+/*!
+ Reconstruct the qualified \a id using dot notation
+ and return the fully qualified string.
+ */
+QString QmlDocVisitor::getFullyQualifiedId(QQmlJS::AST::UiQualifiedId *id)
+{
+ QString result;
+ if (id) {
+ result = id->name.toString();
+ id = id->next;
+ while (id != nullptr) {
+ result += QChar('.') + id->name.toString();
+ id = id->next;
+ }
+ }
+ return result;
+}
+
+/*!
+ Begin the visit of the object \a definition, recording it in the
+ qdoc database. Increment the object nesting level, which is used
+ to test whether we are at the public API level. The public level
+ is level 1.
+
+ Defers the construction of a QmlTypeNode instance to
+ applyDocumentation(), by passing \c nullptr as the second
+ argument.
+ */
+bool QmlDocVisitor::visit(QQmlJS::AST::UiObjectDefinition *definition)
+{
+ QString type = getFullyQualifiedId(definition->qualifiedTypeNameId);
+ m_nestingLevel++;
+ if (m_current->isNamespace()) {
+ auto component = applyDocumentation(definition->firstSourceLocation(), nullptr);
+ Q_ASSERT(component);
+ auto *qmlTypeNode = static_cast<QmlTypeNode *>(component);
+ if (!component->doc().isEmpty())
+ qmlTypeNode->setQmlBaseName(type);
+ qmlTypeNode->setTitle(m_name);
+ qmlTypeNode->setImportList(m_importList);
+ m_importList.clear();
+ m_current = qmlTypeNode;
+ }
+
+ return true;
+}
+
+/*!
+ End the visit of the object \a definition. In particular,
+ decrement the object nesting level, which is used to test
+ whether we are at the public API level. The public API
+ level is level 1. It won't decrement below 0.
+ */
+void QmlDocVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *definition)
+{
+ if (m_nestingLevel > 0) {
+ --m_nestingLevel;
+ }
+ m_lastEndOffset = definition->lastSourceLocation().end();
+}
+
+bool QmlDocVisitor::visit(QQmlJS::AST::UiImport *import)
+{
+ QString name = m_document.mid(import->fileNameToken.offset, import->fileNameToken.length);
+ if (name[0] == '\"')
+ name = name.mid(1, name.size() - 2);
+ QString version;
+ if (import->version) {
+ const auto start = import->version->firstSourceLocation().begin();
+ const auto end = import->version->lastSourceLocation().end();
+ version = m_document.mid(start, end - start);
+ }
+ QString importUri = getFullyQualifiedId(import->importUri);
+ m_importList.append(ImportRec(name, version, importUri));
+
+ return true;
+}
+
+void QmlDocVisitor::endVisit(QQmlJS::AST::UiImport *definition)
+{
+ m_lastEndOffset = definition->lastSourceLocation().end();
+}
+
+bool QmlDocVisitor::visit(QQmlJS::AST::UiObjectBinding *)
+{
+ ++m_nestingLevel;
+ return true;
+}
+
+void QmlDocVisitor::endVisit(QQmlJS::AST::UiObjectBinding *)
+{
+ --m_nestingLevel;
+}
+
+bool QmlDocVisitor::visit(QQmlJS::AST::UiArrayBinding *)
+{
+ return true;
+}
+
+void QmlDocVisitor::endVisit(QQmlJS::AST::UiArrayBinding *) {}
+
+static QString qualifiedIdToString(QQmlJS::AST::UiQualifiedId *node)
+{
+ QString s;
+
+ for (QQmlJS::AST::UiQualifiedId *it = node; it; it = it->next) {
+ s.append(it->name);
+
+ if (it->next)
+ s.append(QLatin1Char('.'));
+ }
+
+ return s;
+}
+
+/*!
+ Visits the public \a member declaration, which can be a
+ signal or a property. It is a custom signal or property.
+ Only visit the \a member if the nestingLevel is 1.
+ */
+bool QmlDocVisitor::visit(QQmlJS::AST::UiPublicMember *member)
+{
+ if (m_nestingLevel > 1) {
+ return true;
+ }
+ switch (member->type) {
+ case QQmlJS::AST::UiPublicMember::Signal: {
+ if (m_current->isQmlType()) {
+ auto *qmlType = static_cast<QmlTypeNode *>(m_current);
+ if (qmlType) {
+ FunctionNode::Metaness metaness = FunctionNode::QmlSignal;
+ QString name = member->name.toString();
+ auto *newSignal = new FunctionNode(metaness, m_current, name);
+ Parameters &parameters = newSignal->parameters();
+ for (QQmlJS::AST::UiParameterList *it = member->parameters; it; it = it->next) {
+ const QString type = it->type ? it->type->toString() : QString();
+ if (!type.isEmpty() && !it->name.isEmpty())
+ parameters.append(type, it->name.toString());
+ }
+ applyDocumentation(member->firstSourceLocation(), newSignal);
+ }
+ }
+ break;
+ }
+ case QQmlJS::AST::UiPublicMember::Property: {
+ QString type = qualifiedIdToString(member->memberType);
+ if (m_current->isQmlType()) {
+ auto *qmlType = static_cast<QmlTypeNode *>(m_current);
+ if (qmlType) {
+ QString name = member->name.toString();
+ QmlPropertyNode *qmlPropNode = qmlType->hasQmlProperty(name);
+ if (qmlPropNode == nullptr)
+ qmlPropNode = new QmlPropertyNode(qmlType, name, type, false);
+ qmlPropNode->markReadOnly(member->isReadonly());
+ if (member->isDefaultMember())
+ qmlPropNode->markDefault();
+ if (member->requiredToken().isValid())
+ qmlPropNode->setRequired();
+ applyDocumentation(member->firstSourceLocation(), qmlPropNode);
+ }
+ }
+ break;
+ }
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+/*!
+ End the visit of the \a member.
+ */
+void QmlDocVisitor::endVisit(QQmlJS::AST::UiPublicMember *member)
+{
+ m_lastEndOffset = member->lastSourceLocation().end();
+}
+
+bool QmlDocVisitor::visit(QQmlJS::AST::IdentifierPropertyName *)
+{
+ return true;
+}
+
+/*!
+ Begin the visit of the function declaration \a fd, but only
+ if the nesting level is 1.
+ */
+bool QmlDocVisitor::visit(QQmlJS::AST::FunctionDeclaration *fd)
+{
+ if (m_nestingLevel <= 1) {
+ FunctionNode::Metaness metaness = FunctionNode::QmlMethod;
+ if (!m_current->isQmlType())
+ return true;
+ QString name = fd->name.toString();
+ auto *method = new FunctionNode(metaness, m_current, name);
+ Parameters &parameters = method->parameters();
+ QQmlJS::AST::FormalParameterList *formals = fd->formals;
+ if (formals) {
+ QQmlJS::AST::FormalParameterList *fp = formals;
+ do {
+ QString defaultValue;
+ auto initializer = fp->element->initializer;
+ if (initializer) {
+ auto loc = initializer->firstSourceLocation();
+ defaultValue = m_document.mid(loc.begin(), loc.length);
+ }
+ parameters.append(QString(), fp->element->bindingIdentifier.toString(),
+ defaultValue);
+ fp = fp->next;
+ } while (fp && fp != formals);
+ }
+ applyDocumentation(fd->firstSourceLocation(), method);
+ }
+ return true;
+}
+
+/*!
+ End the visit of the function declaration, \a fd.
+ */
+void QmlDocVisitor::endVisit(QQmlJS::AST::FunctionDeclaration *fd)
+{
+ m_lastEndOffset = fd->lastSourceLocation().end();
+}
+
+/*!
+ Begin the visit of the signal handler declaration \a sb, but only
+ if the nesting level is 1.
+
+ This visit is now deprecated. It has been decided to document
+ public signals. If a signal handler must be discussed in the
+ documentation, that discussion must take place in the comment
+ for the signal.
+ */
+bool QmlDocVisitor::visit(QQmlJS::AST::UiScriptBinding *)
+{
+ return true;
+}
+
+void QmlDocVisitor::endVisit(QQmlJS::AST::UiScriptBinding *sb)
+{
+ m_lastEndOffset = sb->lastSourceLocation().end();
+}
+
+bool QmlDocVisitor::visit(QQmlJS::AST::UiQualifiedId *)
+{
+ return true;
+}
+
+void QmlDocVisitor::endVisit(QQmlJS::AST::UiQualifiedId *)
+{
+ // nothing.
+}
+
+void QmlDocVisitor::throwRecursionDepthError()
+{
+ hasRecursionDepthError = true;
+}
+
+bool QmlDocVisitor::hasError() const
+{
+ return hasRecursionDepthError;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/qmlvisitor.h b/src/qdoc/qdoc/src/qdoc/qmlvisitor.h
new file mode 100644
index 000000000..201dc0e61
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/qmlvisitor.h
@@ -0,0 +1,93 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QMLVISITOR_H
+#define QMLVISITOR_H
+
+#include "node.h"
+#include "qmltypenode.h"
+
+#include <QtCore/qstring.h>
+
+#include <private/qqmljsastvisitor_p.h>
+#include <private/qqmljsengine_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+
+struct QmlPropArgs
+{
+ QString m_type {};
+ QString m_module {};
+ QString m_component {};
+ QString m_name;
+
+ void clear()
+ {
+ m_type.clear();
+ m_module.clear();
+ m_component.clear();
+ m_name.clear();
+ }
+};
+
+class QmlDocVisitor : public QQmlJS::AST::Visitor
+{
+public:
+ QmlDocVisitor(const QString &filePath, const QString &code, QQmlJS::Engine *engine,
+ const QSet<QString> &commands, const QSet<QString> &topics);
+ ~QmlDocVisitor() override = default;
+
+ bool visit(QQmlJS::AST::UiImport *import) override;
+ void endVisit(QQmlJS::AST::UiImport *definition) override;
+
+ bool visit(QQmlJS::AST::UiObjectDefinition *definition) override;
+ void endVisit(QQmlJS::AST::UiObjectDefinition *definition) override;
+
+ bool visit(QQmlJS::AST::UiPublicMember *member) override;
+ void endVisit(QQmlJS::AST::UiPublicMember *definition) override;
+
+ bool visit(QQmlJS::AST::UiObjectBinding *) override;
+ void endVisit(QQmlJS::AST::UiObjectBinding *) override;
+ void endVisit(QQmlJS::AST::UiArrayBinding *) override;
+ bool visit(QQmlJS::AST::UiArrayBinding *) override;
+
+ bool visit(QQmlJS::AST::IdentifierPropertyName *idproperty) override;
+
+ bool visit(QQmlJS::AST::FunctionDeclaration *) override;
+ void endVisit(QQmlJS::AST::FunctionDeclaration *) override;
+
+ bool visit(QQmlJS::AST::UiScriptBinding *) override;
+ void endVisit(QQmlJS::AST::UiScriptBinding *) override;
+
+ bool visit(QQmlJS::AST::UiQualifiedId *) override;
+ void endVisit(QQmlJS::AST::UiQualifiedId *) override;
+
+ void throwRecursionDepthError() final;
+ [[nodiscard]] bool hasError() const;
+
+private:
+ QString getFullyQualifiedId(QQmlJS::AST::UiQualifiedId *id);
+ [[nodiscard]] QQmlJS::SourceLocation precedingComment(quint32 offset) const;
+ Node *applyDocumentation(QQmlJS::SourceLocation location, Node *node);
+ void applyMetacommands(QQmlJS::SourceLocation location, Node *node, Doc &doc);
+ bool splitQmlPropertyArg(const Doc &doc, const QString &arg, QmlPropArgs &qpa);
+
+ QQmlJS::Engine *m_engine { nullptr };
+ quint32 m_lastEndOffset {};
+ quint32 m_nestingLevel {};
+ QString m_filePath {};
+ QString m_name {};
+ QString m_document {};
+ ImportList m_importList {};
+ QSet<QString> m_commands {};
+ QSet<QString> m_topics {};
+ QSet<quint32> m_usedComments {};
+ Aggregate *m_current { nullptr };
+ bool hasRecursionDepthError { false };
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/quoter.cpp b/src/qdoc/qdoc/src/qdoc/quoter.cpp
new file mode 100644
index 000000000..37799a9e9
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/quoter.cpp
@@ -0,0 +1,338 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "quoter.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qregularexpression.h>
+
+QT_BEGIN_NAMESPACE
+
+QHash<QString, QString> Quoter::s_commentHash;
+
+static void replaceMultipleNewlines(QString &s)
+{
+ const qsizetype n = s.size();
+ bool slurping = false;
+ int j = -1;
+ const QChar newLine = QLatin1Char('\n');
+ QChar *d = s.data();
+ for (int i = 0; i != n; ++i) {
+ const QChar c = d[i];
+ bool hit = (c == newLine);
+ if (slurping && hit)
+ continue;
+ d[++j] = c;
+ slurping = hit;
+ }
+ s.resize(++j);
+}
+
+// This is equivalent to line.split( QRegularExpression("\n(?!\n|$)") ) but much faster
+QStringList Quoter::splitLines(const QString &line)
+{
+ QStringList result;
+ qsizetype i = line.size();
+ while (true) {
+ qsizetype j = i - 1;
+ while (j >= 0 && line.at(j) == QLatin1Char('\n'))
+ --j;
+ while (j >= 0 && line.at(j) != QLatin1Char('\n'))
+ --j;
+ result.prepend(line.mid(j + 1, i - j - 1));
+ if (j < 0)
+ break;
+ i = j;
+ }
+ return result;
+}
+
+/*
+ Transforms 'int x = 3 + 4' into 'int x=3+4'. A white space is kept
+ between 'int' and 'x' because it is meaningful in C++.
+*/
+static void trimWhiteSpace(QString &str)
+{
+ enum { Normal, MetAlnum, MetSpace } state = Normal;
+ const qsizetype n = str.size();
+
+ int j = -1;
+ QChar *d = str.data();
+ for (int i = 0; i != n; ++i) {
+ const QChar c = d[i];
+ if (c.isLetterOrNumber()) {
+ if (state == Normal) {
+ state = MetAlnum;
+ } else {
+ if (state == MetSpace)
+ str[++j] = c;
+ state = Normal;
+ }
+ str[++j] = c;
+ } else if (c.isSpace()) {
+ if (state == MetAlnum)
+ state = MetSpace;
+ } else {
+ state = Normal;
+ str[++j] = c;
+ }
+ }
+ str.resize(++j);
+}
+
+Quoter::Quoter() : m_silent(false)
+{
+ /* We're going to hard code these delimiters:
+ * C++, Qt, Qt Script, Java:
+ //! [<id>]
+ * .pro, .py, CMake files:
+ #! [<id>]
+ * .html, .qrc, .ui, .xq, .xml files:
+ <!-- [<id>] -->
+ */
+ if (s_commentHash.empty()) {
+ s_commentHash["pro"] = "#!";
+ s_commentHash["py"] = "#!";
+ s_commentHash["cmake"] = "#!";
+ s_commentHash["html"] = "<!--";
+ s_commentHash["qrc"] = "<!--";
+ s_commentHash["ui"] = "<!--";
+ s_commentHash["xml"] = "<!--";
+ s_commentHash["xq"] = "<!--";
+ }
+}
+
+void Quoter::reset()
+{
+ m_silent = false;
+ m_plainLines.clear();
+ m_markedLines.clear();
+ m_codeLocation = Location();
+}
+
+void Quoter::quoteFromFile(const QString &userFriendlyFilePath, const QString &plainCode,
+ const QString &markedCode)
+{
+ m_silent = false;
+
+ /*
+ Split the source code into logical lines. Empty lines are
+ treated specially. Before:
+
+ p->alpha();
+ p->beta();
+
+ p->gamma();
+
+
+ p->delta();
+
+ After:
+
+ p->alpha();
+ p->beta();\n
+ p->gamma();\n\n
+ p->delta();
+
+ Newlines are preserved because they affect codeLocation.
+ */
+ m_codeLocation = Location(userFriendlyFilePath);
+
+ m_plainLines = splitLines(plainCode);
+ m_markedLines = splitLines(markedCode);
+ if (m_markedLines.size() != m_plainLines.size()) {
+ m_codeLocation.warning(
+ QStringLiteral("Something is wrong with qdoc's handling of marked code"));
+ m_markedLines = m_plainLines;
+ }
+
+ /*
+ Squeeze blanks (cat -s).
+ */
+ for (auto &line : m_markedLines)
+ replaceMultipleNewlines(line);
+ m_codeLocation.start();
+}
+
+QString Quoter::quoteLine(const Location &docLocation, const QString &command,
+ const QString &pattern)
+{
+ if (m_plainLines.isEmpty()) {
+ failedAtEnd(docLocation, command);
+ return QString();
+ }
+
+ if (pattern.isEmpty()) {
+ docLocation.warning(QStringLiteral("Missing pattern after '\\%1'").arg(command));
+ return QString();
+ }
+
+ if (match(docLocation, pattern, m_plainLines.first()))
+ return getLine();
+
+ if (!m_silent) {
+ docLocation.warning(QStringLiteral("Command '\\%1' failed").arg(command));
+ m_codeLocation.warning(QStringLiteral("Pattern '%1' didn't match here").arg(pattern));
+ m_silent = true;
+ }
+ return QString();
+}
+
+QString Quoter::quoteSnippet(const Location &docLocation, const QString &identifier)
+{
+ QString comment = commentForCode();
+ QString delimiter = comment + QString(" [%1]").arg(identifier);
+ QString t;
+ int indent = 0;
+
+ while (!m_plainLines.isEmpty()) {
+ if (match(docLocation, delimiter, m_plainLines.first())) {
+ QString startLine = getLine();
+ while (indent < startLine.size() && startLine[indent] == QLatin1Char(' '))
+ indent++;
+ break;
+ }
+ getLine();
+ }
+ while (!m_plainLines.isEmpty()) {
+ QString line = m_plainLines.first();
+ if (match(docLocation, delimiter, line)) {
+ QString lastLine = getLine(indent);
+ qsizetype dIndex = lastLine.indexOf(delimiter);
+ if (dIndex > 0) {
+ // The delimiter might be preceded on the line by other
+ // delimeters, so look for the first comment on the line.
+ QString leading = lastLine.left(dIndex);
+ dIndex = leading.indexOf(comment);
+ if (dIndex != -1)
+ leading = leading.left(dIndex);
+ if (leading.endsWith(QLatin1String("<@comment>")))
+ leading.chop(10);
+ if (!leading.trimmed().isEmpty())
+ t += leading;
+ }
+ return t;
+ }
+
+ t += removeSpecialLines(line, comment, indent);
+ }
+ failedAtEnd(docLocation, QString("snippet (%1)").arg(delimiter));
+ return t;
+}
+
+QString Quoter::quoteTo(const Location &docLocation, const QString &command, const QString &pattern)
+{
+ QString t;
+ QString comment = commentForCode();
+
+ if (pattern.isEmpty()) {
+ while (!m_plainLines.isEmpty()) {
+ QString line = m_plainLines.first();
+ t += removeSpecialLines(line, comment);
+ }
+ } else {
+ while (!m_plainLines.isEmpty()) {
+ if (match(docLocation, pattern, m_plainLines.first())) {
+ return t;
+ }
+ t += getLine();
+ }
+ failedAtEnd(docLocation, command);
+ }
+ return t;
+}
+
+QString Quoter::quoteUntil(const Location &docLocation, const QString &command,
+ const QString &pattern)
+{
+ QString t = quoteTo(docLocation, command, pattern);
+ t += getLine();
+ return t;
+}
+
+QString Quoter::getLine(int unindent)
+{
+ if (m_plainLines.isEmpty())
+ return QString();
+
+ m_plainLines.removeFirst();
+
+ QString t = m_markedLines.takeFirst();
+ int i = 0;
+ while (i < unindent && i < t.size() && t[i] == QLatin1Char(' '))
+ i++;
+
+ t = t.mid(i);
+ t += QLatin1Char('\n');
+ m_codeLocation.advanceLines(t.count(QLatin1Char('\n')));
+ return t;
+}
+
+bool Quoter::match(const Location &docLocation, const QString &pattern0, const QString &line)
+{
+ QString str = line;
+ while (str.endsWith(QLatin1Char('\n')))
+ str.truncate(str.size() - 1);
+
+ QString pattern = pattern0;
+ if (pattern.startsWith(QLatin1Char('/')) && pattern.endsWith(QLatin1Char('/'))
+ && pattern.size() > 2) {
+ QRegularExpression rx(pattern.mid(1, pattern.size() - 2));
+ if (!m_silent && !rx.isValid()) {
+ docLocation.warning(
+ QStringLiteral("Invalid regular expression '%1'").arg(rx.pattern()));
+ m_silent = true;
+ }
+ return str.indexOf(rx) != -1;
+ }
+ trimWhiteSpace(str);
+ trimWhiteSpace(pattern);
+ return str.indexOf(pattern) != -1;
+}
+
+void Quoter::failedAtEnd(const Location &docLocation, const QString &command)
+{
+ if (!m_silent && !command.isEmpty()) {
+ if (m_codeLocation.filePath().isEmpty()) {
+ docLocation.warning(QStringLiteral("Unexpected '\\%1'").arg(command));
+ } else {
+ docLocation.warning(QStringLiteral("Command '\\%1' failed at end of file '%2'")
+ .arg(command, m_codeLocation.filePath()));
+ }
+ m_silent = true;
+ }
+}
+
+QString Quoter::commentForCode() const
+{
+ QFileInfo fi = QFileInfo(m_codeLocation.fileName());
+ if (fi.fileName() == "CMakeLists.txt")
+ return "#!";
+ return s_commentHash.value(fi.suffix(), "//!");
+}
+
+QString Quoter::removeSpecialLines(const QString &line, const QString &comment, int unindent)
+{
+ QString t;
+
+ // Remove special macros to support Qt namespacing.
+ QString trimmed = line.trimmed();
+ if (trimmed.startsWith("QT_BEGIN_NAMESPACE")) {
+ getLine();
+ } else if (trimmed.startsWith("QT_END_NAMESPACE")) {
+ getLine();
+ t += QLatin1Char('\n');
+ } else if (!trimmed.startsWith(comment)) {
+ // Ordinary code
+ t += getLine(unindent);
+ } else {
+ // Comments
+ if (line.contains(QLatin1Char('\n')))
+ t += QLatin1Char('\n');
+ getLine();
+ }
+ return t;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/quoter.h b/src/qdoc/qdoc/src/qdoc/quoter.h
new file mode 100644
index 000000000..087e9ffd2
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/quoter.h
@@ -0,0 +1,45 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef QUOTER_H
+#define QUOTER_H
+
+#include "location.h"
+
+#include <QtCore/qhash.h>
+#include <QtCore/qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+
+class Quoter
+{
+public:
+ Quoter();
+
+ void reset();
+ void quoteFromFile(const QString &userFriendlyFileName, const QString &plainCode,
+ const QString &markedCode);
+ QString quoteLine(const Location &docLocation, const QString &command, const QString &pattern);
+ QString quoteTo(const Location &docLocation, const QString &command, const QString &pattern);
+ QString quoteUntil(const Location &docLocation, const QString &command, const QString &pattern);
+ QString quoteSnippet(const Location &docLocation, const QString &identifier);
+
+ static QStringList splitLines(const QString &line);
+
+private:
+ QString getLine(int unindent = 0);
+ void failedAtEnd(const Location &docLocation, const QString &command);
+ bool match(const Location &docLocation, const QString &pattern, const QString &line);
+ [[nodiscard]] QString commentForCode() const;
+ QString removeSpecialLines(const QString &line, const QString &comment, int unindent = 0);
+
+ bool m_silent {};
+ QStringList m_plainLines {};
+ QStringList m_markedLines {};
+ Location m_codeLocation {};
+ static QHash<QString, QString> s_commentHash;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/relatedclass.cpp b/src/qdoc/qdoc/src/qdoc/relatedclass.cpp
new file mode 100644
index 000000000..3eea8df92
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/relatedclass.cpp
@@ -0,0 +1,46 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "relatedclass.h"
+
+#include "node.h"
+
+/*!
+ \struct RelatedClass
+ \brief A struct for indicating that a ClassNode is related in some way to another ClassNode.
+
+ This struct has nothing to do with the \c {\\relates} command. This struct
+ is used for indicating that a ClassNode is a base class of another ClassNode,
+ is a derived class of another ClassNode, or is an ignored base class of
+ another ClassNode. This struct is only used in ClassNode.
+*/
+
+/*! \fn RelatedClass::RelatedClass()
+ The default constructor does nothing. It is only used for allocating empty
+ instances of RelatedClass in containers.
+ */
+
+/*! \fn RelatedClass::RelatedClass(Access access, ClassNode *node)
+ This is the constructor used when the related class has been resolved.
+ In other words, when the ClassNode has been created so that \a node is
+ not \c nullptr.
+*/
+
+/*! \fn RelatedClass::RelatedClass(Access access, const QStringList &path, const QString &signature)
+ This is the constructor used when the related class has not bee resolved,
+ because it hasn't been created yet. In that case, we store the qualified
+ \a path name of the class and the \a signature of the class, which I think
+ is just the name of the class.
+
+ \note We might be able to simplify the whole RelatedClass concept. Maybe we
+ can get rid of it completely.
+*/
+
+/*! \fn bool RelatedClass::isPrivate() const
+ Returns \c true if this RelatedClass is marked as Access::Private.
+*/
+bool RelatedClass::isPrivate() const
+{
+ return (m_access == Access::Private);
+}
+
diff --git a/src/qdoc/qdoc/src/qdoc/relatedclass.h b/src/qdoc/qdoc/src/qdoc/relatedclass.h
new file mode 100644
index 000000000..9ca3b849a
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/relatedclass.h
@@ -0,0 +1,34 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef RELATEDCLASS_H
+#define RELATEDCLASS_H
+
+#include "access.h"
+
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+class ClassNode;
+
+struct RelatedClass
+{
+ RelatedClass() = default;
+ // constructor for resolved base class
+ RelatedClass(Access access, ClassNode *node) : m_access(access), m_node(node) {}
+ // constructor for unresolved base class
+ RelatedClass(Access access, QStringList path) : m_access(access), m_path(std::move(path)) { }
+ [[nodiscard]] bool isPrivate() const;
+
+ Access m_access {};
+ ClassNode *m_node { nullptr };
+ QStringList m_path {};
+};
+
+QT_END_NAMESPACE
+
+#endif // RELATEDCLASS_H
diff --git a/src/qdoc/qdoc/src/qdoc/sections.cpp b/src/qdoc/qdoc/src/qdoc/sections.cpp
new file mode 100644
index 000000000..bf066c08e
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/sections.cpp
@@ -0,0 +1,992 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "sections.h"
+
+#include "aggregate.h"
+#include "classnode.h"
+#include "config.h"
+#include "enumnode.h"
+#include "functionnode.h"
+#include "generator.h"
+#include "utilities.h"
+#include "namespacenode.h"
+#include "qmlpropertynode.h"
+#include "qmltypenode.h"
+#include "sharedcommentnode.h"
+#include "typedefnode.h"
+#include "variablenode.h"
+
+#include <QtCore/qobjectdefs.h>
+
+QT_BEGIN_NAMESPACE
+
+QList<Section> Sections::s_stdSummarySections {
+ { "Namespaces", "namespace", "namespaces", "", Section::Summary },
+ { "Classes", "class", "classes", "", Section::Summary },
+ { "Types", "type", "types", "", Section::Summary },
+ { "Variables", "variable", "variables", "", Section::Summary },
+ { "Static Variables", "static variable", "static variables", "", Section::Summary },
+ { "Functions", "function", "functions", "", Section::Summary },
+ { "Macros", "macro", "macros", "", Section::Summary },
+};
+
+QList<Section> Sections::s_stdDetailsSections {
+ { "Namespaces", "namespace", "namespaces", "nmspace", Section::Details },
+ { "Classes", "class", "classes", "classes", Section::Details },
+ { "Type Documentation", "type", "types", "types", Section::Details },
+ { "Variable Documentation", "variable", "variables", "vars", Section::Details },
+ { "Static Variables", "static variable", "static variables", QString(), Section::Details },
+ { "Function Documentation", "function", "functions", "func", Section::Details },
+ { "Macro Documentation", "macro", "macros", "macros", Section::Details },
+};
+
+QList<Section> Sections::s_stdCppClassSummarySections {
+ { "Public Types", "public type", "public types", "", Section::Summary },
+ { "Properties", "property", "properties", "", Section::Summary },
+ { "Public Functions", "public function", "public functions", "", Section::Summary },
+ { "Public Slots", "public slot", "public slots", "", Section::Summary },
+ { "Signals", "signal", "signals", "", Section::Summary },
+ { "Public Variables", "public variable", "public variables", "", Section::Summary },
+ { "Static Public Members", "static public member", "static public members", "", Section::Summary },
+ { "Protected Types", "protected type", "protected types", "", Section::Summary },
+ { "Protected Functions", "protected function", "protected functions", "", Section::Summary },
+ { "Protected Slots", "protected slot", "protected slots", "", Section::Summary },
+ { "Protected Variables", "protected type", "protected variables", "", Section::Summary },
+ { "Static Protected Members", "static protected member", "static protected members", "", Section::Summary },
+ { "Private Types", "private type", "private types", "", Section::Summary },
+ { "Private Functions", "private function", "private functions", "", Section::Summary },
+ { "Private Slots", "private slot", "private slots", "", Section::Summary },
+ { "Static Private Members", "static private member", "static private members", "", Section::Summary },
+ { "Related Non-Members", "related non-member", "related non-members", "", Section::Summary },
+ { "Macros", "macro", "macros", "", Section::Summary },
+};
+
+QList<Section> Sections::s_stdCppClassDetailsSections {
+ { "Member Type Documentation", "member", "members", "types", Section::Details },
+ { "Property Documentation", "member", "members", "prop", Section::Details },
+ { "Member Function Documentation", "member", "members", "func", Section::Details },
+ { "Member Variable Documentation", "member", "members", "vars", Section::Details },
+ { "Related Non-Members", "member", "members", "relnonmem", Section::Details },
+ { "Macro Documentation", "member", "members", "macros", Section::Details },
+};
+
+QList<Section> Sections::s_stdQmlTypeSummarySections {
+ { "Properties", "property", "properties", "", Section::Summary },
+ { "Attached Properties", "attached property", "attached properties", "", Section::Summary },
+ { "Signals", "signal", "signals", "", Section::Summary },
+ { "Signal Handlers", "signal handler", "signal handlers", "", Section::Summary },
+ { "Attached Signals", "attached signal", "attached signals", "", Section::Summary },
+ { "Methods", "method", "methods", "", Section::Summary },
+ { "Attached Methods", "attached method", "attached methods", "", Section::Summary },
+};
+
+QList<Section> Sections::s_stdQmlTypeDetailsSections {
+ { "Property Documentation", "member", "members", "qmlprop", Section::Details },
+ { "Attached Property Documentation", "member", "members", "qmlattprop", Section::Details },
+ { "Signal Documentation", "signal", "signals", "qmlsig", Section::Details },
+ { "Signal Handler Documentation", "signal handler", "signal handlers", "qmlsighan", Section::Details },
+ { "Attached Signal Documentation", "signal", "signals", "qmlattsig", Section::Details },
+ { "Method Documentation", "member", "members", "qmlmeth", Section::Details },
+ { "Attached Method Documentation", "member", "members", "qmlattmeth", Section::Details },
+};
+
+QList<Section> Sections::s_sinceSections {
+ { "New Namespaces", "", "", "", Section::Details },
+ { "New Classes", "", "", "", Section::Details },
+ { "New Member Functions", "", "", "", Section::Details },
+ { "New Functions in Namespaces", "", "", "", Section::Details },
+ { "New Global Functions", "", "", "", Section::Details },
+ { "New Macros", "", "", "", Section::Details },
+ { "New Enum Types", "", "", "", Section::Details },
+ { "New Enum Values", "", "", "", Section::Details },
+ { "New Type Aliases", "", "", "", Section::Details },
+ { "New Properties", "", "", "", Section::Details },
+ { "New Variables", "", "", "", Section::Details },
+ { "New QML Types", "", "", "", Section::Details },
+ { "New QML Properties", "", "", "", Section::Details },
+ { "New QML Signals", "", "", "", Section::Details },
+ { "New QML Signal Handlers", "", "", "", Section::Details },
+ { "New QML Methods", "", "", "", Section::Details },
+};
+
+QList<Section> Sections::s_allMembers{ { "", "member", "members", "", Section::AllMembers } };
+
+/*!
+ \class Section
+ \brief A class for containing the elements of one documentation section
+ */
+
+/*!
+ The destructor must delete the members of collections
+ when the members are allocated on the heap.
+ */
+Section::~Section()
+{
+ clear();
+}
+
+/*!
+ A Section is now an element in a static vector, so we
+ don't have to repeatedly construct and destroy them. But
+ we do need to clear them before each call to build the
+ sections for a C++ or QML entity.
+ */
+void Section::clear()
+{
+ m_reimplementedMemberMap.clear();
+ m_members.clear();
+ m_obsoleteMembers.clear();
+ m_reimplementedMembers.clear();
+ m_inheritedMembers.clear();
+ m_classNodesList.clear();
+ m_aggregate = nullptr;
+}
+
+/*!
+ Construct a name for the \a node that can be used for sorting
+ a set of nodes into equivalence classes.
+ */
+QString sortName(const Node *node)
+{
+ QString nodeName{node->name()};
+
+ int numDigits = 0;
+ for (qsizetype i = nodeName.size() - 1; i > 0; --i) {
+ if (nodeName.at(i).digitValue() == -1)
+ break;
+ ++numDigits;
+ }
+
+ // we want 'qint8' to appear before 'qint16'
+ if (numDigits > 0) {
+ for (int i = 0; i < 4 - numDigits; ++i)
+ nodeName.insert(nodeName.size() - numDigits - 1, QLatin1Char('0'));
+ }
+
+ if (node->isClassNode())
+ return QLatin1Char('A') + nodeName;
+
+ if (node->isFunction(Node::CPP)) {
+ const auto *fn = static_cast<const FunctionNode *>(node);
+
+ QString sortNo;
+ if (fn->isCtor())
+ sortNo = QLatin1String("C");
+ else if (fn->isCCtor())
+ sortNo = QLatin1String("D");
+ else if (fn->isMCtor())
+ sortNo = QLatin1String("E");
+ else if (fn->isDtor())
+ sortNo = QLatin1String("F");
+ else if (nodeName.startsWith(QLatin1String("operator")) && nodeName.size() > 8
+ && !nodeName[8].isLetterOrNumber())
+ sortNo = QLatin1String("H");
+ else
+ sortNo = QLatin1String("G");
+
+ return sortNo + nodeName + QLatin1Char(' ') + QString::number(fn->overloadNumber(), 36);
+ }
+
+ if (node->isFunction(Node::QML))
+ return QLatin1Char('E') + nodeName + QLatin1Char(' ') +
+ QString::number(static_cast<const FunctionNode*>(node)->overloadNumber(), 36);
+
+ if (node->isProperty() || node->isVariable())
+ return QLatin1Char('G') + nodeName;
+
+ return QLatin1Char('B') + nodeName;
+}
+
+/*!
+ Inserts the \a node into this section if it is appropriate
+ for this section.
+ */
+void Section::insert(Node *node)
+{
+ bool irrelevant = false;
+ bool inherited = false;
+ if (!node->isRelatedNonmember()) {
+ Aggregate *p = node->parent();
+ if (!p->isNamespace() && p != m_aggregate) {
+ if (!p->isQmlType() || !p->isAbstract())
+ inherited = true;
+ }
+ }
+
+ if (node->isPrivate() || node->isInternal()) {
+ irrelevant = true;
+ } else if (node->isFunction()) {
+ auto *func = static_cast<FunctionNode *>(node);
+ irrelevant = (inherited && (func->isSomeCtor() || func->isDtor()));
+ } else if (node->isClassNode() || node->isEnumType() || node->isTypedef()
+ || node->isVariable()) {
+ irrelevant = (inherited && m_style != AllMembers);
+ if (!irrelevant && m_style == Details && node->isTypedef()) {
+ const auto *tdn = static_cast<const TypedefNode *>(node);
+ if (tdn->associatedEnum())
+ irrelevant = true;
+ }
+ }
+
+ if (!irrelevant) {
+ QString key = sortName(node);
+ if (node->isDeprecated()) {
+ m_obsoleteMembers.push_back(node);
+ } else {
+ if (!inherited || m_style == AllMembers)
+ m_members.push_back(node);
+
+ if (inherited && (node->parent()->isClassNode() || node->parent()->isNamespace())) {
+ if (m_inheritedMembers.isEmpty()
+ || m_inheritedMembers.last().first != node->parent()) {
+ std::pair<Aggregate *, int> p(node->parent(), 0);
+ m_inheritedMembers.append(p);
+ }
+ m_inheritedMembers.last().second++;
+ }
+ }
+ }
+}
+
+/*!
+ Returns \c true if the \a node is a reimplemented member
+ function of the current class. If true, the \a node is
+ inserted into the reimplemented member map. True
+ is returned only if \a node is inserted into the map.
+ That is, false is returned if the \a node is already in
+ the map.
+ */
+bool Section::insertReimplementedMember(Node *node)
+{
+ if (!node->isPrivate() && !node->isRelatedNonmember()) {
+ const auto *fn = static_cast<const FunctionNode *>(node);
+ if (!fn->overridesThis().isEmpty()) {
+ if (fn->parent() == m_aggregate) {
+ QString key = sortName(fn);
+ if (!m_reimplementedMemberMap.contains(key)) {
+ m_reimplementedMemberMap.insert(key, node);
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/*!
+ If this section is not empty, convert its maps to sequential
+ structures for better traversal during doc generation.
+ */
+void Section::reduce()
+{
+ // TODO:TEMPORARY:INTERMEDITATE: Section uses a series of maps
+ // to internally manage the categorization of the various members
+ // of an aggregate. It further uses a secondary "flattened"
+ // (usually vector) version that is later used by consumers of a
+ // Section content.
+ //
+ // One of the uses of those maps is that of ordering, by using
+ // keys generated with `sortName`.
+ // Nonetheless, this is the only usage that comes from the keys,
+ // as they are neither necessary nor used outside of the internal
+ // code for Section.
+ //
+ // Hence, the codebase is moving towards removing the maps in
+ // favor of building a flattened, consumer ready, version of the
+ // categorization directly, cutting the intermediate conversion
+ // step.
+ //
+ // To do so while keeping as much of the old behavior as possible,
+ // we provide a sorting for the flattened version that is based on
+ // `sortName`, as the previous ordering was.
+ //
+ // This acts as a relatively heavy pessimization, as `sortName`,
+ // used as a comparator, can be called multiple times for each
+ // Node, while before it would have been called almost-once.
+ //
+ // Instead of fixing this issue, by for example caching the
+ // sortName of each Node instance, we temporarily keep the
+ // pessimization while the various maps are removed.
+ //
+ // When all the maps are removed, we can remove `sortName`, which
+ // produces strings to use as key requiring a few allocations and
+ // expensive operations, with an actual comparator function, which
+ // should be more lightweight and more than offset the
+ // multiple-calls.
+ static auto node_less_than = [](const Node* left, const Node* right) {
+ return sortName(left) < sortName(right);
+ };
+
+ std::stable_sort(m_members.begin(), m_members.end(), node_less_than);
+ std::stable_sort(m_obsoleteMembers.begin(), m_obsoleteMembers.end(), node_less_than);
+
+ m_reimplementedMembers = m_reimplementedMemberMap.values().toVector();
+
+ for (auto &cn : m_classNodesList) {
+ std::stable_sort(cn.second.begin(), cn.second.end(), node_less_than);
+ }
+}
+
+/*!
+ \class Sections
+ \brief A class for creating vectors of collections for documentation
+
+ Each element in a vector is an instance of Section, which
+ contains all the elements that will be documented in one
+ section of a reference documentation page.
+ */
+
+/*!
+ This constructor builds the vectors of sections based on the
+ type of the \a aggregate node.
+ */
+Sections::Sections(Aggregate *aggregate) : m_aggregate(aggregate)
+{
+ initAggregate(s_allMembers, m_aggregate);
+ switch (m_aggregate->nodeType()) {
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ initAggregate(s_stdCppClassSummarySections, m_aggregate);
+ initAggregate(s_stdCppClassDetailsSections, m_aggregate);
+ buildStdCppClassRefPageSections();
+ break;
+ case Node::QmlType:
+ case Node::QmlValueType:
+ initAggregate(s_stdQmlTypeSummarySections, m_aggregate);
+ initAggregate(s_stdQmlTypeDetailsSections, m_aggregate);
+ buildStdQmlTypeRefPageSections();
+ break;
+ case Node::Namespace:
+ case Node::HeaderFile:
+ case Node::Proxy:
+ default:
+ initAggregate(s_stdSummarySections, m_aggregate);
+ initAggregate(s_stdDetailsSections, m_aggregate);
+ buildStdRefPageSections();
+ break;
+ }
+}
+
+/*!
+ This constructor builds a vector of sections from the \e since
+ node map, \a nsmap
+ */
+Sections::Sections(const NodeMultiMap &nsmap) : m_aggregate(nullptr)
+{
+ if (nsmap.isEmpty())
+ return;
+ SectionVector &sections = sinceSections();
+ for (auto it = nsmap.constBegin(); it != nsmap.constEnd(); ++it) {
+ Node *node = it.value();
+ switch (node->nodeType()) {
+ case Node::QmlType:
+ sections[SinceQmlTypes].appendMember(node);
+ break;
+ case Node::Namespace:
+ sections[SinceNamespaces].appendMember(node);
+ break;
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ sections[SinceClasses].appendMember(node);
+ break;
+ case Node::Enum: {
+ // The map can contain an enum node with \since, or an enum node
+ // with \value containing a since-clause. In the latter case,
+ // key() is an empty string.
+ if (!it.key().isEmpty())
+ sections[SinceEnumTypes].appendMember(node);
+ else
+ sections[SinceEnumValues].appendMember(node);
+ break;
+ }
+ case Node::Typedef:
+ case Node::TypeAlias:
+ sections[SinceTypeAliases].appendMember(node);
+ break;
+ case Node::Function: {
+ const auto *fn = static_cast<const FunctionNode *>(node);
+ switch (fn->metaness()) {
+ case FunctionNode::QmlSignal:
+ sections[SinceQmlSignals].appendMember(node);
+ break;
+ case FunctionNode::QmlSignalHandler:
+ sections[SinceQmlSignalHandlers].appendMember(node);
+ break;
+ case FunctionNode::QmlMethod:
+ sections[SinceQmlMethods].appendMember(node);
+ break;
+ default:
+ if (fn->isMacro())
+ sections[SinceMacros].appendMember(node);
+ else {
+ Node *p = fn->parent();
+ if (p) {
+ if (p->isClassNode())
+ sections[SinceMemberFunctions].appendMember(node);
+ else if (p->isNamespace()) {
+ if (p->name().isEmpty())
+ sections[SinceGlobalFunctions].appendMember(node);
+ else
+ sections[SinceNamespaceFunctions].appendMember(node);
+ } else
+ sections[SinceGlobalFunctions].appendMember(node);
+ } else
+ sections[SinceGlobalFunctions].appendMember(node);
+ }
+ break;
+ }
+ break;
+ }
+ case Node::Property:
+ sections[SinceProperties].appendMember(node);
+ break;
+ case Node::Variable:
+ sections[SinceVariables].appendMember(node);
+ break;
+ case Node::QmlProperty:
+ sections[SinceQmlProperties].appendMember(node);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+/*!
+ The behavior of the destructor depends on the type of the
+ Aggregate node that was passed to the constructor. If the
+ constructor was passed a multimap, the destruction is a
+ bit different because there was no Aggregate node.
+ */
+Sections::~Sections()
+{
+ if (m_aggregate) {
+ switch (m_aggregate->nodeType()) {
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ clear(stdCppClassSummarySections());
+ clear(stdCppClassDetailsSections());
+ allMembersSection().clear();
+ break;
+ case Node::QmlType:
+ case Node::QmlValueType:
+ clear(stdQmlTypeSummarySections());
+ clear(stdQmlTypeDetailsSections());
+ allMembersSection().clear();
+ break;
+ default:
+ clear(stdSummarySections());
+ clear(stdDetailsSections());
+ allMembersSection().clear();
+ break;
+ }
+ m_aggregate = nullptr;
+ } else {
+ clear(sinceSections());
+ }
+}
+
+/*!
+ Initialize the Aggregate in each Section of vector \a v with \a aggregate.
+ */
+void Sections::initAggregate(SectionVector &v, Aggregate *aggregate)
+{
+ for (Section &section : v)
+ section.setAggregate(aggregate);
+}
+
+/*!
+ Reset each Section in vector \a v to its initialized state.
+ */
+void Sections::clear(QList<Section> &v)
+{
+ for (Section &section : v)
+ section.clear();
+}
+
+/*!
+ Linearize the maps in each Section in \a v.
+ */
+void Sections::reduce(QList<Section> &v)
+{
+ for (Section &section : v)
+ section.reduce();
+}
+
+/*!
+ This is a private helper function for buildStdRefPageSections().
+ */
+void Sections::stdRefPageSwitch(SectionVector &v, Node *n, Node *t)
+{
+ // t is the reference node to be tested, n is the node to be distributed.
+ // t differs from n only for shared comment nodes.
+ if (!t)
+ t = n;
+
+ switch (t->nodeType()) {
+ case Node::Namespace:
+ v[StdNamespaces].insert(n);
+ return;
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ v[StdClasses].insert(n);
+ return;
+ case Node::Enum:
+ case Node::Typedef:
+ case Node::TypeAlias:
+ v[StdTypes].insert(n);
+ return;
+ case Node::Function: {
+ auto *func = static_cast<FunctionNode *>(t);
+ if (func->isMacro())
+ v[StdMacros].insert(n);
+ else
+ v[StdFunctions].insert(n);
+ }
+ return;
+ case Node::Variable: {
+ const auto *var = static_cast<const VariableNode *>(t);
+ if (!var->doc().isEmpty()) {
+ if (var->isStatic())
+ v[StdStaticVariables].insert(n);
+ else
+ v[StdVariables].insert(n);
+ }
+ }
+ return;
+ case Node::SharedComment: {
+ auto *scn = static_cast<SharedCommentNode *>(t);
+ if (!scn->doc().isEmpty() && scn->collective().size())
+ stdRefPageSwitch(
+ v, scn,
+ scn->collective().first()); // TODO: warn about mixed node types in collective?
+ }
+ return;
+ default:
+ return;
+ }
+}
+
+/*!
+ Build the section vectors for a standard reference page,
+ when the aggregate node is not a C++ class or a QML type.
+
+ If this is for a namespace page then if the namespace node
+ itself does not have documentation, only its children that
+ have documentation should be documented. In other words,
+ there are cases where a namespace is declared but does not
+ have documentation, but some of the elements declared in
+ that namespace do have documentation.
+
+ This special processing of namespaces that do not have a
+ documentation comment is meant to allow documenting its
+ members that do have documentation while avoiding posting
+ error messages for its members that are not documented.
+ */
+void Sections::buildStdRefPageSections()
+{
+ const NamespaceNode *ns = nullptr;
+ bool documentAll = true; // document all the children
+ if (m_aggregate->isNamespace()) {
+ ns = static_cast<const NamespaceNode *>(m_aggregate);
+ if (!ns->hasDoc())
+ documentAll = false; // only document children that have documentation
+ }
+ for (auto it = m_aggregate->constBegin(); it != m_aggregate->constEnd(); ++it) {
+ Node *n = *it;
+ if (documentAll || n->hasDoc()) {
+ stdRefPageSwitch(stdSummarySections(), n);
+ stdRefPageSwitch(stdDetailsSections(), n);
+ }
+ }
+ if (!m_aggregate->relatedByProxy().isEmpty()) {
+ const QList<Node *> &relatedBy = m_aggregate->relatedByProxy();
+ for (const auto &node : relatedBy)
+ stdRefPageSwitch(stdSummarySections(), node);
+ }
+ /*
+ If we are building the sections for the reference page
+ for a namespace node, include all the namespace node's
+ included children in the sections.
+ */
+ if (ns && !ns->includedChildren().isEmpty()) {
+ const QList<Node *> &children = ns->includedChildren();
+ for (const auto &child : children) {
+ if (documentAll || child->hasDoc())
+ stdRefPageSwitch(stdSummarySections(), child);
+ }
+ }
+ reduce(stdSummarySections());
+ reduce(stdDetailsSections());
+ allMembersSection().reduce();
+}
+
+/*!
+ Inserts the node \a n in one of the entries in the vector \a v
+ depending on the node's type, access attribute, and a few other
+ attributes if the node is a signal, slot, or function.
+ */
+void Sections::distributeNodeInSummaryVector(SectionVector &sv, Node *n)
+{
+ if (n->isSharedCommentNode())
+ return;
+ if (n->isFunction()) {
+ auto *fn = static_cast<FunctionNode *>(n);
+ if (fn->isRelatedNonmember()) {
+ if (fn->isMacro())
+ sv[Macros].insert(n);
+ else
+ sv[RelatedNonmembers].insert(n);
+ return;
+ }
+ if (fn->isIgnored())
+ return;
+ if (fn->isSlot()) {
+ if (fn->isPublic())
+ sv[PublicSlots].insert(fn);
+ else if (fn->isPrivate())
+ sv[PrivateSlots].insert(fn);
+ else
+ sv[ProtectedSlots].insert(fn);
+ } else if (fn->isSignal()) {
+ if (fn->isPublic())
+ sv[Signals].insert(fn);
+ } else if (fn->isPublic()) {
+ if (fn->isStatic())
+ sv[StaticPublicMembers].insert(fn);
+ else if (!sv[PublicFunctions].insertReimplementedMember(fn))
+ sv[PublicFunctions].insert(fn);
+ } else if (fn->isPrivate()) {
+ if (fn->isStatic())
+ sv[StaticPrivateMembers].insert(fn);
+ else if (!sv[PrivateFunctions].insertReimplementedMember(fn))
+ sv[PrivateFunctions].insert(fn);
+ } else { // protected
+ if (fn->isStatic())
+ sv[StaticProtectedMembers].insert(fn);
+ else if (!sv[ProtectedFunctions].insertReimplementedMember(fn))
+ sv[ProtectedFunctions].insert(fn);
+ }
+ return;
+ }
+ if (n->isRelatedNonmember()) {
+ sv[RelatedNonmembers].insert(n);
+ return;
+ }
+ if (n->isVariable()) {
+ if (n->isStatic()) {
+ if (n->isPublic())
+ sv[StaticPublicMembers].insert(n);
+ else if (n->isPrivate())
+ sv[StaticPrivateMembers].insert(n);
+ else
+ sv[StaticProtectedMembers].insert(n);
+ } else {
+ if (n->isPublic())
+ sv[PublicVariables].insert(n);
+ else if (!n->isPrivate())
+ sv[ProtectedVariables].insert(n);
+ }
+ return;
+ }
+ /*
+ Getting this far means the node is either a property
+ or some kind of type, like an enum or a typedef.
+ */
+ if (n->isTypedef() && (n->name() == QLatin1String("QtGadgetHelper")))
+ return;
+ if (n->isProperty())
+ sv[Properties].insert(n);
+ else if (n->isPublic())
+ sv[PublicTypes].insert(n);
+ else if (n->isPrivate())
+ sv[PrivateTypes].insert(n);
+ else
+ sv[ProtectedTypes].insert(n);
+}
+
+/*!
+ Inserts the node \a n in one of the entries in the vector \a v
+ depending on the node's type, access attribute, and a few other
+ attributes if the node is a signal, slot, or function.
+ */
+void Sections::distributeNodeInDetailsVector(SectionVector &dv, Node *n)
+{
+ if (n->isSharingComment())
+ return;
+
+ // t is the reference node to be tested - typically it's this node (n), but for
+ // shared comment nodes we need to distribute based on the nodes in its collective.
+ Node *t = n;
+
+ if (n->isSharedCommentNode() && n->hasDoc()) {
+ auto *scn = static_cast<SharedCommentNode *>(n);
+ if (scn->collective().size())
+ t = scn->collective().first(); // TODO: warn about mixed node types in collective?
+ }
+
+ if (t->isFunction()) {
+ auto *fn = static_cast<FunctionNode *>(t);
+ if (fn->isRelatedNonmember()) {
+ if (fn->isMacro())
+ dv[DetailsMacros].insert(n);
+ else
+ dv[DetailsRelatedNonmembers].insert(n);
+ return;
+ }
+ if (fn->isIgnored())
+ return;
+ if (!fn->hasAssociatedProperties() || !fn->doc().isEmpty())
+ dv[DetailsMemberFunctions].insert(n);
+ return;
+ }
+ if (t->isRelatedNonmember()) {
+ dv[DetailsRelatedNonmembers].insert(n);
+ return;
+ }
+ if (t->isEnumType() || t->isTypedef()) {
+ if (t->name() != QLatin1String("QtGadgetHelper"))
+ dv[DetailsMemberTypes].insert(n);
+ return;
+ }
+ if (t->isProperty())
+ dv[DetailsProperties].insert(n);
+ else if (t->isVariable() && !t->doc().isEmpty())
+ dv[DetailsMemberVariables].insert(n);
+}
+
+void Sections::distributeQmlNodeInDetailsVector(SectionVector &dv, Node *n)
+{
+ if (n->isSharingComment())
+ return;
+
+ // t is the reference node to be tested - typically it's this node (n), but for
+ // shared comment nodes we need to distribute based on the nodes in its collective.
+ Node *t = n;
+
+ if (n->isSharedCommentNode() && n->hasDoc()) {
+ if (n->isPropertyGroup()) {
+ dv[QmlProperties].insert(n);
+ return;
+ }
+ auto *scn = static_cast<SharedCommentNode *>(n);
+ if (scn->collective().size())
+ t = scn->collective().first(); // TODO: warn about mixed node types in collective?
+ }
+
+ if (t->isQmlProperty()) {
+ auto *pn = static_cast<QmlPropertyNode *>(t);
+ if (pn->isAttached())
+ dv[QmlAttachedProperties].insert(n);
+ else
+ dv[QmlProperties].insert(n);
+ } else if (t->isFunction()) {
+ auto *fn = static_cast<FunctionNode *>(t);
+ if (fn->isQmlSignal()) {
+ if (fn->isAttached())
+ dv[QmlAttachedSignals].insert(n);
+ else
+ dv[QmlSignals].insert(n);
+ } else if (fn->isQmlSignalHandler()) {
+ dv[QmlSignalHandlers].insert(n);
+ } else if (fn->isQmlMethod()) {
+ if (fn->isAttached())
+ dv[QmlAttachedMethods].insert(n);
+ else
+ dv[QmlMethods].insert(n);
+ }
+ }
+}
+
+/*!
+ Distributes a node \a n into the correct place in the summary section vector \a sv.
+ Nodes that are sharing a comment are handled recursively - for recursion, the \a
+ sharing parameter is set to \c true.
+ */
+void Sections::distributeQmlNodeInSummaryVector(SectionVector &sv, Node *n, bool sharing)
+{
+ if (n->isSharingComment() && !sharing)
+ return;
+ if (n->isQmlProperty()) {
+ auto *pn = static_cast<QmlPropertyNode *>(n);
+ if (pn->isAttached())
+ sv[QmlAttachedProperties].insert(pn);
+ else
+ sv[QmlProperties].insert(pn);
+ } else if (n->isFunction()) {
+ auto *fn = static_cast<FunctionNode *>(n);
+ if (fn->isQmlSignal()) {
+ if (fn->isAttached())
+ sv[QmlAttachedSignals].insert(fn);
+ else
+ sv[QmlSignals].insert(fn);
+ } else if (fn->isQmlSignalHandler()) {
+ sv[QmlSignalHandlers].insert(fn);
+ } else if (fn->isQmlMethod()) {
+ if (fn->isAttached())
+ sv[QmlAttachedMethods].insert(fn);
+ else
+ sv[QmlMethods].insert(fn);
+ }
+ } else if (n->isSharedCommentNode()) {
+ auto *scn = static_cast<SharedCommentNode *>(n);
+ if (scn->isPropertyGroup()) {
+ sv[QmlProperties].insert(scn);
+ } else {
+ for (const auto &child : scn->collective())
+ distributeQmlNodeInSummaryVector(sv, child, true);
+ }
+ }
+}
+
+static void pushBaseClasses(QStack<ClassNode *> &stack, ClassNode *cn)
+{
+ const QList<RelatedClass> baseClasses = cn->baseClasses();
+ for (const auto &cls : baseClasses) {
+ if (cls.m_node)
+ stack.prepend(cls.m_node);
+ }
+}
+
+/*!
+ Build the section vectors for a standard reference page,
+ when the aggregate node is a C++.
+ */
+void Sections::buildStdCppClassRefPageSections()
+{
+ SectionVector &summarySections = stdCppClassSummarySections();
+ SectionVector &detailsSections = stdCppClassDetailsSections();
+ Section &allMembers = allMembersSection();
+
+ for (auto it = m_aggregate->constBegin(); it != m_aggregate->constEnd(); ++it) {
+ Node *n = *it;
+ if (!n->isPrivate() && !n->isProperty() && !n->isRelatedNonmember()
+ && !n->isSharedCommentNode())
+ allMembers.insert(n);
+
+ distributeNodeInSummaryVector(summarySections, n);
+ distributeNodeInDetailsVector(detailsSections, n);
+ }
+ if (!m_aggregate->relatedByProxy().isEmpty()) {
+ const QList<Node *> relatedBy = m_aggregate->relatedByProxy();
+ for (const auto &node : relatedBy)
+ distributeNodeInSummaryVector(summarySections, node);
+ }
+
+ QStack<ClassNode *> stack;
+ auto *cn = static_cast<ClassNode *>(m_aggregate);
+ pushBaseClasses(stack, cn);
+ while (!stack.isEmpty()) {
+ ClassNode *cn = stack.pop();
+ for (auto it = cn->constBegin(); it != cn->constEnd(); ++it) {
+ Node *n = *it;
+ if (!n->isPrivate() && !n->isProperty() && !n->isRelatedNonmember()
+ && !n->isSharedCommentNode())
+ allMembers.insert(n);
+ }
+ pushBaseClasses(stack, cn);
+ }
+ reduce(summarySections);
+ reduce(detailsSections);
+ allMembers.reduce();
+}
+
+/*!
+ Build the section vectors for a standard reference page,
+ when the aggregate node is a QML type.
+ */
+void Sections::buildStdQmlTypeRefPageSections()
+{
+ ClassNodes *classNodes = nullptr;
+ SectionVector &summarySections = stdQmlTypeSummarySections();
+ SectionVector &detailsSections = stdQmlTypeDetailsSections();
+ Section &allMembers = allMembersSection();
+
+ const Aggregate *qtn = m_aggregate;
+ while (qtn) {
+ if (!qtn->isAbstract() || !classNodes)
+ classNodes = &allMembers.classNodesList().emplace_back(static_cast<const QmlTypeNode*>(qtn), NodeVector{});
+ for (const auto n : qtn->childNodes()) {
+ if (n->isInternal())
+ continue;
+
+ // Skip overridden property/function documentation from abstract base type
+ if (qtn != m_aggregate && qtn->isAbstract()) {
+ NodeList candidates;
+ m_aggregate->findChildren(n->name(), candidates);
+ if (std::any_of(candidates.cbegin(), candidates.cend(), [&n](const Node *c) {
+ if (c->nodeType() == n->nodeType()) {
+ if (!n->isFunction() ||
+ compare(static_cast<const FunctionNode *>(n),
+ static_cast<const FunctionNode *>(c)) == 0)
+ return true;
+ }
+ return false;
+ })) {
+ continue;
+ }
+ }
+
+ if (!n->isSharedCommentNode() || n->isPropertyGroup()) {
+ allMembers.insert(n);
+ classNodes->second.push_back(n);
+ }
+
+
+ if (qtn == m_aggregate || qtn->isAbstract()) {
+ distributeQmlNodeInSummaryVector(summarySections, n);
+ distributeQmlNodeInDetailsVector(detailsSections, n);
+ }
+ }
+ if (qtn->qmlBaseNode() == qtn) {
+ qCDebug(lcQdoc, "error: circular type definition: '%s' inherits itself",
+ qPrintable(qtn->name()));
+ break;
+ }
+ qtn = static_cast<QmlTypeNode *>(qtn->qmlBaseNode());
+ }
+
+ reduce(summarySections);
+ reduce(detailsSections);
+ allMembers.reduce();
+}
+
+/*!
+ Returns true if any sections in this object contain obsolete
+ members. If it returns false, then \a summary_spv and \a details_spv
+ have not been modified. Otherwise, both vectors will contain pointers
+ to the sections that contain obsolete members.
+ */
+bool Sections::hasObsoleteMembers(SectionPtrVector *summary_spv,
+ SectionPtrVector *details_spv) const
+{
+ const SectionVector *sections = nullptr;
+ if (m_aggregate->isClassNode())
+ sections = &stdCppClassSummarySections();
+ else if (m_aggregate->isQmlType())
+ sections = &stdQmlTypeSummarySections();
+ else
+ sections = &stdSummarySections();
+ for (const auto &section : *sections) {
+ if (!section.obsoleteMembers().isEmpty())
+ summary_spv->append(&section);
+ }
+ if (m_aggregate->isClassNode())
+ sections = &stdCppClassDetailsSections();
+ else if (m_aggregate->isQmlType())
+ sections = &stdQmlTypeDetailsSections();
+ else
+ sections = &stdDetailsSections();
+ for (const auto &it : *sections) {
+ if (!it.obsoleteMembers().isEmpty())
+ details_spv->append(&it);
+ }
+ return !summary_spv->isEmpty();
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/sections.h b/src/qdoc/qdoc/src/qdoc/sections.h
new file mode 100644
index 000000000..70c73a91c
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/sections.h
@@ -0,0 +1,206 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef SECTIONS_H
+#define SECTIONS_H
+
+#include "node.h"
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+
+typedef std::pair<const QmlTypeNode *, NodeVector> ClassNodes;
+typedef QList<ClassNodes> ClassNodesList;
+
+class Section
+{
+public:
+ enum Style { Summary, Details, AllMembers, Accessors };
+
+public:
+ Section(
+ QString title, QString singular, QString plural,
+ QString divclass, Style style
+ ) : m_title{title}, m_singular{singular}, m_plural{plural},
+ m_divClass{divclass}, m_style{style}
+ {}
+
+ ~Section();
+
+ void insert(Node *node);
+ bool insertReimplementedMember(Node *node);
+
+ void appendMember(Node *node) { m_members.append(node); }
+
+ void clear();
+ void reduce();
+ [[nodiscard]] bool isEmpty() const
+ {
+ return (m_members.isEmpty() && m_inheritedMembers.isEmpty()
+ && m_reimplementedMemberMap.isEmpty() && m_classNodesList.isEmpty());
+ }
+
+ [[nodiscard]] Style style() const { return m_style; }
+ [[nodiscard]] const QString &title() const { return m_title; }
+ [[nodiscard]] const QString &divClass() const { return m_divClass; }
+ [[nodiscard]] const QString &singular() const { return m_singular; }
+ [[nodiscard]] const QString &plural() const { return m_plural; }
+ [[nodiscard]] const NodeVector &members() const { return m_members; }
+ [[nodiscard]] const NodeVector &reimplementedMembers() const { return m_reimplementedMembers; }
+ [[nodiscard]] const QList<std::pair<Aggregate *, int>> &inheritedMembers() const
+ {
+ return m_inheritedMembers;
+ }
+ ClassNodesList &classNodesList() { return m_classNodesList; }
+ [[nodiscard]] const NodeVector &obsoleteMembers() const { return m_obsoleteMembers; }
+ void appendMembers(const NodeVector &nv) { m_members.append(nv); }
+ [[nodiscard]] const Aggregate *aggregate() const { return m_aggregate; }
+ void setAggregate(Aggregate *t) { m_aggregate = t; }
+
+private:
+ QString m_title {};
+ QString m_singular {};
+ QString m_plural {};
+ QString m_divClass {};
+ Style m_style {};
+
+ Aggregate *m_aggregate { nullptr };
+ NodeVector m_members {};
+ NodeVector m_obsoleteMembers {};
+ NodeVector m_reimplementedMembers {};
+ QList<std::pair<Aggregate *, int>> m_inheritedMembers {};
+ ClassNodesList m_classNodesList {};
+
+ QMultiMap<QString, Node *> m_reimplementedMemberMap {};
+};
+
+typedef QList<Section> SectionVector;
+typedef QList<const Section *> SectionPtrVector;
+
+class Sections
+{
+public:
+ enum VectorIndex {
+ PublicTypes = 0,
+ DetailsMemberTypes = 0,
+ SinceNamespaces = 0,
+ StdNamespaces = 0,
+ QmlProperties = 0,
+ Properties = 1,
+ DetailsProperties = 1,
+ SinceClasses = 1,
+ StdClasses = 1,
+ QmlAttachedProperties = 1,
+ PublicFunctions = 2,
+ DetailsMemberFunctions = 2,
+ SinceMemberFunctions = 2,
+ StdTypes = 2,
+ QmlSignals = 2,
+ PublicSlots = 3,
+ DetailsMemberVariables = 3,
+ SinceNamespaceFunctions = 3,
+ StdVariables = 3,
+ QmlSignalHandlers = 3,
+ Signals = 4,
+ SinceGlobalFunctions = 4,
+ DetailsRelatedNonmembers = 4,
+ StdStaticVariables = 4,
+ QmlAttachedSignals = 4,
+ PublicVariables = 5,
+ SinceMacros = 5,
+ DetailsMacros = 5,
+ StdFunctions = 5,
+ QmlMethods = 5,
+ StaticPublicMembers = 6,
+ SinceEnumTypes = 6,
+ StdMacros = 6,
+ QmlAttachedMethods = 6,
+ SinceEnumValues = 7,
+ ProtectedTypes = 7,
+ SinceTypeAliases = 8,
+ ProtectedFunctions = 8,
+ SinceProperties = 9,
+ ProtectedSlots = 9,
+ SinceVariables = 10,
+ ProtectedVariables = 10,
+ SinceQmlTypes = 11,
+ StaticProtectedMembers = 11,
+ SinceQmlProperties = 12,
+ PrivateTypes = 12,
+ SinceQmlSignals = 13,
+ PrivateFunctions = 13,
+ SinceQmlSignalHandlers = 14,
+ PrivateSlots = 14,
+ SinceQmlMethods = 15,
+ StaticPrivateMembers = 15,
+ RelatedNonmembers = 16,
+ Macros = 17
+ };
+
+ explicit Sections(Aggregate *aggregate);
+ explicit Sections(const NodeMultiMap &nsmap);
+ ~Sections();
+
+ void clear(SectionVector &v);
+ void reduce(SectionVector &v);
+ void buildStdRefPageSections();
+ void buildStdCppClassRefPageSections();
+ void buildStdQmlTypeRefPageSections();
+
+ bool hasObsoleteMembers(SectionPtrVector *summary_spv, SectionPtrVector *details_spv) const;
+
+ static Section &allMembersSection() { return s_allMembers[0]; }
+ SectionVector &sinceSections() { return s_sinceSections; }
+ SectionVector &stdSummarySections() { return s_stdSummarySections; }
+ SectionVector &stdDetailsSections() { return s_stdDetailsSections; }
+ SectionVector &stdCppClassSummarySections() { return s_stdCppClassSummarySections; }
+ SectionVector &stdCppClassDetailsSections() { return s_stdCppClassDetailsSections; }
+ SectionVector &stdQmlTypeSummarySections() { return s_stdQmlTypeSummarySections; }
+ SectionVector &stdQmlTypeDetailsSections() { return s_stdQmlTypeDetailsSections; }
+
+ [[nodiscard]] const SectionVector &stdSummarySections() const { return s_stdSummarySections; }
+ [[nodiscard]] const SectionVector &stdDetailsSections() const { return s_stdDetailsSections; }
+ [[nodiscard]] const SectionVector &stdCppClassSummarySections() const
+ {
+ return s_stdCppClassSummarySections;
+ }
+ [[nodiscard]] const SectionVector &stdCppClassDetailsSections() const
+ {
+ return s_stdCppClassDetailsSections;
+ }
+ [[nodiscard]] const SectionVector &stdQmlTypeSummarySections() const
+ {
+ return s_stdQmlTypeSummarySections;
+ }
+ [[nodiscard]] const SectionVector &stdQmlTypeDetailsSections() const
+ {
+ return s_stdQmlTypeDetailsSections;
+ }
+
+ [[nodiscard]] Aggregate *aggregate() const { return m_aggregate; }
+
+private:
+ void stdRefPageSwitch(SectionVector &v, Node *n, Node *t = nullptr);
+ void distributeNodeInSummaryVector(SectionVector &sv, Node *n);
+ void distributeNodeInDetailsVector(SectionVector &dv, Node *n);
+ void distributeQmlNodeInDetailsVector(SectionVector &dv, Node *n);
+ void distributeQmlNodeInSummaryVector(SectionVector &sv, Node *n, bool sharing = false);
+ void initAggregate(SectionVector &v, Aggregate *aggregate);
+
+private:
+ Aggregate *m_aggregate { nullptr };
+
+ static SectionVector s_stdSummarySections;
+ static SectionVector s_stdDetailsSections;
+ static SectionVector s_stdCppClassSummarySections;
+ static SectionVector s_stdCppClassDetailsSections;
+ static SectionVector s_stdQmlTypeSummarySections;
+ static SectionVector s_stdQmlTypeDetailsSections;
+ static SectionVector s_sinceSections;
+ static SectionVector s_allMembers;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/sharedcommentnode.cpp b/src/qdoc/qdoc/src/qdoc/sharedcommentnode.cpp
new file mode 100644
index 000000000..05204d1f3
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/sharedcommentnode.cpp
@@ -0,0 +1,56 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "sharedcommentnode.h"
+
+#include "aggregate.h"
+#include "functionnode.h"
+#include "qmltypenode.h"
+
+QT_BEGIN_NAMESPACE
+
+SharedCommentNode::SharedCommentNode(QmlTypeNode *parent, int count, QString &group)
+ : Node(Node::SharedComment, parent, group)
+{
+ m_collective.reserve(count);
+}
+
+/*!
+ Searches the shared comment node's member nodes for function
+ nodes. Each function node's overload flag is set.
+ */
+void SharedCommentNode::setOverloadFlags()
+{
+ for (auto *node : m_collective) {
+ if (node->isFunction())
+ static_cast<FunctionNode *>(node)->setOverloadFlag();
+ }
+}
+
+/*!
+ Clone this node on the heap and make the clone a child of
+ \a parent.
+
+ Returns the pointer to the clone.
+ */
+Node *SharedCommentNode::clone(Aggregate *parent)
+{
+ auto *scn = new SharedCommentNode(*this); // shallow copy
+ scn->setParent(nullptr);
+ parent->addChild(scn);
+
+ return scn;
+}
+
+/*!
+ Sets the related nonmember flag in this node and in each
+ node in the shared comment's collective to \a value.
+ */
+void SharedCommentNode::setRelatedNonmember(bool value)
+{
+ Node::setRelatedNonmember(value);
+ for (auto *node : m_collective)
+ node->setRelatedNonmember(value);
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/sharedcommentnode.h b/src/qdoc/qdoc/src/qdoc/sharedcommentnode.h
new file mode 100644
index 000000000..d319d7c59
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/sharedcommentnode.h
@@ -0,0 +1,51 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef SHAREDCOMMENTNODE_H
+#define SHAREDCOMMENTNODE_H
+
+#include "node.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qlist.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+class QmlTypeNode;
+
+class SharedCommentNode : public Node
+{
+public:
+ explicit SharedCommentNode(Node *node) : Node(Node::SharedComment, node->parent(), QString())
+ {
+ m_collective.reserve(1);
+ append(node);
+ }
+ SharedCommentNode(QmlTypeNode *parent, int count, QString &group);
+ ~SharedCommentNode() override { m_collective.clear(); }
+
+ [[nodiscard]] bool isPropertyGroup() const override
+ {
+ return !name().isEmpty() && !m_collective.isEmpty() && (m_collective.at(0)->isQmlProperty());
+ }
+ [[nodiscard]] qsizetype count() const { return m_collective.size(); }
+ void append(Node *node)
+ {
+ m_collective.append(node);
+ node->setSharedCommentNode(this);
+ setGenus(node->genus());
+ }
+ void sort() { std::sort(m_collective.begin(), m_collective.end(), Node::nodeNameLessThan); }
+ [[nodiscard]] const QList<Node *> &collective() const { return m_collective; }
+ void setOverloadFlags();
+ void setRelatedNonmember(bool value) override;
+ Node *clone(Aggregate *parent) override;
+
+private:
+ QList<Node *> m_collective {};
+};
+
+QT_END_NAMESPACE
+
+#endif // SHAREDCOMMENTNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/singleton.h b/src/qdoc/qdoc/src/qdoc/singleton.h
new file mode 100644
index 000000000..085a5ea64
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/singleton.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#ifndef SINGLETON_H
+#define SINGLETON_H
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class Singleton
+ \internal
+
+ Class template for singleton objects in QDoc.
+ */
+template<typename T>
+class Singleton
+{
+public:
+ Singleton(const Singleton &) = delete;
+ Singleton &operator=(const Singleton &) = delete;
+ static T &instance()
+ {
+ static T s_instance {};
+ return s_instance;
+ }
+
+protected:
+ Singleton() = default;
+};
+
+QT_END_NAMESPACE
+
+#endif // SINGLETON_H
diff --git a/src/qdoc/qdoc/src/qdoc/sourcefileparser.h b/src/qdoc/qdoc/src/qdoc/sourcefileparser.h
new file mode 100644
index 000000000..95bc71627
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/sourcefileparser.h
@@ -0,0 +1,82 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "clangcodeparser.h"
+#include "puredocparser.h"
+
+#include <variant>
+
+#include <QString>
+#include <QFileInfo>
+
+struct CppSourceFile {};
+struct QDocSourceFile {};
+struct JsSourceFile {};
+struct UnknownSourceFile {};
+
+using SourceFileTag = std::variant<CppSourceFile, QDocSourceFile, JsSourceFile, UnknownSourceFile>;
+using TaggedSourceFile = std::pair<QString, SourceFileTag>;
+
+inline TaggedSourceFile tag_source_file(const QString& path) {
+ static QStringList cpp_file_extensions{
+ "c++", "cc", "cpp", "cxx", "mm"
+ };
+ static QStringList qdoc_file_extensions{ "qdoc" };
+ static QStringList javascript_file_extensions{ "js" };
+
+ QString extension{QFileInfo(path).suffix()};
+
+ if (cpp_file_extensions.contains(extension)) return TaggedSourceFile{path, CppSourceFile{}};
+ else if (qdoc_file_extensions.contains(extension)) return TaggedSourceFile{path, QDocSourceFile{}};
+ else if (javascript_file_extensions.contains(extension)) return TaggedSourceFile{path, JsSourceFile{}};
+
+ return TaggedSourceFile{path, UnknownSourceFile{}};
+}
+
+class SourceFileParser {
+public:
+ struct ParseResult {
+ std::vector<UntiedDocumentation> untied;
+ std::vector<TiedDocumentation> tied;
+ };
+
+public:
+ SourceFileParser(ClangCodeParser& clang_parser, PureDocParser& pure_parser)
+ : cpp_file_parser(clang_parser),
+ pure_file_parser(pure_parser)
+ {}
+
+ ParseResult operator()(const TaggedSourceFile& source) {
+ if (std::holds_alternative<CppSourceFile>(source.second))
+ return (*this)(source.first, std::get<CppSourceFile>(source.second));
+ else if (std::holds_alternative<QDocSourceFile>(source.second))
+ return (*this)(source.first, std::get<QDocSourceFile>(source.second));
+ else if (std::holds_alternative<JsSourceFile>(source.second))
+ return (*this)(source.first, std::get<JsSourceFile>(source.second));
+ else if (std::holds_alternative<UnknownSourceFile>(source.second))
+ return {{}, {}};
+
+ Q_UNREACHABLE();
+ }
+
+private:
+ ParseResult operator()(const QString& path, CppSourceFile) {
+ auto [untied, tied] = cpp_file_parser.parse_cpp_file(path);
+
+ return {untied, tied};
+ }
+
+ ParseResult operator()(const QString& path, QDocSourceFile) {
+ return {pure_file_parser.parse_qdoc_file(path), {}};
+ }
+
+ ParseResult operator()(const QString& path, JsSourceFile) {
+ return {pure_file_parser.parse_qdoc_file(path), {}};
+ }
+
+private:
+ ClangCodeParser& cpp_file_parser;
+ PureDocParser& pure_file_parser;
+};
diff --git a/src/qdoc/qdoc/src/qdoc/tagfilewriter.cpp b/src/qdoc/qdoc/src/qdoc/tagfilewriter.cpp
new file mode 100644
index 000000000..7b6b496ca
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/tagfilewriter.cpp
@@ -0,0 +1,306 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "tagfilewriter.h"
+
+#include "access.h"
+#include "aggregate.h"
+#include "classnode.h"
+#include "enumnode.h"
+#include "functionnode.h"
+#include "htmlgenerator.h"
+#include "location.h"
+#include "node.h"
+#include "propertynode.h"
+#include "qdocdatabase.h"
+#include "typedefnode.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class TagFileWriter
+
+ This class handles the generation of the QDoc tag files.
+ */
+
+/*!
+ Default constructor. \a qdb is the pointer to the
+ qdoc database that is used when reading and writing the
+ index files.
+ */
+TagFileWriter::TagFileWriter() : m_qdb(QDocDatabase::qdocDB()) { }
+
+/*!
+ Generate the tag file section with the given \a writer for the \a parent
+ node.
+ */
+void TagFileWriter::generateTagFileCompounds(QXmlStreamWriter &writer, const Aggregate *parent)
+{
+ const auto &nonFunctionList = const_cast<Aggregate *>(parent)->nonfunctionList();
+ for (const auto *node : nonFunctionList) {
+ if (!node->url().isNull() || node->isPrivate())
+ continue;
+
+ QString kind;
+ switch (node->nodeType()) {
+ case Node::Namespace:
+ kind = "namespace";
+ break;
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ case Node::QmlType:
+ kind = "class";
+ break;
+ default:
+ continue;
+ }
+ const auto *aggregate = static_cast<const Aggregate *>(node);
+
+ QString access = "public";
+ if (node->isProtected())
+ access = "protected";
+
+ QString objName = node->name();
+
+ // Special case: only the root node should have an empty name.
+ if (objName.isEmpty() && node != m_qdb->primaryTreeRoot())
+ continue;
+
+ // *** Write the starting tag for the element here. ***
+ writer.writeStartElement("compound");
+ writer.writeAttribute("kind", kind);
+
+ if (node->isClassNode()) {
+ writer.writeTextElement("name", node->fullDocumentName());
+ writer.writeTextElement("filename", m_generator->fullDocumentLocation(node));
+
+ // Classes contain information about their base classes.
+ const auto *classNode = static_cast<const ClassNode *>(node);
+ const QList<RelatedClass> &bases = classNode->baseClasses();
+ for (const auto &related : bases) {
+ ClassNode *n = related.m_node;
+ if (n)
+ writer.writeTextElement("base", n->name());
+ }
+
+ // Recurse to write all members.
+ generateTagFileMembers(writer, aggregate);
+ writer.writeEndElement();
+
+ // Recurse to write all compounds.
+ generateTagFileCompounds(writer, aggregate);
+ } else {
+ writer.writeTextElement("name", node->fullDocumentName());
+ writer.writeTextElement("filename", m_generator->fullDocumentLocation(node));
+
+ // Recurse to write all members.
+ generateTagFileMembers(writer, aggregate);
+ writer.writeEndElement();
+
+ // Recurse to write all compounds.
+ generateTagFileCompounds(writer, aggregate);
+ }
+ }
+}
+
+/*!
+ Writes all the members of the \a parent node with the \a writer.
+ The node represents a C++ class, namespace, etc.
+ */
+void TagFileWriter::generateTagFileMembers(QXmlStreamWriter &writer, const Aggregate *parent)
+{
+ auto childNodes = parent->childNodes();
+ std::sort(childNodes.begin(), childNodes.end(), Node::nodeNameLessThan);
+ for (const auto *node : childNodes) {
+ if (!node->url().isNull())
+ continue;
+
+ QString nodeName;
+ QString kind;
+ switch (node->nodeType()) {
+ case Node::Enum:
+ nodeName = "member";
+ kind = "enumeration";
+ break;
+ case Node::TypeAlias: // Treated as typedef
+ case Node::Typedef:
+ nodeName = "member";
+ kind = "typedef";
+ break;
+ case Node::Property:
+ nodeName = "member";
+ kind = "property";
+ break;
+ case Node::Function:
+ nodeName = "member";
+ kind = "function";
+ break;
+ case Node::Namespace:
+ nodeName = "namespace";
+ break;
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ nodeName = "class";
+ break;
+ case Node::Variable:
+ default:
+ continue;
+ }
+
+ QString access;
+ switch (node->access()) {
+ case Access::Public:
+ access = "public";
+ break;
+ case Access::Protected:
+ access = "protected";
+ break;
+ case Access::Private:
+ default:
+ continue;
+ }
+
+ QString objName = node->name();
+
+ // Special case: only the root node should have an empty name.
+ if (objName.isEmpty() && node != m_qdb->primaryTreeRoot())
+ continue;
+
+ // *** Write the starting tag for the element here. ***
+ writer.writeStartElement(nodeName);
+ if (!kind.isEmpty())
+ writer.writeAttribute("kind", kind);
+
+ switch (node->nodeType()) {
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ writer.writeCharacters(node->fullDocumentName());
+ writer.writeEndElement();
+ break;
+ case Node::Namespace:
+ writer.writeCharacters(node->fullDocumentName());
+ writer.writeEndElement();
+ break;
+ case Node::Function: {
+ /*
+ Function nodes contain information about
+ the type of function being described.
+ */
+
+ const auto *functionNode = static_cast<const FunctionNode *>(node);
+ writer.writeAttribute("protection", access);
+ writer.writeAttribute("virtualness", functionNode->virtualness());
+ writer.writeAttribute("static", functionNode->isStatic() ? "yes" : "no");
+
+ if (functionNode->isNonvirtual())
+ writer.writeTextElement("type", functionNode->returnType());
+ else
+ writer.writeTextElement("type", "virtual " + functionNode->returnType());
+
+ writer.writeTextElement("name", objName);
+ const QStringList pieces =
+ m_generator->fullDocumentLocation(node).split(QLatin1Char('#'));
+ writer.writeTextElement("anchorfile", pieces[0]);
+ writer.writeTextElement("anchor", pieces[1]);
+ QString signature = functionNode->signature(Node::SignatureReturnType);
+ signature = signature.mid(signature.indexOf(QChar('('))).trimmed();
+ if (functionNode->isConst())
+ signature += " const";
+ if (functionNode->isFinal())
+ signature += " final";
+ if (functionNode->isOverride())
+ signature += " override";
+ if (functionNode->isPureVirtual())
+ signature += " = 0";
+ writer.writeTextElement("arglist", signature);
+ }
+ writer.writeEndElement(); // member
+ break;
+ case Node::Property: {
+ const auto *propertyNode = static_cast<const PropertyNode *>(node);
+ writer.writeAttribute("type", propertyNode->dataType());
+ writer.writeTextElement("name", objName);
+ const QStringList pieces =
+ m_generator->fullDocumentLocation(node).split(QLatin1Char('#'));
+ writer.writeTextElement("anchorfile", pieces[0]);
+ writer.writeTextElement("anchor", pieces[1]);
+ writer.writeTextElement("arglist", QString());
+ }
+ writer.writeEndElement(); // member
+ break;
+ case Node::Enum: {
+ const auto *enumNode = static_cast<const EnumNode *>(node);
+ writer.writeTextElement("name", objName);
+ const QStringList pieces =
+ m_generator->fullDocumentLocation(node).split(QLatin1Char('#'));
+ writer.writeTextElement("anchorfile", pieces[0]);
+ writer.writeTextElement("anchor", pieces[1]);
+ writer.writeEndElement(); // member
+
+ for (const auto &item : enumNode->items()) {
+ writer.writeStartElement("member");
+ writer.writeAttribute("kind", "enumvalue");
+ writer.writeTextElement("name", item.name());
+ writer.writeTextElement("anchorfile", pieces[0]);
+ writer.writeTextElement("anchor", pieces[1]);
+ writer.writeTextElement("arglist", QString());
+ writer.writeEndElement(); // member
+ }
+ } break;
+ case Node::TypeAlias: // Treated as typedef
+ case Node::Typedef: {
+ const auto *typedefNode = static_cast<const TypedefNode *>(node);
+ if (typedefNode->associatedEnum())
+ writer.writeAttribute("type", typedefNode->associatedEnum()->fullDocumentName());
+ else
+ writer.writeAttribute("type", QString());
+ writer.writeTextElement("name", objName);
+ const QStringList pieces =
+ m_generator->fullDocumentLocation(node).split(QLatin1Char('#'));
+ writer.writeTextElement("anchorfile", pieces[0]);
+ writer.writeTextElement("anchor", pieces[1]);
+ writer.writeTextElement("arglist", QString());
+ }
+ writer.writeEndElement(); // member
+ break;
+
+ case Node::Variable:
+ default:
+ break;
+ }
+ }
+}
+
+/*!
+ Writes a tag file named \a fileName.
+ */
+void TagFileWriter::generateTagFile(const QString &fileName, Generator *g)
+{
+ QFile file(fileName);
+ QFileInfo fileInfo(fileName);
+
+ // If no path was specified or it doesn't exist,
+ // default to the output directory
+ if (fileInfo.fileName() == fileName || !fileInfo.dir().exists())
+ file.setFileName(m_generator->outputDir() + QLatin1Char('/') + fileInfo.fileName());
+
+ if (!file.open(QFile::WriteOnly | QFile::Text)) {
+ Location().warning(QString("Failed to open %1 for writing.").arg(file.fileName()));
+ return;
+ }
+
+ m_generator = g;
+ QXmlStreamWriter writer(&file);
+ writer.setAutoFormatting(true);
+ writer.writeStartDocument();
+ writer.writeStartElement("tagfile");
+ generateTagFileCompounds(writer, m_qdb->primaryTreeRoot());
+ writer.writeEndElement(); // tagfile
+ writer.writeEndDocument();
+ file.close();
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/tagfilewriter.h b/src/qdoc/qdoc/src/qdoc/tagfilewriter.h
new file mode 100644
index 000000000..40a1ed399
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/tagfilewriter.h
@@ -0,0 +1,33 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef TAGFILEWRITER_H
+#define TAGFILEWRITER_H
+
+#include <QtCore/qxmlstream.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+class Generator;
+class QDocDatabase;
+
+class TagFileWriter
+{
+public:
+ TagFileWriter();
+ ~TagFileWriter() = default;
+
+ void generateTagFile(const QString &fileName, Generator *generator);
+
+private:
+ void generateTagFileCompounds(QXmlStreamWriter &writer, const Aggregate *inner);
+ void generateTagFileMembers(QXmlStreamWriter &writer, const Aggregate *inner);
+
+ QDocDatabase *m_qdb { nullptr };
+ Generator *m_generator { nullptr };
+};
+
+QT_END_NAMESPACE
+
+#endif // TAGFILEWRITER_H
diff --git a/src/qdoc/qdoc/src/qdoc/template_declaration.h b/src/qdoc/qdoc/src/qdoc/template_declaration.h
new file mode 100644
index 000000000..a0aade682
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/template_declaration.h
@@ -0,0 +1,502 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <algorithm>
+#include <cstdint>
+#include <functional>
+#include <numeric>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include <QString>
+
+/*
+ * Represents a general declaration that has a form that can be
+ * described by a type, name and initializer triplet, or any such form
+ * that can be described by zero or more of those same parts.
+ *
+ * For example, it can be used to represent a C++ variable declaration
+ * such as:
+ *
+ * std::vector<int> foo = { 1, 2, 3 };
+ *
+ * Where `std::vector<int>` is the type, `foo` is the name and `{ 1, 2,
+ * 3 }` is the initializer.
+ *
+ * Similarly, it can be used to represent a non-type template parameter
+ * declaration, such as the `foo` parameter in:
+ *
+ * template<int foo = 10>
+ *
+ * Where `int` is the type, `foo` is the name and `10` is the
+ * initializer.
+ *
+ * An instance can be used to represent less information dense elements
+ * by setting one or more of the fields as the empty string.
+ *
+ * For example, a template type parameter such as `T` in:
+ *
+ * template<typename T = int>
+ *
+ * Can be represented by an instance that has an empty string as the
+ * type, `T` as the name and `int` as the initializer.
+ *
+ * In general, it can be used to represent any such element that has
+ * zero or more of the three components, albeit, in QDoc, it is
+ * specifically intended to be used to represent various C++
+ * declarations.
+ *
+ * All three fields are lowered stringified version of the original
+ * declaration, so that the type should be used at the end of a
+ * pipeline where the semantic property of the represented code are not
+ * required.
+ */
+struct ValuedDeclaration
+{
+ struct PrintingPolicy
+ {
+ bool include_type = true;
+ bool include_name = true;
+ bool include_initializer = true;
+ };
+
+ std::string type;
+ std::string name;
+ std::string initializer;
+
+ // KLUDGE: Workaround for
+ // https://stackoverflow.com/questions/53408962/try-to-understand-compiler-error-message-default-member-initializer-required-be
+ static PrintingPolicy default_printing_policy() { return PrintingPolicy{}; }
+
+ /*
+ * Constructs and returns a human-readable representation of this
+ * declaration.
+ *
+ * The constructed string is formatted so that as to rebuild a
+ * possible version of the C++ code that is modeled by an instance
+ * of this type.
+ *
+ * Each component participates in the human-presentable version if
+ * it they are not the empty string.
+ *
+ * The "type" and "name" component participate with their literal
+ * representation.
+ *
+ * The "iniitlalizer" components contributes an equal symbol,
+ * followed by a space followed by the literal representation of
+ * the component.
+ *
+ * The component contributes in an ordered way, with "type"
+ * contributing first, "name" contributing second and
+ * "initializer" contributing last.
+ *
+ * Each contribution is separated by a space if the component that
+ * comes before it, if any, has contributed to the human-readable
+ * representation.
+ *
+ * For example, an instance of this type that has "type" component
+ * "int", "name" component "foo" and "iniitializer" component
+ * "100", would be represented as:
+ *
+ * int foo = 100
+ *
+ * Where "int" is the "type" component contribution, "foo" is the
+ * "name" component contribution and "= 100" is the "initializer"
+ * component contribution.
+ * Each of those contribution is separated by a space, as each
+ * "preceding" component has contributed to the representation.
+ *
+ * If we provide a similar instance with, for example, the "type"
+ * and "name" components as the empty string, then the
+ * representation would be "= 100", which is the "initializer"
+ * component contribution, the only component that is not the
+ * empty string.
+ *
+ * The policy argument allows to treat certain components as if
+ * they were the empty string.
+ *
+ * For example, given an instance of this type that has "type"
+ * component "double", "name" component "bar" and "iniitializer"
+ * component "10.2", its human-readable representation would be
+ * "double bar = 10.2".
+ *
+ * If the representation of that same instance was obtained by
+ * using a policy that excludes the "name" component, then that
+ * representation would be "double = 10.2", which is equivalent
+ * to the representation of an instance that is the same as the
+ * orginal one with the "name" component as the empty string.
+ */
+ inline std::string to_std_string(PrintingPolicy policy = default_printing_policy()) const
+ {
+ std::string s{};
+
+ if (!type.empty() && policy.include_type)
+ s += (s.empty() ? "" : " ") + type;
+
+ if (!name.empty() && policy.include_name)
+ s += (s.empty() ? "" : " ") + name;
+
+ if (!initializer.empty() && policy.include_initializer)
+ s += (s.empty() ? "= " : " = ") + initializer;
+
+ return s;
+ }
+};
+
+struct RelaxedTemplateParameter;
+
+struct TemplateDeclarationStorage
+{
+ std::vector<RelaxedTemplateParameter> parameters;
+
+ inline std::string to_std_string() const;
+};
+
+/*
+ * Represents a C++ template parameter.
+ *
+ * The model used by this representation is a slighly simplified
+ * model.
+ *
+ * In the model, template parameters are one of:
+ *
+ * - A type template parameter.
+ * - A non type template parameter.
+ * - A template template parameter.
+ *
+ * Furthermore, each parameter can:
+ *
+ * - Be a parameter pack.
+ * - Carry an additional template declaration (as a template template
+ * parameter would).
+ * - Have no declared type.
+ * - Have no declared name.
+ * - Have no declared initializer.
+ *
+ * Due to this simplified model certain incorrect parameters can be
+ * represented.
+ *
+ * For example, it might be possible to represent a parameter pack
+ * that has a default initializer, a non-type template parameter that
+ * has no type or a template template parameter that carries no
+ * template declaration.
+ *
+ * The model further elides some of the semantic that might be carried
+ * by a parameter.
+ * For example, the model has no specific concept for template
+ * constraints.
+ *
+ * Template parameters can be represented as instances of the type.
+ *
+ * For example, a type template parameter `typename T` can be
+ * represented as the following instance:
+ *
+ * RelaxedTemplateParameter{
+ * RelaxedTemplateParameter::Kind::TypeTemplateParameter,
+ * false,
+ * {
+ * "",
+ * "T",
+ * ""
+ * },
+ * {}
+ * };
+ *
+ * And a non-type template parameter pack "int... Args" as:
+ *
+ * RelaxedTemplateParameter{
+ * RelaxedTemplateParameter::Kind::NonTypeTemplateParameter,
+ * true,
+ * {
+ * "int",
+ * "Args",
+ * ""
+ * },
+ * {}
+ * };
+ *
+ * Due to the relaxed constraint and the representable incorrect
+ * parameters, the type is intended to be used for data that is
+ * already validated and known to be correct, such as data that is
+ * extracted from Clang.
+ */
+struct RelaxedTemplateParameter
+{
+ enum class Kind : std::uint8_t {
+ TypeTemplateParameter,
+ NonTypeTemplateParameter,
+ TemplateTemplateParameter
+ };
+
+ Kind kind;
+ bool is_parameter_pack;
+ ValuedDeclaration valued_declaration;
+ std::optional<TemplateDeclarationStorage> template_declaration;
+
+ /*
+ * Constructs and returns a human-readable representation of this
+ * parameter.
+ *
+ * The constructed string is formatted so that as to rebuild a
+ * possible version of the C++ code that is modeled by an instance
+ * of this type.
+ *
+ * The format of the representation varies based on the "kind" of
+ * the parameter.
+ *
+ * - A "TypeTemplateParameter", is constructed as the
+ * concatenation of the literal "typename", followed by the
+ * literal "..." if the parameter is a pack, followed by the
+ * human-readable representaion of "valued_declaration".
+ *
+ * If the human-readable representation of
+ * "valued_declaration" is not the empty string, it is
+ * preceded by a space when it contributes to the
+ * representation.
+ *
+ * For example, the C++ type template parameter "typename Foo
+ * = int", would be represented by the instance:
+ *
+ * RelaxedTemplateParameter{
+ * RelaxedTemplateParameter::Kind::TypeTemplateParameter,
+ * false,
+ * {
+ * "",
+ * "Foo",
+ * "int"
+ * },
+ * {}
+ * };
+ *
+ * And its representation would be:
+ *
+ * typename Foo = int
+ *
+ * Where "typename" is the added literal and "Foo = int" is
+ * the representation for "valued_declaration", with a space
+ * in-between the two contributions.
+ *
+ * - A "NonTypeTemplateParameter", is constructed by the
+ * contribution of the "type" compoment of "valued_declaration",
+ * followed by the literal "..." if the parameter is a pack,
+ * followed by the human-presentable version of
+ * "valued_declaration" without its "type" component
+ * contribution.
+ *
+ * If the contribution of the "type" component of
+ * "valued_declaration" is not empty, the next contribution is
+ * preceded by a space.
+ *
+ * For example, the C++ non-type template parameter "int...
+ * SIZE", would be represented by the instance:
+ *
+ *
+ * RelaxedTemplateParameter{
+ * RelaxedTemplateParameter::Kind::NonTypeTemplateParameter,
+ * true,
+ * {
+ * "int",
+ * "SIZE",
+ * ""
+ * },
+ * {}
+ * };
+ *
+ * And its representation would be:
+ *
+ * int... SIZE
+ *
+ * Where "int" is the "type" component contribution of
+ * "valued_declaration", "..." is the added literal due to
+ * the parameter being a pack and " SIZE" being the
+ * human-readable representation of "valued_declaration"
+ * without its "type" component contribution, preceded by a
+ * space.
+ *
+ * - A "TemplateTemplateParameter", is constructed by the
+ * contribution of the human-presentable representation of
+ * "template_declaration", followed by the representation of
+ * this parameter if it was a "TypeTemplateParameter", with a
+ * space between the two contributions if the
+ * human-presentable representation of "template_declaration"
+ * is not empty.
+ *
+ * For example, the C++ template template template parameter
+ * "template<typename> T", would be represented by the
+ * instance:
+ *
+ *
+ * RelaxedTemplateParameter{
+ * RelaxedTemplateParameter::Kind::TemplateTemplateParameter,
+ * false,
+ * {
+ * "",
+ * "T",
+ * ""
+ * },
+ * {
+ * RelaxedTemplateParameter{
+ * RelaxedTemplateParameter::Kind::TypeTemplateParameter,
+ * false,
+ * {
+ * "",
+ * "",
+ * ""
+ * },
+ * {}
+ * }
+ * }
+ * };
+ *
+ * And its representation would be:
+ *
+ * template <typename> typename T
+ *
+ * Where "template <typename>" human-presentable version of
+ * "template_declaration" and "typename T" is the
+ * human-presentable version of this parameter if it was a
+ * type template parameter.
+ *
+ * With a space between the two contributions.
+ */
+ inline std::string to_std_string() const
+ {
+ switch (kind) {
+ // TODO: This can probably be moved under the template
+ // template parameter case and reused through a fallback.
+ case Kind::TypeTemplateParameter: {
+ std::string valued_declaration_string = valued_declaration.to_std_string();
+
+ return std::string("typename") + (is_parameter_pack ? "..." : "")
+ + (valued_declaration_string.empty() ? "" : " ") + valued_declaration_string;
+ }
+ case Kind::NonTypeTemplateParameter: {
+ std::string type_string = valued_declaration.type + (is_parameter_pack ? "..." : "");
+
+ return type_string + (type_string.empty() ? "" : " ")
+ + valued_declaration.to_std_string(
+ ValuedDeclaration::PrintingPolicy{ false, true, true });
+ }
+ case Kind::TemplateTemplateParameter: {
+ std::string valued_declaration_string = valued_declaration.to_std_string();
+
+ return (template_declaration ? (*template_declaration).to_std_string() + " " : "")
+ + "typename" + (is_parameter_pack ? "..." : "")
+ + (valued_declaration_string.empty() ? "" : " ") + valued_declaration_string;
+ }
+ default:
+ return "";
+ }
+ }
+};
+
+/*
+ * Represents a C++ template declaration as a collection of template
+ * parameters.
+ *
+ * The parameters for the declaration follow the same relaxed rules as
+ * `RelaxedTemplateParameter` and inherit the possibility of
+ * representing incorrect declarations.
+ *
+ * Due to the relaxed constraint and the representable incorrect
+ * parameters, the type is intended to be used for data that is
+ * already validated and known to be correct, such as data that is
+ * extracted from Clang.
+ */
+struct RelaxedTemplateDeclaration : TemplateDeclarationStorage
+{
+ inline QString to_qstring() const { return QString::fromStdString(to_std_string()); }
+};
+
+/*
+ * Constructs and returns a human-readable representation of this
+ * declaration.
+ *
+ * The constructed string is formatted so as to rebuild a
+ * possible version of the C++ code that is modeled by an instance
+ * of this type.
+ *
+ * The representation of a declaration is constructed by the literal
+ * "template <", followed by the human-presentable version of each
+ * parameter in "parameters", with a comma and a space between each
+ * parameter, followed by a closing literal ">".
+ *
+ * For example, the empty declaration is represented as "template <>".
+ *
+ * While a template declaration that has a type template parameter
+ * "Foo" with initializer "int" and a non-type template parameter pack
+ * with type "int" and name "S" would be represented as:
+ *
+ * template <typename Foo = int, int... S>
+ */
+inline std::string TemplateDeclarationStorage::to_std_string() const
+{
+ if (parameters.empty())
+ return "template <>";
+
+ return "template <"
+ + std::accumulate(std::next(parameters.cbegin()), parameters.cend(),
+ parameters.front().to_std_string(),
+ [](auto &&acc, const RelaxedTemplateParameter &parameter) {
+ return acc + ", " + parameter.to_std_string();
+ })
+ + ">";
+}
+
+/*
+ * Returns true if the two template declaration represented by left
+ * and right are substitutable.
+ *
+ * QDoc uses a simplified model for template declarations and,
+ * similarly, uses a simplified model of "substitutability".
+ *
+ * Two declarations are substitutable if:
+ *
+ * - They have the same amount of parameters
+ * - For each pair of parameters with the same postion:
+ * - They have the same kind
+ * - They are both parameter packs or both are not parameter packs
+ * - If they are non-type template parameters then they have the same type
+ * - If they are both template template parameters then they both
+ * carry an additional template declaration and the additional
+ * template declarations are substitutable
+ *
+ * This means that in the simplified models, we generally ignore default arguments, name and such.
+ *
+ * This model does not follow the way C++ performs disambiguation but
+ * should be enough to handle most cases in the documentation.
+ */
+inline bool are_template_declarations_substitutable(const TemplateDeclarationStorage& left, const TemplateDeclarationStorage& right) {
+ static auto are_template_parameters_substitutable = [](const RelaxedTemplateParameter& left, const RelaxedTemplateParameter& right) {
+ if (left.kind != right.kind) return false;
+ if (left.is_parameter_pack != right.is_parameter_pack) return false;
+
+ if (left.kind == RelaxedTemplateParameter::Kind::NonTypeTemplateParameter &&
+ (left.valued_declaration.type != right.valued_declaration.type))
+ return false;
+
+ if (left.kind == RelaxedTemplateParameter::Kind::TemplateTemplateParameter) {
+ if (!left.template_declaration && right.template_declaration) return false;
+ if (left.template_declaration && !right.template_declaration) return false;
+
+ if (left.template_declaration && right.template_declaration)
+ return are_template_declarations_substitutable(*left.template_declaration, *right.template_declaration);
+ }
+
+ return true;
+ };
+
+ const auto& left_parameters = left.parameters;
+ const auto& right_parameters = right.parameters;
+
+ if (left_parameters.size() != right_parameters.size()) return false;
+
+ return std::transform_reduce(left_parameters.cbegin(), left_parameters.cend(), right_parameters.cbegin(),
+ true,
+ std::logical_and<bool>{},
+ are_template_parameters_substitutable
+ );
+}
diff --git a/src/qdoc/qdoc/src/qdoc/text.cpp b/src/qdoc/qdoc/src/qdoc/text.cpp
new file mode 100644
index 000000000..d3401ce68
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/text.cpp
@@ -0,0 +1,341 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "text.h"
+
+#include <QtCore/qregularexpression.h>
+
+#include <cstdio>
+
+QT_BEGIN_NAMESPACE
+
+Text::Text() : m_first(nullptr), m_last(nullptr) { }
+
+Text::Text(const QString &str) : m_first(nullptr), m_last(nullptr)
+{
+ operator<<(str);
+}
+
+Text::Text(const Text &text) : m_first(nullptr), m_last(nullptr)
+{
+ operator=(text);
+}
+
+Text::~Text()
+{
+ clear();
+}
+
+Text &Text::operator=(const Text &text)
+{
+ if (this != &text) {
+ clear();
+ operator<<(text);
+ }
+ return *this;
+}
+
+Text &Text::operator<<(Atom::AtomType atomType)
+{
+ return operator<<(Atom(atomType));
+}
+
+Text &Text::operator<<(const QString &string)
+{
+ return string.isEmpty() ? *this : operator<<(Atom(Atom::String, string));
+}
+
+Text &Text::operator<<(const Atom &atom)
+{
+ if (atom.count() < 2) {
+ if (m_first == nullptr) {
+ m_first = new Atom(atom.type(), atom.string());
+ m_last = m_first;
+ } else
+ m_last = new Atom(m_last, atom.type(), atom.string());
+ } else {
+ if (m_first == nullptr) {
+ m_first = new Atom(atom.type(), atom.string(), atom.string(1));
+ m_last = m_first;
+ } else
+ m_last = new Atom(m_last, atom.type(), atom.string(), atom.string(1));
+ }
+ return *this;
+}
+
+/*!
+ Special output operator for LinkAtom. It makes a copy of
+ the LinkAtom \a atom and connects the cop;y to the list
+ in this Text.
+ */
+Text &Text::operator<<(const LinkAtom &atom)
+{
+ if (m_first == nullptr) {
+ m_first = new LinkAtom(atom);
+ m_last = m_first;
+ } else
+ m_last = new LinkAtom(m_last, atom);
+ return *this;
+}
+
+Text &Text::operator<<(const Text &text)
+{
+ const Atom *atom = text.firstAtom();
+ while (atom != nullptr) {
+ operator<<(*atom);
+ atom = atom->next();
+ }
+ return *this;
+}
+
+void Text::stripFirstAtom()
+{
+ if (m_first != nullptr) {
+ if (m_first == m_last)
+ m_last = nullptr;
+ Atom *oldFirst = m_first;
+ m_first = m_first->next();
+ delete oldFirst;
+ }
+}
+
+void Text::stripLastAtom()
+{
+ if (m_last != nullptr) {
+ Atom *oldLast = m_last;
+ if (m_first == m_last) {
+ m_first = nullptr;
+ m_last = nullptr;
+ } else {
+ m_last = m_first;
+ while (m_last->next() != oldLast)
+ m_last = m_last->next();
+ m_last->setNext(nullptr);
+ }
+ delete oldLast;
+ }
+}
+
+/*!
+ This function traverses the atom list of the Text object,
+ extracting all the string parts. It concatenates them to
+ a result string and returns it.
+ */
+QString Text::toString() const
+{
+ QString str;
+ const Atom *atom = firstAtom();
+ while (atom != nullptr) {
+ if (atom->type() == Atom::String || atom->type() == Atom::AutoLink
+ || atom->type() == Atom::C)
+ str += atom->string();
+ atom = atom->next();
+ }
+ return str;
+}
+
+/*!
+ Returns true if this Text contains the substring \a str.
+ */
+bool Text::contains(const QString &str) const
+{
+ const Atom *atom = firstAtom();
+ while (atom != nullptr) {
+ if (atom->type() == Atom::String || atom->type() == Atom::AutoLink
+ || atom->type() == Atom::C)
+ if (atom->string().contains(str, Qt::CaseInsensitive))
+ return true;
+ atom = atom->next();
+ }
+ return false;
+}
+
+Text Text::subText(Atom::AtomType left, Atom::AtomType right, const Atom *from,
+ bool inclusive) const
+{
+ const Atom *begin = from ? from : firstAtom();
+ const Atom *end;
+
+ while (begin != nullptr && begin->type() != left)
+ begin = begin->next();
+ if (begin != nullptr) {
+ if (!inclusive)
+ begin = begin->next();
+ }
+
+ end = begin;
+ while (end != nullptr && end->type() != right)
+ end = end->next();
+ if (end == nullptr)
+ begin = nullptr;
+ else if (inclusive)
+ end = end->next();
+ return subText(begin, end);
+}
+
+Text Text::sectionHeading(const Atom *sectionLeft)
+{
+ if (sectionLeft != nullptr) {
+ const Atom *begin = sectionLeft;
+ while (begin != nullptr && begin->type() != Atom::SectionHeadingLeft)
+ begin = begin->next();
+ if (begin != nullptr)
+ begin = begin->next();
+
+ const Atom *end = begin;
+ while (end != nullptr && end->type() != Atom::SectionHeadingRight)
+ end = end->next();
+
+ if (end != nullptr)
+ return subText(begin, end);
+ }
+ return Text();
+}
+
+/*!
+ Prints a human-readable version of the contained atoms to stderr.
+
+ The output is formatted as a linear list of atoms, with each atom
+ being on its own line.
+
+ Each atom is represented by its type and its stringified-contents,
+ if any, with a space between the two.
+
+ Indentation is used to emphasize the possible block-level
+ relationship between consecutive atoms, increasing after a
+ "Left" atom and decreasing just before a "Right" atom.
+
+ For example, if this `Text` represented the block-comment
+ containing the text:
+
+ \c {\l {somelink} {This is a link}}
+
+ Then the human-readable output would look like the following:
+
+ \badcode
+ ParaLeft
+ Link "somelink"
+ FormattingLeft "link"
+ String "This is a link"
+ FormattingRight "link"
+ String
+ ParaRight
+ \endcode
+ */
+void Text::dump() const
+{
+ constexpr int minimum_indentation_level { 1 };
+ int indentation_level { minimum_indentation_level };
+ int indentation_width { 4 };
+
+ const Atom *atom = firstAtom();
+ while (atom != nullptr) {
+ QString str = atom->string();
+ str.replace("\\", "\\\\");
+ str.replace("\"", "\\\"");
+ str.replace("\n", "\\n");
+ static const QRegularExpression re(R"([^ -~])");
+ str.replace(re, "?");
+ if (!str.isEmpty())
+ str = " \"" + str + QLatin1Char('"');
+
+ QString atom_type = atom->typeString();
+ if (atom_type.contains("Right"))
+ indentation_level = std::max(minimum_indentation_level, indentation_level - 1);
+
+ fprintf(stderr, "%s%s%s\n",
+ QString(indentation_level * indentation_width, ' ').toLatin1().data(),
+ atom_type.toLatin1().data(), str.toLatin1().data());
+
+ if (atom_type.contains("Left"))
+ indentation_level += 1;
+
+ atom = atom->next();
+ }
+}
+
+Text Text::subText(const Atom *begin, const Atom *end)
+{
+ Text text;
+ if (begin != nullptr) {
+ while (begin != end) {
+ text << *begin;
+ begin = begin->next();
+ }
+ }
+ return text;
+}
+
+void Text::clear()
+{
+ while (m_first != nullptr) {
+ Atom *atom = m_first;
+ m_first = m_first->next();
+ delete atom;
+ }
+ m_first = nullptr;
+ m_last = nullptr;
+}
+
+int Text::compare(const Text &text1, const Text &text2)
+{
+ if (text1.isEmpty())
+ return text2.isEmpty() ? 0 : -1;
+ if (text2.isEmpty())
+ return 1;
+
+ const Atom *atom1 = text1.firstAtom();
+ const Atom *atom2 = text2.firstAtom();
+
+ for (;;) {
+ if (atom1->type() != atom2->type())
+ return (int)atom1->type() - (int)atom2->type();
+ int cmp = QString::compare(atom1->string(), atom2->string());
+ if (cmp != 0)
+ return cmp;
+
+ if (atom1 == text1.lastAtom())
+ return atom2 == text2.lastAtom() ? 0 : -1;
+ if (atom2 == text2.lastAtom())
+ return 1;
+ atom1 = atom1->next();
+ atom2 = atom2->next();
+ }
+}
+
+/*!
+ \internal
+
+ \brief Splits the current Text from \a start to end into a new Text object.
+
+ Returns a new Text from the first Atom in this Text of atom type \a start.
+ */
+Text Text::splitAtFirst(Atom::AtomType start) {
+ if (m_first == nullptr)
+ return {};
+
+ Atom *previous = nullptr;
+ Atom *current = m_first;
+
+ while (current != nullptr) {
+ if (current->type() == start)
+ break;
+ previous = current;
+ current = current->next();
+ }
+
+ if (!current)
+ return {};
+
+ Text splitText = Text(current, m_last);
+
+ // Reset this Text's first and last atom pointers based on
+ // whether all or part of the content was extracted.
+ m_first = previous ? m_first : nullptr;
+ if (m_last = previous; m_last)
+ m_last->setNext(nullptr);
+
+ return splitText;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/text.h b/src/qdoc/qdoc/src/qdoc/text.h
new file mode 100644
index 000000000..d42143909
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/text.h
@@ -0,0 +1,87 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef TEXT_H
+#define TEXT_H
+
+#include "atom.h"
+
+QT_BEGIN_NAMESPACE
+
+class Text
+{
+public:
+ Text();
+ explicit Text(const QString &str);
+ Text(const Text &text);
+ ~Text();
+
+ Text &operator=(const Text &text);
+
+ Atom *firstAtom() { return m_first; }
+ Atom *lastAtom() { return m_last; }
+ Text &operator<<(Atom::AtomType atomType);
+ Text &operator<<(const QString &string);
+ Text &operator<<(const Atom &atom);
+ Text &operator<<(const LinkAtom &atom);
+ Text &operator<<(const Text &text);
+ void stripFirstAtom();
+ void stripLastAtom();
+
+ [[nodiscard]] bool isEmpty() const { return m_first == nullptr; }
+ [[nodiscard]] bool contains(const QString &str) const;
+ [[nodiscard]] QString toString() const;
+ [[nodiscard]] const Atom *firstAtom() const { return m_first; }
+ [[nodiscard]] const Atom *lastAtom() const { return m_last; }
+ Text subText(Atom::AtomType left, Atom::AtomType right, const Atom *from = nullptr,
+ bool inclusive = false) const;
+ void dump() const;
+ void clear();
+
+ static Text subText(const Atom *begin, const Atom *end = nullptr);
+ static Text sectionHeading(const Atom *sectionBegin);
+ static int compare(const Text &text1, const Text &text2);
+
+ [[nodiscard]] Text splitAtFirst(Atom::AtomType start);
+
+private:
+ Text(Atom *start, Atom *end) : m_first(start), m_last(end)
+ {
+ Q_ASSERT(m_first != nullptr);
+ Q_ASSERT(m_last != nullptr);
+
+ m_last->setNext(nullptr);
+ }
+
+ Atom *m_first { nullptr };
+ Atom *m_last { nullptr };
+};
+
+inline bool operator==(const Text &text1, const Text &text2)
+{
+ return Text::compare(text1, text2) == 0;
+}
+inline bool operator!=(const Text &text1, const Text &text2)
+{
+ return Text::compare(text1, text2) != 0;
+}
+inline bool operator<(const Text &text1, const Text &text2)
+{
+ return Text::compare(text1, text2) < 0;
+}
+inline bool operator<=(const Text &text1, const Text &text2)
+{
+ return Text::compare(text1, text2) <= 0;
+}
+inline bool operator>(const Text &text1, const Text &text2)
+{
+ return Text::compare(text1, text2) > 0;
+}
+inline bool operator>=(const Text &text1, const Text &text2)
+{
+ return Text::compare(text1, text2) >= 0;
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/tokenizer.cpp b/src/qdoc/qdoc/src/qdoc/tokenizer.cpp
new file mode 100644
index 000000000..bfbfc53c5
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/tokenizer.cpp
@@ -0,0 +1,788 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "tokenizer.h"
+
+#include "config.h"
+#include "generator.h"
+
+#include <QtCore/qfile.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringconverter.h>
+
+#include <cctype>
+#include <cstring>
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+#define LANGUAGE_CPP "Cpp"
+
+/* qmake ignore Q_OBJECT */
+
+/*
+ Keep in sync with tokenizer.h.
+*/
+static const char *kwords[] = { "char",
+ "class",
+ "const",
+ "double",
+ "enum",
+ "explicit",
+ "friend",
+ "inline",
+ "int",
+ "long",
+ "namespace",
+ "operator",
+ "private",
+ "protected",
+ "public",
+ "short",
+ "signals",
+ "signed",
+ "slots",
+ "static",
+ "struct",
+ "template",
+ "typedef",
+ "typename",
+ "union",
+ "unsigned",
+ "using",
+ "virtual",
+ "void",
+ "volatile",
+ "__int64",
+ "default",
+ "delete",
+ "final",
+ "override",
+ "Q_OBJECT",
+ "Q_OVERRIDE",
+ "Q_PROPERTY",
+ "Q_PRIVATE_PROPERTY",
+ "Q_DECLARE_SEQUENTIAL_ITERATOR",
+ "Q_DECLARE_MUTABLE_SEQUENTIAL_ITERATOR",
+ "Q_DECLARE_ASSOCIATIVE_ITERATOR",
+ "Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR",
+ "Q_DECLARE_FLAGS",
+ "Q_SIGNALS",
+ "Q_SLOTS",
+ "QT_COMPAT",
+ "QT_COMPAT_CONSTRUCTOR",
+ "QT_DEPRECATED",
+ "QT_MOC_COMPAT",
+ "QT_MODULE",
+ "QT3_SUPPORT",
+ "QT3_SUPPORT_CONSTRUCTOR",
+ "QT3_MOC_SUPPORT",
+ "QDOC_PROPERTY",
+ "QPrivateSignal" };
+
+static const int KwordHashTableSize = 4096;
+static int kwordHashTable[KwordHashTableSize];
+
+static QHash<QByteArray, bool> *ignoredTokensAndDirectives = nullptr;
+
+static QRegularExpression *comment = nullptr;
+static QRegularExpression *versionX = nullptr;
+static QRegularExpression *definedX = nullptr;
+
+static QRegularExpression *defines = nullptr;
+static QRegularExpression *falsehoods = nullptr;
+
+static QStringDecoder sourceDecoder;
+
+/*
+ This function is a perfect hash function for the 37 keywords of C99
+ (with a hash table size of 512). It should perform well on our
+ Qt-enhanced C++ subset.
+*/
+static int hashKword(const char *s, int len)
+{
+ return (((uchar)s[0]) + (((uchar)s[2]) << 5) + (((uchar)s[len - 1]) << 3)) % KwordHashTableSize;
+}
+
+static void insertKwordIntoHash(const char *s, int number)
+{
+ int k = hashKword(s, int(strlen(s)));
+ while (kwordHashTable[k]) {
+ if (++k == KwordHashTableSize)
+ k = 0;
+ }
+ kwordHashTable[k] = number;
+}
+
+Tokenizer::Tokenizer(const Location &loc, QFile &in)
+{
+ init();
+ m_in = in.readAll();
+ m_pos = 0;
+ start(loc);
+}
+
+Tokenizer::Tokenizer(const Location &loc, QByteArray in) : m_in(std::move(in))
+{
+ init();
+ m_pos = 0;
+ start(loc);
+}
+
+Tokenizer::~Tokenizer()
+{
+ delete[] m_lexBuf1;
+ delete[] m_lexBuf2;
+}
+
+int Tokenizer::getToken()
+{
+ token_too_long_warning_was_issued = false;
+
+ char *t = m_prevLex;
+ m_prevLex = m_lex;
+ m_lex = t;
+
+ while (m_ch != EOF) {
+ m_tokLoc = m_curLoc;
+ m_lexLen = 0;
+
+ if (isspace(m_ch)) {
+ do {
+ m_ch = getChar();
+ } while (isspace(m_ch));
+ } else if (isalpha(m_ch) || m_ch == '_') {
+ do {
+ m_ch = getChar();
+ } while (isalnum(m_ch) || m_ch == '_');
+
+ int k = hashKword(m_lex, int(m_lexLen));
+ for (;;) {
+ int i = kwordHashTable[k];
+ if (i == 0) {
+ return Tok_Ident;
+ } else if (i == -1) {
+ if (!m_parsingMacro && ignoredTokensAndDirectives->contains(m_lex)) {
+ if (ignoredTokensAndDirectives->value(m_lex)) { // it's a directive
+ int parenDepth = 0;
+ while (m_ch != EOF && (m_ch != ')' || parenDepth > 1)) {
+ if (m_ch == '(')
+ ++parenDepth;
+ else if (m_ch == ')')
+ --parenDepth;
+ m_ch = getChar();
+ }
+ if (m_ch == ')')
+ m_ch = getChar();
+ }
+ break;
+ }
+ } else if (strcmp(m_lex, kwords[i - 1]) == 0) {
+ int ret = (int)Tok_FirstKeyword + i - 1;
+ if (ret != Tok_typename)
+ return ret;
+ break;
+ }
+
+ if (++k == KwordHashTableSize)
+ k = 0;
+ }
+ } else if (isdigit(m_ch)) {
+ do {
+ m_ch = getChar();
+ } while (isalnum(m_ch) || m_ch == '.' || m_ch == '+' || m_ch == '-');
+ return Tok_Number;
+ } else {
+ switch (m_ch) {
+ case '!':
+ case '%':
+ m_ch = getChar();
+ if (m_ch == '=')
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ case '"':
+ m_ch = getChar();
+
+ while (m_ch != EOF && m_ch != '"') {
+ if (m_ch == '\\')
+ m_ch = getChar();
+ m_ch = getChar();
+ }
+ m_ch = getChar();
+
+ if (m_ch == EOF)
+ m_tokLoc.warning(
+ QStringLiteral("Unterminated C++ string literal"),
+ QStringLiteral("Maybe you forgot '/*!' at the beginning of the file?"));
+ else
+ return Tok_String;
+ break;
+ case '#':
+ return getTokenAfterPreprocessor();
+ case '&':
+ m_ch = getChar();
+ /*
+ Removed check for '&&', only interpret '&=' as an operator.
+ '&&' is also used for an rvalue reference. QTBUG-32675
+ */
+ if (m_ch == '=') {
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ } else {
+ return Tok_Ampersand;
+ }
+ case '\'':
+ m_ch = getChar();
+ /*
+ Allow empty character literal. QTBUG-25775
+ */
+ if (m_ch == '\'') {
+ m_ch = getChar();
+ break;
+ }
+ if (m_ch == '\\')
+ m_ch = getChar();
+ do {
+ m_ch = getChar();
+ } while (m_ch != EOF && m_ch != '\'');
+
+ if (m_ch == EOF) {
+ m_tokLoc.warning(QStringLiteral("Unterminated C++ character literal"));
+ } else {
+ m_ch = getChar();
+ return Tok_Number;
+ }
+ break;
+ case '(':
+ m_ch = getChar();
+ if (m_numPreprocessorSkipping == 0)
+ m_parenDepth++;
+ if (isspace(m_ch)) {
+ do {
+ m_ch = getChar();
+ } while (isspace(m_ch));
+ m_lexLen = 1;
+ m_lex[1] = '\0';
+ }
+ if (m_ch == '*') {
+ m_ch = getChar();
+ return Tok_LeftParenAster;
+ }
+ return Tok_LeftParen;
+ case ')':
+ m_ch = getChar();
+ if (m_numPreprocessorSkipping == 0)
+ m_parenDepth--;
+ return Tok_RightParen;
+ case '*':
+ m_ch = getChar();
+ if (m_ch == '=') {
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ } else {
+ return Tok_Aster;
+ }
+ case '^':
+ m_ch = getChar();
+ if (m_ch == '=') {
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ } else {
+ return Tok_Caret;
+ }
+ case '+':
+ m_ch = getChar();
+ if (m_ch == '+' || m_ch == '=')
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ case ',':
+ m_ch = getChar();
+ return Tok_Comma;
+ case '-':
+ m_ch = getChar();
+ if (m_ch == '-' || m_ch == '=') {
+ m_ch = getChar();
+ } else if (m_ch == '>') {
+ m_ch = getChar();
+ if (m_ch == '*')
+ m_ch = getChar();
+ }
+ return Tok_SomeOperator;
+ case '.':
+ m_ch = getChar();
+ if (m_ch == '*') {
+ m_ch = getChar();
+ } else if (m_ch == '.') {
+ do {
+ m_ch = getChar();
+ } while (m_ch == '.');
+ return Tok_Ellipsis;
+ } else if (isdigit(m_ch)) {
+ do {
+ m_ch = getChar();
+ } while (isalnum(m_ch) || m_ch == '.' || m_ch == '+' || m_ch == '-');
+ return Tok_Number;
+ }
+ return Tok_SomeOperator;
+ case '/':
+ m_ch = getChar();
+ if (m_ch == '/') {
+ do {
+ m_ch = getChar();
+ } while (m_ch != EOF && m_ch != '\n');
+ } else if (m_ch == '*') {
+ bool metDoc = false; // empty doc is no doc
+ bool metSlashAsterBang = false;
+ bool metAster = false;
+ bool metAsterSlash = false;
+
+ m_ch = getChar();
+ if (m_ch == '!')
+ metSlashAsterBang = true;
+
+ while (!metAsterSlash) {
+ if (m_ch == EOF) {
+ m_tokLoc.warning(QStringLiteral("Unterminated C++ comment"));
+ break;
+ } else {
+ if (m_ch == '*') {
+ metAster = true;
+ } else if (metAster && m_ch == '/') {
+ metAsterSlash = true;
+ } else {
+ metAster = false;
+ if (isgraph(m_ch))
+ metDoc = true;
+ }
+ }
+ m_ch = getChar();
+ }
+ if (metSlashAsterBang && metDoc)
+ return Tok_Doc;
+ else if (m_parenDepth > 0)
+ return Tok_Comment;
+ } else {
+ if (m_ch == '=')
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ }
+ break;
+ case ':':
+ m_ch = getChar();
+ if (m_ch == ':') {
+ m_ch = getChar();
+ return Tok_Gulbrandsen;
+ } else {
+ return Tok_Colon;
+ }
+ case ';':
+ m_ch = getChar();
+ return Tok_Semicolon;
+ case '<':
+ m_ch = getChar();
+ if (m_ch == '<') {
+ m_ch = getChar();
+ if (m_ch == '=')
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ } else if (m_ch == '=') {
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ } else {
+ return Tok_LeftAngle;
+ }
+ case '=':
+ m_ch = getChar();
+ if (m_ch == '=') {
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ } else {
+ return Tok_Equal;
+ }
+ case '>':
+ m_ch = getChar();
+ if (m_ch == '>') {
+ m_ch = getChar();
+ if (m_ch == '=')
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ } else if (m_ch == '=') {
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ } else {
+ return Tok_RightAngle;
+ }
+ case '?':
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ case '[':
+ m_ch = getChar();
+ if (m_numPreprocessorSkipping == 0)
+ m_bracketDepth++;
+ return Tok_LeftBracket;
+ case '\\':
+ m_ch = getChar();
+ m_ch = getChar(); // skip one character
+ break;
+ case ']':
+ m_ch = getChar();
+ if (m_numPreprocessorSkipping == 0)
+ m_bracketDepth--;
+ return Tok_RightBracket;
+ case '{':
+ m_ch = getChar();
+ if (m_numPreprocessorSkipping == 0)
+ m_braceDepth++;
+ return Tok_LeftBrace;
+ case '}':
+ m_ch = getChar();
+ if (m_numPreprocessorSkipping == 0)
+ m_braceDepth--;
+ return Tok_RightBrace;
+ case '|':
+ m_ch = getChar();
+ if (m_ch == '|' || m_ch == '=')
+ m_ch = getChar();
+ return Tok_SomeOperator;
+ case '~':
+ m_ch = getChar();
+ return Tok_Tilde;
+ case '@':
+ m_ch = getChar();
+ return Tok_At;
+ default:
+ // ### We should really prevent qdoc from looking at snippet files rather than
+ // ### suppress warnings when reading them.
+ if (m_numPreprocessorSkipping == 0
+ && !(m_tokLoc.fileName().endsWith(".qdoc")
+ || m_tokLoc.fileName().endsWith(".js"))) {
+ m_tokLoc.warning(QStringLiteral("Hostile character 0x%1 in C++ source")
+ .arg((uchar)m_ch, 1, 16));
+ }
+ m_ch = getChar();
+ }
+ }
+ }
+
+ if (m_preprocessorSkipping.size() > 1) {
+ m_tokLoc.warning(QStringLiteral("Expected #endif before end of file"));
+ // clear it out or we get an infinite loop!
+ while (!m_preprocessorSkipping.isEmpty()) {
+ popSkipping();
+ }
+ }
+
+ strcpy(m_lex, "end-of-input");
+ m_lexLen = strlen(m_lex);
+ return Tok_Eoi;
+}
+
+void Tokenizer::initialize()
+{
+ Config &config = Config::instance();
+ QString versionSym = config.get(CONFIG_VERSIONSYM).asString();
+ const QLatin1String defaultEncoding("UTF-8");
+
+ QString sourceEncoding = config.get(CONFIG_SOURCEENCODING).asString(defaultEncoding);
+ if (!QStringConverter::encodingForName(sourceEncoding.toUtf8().constData())) {
+ Location().warning(QStringLiteral("Source encoding '%1' not supported, using '%2' as default.")
+ .arg(sourceEncoding, defaultEncoding));
+ sourceEncoding = defaultEncoding;
+ }
+ sourceDecoder = QStringDecoder(sourceEncoding.toUtf8().constData());
+ Q_ASSERT(sourceDecoder.isValid());
+
+ comment = new QRegularExpression("/(?:\\*.*\\*/|/.*\n|/[^\n]*$)", QRegularExpression::InvertedGreedinessOption);
+ versionX = new QRegularExpression("$cannot possibly match^");
+ if (!versionSym.isEmpty())
+ versionX->setPattern("^[ \t]*(?:" + QRegularExpression::escape(versionSym)
+ + ")[ \t]+\"([^\"]*)\"[ \t]*$");
+ definedX = new QRegularExpression("^defined ?\\(?([A-Z_0-9a-z]+) ?\\)?$");
+
+ QStringList d{config.get(CONFIG_DEFINES).asStringList()};
+ d += "qdoc";
+ defines = new QRegularExpression(QRegularExpression::anchoredPattern(d.join('|')));
+ falsehoods = new QRegularExpression(QRegularExpression::anchoredPattern(
+ config.get(CONFIG_FALSEHOODS).asStringList().join('|')));
+
+ /*
+ The keyword hash table is always cleared before any words are inserted.
+ */
+ memset(kwordHashTable, 0, sizeof(kwordHashTable));
+ for (int i = 0; i < Tok_LastKeyword - Tok_FirstKeyword + 1; i++)
+ insertKwordIntoHash(kwords[i], i + 1);
+
+ ignoredTokensAndDirectives = new QHash<QByteArray, bool>;
+
+ const QStringList tokens{config.get(LANGUAGE_CPP
+ + Config::dot
+ + CONFIG_IGNORETOKENS).asStringList()};
+ for (const auto &token : tokens) {
+ const QByteArray tb = token.toLatin1();
+ ignoredTokensAndDirectives->insert(tb, false);
+ insertKwordIntoHash(tb.data(), -1);
+ }
+
+ const QStringList directives{config.get(LANGUAGE_CPP
+ + Config::dot
+ + CONFIG_IGNOREDIRECTIVES).asStringList()};
+ for (const auto &directive : directives) {
+ const QByteArray db = directive.toLatin1();
+ ignoredTokensAndDirectives->insert(db, true);
+ insertKwordIntoHash(db.data(), -1);
+ }
+}
+
+/*!
+ The heap allocated variables are freed here. The keyword
+ hash table is not cleared here, but it is cleared in the
+ initialize() function, before any keywords are inserted.
+ */
+void Tokenizer::terminate()
+{
+ delete comment;
+ comment = nullptr;
+ delete versionX;
+ versionX = nullptr;
+ delete definedX;
+ definedX = nullptr;
+ delete defines;
+ defines = nullptr;
+ delete falsehoods;
+ falsehoods = nullptr;
+ delete ignoredTokensAndDirectives;
+ ignoredTokensAndDirectives = nullptr;
+}
+
+void Tokenizer::init()
+{
+ m_lexBuf1 = new char[(int)yyLexBufSize];
+ m_lexBuf2 = new char[(int)yyLexBufSize];
+ m_prevLex = m_lexBuf1;
+ m_prevLex[0] = '\0';
+ m_lex = m_lexBuf2;
+ m_lex[0] = '\0';
+ m_lexLen = 0;
+ m_preprocessorSkipping.push(false);
+ m_numPreprocessorSkipping = 0;
+ m_braceDepth = 0;
+ m_parenDepth = 0;
+ m_bracketDepth = 0;
+ m_ch = '\0';
+ m_parsingMacro = false;
+}
+
+void Tokenizer::start(const Location &loc)
+{
+ m_tokLoc = loc;
+ m_curLoc = loc;
+ m_curLoc.start();
+ strcpy(m_prevLex, "beginning-of-input");
+ strcpy(m_lex, "beginning-of-input");
+ m_lexLen = strlen(m_lex);
+ m_braceDepth = 0;
+ m_parenDepth = 0;
+ m_bracketDepth = 0;
+ m_ch = '\0';
+ m_ch = getChar();
+}
+
+/*
+ Returns the next token, if # was met. This function interprets the
+ preprocessor directive, skips over any #ifdef'd out tokens, and returns the
+ token after all of that.
+*/
+int Tokenizer::getTokenAfterPreprocessor()
+{
+ m_ch = getChar();
+ while (isspace(m_ch) && m_ch != '\n')
+ m_ch = getChar();
+
+ /*
+ #directive condition
+ */
+ QString directive;
+ QString condition;
+
+ while (isalpha(m_ch)) {
+ directive += QChar(m_ch);
+ m_ch = getChar();
+ }
+ if (!directive.isEmpty()) {
+ while (m_ch != EOF && m_ch != '\n') {
+ if (m_ch == '\\') {
+ m_ch = getChar();
+ if (m_ch == '\r')
+ m_ch = getChar();
+ }
+ condition += QChar(m_ch);
+ m_ch = getChar();
+ }
+ condition.remove(*comment);
+ condition = condition.simplified();
+
+ /*
+ The #if, #ifdef, #ifndef, #elif, #else, and #endif
+ directives have an effect on the skipping stack. For
+ instance, if the code processed so far is
+
+ #if 1
+ #if 0
+ #if 1
+ // ...
+ #else
+
+ the skipping stack contains, from bottom to top, false true
+ true (assuming 0 is false and 1 is true). If at least one
+ entry of the stack is true, the tokens are skipped.
+
+ This mechanism is simple yet hard to understand.
+ */
+ if (directive[0] == QChar('i')) {
+ if (directive == QString("if"))
+ pushSkipping(!isTrue(condition));
+ else if (directive == QString("ifdef"))
+ pushSkipping(!defines->match(condition).hasMatch());
+ else if (directive == QString("ifndef"))
+ pushSkipping(defines->match(condition).hasMatch());
+ } else if (directive[0] == QChar('e')) {
+ if (directive == QString("elif")) {
+ bool old = popSkipping();
+ if (old)
+ pushSkipping(!isTrue(condition));
+ else
+ pushSkipping(true);
+ } else if (directive == QString("else")) {
+ pushSkipping(!popSkipping());
+ } else if (directive == QString("endif")) {
+ popSkipping();
+ }
+ } else if (directive == QString("define")) {
+ auto match = versionX->match(condition);
+ if (match.hasMatch())
+ m_version = match.captured(1);
+ }
+ }
+
+ int tok;
+ do {
+ /*
+ We set yyLex now, and after getToken() this will be
+ yyPrevLex. This way, we skip over the preprocessor
+ directive.
+ */
+ qstrcpy(m_lex, m_prevLex);
+
+ /*
+ If getToken() meets another #, it will call
+ getTokenAfterPreprocessor() once again, which could in turn
+ call getToken() again, etc. Unless there are 10,000 or so
+ preprocessor directives in a row, this shouldn't overflow
+ the stack.
+ */
+ tok = getToken();
+ } while (m_numPreprocessorSkipping > 0 && tok != Tok_Eoi);
+ return tok;
+}
+
+/*
+ Pushes a new skipping value onto the stack. This corresponds to entering a
+ new #if block.
+*/
+void Tokenizer::pushSkipping(bool skip)
+{
+ m_preprocessorSkipping.push(skip);
+ if (skip)
+ m_numPreprocessorSkipping++;
+}
+
+/*
+ Pops a skipping value from the stack. This corresponds to reaching a #endif.
+*/
+bool Tokenizer::popSkipping()
+{
+ if (m_preprocessorSkipping.isEmpty()) {
+ m_tokLoc.warning(QStringLiteral("Unexpected #elif, #else or #endif"));
+ return true;
+ }
+
+ bool skip = m_preprocessorSkipping.pop();
+ if (skip)
+ m_numPreprocessorSkipping--;
+ return skip;
+}
+
+/*
+ Returns \c true if the condition evaluates as true, otherwise false. The
+ condition is represented by a string. Unsophisticated parsing techniques are
+ used. The preprocessing method could be named StriNg-Oriented PreProcessing,
+ as SNOBOL stands for StriNg-Oriented symBOlic Language.
+*/
+bool Tokenizer::isTrue(const QString &condition)
+{
+ int firstOr = -1;
+ int firstAnd = -1;
+ int parenDepth = 0;
+
+ /*
+ Find the first logical operator at top level, but be careful
+ about precedence. Examples:
+
+ X || Y // the or
+ X || Y || Z // the leftmost or
+ X || Y && Z // the or
+ X && Y || Z // the or
+ (X || Y) && Z // the and
+ */
+ for (int i = 0; i < condition.size() - 1; i++) {
+ QChar ch = condition[i];
+ if (ch == QChar('(')) {
+ parenDepth++;
+ } else if (ch == QChar(')')) {
+ parenDepth--;
+ } else if (parenDepth == 0) {
+ if (condition[i + 1] == ch) {
+ if (ch == QChar('|')) {
+ firstOr = i;
+ break;
+ } else if (ch == QChar('&')) {
+ if (firstAnd == -1)
+ firstAnd = i;
+ }
+ }
+ }
+ }
+ if (firstOr != -1)
+ return isTrue(condition.left(firstOr)) || isTrue(condition.mid(firstOr + 2));
+ if (firstAnd != -1)
+ return isTrue(condition.left(firstAnd)) && isTrue(condition.mid(firstAnd + 2));
+
+ QString t = condition.simplified();
+ if (t.isEmpty())
+ return true;
+
+ if (t[0] == QChar('!'))
+ return !isTrue(t.mid(1));
+ if (t[0] == QChar('(') && t.endsWith(QChar(')')))
+ return isTrue(t.mid(1, t.size() - 2));
+
+ auto match = definedX->match(t);
+ if (match.hasMatch())
+ return defines->match(match.captured(1)).hasMatch();
+ else
+ return !falsehoods->match(t).hasMatch();
+}
+
+QString Tokenizer::lexeme() const
+{
+ return sourceDecoder(m_lex);
+}
+
+QString Tokenizer::previousLexeme() const
+{
+ return sourceDecoder(m_prevLex);
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/tokenizer.h b/src/qdoc/qdoc/src/qdoc/tokenizer.h
new file mode 100644
index 000000000..d5669dfb7
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/tokenizer.h
@@ -0,0 +1,179 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef TOKENIZER_H
+#define TOKENIZER_H
+
+#include "location.h"
+
+#include <QtCore/qfile.h>
+#include <QtCore/qstack.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+/*
+ Here come the C++ tokens we support. The first part contains
+ all-purpose tokens; then come keywords.
+
+ If you add a keyword, make sure to modify the keyword array in
+ tokenizer.cpp as well, and possibly adjust Tok_FirstKeyword and
+ Tok_LastKeyword.
+*/
+enum {
+ Tok_Eoi,
+ Tok_Ampersand,
+ Tok_Aster,
+ Tok_Caret,
+ Tok_LeftParen,
+ Tok_RightParen,
+ Tok_LeftParenAster,
+ Tok_Equal,
+ Tok_LeftBrace,
+ Tok_RightBrace,
+ Tok_Semicolon,
+ Tok_Colon,
+ Tok_LeftAngle,
+ Tok_RightAngle,
+ Tok_Comma,
+ Tok_Ellipsis,
+ Tok_Gulbrandsen,
+ Tok_LeftBracket,
+ Tok_RightBracket,
+ Tok_Tilde,
+ Tok_SomeOperator,
+ Tok_Number,
+ Tok_String,
+ Tok_Doc,
+ Tok_Comment,
+ Tok_Ident,
+ Tok_At,
+ Tok_char,
+ Tok_class,
+ Tok_const,
+ Tok_double,
+ Tok_int,
+ Tok_long,
+ Tok_operator,
+ Tok_short,
+ Tok_signed,
+ Tok_typename,
+ Tok_unsigned,
+ Tok_void,
+ Tok_volatile,
+ Tok_int64,
+ Tok_QPrivateSignal,
+ Tok_FirstKeyword = Tok_char,
+ Tok_LastKeyword = Tok_QPrivateSignal
+};
+
+/*
+ The Tokenizer class implements lexical analysis of C++ source
+ files.
+
+ Not every operator or keyword of C++ is recognized; only those
+ that are interesting to us. Some Qt keywords or macros are also
+ recognized.
+*/
+
+class Tokenizer
+{
+public:
+ Tokenizer(const Location &loc, QByteArray in);
+ Tokenizer(const Location &loc, QFile &file);
+
+ ~Tokenizer();
+
+ int getToken();
+ void setParsingFnOrMacro(bool macro) { m_parsingMacro = macro; }
+
+ [[nodiscard]] const Location &location() const { return m_tokLoc; }
+ [[nodiscard]] QString previousLexeme() const;
+ [[nodiscard]] QString lexeme() const;
+ [[nodiscard]] QString version() const { return m_version; }
+ [[nodiscard]] int parenDepth() const { return m_parenDepth; }
+ [[nodiscard]] int bracketDepth() const { return m_bracketDepth; }
+
+ static void initialize();
+ static void terminate();
+ static bool isTrue(const QString &condition);
+
+private:
+ void init();
+ void start(const Location &loc);
+ /*
+ Represents the maximum amount of characters that a token can be composed
+ of.
+
+ When a token with more characters than the maximum amount is encountered, a
+ warning is issued and parsing continues, discarding all characters from the
+ currently parsed token that don't fit into the buffer.
+ */
+ enum { yyLexBufSize = 1048576 };
+
+ int getch() { return m_pos == m_in.size() ? EOF : m_in[m_pos++]; }
+
+ inline int getChar()
+ {
+ using namespace Qt::StringLiterals;
+
+ if (m_ch == EOF)
+ return EOF;
+ if (m_lexLen < yyLexBufSize - 1) {
+ m_lex[m_lexLen++] = (char)m_ch;
+ m_lex[m_lexLen] = '\0';
+ } else if (!token_too_long_warning_was_issued) {
+ location().warning(
+ u"The content is too long.\n"_s,
+ u"The maximum amount of characters for this content is %1.\n"_s.arg(yyLexBufSize) +
+ "Consider splitting it or reducing its size."
+ );
+
+ token_too_long_warning_was_issued = true;
+ }
+ m_curLoc.advance(QChar(m_ch));
+ int ch = getch();
+ if (ch == EOF)
+ return EOF;
+ // cast explicitly to make sure the value of ch
+ // is in range [0..255] to avoid assert messages
+ // when using debug CRT that checks its input.
+ return int(uint(uchar(ch)));
+ }
+
+ int getTokenAfterPreprocessor();
+ void pushSkipping(bool skip);
+ bool popSkipping();
+
+ Location m_tokLoc;
+ Location m_curLoc;
+ char *m_lexBuf1 { nullptr };
+ char *m_lexBuf2 { nullptr };
+ char *m_prevLex { nullptr };
+ char *m_lex { nullptr };
+ size_t m_lexLen {};
+ QStack<bool> m_preprocessorSkipping;
+ int m_numPreprocessorSkipping {};
+ int m_braceDepth {};
+ int m_parenDepth {};
+ int m_bracketDepth {};
+ int m_ch {};
+
+ QString m_version {};
+ bool m_parsingMacro {};
+
+ // Used to ensure that the warning that is issued when a token is
+ // too long to fit into our fixed sized buffer is not repeated for each
+ // character of that token after the last saved one.
+ // The flag is reset whenever a new token is requested, so as to allow
+ // reporting all such tokens that are too long during a single execution.
+ bool token_too_long_warning_was_issued{false};
+
+protected:
+ QByteArray m_in {};
+ int m_pos {};
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/topic.h b/src/qdoc/qdoc/src/qdoc/topic.h
new file mode 100644
index 000000000..1f9646864
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/topic.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#ifndef TOPIC_H
+#define TOPIC_H
+
+QT_BEGIN_NAMESPACE
+
+struct Topic
+{
+public:
+ Topic() = default;
+ Topic(QString &t, QString a) : m_topic(t), m_args(std::move(a)) { }
+ ~Topic() = default;
+
+ [[nodiscard]] bool isEmpty() const { return m_topic.isEmpty(); }
+ void clear()
+ {
+ m_topic.clear();
+ m_args.clear();
+ }
+
+ QString m_topic {};
+ QString m_args {};
+};
+typedef QList<Topic> TopicList;
+
+QT_END_NAMESPACE
+
+#endif // TOPIC_H
diff --git a/src/qdoc/qdoc/src/qdoc/tree.cpp b/src/qdoc/qdoc/src/qdoc/tree.cpp
new file mode 100644
index 000000000..5c8f7d6d1
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/tree.cpp
@@ -0,0 +1,1357 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "tree.h"
+
+#include "classnode.h"
+#include "collectionnode.h"
+#include "doc.h"
+#include "enumnode.h"
+#include "functionnode.h"
+#include "htmlgenerator.h"
+#include "location.h"
+#include "node.h"
+#include "qdocdatabase.h"
+#include "text.h"
+#include "typedefnode.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class Tree
+
+ This class constructs and maintains a tree of instances of
+ the subclasses of Node.
+
+ This class is now private. Only class QDocDatabase has access.
+ Please don't change this. If you must access class Tree, do it
+ though the pointer to the singleton QDocDatabase.
+
+ Tree is being converted to a forest. A static member provides a
+ map of Tree *values with the module names as the keys. There is
+ one Tree in the map for each index file read, and there is one
+ tree that is not in the map for the module whose documentation
+ is being generated.
+ */
+
+/*!
+ Constructs a Tree. \a qdb is the pointer to the singleton
+ qdoc database that is constructing the tree. This might not
+ be necessary, and it might be removed later.
+
+ \a camelCaseModuleName is the project name for this tree
+ as it appears in the qdocconf file.
+ */
+Tree::Tree(const QString &camelCaseModuleName, QDocDatabase *qdb)
+ : m_camelCaseModuleName(camelCaseModuleName),
+ m_physicalModuleName(camelCaseModuleName.toLower()),
+ m_qdb(qdb),
+ m_root(nullptr, QString())
+{
+ m_root.setPhysicalModuleName(m_physicalModuleName);
+ m_root.setTree(this);
+}
+
+/*!
+ Destroys the Tree.
+
+ There are two maps of targets, keywords, and contents.
+ One map is indexed by ref, the other by title. Both maps
+ use the same set of TargetRec objects as the values,
+ so we only need to delete the values from one of them.
+
+ The Node instances themselves are destroyed by the root
+ node's (\c m_root) destructor.
+ */
+Tree::~Tree()
+{
+ qDeleteAll(m_nodesByTargetRef);
+ m_nodesByTargetRef.clear();
+ m_nodesByTargetTitle.clear();
+}
+
+/* API members */
+
+/*!
+ Calls findClassNode() first with \a path and \a start. If
+ it finds a node, the node is returned. If not, it calls
+ findNamespaceNode() with the same parameters. The result
+ is returned.
+ */
+Node *Tree::findNodeForInclude(const QStringList &path) const
+{
+ Node *n = findClassNode(path);
+ if (n == nullptr)
+ n = findNamespaceNode(path);
+ return n;
+}
+
+/*!
+ This function searches this tree for an Aggregate node with
+ the specified \a name. It returns the pointer to that node
+ or nullptr.
+
+ We might need to split the name on '::' but we assume the
+ name is a single word at the moment.
+ */
+Aggregate *Tree::findAggregate(const QString &name)
+{
+ QStringList path = name.split(QLatin1String("::"));
+ return static_cast<Aggregate *>(findNodeRecursive(path, 0, const_cast<NamespaceNode *>(root()),
+ &Node::isFirstClassAggregate));
+}
+
+/*!
+ Find the C++ class node named \a path. Begin the search at the
+ \a start node. If the \a start node is 0, begin the search
+ at the root of the tree. Only a C++ class node named \a path is
+ acceptible. If one is not found, 0 is returned.
+ */
+ClassNode *Tree::findClassNode(const QStringList &path, const Node *start) const
+{
+ if (start == nullptr)
+ start = const_cast<NamespaceNode *>(root());
+ return static_cast<ClassNode *>(findNodeRecursive(path, 0, start, &Node::isClassNode));
+}
+
+/*!
+ Find the Namespace node named \a path. Begin the search at
+ the root of the tree. Only a Namespace node named \a path
+ is acceptible. If one is not found, 0 is returned.
+ */
+NamespaceNode *Tree::findNamespaceNode(const QStringList &path) const
+{
+ Node *start = const_cast<NamespaceNode *>(root());
+ return static_cast<NamespaceNode *>(findNodeRecursive(path, 0, start, &Node::isNamespace));
+}
+
+/*!
+ This function searches for the node specified by \a path.
+ The matching node can be one of several different types
+ including a C++ class, a C++ namespace, or a C++ header
+ file.
+
+ I'm not sure if it can be a QML type, but if that is a
+ possibility, the code can easily accommodate it.
+
+ If a matching node is found, a pointer to it is returned.
+ Otherwise 0 is returned.
+ */
+Aggregate *Tree::findRelatesNode(const QStringList &path)
+{
+ Node *n = findNodeRecursive(path, 0, root(), &Node::isRelatableType);
+ return (((n != nullptr) && n->isAggregate()) ? static_cast<Aggregate *>(n) : nullptr);
+}
+
+/*!
+ Inserts function name \a funcName and function role \a funcRole into
+ the property function map for the specified \a property.
+ */
+void Tree::addPropertyFunction(PropertyNode *property, const QString &funcName,
+ PropertyNode::FunctionRole funcRole)
+{
+ m_unresolvedPropertyMap[property].insert(funcRole, funcName);
+}
+
+/*!
+ This function resolves C++ inheritance and reimplementation
+ settings for each C++ class node found in the tree beginning
+ at \a n. It also calls itself recursively for each C++ class
+ node or namespace node it encounters.
+
+ This function does not resolve QML inheritance.
+ */
+void Tree::resolveBaseClasses(Aggregate *n)
+{
+ for (auto it = n->constBegin(); it != n->constEnd(); ++it) {
+ if ((*it)->isClassNode()) {
+ auto *cn = static_cast<ClassNode *>(*it);
+ QList<RelatedClass> &bases = cn->baseClasses();
+ for (auto &base : bases) {
+ if (base.m_node == nullptr) {
+ Node *n = m_qdb->findClassNode(base.m_path);
+ /*
+ If the node for the base class was not found,
+ the reason might be that the subclass is in a
+ namespace and the base class is in the same
+ namespace, but the base class name was not
+ qualified with the namespace name. That is the
+ case most of the time. Then restart the search
+ at the parent of the subclass node (the namespace
+ node) using the unqualified base class name.
+ */
+ if (n == nullptr) {
+ Aggregate *parent = cn->parent();
+ if (parent != nullptr)
+ // Exclude the root namespace
+ if (parent->isNamespace() && !parent->name().isEmpty())
+ n = findClassNode(base.m_path, parent);
+ }
+ if (n != nullptr) {
+ auto *bcn = static_cast<ClassNode *>(n);
+ base.m_node = bcn;
+ bcn->addDerivedClass(base.m_access, cn);
+ }
+ }
+ }
+ resolveBaseClasses(cn);
+ } else if ((*it)->isNamespace()) {
+ resolveBaseClasses(static_cast<NamespaceNode *>(*it));
+ }
+ }
+}
+
+/*!
+ */
+void Tree::resolvePropertyOverriddenFromPtrs(Aggregate *n)
+{
+ for (auto node = n->constBegin(); node != n->constEnd(); ++node) {
+ if ((*node)->isClassNode()) {
+ auto *cn = static_cast<ClassNode *>(*node);
+ for (auto property = cn->constBegin(); property != cn->constEnd(); ++property) {
+ if ((*property)->isProperty())
+ cn->resolvePropertyOverriddenFromPtrs(static_cast<PropertyNode *>(*property));
+ }
+ resolvePropertyOverriddenFromPtrs(cn);
+ } else if ((*node)->isNamespace()) {
+ resolvePropertyOverriddenFromPtrs(static_cast<NamespaceNode *>(*node));
+ }
+ }
+}
+
+/*!
+ Resolves access functions associated with each PropertyNode stored
+ in \c m_unresolvedPropertyMap, and adds them into the property node.
+ This allows the property node to list the access functions when
+ generating their documentation.
+ */
+void Tree::resolveProperties()
+{
+ for (auto propEntry = m_unresolvedPropertyMap.constBegin();
+ propEntry != m_unresolvedPropertyMap.constEnd(); ++propEntry) {
+ PropertyNode *property = propEntry.key();
+ Aggregate *parent = property->parent();
+ QString getterName = (*propEntry)[PropertyNode::FunctionRole::Getter];
+ QString setterName = (*propEntry)[PropertyNode::FunctionRole::Setter];
+ QString resetterName = (*propEntry)[PropertyNode::FunctionRole::Resetter];
+ QString notifierName = (*propEntry)[PropertyNode::FunctionRole::Notifier];
+ QString bindableName = (*propEntry)[PropertyNode::FunctionRole::Bindable];
+
+ for (auto it = parent->constBegin(); it != parent->constEnd(); ++it) {
+ if ((*it)->isFunction()) {
+ auto *function = static_cast<FunctionNode *>(*it);
+ if (function->access() == property->access()
+ && (function->status() == property->status() || function->doc().isEmpty())) {
+ if (function->name() == getterName) {
+ property->addFunction(function, PropertyNode::FunctionRole::Getter);
+ } else if (function->name() == setterName) {
+ property->addFunction(function, PropertyNode::FunctionRole::Setter);
+ } else if (function->name() == resetterName) {
+ property->addFunction(function, PropertyNode::FunctionRole::Resetter);
+ } else if (function->name() == notifierName) {
+ property->addSignal(function, PropertyNode::FunctionRole::Notifier);
+ } else if (function->name() == bindableName) {
+ property->addFunction(function, PropertyNode::FunctionRole::Bindable);
+ }
+ }
+ }
+ }
+ }
+
+ for (auto propEntry = m_unresolvedPropertyMap.constBegin();
+ propEntry != m_unresolvedPropertyMap.constEnd(); ++propEntry) {
+ PropertyNode *property = propEntry.key();
+ // redo it to set the property functions
+ if (property->overriddenFrom())
+ property->setOverriddenFrom(property->overriddenFrom());
+ }
+
+ m_unresolvedPropertyMap.clear();
+}
+
+/*!
+ For each QML class node that points to a C++ class node,
+ follow its C++ class node pointer and set the C++ class
+ node's QML class node pointer back to the QML class node.
+ */
+void Tree::resolveCppToQmlLinks()
+{
+
+ const NodeList &children = m_root.childNodes();
+ for (auto *child : children) {
+ if (child->isQmlType()) {
+ auto *qcn = static_cast<QmlTypeNode *>(child);
+ auto *cn = const_cast<ClassNode *>(qcn->classNode());
+ if (cn)
+ cn->insertQmlNativeType(qcn);
+ }
+ }
+}
+
+/*!
+ For each \a aggregate, recursively set the \\since version based on
+ \\since information from the associated physical or logical module.
+ That is, C++ and QML types inherit the \\since of their module,
+ unless that command is explicitly used in the type documentation.
+
+ In addition, resolve the since information for individual enum
+ values.
+*/
+void Tree::resolveSince(Aggregate &aggregate)
+{
+ for (auto *child : aggregate.childNodes()) {
+ // Order matters; resolve since-clauses in enum values
+ // first as EnumNode is not an Aggregate
+ if (child->isEnumType())
+ resolveEnumValueSince(static_cast<EnumNode&>(*child));
+ if (!child->isAggregate())
+ continue;
+ if (!child->since().isEmpty())
+ continue;
+
+ if (const auto collectionNode = m_qdb->getModuleNode(child))
+ child->setSince(collectionNode->since());
+
+ resolveSince(static_cast<Aggregate&>(*child));
+ }
+}
+
+/*!
+ Resolve since information for values of enum node \a en.
+
+ Enum values are not derived from Node, but they can have
+ 'since' information associated with them. Since-strings
+ for each enum item are initially stored in the Doc
+ instance of EnumNode as SinceTag atoms; parse the doc
+ and store them into each EnumItem.
+*/
+void Tree::resolveEnumValueSince(EnumNode &en)
+{
+ const QStringList enumItems{en.doc().enumItemNames()};
+ const Atom *atom = en.doc().body().firstAtom();
+ while ((atom = atom->find(Atom::ListTagLeft))) {
+ if (atom = atom->next(); !atom)
+ break;
+ if (auto val = atom->string(); enumItems.contains(val)) {
+ if (atom = atom->next(); atom && atom->next(Atom::SinceTagLeft))
+ en.setSince(val, atom->next()->next()->string());
+ }
+ }
+}
+
+/*!
+ Traverse this Tree and for each ClassNode found, remove
+ from its list of base classes any that are marked private
+ or internal. When a class is removed from a base class
+ list, promote its public pase classes to be base classes
+ of the class where the base class was removed. This is
+ done for documentation purposes. The function is recursive
+ on namespace nodes.
+ */
+void Tree::removePrivateAndInternalBases(NamespaceNode *rootNode)
+{
+ if (rootNode == nullptr)
+ rootNode = root();
+
+ for (auto node = rootNode->constBegin(); node != rootNode->constEnd(); ++node) {
+ if ((*node)->isClassNode())
+ static_cast<ClassNode *>(*node)->removePrivateAndInternalBases();
+ else if ((*node)->isNamespace())
+ removePrivateAndInternalBases(static_cast<NamespaceNode *>(*node));
+ }
+}
+
+/*!
+ */
+ClassList Tree::allBaseClasses(const ClassNode *classNode) const
+{
+ ClassList result;
+ const auto &baseClasses = classNode->baseClasses();
+ for (const auto &relatedClass : baseClasses) {
+ if (relatedClass.m_node != nullptr) {
+ result += relatedClass.m_node;
+ result += allBaseClasses(relatedClass.m_node);
+ }
+ }
+ return result;
+}
+
+/*!
+ Find the node with the specified \a path name that is of
+ the specified \a type and \a subtype. Begin the search at
+ the \a start node. If the \a start node is 0, begin the
+ search at the tree root. \a subtype is not used unless
+ \a type is \c{Page}.
+ */
+Node *Tree::findNodeByNameAndType(const QStringList &path, bool (Node::*isMatch)() const) const
+{
+ return findNodeRecursive(path, 0, root(), isMatch);
+}
+
+/*!
+ Recursive search for a node identified by \a path. Each
+ path element is a name. \a pathIndex specifies the index
+ of the name in \a path to try to match. \a start is the
+ node whose children shoulod be searched for one that has
+ that name. Each time a match is found, increment the
+ \a pathIndex and call this function recursively.
+
+ If the end of the path is reached (i.e. if a matching
+ node is found for each name in the \a path), the \a type
+ must match the type of the last matching node, and if the
+ type is \e{Page}, the \a subtype must match as well.
+
+ If the algorithm is successful, the pointer to the final
+ node is returned. Otherwise 0 is returned.
+ */
+Node *Tree::findNodeRecursive(const QStringList &path, int pathIndex, const Node *start,
+ bool (Node::*isMatch)() const) const
+{
+ if (start == nullptr || path.isEmpty())
+ return nullptr;
+ Node *node = const_cast<Node *>(start);
+ if (!node->isAggregate())
+ return ((pathIndex >= path.size()) ? node : nullptr);
+ auto *current = static_cast<Aggregate *>(node);
+ const NodeList &children = current->childNodes();
+ const QString &name = path.at(pathIndex);
+ for (auto *node : children) {
+ if (node == nullptr)
+ continue;
+ if (node->name() == name) {
+ if (pathIndex + 1 >= path.size()) {
+ if ((node->*(isMatch))())
+ return node;
+ continue;
+ } else { // Search the children of n for the next name in the path.
+ node = findNodeRecursive(path, pathIndex + 1, node, isMatch);
+ if (node != nullptr)
+ return node;
+ }
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ Searches the tree for a node that matches the \a path plus
+ the \a target. The search begins at \a start and moves up
+ the parent chain from there, or, if \a start is 0, the search
+ begins at the root.
+
+ The \a flags can indicate whether to search base classes and/or
+ the enum values in enum types. \a genus further restricts
+ the type of nodes to match, i.e. CPP or QML.
+
+ If a matching node is found, \a ref is set to the HTML fragment
+ identifier to use for the link. On return, the optional
+ \a targetType parameter contains the type of the resolved
+ target; section title (Contents), \\target, \\keyword, or other
+ (Unknown).
+ */
+const Node *Tree::findNodeForTarget(const QStringList &path, const QString &target,
+ const Node *start, int flags, Node::Genus genus,
+ QString &ref, TargetRec::TargetType *targetType) const
+{
+ const Node *node = nullptr;
+
+ // Retrieves and sets ref from target for Node n.
+ // Returns n on valid (or empty) target, or nullptr on an invalid target.
+ auto set_ref_from_target = [this, &ref, &target](const Node *n) -> const Node* {
+ if (!target.isEmpty()) {
+ if (ref = getRef(target, n); ref.isEmpty())
+ return nullptr;
+ }
+ return n;
+ };
+
+ if (genus == Node::DontCare || genus == Node::DOC) {
+ if (node = findPageNodeByTitle(path.at(0)); node) {
+ if (node = set_ref_from_target(node); node)
+ return node;
+ }
+ }
+
+ const TargetRec *result = findUnambiguousTarget(path.join(QLatin1String("::")), genus);
+ if (result) {
+ ref = result->m_ref;
+ if (node = set_ref_from_target(result->m_node); node) {
+ // Delay returning references to section titles as we
+ // may find a better match below
+ if (result->m_type != TargetRec::Contents) {
+ if (targetType)
+ *targetType = result->m_type;
+ return node;
+ }
+ ref.clear();
+ }
+ }
+
+ const Node *current = start ? start : root();
+ /*
+ If the path contains one or two double colons ("::"),
+ check if the first two path elements refer to a QML type.
+ If so, path[0] is QML module identifier, and path[1] is
+ the type.
+ */
+ int path_idx = 0;
+ if ((genus == Node::QML || genus == Node::DontCare)
+ && path.size() >= 2 && !path[0].isEmpty()) {
+ if (auto *qcn = lookupQmlType(path.sliced(0, 2).join(QLatin1String("::"))); qcn) {
+ current = qcn;
+ // No further elements in the path, return the type
+ if (path.size() == 2)
+ return set_ref_from_target(qcn);
+ path_idx = 2;
+ }
+ }
+
+ while (current) {
+ if (current->isAggregate()) {
+ if (const Node *match = matchPathAndTarget(
+ path, path_idx, target, current, flags, genus, ref);
+ match != nullptr)
+ return match;
+ }
+ current = current->parent();
+ path_idx = 0;
+ }
+
+ if (node && result) {
+ // Fall back to previously found section title
+ ref = result->m_ref;
+ if (targetType)
+ *targetType = result->m_type;
+ }
+ return node;
+}
+
+/*!
+ First, the \a path is used to find a node. The \a path
+ matches some part of the node's fully quallified name.
+ If the \a target is not empty, it must match a target
+ in the matching node. If the matching of the \a path
+ and the \a target (if present) is successful, \a ref
+ is set from the \a target, and the pointer to the
+ matching node is returned. \a idx is the index into the
+ \a path where to begin the matching. The function is
+ recursive with idx being incremented for each recursive
+ call.
+
+ The matching node must be of the correct \a genus, i.e.
+ either QML or C++, but \a genus can be set to \c DontCare.
+ \a flags indicates whether to search base classes and
+ whether to search for an enum value. \a node points to
+ the node where the search should begin, assuming the
+ \a path is a not a fully-qualified name. \a node is
+ most often the root of this Tree.
+ */
+const Node *Tree::matchPathAndTarget(const QStringList &path, int idx, const QString &target,
+ const Node *node, int flags, Node::Genus genus,
+ QString &ref) const
+{
+ /*
+ If the path has been matched, then if there is a target,
+ try to match the target. If there is a target, but you
+ can't match it at the end of the path, give up; return 0.
+ */
+ if (idx == path.size()) {
+ if (!target.isEmpty()) {
+ ref = getRef(target, node);
+ if (ref.isEmpty())
+ return nullptr;
+ }
+ if (node->isFunction() && node->name() == node->parent()->name())
+ node = node->parent();
+ return node;
+ }
+
+ QString name = path.at(idx);
+ if (node->isAggregate()) {
+ NodeVector nodes;
+ static_cast<const Aggregate *>(node)->findChildren(name, nodes);
+ for (const auto *child : std::as_const(nodes)) {
+ if (genus != Node::DontCare && !(genus & child->genus()))
+ continue;
+ const Node *t = matchPathAndTarget(path, idx + 1, target, child, flags, genus, ref);
+ if (t && !t->isPrivate())
+ return t;
+ }
+ }
+ if (target.isEmpty() && (flags & SearchEnumValues)) {
+ const auto *enumNode = node->isAggregate() ?
+ findEnumNode(nullptr, node, path, idx) :
+ findEnumNode(node, nullptr, path, idx);
+ if (enumNode)
+ return enumNode;
+ }
+ if (((genus == Node::CPP) || (genus == Node::DontCare)) && node->isClassNode()
+ && (flags & SearchBaseClasses)) {
+ const ClassList bases = allBaseClasses(static_cast<const ClassNode *>(node));
+ for (const auto *base : bases) {
+ const Node *t = matchPathAndTarget(path, idx, target, base, flags, genus, ref);
+ if (t && !t->isPrivate())
+ return t;
+ if (target.isEmpty() && (flags & SearchEnumValues)) {
+ if ((t = findEnumNode(base->findChildNode(path.at(idx), genus, flags), base, path, idx)))
+ return t;
+ }
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ Searches the tree for a node that matches the \a path. The
+ search begins at \a start but can move up the parent chain
+ recursively if no match is found. The \a flags are used to
+ restrict the search.
+ */
+const Node *Tree::findNode(const QStringList &path, const Node *start, int flags,
+ Node::Genus genus) const
+{
+ const Node *current = start;
+ if (current == nullptr)
+ current = root();
+
+ do {
+ const Node *node = current;
+ int i;
+ int start_idx = 0;
+
+ /*
+ If the path contains one or two double colons ("::"),
+ check first to see if the first two path strings refer
+ to a QML element. If they do, path[0] will be the QML
+ module identifier, and path[1] will be the QML type.
+ If the answer is yes, the reference identifies a QML
+ type node.
+ */
+ if (((genus == Node::QML) || (genus == Node::DontCare)) && (path.size() >= 2)
+ && !path[0].isEmpty()) {
+ QmlTypeNode *qcn = lookupQmlType(QString(path[0] + "::" + path[1]));
+ if (qcn != nullptr) {
+ node = qcn;
+ if (path.size() == 2)
+ return node;
+ start_idx = 2;
+ }
+ }
+
+ for (i = start_idx; i < path.size(); ++i) {
+ if (node == nullptr || !node->isAggregate())
+ break;
+
+ // Clear the TypesOnly flag until the last path segment, as e.g. namespaces are not
+ // types. We also ignore module nodes as they are not aggregates and thus have no
+ // children.
+ int tmpFlags = (i < path.size() - 1) ? (flags & ~TypesOnly) | IgnoreModules : flags;
+
+ const Node *next = static_cast<const Aggregate *>(node)->findChildNode(path.at(i),
+ genus, tmpFlags);
+ const Node *enumNode = (flags & SearchEnumValues) ?
+ findEnumNode(next, node, path, i) : nullptr;
+
+ if (enumNode)
+ return enumNode;
+
+
+ if (!next && ((genus == Node::CPP) || (genus == Node::DontCare))
+ && node->isClassNode() && (flags & SearchBaseClasses)) {
+ const ClassList bases = allBaseClasses(static_cast<const ClassNode *>(node));
+ for (const auto *base : bases) {
+ next = base->findChildNode(path.at(i), genus, tmpFlags);
+ if (flags & SearchEnumValues)
+ if ((enumNode = findEnumNode(next, base, path, i)))
+ return enumNode;
+ if (next)
+ break;
+ }
+ }
+ node = next;
+ }
+ if ((node != nullptr) && i == path.size())
+ return node;
+ current = current->parent();
+ } while (current != nullptr);
+
+ return nullptr;
+}
+
+
+/*!
+ \internal
+
+ Helper function to return an enum that matches the \a path at a specified \a offset.
+ If \a node is a valid enum node, the enum name is assumed to be included in the path
+ (i.e, a scoped enum). Otherwise, query the \a aggregate (typically, the class node)
+ for enum node that includes the value at the last position in \a path.
+ */
+const Node *Tree::findEnumNode(const Node *node, const Node *aggregate, const QStringList &path, int offset) const
+{
+ // Scoped enum (path ends in enum_name :: enum_value)
+ if (node && node->isEnumType() && offset == path.size() - 1) {
+ const auto *en = static_cast<const EnumNode*>(node);
+ if (en->isScoped() && en->hasItem(path.last()))
+ return en;
+ }
+
+ // Standard enum (path ends in class_name :: enum_value)
+ return (!node && aggregate && offset == path.size() - 1) ?
+ static_cast<const Aggregate *>(aggregate)->findEnumNodeForValue(path.last()) :
+ nullptr;
+}
+
+/*!
+ This function searches for a node with a canonical title
+ constructed from \a target. If the node it finds is \a node,
+ it returns the ref from that node. Otherwise it returns an
+ empty string.
+ */
+QString Tree::getRef(const QString &target, const Node *node) const
+{
+ auto it = m_nodesByTargetTitle.constFind(target);
+ if (it != m_nodesByTargetTitle.constEnd()) {
+ do {
+ if (it.value()->m_node == node)
+ return it.value()->m_ref;
+ ++it;
+ } while (it != m_nodesByTargetTitle.constEnd() && it.key() == target);
+ }
+ QString key = Utilities::asAsciiPrintable(target);
+ it = m_nodesByTargetRef.constFind(key);
+ if (it != m_nodesByTargetRef.constEnd()) {
+ do {
+ if (it.value()->m_node == node)
+ return it.value()->m_ref;
+ ++it;
+ } while (it != m_nodesByTargetRef.constEnd() && it.key() == key);
+ }
+ return QString();
+}
+
+/*!
+ Inserts a new target into the target table. \a name is the
+ key. The target record contains the \a type, a pointer to
+ the \a node, the \a priority. and a canonicalized form of
+ the \a name, which is later used.
+ */
+void Tree::insertTarget(const QString &name, const QString &title, TargetRec::TargetType type,
+ Node *node, int priority)
+{
+ auto *target = new TargetRec(name, type, node, priority);
+ m_nodesByTargetRef.insert(name, target);
+ m_nodesByTargetTitle.insert(title, target);
+}
+
+/*!
+ \internal
+
+ \a root is the root node of the tree to resolve targets for. This function
+ traverses the tree starting from the root node and processes each child
+ node. If the child node is an aggregate node, this function is called
+ recursively on the child node.
+ */
+void Tree::resolveTargets(Aggregate *root)
+{
+ for (auto *child : root->childNodes()) {
+ addToPageNodeByTitleMap(child);
+ populateTocSectionTargetMap(child);
+ addKeywordsToTargetMaps(child);
+ addTargetsToTargetMap(child);
+
+ if (child->isAggregate())
+ resolveTargets(static_cast<Aggregate *>(child));
+ }
+}
+
+/*!
+ \internal
+
+ Updates the target maps for targets associated with the given \a node.
+ */
+void Tree::addTargetsToTargetMap(Node *node) {
+ if (!node || !node->doc().hasTargets())
+ return;
+
+ for (Atom *i : std::as_const(node->doc().targets())) {
+ const QString ref = refForAtom(i);
+ const QString title = i->string();
+ if (!ref.isEmpty() && !title.isEmpty()) {
+ QString key = Utilities::asAsciiPrintable(title);
+ auto *target = new TargetRec(ref, TargetRec::Target, node, 2);
+ m_nodesByTargetRef.insert(key, target);
+ m_nodesByTargetTitle.insert(title, target);
+ }
+ }
+}
+
+/*!
+ \internal
+
+ Updates the target maps for keywords associated with the given \a node.
+ */
+void Tree::addKeywordsToTargetMaps(Node *node) {
+ if (!node->doc().hasKeywords())
+ return;
+
+ for (Atom *i : std::as_const(node->doc().keywords())) {
+ QString ref = refForAtom(i);
+ QString title = i->string();
+ if (!ref.isEmpty() && !title.isEmpty()) {
+ auto *target = new TargetRec(ref, TargetRec::Keyword, node, 1);
+ m_nodesByTargetRef.insert(Utilities::asAsciiPrintable(title), target);
+ m_nodesByTargetTitle.insert(title, target);
+ }
+ }
+}
+
+/*!
+ \internal
+
+ Populates the map of targets for each section in the table of contents for
+ the given \a node while ensuring that each target has a unique reference.
+ */
+void Tree::populateTocSectionTargetMap(Node *node) {
+ if (!node || !node->doc().hasTableOfContents())
+ return;
+
+ QStack<Atom *> tocLevels;
+ QSet<QString> anchors;
+
+ qsizetype index = 0;
+
+ for (Atom *atom: std::as_const(node->doc().tableOfContents())) {
+ while (!tocLevels.isEmpty() && tocLevels.top()->string().toInt() >= atom->string().toInt())
+ tocLevels.pop();
+
+ tocLevels.push(atom);
+
+ QString ref = refForAtom(atom);
+ const QString &title = Text::sectionHeading(atom).toString();
+ if (ref.isEmpty() || title.isEmpty())
+ continue;
+
+ if (anchors.contains(ref)) {
+ QStringList refParts;
+ for (const auto tocLevel : tocLevels)
+ refParts << refForAtom(tocLevel);
+
+ refParts << QString::number(index);
+ ref = refParts.join(QLatin1Char('-'));
+ }
+
+ anchors.insert(ref);
+ if (atom->next(Atom::SectionHeadingLeft))
+ atom->next()->append(ref);
+ ++index;
+
+ const QString &key = Utilities::asAsciiPrintable(title);
+ auto *target = new TargetRec(ref, TargetRec::Contents, node, 3);
+ m_nodesByTargetRef.insert(key, target);
+ m_nodesByTargetTitle.insert(title, target);
+ }
+}
+
+/*!
+ \internal
+
+ Checks if the \a node's title is registered in the page nodes by title map.
+ If not, it stores the page node in the map.
+ */
+void Tree::addToPageNodeByTitleMap(Node *node) {
+ if (!node || !node->isTextPageNode())
+ return;
+
+ auto *pageNode = static_cast<PageNode *>(node);
+ QString key = pageNode->title();
+ if (key.isEmpty())
+ return;
+
+ if (key.contains(QChar(' ')))
+ key = Utilities::asAsciiPrintable(key);
+ const QList<PageNode *> nodes = m_pageNodesByTitle.values(key);
+
+ bool alreadyThere = std::any_of(nodes.cbegin(), nodes.cend(), [&](const auto &knownNode) {
+ return knownNode->isExternalPage() && knownNode->name() == pageNode->name();
+ });
+
+ if (!alreadyThere)
+ m_pageNodesByTitle.insert(key, pageNode);
+}
+
+/*!
+ Searches for a \a target anchor, matching the given \a genus, and returns
+ the associated TargetRec instance.
+ */
+const TargetRec *Tree::findUnambiguousTarget(const QString &target, Node::Genus genus) const
+{
+ auto findBestCandidate = [&](const TargetMap &tgtMap, const QString &key) {
+ TargetRec *best = nullptr;
+ auto [it, end] = tgtMap.equal_range(key);
+ while (it != end) {
+ TargetRec *candidate = it.value();
+ if ((genus == Node::DontCare) || (genus & candidate->genus())) {
+ if (!best || (candidate->m_priority < best->m_priority))
+ best = candidate;
+ }
+ ++it;
+ }
+ return best;
+ };
+
+ TargetRec *bestTarget = findBestCandidate(m_nodesByTargetTitle, target);
+ if (!bestTarget)
+ bestTarget = findBestCandidate(m_nodesByTargetRef, Utilities::asAsciiPrintable(target));
+
+ return bestTarget;
+}
+
+/*!
+ This function searches for a node with the specified \a title.
+ */
+const PageNode *Tree::findPageNodeByTitle(const QString &title) const
+{
+ PageNodeMultiMap::const_iterator it;
+ if (title.contains(QChar(' ')))
+ it = m_pageNodesByTitle.constFind(Utilities::asAsciiPrintable(title));
+ else
+ it = m_pageNodesByTitle.constFind(title);
+ if (it != m_pageNodesByTitle.constEnd()) {
+ /*
+ Reporting all these duplicate section titles is probably
+ overkill. We should report the duplicate file and let
+ that suffice.
+ */
+ PageNodeMultiMap::const_iterator j = it;
+ ++j;
+ if (j != m_pageNodesByTitle.constEnd() && j.key() == it.key()) {
+ while (j != m_pageNodesByTitle.constEnd()) {
+ if (j.key() == it.key() && j.value()->url().isEmpty()) {
+ break; // Just report one duplicate for now.
+ }
+ ++j;
+ }
+ if (j != m_pageNodesByTitle.cend()) {
+ it.value()->location().warning("This page title exists in more than one file: "
+ + title);
+ j.value()->location().warning("[It also exists here]");
+ }
+ }
+ return it.value();
+ }
+ return nullptr;
+}
+
+/*!
+ Returns a canonical title for the \a atom, if the \a atom
+ is a SectionLeft, SectionHeadingLeft, Keyword, or Target.
+ */
+QString Tree::refForAtom(const Atom *atom)
+{
+ Q_ASSERT(atom);
+
+ switch (atom->type()) {
+ case Atom::SectionLeft:
+ atom = atom->next();
+ [[fallthrough]];
+ case Atom::SectionHeadingLeft:
+ if (atom->count() == 2)
+ return atom->string(1);
+ return Utilities::asAsciiPrintable(Text::sectionHeading(atom).toString());
+ case Atom::Target:
+ [[fallthrough]];
+ case Atom::Keyword:
+ return Utilities::asAsciiPrintable(atom->string());
+ default:
+ return {};
+ }
+}
+
+/*!
+ \fn const CNMap &Tree::groups() const
+ Returns a const reference to the collection of all
+ group nodes.
+*/
+
+/*!
+ \fn const ModuleMap &Tree::modules() const
+ Returns a const reference to the collection of all
+ module nodes.
+*/
+
+/*!
+ \fn const QmlModuleMap &Tree::qmlModules() const
+ Returns a const reference to the collection of all
+ QML module nodes.
+*/
+
+/*!
+ Returns a pointer to the collection map specified by \a type.
+ Returns null if \a type is not specified.
+ */
+CNMap *Tree::getCollectionMap(Node::NodeType type)
+{
+ switch (type) {
+ case Node::Group:
+ return &m_groups;
+ case Node::Module:
+ return &m_modules;
+ case Node::QmlModule:
+ return &m_qmlModules;
+ default:
+ break;
+ }
+ return nullptr;
+}
+
+/*!
+ Searches this tree for a collection named \a name with the
+ specified \a type. If the collection is found, a pointer
+ to it is returned. If a collection is not found, null is
+ returned.
+ */
+CollectionNode *Tree::getCollection(const QString &name, Node::NodeType type)
+{
+ CNMap *map = getCollectionMap(type);
+ if (map) {
+ auto it = map->constFind(name);
+ if (it != map->cend())
+ return it.value();
+ }
+ return nullptr;
+}
+
+/*!
+ Find the group, module, or QML module named \a name and return a
+ pointer to that collection node. \a type specifies which kind of
+ collection node you want. If a collection node with the specified \a
+ name and \a type is not found, a new one is created, and the pointer
+ to the new one is returned.
+
+ If a new collection node is created, its parent is the tree
+ root, and the new collection node is marked \e{not seen}.
+
+ \a genus must be specified, i.e. it must not be \c{DontCare}.
+ If it is \c{DontCare}, 0 is returned, which is a programming
+ error.
+ */
+CollectionNode *Tree::findCollection(const QString &name, Node::NodeType type)
+{
+ CNMap *m = getCollectionMap(type);
+ if (!m) // error
+ return nullptr;
+ auto it = m->constFind(name);
+ if (it != m->cend())
+ return it.value();
+ CollectionNode *cn = new CollectionNode(type, root(), name);
+ cn->markNotSeen();
+ m->insert(name, cn);
+ return cn;
+}
+
+/*! \fn CollectionNode *Tree::findGroup(const QString &name)
+ Find the group node named \a name and return a pointer
+ to it. If the group node is not found, add a new group
+ node named \a name and return a pointer to the new one.
+
+ If a new group node is added, its parent is the tree root,
+ and the new group node is marked \e{not seen}.
+ */
+
+/*! \fn CollectionNode *Tree::findModule(const QString &name)
+ Find the module node named \a name and return a pointer
+ to it. If a matching node is not found, add a new module
+ node named \a name and return a pointer to that one.
+
+ If a new module node is added, its parent is the tree root,
+ and the new module node is marked \e{not seen}.
+ */
+
+/*! \fn CollectionNode *Tree::findQmlModule(const QString &name)
+ Find the QML module node named \a name and return a pointer
+ to it. If a matching node is not found, add a new QML module
+ node named \a name and return a pointer to that one.
+
+ If a new QML module node is added, its parent is the tree root,
+ and the new node is marked \e{not seen}.
+ */
+
+/*! \fn CollectionNode *Tree::addGroup(const QString &name)
+ Looks up the group node named \a name in the collection
+ of all group nodes. If a match is found, a pointer to the
+ node is returned. Otherwise, a new group node named \a name
+ is created and inserted into the collection, and the pointer
+ to that node is returned.
+ */
+
+/*! \fn CollectionNode *Tree::addModule(const QString &name)
+ Looks up the module node named \a name in the collection
+ of all module nodes. If a match is found, a pointer to the
+ node is returned. Otherwise, a new module node named \a name
+ is created and inserted into the collection, and the pointer
+ to that node is returned.
+ */
+
+/*! \fn CollectionNode *Tree::addQmlModule(const QString &name)
+ Looks up the QML module node named \a name in the collection
+ of all QML module nodes. If a match is found, a pointer to the
+ node is returned. Otherwise, a new QML module node named \a name
+ is created and inserted into the collection, and the pointer
+ to that node is returned.
+ */
+
+/*!
+ Looks up the group node named \a name in the collection
+ of all group nodes. If a match is not found, a new group
+ node named \a name is created and inserted into the collection.
+ Then append \a node to the group's members list, and append the
+ group name to the list of group names in \a node. The parent of
+ \a node is not changed by this function. Returns a pointer to
+ the group node.
+ */
+CollectionNode *Tree::addToGroup(const QString &name, Node *node)
+{
+ CollectionNode *cn = findGroup(name);
+ if (!node->isInternal()) {
+ cn->addMember(node);
+ node->appendGroupName(name);
+ }
+ return cn;
+}
+
+/*!
+ Looks up the module node named \a name in the collection
+ of all module nodes. If a match is not found, a new module
+ node named \a name is created and inserted into the collection.
+ Then append \a node to the module's members list. The parent of
+ \a node is not changed by this function. Returns the module node.
+ */
+CollectionNode *Tree::addToModule(const QString &name, Node *node)
+{
+ CollectionNode *cn = findModule(name);
+ cn->addMember(node);
+ node->setPhysicalModuleName(name);
+ return cn;
+}
+
+/*!
+ Looks up the QML module named \a name. If it isn't there,
+ create it. Then append \a node to the QML module's member
+ list. The parent of \a node is not changed by this function.
+ Returns the pointer to the QML module node.
+ */
+CollectionNode *Tree::addToQmlModule(const QString &name, Node *node)
+{
+ QStringList qmid;
+ QStringList dotSplit;
+ QStringList blankSplit = name.split(QLatin1Char(' '));
+ qmid.append(blankSplit[0]);
+ if (blankSplit.size() > 1) {
+ qmid.append(blankSplit[0] + blankSplit[1]);
+ dotSplit = blankSplit[1].split(QLatin1Char('.'));
+ qmid.append(blankSplit[0] + dotSplit[0]);
+ }
+
+ CollectionNode *cn = findQmlModule(blankSplit[0]);
+ cn->addMember(node);
+ node->setQmlModule(cn);
+ if (node->isQmlType()) {
+ QmlTypeNode *n = static_cast<QmlTypeNode *>(node);
+ for (int i = 0; i < qmid.size(); ++i) {
+ QString key = qmid[i] + "::" + node->name();
+ insertQmlType(key, n);
+ }
+ }
+ return cn;
+}
+
+/*!
+ If the QML type map does not contain \a key, insert node
+ \a n with the specified \a key.
+ */
+void Tree::insertQmlType(const QString &key, QmlTypeNode *n)
+{
+ if (!m_qmlTypeMap.contains(key))
+ m_qmlTypeMap.insert(key, n);
+}
+
+/*!
+ Finds the function node with the specifried name \a path that
+ also has the specified \a parameters and returns a pointer to
+ the first matching function node if one is found.
+
+ This function begins searching the tree at \a relative for
+ the \l {FunctionNode} {function node} identified by \a path
+ that has the specified \a parameters. The \a flags are
+ used to restrict the search. If a matching node is found, a
+ pointer to it is returned. Otherwise, nullis returned. If
+ \a relative is ull, the search begins at the tree root.
+ */
+const FunctionNode *Tree::findFunctionNode(const QStringList &path, const Parameters &parameters,
+ const Node *relative, Node::Genus genus) const
+{
+ if (path.size() == 3 && !path[0].isEmpty()
+ && ((genus == Node::QML) || (genus == Node::DontCare))) {
+ QmlTypeNode *qcn = lookupQmlType(QString(path[0] + "::" + path[1]));
+ if (qcn == nullptr) {
+ QStringList p(path[1]);
+ Node *n = findNodeByNameAndType(p, &Node::isQmlType);
+ if ((n != nullptr) && n->isQmlType())
+ qcn = static_cast<QmlTypeNode *>(n);
+ }
+ if (qcn != nullptr)
+ return static_cast<const FunctionNode *>(qcn->findFunctionChild(path[2], parameters));
+ }
+
+ if (relative == nullptr)
+ relative = root();
+ else if (genus != Node::DontCare) {
+ if (!(genus & relative->genus()))
+ relative = root();
+ }
+
+ do {
+ Node *node = const_cast<Node *>(relative);
+ int i;
+
+ for (i = 0; i < path.size(); ++i) {
+ if (node == nullptr || !node->isAggregate())
+ break;
+
+ Aggregate *aggregate = static_cast<Aggregate *>(node);
+ Node *next = nullptr;
+ if (i == path.size() - 1)
+ next = aggregate->findFunctionChild(path.at(i), parameters);
+ else
+ next = aggregate->findChildNode(path.at(i), genus);
+
+ if ((next == nullptr) && aggregate->isClassNode()) {
+ const ClassList bases = allBaseClasses(static_cast<const ClassNode *>(aggregate));
+ for (auto *base : bases) {
+ if (i == path.size() - 1)
+ next = base->findFunctionChild(path.at(i), parameters);
+ else
+ next = base->findChildNode(path.at(i), genus);
+
+ if (next != nullptr)
+ break;
+ }
+ }
+
+ node = next;
+ } // for (i = 0; i < path.size(); ++i)
+
+ if (node && i == path.size() && node->isFunction()) {
+ // A function node was found at the end of the path.
+ // If it is not marked private, return it. If it is
+ // marked private, then if it overrides a function,
+ // find that function instead because it might not
+ // be marked private. If all the overloads are
+ // marked private, return the original function node.
+ // This should be replace with findOverriddenFunctionNode().
+ const FunctionNode *fn = static_cast<const FunctionNode *>(node);
+ const FunctionNode *FN = fn;
+ while (FN->isPrivate() && !FN->overridesThis().isEmpty()) {
+ QStringList path = FN->overridesThis().split("::");
+ FN = m_qdb->findFunctionNode(path, parameters, relative, genus);
+ if (FN == nullptr)
+ break;
+ if (!FN->isPrivate())
+ return FN;
+ }
+ return fn;
+ }
+ relative = relative->parent();
+ } while (relative);
+ return nullptr;
+}
+
+/*!
+ Search this tree recursively from \a parent to find a function
+ node with the specified \a tag. If no function node is found
+ with the required \a tag, return 0.
+ */
+FunctionNode *Tree::findFunctionNodeForTag(const QString &tag, Aggregate *parent)
+{
+ if (parent == nullptr)
+ parent = root();
+ const NodeList &children = parent->childNodes();
+ for (Node *n : children) {
+ if (n != nullptr && n->isFunction() && n->hasTag(tag))
+ return static_cast<FunctionNode *>(n);
+ }
+ for (Node *n : children) {
+ if (n != nullptr && n->isAggregate()) {
+ n = findFunctionNodeForTag(tag, static_cast<Aggregate *>(n));
+ if (n != nullptr)
+ return static_cast<FunctionNode *>(n);
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ There should only be one macro node for macro name \a t.
+ The macro node is not built until the \macro command is seen.
+ */
+FunctionNode *Tree::findMacroNode(const QString &t, const Aggregate *parent)
+{
+ if (parent == nullptr)
+ parent = root();
+ const NodeList &children = parent->childNodes();
+ for (Node *n : children) {
+ if (n != nullptr && (n->isMacro() || n->isFunction()) && n->name() == t)
+ return static_cast<FunctionNode *>(n);
+ }
+ for (Node *n : children) {
+ if (n != nullptr && n->isAggregate()) {
+ FunctionNode *fn = findMacroNode(t, static_cast<Aggregate *>(n));
+ if (fn != nullptr)
+ return fn;
+ }
+ }
+ return nullptr;
+}
+
+/*!
+ Add the class and struct names in \a arg to the \e {don't document}
+ map.
+ */
+void Tree::addToDontDocumentMap(QString &arg)
+{
+ arg.remove(QChar('('));
+ arg.remove(QChar(')'));
+ QString t = arg.simplified();
+ QStringList sl = t.split(QChar(' '));
+ if (sl.isEmpty())
+ return;
+ for (const QString &s : sl) {
+ if (!m_dontDocumentMap.contains(s))
+ m_dontDocumentMap.insert(s, nullptr);
+ }
+}
+
+/*!
+ The \e {don't document} map has been loaded with the names
+ of classes and structs in the current module that are not
+ documented and should not be documented. Now traverse the
+ map, and for each class or struct name, find the class node
+ that represents that class or struct and mark it with the
+ \C DontDocument status.
+
+ This results in a map of the class and struct nodes in the
+ module that are in the public API but are not meant to be
+ used by anyone. They are only used internally, but for one
+ reason or another, they must have public visibility.
+ */
+void Tree::markDontDocumentNodes()
+{
+ for (auto it = m_dontDocumentMap.begin(); it != m_dontDocumentMap.end(); ++it) {
+ Aggregate *node = findAggregate(it.key());
+ if (node != nullptr)
+ node->setStatus(Node::DontDocument);
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/tree.h b/src/qdoc/qdoc/src/qdoc/tree.h
new file mode 100644
index 000000000..d0bed25d6
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/tree.h
@@ -0,0 +1,183 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef TREE_H
+#define TREE_H
+
+#include "examplenode.h"
+#include "namespacenode.h"
+#include "node.h"
+#include "propertynode.h"
+#include "proxynode.h"
+#include "qmltypenode.h"
+
+#include <QtCore/qstack.h>
+
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+class CollectionNode;
+class FunctionNode;
+class QDocDatabase;
+
+struct TargetRec
+{
+public:
+ enum TargetType { Unknown, Target, Keyword, Contents };
+
+ TargetRec(QString name, TargetRec::TargetType type, Node *node, int priority)
+ : m_node(node), m_ref(std::move(name)), m_type(type), m_priority(priority)
+ {
+ // Discard the dedicated ref for keywords - they always
+ // link to the top of the QDoc comment they appear in
+ if (type == Keyword)
+ m_ref.clear();
+ }
+
+ [[nodiscard]] bool isEmpty() const { return m_ref.isEmpty(); }
+ [[nodiscard]] Node::Genus genus() const { return (m_node ? m_node->genus() : Node::DontCare); }
+
+ Node *m_node { nullptr };
+ QString m_ref {};
+ TargetType m_type {};
+ int m_priority {};
+};
+
+typedef QMultiMap<QString, TargetRec *> TargetMap;
+typedef QMultiMap<QString, PageNode *> PageNodeMultiMap;
+typedef QMap<QString, QmlTypeNode *> QmlTypeMap;
+typedef QMultiMap<QString, const ExampleNode *> ExampleNodeMap;
+
+class Tree
+{
+ friend class QDocForest;
+ friend class QDocDatabase;
+
+private: // Note the constructor and destructor are private.
+ typedef QMap<PropertyNode::FunctionRole, QString> RoleMap;
+ typedef QMap<PropertyNode *, RoleMap> PropertyMap;
+
+ Tree(const QString &camelCaseModuleName, QDocDatabase *qdb);
+ ~Tree();
+
+public: // Of necessity, a few public functions remain.
+ [[nodiscard]] Node *findNodeByNameAndType(const QStringList &path,
+ bool (Node::*isMatch)() const) const;
+
+ [[nodiscard]] const QString &camelCaseModuleName() const { return m_camelCaseModuleName; }
+ [[nodiscard]] const QString &physicalModuleName() const { return m_physicalModuleName; }
+ [[nodiscard]] const QString &indexFileName() const { return m_indexFileName; }
+ [[nodiscard]] const QString &indexTitle() const { return m_indexTitle; }
+ void setIndexTitle(const QString &t) { m_indexTitle = t; }
+ NodeList &proxies() { return m_proxies; }
+ void appendProxy(ProxyNode *t) { m_proxies.append(t); }
+ void addToDontDocumentMap(QString &arg);
+ void markDontDocumentNodes();
+ static QString refForAtom(const Atom *atom);
+
+private: // The rest of the class is private.
+ Aggregate *findAggregate(const QString &name);
+ [[nodiscard]] Node *findNodeForInclude(const QStringList &path) const;
+ ClassNode *findClassNode(const QStringList &path, const Node *start = nullptr) const;
+ [[nodiscard]] NamespaceNode *findNamespaceNode(const QStringList &path) const;
+ const FunctionNode *findFunctionNode(const QStringList &path, const Parameters &parameters,
+ const Node *relative, Node::Genus genus) const;
+ Node *findNodeRecursive(const QStringList &path, int pathIndex, const Node *start,
+ bool (Node::*)() const) const;
+ const Node *findNodeForTarget(const QStringList &path, const QString &target, const Node *node,
+ int flags, Node::Genus genus, QString &ref,
+ TargetRec::TargetType *targetType = nullptr) const;
+ const Node *matchPathAndTarget(const QStringList &path, int idx, const QString &target,
+ const Node *node, int flags, Node::Genus genus,
+ QString &ref) const;
+
+ const Node *findNode(const QStringList &path, const Node *relative, int flags,
+ Node::Genus genus) const;
+
+ Aggregate *findRelatesNode(const QStringList &path);
+ const Node *findEnumNode(const Node *node, const Node *aggregate, const QStringList &path, int offset) const;
+ QString getRef(const QString &target, const Node *node) const;
+ void insertTarget(const QString &name, const QString &title, TargetRec::TargetType type,
+ Node *node, int priority);
+ void resolveTargets(Aggregate *root);
+ void addToPageNodeByTitleMap(Node *node);
+ void populateTocSectionTargetMap(Node *node);
+ void addKeywordsToTargetMaps(Node *node);
+ void addTargetsToTargetMap(Node *node);
+
+ const TargetRec *findUnambiguousTarget(const QString &target, Node::Genus genus) const;
+ [[nodiscard]] const PageNode *findPageNodeByTitle(const QString &title) const;
+
+ void addPropertyFunction(PropertyNode *property, const QString &funcName,
+ PropertyNode::FunctionRole funcRole);
+ void resolveBaseClasses(Aggregate *n);
+ void resolvePropertyOverriddenFromPtrs(Aggregate *n);
+ void resolveProperties();
+ void resolveCppToQmlLinks();
+ void resolveSince(Aggregate &aggregate);
+ void resolveEnumValueSince(EnumNode &en);
+ void removePrivateAndInternalBases(NamespaceNode *rootNode);
+ NamespaceNode *root() { return &m_root; }
+ [[nodiscard]] const NamespaceNode *root() const { return &m_root; }
+
+ ClassList allBaseClasses(const ClassNode *classe) const;
+
+ CNMap *getCollectionMap(Node::NodeType type);
+ [[nodiscard]] const CNMap &groups() const { return m_groups; }
+ [[nodiscard]] const CNMap &modules() const { return m_modules; }
+ [[nodiscard]] const CNMap &qmlModules() const { return m_qmlModules; }
+
+ CollectionNode *getCollection(const QString &name, Node::NodeType type);
+ CollectionNode *findCollection(const QString &name, Node::NodeType type);
+
+ CollectionNode *findGroup(const QString &name) { return findCollection(name, Node::Group); }
+ CollectionNode *findModule(const QString &name) { return findCollection(name, Node::Module); }
+ CollectionNode *findQmlModule(const QString &name)
+ {
+ return findCollection(name, Node::QmlModule);
+ }
+
+ CollectionNode *addGroup(const QString &name) { return findGroup(name); }
+ CollectionNode *addModule(const QString &name) { return findModule(name); }
+ CollectionNode *addQmlModule(const QString &name) { return findQmlModule(name); }
+
+ CollectionNode *addToGroup(const QString &name, Node *node);
+ CollectionNode *addToModule(const QString &name, Node *node);
+ CollectionNode *addToQmlModule(const QString &name, Node *node);
+
+ [[nodiscard]] QmlTypeNode *lookupQmlType(const QString &name) const
+ {
+ return m_qmlTypeMap.value(name);
+ }
+ void insertQmlType(const QString &key, QmlTypeNode *n);
+ void addExampleNode(ExampleNode *n) { m_exampleNodeMap.insert(n->title(), n); }
+ ExampleNodeMap &exampleNodeMap() { return m_exampleNodeMap; }
+ void setIndexFileName(const QString &t) { m_indexFileName = t; }
+
+ FunctionNode *findFunctionNodeForTag(const QString &tag, Aggregate *parent = nullptr);
+ FunctionNode *findMacroNode(const QString &t, const Aggregate *parent = nullptr);
+
+private:
+ QString m_camelCaseModuleName {};
+ QString m_physicalModuleName {};
+ QString m_indexFileName {};
+ QString m_indexTitle {};
+ QDocDatabase *m_qdb { nullptr };
+ NamespaceNode m_root;
+ PropertyMap m_unresolvedPropertyMap {};
+ PageNodeMultiMap m_pageNodesByTitle {};
+ TargetMap m_nodesByTargetRef {};
+ TargetMap m_nodesByTargetTitle {};
+ CNMap m_groups {};
+ CNMap m_modules {};
+ CNMap m_qmlModules {};
+ QmlTypeMap m_qmlTypeMap {};
+ ExampleNodeMap m_exampleNodeMap {};
+ NodeList m_proxies {};
+ NodeMap m_dontDocumentMap {};
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/typedefnode.cpp b/src/qdoc/qdoc/src/qdoc/typedefnode.cpp
new file mode 100644
index 000000000..997e570d3
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/typedefnode.cpp
@@ -0,0 +1,55 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "typedefnode.h"
+
+#include "aggregate.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class TypedefNode
+ */
+
+/*!
+ */
+void TypedefNode::setAssociatedEnum(const EnumNode *enume)
+{
+ m_associatedEnum = enume;
+}
+
+/*!
+ Clone this node on the heap and make the clone a child of
+ \a parent.
+
+ Returns the pointer to the clone.
+ */
+Node *TypedefNode::clone(Aggregate *parent)
+{
+ auto *tn = new TypedefNode(*this); // shallow copy
+ tn->setParent(nullptr);
+ parent->addChild(tn);
+
+ return tn;
+}
+
+/*!
+ \class TypeAliasNode
+ */
+
+/*!
+ Clone this node on the heap and make the clone a child of
+ \a parent.
+
+ Returns the pointer to the clone.
+ */
+Node *TypeAliasNode::clone(Aggregate *parent)
+{
+ auto *tan = new TypeAliasNode(*this); // shallow copy
+ tan->setParent(nullptr);
+ parent->addChild(tan);
+
+ return tan;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/typedefnode.h b/src/qdoc/qdoc/src/qdoc/typedefnode.h
new file mode 100644
index 000000000..839cd6a0b
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/typedefnode.h
@@ -0,0 +1,54 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef TYPEDEFNODE_H
+#define TYPEDEFNODE_H
+
+#include "enumnode.h"
+#include "node.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+
+class TypedefNode : public Node
+{
+public:
+ TypedefNode(Aggregate *parent, const QString &name, NodeType type = Typedef)
+ : Node(type, parent, name)
+ {
+ }
+
+ bool hasAssociatedEnum() const { return m_associatedEnum != nullptr; }
+ const EnumNode *associatedEnum() const { return m_associatedEnum; }
+ Node *clone(Aggregate *parent) override;
+
+private:
+ void setAssociatedEnum(const EnumNode *t);
+
+ friend class EnumNode;
+
+ const EnumNode *m_associatedEnum { nullptr };
+};
+
+class TypeAliasNode : public TypedefNode
+{
+public:
+ TypeAliasNode(Aggregate *parent, const QString &name, const QString &aliasedType)
+ : TypedefNode(parent, name, NodeType::TypeAlias), m_aliasedType(aliasedType)
+ {
+ }
+
+ const QString &aliasedType() const { return m_aliasedType; }
+ Node *clone(Aggregate *parent) override;
+
+private:
+ QString m_aliasedType {};
+};
+
+QT_END_NAMESPACE
+
+#endif // TYPEDEFNODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/utilities.cpp b/src/qdoc/qdoc/src/qdoc/utilities.cpp
new file mode 100644
index 000000000..2825804d6
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/utilities.cpp
@@ -0,0 +1,254 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QtCore/qprocess.h>
+#include <QCryptographicHash>
+#include "location.h"
+#include "utilities.h"
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcQdoc, "qt.qdoc")
+Q_LOGGING_CATEGORY(lcQdocClang, "qt.qdoc.clang")
+
+/*!
+ \namespace Utilities
+ \internal
+ \brief This namespace holds QDoc-internal utility methods.
+ */
+namespace Utilities {
+static inline void setDebugEnabled(bool value)
+{
+ const_cast<QLoggingCategory &>(lcQdoc()).setEnabled(QtDebugMsg, value);
+ const_cast<QLoggingCategory &>(lcQdocClang()).setEnabled(QtDebugMsg, value);
+}
+
+void startDebugging(const QString &message)
+{
+ setDebugEnabled(true);
+ qCDebug(lcQdoc, "START DEBUGGING: %ls", qUtf16Printable(message));
+}
+
+void stopDebugging(const QString &message)
+{
+ qCDebug(lcQdoc, "STOP DEBUGGING: %ls", qUtf16Printable(message));
+ setDebugEnabled(false);
+}
+
+bool debugging()
+{
+ return lcQdoc().isEnabled(QtDebugMsg);
+}
+
+/*!
+ \internal
+ Convenience method that's used to get the correct punctuation character for
+ the words at \a wordPosition in a list of \a numberOfWords length.
+ For the last position in the list, returns "." (full stop). For any other
+ word, this method calls comma().
+
+ \sa comma()
+ */
+QString separator(qsizetype wordPosition, qsizetype numberOfWords)
+{
+ static QString terminator = QStringLiteral(".");
+ if (wordPosition == numberOfWords - 1)
+ return terminator;
+ else
+ return comma(wordPosition, numberOfWords);
+}
+
+/*!
+ \internal
+ Convenience method that's used to get the correct punctuation character for
+ the words at \a wordPosition in a list of \a numberOfWords length.
+
+ For a list of length one, returns an empty QString. For a list of length
+ two, returns the string " and ". For any length beyond two, returns the
+ string ", " until the last element, which returns ", and ".
+
+ \sa comma()
+ */
+QString comma(qsizetype wordPosition, qsizetype numberOfWords)
+{
+ if (wordPosition == numberOfWords - 1)
+ return QString();
+ if (numberOfWords == 2)
+ return QStringLiteral(" and ");
+ if (wordPosition == 0 || wordPosition < numberOfWords - 2)
+ return QStringLiteral(", ");
+ return QStringLiteral(", and ");
+}
+
+/*!
+ \brief Returns an ascii-printable representation of \a str.
+
+ Replace non-ascii-printable characters in \a str from a subset of such
+ characters. The subset includes alphanumeric (alnum) characters
+ ([a-zA-Z0-9]), space, punctuation characters, and common symbols. Non-alnum
+ characters in this subset are replaced by a single hyphen. Leading,
+ trailing, and consecutive hyphens are removed, such that the resulting
+ string does not start or end with a hyphen. All characters are converted to
+ lowercase.
+
+ If any character in \a str is non-latin, or latin and not found in the
+ aforementioned subset (e.g. 'ß', 'å', or 'ö'), a hash of \a str is appended
+ to the final string.
+
+ Returns a string that is normalized for use where ascii-printable strings
+ are required, such as file names or fragment identifiers in URLs.
+
+ The implementation is equivalent to:
+
+ \code
+ name.replace(QRegularExpression("[^A-Za-z0-9]+"), " ");
+ name = name.simplified();
+ name.replace(QLatin1Char(' '), QLatin1Char('-'));
+ name = name.toLower();
+ \endcode
+
+ However, it has been measured to be approximately four times faster.
+*/
+QString asAsciiPrintable(const QString &str)
+{
+ auto legal_ascii = [](const uint value) {
+ const uint start_ascii_subset{ 32 };
+ const uint end_ascii_subset{ 126 };
+
+ return value >= start_ascii_subset && value <= end_ascii_subset;
+ };
+
+ QString result;
+ bool begun = false;
+ bool has_non_alnum_content{ false };
+
+ for (const auto &c : str) {
+ char16_t u = c.unicode();
+ if (!legal_ascii(u))
+ has_non_alnum_content = true;
+ if (u >= 'A' && u <= 'Z')
+ u += 'a' - 'A';
+ if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) {
+ result += QLatin1Char(u);
+ begun = true;
+ } else if (begun) {
+ result += QLatin1Char('-');
+ begun = false;
+ }
+ }
+ if (result.endsWith(QLatin1Char('-')))
+ result.chop(1);
+
+ if (has_non_alnum_content) {
+ auto title_hash = QString::fromLocal8Bit(
+ QCryptographicHash::hash(str.toUtf8(), QCryptographicHash::Md5).toHex());
+ title_hash.truncate(8);
+ if (!result.isEmpty())
+ result.append(QLatin1Char('-'));
+ result.append(title_hash);
+ }
+
+ return result;
+}
+
+/*!
+ \internal
+*/
+static bool runProcess(const QString &program, const QStringList &arguments,
+ QByteArray *stdOutIn, QByteArray *stdErrIn)
+{
+ QProcess process;
+ process.start(program, arguments, QProcess::ReadWrite);
+ if (!process.waitForStarted()) {
+ qCDebug(lcQdoc).nospace() << "Unable to start " << process.program()
+ << ": " << process.errorString();
+ return false;
+ }
+ process.closeWriteChannel();
+ const bool finished = process.waitForFinished();
+ const QByteArray stdErr = process.readAllStandardError();
+ if (stdErrIn)
+ *stdErrIn = stdErr;
+ if (stdOutIn)
+ *stdOutIn = process.readAllStandardOutput();
+
+ if (!finished) {
+ qCDebug(lcQdoc).nospace() << process.program() << " timed out: " << stdErr;
+ process.kill();
+ return false;
+ }
+
+ if (process.exitStatus() != QProcess::NormalExit) {
+ qCDebug(lcQdoc).nospace() << process.program() << " crashed: " << stdErr;
+ return false;
+ }
+
+ if (process.exitCode() != 0) {
+ qCDebug(lcQdoc).nospace() << process.program() << " exited with "
+ << process.exitCode() << ": " << stdErr;
+ return false;
+ }
+
+ return true;
+}
+
+/*!
+ \internal
+*/
+static QByteArray frameworkSuffix() {
+ return QByteArrayLiteral(" (framework directory)");
+}
+
+/*!
+ \internal
+ Determine the compiler's internal include paths from the output of
+
+ \badcode
+ [clang++|g++] -E -x c++ - -v </dev/null
+ \endcode
+
+ Output looks like:
+
+ \badcode
+ #include <...> search starts here:
+ /usr/local/include
+ /System/Library/Frameworks (framework directory)
+ End of search list.
+ \endcode
+*/
+QStringList getInternalIncludePaths(const QString &compiler)
+{
+ QStringList result;
+ QStringList arguments;
+ arguments << QStringLiteral("-E") << QStringLiteral("-x") << QStringLiteral("c++")
+ << QStringLiteral("-") << QStringLiteral("-v");
+ QByteArray stdOut;
+ QByteArray stdErr;
+ if (!runProcess(compiler, arguments, &stdOut, &stdErr))
+ return result;
+ const QByteArrayList stdErrLines = stdErr.split('\n');
+ bool isIncludeDir = false;
+ for (const QByteArray &line : stdErrLines) {
+ if (isIncludeDir) {
+ if (line.startsWith(QByteArrayLiteral("End of search list"))) {
+ isIncludeDir = false;
+ } else {
+ QByteArray prefix("-I");
+ QByteArray headerPath{line.trimmed()};
+ if (headerPath.endsWith(frameworkSuffix())) {
+ headerPath.truncate(headerPath.size() - frameworkSuffix().size());
+ prefix = QByteArrayLiteral("-F");
+ }
+ result.append(QString::fromLocal8Bit(prefix + headerPath));
+ }
+ } else if (line.startsWith(QByteArrayLiteral("#include <...> search starts here"))) {
+ isIncludeDir = true;
+ }
+ }
+
+ return result;
+}
+
+} // namespace Utilities
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/utilities.h b/src/qdoc/qdoc/src/qdoc/utilities.h
new file mode 100644
index 000000000..0d485f650
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/utilities.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef UTILITIES_H
+#define UTILITIES_H
+
+#include <QtCore/qstring.h>
+#include <QtCore/qloggingcategory.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcQdoc)
+Q_DECLARE_LOGGING_CATEGORY(lcQdocClang)
+
+namespace Utilities {
+void startDebugging(const QString &message);
+void stopDebugging(const QString &message);
+bool debugging();
+
+QString separator(qsizetype wordPosition, qsizetype numberOfWords);
+QString comma(qsizetype wordPosition, qsizetype numberOfWords);
+QString asAsciiPrintable(const QString &name);
+QStringList getInternalIncludePaths(const QString &compiler);
+}
+
+QT_END_NAMESPACE
+
+#endif // UTILITIES_H
diff --git a/src/qdoc/qdoc/src/qdoc/variablenode.cpp b/src/qdoc/qdoc/src/qdoc/variablenode.cpp
new file mode 100644
index 000000000..11c8363f3
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/variablenode.cpp
@@ -0,0 +1,23 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "variablenode.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ Clone this node on the heap and make the clone a child of
+ \a parent.
+
+ Returns a pointer to the clone.
+ */
+Node *VariableNode::clone(Aggregate *parent)
+{
+ auto *vn = new VariableNode(*this); // shallow copy
+ vn->setParent(nullptr);
+ parent->addChild(vn);
+
+ return vn;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/variablenode.h b/src/qdoc/qdoc/src/qdoc/variablenode.h
new file mode 100644
index 000000000..7db1252fe
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/variablenode.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef VARIABLENODE_H
+#define VARIABLENODE_H
+
+#include "aggregate.h"
+#include "node.h"
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class VariableNode : public Node
+{
+public:
+ VariableNode(Aggregate *parent, const QString &name);
+
+ void setLeftType(const QString &leftType) { m_leftType = leftType; }
+ void setRightType(const QString &rightType) { m_rightType = rightType; }
+ void setStatic(bool b) { m_static = b; }
+
+ [[nodiscard]] const QString &leftType() const { return m_leftType; }
+ [[nodiscard]] const QString &rightType() const { return m_rightType; }
+ [[nodiscard]] QString dataType() const { return m_leftType + m_rightType; }
+ [[nodiscard]] bool isStatic() const override { return m_static; }
+ Node *clone(Aggregate *parent) override;
+
+private:
+ QString m_leftType {};
+ QString m_rightType {};
+ bool m_static { false };
+};
+
+inline VariableNode::VariableNode(Aggregate *parent, const QString &name)
+ : Node(Variable, parent, name)
+{
+ setGenus(Node::CPP);
+}
+
+QT_END_NAMESPACE
+
+#endif // VARIABLENODE_H
diff --git a/src/qdoc/qdoc/src/qdoc/webxmlgenerator.cpp b/src/qdoc/qdoc/src/qdoc/webxmlgenerator.cpp
new file mode 100644
index 000000000..c2cc38161
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/webxmlgenerator.cpp
@@ -0,0 +1,903 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "webxmlgenerator.h"
+
+#include "aggregate.h"
+#include "collectionnode.h"
+#include "config.h"
+#include "helpprojectwriter.h"
+#include "node.h"
+#include "propertynode.h"
+#include "qdocdatabase.h"
+#include "quoter.h"
+#include "utilities.h"
+
+#include <QtCore/qxmlstream.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+static CodeMarker *marker_ = nullptr;
+
+WebXMLGenerator::WebXMLGenerator(FileResolver& file_resolver) : HtmlGenerator(file_resolver) {}
+
+void WebXMLGenerator::initializeGenerator()
+{
+ HtmlGenerator::initializeGenerator();
+}
+
+void WebXMLGenerator::terminateGenerator()
+{
+ HtmlGenerator::terminateGenerator();
+}
+
+QString WebXMLGenerator::format()
+{
+ return "WebXML";
+}
+
+QString WebXMLGenerator::fileExtension() const
+{
+ // As this is meant to be an intermediate format,
+ // use .html for internal references. The name of
+ // the output file is set separately in
+ // beginSubPage() calls.
+ return "html";
+}
+
+/*!
+ Most of the output is generated by QDocIndexFiles and the append() callback.
+ Some pages produce supplementary output while being generated, and that's
+ handled here.
+*/
+qsizetype WebXMLGenerator::generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker)
+{
+ if (m_supplement && currentWriter)
+ addAtomElements(*currentWriter.data(), atom, relative, marker);
+ return 0;
+}
+
+void WebXMLGenerator::generateCppReferencePage(Aggregate *aggregate, CodeMarker * /* marker */)
+{
+ QByteArray data;
+ QXmlStreamWriter writer(&data);
+ writer.setAutoFormatting(true);
+ beginSubPage(aggregate, Generator::fileName(aggregate, "webxml"));
+ writer.writeStartDocument();
+ writer.writeStartElement("WebXML");
+ writer.writeStartElement("document");
+
+ generateIndexSections(writer, aggregate);
+
+ writer.writeEndElement(); // document
+ writer.writeEndElement(); // WebXML
+ writer.writeEndDocument();
+
+ out() << data;
+ endSubPage();
+}
+
+void WebXMLGenerator::generatePageNode(PageNode *pn, CodeMarker * /* marker */)
+{
+ QByteArray data;
+ currentWriter.reset(new QXmlStreamWriter(&data));
+ currentWriter->setAutoFormatting(true);
+ beginSubPage(pn, Generator::fileName(pn, "webxml"));
+ currentWriter->writeStartDocument();
+ currentWriter->writeStartElement("WebXML");
+ currentWriter->writeStartElement("document");
+
+ generateIndexSections(*currentWriter.data(), pn);
+
+ currentWriter->writeEndElement(); // document
+ currentWriter->writeEndElement(); // WebXML
+ currentWriter->writeEndDocument();
+
+ out() << data;
+ endSubPage();
+}
+
+void WebXMLGenerator::generateExampleFilePage(const Node *en, ResolvedFile resolved_file, CodeMarker* /* marker */)
+{
+ // TODO: [generator-insufficient-structural-abstraction]
+
+ QByteArray data;
+ QXmlStreamWriter writer(&data);
+ writer.setAutoFormatting(true);
+ beginSubPage(en, linkForExampleFile(resolved_file.get_query(), "webxml"));
+ writer.writeStartDocument();
+ writer.writeStartElement("WebXML");
+ writer.writeStartElement("document");
+ writer.writeStartElement("page");
+ writer.writeAttribute("name", resolved_file.get_query());
+ writer.writeAttribute("href", linkForExampleFile(resolved_file.get_query()));
+ const QString title = exampleFileTitle(static_cast<const ExampleNode *>(en), resolved_file.get_query());
+ writer.writeAttribute("title", title);
+ writer.writeAttribute("fulltitle", title);
+ writer.writeAttribute("subtitle", resolved_file.get_query());
+ writer.writeStartElement("description");
+
+ if (Config::instance().get(CONFIG_LOCATIONINFO).asBool()) {
+ writer.writeAttribute("path", resolved_file.get_path());
+ writer.writeAttribute("line", "0");
+ writer.writeAttribute("column", "0");
+ }
+
+ Quoter quoter;
+ Doc::quoteFromFile(en->doc().location(), quoter, resolved_file);
+ QString code = quoter.quoteTo(en->location(), QString(), QString());
+ writer.writeTextElement("code", trimmedTrailing(code, QString(), QString()));
+
+ writer.writeEndElement(); // description
+ writer.writeEndElement(); // page
+ writer.writeEndElement(); // document
+ writer.writeEndElement(); // WebXML
+ writer.writeEndDocument();
+
+ out() << data;
+ endSubPage();
+}
+
+void WebXMLGenerator::generateIndexSections(QXmlStreamWriter &writer, Node *node)
+{
+ marker_ = CodeMarker::markerForFileName(node->location().filePath());
+ auto qdocIndexFiles = QDocIndexFiles::qdocIndexFiles();
+ if (qdocIndexFiles) {
+ qdocIndexFiles->generateIndexSections(writer, node, this);
+ // generateIndexSections does nothing for groups, so handle them explicitly
+ if (node->isGroup())
+ qdocIndexFiles->generateIndexSection(writer, node, this);
+ }
+}
+
+// Handles callbacks from QDocIndexFiles to add documentation to node
+void WebXMLGenerator::append(QXmlStreamWriter &writer, Node *node)
+{
+ Q_ASSERT(marker_);
+
+ writer.writeStartElement("description");
+ if (Config::instance().get(CONFIG_LOCATIONINFO).asBool()) {
+ writer.writeAttribute("path", node->doc().location().filePath());
+ writer.writeAttribute("line", QString::number(node->doc().location().lineNo()));
+ writer.writeAttribute("column", QString::number(node->doc().location().columnNo()));
+ }
+
+ if (node->isTextPageNode())
+ generateRelations(writer, node);
+
+ if (node->isModule()) {
+ writer.writeStartElement("generatedlist");
+ writer.writeAttribute("contents", "classesbymodule");
+ auto *cnn = static_cast<CollectionNode *>(node);
+
+ if (cnn->hasNamespaces()) {
+ writer.writeStartElement("section");
+ writer.writeStartElement("heading");
+ writer.writeAttribute("level", "1");
+ writer.writeCharacters("Namespaces");
+ writer.writeEndElement(); // heading
+ NodeMap namespaces{cnn->getMembers(Node::Namespace)};
+ generateAnnotatedList(writer, node, namespaces);
+ writer.writeEndElement(); // section
+ }
+ if (cnn->hasClasses()) {
+ writer.writeStartElement("section");
+ writer.writeStartElement("heading");
+ writer.writeAttribute("level", "1");
+ writer.writeCharacters("Classes");
+ writer.writeEndElement(); // heading
+ NodeMap classes{cnn->getMembers([](const Node *n){ return n->isClassNode(); })};
+ generateAnnotatedList(writer, node, classes);
+ writer.writeEndElement(); // section
+ }
+ writer.writeEndElement(); // generatedlist
+ }
+
+ m_inLink = m_inSectionHeading = m_hasQuotingInformation = false;
+
+ const Atom *atom = node->doc().body().firstAtom();
+ while (atom)
+ atom = addAtomElements(writer, atom, node, marker_);
+
+ QList<Text> alsoList = node->doc().alsoList();
+ supplementAlsoList(node, alsoList);
+
+ if (!alsoList.isEmpty()) {
+ writer.writeStartElement("see-also");
+ for (const auto &item : alsoList) {
+ const auto *atom = item.firstAtom();
+ while (atom)
+ atom = addAtomElements(writer, atom, node, marker_);
+ }
+ writer.writeEndElement(); // see-also
+ }
+
+ if (node->isExample()) {
+ m_supplement = true;
+ generateRequiredLinks(node, marker_);
+ m_supplement = false;
+ } else if (node->isGroup()) {
+ auto *cn = static_cast<CollectionNode *>(node);
+ if (!cn->noAutoList())
+ generateAnnotatedList(writer, node, cn->members());
+ }
+
+ writer.writeEndElement(); // description
+}
+
+void WebXMLGenerator::generateDocumentation(Node *node)
+{
+ // Don't generate nodes that are already processed, or if they're not supposed to
+ // generate output, ie. external, index or images nodes.
+ if (!node->url().isNull() || node->isExternalPage() || node->isIndexNode())
+ return;
+
+ if (node->isInternal() && !m_showInternal)
+ return;
+
+ if (node->parent()) {
+ if (node->isNamespace() || node->isClassNode() || node->isHeader())
+ generateCppReferencePage(static_cast<Aggregate *>(node), nullptr);
+ else if (node->isCollectionNode()) {
+ if (node->wasSeen()) {
+ // see remarks in base class impl.
+ m_qdb->mergeCollections(static_cast<CollectionNode *>(node));
+ generatePageNode(static_cast<PageNode *>(node), nullptr);
+ }
+ } else if (node->isTextPageNode())
+ generatePageNode(static_cast<PageNode *>(node), nullptr);
+ // else if TODO: anything else?
+ }
+
+ if (node->isAggregate()) {
+ auto *aggregate = static_cast<Aggregate *>(node);
+ for (auto c : aggregate->childNodes()) {
+ if ((c->isAggregate() || c->isTextPageNode() || c->isCollectionNode())
+ && !c->isPrivate())
+ generateDocumentation(c);
+ }
+ }
+}
+
+const Atom *WebXMLGenerator::addAtomElements(QXmlStreamWriter &writer, const Atom *atom,
+ const Node *relative, CodeMarker *marker)
+{
+ bool keepQuoting = false;
+
+ if (!atom)
+ return nullptr;
+
+ switch (atom->type()) {
+ case Atom::AnnotatedList: {
+ const CollectionNode *cn = m_qdb->getCollectionNode(atom->string(), Node::Group);
+ if (cn)
+ generateAnnotatedList(writer, relative, cn->members());
+ } break;
+ case Atom::AutoLink: {
+ const Node *node{nullptr};
+ QString link{};
+
+ if (!m_inLink && !m_inSectionHeading) {
+ link = getAutoLink(atom, relative, &node, Node::API);
+
+ if (!link.isEmpty() && node && node->isDeprecated()
+ && relative->parent() != node && !relative->isDeprecated()) {
+ link.clear();
+ }
+ }
+
+ startLink(writer, atom, node, link);
+
+ writer.writeCharacters(atom->string());
+
+ if (m_inLink) {
+ writer.writeEndElement(); // link
+ m_inLink = false;
+ }
+
+ break;
+ }
+ case Atom::BaseName:
+ break;
+ case Atom::BriefLeft:
+
+ writer.writeStartElement("brief");
+ switch (relative->nodeType()) {
+ case Node::Property:
+ writer.writeCharacters("This property");
+ break;
+ case Node::Variable:
+ writer.writeCharacters("This variable");
+ break;
+ default:
+ break;
+ }
+ if (relative->isProperty() || relative->isVariable()) {
+ QString str;
+ const Atom *a = atom->next();
+ while (a != nullptr && a->type() != Atom::BriefRight) {
+ if (a->type() == Atom::String || a->type() == Atom::AutoLink)
+ str += a->string();
+ a = a->next();
+ }
+ str[0] = str[0].toLower();
+ if (str.endsWith('.'))
+ str.chop(1);
+
+ const QList<QStringView> words = QStringView{str}.split(' ');
+ if (!words.isEmpty()) {
+ QStringView first(words.at(0));
+ if (!(first == u"contains" || first == u"specifies" || first == u"describes"
+ || first == u"defines" || first == u"holds" || first == u"determines"))
+ writer.writeCharacters(" holds ");
+ else
+ writer.writeCharacters(" ");
+ }
+ }
+ break;
+
+ case Atom::BriefRight:
+ if (relative->isProperty() || relative->isVariable())
+ writer.writeCharacters(".");
+
+ writer.writeEndElement(); // brief
+ break;
+
+ case Atom::C:
+ writer.writeStartElement("teletype");
+ if (m_inLink)
+ writer.writeAttribute("type", "normal");
+ else
+ writer.writeAttribute("type", "highlighted");
+
+ writer.writeCharacters(plainCode(atom->string()));
+ writer.writeEndElement(); // teletype
+ break;
+
+ case Atom::Code:
+ if (!m_hasQuotingInformation)
+ writer.writeTextElement(
+ "code", trimmedTrailing(plainCode(atom->string()), QString(), QString()));
+ else
+ keepQuoting = true;
+ break;
+
+ case Atom::CodeBad:
+ writer.writeTextElement("badcode",
+ trimmedTrailing(plainCode(atom->string()), QString(), QString()));
+ break;
+
+ case Atom::CodeQuoteArgument:
+ if (m_quoting) {
+ if (quoteCommand == "dots") {
+ writer.writeAttribute("indent", atom->string());
+ writer.writeCharacters("...");
+ } else {
+ writer.writeCharacters(atom->string());
+ }
+ writer.writeEndElement(); // code
+ keepQuoting = true;
+ }
+ break;
+
+ case Atom::CodeQuoteCommand:
+ if (m_quoting) {
+ quoteCommand = atom->string();
+ writer.writeStartElement(quoteCommand);
+ }
+ break;
+
+ case Atom::ExampleFileLink: {
+ if (!m_inLink) {
+ QString link = linkForExampleFile(atom->string());
+ if (!link.isEmpty())
+ startLink(writer, atom, relative, link);
+ }
+ } break;
+
+ case Atom::ExampleImageLink: {
+ if (!m_inLink) {
+ QString link = atom->string();
+ if (!link.isEmpty())
+ startLink(writer, atom, nullptr, "images/used-in-examples/" + link);
+ }
+ } break;
+
+ case Atom::FootnoteLeft:
+ writer.writeStartElement("footnote");
+ break;
+
+ case Atom::FootnoteRight:
+ writer.writeEndElement(); // footnote
+ break;
+
+ case Atom::FormatEndif:
+ writer.writeEndElement(); // raw
+ break;
+ case Atom::FormatIf:
+ writer.writeStartElement("raw");
+ writer.writeAttribute("format", atom->string());
+ break;
+ case Atom::FormattingLeft: {
+ if (atom->string() == ATOM_FORMATTING_BOLD)
+ writer.writeStartElement("bold");
+ else if (atom->string() == ATOM_FORMATTING_ITALIC)
+ writer.writeStartElement("italic");
+ else if (atom->string() == ATOM_FORMATTING_UNDERLINE)
+ writer.writeStartElement("underline");
+ else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT)
+ writer.writeStartElement("subscript");
+ else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT)
+ writer.writeStartElement("superscript");
+ else if (atom->string() == ATOM_FORMATTING_TELETYPE)
+ writer.writeStartElement("teletype");
+ else if (atom->string() == ATOM_FORMATTING_PARAMETER)
+ writer.writeStartElement("argument");
+ else if (atom->string() == ATOM_FORMATTING_INDEX)
+ writer.writeStartElement("index");
+ } break;
+
+ case Atom::FormattingRight: {
+ if (atom->string() == ATOM_FORMATTING_BOLD)
+ writer.writeEndElement();
+ else if (atom->string() == ATOM_FORMATTING_ITALIC)
+ writer.writeEndElement();
+ else if (atom->string() == ATOM_FORMATTING_UNDERLINE)
+ writer.writeEndElement();
+ else if (atom->string() == ATOM_FORMATTING_SUBSCRIPT)
+ writer.writeEndElement();
+ else if (atom->string() == ATOM_FORMATTING_SUPERSCRIPT)
+ writer.writeEndElement();
+ else if (atom->string() == ATOM_FORMATTING_TELETYPE)
+ writer.writeEndElement();
+ else if (atom->string() == ATOM_FORMATTING_PARAMETER)
+ writer.writeEndElement();
+ else if (atom->string() == ATOM_FORMATTING_INDEX)
+ writer.writeEndElement();
+ else if (atom->string() == ATOM_FORMATTING_TRADEMARK && appendTrademark(atom))
+ writer.writeCharacters(QChar(0x2122)); // 'TM' symbol
+ }
+ if (m_inLink) {
+ writer.writeEndElement(); // link
+ m_inLink = false;
+ }
+ break;
+
+ case Atom::GeneratedList:
+ writer.writeStartElement("generatedlist");
+ writer.writeAttribute("contents", atom->string());
+ writer.writeEndElement();
+ break;
+
+ // TODO: The other generators treat inlineimage and image
+ // simultaneously as the diffirences aren't big. It should be
+ // possible to do the same for webxmlgenerator instead of
+ // repeating the code.
+
+ // TODO: [generator-insufficient-structural-abstraction]
+ case Atom::Image: {
+ auto maybe_resolved_file{file_resolver.resolve(atom->string())};
+ if (!maybe_resolved_file) {
+ // TODO: [uncentralized-admonition][failed-resolve-file]
+ relative->location().warning(QStringLiteral("Missing image: %1").arg(atom->string()));
+ } else {
+ ResolvedFile file{*maybe_resolved_file};
+ QString file_name{QFileInfo{file.get_path()}.fileName()};
+
+ // TODO: [uncentralized-output-directory-structure]
+ Config::copyFile(relative->doc().location(), file.get_path(), file_name, outputDir() + QLatin1String("/images"));
+
+ writer.writeStartElement("image");
+ // TODO: [uncentralized-output-directory-structure]
+ writer.writeAttribute("href", "images/" + file_name);
+ writer.writeEndElement();
+ // TODO: [uncentralized-output-directory-structure]
+ setImageFileName(relative, "images/" + file_name);
+ }
+ break;
+ }
+ // TODO: [generator-insufficient-structural-abstraction]
+ case Atom::InlineImage: {
+ auto maybe_resolved_file{file_resolver.resolve(atom->string())};
+ if (!maybe_resolved_file) {
+ // TODO: [uncentralized-admonition][failed-resolve-file]
+ relative->location().warning(QStringLiteral("Missing image: %1").arg(atom->string()));
+ } else {
+ ResolvedFile file{*maybe_resolved_file};
+ QString file_name{QFileInfo{file.get_path()}.fileName()};
+
+ // TODO: [uncentralized-output-directory-structure]
+ Config::copyFile(relative->doc().location(), file.get_path(), file_name, outputDir() + QLatin1String("/images"));
+
+ writer.writeStartElement("inlineimage");
+ // TODO: [uncentralized-output-directory-structure]
+ writer.writeAttribute("href", "images/" + file_name);
+ writer.writeEndElement();
+ // TODO: [uncentralized-output-directory-structure]
+ setImageFileName(relative, "images/" + file_name);
+ }
+ break;
+ }
+ case Atom::ImageText:
+ break;
+
+ case Atom::ImportantLeft:
+ writer.writeStartElement("para");
+ writer.writeTextElement("bold", "Important:");
+ writer.writeCharacters(" ");
+ break;
+
+ case Atom::LegaleseLeft:
+ writer.writeStartElement("legalese");
+ break;
+
+ case Atom::LegaleseRight:
+ writer.writeEndElement(); // legalese
+ break;
+
+ case Atom::Link:
+ case Atom::LinkNode:
+ if (!m_inLink) {
+ const Node *node = nullptr;
+ QString link = getLink(atom, relative, &node);
+ if (!link.isEmpty())
+ startLink(writer, atom, node, link);
+ }
+ break;
+
+ case Atom::ListLeft:
+ writer.writeStartElement("list");
+
+ if (atom->string() == ATOM_LIST_BULLET)
+ writer.writeAttribute("type", "bullet");
+ else if (atom->string() == ATOM_LIST_TAG)
+ writer.writeAttribute("type", "definition");
+ else if (atom->string() == ATOM_LIST_VALUE) {
+ if (relative->isEnumType())
+ writer.writeAttribute("type", "enum");
+ else
+ writer.writeAttribute("type", "definition");
+ } else {
+ writer.writeAttribute("type", "ordered");
+ if (atom->string() == ATOM_LIST_UPPERALPHA)
+ writer.writeAttribute("start", "A");
+ else if (atom->string() == ATOM_LIST_LOWERALPHA)
+ writer.writeAttribute("start", "a");
+ else if (atom->string() == ATOM_LIST_UPPERROMAN)
+ writer.writeAttribute("start", "I");
+ else if (atom->string() == ATOM_LIST_LOWERROMAN)
+ writer.writeAttribute("start", "i");
+ else // (atom->string() == ATOM_LIST_NUMERIC)
+ writer.writeAttribute("start", "1");
+ }
+ break;
+
+ case Atom::ListItemNumber:
+ break;
+ case Atom::ListTagLeft: {
+ writer.writeStartElement("definition");
+
+ writer.writeTextElement(
+ "term", plainCode(marker->markedUpEnumValue(atom->next()->string(), relative)));
+ } break;
+
+ case Atom::ListTagRight:
+ writer.writeEndElement(); // definition
+ break;
+
+ case Atom::ListItemLeft:
+ writer.writeStartElement("item");
+ break;
+
+ case Atom::ListItemRight:
+ writer.writeEndElement(); // item
+ break;
+
+ case Atom::ListRight:
+ writer.writeEndElement(); // list
+ break;
+
+ case Atom::NoteLeft:
+ writer.writeStartElement("para");
+ writer.writeTextElement("bold", "Note:");
+ writer.writeCharacters(" ");
+ break;
+
+ // End admonition elements
+ case Atom::ImportantRight:
+ case Atom::NoteRight:
+ case Atom::WarningRight:
+ writer.writeEndElement(); // para
+ break;
+
+ case Atom::Nop:
+ break;
+
+ case Atom::CaptionLeft:
+ case Atom::ParaLeft:
+ writer.writeStartElement("para");
+ break;
+
+ case Atom::CaptionRight:
+ case Atom::ParaRight:
+ writer.writeEndElement(); // para
+ break;
+
+ case Atom::QuotationLeft:
+ writer.writeStartElement("quote");
+ break;
+
+ case Atom::QuotationRight:
+ writer.writeEndElement(); // quote
+ break;
+
+ case Atom::RawString:
+ writer.writeCharacters(atom->string());
+ break;
+
+ case Atom::SectionLeft:
+ writer.writeStartElement("section");
+ writer.writeAttribute("id",
+ Utilities::asAsciiPrintable(Text::sectionHeading(atom).toString()));
+ break;
+
+ case Atom::SectionRight:
+ writer.writeEndElement(); // section
+ break;
+
+ case Atom::SectionHeadingLeft: {
+ writer.writeStartElement("heading");
+ int unit = atom->string().toInt(); // + hOffset(relative)
+ writer.writeAttribute("level", QString::number(unit));
+ m_inSectionHeading = true;
+ } break;
+
+ case Atom::SectionHeadingRight:
+ writer.writeEndElement(); // heading
+ m_inSectionHeading = false;
+ break;
+
+ case Atom::SidebarLeft:
+ case Atom::SidebarRight:
+ break;
+
+ case Atom::SnippetCommand:
+ if (m_quoting) {
+ writer.writeStartElement(atom->string());
+ }
+ break;
+
+ case Atom::SnippetIdentifier:
+ if (m_quoting) {
+ writer.writeAttribute("identifier", atom->string());
+ writer.writeEndElement();
+ keepQuoting = true;
+ }
+ break;
+
+ case Atom::SnippetLocation:
+ if (m_quoting) {
+ const QString &location = atom->string();
+ writer.writeAttribute("location", location);
+ auto maybe_resolved_file{file_resolver.resolve(location)};
+ // const QString resolved = Doc::resolveFile(Location(), location);
+ if (maybe_resolved_file)
+ writer.writeAttribute("path", (*maybe_resolved_file).get_path());
+ else {
+ // TODO: [uncetnralized-admonition][failed-resolve-file]
+ QString details = std::transform_reduce(
+ file_resolver.get_search_directories().cbegin(),
+ file_resolver.get_search_directories().cend(),
+ u"Searched directories:"_s,
+ std::plus(),
+ [](const DirectoryPath &directory_path) -> QString { return u' ' + directory_path.value(); }
+ );
+
+ relative->location().warning(u"Cannot find file to quote from: %1"_s.arg(location), details);
+ }
+ }
+ break;
+
+ case Atom::String:
+ writer.writeCharacters(atom->string());
+ break;
+ case Atom::TableLeft:
+ writer.writeStartElement("table");
+ if (atom->string().contains("%"))
+ writer.writeAttribute("width", atom->string());
+ break;
+
+ case Atom::TableRight:
+ writer.writeEndElement(); // table
+ break;
+
+ case Atom::TableHeaderLeft:
+ writer.writeStartElement("header");
+ break;
+
+ case Atom::TableHeaderRight:
+ writer.writeEndElement(); // header
+ break;
+
+ case Atom::TableRowLeft:
+ writer.writeStartElement("row");
+ break;
+
+ case Atom::TableRowRight:
+ writer.writeEndElement(); // row
+ break;
+
+ case Atom::TableItemLeft: {
+ writer.writeStartElement("item");
+ QStringList spans = atom->string().split(",");
+ if (spans.size() == 2) {
+ if (spans.at(0) != "1")
+ writer.writeAttribute("colspan", spans.at(0).trimmed());
+ if (spans.at(1) != "1")
+ writer.writeAttribute("rowspan", spans.at(1).trimmed());
+ }
+ } break;
+ case Atom::TableItemRight:
+ writer.writeEndElement(); // item
+ break;
+
+ case Atom::Target:
+ writer.writeStartElement("target");
+ writer.writeAttribute("name", Utilities::asAsciiPrintable(atom->string()));
+ writer.writeEndElement();
+ break;
+
+ case Atom::WarningLeft:
+ writer.writeStartElement("para");
+ writer.writeTextElement("bold", "Warning:");
+ writer.writeCharacters(" ");
+ break;
+
+ case Atom::UnhandledFormat:
+ case Atom::UnknownCommand:
+ writer.writeCharacters(atom->typeString());
+ break;
+ default:
+ break;
+ }
+
+ m_hasQuotingInformation = keepQuoting;
+ return atom->next();
+}
+
+void WebXMLGenerator::startLink(QXmlStreamWriter &writer, const Atom *atom, const Node *node,
+ const QString &link)
+{
+ QString fullName = link;
+ if (node)
+ fullName = node->fullName();
+ if (!fullName.isEmpty() && !link.isEmpty()) {
+ writer.writeStartElement("link");
+ if (atom && !atom->string().isEmpty())
+ writer.writeAttribute("raw", atom->string());
+ else
+ writer.writeAttribute("raw", fullName);
+ writer.writeAttribute("href", link);
+ writer.writeAttribute("type", targetType(node));
+ if (node) {
+ switch (node->nodeType()) {
+ case Node::Enum:
+ writer.writeAttribute("enum", fullName);
+ break;
+ case Node::Example: {
+ const auto *en = static_cast<const ExampleNode *>(node);
+ const QString fileTitle = atom ? exampleFileTitle(en, atom->string()) : QString();
+ if (!fileTitle.isEmpty()) {
+ writer.writeAttribute("page", fileTitle);
+ break;
+ }
+ }
+ Q_FALLTHROUGH();
+ case Node::Page:
+ writer.writeAttribute("page", fullName);
+ break;
+ case Node::Property: {
+ const auto *propertyNode = static_cast<const PropertyNode *>(node);
+ if (!propertyNode->getters().empty())
+ writer.writeAttribute("getter", propertyNode->getters().at(0)->fullName());
+ } break;
+ default:
+ break;
+ }
+ }
+ m_inLink = true;
+ }
+}
+
+void WebXMLGenerator::endLink(QXmlStreamWriter &writer)
+{
+ if (m_inLink) {
+ writer.writeEndElement(); // link
+ m_inLink = false;
+ }
+}
+
+void WebXMLGenerator::generateRelations(QXmlStreamWriter &writer, const Node *node)
+{
+ if (node && !node->links().empty()) {
+ std::pair<QString, QString> anchorPair;
+ const Node *linkNode;
+
+ for (auto it = node->links().cbegin(); it != node->links().cend(); ++it) {
+
+ linkNode = m_qdb->findNodeForTarget(it.value().first, node);
+
+ if (!linkNode)
+ linkNode = node;
+
+ if (linkNode == node)
+ anchorPair = it.value();
+ else
+ anchorPair = anchorForNode(linkNode);
+
+ writer.writeStartElement("relation");
+ writer.writeAttribute("href", anchorPair.first);
+ writer.writeAttribute("type", targetType(linkNode));
+
+ switch (it.key()) {
+ case Node::StartLink:
+ writer.writeAttribute("meta", "start");
+ break;
+ case Node::NextLink:
+ writer.writeAttribute("meta", "next");
+ break;
+ case Node::PreviousLink:
+ writer.writeAttribute("meta", "previous");
+ break;
+ case Node::ContentsLink:
+ writer.writeAttribute("meta", "contents");
+ break;
+ default:
+ writer.writeAttribute("meta", "");
+ }
+ writer.writeAttribute("description", anchorPair.second);
+ writer.writeEndElement(); // link
+ }
+ }
+}
+
+void WebXMLGenerator::generateAnnotatedList(QXmlStreamWriter &writer, const Node *relative,
+ const NodeMap &nodeMap)
+{
+ generateAnnotatedList(writer, relative, nodeMap.values());
+}
+
+void WebXMLGenerator::generateAnnotatedList(QXmlStreamWriter &writer, const Node *relative,
+ const NodeList &nodeList)
+{
+ writer.writeStartElement("table");
+ writer.writeAttribute("width", "100%");
+
+ for (const auto *node : nodeList) {
+ writer.writeStartElement("row");
+ writer.writeStartElement("item");
+ writer.writeStartElement("para");
+ const QString link = linkForNode(node, relative);
+ startLink(writer, node->doc().body().firstAtom(), node, link);
+ endLink(writer);
+ writer.writeEndElement(); // para
+ writer.writeEndElement(); // item
+
+ writer.writeStartElement("item");
+ writer.writeStartElement("para");
+ writer.writeCharacters(node->doc().briefText().toString());
+ writer.writeEndElement(); // para
+ writer.writeEndElement(); // item
+ writer.writeEndElement(); // row
+ }
+ writer.writeEndElement(); // table
+}
+
+QString WebXMLGenerator::fileBase(const Node *node) const
+{
+ return Generator::fileBase(node);
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/webxmlgenerator.h b/src/qdoc/qdoc/src/qdoc/webxmlgenerator.h
new file mode 100644
index 000000000..7065670b2
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/webxmlgenerator.h
@@ -0,0 +1,60 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef WEBXMLGENERATOR_H
+#define WEBXMLGENERATOR_H
+
+#include "codemarker.h"
+#include "htmlgenerator.h"
+#include "qdocindexfiles.h"
+
+#include <QtCore/qscopedpointer.h>
+#include <QtCore/qxmlstream.h>
+
+QT_BEGIN_NAMESPACE
+
+class Aggregate;
+
+class WebXMLGenerator : public HtmlGenerator, public IndexSectionWriter
+{
+public:
+ WebXMLGenerator(FileResolver& file_resolver);
+
+ void initializeGenerator() override;
+ void terminateGenerator() override;
+ QString format() override;
+ // from IndexSectionWriter
+ void append(QXmlStreamWriter &writer, Node *node) override;
+
+protected:
+ qsizetype generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker) override;
+ void generateCppReferencePage(Aggregate *aggregate, CodeMarker *marker) override;
+ void generatePageNode(PageNode *pn, CodeMarker *marker) override;
+ void generateDocumentation(Node *node) override;
+ void generateExampleFilePage(const Node *en, ResolvedFile file, CodeMarker *marker = nullptr) override;
+ [[nodiscard]] QString fileExtension() const override;
+
+ virtual const Atom *addAtomElements(QXmlStreamWriter &writer, const Atom *atom,
+ const Node *relative, CodeMarker *marker);
+ virtual void generateIndexSections(QXmlStreamWriter &writer, Node *node);
+
+private:
+ void generateAnnotatedList(QXmlStreamWriter &writer, const Node *relative,
+ const NodeMap &nodeMap);
+ void generateAnnotatedList(QXmlStreamWriter &writer, const Node *relative,
+ const NodeList &nodeList);
+ void generateRelations(QXmlStreamWriter &writer, const Node *node);
+ void startLink(QXmlStreamWriter &writer, const Atom *atom, const Node *node,
+ const QString &link);
+ void endLink(QXmlStreamWriter &writer);
+ QString fileBase(const Node *node) const override;
+
+ bool m_hasQuotingInformation { false };
+ QString quoteCommand {};
+ QScopedPointer<QXmlStreamWriter> currentWriter {};
+ bool m_supplement { false };
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/qdoc/qdoc/src/qdoc/xmlgenerator.cpp b/src/qdoc/qdoc/src/qdoc/xmlgenerator.cpp
new file mode 100644
index 000000000..ffad5259d
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/xmlgenerator.cpp
@@ -0,0 +1,487 @@
+// Copyright (C) 2019 Thibaut Cuvelier
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "xmlgenerator.h"
+
+#include "enumnode.h"
+#include "examplenode.h"
+#include "functionnode.h"
+#include "qdocdatabase.h"
+#include "typedefnode.h"
+
+using namespace Qt::Literals::StringLiterals;
+
+QT_BEGIN_NAMESPACE
+
+const QRegularExpression XmlGenerator::m_funcLeftParen(QStringLiteral("^\\S+(\\(.*\\))"));
+
+XmlGenerator::XmlGenerator(FileResolver& file_resolver) : Generator(file_resolver) {}
+
+/*!
+ Do not display \brief for QML types, document and collection nodes
+ */
+bool XmlGenerator::hasBrief(const Node *node)
+{
+ return !(node->isQmlType() || node->isPageNode() || node->isCollectionNode());
+}
+
+/*!
+ Determines whether the list atom should be shown with three columns
+ (constant-value-description).
+ */
+bool XmlGenerator::isThreeColumnEnumValueTable(const Atom *atom)
+{
+ while (atom && !(atom->type() == Atom::ListRight && atom->string() == ATOM_LIST_VALUE)) {
+ if (atom->type() == Atom::ListItemLeft && !matchAhead(atom, Atom::ListItemRight))
+ return true;
+ atom = atom->next();
+ }
+ return false;
+}
+
+/*!
+ Determines whether the list atom should be shown with just one column (value).
+ */
+bool XmlGenerator::isOneColumnValueTable(const Atom *atom)
+{
+ if (atom->type() != Atom::ListLeft || atom->string() != ATOM_LIST_VALUE)
+ return false;
+
+ while (atom && atom->type() != Atom::ListTagRight)
+ atom = atom->next();
+
+ if (atom) {
+ if (!matchAhead(atom, Atom::ListItemLeft))
+ return false;
+ if (!atom->next())
+ return false;
+ return matchAhead(atom->next(), Atom::ListItemRight);
+ }
+ return false;
+}
+
+/*!
+ Header offset depending on the type of the node
+ */
+int XmlGenerator::hOffset(const Node *node)
+{
+ switch (node->nodeType()) {
+ case Node::Namespace:
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ case Node::Module:
+ return 2;
+ case Node::QmlModule:
+ case Node::QmlValueType:
+ case Node::QmlType:
+ case Node::Page:
+ case Node::Group:
+ return 1;
+ case Node::Enum:
+ case Node::TypeAlias:
+ case Node::Typedef:
+ case Node::Function:
+ case Node::Property:
+ default:
+ return 3;
+ }
+}
+
+/*!
+ Rewrites the brief of this node depending on its first word.
+ Only for properties and variables (does nothing otherwise).
+ */
+void XmlGenerator::rewritePropertyBrief(const Atom *atom, const Node *relative)
+{
+ if (relative->nodeType() != Node::Property && relative->nodeType() != Node::Variable)
+ return;
+ atom = atom->next();
+ if (!atom || atom->type() != Atom::String)
+ return;
+
+ const QString firstWord =
+ atom->string().toLower().section(' ', 0, 0, QString::SectionSkipEmpty);
+ const QStringList words{ "the", "a", "an", "whether", "which" };
+ if (words.contains(firstWord)) {
+ QString str = QLatin1String("This ")
+ + QLatin1String(relative->nodeType() == Node::Property ? "property" : "variable")
+ + QLatin1String(" holds ") + atom->string().left(1).toLower()
+ + atom->string().mid(1);
+ const_cast<Atom *>(atom)->setString(str);
+ }
+}
+
+/*!
+ Returns the type of this atom as an enumeration.
+ */
+Node::NodeType XmlGenerator::typeFromString(const Atom *atom)
+{
+ const auto &name = atom->string();
+ if (name.startsWith(QLatin1String("qml")))
+ return Node::QmlModule;
+ else if (name.startsWith(QLatin1String("groups")))
+ return Node::Group;
+ else
+ return Node::Module;
+}
+
+/*!
+ For images shown in examples, set the image file to the one it
+ will have once the documentation is generated.
+ */
+void XmlGenerator::setImageFileName(const Node *relative, const QString &fileName)
+{
+ if (relative->isExample()) {
+ const auto cen = static_cast<const ExampleNode *>(relative);
+ if (cen->imageFileName().isEmpty()) {
+ auto *en = const_cast<ExampleNode *>(cen);
+ en->setImageFileName(fileName);
+ }
+ }
+}
+
+/*!
+ Handles the differences in lists between list tags and since tags, and
+ returns the content of the list entry \a atom (first member of the pair).
+ It also returns the number of items to skip ahead (second member of the pair).
+ */
+std::pair<QString, int> XmlGenerator::getAtomListValue(const Atom *atom)
+{
+ const Atom *lookAhead = atom->next();
+ if (!lookAhead)
+ return std::pair<QString, int>(QString(), 1);
+
+ QString t = lookAhead->string();
+ lookAhead = lookAhead->next();
+ if (!lookAhead || lookAhead->type() != Atom::ListTagRight)
+ return std::pair<QString, int>(QString(), 1);
+
+ lookAhead = lookAhead->next();
+ int skipAhead;
+ if (lookAhead && lookAhead->type() == Atom::SinceTagLeft) {
+ lookAhead = lookAhead->next();
+ Q_ASSERT(lookAhead && lookAhead->type() == Atom::String);
+ t += QLatin1String(" (since ");
+ if (lookAhead->string().at(0).isDigit())
+ t += QLatin1String("Qt ");
+ t += lookAhead->string() + QLatin1String(")");
+ skipAhead = 4;
+ } else {
+ skipAhead = 1;
+ }
+ return std::pair<QString, int>(t, skipAhead);
+}
+
+/*!
+ Parses the table attributes from the given \a atom.
+ This method returns a pair containing the width (%) and
+ the attribute for this table (either "generic" or
+ "borderless").
+ */
+std::pair<QString, QString> XmlGenerator::getTableWidthAttr(const Atom *atom)
+{
+ QString p0, p1;
+ QString attr = "generic";
+ QString width;
+ if (atom->count() > 0) {
+ p0 = atom->string(0);
+ if (atom->count() > 1)
+ p1 = atom->string(1);
+ }
+ if (!p0.isEmpty()) {
+ if (p0 == QLatin1String("borderless"))
+ attr = p0;
+ else if (p0.contains(QLatin1Char('%')))
+ width = p0;
+ }
+ if (!p1.isEmpty()) {
+ if (p1 == QLatin1String("borderless"))
+ attr = p1;
+ else if (p1.contains(QLatin1Char('%')))
+ width = p1;
+ }
+
+ // Many times, in the documentation, there is a space before the % sign:
+ // this breaks the parsing logic above.
+ if (width == QLatin1String("%")) {
+ // The percentage is typically stored in p0, parse it as an int.
+ bool ok = false;
+ int widthPercentage = p0.toInt(&ok);
+ if (ok) {
+ width = QString::number(widthPercentage) + "%";
+ } else {
+ width = {};
+ }
+ }
+
+ return {width, attr};
+}
+
+/*!
+ Registers an anchor reference and returns a unique
+ and cleaned copy of the reference (the one that should be
+ used in the output).
+ To ensure unicity throughout the document, this method
+ uses the \a refMap cache.
+ */
+QString XmlGenerator::registerRef(const QString &ref, bool xmlCompliant)
+{
+ QString cleanRef = Generator::cleanRef(ref, xmlCompliant);
+
+ for (;;) {
+ QString &prevRef = refMap[cleanRef.toLower()];
+ if (prevRef.isEmpty()) {
+ // This reference has never been met before for this document: register it.
+ prevRef = ref;
+ break;
+ } else if (prevRef == ref) {
+ // This exact same reference was already found. This case typically occurs within refForNode.
+ break;
+ }
+ cleanRef += QLatin1Char('x');
+ }
+ return cleanRef;
+}
+
+/*!
+ Generates a clean and unique reference for the given \a node.
+ This reference may depend on the type of the node (typedef,
+ QML signal, etc.)
+ */
+QString XmlGenerator::refForNode(const Node *node)
+{
+ QString ref;
+ switch (node->nodeType()) {
+ case Node::Enum:
+ ref = node->name() + "-enum";
+ break;
+ case Node::Typedef: {
+ const auto *tdf = static_cast<const TypedefNode *>(node);
+ if (tdf->associatedEnum())
+ return refForNode(tdf->associatedEnum());
+ } Q_FALLTHROUGH();
+ case Node::TypeAlias:
+ ref = node->name() + "-typedef";
+ break;
+ case Node::Function: {
+ const auto fn = static_cast<const FunctionNode *>(node);
+ switch (fn->metaness()) {
+ case FunctionNode::QmlSignal:
+ ref = fn->name() + "-signal";
+ break;
+ case FunctionNode::QmlSignalHandler:
+ ref = fn->name() + "-signal-handler";
+ break;
+ case FunctionNode::QmlMethod:
+ ref = fn->name() + "-method";
+ if (fn->overloadNumber() != 0)
+ ref += QLatin1Char('-') + QString::number(fn->overloadNumber());
+ break;
+ default:
+ if (fn->hasOneAssociatedProperty() && fn->doc().isEmpty()) {
+ return refForNode(fn->associatedProperties()[0]);
+ } else {
+ ref = fn->name();
+ if (fn->overloadNumber() != 0)
+ ref += QLatin1Char('-') + QString::number(fn->overloadNumber());
+ }
+ break;
+ }
+ } break;
+ case Node::SharedComment: {
+ if (!node->isPropertyGroup())
+ break;
+ } Q_FALLTHROUGH();
+ case Node::QmlProperty:
+ if (node->isAttached())
+ ref = node->name() + "-attached-prop";
+ else
+ ref = node->name() + "-prop";
+ break;
+ case Node::Property:
+ ref = node->name() + "-prop";
+ break;
+ case Node::Variable:
+ ref = node->name() + "-var";
+ break;
+ default:
+ break;
+ }
+ return registerRef(ref);
+}
+
+/*!
+ Construct the link string for the \a node and return it.
+ The \a relative node is used to decide whether the link
+ we are generating is in the same file as the target.
+ Note the relative node can be 0, which pretty much
+ guarantees that the link and the target aren't in the
+ same file.
+ */
+QString XmlGenerator::linkForNode(const Node *node, const Node *relative)
+{
+ if (node == nullptr)
+ return QString();
+ if (!node->url().isNull())
+ return node->url();
+ if (fileBase(node).isEmpty())
+ return QString();
+ if (node->isPrivate())
+ return QString();
+
+ QString fn = fileName(node);
+ if (node->parent() && node->parent()->isQmlType() && node->parent()->isAbstract()) {
+ if (Generator::qmlTypeContext()) {
+ if (Generator::qmlTypeContext()->inherits(node->parent())) {
+ fn = fileName(Generator::qmlTypeContext());
+ } else if (node->parent()->isInternal() && !noLinkErrors()) {
+ node->doc().location().warning(
+ QStringLiteral("Cannot link to property in internal type '%1'")
+ .arg(node->parent()->name()));
+ return QString();
+ }
+ }
+ }
+
+ QString link = fn;
+
+ if (!node->isPageNode() || node->isPropertyGroup()) {
+ QString ref = refForNode(node);
+ if (relative && fn == fileName(relative) && ref == refForNode(relative))
+ return QString();
+
+ link += QLatin1Char('#');
+ link += ref;
+ }
+
+ /*
+ If the output is going to subdirectories, the
+ two nodes have different output directories if 'node'
+ was read from index.
+ */
+ if (relative && (node != relative)) {
+ if (useOutputSubdirs() && !node->isExternalPage() && node->isIndexNode())
+ link.prepend("../%1/"_L1.arg(node->tree()->physicalModuleName()));
+ }
+ return link;
+}
+
+/*!
+ This function is called for links, i.e. for words that
+ are marked with the qdoc link command. For autolinks
+ that are not marked with the qdoc link command, the
+ getAutoLink() function is called
+
+ It returns the string for a link found by using the data
+ in the \a atom to search the database. It also sets \a node
+ to point to the target node for that link. \a relative points
+ to the node holding the qdoc comment where the link command
+ was found.
+ */
+QString XmlGenerator::getLink(const Atom *atom, const Node *relative, const Node **node)
+{
+ const QString &t = atom->string();
+
+ if (t.isEmpty())
+ return t;
+
+ if (t.at(0) == QChar('h')) {
+ if (t.startsWith("http:") || t.startsWith("https:"))
+ return t;
+ } else if (t.at(0) == QChar('f')) {
+ if (t.startsWith("file:") || t.startsWith("ftp:"))
+ return t;
+ } else if (t.at(0) == QChar('m')) {
+ if (t.startsWith("mailto:"))
+ return t;
+ }
+ return getAutoLink(atom, relative, node);
+}
+
+/*!
+ This function is called for autolinks, i.e. for words that
+ are not marked with the qdoc link command that qdoc has
+ reason to believe should be links.
+
+ It returns the string for a link found by using the data
+ in the \a atom to search the database. It also sets \a node
+ to point to the target node for that link. \a relative points
+ to the node holding the qdoc comment where the link command
+ was found.
+ */
+QString XmlGenerator::getAutoLink(const Atom *atom, const Node *relative, const Node **node,
+ Node::Genus genus)
+{
+ QString ref;
+
+ *node = m_qdb->findNodeForAtom(atom, relative, ref, genus);
+ if (!(*node))
+ return QString();
+
+ QString link = (*node)->url();
+ if (link.isNull()) {
+ link = linkForNode(*node, relative);
+ } else if (link.isEmpty()) {
+ return link; // Explicit empty url (node is ignored as a link target)
+ }
+ if (!ref.isEmpty()) {
+ qsizetype hashtag = link.lastIndexOf(QChar('#'));
+ if (hashtag != -1)
+ link.truncate(hashtag);
+ link += QLatin1Char('#') + ref;
+ }
+ return link;
+}
+
+std::pair<QString, QString> XmlGenerator::anchorForNode(const Node *node)
+{
+ std::pair<QString, QString> anchorPair;
+
+ anchorPair.first = Generator::fileName(node);
+ if (node->isTextPageNode())
+ anchorPair.second = node->title();
+
+ return anchorPair;
+}
+
+/*!
+ Returns a string describing the \a node type.
+ */
+QString XmlGenerator::targetType(const Node *node)
+{
+ if (!node)
+ return QStringLiteral("external");
+
+ switch (node->nodeType()) {
+ case Node::Namespace:
+ return QStringLiteral("namespace");
+ case Node::Class:
+ case Node::Struct:
+ case Node::Union:
+ return QStringLiteral("class");
+ case Node::Page:
+ case Node::Example:
+ return QStringLiteral("page");
+ case Node::Enum:
+ return QStringLiteral("enum");
+ case Node::TypeAlias:
+ return QStringLiteral("alias");
+ case Node::Typedef:
+ return QStringLiteral("typedef");
+ case Node::Property:
+ return QStringLiteral("property");
+ case Node::Function:
+ return QStringLiteral("function");
+ case Node::Variable:
+ return QStringLiteral("variable");
+ case Node::Module:
+ return QStringLiteral("module");
+ default:
+ break;
+ }
+ return QString();
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/xmlgenerator.h b/src/qdoc/qdoc/src/qdoc/xmlgenerator.h
new file mode 100644
index 000000000..5f7ba67fd
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/xmlgenerator.h
@@ -0,0 +1,54 @@
+// Copyright (C) 2019 Thibaut Cuvelier
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef XMLGENERATOR_H
+#define XMLGENERATOR_H
+
+#include "node.h"
+#include "generator.h"
+#include "filesystem/fileresolver.h"
+
+#include <QtCore/qmap.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+class XmlGenerator : public Generator
+{
+public:
+ explicit XmlGenerator(FileResolver& file_resolver);
+
+protected:
+ QHash<QString, QString> refMap;
+
+ static bool hasBrief(const Node *node);
+ static bool isThreeColumnEnumValueTable(const Atom *atom);
+ static bool isOneColumnValueTable(const Atom *atom);
+ static int hOffset(const Node *node);
+
+ static void rewritePropertyBrief(const Atom *atom, const Node *relative);
+ static Node::NodeType typeFromString(const Atom *atom);
+ static void setImageFileName(const Node *relative, const QString &fileName);
+ static std::pair<QString, int> getAtomListValue(const Atom *atom);
+ static std::pair<QString, QString> getTableWidthAttr(const Atom *atom);
+
+ QString registerRef(const QString &ref, bool xmlCompliant = false);
+ QString refForNode(const Node *node);
+ QString linkForNode(const Node *node, const Node *relative);
+ QString getLink(const Atom *atom, const Node *relative, const Node **node);
+ QString getAutoLink(const Atom *atom, const Node *relative, const Node **node,
+ Node::Genus = Node::DontCare);
+
+ std::pair<QString, QString> anchorForNode(const Node *node);
+
+ static QString targetType(const Node *node);
+
+protected:
+ static const QRegularExpression m_funcLeftParen;
+ const Node *m_linkNode { nullptr };
+};
+
+QT_END_NAMESPACE
+
+#endif // XMLGENERATOR_H
diff --git a/src/qdoc/qdoc/tests/CMakeLists.txt b/src/qdoc/qdoc/tests/CMakeLists.txt
new file mode 100644
index 000000000..143837517
--- /dev/null
+++ b/src/qdoc/qdoc/tests/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_subdirectory(qdoc)
+add_subdirectory(config)
+add_subdirectory(qdoccommandlineparser)
+add_subdirectory(utilities)
+add_subdirectory(generatedoutput)
+add_subdirectory(validateqdocoutputfiles)
diff --git a/src/qdoc/qdoc/tests/config/CMakeLists.txt b/src/qdoc/qdoc/tests/config/CMakeLists.txt
new file mode 100644
index 000000000..ef483291d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/CMakeLists.txt
@@ -0,0 +1,24 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## tst_config Test:
+#####################################################################
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_config LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_test(tst_config
+ SOURCES
+ ${CMAKE_CURRENT_LIST_DIR}/tst_config.cpp
+
+ ${CMAKE_CURRENT_LIST_DIR}/../../src/qdoc/config.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/../../src/qdoc/location.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/../../src/qdoc/qdoccommandlineparser.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/../../src/qdoc/utilities.cpp
+ INCLUDE_DIRECTORIES
+ ${CMAKE_CURRENT_LIST_DIR}/../../src/
+)
diff --git a/src/qdoc/qdoc/tests/config/testdata/configs/exampletest.qdocconf b/src/qdoc/qdoc/tests/config/testdata/configs/exampletest.qdocconf
new file mode 100644
index 000000000..a1459f977
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/configs/exampletest.qdocconf
@@ -0,0 +1,2 @@
+project = ExampleTest
+exampledirs = ../exampletest/examples
diff --git a/src/qdoc/qdoc/tests/config/testdata/configs/expandvars.qdocconf b/src/qdoc/qdoc/tests/config/testdata/configs/expandvars.qdocconf
new file mode 100644
index 000000000..ace4ab13b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/configs/expandvars.qdocconf
@@ -0,0 +1,13 @@
+project = ExpandVars
+
+{data,data1,data2} = foo
+data1 += bar
+data2 += "bar baz"
+
+expanded1 = $data
+expanded2 = ${data1,,}
+expanded3 = "${data1,} ${data2,}"
+literally = \$data \${data}
+
+listdata = ${QDOC_TSTCONFIG_LIST}
+csvlist = ${listdata,,}
diff --git a/src/qdoc/qdoc/tests/config/testdata/configs/includepaths.qdocconf b/src/qdoc/qdoc/tests/config/testdata/configs/includepaths.qdocconf
new file mode 100644
index 000000000..2d6ff22af
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/configs/includepaths.qdocconf
@@ -0,0 +1,2 @@
+project = IncludePaths
+include(../includepaths/includepaths.qdocconf)
diff --git a/src/qdoc/qdoc/tests/config/testdata/configs/includes/test.qdoc b/src/qdoc/qdoc/tests/config/testdata/configs/includes/test.qdoc
new file mode 100644
index 000000000..700f1e819
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/configs/includes/test.qdoc
@@ -0,0 +1 @@
+/*! nothing here */
diff --git a/src/qdoc/qdoc/tests/config/testdata/configs/paths.qdocconf b/src/qdoc/qdoc/tests/config/testdata/configs/paths.qdocconf
new file mode 100644
index 000000000..93f7b3586
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/configs/paths.qdocconf
@@ -0,0 +1,5 @@
+project = Paths
+include(../paths/paths.qdocconf)
+
+sourcedirs += .
+sourcedirs += includes
diff --git a/src/qdoc/qdoc/tests/config/testdata/configs/vars.qdocconf b/src/qdoc/qdoc/tests/config/testdata/configs/vars.qdocconf
new file mode 100644
index 000000000..1f5da6eb8
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/configs/vars.qdocconf
@@ -0,0 +1,17 @@
+project = Variables
+
+untrue = false
+true = "Sir Yes, Sir!"
+
+void =
+int = 1
+int += 1
+
+list = \
+ testing line \
+ "by\n" \
+ "line"
+
+some.thing =
+some.where =
+some.time =
diff --git a/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/empty/test.pro b/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/empty/test.pro
new file mode 100644
index 000000000..556df42ea
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/empty/test.pro
@@ -0,0 +1 @@
+# nothing
diff --git a/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example1/example1.pro b/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example1/example1.pro
new file mode 100644
index 000000000..556df42ea
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example1/example1.pro
@@ -0,0 +1 @@
+# nothing
diff --git a/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example2/example2.qmlproject b/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example2/example2.qmlproject
new file mode 100644
index 000000000..556df42ea
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example2/example2.qmlproject
@@ -0,0 +1 @@
+# nothing
diff --git a/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example3/example3.pyproject b/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example3/example3.pyproject
new file mode 100644
index 000000000..556df42ea
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example3/example3.pyproject
@@ -0,0 +1 @@
+# nothing
diff --git a/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example4/CMakeLists.txt b/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example4/CMakeLists.txt
new file mode 100644
index 000000000..556df42ea
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example4/CMakeLists.txt
@@ -0,0 +1 @@
+# nothing
diff --git a/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example4/example4.pro b/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example4/example4.pro
new file mode 100644
index 000000000..556df42ea
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/exampletest/examples/test/example4/example4.pro
@@ -0,0 +1 @@
+# nothing
diff --git a/src/qdoc/qdoc/tests/config/testdata/includepaths/include/framework/ignore.h b/src/qdoc/qdoc/tests/config/testdata/includepaths/include/framework/ignore.h
new file mode 100644
index 000000000..b2a4ba591
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/includepaths/include/framework/ignore.h
@@ -0,0 +1 @@
+# nothing here
diff --git a/src/qdoc/qdoc/tests/config/testdata/includepaths/include/more/ignore.h b/src/qdoc/qdoc/tests/config/testdata/includepaths/include/more/ignore.h
new file mode 100644
index 000000000..b2a4ba591
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/includepaths/include/more/ignore.h
@@ -0,0 +1 @@
+# nothing here
diff --git a/src/qdoc/qdoc/tests/config/testdata/includepaths/include/purpose.h b/src/qdoc/qdoc/tests/config/testdata/includepaths/include/purpose.h
new file mode 100644
index 000000000..0f7af352b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/includepaths/include/purpose.h
@@ -0,0 +1 @@
+#define PURPOSE "Pass butter"
diff --git a/src/qdoc/qdoc/tests/config/testdata/includepaths/include/system/ignore.h b/src/qdoc/qdoc/tests/config/testdata/includepaths/include/system/ignore.h
new file mode 100644
index 000000000..b2a4ba591
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/includepaths/include/system/ignore.h
@@ -0,0 +1 @@
+# nothing here
diff --git a/src/qdoc/qdoc/tests/config/testdata/includepaths/includepaths.qdocconf b/src/qdoc/qdoc/tests/config/testdata/includepaths/includepaths.qdocconf
new file mode 100644
index 000000000..6288c4258
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/includepaths/includepaths.qdocconf
@@ -0,0 +1,16 @@
+includepaths = -I./include
+
+# without prefix but same path, should be identical
+# (Config should not remove duplicates)
+includepaths += include
+
+# space between prefix and path - incorrect but we allow it
+includepaths += -I include/more
+
+# system paths and framework paths
+includepaths += \
+ -F./include/framework \
+ -isysteminclude/system
+
+# nonexistent paths are to be ignored
+includepaths += invalid
diff --git a/src/qdoc/qdoc/tests/config/testdata/paths/includes/test.qdoc b/src/qdoc/qdoc/tests/config/testdata/paths/includes/test.qdoc
new file mode 100644
index 000000000..700f1e819
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/paths/includes/test.qdoc
@@ -0,0 +1 @@
+/*! nothing here */
diff --git a/src/qdoc/qdoc/tests/config/testdata/paths/paths.qdocconf b/src/qdoc/qdoc/tests/config/testdata/paths/paths.qdocconf
new file mode 100644
index 000000000..d360f4c1e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/testdata/paths/paths.qdocconf
@@ -0,0 +1,2 @@
+sourcedirs += does/not/exist
+sourcedirs = includes
diff --git a/src/qdoc/qdoc/tests/config/tst_config.cpp b/src/qdoc/qdoc/tests/config/tst_config.cpp
new file mode 100644
index 000000000..0e73130b8
--- /dev/null
+++ b/src/qdoc/qdoc/tests/config/tst_config.cpp
@@ -0,0 +1,180 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qdoc/config.h"
+
+#include <QtCore/qdir.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qstringlist.h>
+#include <QtTest/QtTest>
+
+class tst_Config : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void classMembersInitializeToFalseOrEmpty();
+ void includePathsFromCommandLine();
+ void variables();
+ void paths();
+ void includepaths();
+ void getExampleProjectFile();
+ void expandVars();
+
+private:
+ Config &initConfig(const QStringList &args = QStringList(),
+ const char *qdocconf = nullptr);
+ Config &initConfig(const char *qdocconf)
+ {
+ return initConfig(QStringList(), qdocconf);
+ }
+};
+
+/*
+ Initializes the Config with optional arguments and a .qdocconf file
+ to load, and returns a reference to it.
+*/
+Config &tst_Config::initConfig(const QStringList &args, const char *qdocconf)
+{
+ QStringList fullArgs = { QStringLiteral("./qdoc") };
+ fullArgs << args;
+ Config::instance().init("QDoc Test", fullArgs);
+
+ if (qdocconf) {
+ const auto configFile = QFINDTESTDATA(qdocconf);
+ if (!configFile.isEmpty())
+ Config::instance().load(configFile);
+ }
+
+ return Config::instance();
+}
+
+void tst_Config::classMembersInitializeToFalseOrEmpty()
+{
+ auto &config = initConfig();
+ QCOMPARE(config.showInternal(), false);
+ QCOMPARE(config.singleExec(), false);
+
+ QVERIFY(config.defines().isEmpty());
+ QVERIFY(config.includePaths().isEmpty());
+ QVERIFY(config.dependModules().isEmpty());
+ QVERIFY(config.indexDirs().isEmpty());
+ QVERIFY(config.currentDir().isEmpty());
+ QVERIFY(config.previousCurrentDir().isEmpty());
+}
+
+void tst_Config::includePathsFromCommandLine()
+{
+ const auto mockIncludePath1 = QString("-I" + QDir().absoluteFilePath("/qt5/qtdoc/doc/."));
+ const auto mockIncludePath2 = QString("-I" + QDir().absoluteFilePath("/qt5/qtbase/mkspecs/linux-g++"));
+ const QStringList commandLineArgs = { mockIncludePath1, mockIncludePath2 };
+ auto &config = initConfig(commandLineArgs);
+
+ const QStringList expected = { mockIncludePath1, mockIncludePath2 };
+ const QStringList actual = config.includePaths();
+
+ QCOMPARE(actual, expected);
+}
+
+// Tests different types of variables; string, string list, bool, int,
+// empty and undefined variables, and subvariables.
+void tst_Config::variables()
+{
+ auto &config = initConfig("/testdata/configs/vars.qdocconf");
+
+ const QStringList list = { "testing", "line", "by\n", "line" };
+ QCOMPARE(config.get("list").asStringList(), list);
+ QCOMPARE(config.get("list").asString(), "testing line by\nline");
+ QCOMPARE(config.get("true").asBool(), true);
+ QCOMPARE(config.get("untrue").asBool(), false);
+ QCOMPARE(config.get("int").asInt(), 2);
+ QCOMPARE(config.get("void").asString(), QString());
+ QVERIFY(!config.get("void").asString().isNull());
+ QCOMPARE(config.get("void").asString("undefined"), QString());
+ QCOMPARE(config.get("undefined").asString("undefined"), "undefined");
+ QVERIFY(config.get("undefined").asString().isNull());
+
+ QSet<QString> subVars = { "thing", "where", "time" };
+ QCOMPARE(config.subVars("some"), subVars);
+}
+
+// Tests whether paths or variables are resolved correctly.
+void tst_Config::paths()
+{
+ auto &config = initConfig();
+ const auto docConfig = QFINDTESTDATA("/testdata/configs/paths.qdocconf");
+ if (!docConfig.isEmpty())
+ config.load(docConfig);
+
+ auto rootDir = QFileInfo(docConfig).dir();
+ QVERIFY(rootDir.cdUp());
+
+ const auto paths = config.getCanonicalPathList("sourcedirs");
+ QVERIFY(paths.size() == 3);
+
+ QCOMPARE(paths[0], rootDir.absoluteFilePath("paths/includes"));
+ QCOMPARE(paths[1], rootDir.absoluteFilePath("configs"));
+ QCOMPARE(paths[2], rootDir.absoluteFilePath("configs/includes"));
+}
+
+// Tests whether includepaths are resolved correctly
+void tst_Config::includepaths()
+{
+ auto &config = initConfig();
+ const auto docConfig = QFINDTESTDATA("/testdata/configs/includepaths.qdocconf");
+ if (!docConfig.isEmpty())
+ config.load(docConfig);
+
+ auto rootDir = QFileInfo(docConfig).dir();
+ QVERIFY(rootDir.cdUp());
+
+ const auto paths = config.getCanonicalPathList("includepaths",
+ Config::IncludePaths);
+ QVERIFY(paths.size() == 5);
+
+ QCOMPARE(paths[0], "-I" + rootDir.absoluteFilePath("includepaths/include"));
+ QCOMPARE(paths[0], paths[1]);
+ QCOMPARE(paths[2], "-I" + rootDir.absoluteFilePath("includepaths/include/more"));
+ QCOMPARE(paths[3], "-F" + rootDir.absoluteFilePath("includepaths/include/framework"));
+ QCOMPARE(paths[4], "-isystem" + rootDir.absoluteFilePath("includepaths/include/system"));
+}
+
+void::tst_Config::getExampleProjectFile()
+{
+ auto &config = initConfig();
+ const auto docConfig = QFINDTESTDATA("/testdata/configs/exampletest.qdocconf");
+ if (!docConfig.isEmpty())
+ config.load(docConfig);
+
+ auto rootDir = QFileInfo(docConfig).dir();
+ QVERIFY(rootDir.cd("../exampletest/examples/test"));
+
+ QVERIFY(config.getExampleProjectFile("invalid").isEmpty());
+ QVERIFY(config.getExampleProjectFile("test/empty").isEmpty());
+
+ QCOMPARE(config.getExampleProjectFile("test/example1"),
+ rootDir.absoluteFilePath("example1/example1.pro"));
+ QCOMPARE(config.getExampleProjectFile("test/example2"),
+ rootDir.absoluteFilePath("example2/example2.qmlproject"));
+ QCOMPARE(config.getExampleProjectFile("test/example3"),
+ rootDir.absoluteFilePath("example3/example3.pyproject"));
+ QCOMPARE(config.getExampleProjectFile("test/example4"),
+ rootDir.absoluteFilePath("example4/CMakeLists.txt"));
+}
+
+void::tst_Config::expandVars()
+{
+ qputenv("QDOC_TSTCONFIG_LIST", QByteArray("a b c"));
+ auto &config = initConfig("/testdata/configs/expandvars.qdocconf");
+
+ QCOMPARE(config.get("expanded1").asString(), "foo");
+ QCOMPARE(config.get("expanded2").asString(), "foo,bar");
+ QCOMPARE(config.get("expanded3").asString(), "foobar foobar baz");
+ QCOMPARE(config.get("literally").asString(), "$data ${data}");
+ QCOMPARE(config.get("csvlist").asString(), "a,b,c");
+}
+
+QTEST_APPLESS_MAIN(tst_Config)
+
+#include "tst_config.moc"
diff --git a/src/qdoc/qdoc/tests/generatedoutput/CMakeLists.txt b/src/qdoc/qdoc/tests/generatedoutput/CMakeLists.txt
new file mode 100644
index 000000000..1dea3ae59
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/CMakeLists.txt
@@ -0,0 +1,46 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# It's mostly manually written.
+
+#####################################################################
+## tst_generatedOutput Test:
+#####################################################################
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_generatedOutput LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_test(tst_generatedOutput
+ SOURCES
+ ${CMAKE_CURRENT_LIST_DIR}/tst_generatedoutput.cpp
+)
+
+# Write relevant Qt include path to a file, to be read in by QDoc
+set(config_subfolder "")
+get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+if(is_multi_config)
+ set(config_subfolder "$<CONFIG>/")
+endif()
+set(includepathsfile "${CMAKE_CURRENT_BINARY_DIR}/${config_subfolder}qdocincludepaths.inc")
+set(framework_path "\n")
+
+find_package(Qt6 COMPONENTS Core REQUIRED)
+if(Qt6Core_FOUND)
+ get_target_property(include_paths Qt6::Core INTERFACE_INCLUDE_DIRECTORIES)
+endif()
+
+while(include_paths)
+ list(POP_BACK include_paths inc_path)
+ if(inc_path MATCHES "(.+)/QtCore\.framework$")
+ string(APPEND framework_path "-F${CMAKE_MATCH_1}")
+ break()
+ endif()
+endwhile()
+
+set (include_paths "$<TARGET_PROPERTY:tst_generatedOutput,INCLUDE_DIRECTORIES>")
+file(GENERATE OUTPUT ${includepathsfile} CONTENT "-I$<JOIN:${include_paths},\n-I>${framework_path}")
+
+add_dependencies(tst_generatedOutput Qt::qdoc)
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/autolinking.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/autolinking.html
new file mode 100644
index 000000000..ac04c33cd
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/autolinking.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classlists.qdoc -->
+ <title>Autolinking | TestCPP</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#testqdoc">TestQDoc</a></li>
+<li class="level1"><a href="#someprop">someProp</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Autolinking</h1>
+<!-- $$$autolinking.html-description -->
+<div class="descr" id="details">
+<h2 id="testqdoc">TestQDoc</h2>
+<p>The string <a href="testqdoc.html" translate="no">TestQDoc</a> links to the C++ namespace unless linking explicitly, <a href="autolinking.html#testqdoc">like this</a>, or <a href="testqdoc.html" translate="no">this</a>. Also,</p>
+<p>Autolinks:</p>
+<ul>
+<li><a href="testqdoc-testderived.html" translate="no">TestQDoc::TestDerived</a></li>
+</ul>
+<p>Explicit links:</p>
+<ul>
+<li><a href="testqdoc-testderived.html" translate="no">TestQDoc::TestDerived</a></li>
+<li><a href="obsolete-classes.html#testqdoc">Obsolete Classes#TestQDoc</a></li>
+</ul>
+<h2 id="someprop">someProp</h2>
+</div>
+<!-- @@@autolinking.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/cpptypes.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/cpptypes.html
new file mode 100644
index 000000000..8d91afd76
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/cpptypes.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classlists.qdoc -->
+ <title>Test C++ Types | TestCPP</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Test C++ Types</h1>
+<!-- $$$cpptypes-description -->
+<div class="descr" id="details">
+<ul>
+<li translate="no"><a href="testqdoc-test.html">TestQDoc::Test</a></li>
+<li translate="no"><a href="testqdoc-test.html#QDOCTEST_MACRO2">TestQDoc::Test::QDOCTEST_MACRO2</a></li>
+<li translate="no"><a href="testqdoc-test.html#operator-eq">TestQDoc::Test::operator=()</a></li>
+<li translate="no"><a href="testqdoc-test.html#someFunctionDefaultArg">TestQDoc::Test::someFunctionDefaultArg()</a></li>
+</ul>
+</div>
+<!-- @@@cpptypes -->
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html">TestQDoc::Test</a></p></td><td class="tblDescr"><p>A class in a namespace</p></td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmodule/all-namespaces.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmodule/all-namespaces.html
new file mode 100644
index 000000000..8d23c2aef
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmodule/all-namespaces.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- namespaces.qdoc -->
+ <title>Namespaces | CrossModule</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Namespaces</h1>
+<!-- $$$all-namespaces.html-description -->
+<div class="descr" id="details">
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="crossmoduleref.html">CrossModuleRef</a></p></td><td class="tblDescr"><p>Namespace that has documented functions in multiple modules</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc.html">TestQDoc</a></p></td><td class="tblDescr"><p>A namespace</p></td></tr>
+</table></div>
+</div>
+<!-- @@@all-namespaces.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmodule/testtype-members.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmodule/testtype-members.html
new file mode 100644
index 000000000..0cb92b933
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmodule/testtype-members.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testtype.cpp -->
+ <meta name="description" content="A class inheriting another class that lives in an external doc module.">
+ <title>List of All Members for TestType | CrossModule</title>
+</head>
+<body>
+<li>TestType</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for TestType</h1>
+<p>This is the complete list of members for <a href="testtype.html">TestType</a>, including inherited members.</p>
+<div class="table"><table class="propsummary" translate="no">
+<tr><td class="topAlign"><ul>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#DerivedType-typedef" translate="no">DerivedType</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#NotTypedef-typedef" translate="no">NotTypedef</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#SomeType-typedef" translate="no">SomeType</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#funcPtr" translate="no">funcPtr</a></b></span>(bool, const char *) : void (*)(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#inlineFunction" translate="no">inlineFunction</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEmDashInItsDocs" translate="no">methodWithEmDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testtype.html#nothing" translate="no">nothing</a></b></span>()</li>
+</ul></td><td class="topAlign"><ul>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload" translate="no">overload</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload-1" translate="no">overload</a></b></span>(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a></b></span>(int, int) : int</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a></b></span>(int, bool) const</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#someValue" translate="no">someValue</a></b></span>() : TestQDoc::TestDerived::NotTypedef</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#virtualFun" translate="no">virtualFun</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#virtualFun" translate="no">virtualFun</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#operator-eq" translate="no">operator=</a></b></span>(TestQDoc::Test &amp;&amp;) : TestQDoc::Test &amp;</li>
+</ul>
+</td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmodule/testtype.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmodule/testtype.html
new file mode 100644
index 000000000..ed8bfc96c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmodule/testtype.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testtype.cpp -->
+ <meta name="description" content="A class inheriting another class that lives in an external doc module.">
+ <title>TestType Class | CrossModule</title>
+</head>
+<body>
+<li>TestType</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#public-functions">Public Functions</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#linking">Linking</a></li>
+<li class="level2"><a href="#generated-lists">Generated Lists</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">TestType Class</h1>
+<!-- $$$TestType-brief -->
+<p>A class inheriting another class that lives in an external doc module. <a href="#details">More...</a></p>
+<!-- @@@TestType -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;TestType&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Inherits:</td><td class="memItemRight bottomAlign"> <a href="testqdoc-testderived.html" translate="no">TestQDoc::TestDerived</a></td></tr>
+</table></div>
+<ul>
+<li><a href="testtype-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="public-functions">Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testtype.html#nothing" translate="no">nothing</a></b>()</td></tr>
+</table></div>
+<!-- $$$TestType-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<h3 id="linking">Linking</h3>
+<p>These links go to the parent class:</p>
+<ul>
+<li><a href="testqdoc-testderived.html" translate="no">TestQDoc::TestDerived</a></li>
+<li><a href="testqdoc-test.html" translate="no">Test</a> class <a href="testqdoc.html#usage" translate="no">Usage</a>.</li>
+<li><a href="testqdoc.html#QDOCTEST_MACRO" translate="no">QDOCTEST_MACRO</a></li>
+<li>DontLinkToMe</li>
+</ul>
+<h3 id="generated-lists">Generated Lists</h3>
+<p>This is an annotated list of entries in a group: <div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html">TestQDoc::Test</a></p></td><td class="tblDescr"><p>A class in a namespace</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html#QDOCTEST_MACRO2">TestQDoc::Test::QDOCTEST_MACRO2</a></p></td><td class="tblDescr"><p>A macro with argument x</p></td></tr>
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html#operator-eq">TestQDoc::Test::operator=()</a></p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html#someFunctionDefaultArg">TestQDoc::Test::someFunctionDefaultArg()</a></p></td></tr>
+</table></div>
+</p>
+</div>
+<p><b>See also </b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a>().</p>
+<!-- @@@TestType -->
+<div class="func">
+<h2>Member Function Documentation</h2>
+<!-- $$$nothing[overload1]$$$nothing -->
+<h3 class="fn" translate="no" id="nothing"><span class="type">void</span> TestType::<span class="name">nothing</span>()</h3>
+<p>Nothing to see here.</p>
+<!-- @@@nothing -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmoduleref-sub-crossmodule.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmoduleref-sub-crossmodule.html
new file mode 100644
index 000000000..7d40a92d6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/crossmoduleref-sub-crossmodule.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>CrossModuleRef Namespace | CrossModule</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#functions">Functions</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">CrossModuleRef Namespace</h1>
+<p>The CrossModuleRef namespace includes the following elements from module CrossModule. The full namespace is documented in module TestCPP<a href="crossmoduleref.html" translate="no"> here.</a></p>
+<h2 id="functions">Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="crossmoduleref-sub-crossmodule.html#documentMeToo" translate="no">documentMeToo</a></b>()</td></tr>
+</table></div>
+<div class="func">
+<h2>Function Documentation</h2>
+<!-- $$$documentMeToo[overload1]$$$documentMeToo -->
+<h3 class="fn" translate="no" id="documentMeToo"><span class="type">void</span> CrossModuleRef::<span class="name">documentMeToo</span>()</h3>
+<p>Function under a namespace that's documented elsewhere.</p>
+<!-- @@@documentMeToo -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/testtype-members.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/testtype-members.html
new file mode 100644
index 000000000..0cb92b933
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/testtype-members.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testtype.cpp -->
+ <meta name="description" content="A class inheriting another class that lives in an external doc module.">
+ <title>List of All Members for TestType | CrossModule</title>
+</head>
+<body>
+<li>TestType</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for TestType</h1>
+<p>This is the complete list of members for <a href="testtype.html">TestType</a>, including inherited members.</p>
+<div class="table"><table class="propsummary" translate="no">
+<tr><td class="topAlign"><ul>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#DerivedType-typedef" translate="no">DerivedType</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#NotTypedef-typedef" translate="no">NotTypedef</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#SomeType-typedef" translate="no">SomeType</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#funcPtr" translate="no">funcPtr</a></b></span>(bool, const char *) : void (*)(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#inlineFunction" translate="no">inlineFunction</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEmDashInItsDocs" translate="no">methodWithEmDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testtype.html#nothing" translate="no">nothing</a></b></span>()</li>
+</ul></td><td class="topAlign"><ul>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload" translate="no">overload</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload-1" translate="no">overload</a></b></span>(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a></b></span>(int, int) : int</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a></b></span>(int, bool) const</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#someValue" translate="no">someValue</a></b></span>() : TestQDoc::TestDerived::NotTypedef</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#virtualFun" translate="no">virtualFun</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#virtualFun" translate="no">virtualFun</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#operator-eq" translate="no">operator=</a></b></span>(TestQDoc::Test &amp;&amp;) : TestQDoc::Test &amp;</li>
+</ul>
+</td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/testtype.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/testtype.html
new file mode 100644
index 000000000..ed8bfc96c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/crossmodule/testtype.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testtype.cpp -->
+ <meta name="description" content="A class inheriting another class that lives in an external doc module.">
+ <title>TestType Class | CrossModule</title>
+</head>
+<body>
+<li>TestType</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#public-functions">Public Functions</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#linking">Linking</a></li>
+<li class="level2"><a href="#generated-lists">Generated Lists</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">TestType Class</h1>
+<!-- $$$TestType-brief -->
+<p>A class inheriting another class that lives in an external doc module. <a href="#details">More...</a></p>
+<!-- @@@TestType -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;TestType&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Inherits:</td><td class="memItemRight bottomAlign"> <a href="testqdoc-testderived.html" translate="no">TestQDoc::TestDerived</a></td></tr>
+</table></div>
+<ul>
+<li><a href="testtype-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="public-functions">Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testtype.html#nothing" translate="no">nothing</a></b>()</td></tr>
+</table></div>
+<!-- $$$TestType-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<h3 id="linking">Linking</h3>
+<p>These links go to the parent class:</p>
+<ul>
+<li><a href="testqdoc-testderived.html" translate="no">TestQDoc::TestDerived</a></li>
+<li><a href="testqdoc-test.html" translate="no">Test</a> class <a href="testqdoc.html#usage" translate="no">Usage</a>.</li>
+<li><a href="testqdoc.html#QDOCTEST_MACRO" translate="no">QDOCTEST_MACRO</a></li>
+<li>DontLinkToMe</li>
+</ul>
+<h3 id="generated-lists">Generated Lists</h3>
+<p>This is an annotated list of entries in a group: <div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html">TestQDoc::Test</a></p></td><td class="tblDescr"><p>A class in a namespace</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html#QDOCTEST_MACRO2">TestQDoc::Test::QDOCTEST_MACRO2</a></p></td><td class="tblDescr"><p>A macro with argument x</p></td></tr>
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html#operator-eq">TestQDoc::Test::operator=()</a></p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html#someFunctionDefaultArg">TestQDoc::Test::someFunctionDefaultArg()</a></p></td></tr>
+</table></div>
+</p>
+</div>
+<p><b>See also </b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a>().</p>
+<!-- @@@TestType -->
+<div class="func">
+<h2>Member Function Documentation</h2>
+<!-- $$$nothing[overload1]$$$nothing -->
+<h3 class="fn" translate="no" id="nothing"><span class="type">void</span> TestType::<span class="name">nothing</span>()</h3>
+<p>Nothing to see here.</p>
+<!-- @@@nothing -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/includefromexampledirs/index.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/includefromexampledirs/index.html
new file mode 100644
index 000000000..0d984f60d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/includefromexampledirs/index.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- includefromparent.qdoc -->
+ <title>doc index | Test</title>
+</head>
+<body>
+<h1 class="title">doc index</h1>
+<!-- $$$index.html-description -->
+<div class="descr" id="details">
+<h2 id="c-classes">C++ Classes</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html">TestQDoc::Test</a></p></td><td class="tblDescr"><p>A class in a namespace</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-testderived.html">TestQDoc::TestDerived</a></p></td><td class="tblDescr"><p>A class in a namespace, derived from Test</p></td></tr>
+</table></div>
+<h2 id="qml-types">QML Types</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qml-qdoc-test-abstractparent.html">AbstractParent</a></p></td><td class="tblDescr"><p>Abstract base QML type</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="qml-qdoc-test-child.html">Child</a></p></td><td class="tblDescr"><p>A Child inheriting its parent</p></td></tr>
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qml-int.html">int</a></p></td><td class="tblDescr"><p>An integer value type</p></td></tr>
+</table></div>
+<p>Test include file that is part of the sourcedirs.</p>
+</div>
+<!-- @@@index.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/includefromexampledirs/qml-qdoc-test-abstractparent-members.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/includefromexampledirs/qml-qdoc-test-abstractparent-members.html
new file mode 100644
index 000000000..beb7792f7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/includefromexampledirs/qml-qdoc-test-abstractparent-members.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- includefromparent.qdoc -->
+ <meta name="description" content="Abstract base QML type.">
+ <title>List of All Members for AbstractParent | Test</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>AbstractParent</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for AbstractParent</h1>
+<p>This is the complete list of members for <a href="qml-qdoc-test-abstractparent.html">AbstractParent</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-abstractparent.html#children-prop" translate="no">children</a></b> : list&lt;Child&gt; [default]</li>
+<li class="fn" translate="no">void <b><a href="qml-qdoc-test-abstractparent.html#rear-method" translate="no">rear</a></b>(Child <i>child</i>)</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/includefromexampledirs/qml-qdoc-test-abstractparent.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/includefromexampledirs/qml-qdoc-test-abstractparent.html
new file mode 100644
index 000000000..6d6d47838
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/includefromexampledirs/qml-qdoc-test-abstractparent.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- includefromparent.qdoc -->
+ <meta name="description" content="Abstract base QML type.">
+ <title>AbstractParent QML Type | Test</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>AbstractParent</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#properties">Properties</a></li>
+<li class="level1"><a href="#methods">Methods</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">AbstractParent QML Type</h1>
+<!-- $$$AbstractParent-brief -->
+<p>Abstract base QML type. <a href="#details">More...</a></p>
+<!-- @@@AbstractParent -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import QDoc.Test 1.1</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 1.1</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Inherited By:</td><td class="memItemRight bottomAlign"> <p><a href="qml-qdoc-test-child.html" translate="no">Child</a></p>
+</td></tr></table></div><ul>
+<li><a href="qml-qdoc-test-abstractparent-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="properties">Properties</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-abstractparent.html#children-prop" translate="no">children</a></b> : list&lt;Child&gt;</li>
+</ul>
+<h2 id="methods">Methods</h2>
+<ul>
+<li class="fn" translate="no">void <b><a href="qml-qdoc-test-abstractparent.html#rear-method" translate="no">rear</a></b>(Child <i>child</i>)</li>
+</ul>
+<!-- $$$AbstractParent-description -->
+<h2 id="details">Detailed Description</h2>
+<p>Test include file that is part of the sourcedirs.</p>
+<!-- @@@AbstractParent -->
+<h2>Property Documentation</h2>
+<!-- $$$children -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="children-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">children</span> : <span class="type">list</span>&lt;<span class="type"><a href="qml-qdoc-test-child.html" translate="no">Child</a></span>&gt; <code class="details extra" translate="no">[default]</code></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Children of the type.</p>
+<p>Test include file that is part of the sourcedirs.</p>
+</div></div><!-- @@@children -->
+<br/>
+<h2>Method Documentation</h2>
+<!-- $$$rear[overload1]$$$rearChild -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="rear-method">
+<td class="tblQmlFuncNode"><p>
+<span class="type">void</span> <span class="name">rear</span>(<span class="type"><a href="qml-qdoc-test-child.html" translate="no">Child</a></span> <i>child</i>)</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Do some abstract parenting on <i translate="no">child</i>.</p>
+<p>Test include file that is part of the sourcedirs.</p>
+</div></div><!-- @@@rear -->
+<br/>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/index-linking.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/index-linking.html
new file mode 100644
index 000000000..694a3f061
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/index-linking.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- linking.qdoc -->
+ <title>Linking | IndexLinking</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#qml-properties">QML properties</a></li>
+<li class="level1"><a href="#auto-linking-to-types-in-code-snippets">Auto-linking to types in code snippets</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Linking</h1>
+<!-- $$$index-linking.html-description -->
+<div class="descr" id="details">
+<h2 id="qml-properties">QML properties</h2>
+<ol class="A" type="A"><li>Property group: <a href="https://doc.qt.io/QmlPropertyGroups/qml-qdoc-test-parent.html#group-prop" translate="no">Parent::group</a>.</li>
+<li>Property in a group: <a href="https://doc.qt.io/QmlPropertyGroups/qml-qdoc-test-parent.html#group.c-prop" translate="no">QDoc.Test::Parent::group.c</a>.</li>
+</ol>
+<h2 id="auto-linking-to-types-in-code-snippets">Auto-linking to types in code snippets</h2>
+<pre class="qml" translate="no">import QtQuick
+
+<span class="type"><a href="https://doc.qt.io/QmlPropertyGroups/qml-uicomponents-progressbar.html" translate="no">ProgressBar</a></span> {
+ <span class="comment">// Linking to int value type</span>
+ property <span class="type"><a href="https://doc.qt.io/QmlPropertyGroups/qml-int.html" translate="no">int</a></span> <span class="name">progress</span>: <span class="number">0</span>
+}</pre>
+<pre class="cpp" translate="no"><span class="comment">// 'int' should not link anywhere in C++ code</span>
+<span class="type">int</span> main() { <span class="type">int</span> x{<span class="number">0</span>}; <span class="keyword">return</span> x; }</pre>
+</div>
+<!-- @@@index-linking.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist-docbook/qdoc-test-qmlmodule.xml b/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist-docbook/qdoc-test-qmlmodule.xml
new file mode 100644
index 000000000..73a5f9653
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist-docbook/qdoc-test-qmlmodule.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>QDoc.Test QML Module</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>QML Types for the Test module.</db:para>
+<db:para><db:emphasis role="bold">This module is under development and is subject to change.</db:emphasis></db:para>
+<db:para>This module was introduced in Qt 1.1.</db:para>
+</db:abstract>
+</db:info>
+<db:para><db:emphasis role="bold">This module is under development and is subject to change.</db:emphasis></db:para>
+<db:para>This module was introduced in Qt 1.1.</db:para>
+<db:anchor xml:id="details"/>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist-docbook/test-componentset-example.xml b/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist-docbook/test-componentset-example.xml
new file mode 100644
index 000000000..609b7cec9
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist-docbook/test-componentset-example.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>QML Documentation Example</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>Example for documenting QML types.</db:para>
+</db:abstract>
+</db:info>
+<db:para>This example demonstrates one of the ways to document QML types. It also generates a warning about a missing example image, on purpose.</db:para>
+<db:para>In particular, there are sample types that are documented with QDoc commands comments. There are documentation comments for the QML types and their public interfaces. The types are grouped into a module, the <db:link xlink:href="uicomponents-qmlmodule.xml">UI Components</db:link> module.</db:para>
+<db:para>The uicomponents.qdoc file generates the overview page for the <db:link xlink:href="uicomponents-qmlmodule.xml">UI Components</db:link> module page.</db:para>
+<db:para>The generated documentation is available in the <db:link xlink:href="uicomponents-qmlmodule.xml">UI Components</db:link> module.</db:para>
+<db:section xml:id="qml-class">
+<db:title>QML Class</db:title>
+<db:para>The QML types use the \qmltype to document the type. In addition, they have the \inmodule command in order for QDoc to associate them to the <db:code>UIComponents</db:code> module.</db:para>
+<db:para>QDoc uses the \brief command to place a basic description when listing the types.</db:para>
+</db:section>
+<db:section xml:id="properties-signals-handlers-and-methods">
+<db:title>Properties, Signals, Handlers, and Methods</db:title>
+<db:para>The types have their properties, signals, handlers, and methods defined in their respective QML files. QDoc associates the properties and methods to the types, therefore, you only need to place the documentation above the property, method, or signal.</db:para>
+<db:para>To document the type of a <db:emphasis>property alias</db:emphasis>, you must use the \qmlproperty command to specify the data type.</db:para>
+<db:programlisting language="cpp">\qmlproperty int anAliasedProperty
+An aliased property of type int.
+</db:programlisting>
+<db:section xml:id="internal-documentation">
+<db:title>Internal Documentation</db:title>
+<db:para>You may declare that a documentation is for internal use by placing the \internal command after the beginning QDoc comment <db:code>/*</db:code>. QDoc will prevent the internal documentation from appearing in the public API.</db:para>
+<db:para>If you wish to omit certain parts of the documentation, you may use the \omit and \endomit command.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="qml-types-with-c-implementation">
+<db:title>QML Types with C++ Implementation</db:title>
+<db:para>This example only demonstrates the documentation for types in QML files, but the regular QML commands may be placed inside C++ classes to define the public API of the QML type.</db:para>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist-docbook/testcpp-module.xml b/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist-docbook/testcpp-module.xml
new file mode 100644
index 000000000..3217c0d94
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist-docbook/testcpp-module.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>QDoc Test C++ Classes</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test module page.</db:para>
+<db:para>This module was introduced in Qt 2.0.</db:para>
+</db:abstract>
+</db:info>
+<db:para>A test module page.</db:para>
+<db:para>This module was introduced in Qt 2.0.</db:para>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:note>
+<db:para>This is just a test. /* Look, Ma! {I'm made of arguments!} */</db:para>
+</db:note>
+<db:section xml:id="linking-to-function-like-things">
+<db:title>Linking to function-like things</db:title>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#someFunctionDefaultArg">someFunctionDefaultArg</db:link>()</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#QDOCTEST_MACRO2">QDOCTEST_MACRO2</db:link>()</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#QDOCTEST_MACRO2">QDOCTEST_MACRO2</db:link>(int &amp;x)</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testcpp-module.xml#section">section()</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testcpp-module.xml#section">section() is a section title</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#Test">open( parenthesis</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="https://en.cppreference.com/w/cpp/utility/move">C++11 added std::move(T&amp;&amp; t)</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+<db:section xml:id="section">
+<db:title>section()</db:title>
+</db:section>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist/qdoc-test-qmlmodule.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist/qdoc-test-qmlmodule.html
new file mode 100644
index 000000000..23c56acef
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist/qdoc-test-qmlmodule.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- type.cpp -->
+ <meta name="description" content="QML Types for the Test module.">
+ <title>QDoc.Test QML Module | Test</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">QDoc.Test QML Module</h1>
+<p><b>This module is under development and is subject to change.</b></p>
+<p>This module was introduced in Qt 1.1.</p>
+<!-- $$$QDoc.Test-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@QDoc.Test -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist/test-componentset-example.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist/test-componentset-example.html
new file mode 100644
index 000000000..a6d63699b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist/test-componentset-example.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- examples.qdoc -->
+ <meta name="description" content="Example for documenting QML types.">
+ <title>QML Documentation Example | Test</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#qml-class">QML Class</a></li>
+<li class="level1"><a href="#properties-signals-handlers-and-methods">Properties, Signals, Handlers, and Methods</a></li>
+<li class="level2"><a href="#internal-documentation">Internal Documentation</a></li>
+<li class="level1"><a href="#qml-types-with-c-implementation">QML Types with C++ Implementation</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">QML Documentation Example</h1>
+<!-- $$$componentset-brief -->
+<p>Example for documenting QML types.</p>
+<!-- @@@componentset -->
+<!-- $$$componentset-description -->
+<div class="descr" id="details">
+<p>This example demonstrates one of the ways to document QML types. It also generates a warning about a missing example image, on purpose.</p>
+<p>In particular, there are sample types that are documented with QDoc commands comments. There are documentation comments for the QML types and their public interfaces. The types are grouped into a module, the <a href="uicomponents-qmlmodule.html" translate="no">UI Components</a> module.</p>
+<p>The uicomponents.qdoc file generates the overview page for the <a href="uicomponents-qmlmodule.html" translate="no">UI Components</a> module page.</p>
+<p>The generated documentation is available in the <a href="uicomponents-qmlmodule.html" translate="no">UI Components</a> module.</p>
+<h4 id="qml-class">QML Class</h4>
+<p>The QML types use the \qmltype to document the type. In addition, they have the \inmodule command in order for QDoc to associate them to the <code translate="no">UIComponents</code> module.</p>
+<p>QDoc uses the \brief command to place a basic description when listing the types.</p>
+<h4 id="properties-signals-handlers-and-methods">Properties, Signals, Handlers, and Methods</h4>
+<p>The types have their properties, signals, handlers, and methods defined in their respective QML files. QDoc associates the properties and methods to the types, therefore, you only need to place the documentation above the property, method, or signal.</p>
+<p>To document the type of a <i>property alias</i>, you must use the \qmlproperty command to specify the data type.</p>
+<pre class="cpp" translate="no">\qmlproperty <span class="type">int</span> anAliasedProperty
+An aliased property of type <span class="type">int</span><span class="operator">.</span></pre>
+<h5 id="internal-documentation">Internal Documentation</h5>
+<p>You may declare that a documentation is for internal use by placing the \internal command after the beginning QDoc comment <code translate="no">/*</code>. QDoc will prevent the internal documentation from appearing in the public API.</p>
+<p>If you wish to omit certain parts of the documentation, you may use the \omit and \endomit command.</p>
+<h4 id="qml-types-with-c-implementation">QML Types with C++ Implementation</h4>
+<p>This example only demonstrates the documentation for types in QML files, but the regular QML commands may be placed inside C++ classes to define the public API of the QML type.</p>
+</div>
+<!-- @@@componentset -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist/testcpp-module.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist/testcpp-module.html
new file mode 100644
index 000000000..6b58461da
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/noautolist/testcpp-module.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A test module page.">
+ <title>QDoc Test C++ Classes | Test</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#linking-to-function-like-things">Linking to function-like things</a></li>
+<li class="level3"><a href="#section">section()</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">QDoc Test C++ Classes</h1>
+<!-- $$$TestCPP-brief -->
+<p>A test module page. <a href="#details">More...</a></p>
+<!-- @@@TestCPP -->
+<p>This module was introduced in Qt 2.0.</p>
+<!-- $$$TestCPP-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<div class="admonition note">
+<p><b>Note: </b>This is just a test. /* Look, Ma! {I'm made of arguments!} */</p>
+</div>
+<h3 id="linking-to-function-like-things">Linking to function-like things</h3>
+<ul>
+<li><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a>()</li>
+<li><a href="testqdoc-test.html#QDOCTEST_MACRO2" translate="no">QDOCTEST_MACRO2</a>()</li>
+<li><a href="testqdoc-test.html#QDOCTEST_MACRO2" translate="no">QDOCTEST_MACRO2</a>(int &amp;x)</li>
+<li><a href="testcpp-module.html#section" translate="no">section()</a></li>
+<li><a href="testcpp-module.html#section" translate="no">section() is a section title</a></li>
+<li><a href="testqdoc-test.html#Test" translate="no">open( parenthesis</a></li>
+<li><a href="https://en.cppreference.com/w/cpp/utility/move">C++11 added std::move(T&amp;&amp; t)</a></li>
+</ul>
+<h4 id="section">section()</h4>
+</div>
+<!-- @@@TestCPP -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/obsolete-classes.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/obsolete-classes.html
new file mode 100644
index 000000000..01198c82d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/obsolete-classes.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classlists.qdoc -->
+ <title>Obsolete Classes | TestCPP</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#classes-with-obsolete-members">Classes with obsolete members</a></li>
+<li class="level2"><a href="#testqdoc">TestQDoc</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Obsolete Classes</h1>
+<!-- $$$obsolete-classes.html-description -->
+<div class="descr" id="details">
+<h2 id="classes-with-obsolete-members">Classes with obsolete members</h2>
+<div class="flowListDiv" translate="no">
+<dl class="flowList odd"><dt class="alphaChar"><b>T</b></dt>
+<dd><a href="testqdoc-test-obsolete.html">Test</a> (<a href="testqdoc.html">TestQDoc</a>)</dd>
+<dd><a href="testqdoc-testderived-obsolete.html">TestDerived</a> (<a href="testqdoc.html">TestQDoc</a>)</dd>
+</dl>
+</div>
+<h3 id="testqdoc">TestQDoc</h3>
+</div>
+<!-- @@@obsolete-classes.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/qml-linkmodule-grandchild-members.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/qml-linkmodule-grandchild-members.html
new file mode 100644
index 000000000..7bec0458b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/qml-linkmodule-grandchild-members.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- linking.qdoc -->
+ <title>List of All Members for GrandChild | IndexLinking</title>
+</head>
+<body>
+<li><a href="linkmodule-qmlmodule.html" translate="no">LinkModule</a></li>
+<li>GrandChild</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for GrandChild</h1>
+<p>This is the complete list of members for <a href="qml-linkmodule-grandchild.html">GrandChild</a>, including inherited members.</p>
+<p>The following members are inherited from <a href="https://doc.qt.io/QmlPropertyGroups/qml-qdoc-test-anotherchild.html">AnotherChild</a>.</p>
+<ul>
+<li class="fn" translate="no"><b><a href="https://doc.qt.io/QmlPropertyGroups/qml-qdoc-test-anotherchild.html#name-prop" translate="no">name</a></b> : string</li>
+</ul>
+<p>The following members are inherited from <a href="https://doc.qt.io/QmlPropertyGroups/qml-qdoc-test-parent.html">Parent</a>.</p>
+<ul>
+<li class="fn" translate="no"><b><a href="https://doc.qt.io/QmlPropertyGroups/qml-qdoc-test-parent.html#group-prop" translate="no">group</a></b> : </li>
+<li class="fn" translate="no"><b><a href="https://doc.qt.io/QmlPropertyGroups/qml-qdoc-test-parent.html#group.a-prop" translate="no">group.a</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="https://doc.qt.io/QmlPropertyGroups/qml-qdoc-test-parent.html#group.b-prop" translate="no">group.b</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="https://doc.qt.io/QmlPropertyGroups/qml-qdoc-test-parent.html#group.c-prop" translate="no">group.c</a></b> : int <code class="summary extra" translate="no">(since 2.0)</code></li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/qmlpropertygroups-docbook/qml-qdoc-test-parent.xml b/src/qdoc/qdoc/tests/generatedoutput/expected_output/qmlpropertygroups-docbook/qml-qdoc-test-parent.xml
new file mode 100644
index 000000000..e223892b1
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/qmlpropertygroups-docbook/qml-qdoc-test-parent.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Parent QML Type</db:title>
+<db:productname>QmlPropertyGroups</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>Base QML type.</db:para>
+<db:para>This type was introduced in Qt 1.1.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import QDoc.Test 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since:</db:term>
+<db:listitem>
+<db:para>Qt 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Inherited By:</db:term>
+<db:listitem>
+<db:para><db:link xlink:href="qml-qdoc-test-anotherchild.xml" xlink:role="">AnotherChild</db:link></db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="property-documentation">
+<db:title>Property Documentation</db:title>
+<db:section xml:id="group-prop">
+<db:title>group group</db:title>
+<db:bridgehead renderas="sect2" xml:id="group.a-prop">group.a : int</db:bridgehead>
+<db:bridgehead renderas="sect2" xml:id="group.b-prop">[read-only] group.b : int</db:bridgehead>
+<db:bridgehead renderas="sect2" xml:id="group.c-prop">[since 2.0] group.c : int</db:bridgehead>
+<db:para>Property group.</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/qmlpropertygroups/qml-qdoc-test-anotherchild-members.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/qmlpropertygroups/qml-qdoc-test-anotherchild-members.html
new file mode 100644
index 000000000..a52f5e194
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/qmlpropertygroups/qml-qdoc-test-anotherchild-members.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- parent.qdoc -->
+ <meta name="description" content="Just another child inheriting a parent.">
+ <title>List of All Members for AnotherChild | QmlPropertyGroups</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>AnotherChild</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for AnotherChild</h1>
+<p>This is the complete list of members for <a href="qml-qdoc-test-anotherchild.html">AnotherChild</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-anotherchild.html#name-prop" translate="no">name</a></b> : string</li>
+</ul>
+<p>The following members are inherited from <a href="qml-qdoc-test-parent.html">Parent</a>.</p>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-parent.html#group-prop" translate="no">group</a></b><ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-parent.html#group.a-prop" translate="no">group.a</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-parent.html#group.b-prop" translate="no">group.b</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-parent.html#group.c-prop" translate="no">group.c</a></b> : int <code class="summary extra" translate="no">(since 2.0)</code></li>
+</ul>
+</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/qmlpropertygroups/qml-qdoc-test-parent.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/qmlpropertygroups/qml-qdoc-test-parent.html
new file mode 100644
index 000000000..200dd572a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/qmlpropertygroups/qml-qdoc-test-parent.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- parent.qdoc -->
+ <meta name="description" content="Base QML type.">
+ <title>Parent QML Type | QmlPropertyGroups</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>Parent</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#properties">Properties</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Parent QML Type</h1>
+<!-- $$$Parent-brief -->
+<p>Base QML type. <a href="#details">More...</a></p>
+<!-- @@@Parent -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import QDoc.Test 1.1</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 1.1</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Inherited By:</td><td class="memItemRight bottomAlign"> <p><a href="qml-qdoc-test-anotherchild.html" translate="no">AnotherChild</a></p>
+</td></tr></table></div><ul>
+<li><a href="qml-qdoc-test-parent-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="properties">Properties</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-parent.html#group-prop" translate="no">group</a></b><ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-parent.html#group.a-prop" translate="no">group.a</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-parent.html#group.b-prop" translate="no">group.b</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-parent.html#group.c-prop" translate="no">group.c</a></b> : int <code class="summary extra" translate="no">(since 2.0)</code></li>
+</ul>
+</li>
+</ul>
+<!-- $$$Parent-description -->
+<h2 id="details">Detailed Description</h2>
+<!-- @@@Parent -->
+<h2>Property Documentation</h2>
+<!-- $$$group -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="even" id="group-prop"><th class="centerAlign"><p><b>group group</b></p></th></tr>
+<tr valign="top" class="odd" id="group.a-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">group.a</span> : <span class="type"><a href="qml-int.html" translate="no">int</a></span></p></td></tr>
+<tr valign="top" class="odd" id="group.b-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">group.b</span> : <span class="type"><a href="qml-int.html" translate="no">int</a></span> <code class="details extra" translate="no">[read-only]</code></p></td></tr>
+<tr valign="top" class="odd" id="group.c-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">group.c</span> : <span class="type"><a href="qml-int.html" translate="no">int</a></span> <code class="details extra" translate="no">[since 2.0]</code></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Property group.</p>
+</div></div><!-- @@@group -->
+<br/>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp-module.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp-module.html
new file mode 100644
index 000000000..83ab5b175
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp-module.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A test module page.">
+ <title>QDoc Test C++ Classes | TestCPP</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#namespaces">Namespaces</a></li>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#linking-to-function-like-things">Linking to function-like things</a></li>
+<li class="level3"><a href="#section">section()</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">QDoc Test C++ Classes</h1>
+<!-- $$$TestCPP-brief -->
+<p>A test module page. <a href="#details">More...</a></p>
+<!-- @@@TestCPP -->
+<p>This module was introduced in Qt 2.0.</p>
+<h2 id="namespaces">Namespaces</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="crossmoduleref.html">CrossModuleRef</a></p></td><td class="tblDescr"><p>Namespace that has documented functions in multiple modules</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc.html">TestQDoc</a></p></td><td class="tblDescr"><p>A namespace</p></td></tr>
+</table></div>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html">TestQDoc::Test</a></p></td><td class="tblDescr"><p>A class in a namespace</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-testderived.html">TestQDoc::TestDerived</a></p></td><td class="tblDescr"><p>A class in a namespace, derived from Test</p></td></tr>
+</table></div>
+<!-- $$$TestCPP-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<div class="admonition note">
+<p><b>Note: </b>This is just a test. /* Look, Ma! {I'm made of arguments!} */</p>
+</div>
+<h3 id="linking-to-function-like-things">Linking to function-like things</h3>
+<ul>
+<li><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a>()</li>
+<li><a href="testqdoc-test.html#QDOCTEST_MACRO2" translate="no">QDOCTEST_MACRO2</a>()</li>
+<li><a href="testqdoc-test.html#QDOCTEST_MACRO2" translate="no">QDOCTEST_MACRO2</a>(int &amp;x)</li>
+<li><a href="testcpp-module.html#section" translate="no">section()</a></li>
+<li><a href="testcpp-module.html#section" translate="no">section() is a section title</a></li>
+<li><a href="testqdoc-test.html#Test" translate="no">open( parenthesis</a></li>
+<li><a href="https://en.cppreference.com/w/cpp/utility/move">C++11 added std::move(T&amp;&amp; t)</a></li>
+</ul>
+<h4 id="section">section()</h4>
+</div>
+<!-- @@@TestCPP -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp.index b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp.index
new file mode 100644
index 000000000..9dcb763a8
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp.index
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="TestCPP Reference Documentation" version="" project="TestCPP">
+ <namespace name="" status="active" access="public" module="testcpp">
+ <function name="QDOCTEST_MACRO" href="testqdoc.html#QDOCTEST_MACRO" status="active" access="public" documented="true" related="0" meta="macrowithoutparams" signature="QDOCTEST_MACRO"/>
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ </function>
+ <page name="autolinking.html" href="autolinking.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Autolinking" fulltitle="Autolinking" subtitle="">
+ <contents name="testqdoc" title="TestQDoc" level="1"/>
+ <contents name="someprop" title="someProp" level="1"/>
+ </page>
+ <namespace name="CrossModuleRef" href="crossmoduleref.html" status="active" access="public" location="testcpp.h" since="3.0" documented="true" module="TestCPP" brief="Namespace that has documented functions in multiple modules">
+ <function name="documentMe" fullname="CrossModuleRef::documentMe" href="crossmoduleref.html#documentMe" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void documentMe()"/>
+ </namespace>
+ <class name="DontLinkToMe" href="dontlinktome.html" status="ignored" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="Class that does not generate documentation"/>
+ <page name="obsolete-classes.html" href="obsolete-classes.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Obsolete Classes" fulltitle="Obsolete Classes" subtitle="">
+ <contents name="classes-with-obsolete-members" title="Classes with obsolete members" level="1"/>
+ <contents name="testqdoc" title="TestQDoc" level="2"/>
+ </page>
+ <namespace name="TestQDoc" href="testqdoc.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="A namespace">
+ <contents name="usage" title="Usage" level="1"/>
+ <function name="QDOCTEST_MACRO" href="testqdoc.html#QDOCTEST_MACRO" status="active" access="public" documented="true" related="0" meta="macrowithoutparams" signature="QDOCTEST_MACRO"/>
+ <class threadsafety="reentrant" name="Test" fullname="TestQDoc::Test" href="testqdoc-test.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" groups="cpptypes,testgroup" module="TestCPP" brief="A class in a namespace">
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ </function>
+ <function name="Test" fullname="TestQDoc::Test::Test" href="testqdoc-test.html#Test" status="active" access="public" documented="true" meta="constructor" noexcept="true" signature="Test()"/>
+ <function name="anotherObsoleteMember" fullname="TestQDoc::Test::anotherObsoleteMember" href="testqdoc-test-obsolete.html#anotherObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void anotherObsoleteMember()"/>
+ <function name="deprecatedMember" fullname="TestQDoc::Test::deprecatedMember" href="testqdoc-test-obsolete.html#deprecatedMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void deprecatedMember()"/>
+ <function name="funcPtr" fullname="TestQDoc::Test::funcPtr" href="testqdoc-test.html#funcPtr" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void (*)(bool)" signature="void (*)(bool) funcPtr(bool b, const char *s)">
+ <parameter type="bool" name="b" default=""/>
+ <parameter type="const char *" name="s" default=""/>
+ </function>
+ <function name="inlineFunction" fullname="TestQDoc::Test::inlineFunction" href="testqdoc-test.html#inlineFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" brief="An inline function, documented using the \fn QDoc command" signature="void inlineFunction()"/>
+ <function name="methodWithEmDashInItsDocs" fullname="TestQDoc::Test::methodWithEmDashInItsDocs" href="testqdoc-test.html#methodWithEmDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEmDashInItsDocs()"/>
+ <function name="methodWithEnDashInItsDocs" fullname="TestQDoc::Test::methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEnDashInItsDocs()"/>
+ <function name="obsoleteMember" fullname="TestQDoc::Test::obsoleteMember" href="testqdoc-test-obsolete.html#obsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void obsoleteMember()"/>
+ <function name="operator++" fullname="TestQDoc::Test::operator++" href="testqdoc-test-obsolete.html#operator-2b-2b" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator++()"/>
+ <function name="operator--" fullname="TestQDoc::Test::operator--" href="testqdoc-test-obsolete.html#operator--" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator--()"/>
+ <function name="operator=" fullname="TestQDoc::Test::operator=" href="testqdoc-test.html#operator-eq" status="active" access="public" documented="true" meta="move-assign" noexcept="true" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator=(TestQDoc::Test &amp;&amp;other)" groups="testgroup">
+ <parameter type="TestQDoc::Test &amp;&amp;" name="other" default=""/>
+ </function>
+ <function name="operator==" href="testqdoc-test.html#operator-eq-eq" status="active" access="public" location="testcpp.h" documented="true" related="2" meta="plain" type="bool" signature="bool operator==(const TestQDoc::Test &amp;lhs, const TestQDoc::Test &amp;rhs)">
+ <parameter type="const TestQDoc::Test &amp;" name="lhs" default=""/>
+ <parameter type="const TestQDoc::Test &amp;" name="rhs" default=""/>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload" status="active" access="protected" location="testcpp.h" documented="true" meta="plain" type="void" signature="void overload()"/>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload-1" status="active" access="protected" location="testcpp.h" documented="true" since="Test 1.2" meta="plain" overload="true" overload-number="1" type="void" signature="void overload(bool b)">
+ <parameter type="bool" name="b" default=""/>
+ </function>
+ <function name="someFunction" fullname="TestQDoc::Test::someFunction" href="testqdoc-test.html#someFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="int" signature="int someFunction(int, int v)">
+ <parameter type="int" name="" default=""/>
+ <parameter type="int" name="v" default="0"/>
+ </function>
+ <function name="someFunctionDefaultArg" fullname="TestQDoc::Test::someFunctionDefaultArg" href="testqdoc-test.html#someFunctionDefaultArg" threadsafety="non-reentrant" status="active" access="public" location="testcpp.h" documented="true" meta="plain" const="true" type="void" signature="void someFunctionDefaultArg(int i, bool b) const" groups="testgroup">
+ <parameter type="int" name="i" default=""/>
+ <parameter type="bool" name="b" default="false"/>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::Test::virtualFun" href="testqdoc-test.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" type="void" signature="void virtualFun()"/>
+ <typedef name="SomeType" fullname="TestQDoc::Test::SomeType" href="testqdoc-test.html#SomeType-typedef" status="active" access="public" location="testcpp.h" documented="true"/>
+ </class>
+ <class name="TestDerived" fullname="TestQDoc::TestDerived" href="testqdoc-testderived.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" bases="TestQDoc::Test" module="TestCPP" brief="A class in a namespace, derived from Test">
+ <function name="someValue" fullname="TestQDoc::TestDerived::someValue" href="testqdoc-testderived.html#someValue" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::TestDerived::NotTypedef" signature="TestQDoc::TestDerived::NotTypedef someValue()"/>
+ <function name="staticObsoleteMember" fullname="TestQDoc::TestDerived::staticObsoleteMember" href="testqdoc-testderived-obsolete.html#staticObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" static="true" type="void" signature="void staticObsoleteMember()"/>
+ <function name="virtualFun" fullname="TestQDoc::TestDerived::virtualFun" href="testqdoc-testderived.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" override="true" type="void" signature="void virtualFun() override"/>
+ <typedef name="DerivedType" fullname="TestQDoc::TestDerived::DerivedType" href="testqdoc-testderived.html#DerivedType-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="Test::SomeType"/>
+ <typedef name="NotTypedef" fullname="TestQDoc::TestDerived::NotTypedef" href="testqdoc-testderived.html#NotTypedef-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="int"/>
+ </class>
+ </namespace>
+ <page name="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command" href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command" status="active" location="classlists.qdoc" documented="true" subtype="externalpage" title="reentrant" fulltitle="reentrant" subtitle=""/>
+ <group name="cpptypes" href="cpptypes.html" status="active" location="classlists.qdoc" documented="true" seen="true" title="Test C++ Types"/>
+ <group name="testgroup" href="testgroup.html" status="internal" seen="false" title=""/>
+ <module name="TestCPP" href="testcpp-module.html" status="active" since="2.0" documented="true" seen="true" title="QDoc Test C++ Classes" brief="A test module page">
+ <contents name="linking-to-function-like-things" title="Linking to function-like things" level="1"/>
+ <contents name="section" title="section()" level="2"/>
+ </module>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/crossmoduleref.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/crossmoduleref.html
new file mode 100644
index 000000000..2d073e237
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/crossmoduleref.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="Namespace that has documented functions in multiple modules.">
+ <title>CrossModuleRef Namespace | TestCPP</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#functions">Functions</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">CrossModuleRef Namespace</h1>
+<!-- $$$CrossModuleRef-brief -->
+<p>Namespace that has documented functions in multiple modules. <a href="#details">More...</a></p>
+<!-- @@@CrossModuleRef -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;CrossModuleRef&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 3.0</td></tr>
+</table></div>
+<h2 id="functions">Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="crossmoduleref.html#documentMe" translate="no">documentMe</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="crossmoduleref-sub-crossmodule.html#documentMeToo" translate="no">documentMeToo</a></b>()</td></tr>
+</table></div>
+<!-- $$$CrossModuleRef-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@CrossModuleRef -->
+<div class="func">
+<h2>Function Documentation</h2>
+<!-- $$$documentMe[overload1]$$$documentMe -->
+<h3 class="fn" translate="no" id="documentMe"><span class="type">void</span> CrossModuleRef::<span class="name">documentMe</span>()</h3>
+<p>Document me!</p>
+<!-- @@@documentMe -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testcpp-module.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testcpp-module.html
new file mode 100644
index 000000000..83ab5b175
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testcpp-module.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A test module page.">
+ <title>QDoc Test C++ Classes | TestCPP</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#namespaces">Namespaces</a></li>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#linking-to-function-like-things">Linking to function-like things</a></li>
+<li class="level3"><a href="#section">section()</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">QDoc Test C++ Classes</h1>
+<!-- $$$TestCPP-brief -->
+<p>A test module page. <a href="#details">More...</a></p>
+<!-- @@@TestCPP -->
+<p>This module was introduced in Qt 2.0.</p>
+<h2 id="namespaces">Namespaces</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="crossmoduleref.html">CrossModuleRef</a></p></td><td class="tblDescr"><p>Namespace that has documented functions in multiple modules</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc.html">TestQDoc</a></p></td><td class="tblDescr"><p>A namespace</p></td></tr>
+</table></div>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html">TestQDoc::Test</a></p></td><td class="tblDescr"><p>A class in a namespace</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-testderived.html">TestQDoc::TestDerived</a></p></td><td class="tblDescr"><p>A class in a namespace, derived from Test</p></td></tr>
+</table></div>
+<!-- $$$TestCPP-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<div class="admonition note">
+<p><b>Note: </b>This is just a test. /* Look, Ma! {I'm made of arguments!} */</p>
+</div>
+<h3 id="linking-to-function-like-things">Linking to function-like things</h3>
+<ul>
+<li><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a>()</li>
+<li><a href="testqdoc-test.html#QDOCTEST_MACRO2" translate="no">QDOCTEST_MACRO2</a>()</li>
+<li><a href="testqdoc-test.html#QDOCTEST_MACRO2" translate="no">QDOCTEST_MACRO2</a>(int &amp;x)</li>
+<li><a href="testcpp-module.html#section" translate="no">section()</a></li>
+<li><a href="testcpp-module.html#section" translate="no">section() is a section title</a></li>
+<li><a href="testqdoc-test.html#Test" translate="no">open( parenthesis</a></li>
+<li><a href="https://en.cppreference.com/w/cpp/utility/move">C++11 added std::move(T&amp;&amp; t)</a></li>
+</ul>
+<h4 id="section">section()</h4>
+</div>
+<!-- @@@TestCPP -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testqdoc-test-members.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testqdoc-test-members.html
new file mode 100644
index 000000000..6749f2332
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testqdoc-test-members.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace.">
+ <title>List of All Members for Test | TestCPP</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Test</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for Test</h1>
+<p>This is the complete list of members for <a href="testqdoc-test.html">TestQDoc::Test</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#SomeType-typedef" translate="no">SomeType</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#Test" translate="no">Test</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#funcPtr" translate="no">funcPtr</a></b></span>(bool, const char *) : void (*)(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#inlineFunction" translate="no">inlineFunction</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEmDashInItsDocs" translate="no">methodWithEmDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload" translate="no">overload</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload-1" translate="no">overload</a></b></span>(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a></b></span>(int, int) : int</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a></b></span>(int, bool) const</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#virtualFun" translate="no">virtualFun</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#operator-eq" translate="no">operator=</a></b></span>(TestQDoc::Test &amp;&amp;) : TestQDoc::Test &amp;</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testqdoc-test.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testqdoc-test.html
new file mode 100644
index 000000000..028339088
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testqdoc-test.html
@@ -0,0 +1,162 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace.">
+ <title>Test Class | TestCPP</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Test</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#public-types">Public Types</a></li>
+<li class="level1"><a href="#public-functions">Public Functions</a></li>
+<li class="level1"><a href="#protected-functions">Protected Functions</a></li>
+<li class="level1"><a href="#related-non-members">Related Non-Members</a></li>
+<li class="level1"><a href="#macros">Macros</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Test Class</h1>
+<span class="small-subtitle" translate="no">class <a href="testqdoc.html" translate="no">TestQDoc</a>::Test</span>
+<!-- $$$Test-brief -->
+<p>A class in a namespace. <a href="#details">More...</a></p>
+<!-- @@@Test -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Test&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Inherited By:</td><td class="memItemRight bottomAlign"> <p><a href="testqdoc-testderived.html" translate="no">TestQDoc::TestDerived</a></p>
+</td></tr>
+</table></div>
+<ul>
+<li><a href="testqdoc-test-members.html">List of all members, including inherited members</a></li>
+<li><a href="testqdoc-test-obsolete.html">Deprecated members</a></li>
+<li>Test is part of <a href="cpptypes.html">Test C++ Types</a>.</li>
+</ul>
+<p><b>Note:</b> All functions in this class are <a href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</a> with the following exceptions:</p>
+<ul>
+<li><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a>(int i, bool b) const</li>
+</ul>
+<h2 id="public-types">Public Types</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#SomeType-typedef" translate="no">SomeType</a></b></td></tr>
+</table></div>
+<h2 id="public-functions">Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#Test" translate="no">Test</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void (*)(bool) </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#funcPtr" translate="no">funcPtr</a></b>(bool <i>b</i>, const char *<i>s</i>)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#inlineFunction" translate="no">inlineFunction</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#methodWithEmDashInItsDocs" translate="no">methodWithEmDashInItsDocs</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> int </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a></b>(int, int <i>v</i> = 0)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a></b>(int <i>i</i>, bool <i>b</i> = false) const</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> virtual void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#virtualFun" translate="no">virtualFun</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> TestQDoc::Test &amp;</td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#operator-eq" translate="no">operator=</a></b>(TestQDoc::Test &amp;&amp;<i>other</i>)</td></tr>
+</table></div>
+<h2 id="protected-functions">Protected Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#overload" translate="no">overload</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since Test 1.2)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#overload-1" translate="no">overload</a></b>(bool <i>b</i>)</td></tr>
+</table></div>
+<h2 id="related-non-members">Related Non-Members</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> bool </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#operator-eq-eq" translate="no">operator==</a></b>(const TestQDoc::Test &amp;<i>lhs</i>, const TestQDoc::Test &amp;<i>rhs</i>)</td></tr>
+</table></div>
+<h2 id="macros">Macros</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since Test 1.1)</code> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#QDOCTEST_MACRO2" translate="no">QDOCTEST_MACRO2</a></b>(int &amp;<i>x</i>)</td></tr>
+</table></div>
+<!-- $$$Test-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Test -->
+<div class="types">
+<h2>Member Type Documentation</h2>
+<!-- $$$SomeType -->
+<h3 class="fn" translate="no" id="SomeType-typedef">Test::<span class="name">SomeType</span></h3>
+<p>A typedef.</p>
+<!-- @@@SomeType -->
+</div>
+<div class="func">
+<h2>Member Function Documentation</h2>
+<!-- $$$ -->
+<div class="fngroup">
+<h3 class="fn fngroupitem" translate="no" id="overload"><code class="details extra" translate="no">[protected]</code> <span class="type">void</span> Test::<span class="name">overload</span>()</h3><h3 class="fn fngroupitem" translate="no" id="overload-1"><code class="details extra" translate="no">[protected, since Test 1.2]</code> <span class="type">void</span> Test::<span class="name">overload</span>(<span class="type">bool</span> <i>b</i>)</h3></div>
+<p>Overloads that share a documentation comment, optionally taking a parameter <i translate="no">b</i>.</p>
+<!-- @@@ -->
+<!-- $$$Test[overload1]$$$Test -->
+<h3 class="fn" translate="no" id="Test"><code class="details extra" translate="no">[noexcept default]</code> Test::<span class="name">Test</span>()</h3>
+<p>Default constructor.</p>
+<!-- @@@Test -->
+<!-- $$$funcPtr[overload1]$$$funcPtrboolconstchar* -->
+<h3 class="fn" translate="no" id="funcPtr"><span class="type">void</span> (*)(<span class="type">bool</span>) Test::<span class="name">funcPtr</span>(<span class="type">bool</span> <i>b</i>, const <span class="type">char</span> *<i>s</i>)</h3>
+<p>Returns a pointer to a function that takes a boolean. Uses <i translate="no">b</i> and <i translate="no">s</i>.</p>
+<!-- @@@funcPtr -->
+<!-- $$$inlineFunction[overload1]$$$inlineFunction -->
+<h3 class="fn" translate="no" id="inlineFunction"><span class="type">void</span> Test::<span class="name">inlineFunction</span>()</h3>
+<p>An inline function, documented using the \fn QDoc command.</p>
+<!-- @@@inlineFunction -->
+<!-- $$$methodWithEmDashInItsDocs[overload1]$$$methodWithEmDashInItsDocs -->
+<h3 class="fn" translate="no" id="methodWithEmDashInItsDocs"><span class="type">void</span> Test::<span class="name">methodWithEmDashInItsDocs</span>()</h3>
+<p>This method has em dashes in its documentation&mdash;as you'll find represented by <code translate="no">---</code> in the sources&mdash;here and there. The important bit to note is that when passed e.g. to the \c command, the three hyphens are processed as input to the command and not replaced by an em dash.</p>
+<p>-----------------------------------------------------------------------</p>
+<p>People can still add a bunch of dashes, though, without QDoc replacing them all with a series of em dashes.</p>
+<p>&mdash;You can also start a new paragraph with an em dash, if you want to.</p>
+<p><b>See also </b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a>.</p>
+<!-- @@@methodWithEmDashInItsDocs -->
+<!-- $$$methodWithEnDashInItsDocs[overload1]$$$methodWithEnDashInItsDocs -->
+<h3 class="fn" translate="no" id="methodWithEnDashInItsDocs"><span class="type">void</span> Test::<span class="name">methodWithEnDashInItsDocs</span>()</h3>
+<p>This method has en dashes in its documentation &ndash; as you'll find represented by <code translate="no">--</code> in the sources &ndash; here and there. The important bit to note is that when passed e.g. to the \c command, the two hyphens are processed as input to the command and not replaced by an en dash. This also applies to code blocks, where otherwise, the decrement operator would get completely borked:</p>
+<pre class="cpp" translate="no"><span class="keyword">for</span> (<span class="type">int</span> i <span class="operator">=</span> <span class="number">42</span>; i <span class="operator">&gt;</span> <span class="number">0</span>; <span class="operator">-</span><span class="operator">-</span>i)
+ <span class="comment">// Do something cool during countdown.</span></pre>
+<p>...as it would be silly if this would output &ndash;i instead of <code translate="no">--i</code>.</p>
+<p>-----------------------------------------------------------------------</p>
+<p>It still allows people to add a bunch of dashes, though, without replacing them all with a series of en dashes. Of course, they might want to use the \hr command instead, like this:</p>
+<hr />
+<p>&ndash; You can also start a new paragraph with an en dash, if you want to.</p>
+<p><b>See also </b>methodWithEnDashInItsDocs.</p>
+<!-- @@@methodWithEnDashInItsDocs -->
+<!-- $$$someFunction[overload1]$$$someFunctionintint -->
+<h3 class="fn" translate="no" id="someFunction"><span class="type">int</span> Test::<span class="name">someFunction</span>(<span class="type">int</span>, <span class="type">int</span> <i>v</i> = 0)</h3>
+<p>Function that takes a parameter <i translate="no">v</i>. Also returns the value of <i translate="no">v</i>.</p>
+<!-- @@@someFunction -->
+<!-- $$$someFunctionDefaultArg[overload1]$$$someFunctionDefaultArgintbool -->
+<h3 class="fn" translate="no" id="someFunctionDefaultArg"><span class="type">void</span> Test::<span class="name">someFunctionDefaultArg</span>(<span class="type">int</span> <i>i</i>, <span class="type">bool</span> <i>b</i> = false) const</h3>
+<p>Function that takes a parameter <i translate="no">i</i> and <i translate="no">b</i>.</p>
+<p><b>Warning:</b> This function is not <a href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</a>.</p>
+<!-- @@@someFunctionDefaultArg -->
+<!-- $$$virtualFun[overload1]$$$virtualFun -->
+<h3 class="fn" translate="no" id="virtualFun"><code class="details extra" translate="no">[virtual]</code> <span class="type">void</span> Test::<span class="name">virtualFun</span>()</h3>
+<p>Function that must be reimplemented.</p>
+<!-- @@@virtualFun -->
+<!-- $$$operator=[overload1]$$$operator=TestQDoc::Test&& -->
+<h3 class="fn" translate="no" id="operator-eq"><code class="details extra" translate="no">[noexcept default]</code> <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;Test::<span class="name">operator=</span>(<span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;&amp;<i>other</i>)</h3>
+<p>Move-assigns <i translate="no">other</i>.</p>
+<!-- @@@operator= -->
+</div>
+<div class="relnonmem">
+<h2>Related Non-Members</h2>
+<!-- $$$operator==[overload1]$$$operator==constTestQDoc::Test&constTestQDoc::Test& -->
+<h3 class="fn" translate="no" id="operator-eq-eq"><span class="type">bool</span> <span class="name">operator==</span>(const <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;<i>lhs</i>, const <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;<i>rhs</i>)</h3>
+<p>Returns true if <i translate="no">lhs</i> and <i translate="no">rhs</i> are equal.</p>
+<!-- @@@operator== -->
+</div>
+<div class="macros">
+<h2>Macro Documentation</h2>
+<!-- $$$QDOCTEST_MACRO2[overload1]$$$QDOCTEST_MACRO2int& -->
+<h3 class="fn" translate="no" id="QDOCTEST_MACRO2"><code class="details extra" translate="no">[since Test 1.1]</code> <span class="name">QDOCTEST_MACRO2</span>(<span class="type">int</span> &amp;<i>x</i>)</h3>
+<p>A macro with argument <i translate="no">x</i>.</p>
+<p>This macro was introduced in Test 1.1.</p>
+<!-- @@@QDOCTEST_MACRO2 -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testqdoc.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testqdoc.html
new file mode 100644
index 000000000..425c7b396
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testcpp/testqdoc.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A namespace.">
+ <title>TestQDoc Namespace | TestCPP</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#macros">Macros</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#usage">Usage</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">TestQDoc Namespace</h1>
+<!-- $$$TestQDoc-brief -->
+<p>A namespace. <a href="#details">More...</a></p>
+<!-- @@@TestQDoc -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;TestCPP&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+</table></div>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since 2.0)</code> class </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html" translate="no">Test</a></b></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since 2.0)</code> class </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html" translate="no">TestDerived</a></b></td></tr>
+</table></div>
+<h2 id="macros">Macros</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc.html#QDOCTEST_MACRO" translate="no">QDOCTEST_MACRO</a></b></td></tr>
+</table></div>
+<!-- $$$TestQDoc-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<h3 id="usage">Usage</h3>
+<p>This namespace is for testing QDoc output.</p>
+</div>
+<!-- @@@TestQDoc -->
+<div class="classes">
+<h2>Classes</h2>
+<h3> class <a href="testqdoc-test.html">Test</a></h3><!-- $$$Test-brief -->
+<p>A class in a namespace. <a href="testqdoc-test.html#details">More...</a></p>
+<!-- @@@Test -->
+<h3> class <a href="testqdoc-testderived.html">TestDerived</a></h3><!-- $$$TestDerived-brief -->
+<p>A class in a namespace, derived from <a href="testqdoc-test.html" translate="no">Test</a>. <a href="testqdoc-testderived.html#details">More...</a></p>
+<!-- @@@TestDerived -->
+</div>
+<div class="macros">
+<h2>Macro Documentation</h2>
+<!-- $$$QDOCTEST_MACRO[overload1]$$$QDOCTEST_MACRO -->
+<h3 class="fn" translate="no" id="QDOCTEST_MACRO"><span class="name">QDOCTEST_MACRO</span></h3>
+<!-- @@@QDOCTEST_MACRO -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-test-members.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-test-members.html
new file mode 100644
index 000000000..6749f2332
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-test-members.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace.">
+ <title>List of All Members for Test | TestCPP</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Test</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for Test</h1>
+<p>This is the complete list of members for <a href="testqdoc-test.html">TestQDoc::Test</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#SomeType-typedef" translate="no">SomeType</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#Test" translate="no">Test</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#funcPtr" translate="no">funcPtr</a></b></span>(bool, const char *) : void (*)(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#inlineFunction" translate="no">inlineFunction</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEmDashInItsDocs" translate="no">methodWithEmDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload" translate="no">overload</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload-1" translate="no">overload</a></b></span>(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a></b></span>(int, int) : int</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a></b></span>(int, bool) const</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#virtualFun" translate="no">virtualFun</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#operator-eq" translate="no">operator=</a></b></span>(TestQDoc::Test &amp;&amp;) : TestQDoc::Test &amp;</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-test-obsolete.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-test-obsolete.html
new file mode 100644
index 000000000..2b5192da1
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-test-obsolete.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace.">
+ <title>Obsolete Members for Test | TestCPP</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Test</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Obsolete Members for Test</h1>
+<p><b>The following members of class <a href="testqdoc-test.html" translate="no">Test</a> are deprecated.</b> They are provided to keep old source code working. We strongly advise against using them in new code.</p>
+<h2>Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#anotherObsoleteMember" translate="no">anotherObsoleteMember</a></b>()</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated in 6.0)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#deprecatedMember" translate="no">deprecatedMember</a></b>()</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#obsoleteMember" translate="no">obsoleteMember</a></b>()</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> TestQDoc::Test &amp;</td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#operator-2b-2b" translate="no">operator++</a></b>()</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> TestQDoc::Test &amp;</td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#operator--" translate="no">operator--</a></b>()</td></tr>
+</table></div>
+<h2>Member Function Documentation</h2>
+<!-- $$$ -->
+<div class="fngroup">
+<h3 class="fn fngroupitem" translate="no" id="operator-2b-2b"><code class="details extra" translate="no">[deprecated]</code> <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;Test::<span class="name">operator++</span>()</h3><h3 class="fn fngroupitem" translate="no" id="operator--"><code class="details extra" translate="no">[deprecated]</code> <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;Test::<span class="name">operator--</span>()</h3></div>
+<p>This function is deprecated. We strongly advise against using it in new code.</p>
+<!-- @@@ -->
+<!-- $$$anotherObsoleteMember[overload1]$$$anotherObsoleteMember -->
+<h3 class="fn" translate="no" id="anotherObsoleteMember"><code class="details extra" translate="no">[deprecated]</code> <span class="type">void</span> Test::<span class="name">anotherObsoleteMember</span>()</h3>
+<p>This function is deprecated. We strongly advise against using it in new code.</p>
+<p>Use <a href="testqdoc-test-obsolete.html#obsoleteMember" translate="no">obsoleteMember</a>() instead.</p>
+<!-- @@@anotherObsoleteMember -->
+<!-- $$$deprecatedMember[overload1]$$$deprecatedMember -->
+<h3 class="fn" translate="no" id="deprecatedMember"><code class="details extra" translate="no">[deprecated in 6.0]</code> <span class="type">void</span> Test::<span class="name">deprecatedMember</span>()</h3>
+<p>This function is deprecated since 6.0. We strongly advise against using it in new code.</p>
+<p>Use <a href="testqdoc-test.html#someFunction" translate="no">someFunction</a>() instead.</p>
+<!-- @@@deprecatedMember -->
+<!-- $$$obsoleteMember[overload1]$$$obsoleteMember -->
+<h3 class="fn" translate="no" id="obsoleteMember"><code class="details extra" translate="no">[deprecated]</code> <span class="type">void</span> Test::<span class="name">obsoleteMember</span>()</h3>
+<p>This function is deprecated. We strongly advise against using it in new code.</p>
+<p>Use <a href="testqdoc-test.html#someFunction" translate="no">someFunction</a>() instead.</p>
+<!-- @@@obsoleteMember -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-test.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-test.html
new file mode 100644
index 000000000..028339088
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-test.html
@@ -0,0 +1,162 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace.">
+ <title>Test Class | TestCPP</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Test</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#public-types">Public Types</a></li>
+<li class="level1"><a href="#public-functions">Public Functions</a></li>
+<li class="level1"><a href="#protected-functions">Protected Functions</a></li>
+<li class="level1"><a href="#related-non-members">Related Non-Members</a></li>
+<li class="level1"><a href="#macros">Macros</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Test Class</h1>
+<span class="small-subtitle" translate="no">class <a href="testqdoc.html" translate="no">TestQDoc</a>::Test</span>
+<!-- $$$Test-brief -->
+<p>A class in a namespace. <a href="#details">More...</a></p>
+<!-- @@@Test -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Test&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Inherited By:</td><td class="memItemRight bottomAlign"> <p><a href="testqdoc-testderived.html" translate="no">TestQDoc::TestDerived</a></p>
+</td></tr>
+</table></div>
+<ul>
+<li><a href="testqdoc-test-members.html">List of all members, including inherited members</a></li>
+<li><a href="testqdoc-test-obsolete.html">Deprecated members</a></li>
+<li>Test is part of <a href="cpptypes.html">Test C++ Types</a>.</li>
+</ul>
+<p><b>Note:</b> All functions in this class are <a href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</a> with the following exceptions:</p>
+<ul>
+<li><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a>(int i, bool b) const</li>
+</ul>
+<h2 id="public-types">Public Types</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#SomeType-typedef" translate="no">SomeType</a></b></td></tr>
+</table></div>
+<h2 id="public-functions">Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#Test" translate="no">Test</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void (*)(bool) </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#funcPtr" translate="no">funcPtr</a></b>(bool <i>b</i>, const char *<i>s</i>)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#inlineFunction" translate="no">inlineFunction</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#methodWithEmDashInItsDocs" translate="no">methodWithEmDashInItsDocs</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> int </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a></b>(int, int <i>v</i> = 0)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a></b>(int <i>i</i>, bool <i>b</i> = false) const</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> virtual void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#virtualFun" translate="no">virtualFun</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> TestQDoc::Test &amp;</td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#operator-eq" translate="no">operator=</a></b>(TestQDoc::Test &amp;&amp;<i>other</i>)</td></tr>
+</table></div>
+<h2 id="protected-functions">Protected Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#overload" translate="no">overload</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since Test 1.2)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#overload-1" translate="no">overload</a></b>(bool <i>b</i>)</td></tr>
+</table></div>
+<h2 id="related-non-members">Related Non-Members</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> bool </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#operator-eq-eq" translate="no">operator==</a></b>(const TestQDoc::Test &amp;<i>lhs</i>, const TestQDoc::Test &amp;<i>rhs</i>)</td></tr>
+</table></div>
+<h2 id="macros">Macros</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since Test 1.1)</code> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#QDOCTEST_MACRO2" translate="no">QDOCTEST_MACRO2</a></b>(int &amp;<i>x</i>)</td></tr>
+</table></div>
+<!-- $$$Test-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Test -->
+<div class="types">
+<h2>Member Type Documentation</h2>
+<!-- $$$SomeType -->
+<h3 class="fn" translate="no" id="SomeType-typedef">Test::<span class="name">SomeType</span></h3>
+<p>A typedef.</p>
+<!-- @@@SomeType -->
+</div>
+<div class="func">
+<h2>Member Function Documentation</h2>
+<!-- $$$ -->
+<div class="fngroup">
+<h3 class="fn fngroupitem" translate="no" id="overload"><code class="details extra" translate="no">[protected]</code> <span class="type">void</span> Test::<span class="name">overload</span>()</h3><h3 class="fn fngroupitem" translate="no" id="overload-1"><code class="details extra" translate="no">[protected, since Test 1.2]</code> <span class="type">void</span> Test::<span class="name">overload</span>(<span class="type">bool</span> <i>b</i>)</h3></div>
+<p>Overloads that share a documentation comment, optionally taking a parameter <i translate="no">b</i>.</p>
+<!-- @@@ -->
+<!-- $$$Test[overload1]$$$Test -->
+<h3 class="fn" translate="no" id="Test"><code class="details extra" translate="no">[noexcept default]</code> Test::<span class="name">Test</span>()</h3>
+<p>Default constructor.</p>
+<!-- @@@Test -->
+<!-- $$$funcPtr[overload1]$$$funcPtrboolconstchar* -->
+<h3 class="fn" translate="no" id="funcPtr"><span class="type">void</span> (*)(<span class="type">bool</span>) Test::<span class="name">funcPtr</span>(<span class="type">bool</span> <i>b</i>, const <span class="type">char</span> *<i>s</i>)</h3>
+<p>Returns a pointer to a function that takes a boolean. Uses <i translate="no">b</i> and <i translate="no">s</i>.</p>
+<!-- @@@funcPtr -->
+<!-- $$$inlineFunction[overload1]$$$inlineFunction -->
+<h3 class="fn" translate="no" id="inlineFunction"><span class="type">void</span> Test::<span class="name">inlineFunction</span>()</h3>
+<p>An inline function, documented using the \fn QDoc command.</p>
+<!-- @@@inlineFunction -->
+<!-- $$$methodWithEmDashInItsDocs[overload1]$$$methodWithEmDashInItsDocs -->
+<h3 class="fn" translate="no" id="methodWithEmDashInItsDocs"><span class="type">void</span> Test::<span class="name">methodWithEmDashInItsDocs</span>()</h3>
+<p>This method has em dashes in its documentation&mdash;as you'll find represented by <code translate="no">---</code> in the sources&mdash;here and there. The important bit to note is that when passed e.g. to the \c command, the three hyphens are processed as input to the command and not replaced by an em dash.</p>
+<p>-----------------------------------------------------------------------</p>
+<p>People can still add a bunch of dashes, though, without QDoc replacing them all with a series of em dashes.</p>
+<p>&mdash;You can also start a new paragraph with an em dash, if you want to.</p>
+<p><b>See also </b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a>.</p>
+<!-- @@@methodWithEmDashInItsDocs -->
+<!-- $$$methodWithEnDashInItsDocs[overload1]$$$methodWithEnDashInItsDocs -->
+<h3 class="fn" translate="no" id="methodWithEnDashInItsDocs"><span class="type">void</span> Test::<span class="name">methodWithEnDashInItsDocs</span>()</h3>
+<p>This method has en dashes in its documentation &ndash; as you'll find represented by <code translate="no">--</code> in the sources &ndash; here and there. The important bit to note is that when passed e.g. to the \c command, the two hyphens are processed as input to the command and not replaced by an en dash. This also applies to code blocks, where otherwise, the decrement operator would get completely borked:</p>
+<pre class="cpp" translate="no"><span class="keyword">for</span> (<span class="type">int</span> i <span class="operator">=</span> <span class="number">42</span>; i <span class="operator">&gt;</span> <span class="number">0</span>; <span class="operator">-</span><span class="operator">-</span>i)
+ <span class="comment">// Do something cool during countdown.</span></pre>
+<p>...as it would be silly if this would output &ndash;i instead of <code translate="no">--i</code>.</p>
+<p>-----------------------------------------------------------------------</p>
+<p>It still allows people to add a bunch of dashes, though, without replacing them all with a series of en dashes. Of course, they might want to use the \hr command instead, like this:</p>
+<hr />
+<p>&ndash; You can also start a new paragraph with an en dash, if you want to.</p>
+<p><b>See also </b>methodWithEnDashInItsDocs.</p>
+<!-- @@@methodWithEnDashInItsDocs -->
+<!-- $$$someFunction[overload1]$$$someFunctionintint -->
+<h3 class="fn" translate="no" id="someFunction"><span class="type">int</span> Test::<span class="name">someFunction</span>(<span class="type">int</span>, <span class="type">int</span> <i>v</i> = 0)</h3>
+<p>Function that takes a parameter <i translate="no">v</i>. Also returns the value of <i translate="no">v</i>.</p>
+<!-- @@@someFunction -->
+<!-- $$$someFunctionDefaultArg[overload1]$$$someFunctionDefaultArgintbool -->
+<h3 class="fn" translate="no" id="someFunctionDefaultArg"><span class="type">void</span> Test::<span class="name">someFunctionDefaultArg</span>(<span class="type">int</span> <i>i</i>, <span class="type">bool</span> <i>b</i> = false) const</h3>
+<p>Function that takes a parameter <i translate="no">i</i> and <i translate="no">b</i>.</p>
+<p><b>Warning:</b> This function is not <a href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</a>.</p>
+<!-- @@@someFunctionDefaultArg -->
+<!-- $$$virtualFun[overload1]$$$virtualFun -->
+<h3 class="fn" translate="no" id="virtualFun"><code class="details extra" translate="no">[virtual]</code> <span class="type">void</span> Test::<span class="name">virtualFun</span>()</h3>
+<p>Function that must be reimplemented.</p>
+<!-- @@@virtualFun -->
+<!-- $$$operator=[overload1]$$$operator=TestQDoc::Test&& -->
+<h3 class="fn" translate="no" id="operator-eq"><code class="details extra" translate="no">[noexcept default]</code> <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;Test::<span class="name">operator=</span>(<span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;&amp;<i>other</i>)</h3>
+<p>Move-assigns <i translate="no">other</i>.</p>
+<!-- @@@operator= -->
+</div>
+<div class="relnonmem">
+<h2>Related Non-Members</h2>
+<!-- $$$operator==[overload1]$$$operator==constTestQDoc::Test&constTestQDoc::Test& -->
+<h3 class="fn" translate="no" id="operator-eq-eq"><span class="type">bool</span> <span class="name">operator==</span>(const <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;<i>lhs</i>, const <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;<i>rhs</i>)</h3>
+<p>Returns true if <i translate="no">lhs</i> and <i translate="no">rhs</i> are equal.</p>
+<!-- @@@operator== -->
+</div>
+<div class="macros">
+<h2>Macro Documentation</h2>
+<!-- $$$QDOCTEST_MACRO2[overload1]$$$QDOCTEST_MACRO2int& -->
+<h3 class="fn" translate="no" id="QDOCTEST_MACRO2"><code class="details extra" translate="no">[since Test 1.1]</code> <span class="name">QDOCTEST_MACRO2</span>(<span class="type">int</span> &amp;<i>x</i>)</h3>
+<p>A macro with argument <i translate="no">x</i>.</p>
+<p>This macro was introduced in Test 1.1.</p>
+<!-- @@@QDOCTEST_MACRO2 -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-testderived-members.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-testderived-members.html
new file mode 100644
index 000000000..df89becd0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-testderived-members.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace, derived from Test.">
+ <title>List of All Members for TestDerived | TestCPP</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>TestDerived</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for TestDerived</h1>
+<p>This is the complete list of members for <a href="testqdoc-testderived.html">TestQDoc::TestDerived</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#DerivedType-typedef" translate="no">DerivedType</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#NotTypedef-typedef" translate="no">NotTypedef</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#SomeType-typedef" translate="no">SomeType</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#funcPtr" translate="no">funcPtr</a></b></span>(bool, const char *) : void (*)(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#inlineFunction" translate="no">inlineFunction</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEmDashInItsDocs" translate="no">methodWithEmDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload" translate="no">overload</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload-1" translate="no">overload</a></b></span>(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a></b></span>(int, int) : int</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a></b></span>(int, bool) const</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#someValue" translate="no">someValue</a></b></span>() : TestQDoc::TestDerived::NotTypedef</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#virtualFun" translate="no">virtualFun</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#virtualFun" translate="no">virtualFun</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#operator-eq" translate="no">operator=</a></b></span>(TestQDoc::Test &amp;&amp;) : TestQDoc::Test &amp;</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-testderived-obsolete.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-testderived-obsolete.html
new file mode 100644
index 000000000..42b536e09
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-testderived-obsolete.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace, derived from Test.">
+ <title>Obsolete Members for TestDerived | TestCPP</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>TestDerived</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Obsolete Members for TestDerived</h1>
+<p><b>The following members of class <a href="testqdoc-testderived.html" translate="no">TestDerived</a> are deprecated.</b> They are provided to keep old source code working. We strongly advise against using them in new code.</p>
+<h2>Static Public Members</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived-obsolete.html#staticObsoleteMember" translate="no">staticObsoleteMember</a></b>()</td></tr>
+</table></div>
+<h2>Member Function Documentation</h2>
+<!-- $$$staticObsoleteMember[overload1]$$$staticObsoleteMember -->
+<h3 class="fn" translate="no" id="staticObsoleteMember"><code class="details extra" translate="no">[static, deprecated]</code> <span class="type">void</span> TestDerived::<span class="name">staticObsoleteMember</span>()</h3>
+<p>This function is deprecated. We strongly advise against using it in new code.</p>
+<p>Static obsolete method.</p>
+<!-- @@@staticObsoleteMember -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-testderived.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-testderived.html
new file mode 100644
index 000000000..04f789abb
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc-testderived.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace, derived from Test.">
+ <title>TestDerived Class | TestCPP</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>TestDerived</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#public-types">Public Types</a></li>
+<li class="level1"><a href="#public-functions">Public Functions</a></li>
+<li class="level1"><a href="#reimplemented-public-functions">Reimplemented Public Functions</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">TestDerived Class</h1>
+<span class="small-subtitle" translate="no">class <a href="testqdoc.html" translate="no">TestQDoc</a>::TestDerived</span>
+<!-- $$$TestDerived-brief -->
+<p>A class in a namespace, derived from <a href="testqdoc-test.html" translate="no">Test</a>. <a href="#details">More...</a></p>
+<!-- @@@TestDerived -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;TestDerived&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Inherits:</td><td class="memItemRight bottomAlign"> <a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></td></tr>
+</table></div>
+<ul>
+<li><a href="testqdoc-testderived-members.html">List of all members, including inherited members</a></li>
+<li><a href="testqdoc-testderived-obsolete.html">Deprecated members</a></li>
+</ul>
+<h2 id="public-types">Public Types</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#DerivedType-typedef" translate="no">DerivedType</a></b></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#NotTypedef-typedef" translate="no">NotTypedef</a></b></td></tr>
+</table></div>
+<h2 id="public-functions">Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> TestQDoc::TestDerived::NotTypedef </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#someValue" translate="no">someValue</a></b>()</td></tr>
+</table></div>
+<h2 id="reimplemented-public-functions">Reimplemented Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> virtual void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#virtualFun" translate="no">virtualFun</a></b>() override</td></tr>
+</table></div>
+<!-- $$$TestDerived-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@TestDerived -->
+<div class="types">
+<h2>Member Type Documentation</h2>
+<!-- $$$DerivedType -->
+<h3 class="fn" translate="no" id="DerivedType-typedef"><code class="details extra" translate="no">[alias]</code> TestDerived::<span class="name">DerivedType</span></h3>
+<p>An aliased typedef.</p>
+<!-- @@@DerivedType -->
+<!-- $$$NotTypedef -->
+<h3 class="fn" translate="no" id="NotTypedef-typedef"><code class="details extra" translate="no">[alias]</code> TestDerived::<span class="name">NotTypedef</span></h3>
+<p>I'm an alias, not a typedef.</p>
+<!-- @@@NotTypedef -->
+</div>
+<div class="func">
+<h2>Member Function Documentation</h2>
+<!-- $$$someValue[overload1]$$$someValue -->
+<h3 class="fn" translate="no" id="someValue"><span class="type"><a href="testqdoc-testderived.html#NotTypedef-typedef" translate="no">TestQDoc::TestDerived::NotTypedef</a></span> TestDerived::<span class="name">someValue</span>()</h3>
+<p>Returns a value using an aliases type.</p>
+<!-- @@@someValue -->
+<!-- $$$virtualFun[overload1]$$$virtualFun -->
+<h3 class="fn" translate="no" id="virtualFun"><code class="details extra" translate="no">[override virtual]</code> <span class="type">void</span> TestDerived::<span class="name">virtualFun</span>()</h3>
+<p>Reimplements: <a href="testqdoc-test.html#virtualFun" translate="no">Test::virtualFun</a>().</p>
+<!-- @@@virtualFun -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc.html b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc.html
new file mode 100644
index 000000000..425c7b396
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/expected_output/testqdoc.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A namespace.">
+ <title>TestQDoc Namespace | TestCPP</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#macros">Macros</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#usage">Usage</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">TestQDoc Namespace</h1>
+<!-- $$$TestQDoc-brief -->
+<p>A namespace. <a href="#details">More...</a></p>
+<!-- @@@TestQDoc -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;TestCPP&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+</table></div>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since 2.0)</code> class </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html" translate="no">Test</a></b></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since 2.0)</code> class </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html" translate="no">TestDerived</a></b></td></tr>
+</table></div>
+<h2 id="macros">Macros</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc.html#QDOCTEST_MACRO" translate="no">QDOCTEST_MACRO</a></b></td></tr>
+</table></div>
+<!-- $$$TestQDoc-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<h3 id="usage">Usage</h3>
+<p>This namespace is for testing QDoc output.</p>
+</div>
+<!-- @@@TestQDoc -->
+<div class="classes">
+<h2>Classes</h2>
+<h3> class <a href="testqdoc-test.html">Test</a></h3><!-- $$$Test-brief -->
+<p>A class in a namespace. <a href="testqdoc-test.html#details">More...</a></p>
+<!-- @@@Test -->
+<h3> class <a href="testqdoc-testderived.html">TestDerived</a></h3><!-- $$$TestDerived-brief -->
+<p>A class in a namespace, derived from <a href="testqdoc-test.html" translate="no">Test</a>. <a href="testqdoc-testderived.html#details">More...</a></p>
+<!-- @@@TestDerived -->
+</div>
+<div class="macros">
+<h2>Macro Documentation</h2>
+<!-- $$$QDOCTEST_MACRO[overload1]$$$QDOCTEST_MACRO -->
+<h3 class="fn" translate="no" id="QDOCTEST_MACRO"><span class="name">QDOCTEST_MACRO</span></h3>
+<!-- @@@QDOCTEST_MACRO -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/configs/noautolist.qdocconf b/src/qdoc/qdoc/tests/generatedoutput/testdata/configs/noautolist.qdocconf
new file mode 100644
index 000000000..ad3b402ea
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/configs/noautolist.qdocconf
@@ -0,0 +1,64 @@
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ../images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+includepaths += -I../testcpp
+
+headers = ../testcpp/testcpp.h
+sources = ../testcpp/testcpp.cpp \
+ ../testcpp/classlists.qdoc
+
+macro.CMDFN = \\\\fn
+macro.nothing = \\dontdocument ()
+macro.testnoautolist = \\if defined(test_noautolist)\n\\noautolist\n\\endif
+
+navigation.cppclassespage = "QDoc Test C++ Classes"
+
+moduleheader = TestCPP
+
+project = Test
+description = "A test project for QDoc build artifacts"
+outputdir = ./html
+
+exampledirs = ../qml
+
+headerdirs += ../
+sourcedirs += ../qml
+
+# Exclude source files from other tests' subdirs
+excludedirs = ../bug80259
+
+examples.fileextensions = "*.qml *.cpp"
+
+macro.begincomment = "\\c{/*}"
+macro.QDocTestVer = "1.1"
+
+navigation.qmltypespage = "QDoc.Test QML Module"
+navigation.qmltypestitle = "Types"
+warninglimit += 1
+
+manifestmeta.examplecategories = "Application Examples" \
+ "Desktop" \
+ "Mobile" \
+ "Embedded"
+defines += test_noautolist
+
+outputformats = HTML DocBook
+
+HTML.nosubdirs = true
+DocBook.nosubdirs = true
+HTML.outputsubdir = noautolist
+DocBook.outputsubdir = noautolist-docbook
+
+# TODO: DocBook generator has trouble handling shared comment nodes
+# - allow two warnings related to these
+warninglimit = 2
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/configs/testcpp.qdocconf b/src/qdoc/qdoc/tests/generatedoutput/testdata/configs/testcpp.qdocconf
new file mode 100644
index 000000000..1d4ce57a6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/configs/testcpp.qdocconf
@@ -0,0 +1,31 @@
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ../images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+# By default, use -outputdir directly, no subdir.
+# outputsubdir '.' matches the root of expected_output/
+HTML.nosubdirs = true
+HTML.outputsubdir = .
+
+project = TestCPP
+includepaths += -I../testcpp
+
+headers = ../testcpp/testcpp.h
+sources = ../testcpp/testcpp.cpp \
+ ../testcpp/classlists.qdoc
+exampledirs = ../testcpp/snippets
+
+macro.CMDFN = \\\\fn
+macro.nothing = \\dontdocument ()
+macro.testnoautolist = \\if defined(test_noautolist)\n\\noautolist\n\\endif
+
+navigation.cppclassespage = "QDoc Test C++ Classes"
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/configs/testcpp_singleexec.qdocconf b/src/qdoc/qdoc/tests/generatedoutput/testdata/configs/testcpp_singleexec.qdocconf
new file mode 100644
index 000000000..e9b72b75d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/configs/testcpp_singleexec.qdocconf
@@ -0,0 +1,32 @@
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ../images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+# By default, use -outputdir directly, no subdir.
+# outputsubdir '.' matches the root of expected_output/
+HTML.nosubdirs = true
+HTML.outputsubdir = .
+
+project = TestCPP
+includepaths += -I../testcpp
+
+headers = ../testcpp/testcpp.h
+sources = ../testcpp/testcpp.cpp \
+ ../testcpp/classlists.qdoc
+exampledirs = ../testcpp/snippets
+
+macro.CMDFN = \\\\fn
+macro.nothing = \\dontdocument ()
+macro.testnoautolist = \\if defined(test_noautolist)\n\\noautolist\n\\endif
+
+navigation.cppclassespage = "QDoc Test C++ Classes"
+
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/CrossModule b/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/CrossModule
new file mode 100644
index 000000000..50bea93ab
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/CrossModule
@@ -0,0 +1,2 @@
+#include "../testcpp/TestCPP"
+#include "testtype.h"
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/crossmodule.qdocconf b/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/crossmodule.qdocconf
new file mode 100644
index 000000000..404981136
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/crossmodule.qdocconf
@@ -0,0 +1,26 @@
+project = CrossModule
+includepaths += -I.
+
+depends = testcpp
+
+headers = testtype.h
+sources = testtype.cpp
+
+HTML.nosubdirs = true
+HTML.outputsubdir = crossmodule
+
+defines += test_crossmodule
+
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ../images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/crossmodule_singleexec.qdocconf b/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/crossmodule_singleexec.qdocconf
new file mode 100644
index 000000000..1040bb5ad
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/crossmodule_singleexec.qdocconf
@@ -0,0 +1,6 @@
+include(crossmodule.qdocconf)
+
+sources += namespaces.qdoc
+
+HTML.nosubdirs = true
+HTML.outputsubdir = crossmodule
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/namespaces.qdoc b/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/namespaces.qdoc
new file mode 100644
index 000000000..3732008ac
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/namespaces.qdoc
@@ -0,0 +1,9 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+ \page all-namespaces.html
+ \title Namespaces
+
+ \generatelist namespaces
+*/
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/testtype.cpp b/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/testtype.cpp
new file mode 100644
index 000000000..d7557da73
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/testtype.cpp
@@ -0,0 +1,45 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#include "testtype.h"
+
+/*!
+ \module CrossModule
+*/
+
+/*!
+ Function under a namespace that's documented elsewhere.
+*/
+void CrossModuleRef::documentMeToo()
+{
+}
+
+/*!
+ \class TestType
+ \inmodule CrossModule
+ \brief A class inheriting another class that lives in an external doc
+ module.
+
+ \section1 Linking
+
+ These links go to the parent class:
+ \list
+ \li \l {TestQDoc::TestDerived}
+ \li \l {TestQDoc::}{Test} class \l Usage.
+ \li QDOCTEST_MACRO
+ \li DontLinkToMe
+ \endlist
+
+ \section1 Generated Lists
+
+ This is an annotated list of entries in a group:
+ \annotatedlist testgroup
+
+ \sa {TestQDoc::Test::}{someFunction()}
+*/
+
+/*!
+ Nothing to see here.
+*/
+void TestType::nothing()
+{
+}
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/testtype.h b/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/testtype.h
new file mode 100644
index 000000000..b6b0d446f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/crossmodule/testtype.h
@@ -0,0 +1,16 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#pragma once
+
+#include "../testcpp/testcpp.h"
+
+namespace CrossModuleRef {
+ void documentMeToo();
+}
+
+class TestType : public TestQDoc::TestDerived
+{
+public:
+ TestType() {}
+ void nothing() {}
+};
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/dontdocument/TestCPP b/src/qdoc/qdoc/tests/generatedoutput/testdata/dontdocument/TestCPP
new file mode 100644
index 000000000..7291e6d8f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/dontdocument/TestCPP
@@ -0,0 +1,2 @@
+#include "../TestCPP"
+#include "dont.h"
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/dontdocument/dontdocument.qdocconf b/src/qdoc/qdoc/tests/generatedoutput/testdata/dontdocument/dontdocument.qdocconf
new file mode 100644
index 000000000..605c68b88
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/dontdocument/dontdocument.qdocconf
@@ -0,0 +1,46 @@
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ../images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+project = TestCPP
+includepaths += -I../testcpp
+
+headers = ../testcpp/testcpp.h
+sources = ../testcpp/testcpp.cpp \
+ ../testcpp/classlists.qdoc
+exampledirs = ../testcpp/snippets
+
+macro.CMDFN = \\\\fn
+macro.nothing = \\dontdocument ()
+macro.testnoautolist = \\if defined(test_noautolist)\n\\noautolist\n\\endif
+
+navigation.cppclassespage = "QDoc Test C++ Classes"
+
+headers += dont.h
+sources += dont.cpp test.qdoc
+
+qhp.projects = DontDocument
+
+qhp.DontDocument.file = dontdocument.qhp
+qhp.DontDocument.namespace = org.qt-project.dontdocument.001
+qhp.DontDocument.virtualFolder = test
+qhp.DontDocument.indexTitle = QDoc Test C++ Classes
+qhp.DontDocument.indexRoot =
+
+qhp.DontDocument.subprojects = classes
+qhp.DontDocument.subprojects.classes.title = Classes
+qhp.DontDocument.subprojects.classes.indexTitle = QDoc Test C++ Classes
+qhp.DontDocument.subprojects.classes.selectors = class
+qhp.DontDocument.subprojects.classes.sortPages = true
+
+HTML.nosubdirs = true
+HTML.outputsubdir = dontdocument
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/demo.cpp b/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/demo.cpp
new file mode 100644
index 000000000..a5e983503
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/demo.cpp
@@ -0,0 +1,11 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+bool isOverThousand(int n)
+{
+//! [integer literal with separator]
+ if (n > 1'000)
+ return true;
+//! [integer literal with separator]
+ return false;
+}
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/demo.pro b/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/demo.pro
new file mode 100644
index 000000000..dbe8ff3c0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/demo.pro
@@ -0,0 +1,2 @@
+TEMPLATE = aux
+message("Nothing to see here.")
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/doc/src/demo.qdoc b/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/doc/src/demo.qdoc
new file mode 100644
index 000000000..d06e34ca2
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/doc/src/demo.qdoc
@@ -0,0 +1,11 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+ \example demos/demo
+ \title Demo
+ \image leonardo-da-vinci.png
+ //! Icon made by Smashicons from www.flaticon.com
+
+ \snippet demos/demo/demo.cpp integer literal with separator
+*/
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/dontxclude/CMakeLists.txt b/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/dontxclude/CMakeLists.txt
new file mode 100644
index 000000000..d29157aad
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/dontxclude/CMakeLists.txt
@@ -0,0 +1,2 @@
+cmake_minimum_required(VERSION 3.16)
+project (DONTXCLUDEDIRS_QDOCTEST)
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/excludes/CMakeLists.txt b/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/excludes/CMakeLists.txt
new file mode 100644
index 000000000..09b447642
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/demo/excludes/CMakeLists.txt
@@ -0,0 +1,2 @@
+cmake_minimum_required(VERSION 3.16)
+project (EXCLUDEDIR_QDOCTEST)
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/hidden/doc/src/hidden.qdoc b/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/hidden/doc/src/hidden.qdoc
new file mode 100644
index 000000000..9001e3d96
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/hidden/doc/src/hidden.qdoc
@@ -0,0 +1,11 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+ \example demos/hidden
+ \title Hidden Demo
+ \meta tag broken
+ \brief Tagged 'broken', does not appear in examples-manifest.xml.
+
+ Also missing an image, but that's OK as it's broken anyway.
+*/
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/hidden/hidden.pro b/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/hidden/hidden.pro
new file mode 100644
index 000000000..dbe8ff3c0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/examples/demos/hidden/hidden.pro
@@ -0,0 +1,2 @@
+TEMPLATE = aux
+message("Nothing to see here.")
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/images/01.png b/src/qdoc/qdoc/tests/generatedoutput/testdata/images/01.png
new file mode 100644
index 000000000..d73ab969b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/images/01.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/images/leonardo-da-vinci.png b/src/qdoc/qdoc/tests/generatedoutput/testdata/images/leonardo-da-vinci.png
new file mode 100644
index 000000000..854acb4ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/images/leonardo-da-vinci.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/excludes/anotherindex.qdoc b/src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/excludes/anotherindex.qdoc
new file mode 100644
index 000000000..f8436416f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/excludes/anotherindex.qdoc
@@ -0,0 +1,14 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+//! exampledirs-include
+ \page index.html
+ \title doc index
+
+ \section1 C++ Classes
+ \generatelist {classesbymodule TestCPP}
+ \section1 QML Types
+ \annotatedlist qmltypes
+//! exampledirs-include
+*/
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/excludes/parentinclude.qdoc b/src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/excludes/parentinclude.qdoc
new file mode 100644
index 000000000..4409fba8f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/excludes/parentinclude.qdoc
@@ -0,0 +1,28 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+//! abstract-type
+ \qmltype AbstractParent
+ \inqmlmodule QDoc.Test
+ \ingroup qmltypes
+ \qmlabstract
+ \brief Abstract base QML type.
+//! abstract-type
+*/
+
+/*!
+ //! --- Observe the indented snippet tag:
+ //! children-qmlproperty
+ \qmlproperty list<Child> AbstractParent::children
+ \qmldefault
+ \brief Children of the type.
+ //! children-qmlproperty
+*/
+
+/*!
+//! rear-qmlmethod
+ \qmlmethod void AbstractParent::rear(Child child)
+ \brief Do some abstract parenting on \a child.
+//! rear-qmlmethod
+*/
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/includefromexampledirs.qdocconf b/src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/includefromexampledirs.qdocconf
new file mode 100644
index 000000000..abbfabb87
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/includefromexampledirs.qdocconf
@@ -0,0 +1,64 @@
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ../images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+includepaths += -I../testcpp
+
+headers = ../testcpp/testcpp.h
+sources = ../testcpp/testcpp.cpp \
+ ../testcpp/classlists.qdoc
+
+macro.CMDFN = \\\\fn
+macro.nothing = \\dontdocument ()
+macro.testnoautolist = \\if defined(test_noautolist)\n\\noautolist\n\\endif
+
+navigation.cppclassespage = "QDoc Test C++ Classes"
+
+moduleheader = TestCPP
+
+project = Test
+description = "A test project for QDoc build artifacts"
+outputdir = ./html
+
+exampledirs = ../qml
+
+headerdirs += ../
+sourcedirs += ../qml
+
+# Exclude source files from other tests' subdirs
+excludedirs = ../bug80259
+
+examples.fileextensions = "*.qml *.cpp"
+
+macro.begincomment = "\\c{/*}"
+macro.QDocTestVer = "1.1"
+
+navigation.qmltypespage = "QDoc.Test QML Module"
+navigation.qmltypestitle = "Types"
+warninglimit += 1
+
+manifestmeta.examplecategories = "Application Examples" \
+ "Desktop" \
+ "Mobile" \
+ "Embedded"
+
+sourcedirs += src
+
+excludedirs += excludes \
+ ../qml/componentset
+
+excludefiles += ../qml/parent.qdoc
+
+exampledirs += excludes
+
+HTML.nosubdirs = true
+HTML.outputsubdir = includefromexampledirs
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/src/includefromparent.qdoc b/src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/src/includefromparent.qdoc
new file mode 100644
index 000000000..a7ac5454b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/src/includefromparent.qdoc
@@ -0,0 +1,41 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+\include anotherindex.qdoc exampledirs-include
+
+\include parent.qdocinc
+*/
+
+/*!
+\include parentinclude.qdoc abstract-type
+
+\include parent.qdocinc
+*/
+
+/*!
+\include parentinclude.qdoc children-qmlproperty
+
+\include parent.qdocinc
+*/
+
+/*!
+\include parentinclude.qdoc rear-qmlmethod
+
+\include parent.qdocinc
+*/
+
+/*!
+ \qmltype Child
+ \inqmlmodule QDoc.Test
+ \ingroup qmltypes
+ \inherits AbstractParent
+ \brief A Child inheriting its parent.
+*/
+
+/*!
+ \qmlvaluetype int
+ \inqmlmodule QDoc.Test
+ \ingroup qmltypes
+ \brief An integer value type.
+*/
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/src/parent.qdocinc b/src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/src/parent.qdocinc
new file mode 100644
index 000000000..307c39dbd
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/includefromexampledirs/src/parent.qdocinc
@@ -0,0 +1 @@
+Test include file that is part of the sourcedirs.
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/indexlinking/indexlinking.qdocconf b/src/qdoc/qdoc/tests/generatedoutput/testdata/indexlinking/indexlinking.qdocconf
new file mode 100644
index 000000000..f2c1967de
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/indexlinking/indexlinking.qdocconf
@@ -0,0 +1,29 @@
+# test linking to entities loaded from index
+project = IndexLinking
+
+# Use a URL different from what the dependency below uses,
+# to avoid linking with relative paths. See QTBUG-107762.
+url = indexlinking
+
+depends = \
+ qmlpropertygroups
+
+sources = linking.qdoc
+
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ../images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+# By default, use -outputdir directly, no subdir.
+# outputsubdir '.' matches the root of expected_output/
+HTML.nosubdirs = true
+HTML.outputsubdir = .
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/indexlinking/linking.qdoc b/src/qdoc/qdoc/tests/generatedoutput/testdata/indexlinking/linking.qdoc
new file mode 100644
index 000000000..2206ec712
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/indexlinking/linking.qdoc
@@ -0,0 +1,41 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+ \page index-linking.html
+ \title Linking
+
+ \section1 QML properties
+ \list A
+ \li Property group: \l [QML] {Parent::group}.
+ \li Property in a group: \l [QmlPropertyGroups]
+ QDoc.Test::Parent::group.c.
+ \endlist
+
+ \section1 Auto-linking to types in code snippets
+
+ \qml
+ import QtQuick
+
+ ProgressBar {
+ // Linking to int value type
+ property int progress: 0
+ }
+ \endqml
+
+ \code
+ // 'int' should not link anywhere in C++ code
+ int main() { int x{0}; return x; }
+ \endcode
+*/
+
+/*!
+ \title LinkModule QML Types
+ \qmlmodule LinkModule 1.0
+*/
+
+/*!
+ \qmltype GrandChild
+ \inqmlmodule LinkModule
+ \inherits AnotherChild
+*/
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/DocTest.qml b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/DocTest.qml
new file mode 100644
index 000000000..0bf95132d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/DocTest.qml
@@ -0,0 +1,86 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+
+/*!
+ \qmltype DocTest
+ \inherits Test
+ \inqmlmodule QDoc.Test
+ \brief Represents a doc test case.
+ \since QDoc.Test 0.9
+
+ \section1 Introduction
+
+ A documentation test case, itself documented inline in \DocTest.qml.
+*/
+Item {
+ id: testCase
+
+ /*!
+ \qmlsignal QDocTest::completed
+ */
+ signal completed
+
+ /*!
+ \qmlsignal DocTest::test(var bar)
+ Signal with parameter \a bar.
+ */
+ signal foo(var bar)
+
+ /*!
+ Signals that something is \a really happening.
+ */
+ signal itsHappening(bool really)
+
+ /*!
+ \qmlproperty string DocTest::name
+
+ Name of the test.
+ \qml
+ DocTest {
+ name: "test"
+ // ...
+ }
+ \endqml
+ */
+ required property string name
+
+ /*!
+ Whether the test is active.
+ \default true
+
+ \sa name
+ */
+ property bool active: true
+
+ /*! \internal */
+ property int doctest_internal: -1
+
+ /*!
+ \qmlmethod DocTest::fail(message = "oops")
+ \since QDoc.Test 1.0
+
+ Fails the current test case, with the optional \a message.
+ */
+ function fail(msg) {
+ if (msg === undefined)
+ msg = "oops";
+ }
+
+ /*! \internal */
+ function doctest_fail(msg) {
+ if (msg === undefined)
+ msg = "";
+ }
+
+ /*!
+ \brief Fails the current test case, hard.
+ \list
+ \li Prints out \a msg.
+ \li Accepts a random \a option.
+ \endlist
+ */
+ function fail_hard(msg = "facepalm", option = 123) {
+ }
+}
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/cmaketest/CMakeLists.txt b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/cmaketest/CMakeLists.txt
new file mode 100644
index 000000000..89eafa300
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/cmaketest/CMakeLists.txt
@@ -0,0 +1,2 @@
+cmake_minimum_required(VERSION 3.16)
+project (QDOCTEST)
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/cmaketest/doc/src/cmaketest.qdoc b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/cmaketest/doc/src/cmaketest.qdoc
new file mode 100644
index 000000000..bb7b63263
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/cmaketest/doc/src/cmaketest.qdoc
@@ -0,0 +1,9 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+ \example cmaketest
+ \title CMake Example Project
+ \image leonardo-da-vinci.png
+ //! Icon made by Smashicons from www.flaticon.com
+*/
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/cmaketest/main.cpp b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/cmaketest/main.cpp
new file mode 100644
index 000000000..68d71eb71
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/cmaketest/main.cpp
@@ -0,0 +1 @@
+void main(){}
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/ProgressBar.qml b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/ProgressBar.qml
new file mode 100644
index 000000000..39e48da54
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/ProgressBar.qml
@@ -0,0 +1,98 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 1.0
+
+/*!
+ \qmltype ProgressBar
+ \inqmlmodule UIComponents
+ \brief A component that shows the progress of an event.
+
+ A ProgressBar shows the linear progress of an event as its \l value.
+ The range is specified using the \l {minimum} and the \l{maximum} values.
+
+ The ProgressBar component is part of the \l {UI Components} module.
+
+ This documentation is part of the \l{componentset}{UIComponents} example.
+*/
+Item {
+ id: progressbar
+
+ /*!
+ The minimum value of the ProgressBar range.
+ The \l value must not be less than this value.
+ */
+ property int minimum: 0
+
+ /*!
+ The maximum value of the ProgressBar range.
+ The \l value must not be more than this value.
+ */
+ property int maximum: 100
+
+ /*!
+ The value of the progress.
+ */
+ property int value: 0
+
+ /*!
+ \qmlproperty color ProgressBar::color
+ The color of the ProgressBar's gradient. Must bind to a color type.
+
+ \omit
+ The "\qmlproperty <type> <property name>" is needed because
+ property alias need to have their types manually entered.
+
+ QDoc will not publish the documentation within omit and endomit.
+ \endomit
+
+ \sa secondColor
+ */
+ property alias color: gradient1.color
+
+ /*!
+ \qmlproperty color ProgressBar::secondColor
+ The second color of the ProgressBar's gradient.
+ Must bind to a color type.
+
+ \omit
+ The "\qmlproperty <type> <property name>" is needed because
+ property alias need to have their types manually entered.
+
+ QDoc will not publish the documentation within omit and endomit.
+ \endomit
+
+ \sa color
+ */
+ property alias secondColor: gradient2.color
+
+ width: 250; height: 23
+ clip: true
+
+ Rectangle {
+ id: highlight
+
+ /*!
+ An internal documentation comment. The widthDest property is not
+ a public API and therefore will not be exposed.
+ */
+ property int widthDest: ((progressbar.width * (value - minimum)) / (maximum - minimum) - 6)
+
+ width: highlight.widthDest
+ Behavior on width { SmoothedAnimation { velocity: 1200 } }
+
+ anchors { left: parent.left; top: parent.top; bottom: parent.bottom; margins: 3 }
+ radius: 1
+ gradient: Gradient {
+ GradientStop { id: gradient1; position: 0.0 }
+ GradientStop { id: gradient2; position: 1.0 }
+ }
+
+ }
+ Text {
+ anchors { right: highlight.right; rightMargin: 6; verticalCenter: parent.verticalCenter }
+ color: "white"
+ font.bold: true
+ text: Math.floor((value - minimum) / (maximum - minimum) * 100) + '%'
+ }
+}
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/Switch.qml b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/Switch.qml
new file mode 100644
index 000000000..2b39bb820
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/Switch.qml
@@ -0,0 +1,105 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 1.0
+
+/*!
+ \qmltype ToggleSwitch
+ \inqmlmodule UIComponents
+ \brief A component that can be turned on or off.
+
+ A toggle switch has two states: an \c on and an \c off state. The \c off
+ state is when the \l on property is set to \c false.
+
+ The ToggleSwitch component is part of the \l {UI Components} module.
+
+ This documentation is part of the \l{componentset}{UIComponents} example.
+
+*/
+Item {
+ id: toggleswitch
+ width: background.width; height: background.height
+
+ /*!
+ Indicates the state of the switch. If \c false, then the switch is in
+ the \c off state.
+
+ \omit
+ The \qmlproperty <type> <propertyname> is not necessary as QDoc
+ will associate this property to the ToggleSwitch
+
+ QDoc will not publish the documentation within omit and endomit.
+ \endomit
+ */
+ property bool on: false
+
+
+ /*!
+ A method to toggle the switch. If the switch is \c on, the toggling it
+ will turn it \c off. Toggling a switch in the \c off position will
+ turn it \c on.
+ */
+ function toggle() {
+ if (toggleswitch.state == "on")
+ toggleswitch.state = "off";
+ else
+ toggleswitch.state = "on";
+ }
+
+
+ /*!
+ \internal
+
+ An internal function to synchronize the switch's internals. This
+ function is not for public access. The \internal command will
+ prevent QDoc from publishing this comment in the public API.
+ */
+ function releaseSwitch() {
+ if (knob.x == 1) {
+ if (toggleswitch.state == "off") return;
+ }
+ if (knob.x == 78) {
+ if (toggleswitch.state == "on") return;
+ }
+ toggle();
+ }
+
+ Rectangle {
+ id: background
+ width: 130; height: 48
+ radius: 48
+ color: "lightsteelblue"
+ MouseArea { anchors.fill: parent; onClicked: toggle() }
+ }
+
+ Rectangle {
+ id: knob
+ width: 48; height: 48
+ radius: width
+ color: "lightblue"
+
+ MouseArea {
+ anchors.fill: parent
+ drag.target: knob; drag.axis: Drag.XAxis; drag.minimumX: 1; drag.maximumX: 78
+ onClicked: toggle()
+ onReleased: releaseSwitch()
+ }
+ }
+
+ states: [
+ State {
+ name: "on"
+ PropertyChanges { target: knob; x: 78 }
+ PropertyChanges { target: toggleswitch; on: true }
+ },
+ State {
+ name: "off"
+ PropertyChanges { target: knob; x: 1 }
+ PropertyChanges { target: toggleswitch; on: false }
+ }
+ ]
+
+ transitions: Transition {
+ NumberAnimation { properties: "x"; easing.type: Easing.InOutQuad; duration: 200 }
+ }
+}
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/TabWidget.qml b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/TabWidget.qml
new file mode 100644
index 000000000..1816d31c0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/TabWidget.qml
@@ -0,0 +1,146 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 1.0
+
+/*!
+ \qmltype TabWidget
+ \inqmlmodule UIComponents
+ \brief A widget that places its children as tabs.
+
+ A TabWidget places its children as tabs in a view. Selecting
+ a tab involves selecting the tab at the top.
+
+ The TabWidget component is part of the \l {UI Components} module.
+
+ This documentation is part of the \l{componentset}{UIComponents} example.
+
+ \section1 Adding Tabs
+
+ To add a tab, declare the tab as a child of the TabWidget.
+
+ \code
+ TabWidget {
+ id: tabwidget
+
+ Rectangle {
+ id: tab1
+ color: "red"
+ //... omitted
+ }
+ Rectangle {
+ id: tab2
+ color: "blue"
+ //... omitted
+ }
+
+ }
+ \endcode
+
+*/
+Item {
+ id: tabWidget
+
+ /*!
+ \internal
+
+ Setting the default property to stack.children means any child items
+ of the TabWidget are actually added to the 'stack' item's children.
+
+ See the \l{"Property Binding in QML"}
+ documentation for details on default properties.
+
+ This is an implementation detail, not meant for public knowledge. Putting
+ the \internal command at the beginning will cause QDoc to not publish this
+ documentation in the public API page.
+
+ Normally, a property alias needs to have a
+ "\qmlproperty <type> <propertyname>" to assign the alias a type.
+
+ */
+ default property alias content: stack.children
+
+
+ /*!
+ The currently active tab in the TabWidget.
+ */
+ property int current: 0
+
+ /*!
+ A sample \c{read-only} property.
+ A contrived property to demonstrate QDoc's ability to detect
+ read-only properties.
+
+ The signature is:
+ \code
+ readonly property int sampleReadOnlyProperty: 0
+ \endcode
+
+ Note that the property must be initialized to a value.
+
+ */
+ readonly property int sampleReadOnlyProperty: 0
+
+ /*!
+ \internal
+
+ This handler is an implementation
+ detail. The \c{\internal} command will prevent QDoc from publishing this
+ documentation on the public API.
+ */
+ onCurrentChanged: setOpacities()
+ Component.onCompleted: setOpacities()
+
+ /*!
+ \internal
+
+ An internal function to set the opacity.
+ The \internal command will prevent QDoc from publishing this
+ documentation on the public API.
+ */
+ function setOpacities() {
+ for (var i = 0; i < stack.children.length; ++i) {
+ stack.children[i].opacity = (i == current ? 1 : 0)
+ }
+ }
+
+ Row {
+ id: header
+
+ Repeater {
+ model: stack.children.length
+ delegate: Rectangle {
+ width: tabWidget.width / stack.children.length; height: 36
+
+ Rectangle {
+ width: parent.width; height: 1
+ anchors { bottom: parent.bottom; bottomMargin: 1 }
+ color: "#acb2c2"
+ }
+ BorderImage {
+ anchors { fill: parent; leftMargin: 2; topMargin: 5; rightMargin: 1 }
+ border { left: 7; right: 7 }
+ source: "tab.png"
+ visible: tabWidget.current == index
+ }
+ Text {
+ horizontalAlignment: Qt.AlignHCenter; verticalAlignment: Qt.AlignVCenter
+ anchors.fill: parent
+ text: stack.children[index].title
+ elide: Text.ElideRight
+ font.bold: tabWidget.current == index
+ }
+ MouseArea {
+ anchors.fill: parent
+ onClicked: tabWidget.current = index
+ }
+ }
+ }
+ }
+
+ Item {
+ id: stack
+ width: tabWidget.width
+ anchors.top: header.bottom; anchors.bottom: tabWidget.bottom
+ }
+}
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/componentset.pro b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/componentset.pro
new file mode 100644
index 000000000..5b44737c2
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/componentset.pro
@@ -0,0 +1,5 @@
+SOURCES = componentset.pro \
+ ProgressBar.qml \
+ Switch.qml \
+ TabWidget.qml \
+ uicomponents.qdoc
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/componentset.qml b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/componentset.qml
new file mode 100644
index 000000000..4f75ece75
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/componentset.qml
@@ -0,0 +1,7 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+
+Item {
+}
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/examples.qdoc b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/examples.qdoc
new file mode 100644
index 000000000..898f706c4
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/examples.qdoc
@@ -0,0 +1,82 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+ \example componentset
+ \title QML Documentation Example
+ \brief Example for documenting QML types.
+
+ \testnoautolist
+
+ \meta tags {test,sample}
+ \meta tag {application}
+ \meta category {Application Example}
+ \meta installpath tutorials
+
+ This example demonstrates one of the ways to document QML types. It also
+ generates a warning about a missing example image, on purpose.
+
+ In particular, there are sample types that are documented with QDoc
+ commands comments. There are documentation comments for the QML types
+ and their public interfaces. The types are grouped into a module, the
+ \l{UI Components} module.
+
+ The uicomponents.qdoc file generates
+ the overview page for the \l{UI Components} module page.
+
+ The generated documentation is available in the \l{UI Components} module.
+
+ \section1 QML Class
+
+ The QML types use the \\qmltype to document the
+ type. In addition, they have the \\inmodule
+ command in order for QDoc to associate them to the \c UIComponents module.
+
+ QDoc uses the \\brief command to place a basic
+ description when listing the types.
+
+ \section1 Properties, Signals, Handlers, and Methods
+
+ The types have their properties, signals, handlers, and methods
+ defined in their respective QML files. QDoc associates the properties and
+ methods to the types, therefore, you only need to place the
+ documentation above the property, method, or signal.
+
+ To document the type of a \e {property alias}, you must use the
+ \\qmlproperty command to specify the data type.
+
+ \code
+ \qmlproperty int anAliasedProperty
+ An aliased property of type int.
+ \endcode
+
+ \section2 Internal Documentation
+
+ You may declare that a documentation is for internal use by placing the
+ \\internal command after the beginning QDoc comment
+ \begincomment. QDoc will prevent the internal documentation from appearing
+ in the public API.
+
+ If you wish to omit certain parts of the documentation, you may use the
+ \\omit and \\endomit command.
+
+ \section1 QML Types with C++ Implementation
+
+ This example only demonstrates the documentation for types in QML
+ files, but the regular QML commands may be placed
+ inside C++ classes to define the public API of the QML type.
+
+*/
+
+
+/*!
+ \qmlmodule UIComponents 1.0
+ \title UI Components
+ \brief Basic set of UI components.
+
+ This is a listing of a list of UI components implemented by QML types. These
+ files are available for general import and they are based on the
+ Qt Quick Code Samples.
+
+ This module is part of the \l{componentset}{UIComponents} example.
+*/
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/uicomponents.qdoc.sample b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/uicomponents.qdoc.sample
new file mode 100644
index 000000000..9818adc06
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/componentset/uicomponents.qdoc.sample
@@ -0,0 +1,14 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+ \qmlmodule UIComponents 1.0
+ \title UI Components
+ \brief Basic set of UI components
+
+ This is a listing of a list of UI components implemented by QML types. These
+ files are available for general import and they are based off the \l{Qt
+ Quick Code Samples}.
+
+ This module is part of the \l{componentset}{UIComponents} example.
+*/
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/doctest/DocTest.qml b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/doctest/DocTest.qml
new file mode 100644
index 000000000..ea2312c4c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/doctest/DocTest.qml
@@ -0,0 +1,11 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+
+/*!
+ //! omit the \qmltype command, file name (base) is the type name.
+ \inqmlmodule Test.NoVer
+ \brief Shadows the type name in QDoc.Test module.
+*/
+Item {}
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/modules.qdoc b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/modules.qdoc
new file mode 100644
index 000000000..c3f4379f9
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/modules.qdoc
@@ -0,0 +1,19 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+ \page qmlmodules.html
+ \title QML Modules
+
+ \generatelist qml-modules
+
+ \section1 QML types
+
+ \generatelist qmltypesbymodule QDoc.Test
+
+ \section1 QML value types
+
+ \generatelist qmlvaluetypes
+
+ \generatelist qmlvaluetypesbymodule QDoc.Test
+*/
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/parent.qdoc b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/parent.qdoc
new file mode 100644
index 000000000..a2e41eadd
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/parent.qdoc
@@ -0,0 +1,87 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+ \qmltype AbstractParent
+ \inqmlmodule QDoc.Test
+ \qmlabstract
+ \brief Abstract base QML type.
+*/
+
+/*!
+ \qmlproperty list<Child> AbstractParent::children
+ \qmldefault
+ \brief Children of the type.
+*/
+
+/*!
+ \qmlmethod void AbstractParent::rear(Child child, var method = Strict)
+ \brief Do some abstract parenting on \a child using a specific \a method.
+*/
+
+/*!
+ \qmlproperty string AbstractParent::name
+ \brief Name of this parent.
+*/
+
+/*!
+ \qmlmethod void AbstractParent::name(Child child, name)
+ \brief Name a \a child using \a name.
+
+*/
+
+/*!
+ \qmlmethod void AbstractParent::name()
+ \brief Name all children with random names.
+*/
+
+/*!
+ \qmltype Child
+ \inqmlmodule QDoc.Test
+ \inherits AbstractParent
+ \brief A Child inheriting its parent.
+*/
+
+/*!
+ //! override from abstract base
+ \qmlproperty string Child::name
+ \brief Name of this child.
+*/
+
+/*!
+ //! override from abstract base
+ \qmlmethod void Child::name(Child child, name)
+ \brief Name a \a child of this child using \a name.
+*/
+
+/*!
+ \qmlvaluetype int
+ \inqmlmodule QDoc.Test
+
+ \brief An integer value type.
+*/
+
+/*!
+ \qmlmethod int int::abs()
+ Returns the absolute value of this integer.
+*/
+
+/*!
+ \qmltype InternParent
+ \inqmlmodule QDoc.Test
+ \internal
+ \qmlabstract
+ \brief Internal abstract base QML type.
+*/
+
+/*!
+ \qmlproperty int InternParent::prop
+ \brief Propagated to inheriting type docs.
+*/
+
+/*!
+ \qmltype YetAnotherChild
+ \inherits InternParent
+ \inqmlmodule QDoc.Test
+ \brief A type inheriting from internal abstract parent.
+*/
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/type.cpp b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/type.cpp
new file mode 100644
index 000000000..f3b8b9f0d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/qml/type.cpp
@@ -0,0 +1,127 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "testcpp.h"
+
+/*!
+ \qmlmodule QDoc.Test \QDocTestVer
+ \title QDoc.Test QML Module
+ \brief QML Types for the Test module.
+ \since 1.1
+ \preliminary
+
+ \testnoautolist
+*/
+
+/*!
+ \qmlmodule Test.Empty 1.0
+ \title No QML Types Here
+ \brief A QML module with no member types.
+*/
+
+/*!
+ \qmlmodule Test.NoVer
+ \title Versionless QML Module
+ \brief QML Types for the Test module without version.
+ \since 1.1
+ \modulestate Tech Preview
+*/
+
+/*!
+ \qmltype Type
+ \instantiates TestQDoc::Test
+ \inqmlmodule QDoc.Test
+ \brief A QML type documented in a .cpp file.
+ \meta status { <Work In Progress> }
+*/
+
+/*!
+ \qmltype TypeNoVersion
+ \instantiates TestQDoc::TestDerived
+ \inqmlmodule Test.NoVer
+ \brief Another QML type documented in a .cpp file.
+*/
+
+/*!
+ \qmltype OldType
+ \inqmlmodule QDoc.Test
+ \brief Deprecated old type.
+ \deprecated [1.0]
+*/
+
+/*!
+ \qmlproperty int Type::id
+ \readonly
+ \brief A read-only property.
+*/
+
+/*!
+ \qmlproperty string QDoc.Test::Type::name
+ \required
+ \brief Name of the Test.
+*/
+
+/*!
+ \qmlattachedproperty enumeration Type::type
+ \default Type.NoType
+
+ \value Type.NoType
+ Nothing
+ \value Type.SomeType
+ Something
+*/
+
+/*!
+ \qmlproperty int Type::group.first
+ \qmlproperty int Type::group.second
+ \qmlproperty int Type::group.third
+
+ \brief A property group.
+*/
+
+/*!
+ \qmlsignal Type::group.created
+
+ This signal is prefixed with \e group.
+*/
+
+/*!
+ \qmlproperty int Type::fourth
+ \qmlproperty int Type::fifth
+
+ \brief A group of properties sharing a documentation comment.
+*/
+
+/*!
+ \qmlmethod Type Type::copy(a)
+
+ Returns another Type based on \a a.
+*/
+
+/*!
+ \qmlmethod Type::enable()
+ \qmlmethod Type::disable()
+
+ Enables or disables this type.
+*/
+
+/*!
+ \qmlsignal Type::completed(int status)
+
+ This signal is emitted when the operation completed with \a status.
+*/
+
+/*!
+ \qmlattachedsignal Type::configured()
+
+ This attached signal is emitted when the type was configured.
+*/
+
+/*!
+ \qmlmethod Type::deprecatedMethod()
+
+ \deprecated [6.2] This method has no replacement.
+
+ This is a method that should include information about being deprecated
+ and that it has been so since 6.2 in its docs.
+*/
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/qmlpropertygroups/parent.qdoc b/src/qdoc/qdoc/tests/generatedoutput/testdata/qmlpropertygroups/parent.qdoc
new file mode 100644
index 000000000..baabde91d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/qmlpropertygroups/parent.qdoc
@@ -0,0 +1,37 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+ \qmltype Parent
+ \inqmlmodule QDoc.Test
+ \brief Base QML type.
+*/
+
+/*!
+ \qmlproperty int Parent::group.c
+ \qmlproperty int Parent::group.a
+ \qmlproperty int Parent::group.b
+ \brief Property group.
+*/
+
+/*!
+ \qmlproperty int Parent::group.b
+ \readonly
+*/
+
+/*!
+ \qmlproperty int Parent::group.c
+ \since 2.0
+*/
+
+/*!
+ \qmltype AnotherChild
+ \inqmlmodule QDoc.Test
+ \inherits Parent
+ \brief Just another child inheriting a parent.
+*/
+
+/*!
+ \qmlproperty string AnotherChild::name
+ \brief Name of this child.
+*/
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/qmlpropertygroups/qmlpropertygroups.qdocconf b/src/qdoc/qdoc/tests/generatedoutput/testdata/qmlpropertygroups/qmlpropertygroups.qdocconf
new file mode 100644
index 000000000..b2e569e43
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/qmlpropertygroups/qmlpropertygroups.qdocconf
@@ -0,0 +1,63 @@
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ../images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+includepaths += -I../testcpp
+
+headers = ../testcpp/testcpp.h
+sources = ../testcpp/testcpp.cpp \
+ ../testcpp/classlists.qdoc
+
+macro.CMDFN = \\\\fn
+macro.nothing = \\dontdocument ()
+macro.testnoautolist = \\if defined(test_noautolist)\n\\noautolist\n\\endif
+
+navigation.cppclassespage = "QDoc Test C++ Classes"
+
+moduleheader = TestCPP
+
+project = Test
+description = "A test project for QDoc build artifacts"
+outputdir = ./html
+
+exampledirs = ../qml
+
+headerdirs += ../
+sourcedirs += ../qml
+
+# Exclude source files from other tests' subdirs
+excludedirs = ../bug80259
+
+examples.fileextensions = "*.qml *.cpp"
+
+macro.begincomment = "\\c{/*}"
+macro.QDocTestVer = "1.1"
+
+navigation.qmltypespage = "QDoc.Test QML Module"
+navigation.qmltypestitle = "Types"
+warninglimit += 1
+
+manifestmeta.examplecategories = "Application Examples" \
+ "Desktop" \
+ "Mobile" \
+ "Embedded"
+
+project = QmlPropertyGroups
+url = https://doc.qt.io/${project}
+
+sourcedirs += .
+
+outputformats = HTML DocBook
+
+{HTML.nosubdirs,DocBook.nosubdirs} = true
+HTML.outputsubdir = qmlpropertygroups
+DocBook.outputsubdir = qmlpropertygroups-docbook
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/singleexec/singleexec.qdocconf b/src/qdoc/qdoc/tests/generatedoutput/testdata/singleexec/singleexec.qdocconf
new file mode 100644
index 000000000..7a3354329
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/singleexec/singleexec.qdocconf
@@ -0,0 +1,4 @@
+../configs/testcpp_singleexec.qdocconf
+
+../crossmodule/crossmodule_singleexec.qdocconf
+
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/TestCPP b/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/TestCPP
new file mode 100644
index 000000000..4ed786108
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/TestCPP
@@ -0,0 +1,5 @@
+#include "testcpp.h"
+
+#ifdef test_template
+# include "testtemplate.h"
+#endif
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/classlists.qdoc b/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/classlists.qdoc
new file mode 100644
index 000000000..84b543cb2
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/classlists.qdoc
@@ -0,0 +1,51 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+ \page obsolete-classes.html
+ \title Obsolete Classes
+
+ \section1 Classes with obsolete members
+ \generatelist obsoletecppmembers
+
+ \section2 TestQDoc
+*/
+
+/*!
+ \page autolinking.html
+ \title Autolinking
+
+ //! a section title that qualifies for autolinking
+ \section1 TestQDoc
+
+ The string TestQDoc links to the C++ namespace unless linking explicitly,
+ \l {#TestQDoc}{like this}, or \l {TestQDoc}{this}. Also,
+
+ Autolinks:
+
+ \list
+ \li TestQDoc::TestDerived
+ \endlist
+
+ Explicit links:
+
+ \list
+ \li \l [CPP] {TestQDoc::TestDerived}
+ \li \l {Obsolete Classes#TestQDoc}
+ \endlist
+
+ //! a section title shadowing a known property name
+ \section1 someProp
+*/
+
+/*!
+ \group cpptypes
+ \title Test C++ Types
+
+ \generatelist testgroup
+*/
+
+/*!
+ \externalpage https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command
+ \title reentrant
+*/
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/properties.qdoc b/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/properties.qdoc
new file mode 100644
index 000000000..2de2f93d1
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/properties.qdoc
@@ -0,0 +1,55 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+ \property TestQDoc::TestDerived::bindableProp
+ Some property.
+
+ \sa someProp
+*/
+
+/*!
+ \property TestQDoc::TestDerived::someProp
+ Another property.
+*/
+
+/*!
+ \property TestQDoc::TestDerived::name
+ \brief a name.
+*/
+
+/*!
+ \property TestQDoc::TestDerived::intProp
+ An integer property.
+*/
+
+/*!
+ \property TestQDoc::TestDerived::boolProp
+ A boolean property.
+*/
+
+/*!
+ \fn TestQDoc::TestDerived::invokeMe() const
+ \brief Something invokable.
+*/
+
+/*!
+ \qmlmodule TheModule
+*/
+
+/*!
+ \qmltype TheType
+ \instantiates TestQDoc::TestDerived
+ \inqmlmodule TheModule
+*/
+
+/*!
+ \qmlproperty string TheType::name
+ \brief Read-only status of this property is resolved from Q_PROPERTY.
+*/
+
+/*!
+ //! avoid link warnings for auto-generated links to QProperty
+ \externalpage https://wiki.qt.io/QProperty
+ \title QProperty
+*/
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/snippets/snippet_testcpp.cpp b/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/snippets/snippet_testcpp.cpp
new file mode 100644
index 000000000..1660fbc2b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/snippets/snippet_testcpp.cpp
@@ -0,0 +1,3 @@
+//! [random tag]
+You're not supposed to see this.
+//! [random tag]
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/testcpp.cpp b/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/testcpp.cpp
new file mode 100644
index 000000000..033db4791
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/testcpp.cpp
@@ -0,0 +1,418 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#include "testcpp.h"
+
+namespace TestQDoc {
+
+/*
+//! [random tag]
+\note This is just a test.
+//! [random tag]
+
+//! [args]
+\1\2 \3 \2\1
+//! [args]
+*/
+
+/*!
+ \module TestCPP
+ \qtvariable testcpp
+ \qtcmakepackage QDocTest
+ \title QDoc Test C++ Classes
+ \brief A test module page.
+ \since 2.0
+
+ \testnoautolist
+
+ \include testcpp.cpp random tag
+ \include testcpp.cpp {args} {/} {*} {Look, Ma! {I'm made of arguments!}}
+
+\if defined(test_nestedmacro)
+ \versionnote {module} {\ver 5.15.0}
+ \ver 1.0.0
+\endif
+
+ \section1 Linking to function-like things
+
+ \list
+ \li \l {TestQDoc::Test::someFunctionDefaultArg}
+ {someFunctionDefaultArg()}
+ \li QDOCTEST_MACRO2()
+ \li \l {TestQDoc::Test::}{QDOCTEST_MACRO2(int &x)}
+ \li \l {section()}
+ \li \l {section()} {section() is a section title}
+ \li \l {TestQDoc::Test::Test()} {open( parenthesis}
+ \li \l {https://en.cppreference.com/w/cpp/utility/move}
+ {C++11 added std::move(T&& t)}
+ \endlist
+
+ \section2 section()
+*/
+
+/*!
+ \namespace TestQDoc
+ \inheaderfile TestCPP
+ \inmodule TestCPP
+ \brief A namespace.
+
+ \section1 Usage
+ This namespace is for testing QDoc output.
+*/
+
+/*!
+ \class TestQDoc::Test
+ \inmodule TestCPP
+ \brief A class in a namespace.
+
+\if defined(test_ignoresince)
+ //! omitted by ignoresince
+ \since 1.1
+\endif
+ \ingroup testgroup
+ \ingroup cpptypes
+ \reentrant
+*/
+
+/*!
+ \fn TestQDoc::Test::Test()
+
+ Default constructor.
+*/
+
+/*!
+ \fn Test &Test::operator=(Test &&other)
+ \ingroup testgroup
+
+ Move-assigns \a other.
+*/
+
+/*!
+ \class TestQDoc::TestDerived
+ \inmodule TestCPP
+ \brief A class in a namespace, derived from \l [CPP] Test.
+*/
+
+/*!
+ \macro QDOCTEST_MACRO
+ \relates TestQDoc
+\if defined(test_ignoresince)
+ //! omitted by ignoresince.Test
+ \since Test 0.9
+\endif
+*/
+
+/*!
+ \macro QDOCTEST_MACRO2(int &x)
+ \relates TestQDoc::Test
+ \since Test 1.1
+ \brief A macro with argument \a x.
+ \ingroup testgroup
+*/
+
+/*!
+\if defined(test_properties)
+ \property TestQDoc::Test::id
+\else
+ \nothing
+\endif
+*/
+
+/*!
+ \deprecated [6.0] Use someFunction() instead.
+*/
+void Test::deprecatedMember()
+{
+ return;
+}
+
+/*!
+ \obsolete
+
+ Use someFunction() instead.
+*/
+void Test::obsoleteMember()
+{
+ return;
+}
+
+/*!
+ \obsolete Use obsoleteMember() instead.
+*/
+void Test::anotherObsoleteMember()
+{
+ return;
+}
+
+/*!
+ \nonreentrant
+ Function that takes a parameter \a i and \a b.
+\if defined(test_ignoresince)
+ \since 2.0
+\endif
+ \ingroup testgroup
+*/
+void Test::someFunctionDefaultArg(int i, bool b = false) const
+{
+ return;
+}
+
+/*!
+ \fn void Test::func(bool)
+ \internal
+*/
+
+/*!
+ \fn [funcPtr] void (*funcPtr(bool b, const char *s))(bool)
+
+ Returns a pointer to a function that takes a boolean. Uses \a b and \a s.
+*/
+
+/*!
+ \fn [op-inc] Test::operator++()
+ \fn [op-dec] Test::operator--()
+ \deprecated
+*/
+
+/*!
+ This method has en dashes in its documentation -- as you'll find
+ represented by \c{--} in the sources -- here and there. The important bit
+ to note is that when passed e.g. to the \\c command, the two hyphens are
+ processed as input to the command and not replaced by an en dash. This also
+ applies to code blocks, where otherwise, the decrement operator would get
+ completely borked:
+
+ \code
+ for (int i = 42; i > 0; --i)
+ // Do something cool during countdown.
+ \endcode
+
+ ...as it would be silly if this would output --i instead of \c {--i}.
+
+ -----------------------------------------------------------------------
+
+ It still allows people to add a bunch of dashes, though, without replacing
+ them all with a series of en dashes. Of course, they might want to use the
+ \\hr command instead, like this:
+ \hr
+
+ -- You can also start a new paragraph with an en dash, if you want to.
+
+ //! Self-referencing \sa-command for tests.
+ \sa methodWithEnDashInItsDocs
+*/
+void Test::methodWithEnDashInItsDocs()
+{
+ // Nothing to see here.
+}
+
+/*!
+ This method has em dashes in its documentation---as you'll find
+ represented by \c{---} in the sources---here and there. The important bit
+ to note is that when passed e.g. to the \\c command, the three hyphens are
+ processed as input to the command and not replaced by an em dash.
+
+ -----------------------------------------------------------------------
+
+ People can still add a bunch of dashes, though, without QDoc replacing
+ them all with a series of em dashes.
+
+ ---You can also start a new paragraph with an em dash, if you want to.
+
+ \sa methodWithEnDashInItsDocs
+
+*/
+void Test::methodWithEmDashInItsDocs()
+{
+ // Woah! Look at that!
+}
+
+// Documented below with an \fn command. Unnecessary but we support it, and it's used.
+int Test::someFunction(int, int v)
+{
+ return v;
+}
+
+/*!
+ \fn void TestQDoc::Test::inlineFunction()
+
+ \brief An inline function, documented using the \CMDFN QDoc command.
+*/
+
+/*!
+ \fn int Test::someFunction(int, int v = 0)
+
+ Function that takes a parameter \a v.
+ Also returns the value of \a v.
+\if defined(test_ignoresince)
+ \since Test 1.0
+\endif
+*/
+
+/*!
+ Function that must be reimplemented.
+*/
+void Test::virtualFun()
+{
+ return;
+}
+
+/*!
+ \fn bool Test::operator==(const Test &lhs, const Test &rhs)
+
+ Returns true if \a lhs and \a rhs are equal.
+*/
+
+/*!
+ \typedef TestQDoc::Test::SomeType
+ \brief A typedef.
+*/
+
+/*!
+ \reimp
+*/
+void TestDerived::virtualFun()
+{
+ return;
+}
+
+/*!
+ \fn TestQDoc::Test::overload()
+ \fn Test::overload(bool b)
+ //! The second overload should match even without the fully qualified path
+
+ Overloads that share a documentation comment, optionally taking
+ a parameter \a b.
+*/
+
+/*!
+ \fn Test::overload(bool b)
+ \since Test 1.2
+*/
+
+/*!
+ \typealias TestQDoc::TestDerived::DerivedType
+ An aliased typedef.
+*/
+
+/*!
+ \typedef TestQDoc::TestDerived::NotTypedef
+ I'm an alias, not a typedef.
+*/
+
+/*!
+ \obsolete
+
+ Static obsolete method.
+*/
+void TestDerived::staticObsoleteMember()
+{
+ return;
+}
+
+/*!
+\if defined(test_properties)
+ \fn void TestDerived::emitSomething()
+ Emitted when things happen.
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_properties)
+ \reimp
+\else
+ \nothing
+\endif
+*/
+int TestDerived::id()
+{
+ return 1;
+}
+
+/*!
+ Returns a value using an aliases type.
+*/
+TestDerived::NotTypedef TestDerived::someValue()
+{
+ return 0;
+}
+
+/*!
+\if defined(test_template)
+ \fn template <typename T1, typename T2> void TestQDoc::Test::funcTemplate(T1 a, T2 b)
+ \brief Function template with two parameters, \a a and \a b.
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_template)
+ \struct TestQDoc::Test::Struct
+ \inmodule TestCPP
+ \brief Templated struct.
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_template)
+ \typealias TestQDoc::Test::Specialized
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_template)
+ \class TestQDoc::Vec
+ \inmodule TestCPP
+ \brief Type alias that has its own reference.
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_template)
+ \macro Q_INVOKABLE
+ \relates TestQDoc::Test
+
+ This is a mock Q_INVOKABLE for the purpose of ensuring QDoc autolink to it
+ as expected.
+\else
+ \nothing
+\endif
+*/
+
+} // namespace TestQDoc
+
+
+/*!
+ \namespace CrossModuleRef
+ \inmodule TestCPP
+ \brief Namespace that has documented functions in multiple modules.
+ \since 3.0
+*/
+namespace CrossModuleRef {
+
+/*!
+ Document me!
+*/
+void documentMe()
+{
+}
+
+} // namespace CrossModuleRef
+
+/*!
+ \class DontLinkToMe
+ \inmodule TestCPP
+ \brief Class that does not generate documentation.
+*/
+
+/*!
+ \dontdocument (DontLinkToMe)
+*/
diff --git a/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/testcpp.h b/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/testcpp.h
new file mode 100644
index 000000000..a5b2d221f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/testdata/testcpp/testcpp.h
@@ -0,0 +1,137 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#pragma once
+
+#ifdef test_properties
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qproperty.h>
+#include <QtCore/qstring.h>
+#endif
+
+#define QDOCTEST_MACRO test
+#define QDOCTEST_MACRO2(x) (x) < 0 ? 0 : (x))
+
+namespace TestQDoc {
+
+class Test {
+#ifdef test_properties
+ Q_OBJECT
+ Q_PROPERTY(int id READ id)
+#endif
+public:
+
+#ifdef test_template
+template<typename D, typename T> struct Struct {};
+template<typename T>
+using Specialized = Struct<int, T>;
+#endif
+
+#ifdef test_template
+# define Q_INVOKABLE void foo() {};
+#endif
+
+#ifdef test_scopedenum
+ enum ClassicEnum { Yee, Haw, Howdy, Partner };
+
+ enum class ScopedEnum : unsigned char {
+ This = 0x01,
+ That = 0x02,
+ All = This | That,
+ OmittedValue = 99,
+ UselessValue,
+ VeryLastValue
+ };
+#endif
+ typedef struct {
+ int data;
+ } SomeType;
+ int someFunction(int, int v = 0);
+ void someFunctionDefaultArg(int i, bool b) const;
+ void obsoleteMember();
+ void anotherObsoleteMember();
+ void deprecatedMember();
+ void methodWithEnDashInItsDocs();
+ void methodWithEmDashInItsDocs();
+ void func(bool) {};
+ //! [funcPtr]
+ void (*funcPtr(bool b, const char *s))(bool) {
+ return func;
+ }
+ //! [op-inc]
+ Test &operator++() { return *this; }
+ //! [op-dec]
+ Test &operator--() { return *this; }
+
+ void anotherFunc() {};
+ inline void inlineFunction() {};
+ virtual void virtualFun();
+
+ friend bool operator==(const Test &lhs, const Test &rhs) { return false; }
+
+protected:
+ void overload() {}
+ void overload(bool b) { if (!b) return; }
+#ifdef test_template
+ template <typename T1, typename T2> void funcTemplate(T1 a, T2 b) {
+ a = b;
+ }
+#endif
+#ifdef test_properties
+ virtual int id() { return 0; }
+#endif
+};
+
+class TestDerived : public Test {
+#ifdef test_properties
+ Q_OBJECT
+
+ Q_PROPERTY(QString bindableProp READ bindableProp WRITE setBindableProp NOTIFY bindablePropChanged BINDABLE bindableProp)
+ Q_PROPERTY(QString someProp READ someProp BINDABLE somBindableProp)
+ Q_PROPERTY(int *intProp READ getInt STORED false CONSTANT FINAL)
+ Q_PROPERTY(const QString *name READ name)
+ QDOC_PROPERTY(bool boolProp READ boolProp WRITE setBoolProp NOTIFY boolPropChanged RESET resetBoolProp REVISION 1)
+#endif
+
+public:
+ using DerivedType = Test::SomeType;
+ using NotTypedef = int;
+ void virtualFun() override;
+ static void staticObsoleteMember();
+ NotTypedef someValue();
+#ifdef test_properties
+ QBindable<QString> bindableProp();
+ QBindable<QString> someBindableProp();
+ const QString &someProp();
+ int *getInt();
+ bool boolProp();
+ const QString *name() const;
+
+ Q_INVOKABLE void invokeMe() const {}
+ int id() override;
+
+Q_SIGNALS:
+ void emitSomething(QPrivateSignal);
+ void bindablePropChanged();
+ Q_REVISION(1) void boolPropChanged();
+
+public Q_SLOTS:
+ void setBindableProp(const QString &s);
+ void setBoolProp(bool b);
+ void resetBoolProp();
+#endif
+};
+
+#ifdef test_template
+template <typename T>
+struct BaseVec {};
+template <typename T>
+using Vec = BaseVec<T>;
+#endif
+
+} // namespace TestQDoc
+
+namespace CrossModuleRef {
+ void documentMe();
+}
+
+class DontLinkToMe {};
diff --git a/src/qdoc/qdoc/tests/generatedoutput/tst_generatedoutput.cpp b/src/qdoc/qdoc/tests/generatedoutput/tst_generatedoutput.cpp
new file mode 100644
index 000000000..07ded08ad
--- /dev/null
+++ b/src/qdoc/qdoc/tests/generatedoutput/tst_generatedoutput.cpp
@@ -0,0 +1,283 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#include <QProcess>
+#include <QTemporaryDir>
+#include <QDirIterator>
+#include <QtTest>
+
+class tst_generatedOutput : public QObject
+{
+ Q_OBJECT
+
+public:
+ void setRegenerate() { m_regen = true; }
+
+private slots:
+ void initTestCase();
+ void init();
+
+ // HTML generator
+ void htmlFromCpp();
+
+ // Output format independent tests
+ void inheritedQmlPropertyGroups();
+ void crossModuleLinking();
+ void indexLinking();
+ void includeFromExampleDirs();
+ void singleExec();
+ void preparePhase();
+ void generatePhase();
+ void noAutoList();
+
+private:
+ QScopedPointer<QTemporaryDir> m_outputDir;
+ QString m_qdoc;
+ QDir m_expectedDir;
+ QString m_extraParams;
+ bool m_regen = false;
+
+ void runQDocProcess(const QStringList &arguments);
+ void compareLineByLine(const QStringList &expectedFiles);
+ void testAndCompare(const char *input, const char *outNames, const char *extraParams = nullptr);
+ void copyIndexFiles();
+};
+
+void tst_generatedOutput::initTestCase()
+{
+ // Build the path to the QDoc binary the same way moc tests do for moc.
+ const auto binpath = QLibraryInfo::path(QLibraryInfo::BinariesPath);
+ const auto extension = QSysInfo::productType() == "windows" ? ".exe" : "";
+ m_qdoc = binpath + QLatin1String("/qdoc") + extension;
+ m_expectedDir.setPath(QFINDTESTDATA("expected_output"));
+
+ // Resolve the path to the file containing extra parameters
+ m_extraParams = QFileInfo(QTest::currentAppName()).dir().filePath("qdocincludepaths.inc");
+ if (!QFileInfo::exists(m_extraParams)) {
+ qWarning().nospace() << QStringLiteral("Cannot locate")
+ << qUtf8Printable(m_extraParams);
+ m_extraParams.clear();
+ } else {
+ m_extraParams.insert(0, '@');
+ }
+}
+
+void tst_generatedOutput::init()
+{
+ m_outputDir.reset(new QTemporaryDir());
+ if (!m_outputDir->isValid()) {
+ const QString errorMessage =
+ "Couldn't create temporary directory: " + m_outputDir->errorString();
+ QFAIL(qPrintable(errorMessage));
+ }
+}
+
+void tst_generatedOutput::runQDocProcess(const QStringList &arguments)
+{
+ QProcess qdocProcess;
+ qdocProcess.setProgram(m_qdoc);
+ qdocProcess.setArguments(arguments);
+
+ auto failQDoc = [&](QProcess::ProcessError) {
+ QFAIL(qPrintable(QStringLiteral("Running qdoc failed with exit code %1: %2")
+ .arg(qdocProcess.exitCode()).arg(qdocProcess.errorString())));
+ };
+ QObject::connect(&qdocProcess, &QProcess::errorOccurred, this, failQDoc);
+
+ qdocProcess.start();
+ qdocProcess.waitForFinished();
+ if (qdocProcess.exitCode() == 0)
+ return;
+
+ QString errors = qdocProcess.readAllStandardError();
+ if (!errors.isEmpty())
+ qInfo().nospace() << "Received errors:\n" << qUtf8Printable(errors);
+ if (!QTest::currentTestFailed())
+ failQDoc(QProcess::UnknownError);
+}
+
+void tst_generatedOutput::compareLineByLine(const QStringList &expectedFiles)
+{
+ for (const auto &file : expectedFiles) {
+ QString expected(m_expectedDir.filePath(file));
+ QString actual(m_outputDir->filePath(file));
+
+ QFile expectedFile(expected);
+ if (!expectedFile.open(QIODevice::ReadOnly))
+ QFAIL(qPrintable(QString("Cannot open expected data file: %1").arg(expected)));
+ QTextStream expectedIn(&expectedFile);
+
+ QFile actualFile(actual);
+ if (!actualFile.open(QIODevice::ReadOnly))
+ QFAIL(qPrintable(QString("Cannot open actual data file: %1").arg(actual)));
+ QTextStream actualIn(&actualFile);
+
+ const QLatin1String delim(": ");
+ int lineNumber = 0;
+ while (!expectedIn.atEnd() && !actualIn.atEnd()) {
+ lineNumber++;
+ QString prefix = file + delim + QString::number(lineNumber) + delim;
+ QString expectedLine = prefix + expectedIn.readLine();
+ QString actualLine = prefix + actualIn.readLine();
+ QCOMPARE(actualLine, expectedLine);
+ }
+ }
+}
+
+void tst_generatedOutput::testAndCompare(const char *input, const char *outNames,
+ const char *extraParams)
+{
+ QStringList args{ "-outputdir", m_outputDir->path() + "/", QFINDTESTDATA(input) };
+ if (extraParams)
+ args << QString(QLatin1String(extraParams)).split(QChar(' '));
+
+ runQDocProcess(args);
+
+ if (QTest::currentTestFailed())
+ return;
+
+ QStringList expectedOuts(QString(QLatin1String(outNames)).split(QChar(' ')));
+ if (m_regen) {
+ QVERIFY(m_expectedDir.mkpath(m_expectedDir.path()));
+ for (const auto &file : std::as_const(expectedOuts)) {
+ QFileInfo fileInfo(m_expectedDir.filePath(file));
+ fileInfo.dir().remove(fileInfo.fileName()); // Allowed to fail
+ QVERIFY(m_expectedDir.mkpath(fileInfo.dir().path()));
+ QVERIFY2(QFile::copy(m_outputDir->filePath(file), fileInfo.filePath()),
+ qPrintable(QStringLiteral("Failed to copy '%1'").arg(file)));
+ }
+ QSKIP("Regenerated expected output only.");
+ }
+
+ compareLineByLine(expectedOuts);
+}
+
+// Copy <project>.index to <project>/<project>.index in the outputdir
+void tst_generatedOutput::copyIndexFiles()
+{
+ QDirIterator it(m_outputDir->path(), QStringList("*.index"), QDir::Files, QDirIterator::Subdirectories);
+ while (it.hasNext()) {
+ QFileInfo fileInfo(it.next());
+ QDir indexDir(m_outputDir->path());
+ QVERIFY(indexDir.mkpath(fileInfo.baseName()));
+ QVERIFY(indexDir.cd(fileInfo.baseName()));
+ if (!indexDir.exists(fileInfo.fileName()))
+ QVERIFY(QFile::copy(fileInfo.filePath(), indexDir.filePath(fileInfo.fileName())));
+ }
+}
+
+void tst_generatedOutput::htmlFromCpp()
+{
+ testAndCompare("testdata/configs/testcpp.qdocconf",
+ "testcpp-module.html "
+ "testqdoc-test.html "
+ "testqdoc-test-members.html "
+ "testqdoc-test-obsolete.html "
+ "testqdoc-testderived.html "
+ "testqdoc-testderived-members.html "
+ "testqdoc-testderived-obsolete.html "
+ "obsolete-classes.html "
+ "autolinking.html "
+ "cpptypes.html "
+ "testqdoc.html");
+}
+
+void tst_generatedOutput::inheritedQmlPropertyGroups()
+{
+ testAndCompare("testdata/qmlpropertygroups/qmlpropertygroups.qdocconf",
+ "qmlpropertygroups/qml-qdoc-test-anotherchild-members.html "
+ "qmlpropertygroups/qml-qdoc-test-parent.html "
+ "qmlpropertygroups-docbook/qml-qdoc-test-parent.xml");
+}
+
+void tst_generatedOutput::indexLinking()
+{
+ {
+ QScopedValueRollback<bool> skipRegen(m_regen, false);
+ inheritedQmlPropertyGroups();
+ }
+ copyIndexFiles();
+ QString indexDir = QLatin1String("-indexdir ") + m_outputDir->path();
+ testAndCompare("testdata/indexlinking/indexlinking.qdocconf",
+ "index-linking.html "
+ "qml-linkmodule-grandchild-members.html",
+ indexDir.toLatin1().data());
+}
+
+void tst_generatedOutput::crossModuleLinking()
+{
+ {
+ QScopedValueRollback<bool> skipRegen(m_regen, false);
+ htmlFromCpp();
+ }
+ copyIndexFiles();
+ QString indexDir = QLatin1String("-indexdir ") + m_outputDir->path();
+ testAndCompare("testdata/crossmodule/crossmodule.qdocconf",
+ "crossmodule/testtype.html "
+ "crossmodule/testtype-members.html "
+ "crossmodule/crossmoduleref-sub-crossmodule.html",
+ indexDir.toLatin1().data());
+}
+
+void tst_generatedOutput::includeFromExampleDirs()
+{
+ testAndCompare("testdata/includefromexampledirs/includefromexampledirs.qdocconf",
+ "includefromexampledirs/index.html "
+ "includefromexampledirs/qml-qdoc-test-abstractparent.html "
+ "includefromexampledirs/qml-qdoc-test-abstractparent-members.html");
+}
+
+void tst_generatedOutput::singleExec()
+{
+ // Build both testcpp and crossmodule projects in single-exec mode
+ testAndCompare("testdata/singleexec/singleexec.qdocconf",
+ "testcpp/testcpp-module.html "
+ "testcpp/testqdoc-test.html "
+ "testcpp/testqdoc-test-members.html "
+ "testcpp/testqdoc.html "
+ "testcpp/crossmoduleref.html "
+ "crossmodule/crossmodule/all-namespaces.html "
+ "crossmodule/crossmodule/testtype.html "
+ "crossmodule/crossmodule/testtype-members.html",
+ "-single-exec");
+}
+
+void tst_generatedOutput::preparePhase()
+{
+ testAndCompare("testdata/configs/testcpp.qdocconf",
+ "testcpp.index",
+ "-prepare");
+}
+
+void tst_generatedOutput::generatePhase()
+{
+ testAndCompare("testdata/configs/testcpp.qdocconf",
+ "testcpp-module.html "
+ "testqdoc-test.html "
+ "testqdoc-test-members.html "
+ "testqdoc.html",
+ "-generate");
+}
+
+void tst_generatedOutput::noAutoList()
+{
+ testAndCompare("testdata/configs/noautolist.qdocconf",
+ "noautolist/testcpp-module.html "
+ "noautolist/test-componentset-example.html "
+ "noautolist/qdoc-test-qmlmodule.html "
+ "noautolist-docbook/testcpp-module.xml "
+ "noautolist-docbook/test-componentset-example.xml "
+ "noautolist-docbook/qdoc-test-qmlmodule.xml");
+}
+
+int main(int argc, char *argv[])
+{
+ tst_generatedOutput tc;
+ // Re-populate expected data and skip tests if option -regenerate is set
+ if (argc == 2 && QByteArray(argv[1]) == "-regenerate") {
+ tc.setRegenerate();
+ --argc;
+ }
+ return QTest::qExec(&tc, argc, argv);
+}
+
+#include "tst_generatedoutput.moc"
diff --git a/src/qdoc/qdoc/tests/qdoc/CMakeLists.txt b/src/qdoc/qdoc/tests/qdoc/CMakeLists.txt
new file mode 100644
index 000000000..03c177772
--- /dev/null
+++ b/src/qdoc/qdoc/tests/qdoc/CMakeLists.txt
@@ -0,0 +1,28 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_QDoc LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_test(tst_QDoc
+ SOURCES
+ ${CMAKE_CURRENT_LIST_DIR}/main.cpp
+
+ ${CMAKE_CURRENT_LIST_DIR}/boundaries/filesystem/catch_filepath.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/boundaries/filesystem/catch_directorypath.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/filesystem/catch_fileresolver.cpp
+
+ ${CMAKE_CURRENT_LIST_DIR}/../../src/qdoc/boundaries/filesystem/filepath.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/../../src/qdoc/boundaries/filesystem/directorypath.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/../../src/qdoc/boundaries/filesystem/resolvedfile.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/../../src/qdoc/filesystem/fileresolver.cpp
+ INCLUDE_DIRECTORIES
+ ${CMAKE_CURRENT_LIST_DIR}/../../src/
+ LIBRARIES
+ Qt::QDocCatchPrivate
+ Qt::QDocCatchConversionsPrivate
+ Qt::QDocCatchGeneratorsPrivate
+)
diff --git a/src/qdoc/qdoc/tests/qdoc/boundaries/filesystem/catch_directorypath.cpp b/src/qdoc/qdoc/tests/qdoc/boundaries/filesystem/catch_directorypath.cpp
new file mode 100644
index 000000000..d590c1c4f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/qdoc/boundaries/filesystem/catch_directorypath.cpp
@@ -0,0 +1,195 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <catch_conversions/qdoc_catch_conversions.h>
+
+#include <catch/catch.hpp>
+
+#include <qdoc/boundaries/filesystem/directorypath.h>
+
+#include <catch_generators/generators/path_generator.h>
+
+#include <QFileInfo>
+#include <QTemporaryDir>
+#include <QDir>
+#include <QIODeviceBase>
+#include <QRegularExpression>
+
+SCENARIO("Obtaining a DirectoryPath", "[DirectoryPath][Boundaries][Validation][Canonicalization][Path]") {
+
+ GIVEN("Any string representing a path that does not represent an existing element on the filesystem") {
+ QString path = GENERATE(take(100, filter([](auto path){ return !QFileInfo{path}.exists(); }, qdoc::catch_generators::native_path())));
+ CAPTURE(path);
+
+ WHEN("A DirectoryPath instance is requested from that string") {
+ auto maybe_directory_path{DirectoryPath::refine(path)};
+
+ THEN("A DirectoryPath instance is not obtained") {
+ REQUIRE(!maybe_directory_path);
+ }
+ }
+ }
+
+ GIVEN("Any string representing a path to a file") {
+ QString relative_path = GENERATE(take(100, qdoc::catch_generators::native_relative_file_path()));
+ CAPTURE(relative_path);
+
+ QTemporaryDir working_directory{};
+ REQUIRE(working_directory.isValid());
+
+ QString path_to_file = working_directory.path() + "/" + relative_path;
+
+ AND_GIVEN("That the path represents an existing file on the filesystem") {
+ REQUIRE(QDir{working_directory.path()}.mkpath(QFileInfo{relative_path}.path()));
+ REQUIRE(QFile{path_to_file}.open(QIODeviceBase::ReadWrite | QIODevice::NewOnly));
+
+ WHEN("A DirectoryPath instance is requested from that string") {
+ auto maybe_directory_path{DirectoryPath::refine(path_to_file)};
+
+ THEN("A DirectoryPath instance is not obtained") {
+ REQUIRE(!maybe_directory_path);
+ }
+ }
+ }
+ }
+
+#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
+
+ GIVEN("Any string representing a path to a directory") {
+ // REMARK: [relative-component-permissions]
+ // For tests where we change the permissions of the path, we
+ // want to avoid relative components in a final position.
+ // Relative components are actual objects on the filesystem in
+ // *nix systems.
+ // What this means is that to perform some operations on them,
+ // such as changing permissions, we need the correct
+ // permission in their containing or parent directory.
+ // When we change permissions for those files, the permissions
+ // for their containing or parent directory is actually
+ // changed.
+ // Depending on the way in which the permissions where
+ // changed, it may then be impossible to change them back, as
+ // the containing or parent directory might not provide the
+ // necessary permission to read or change the nodes that it
+ // contains.
+ // For tests in particular, this means that we are not able to
+ // ensure that the correct permissions will be available for
+ // the cleanup of the temporary directories that we need for
+ // testing.
+ // To avoid this situation, we filter out those paths that end
+ // in a relative component.
+ QString relative_path = GENERATE(take(100,
+ filter(
+ [](QString path){
+ QString last_component = path.split(QRegularExpression{R"(\/+)"}, Qt::SkipEmptyParts).last();
+ return (last_component != ".") && (last_component != "..");
+ },
+ qdoc::catch_generators::native_relative_file_path()
+ )
+ ));
+ CAPTURE(relative_path);
+
+ QTemporaryDir working_directory{};
+ REQUIRE(working_directory.isValid());
+
+ QString path_to_directory = working_directory.path() + "/" + relative_path;
+
+ AND_GIVEN("That the path represents an existing directory on the filesystem") {
+ REQUIRE(QDir{working_directory.path()}.mkpath(relative_path));
+
+ AND_GIVEN("That the directory represented by the path is not readable") {
+ REQUIRE(QFile::setPermissions(path_to_directory, QFileDevice::WriteOwner |
+ QFileDevice::ExeOwner |
+ QFileDevice::WriteGroup |
+ QFileDevice::ExeGroup |
+ QFileDevice::WriteOther |
+ QFileDevice::ExeOther));
+
+ CAPTURE(QFileInfo{path_to_directory}.isReadable());
+
+ WHEN("A DirectoryPath instance is requested from that string") {
+ auto maybe_directory_path{DirectoryPath::refine(path_to_directory)};
+
+ THEN("A DirectoryPath instance is not obtained") {
+ // REMARK: [temporary_directory_cleanup]
+ CHECK(!maybe_directory_path);
+ REQUIRE(QFile::setPermissions(path_to_directory, QFileDevice::WriteUser | QFileDevice::ReadUser | QFileDevice::ExeUser));
+ }
+ }
+ }
+
+ AND_GIVEN("That the directory represented by the path is not executable") {
+ REQUIRE(QFile::setPermissions(path_to_directory, QFileDevice::WriteOwner |
+ QFileDevice::ReadOwner |
+ QFileDevice::WriteGroup |
+ QFileDevice::ReadGroup |
+ QFileDevice::WriteOther |
+ QFileDevice::ReadOther));
+
+ WHEN("A DirectoryPath instance is requested from that string") {
+ auto maybe_directory_path{DirectoryPath::refine(path_to_directory)};
+
+ THEN("A DirectoryPath instance is not obtained") {
+ // REMARK: [temporary_directory_cleanup]
+ CHECK(!maybe_directory_path);
+ REQUIRE(QFile::setPermissions(path_to_directory, QFileDevice::WriteUser | QFileDevice::ReadUser | QFileDevice::ExeUser));
+ }
+ }
+ }
+
+ AND_GIVEN("That the directory represented by the path is readable and executable") {
+ REQUIRE(QFile::setPermissions(path_to_directory, QFileDevice::ReadOwner |
+ QFileDevice::ExeOwner |
+ QFileDevice::ReadGroup |
+ QFileDevice::ExeGroup |
+ QFileDevice::ReadOther |
+ QFileDevice::ExeOther));
+
+ WHEN("A DirectoryPath instance is requested from that string") {
+ auto maybe_directory_path{DirectoryPath::refine(path_to_directory)};
+
+ THEN("A DirectoryPath instance is obtained") {
+ // REMARK: [temporary_directory_cleanup]
+ // We restore all permission to
+ // ensure that the temporary directory can be
+ // automatically cleaned up.
+ CHECK(maybe_directory_path);
+ REQUIRE(QFile::setPermissions(path_to_directory, QFileDevice::WriteUser | QFileDevice::ReadUser | QFileDevice::ExeUser));
+ }
+ }
+ }
+ }
+ }
+
+#endif
+
+}
+
+SCENARIO("Inspecting the contents of a DirectoryPath", "[DirectoryPath][Boundaries][Canonicalization][Path][Contents]") {
+ GIVEN("Any string representing a path from which a DirectoryPath instance can be obtained") {
+ QString relative_path = GENERATE(take(100, qdoc::catch_generators::native_relative_directory_path()));
+ CAPTURE(relative_path);
+
+ QTemporaryDir working_directory{};
+ REQUIRE(working_directory.isValid());
+
+ QString path_to_directory = QFileInfo{working_directory.path() + "/" + relative_path}.filePath();
+
+ REQUIRE(QDir{working_directory.path()}.mkpath(relative_path));
+
+ AND_GIVEN("A DirectoryPath instance obtained from that path") {
+ auto maybe_directory_path{DirectoryPath::refine(path_to_directory)};
+ REQUIRE(maybe_directory_path);
+
+ auto directory_path{*maybe_directory_path};
+
+ WHEN("The path that the DirectoryPath contains is inspected") {
+ auto path{directory_path.value()};
+
+ THEN("That path is the same as the canonicazlied version of the path that the DirectoryPath was built from") {
+ REQUIRE(path == QFileInfo(path_to_directory).canonicalFilePath());
+ }
+ }
+ }
+ }
+}
diff --git a/src/qdoc/qdoc/tests/qdoc/boundaries/filesystem/catch_filepath.cpp b/src/qdoc/qdoc/tests/qdoc/boundaries/filesystem/catch_filepath.cpp
new file mode 100644
index 000000000..44d59017b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/qdoc/boundaries/filesystem/catch_filepath.cpp
@@ -0,0 +1,136 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <catch_conversions/qdoc_catch_conversions.h>
+
+#include <catch/catch.hpp>
+
+#include <qdoc/boundaries/filesystem/filepath.h>
+
+#include <catch_generators/generators/path_generator.h>
+
+#include <QFileInfo>
+#include <QTemporaryDir>
+#include <QDir>
+#include <QIODeviceBase>
+
+SCENARIO("Obtaining a FilePath", "[FilePath][Boundaries][Validation][Canonicalization][Path]") {
+
+ GIVEN("Any string representing a path that does not represent an existing element on the filesystem") {
+ QString path = GENERATE(take(100, filter([](auto path){ return !QFileInfo{path}.exists(); }, qdoc::catch_generators::native_path())));
+ CAPTURE(path);
+
+ WHEN("A FilePath instance is requested from that string") {
+ auto maybe_filepath{FilePath::refine(path)};
+
+ THEN("A FilePath instance is not obtained") {
+ REQUIRE(!maybe_filepath);
+ }
+ }
+ }
+
+ GIVEN("Any string representing a path to a directory") {
+ QString relative_path = GENERATE(take(100, qdoc::catch_generators::native_relative_directory_path()));
+ CAPTURE(relative_path);
+
+ QTemporaryDir working_directory{};
+ REQUIRE(working_directory.isValid());
+
+ QString path_to_directory = working_directory.path() + "/" + relative_path;
+
+ AND_GIVEN("That the path represents an existing directory on the filesystem") {
+ REQUIRE(QDir{working_directory.path()}.mkpath(relative_path));
+
+ WHEN("A FilePath instance is requested from that string") {
+ auto maybe_filepath{FilePath::refine(path_to_directory)};
+
+ THEN("A FilePath instance is not obtained") {
+ REQUIRE(!maybe_filepath);
+ }
+ }
+ }
+ }
+
+#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
+
+ GIVEN("Any string representing a path to a file") {
+ QString relative_path = GENERATE(take(100, qdoc::catch_generators::native_relative_file_path()));
+ CAPTURE(relative_path);
+
+ QTemporaryDir working_directory{};
+ REQUIRE(working_directory.isValid());
+
+ QString path_to_file = working_directory.path() + "/" + relative_path;
+
+ AND_GIVEN("That the path represents an existing file on the filesystem") {
+ REQUIRE(QDir{working_directory.path()}.mkpath(QFileInfo{relative_path}.path()));
+ REQUIRE(QFile{path_to_file}.open(QIODeviceBase::ReadWrite | QIODeviceBase::NewOnly));
+
+ AND_GIVEN("That the file represented by the path is not readable") {
+ REQUIRE(QFile::setPermissions(path_to_file, QFileDevice::WriteOwner |
+ QFileDevice::ExeOwner |
+ QFileDevice::WriteGroup |
+ QFileDevice::ExeGroup |
+ QFileDevice::WriteOther |
+ QFileDevice::ExeOther));
+
+ WHEN("A FilePath instance is requested from that string") {
+ auto maybe_filepath{FilePath::refine(path_to_file)};
+
+ THEN("A FilePath instance is not obtained") {
+ // REMARK: [temporary_directory_cleanup]
+ CHECK(!maybe_filepath);
+ REQUIRE(QFile::setPermissions(path_to_file, QFileDevice::WriteUser | QFileDevice::ReadUser | QFileDevice::ExeUser));
+ }
+ }
+ }
+
+ AND_GIVEN("That the file represented by the path is readable") {
+ REQUIRE(QFile::setPermissions(path_to_file, QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther));
+
+ WHEN("A FilePath instance is requested from that string") {
+ auto maybe_filepath{FilePath::refine(path_to_file)};
+
+ THEN("A FilePath instance is obtained") {
+ // REMARK: [temporary_directory_cleanup]
+ CHECK(maybe_filepath);
+ REQUIRE(QFile::setPermissions(path_to_file, QFileDevice::WriteUser | QFileDevice::ReadUser | QFileDevice::ExeUser));
+ }
+ }
+ }
+ }
+ }
+
+#endif
+
+}
+
+SCENARIO("Inspecting the contents of a FilePath", "[FilePath][Boundaries][Canonicalization][Path][Contents]") {
+ GIVEN("Any string representing a path from which a FilePath instance can be obtained") {
+ QString relative_path = GENERATE(take(100, qdoc::catch_generators::native_relative_file_path()));
+ CAPTURE(relative_path);
+
+ QTemporaryDir working_directory{};
+ REQUIRE(working_directory.isValid());
+
+ QString path_to_file = QFileInfo{working_directory.path() + "/" + relative_path}.filePath();
+
+ REQUIRE(QDir{working_directory.path()}.mkpath(QFileInfo{relative_path}.path()));
+ REQUIRE(QFile{path_to_file}.open(QIODeviceBase::ReadWrite | QIODeviceBase::NewOnly));
+
+ AND_GIVEN("A FilePath instance obtained from that path") {
+ auto maybe_filepath{FilePath::refine(path_to_file)};
+ REQUIRE(maybe_filepath);
+
+ auto filepath{*maybe_filepath};
+
+ WHEN("The path that the FilePath contains is inspected") {
+ auto path{filepath.value()};
+
+ THEN("That path is the same as the canonicazlied version of the path that the FilePath was built from") {
+ REQUIRE(path == QFileInfo(path_to_file).canonicalFilePath());
+ }
+ }
+ }
+ }
+}
diff --git a/src/qdoc/qdoc/tests/qdoc/filesystem/catch_fileresolver.cpp b/src/qdoc/qdoc/tests/qdoc/filesystem/catch_fileresolver.cpp
new file mode 100644
index 000000000..f9b5af66b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/qdoc/filesystem/catch_fileresolver.cpp
@@ -0,0 +1,307 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <catch_conversions/qdoc_catch_conversions.h>
+
+#include <catch/catch.hpp>
+
+#include <qdoc/filesystem/fileresolver.h>
+
+#include <catch_generators/generators/path_generator.h>
+
+#include <vector>
+#include <algorithm>
+#include <random>
+
+#include <QTemporaryDir>
+#include <QFileInfo>
+#include <QDir>
+#include <QIODeviceBase>
+
+SCENARIO("Inspecting the directories that will be used for searching", "[ResolvingFiles][Directory][Path][Canonicalization][Contents]") {
+ GIVEN("Some collection of paths representing existing directories on the filesystem") {
+ std::size_t directories_amount = GENERATE(take(10, random(2, 10)));
+
+ std::vector<QTemporaryDir> working_directories(directories_amount);
+
+ std::vector<DirectoryPath> directories;
+ directories.reserve(directories_amount);
+
+ std::transform(
+ working_directories.begin(), working_directories.end(),
+ std::back_inserter(directories),
+ [](auto& dir){ return *DirectoryPath::refine(dir.path()); }
+ );
+
+ AND_GIVEN("That the collection of those paths is ordered and contains no duplicates") {
+ std::sort(directories.begin(), directories.end());
+ directories.erase(std::unique(directories.begin(), directories.end()), directories.end());
+
+ WHEN("A mean of searching for files is obtained from that collection") {
+ FileResolver file_resolver{std::vector(directories)};
+
+ THEN("The collection of directories that will be used for searching is equivalent to the one from which a mean of searching for files was obtained") {
+ REQUIRE(file_resolver.get_search_directories() == directories);
+ }
+ }
+ }
+
+ AND_GIVEN("That the collection of those paths is potentially unordered but contains no duplicates") {
+ std::sort(directories.begin(), directories.end());
+ directories.erase(std::unique(directories.begin(), directories.end()), directories.end());
+
+ std::shuffle(directories.begin(), directories.end(), std::mt19937{std::random_device{}()});
+
+ WHEN("A mean of searching for files is obtained from that collection") {
+ FileResolver file_resolver{std::vector(directories)};
+
+ THEN("The collection of directories that will be used for searching is equivalent to the one from which a mean of searching for files was obtained if it was sorted") {
+ std::sort(directories.begin(), directories.end());
+
+ REQUIRE(file_resolver.get_search_directories() == directories);
+ }
+ }
+ }
+
+ AND_GIVEN("That the collection of those paths is ordered but contains duplicates") {
+ directories.reserve(directories.size());
+
+ std::transform(
+ working_directories.begin(), working_directories.end(),
+ std::back_inserter(directories),
+ [](auto& dir){ return *DirectoryPath::refine(dir.path()); }
+ );
+
+ std::sort(directories.begin(), directories.end());
+
+ WHEN("A mean of searching for files is obtained from that collection") {
+ FileResolver file_resolver{std::vector(directories)};
+
+ THEN("The collection of directories that will be used for searching is equivalent to the one from which a mean of searching for files was obtained if it contained no duplicates") {
+ directories.erase(std::unique(directories.begin(), directories.end()), directories.end());
+
+ REQUIRE(file_resolver.get_search_directories() == directories);
+ }
+ }
+ }
+
+ AND_GIVEN("That the collection of those paths is potentially unordered and contains duplicates") {
+ directories.reserve(directories.size());
+
+ std::transform(
+ working_directories.begin(), working_directories.end(),
+ std::back_inserter(directories),
+ [](auto& dir){ return *DirectoryPath::refine(dir.path()); }
+ );
+
+ std::shuffle(directories.begin(), directories.end(), std::mt19937{std::random_device{}()});
+
+ WHEN("A mean of searching for files is obtained from that collection") {
+ FileResolver file_resolver{std::vector(directories)};
+
+ THEN("The collection of directories that will be used for searching is equivalent to the one from which a mean of searching for files was obtained if it was sorted and it contained no duplicates") {
+ std::sort(directories.begin(), directories.end());
+ directories.erase(std::unique(directories.begin(), directories.end()), directories.end());
+
+ REQUIRE(file_resolver.get_search_directories() == directories);
+ }
+ }
+ }
+ }
+}
+
+SCENARIO("Finding a file based on some root search directories", "[ResolvingFiles][File][Path][Validation]") {
+ // TODO: Rewrite those tests under a single setup. Be careful of
+ // how this is done as Catch will rerun sections only under
+ // specific condition such that we may incur in collisions for the
+ // path if done incorrectly.
+ GIVEN("Some directory on the filesystem") {
+ QTemporaryDir working_directory{};
+ REQUIRE(working_directory.isValid());
+
+ DirectoryPath directory{*DirectoryPath::refine(working_directory.path())};
+
+ AND_GIVEN("A mean of searching for files based on that directory") {
+ FileResolver file_resolver{std::vector{directory}};
+
+ AND_GIVEN("A relative path that does not represent an element on the filesystem that is reachable from that directory") {
+ QString relative_path = GENERATE(filter([](auto& path){ return path != "." && path != ".."; }, take(100, qdoc::catch_generators::native_relative_path())));
+ CAPTURE(relative_path);
+
+ REQUIRE(!QFileInfo{working_directory.path() + '/' + relative_path}.exists());
+
+ WHEN("The relative path is used as a query to resolve a file") {
+ auto maybe_resolved_file{file_resolver.resolve(relative_path)};
+
+ THEN("The query cannot be resolved") {
+ REQUIRE(!maybe_resolved_file);
+ }
+ }
+ }
+ }
+ }
+
+ GIVEN("Some directory on the filesystem") {
+ QTemporaryDir working_directory{};
+ REQUIRE(working_directory.isValid());
+
+ DirectoryPath directory{*DirectoryPath::refine(working_directory.path())};
+
+ AND_GIVEN("A mean of searching for files based on that directory") {
+ FileResolver file_resolver{std::vector{directory}};
+
+ AND_GIVEN("A relative path that represents an existing directory on the filesystem that is reachable from that directory") {
+ QString relative_path = GENERATE(take(100, qdoc::catch_generators::native_relative_directory_path()));
+ CAPTURE(relative_path);
+
+ REQUIRE(QDir{working_directory.path()}.mkpath(relative_path));
+
+ WHEN("The relative path is used as a query to resolve a file") {
+ auto maybe_resolved_file{file_resolver.resolve(relative_path)};
+
+ THEN("The query cannot be resolved") {
+ REQUIRE(!maybe_resolved_file);
+ }
+ }
+ }
+ }
+ }
+
+
+ GIVEN("Some directory on the filesystem") {
+ QTemporaryDir working_directory{};
+ REQUIRE(working_directory.isValid());
+
+ DirectoryPath directory{*DirectoryPath::refine(working_directory.path())};
+
+ AND_GIVEN("A mean of searching for files based on that directory") {
+ FileResolver file_resolver{std::vector{directory}};
+
+ AND_GIVEN("A relative path that represents an existing file on the filesystem that is reachable from that directory") {
+ QString relative_path = GENERATE(take(100, qdoc::catch_generators::native_relative_file_path()));
+ CAPTURE(relative_path);
+
+ REQUIRE(QDir{working_directory.path()}.mkpath(QFileInfo{relative_path}.path()));
+ REQUIRE(QFile{working_directory.path() + "/" + relative_path}.open(QIODeviceBase::ReadWrite | QIODeviceBase::NewOnly));
+
+ WHEN("The relative path is used as a query to resolve a file") {
+ auto maybe_resolved_file{file_resolver.resolve(relative_path)};
+
+ THEN("The query can be resolved") {
+ REQUIRE(maybe_resolved_file);
+ }
+ }
+ }
+ }
+ }
+
+ GIVEN("Some directories on the filesystem") {
+ std::size_t directories_amount = GENERATE(take(10, random(2, 10)));
+
+ std::vector<QTemporaryDir> working_directories(directories_amount);
+ REQUIRE(std::all_of(working_directories.cbegin(), working_directories.cend(), [](auto& dir){ return dir.isValid(); }));
+
+ std::vector<DirectoryPath> directories;
+ directories.reserve(directories_amount);
+
+ std::transform(
+ working_directories.begin(), working_directories.end(),
+ std::back_inserter(directories),
+ [](auto& dir){ return *DirectoryPath::refine(dir.path()); }
+ );
+
+ AND_GIVEN("A relative path that represents an existing file on the filesystem that is reachable from exactly one of those directories") {
+ QString relative_path = GENERATE(take(10, qdoc::catch_generators::native_relative_file_path()));
+ CAPTURE(relative_path);
+
+ std::size_t containing_directory_index = GENERATE_COPY(take(1, random(std::size_t{0}, directories_amount - 1)));
+ CAPTURE(containing_directory_index);
+ CAPTURE(working_directories[containing_directory_index].path());
+
+ REQUIRE(QDir{working_directories[containing_directory_index].path()}.mkpath(QFileInfo{relative_path}.path()));
+ REQUIRE(QFile{working_directories[containing_directory_index].path() + "/" + relative_path}.open(QIODeviceBase::ReadWrite | QIODeviceBase::NewOnly));
+
+ AND_GIVEN("A mean of searching for files based on all of those directories") {
+ FileResolver file_resolver{std::move(directories)};
+
+ WHEN("The relative path is used as a query to resolve a file") {
+ auto maybe_resolved_file{file_resolver.resolve(relative_path)};
+
+ THEN("The query can be resolved") {
+ REQUIRE(maybe_resolved_file);
+ }
+ }
+ }
+ }
+ }
+}
+
+SCENARIO("Inspecting the content of a file that was resolved", "[ResolvingFiles][File][Path][Validation][Contents]") {
+ GIVEN("A mean of resolving files based on some directory") {
+ QTemporaryDir working_directory{};
+ REQUIRE(working_directory.isValid());
+
+ DirectoryPath directory{*DirectoryPath::refine(working_directory.path())};
+
+ FileResolver file_resolver{std::vector{directory}};
+
+ AND_GIVEN("A relative path that represents an existing file on the filesystem that is reachable from that directory") {
+ QString relative_path = GENERATE(take(100, qdoc::catch_generators::native_relative_file_path()));
+ CAPTURE(relative_path);
+
+ REQUIRE(QDir{working_directory.path()}.mkpath(QFileInfo{relative_path}.path()));
+ REQUIRE(QFile{working_directory.path() + "/" + relative_path}.open(QIODeviceBase::ReadWrite | QIODeviceBase::NewOnly));
+
+ WHEN("A file is resolved using that path as a query") {
+ auto resolved_file{*file_resolver.resolve(relative_path)};
+
+ THEN("The resolved file contains the query that was used to resolve it") {
+ REQUIRE(resolved_file.get_query() == relative_path);
+ }
+
+ THEN("The resolved file contains a canonicalized path pointing to the resolved file") {
+ REQUIRE(resolved_file.get_path() == QFileInfo{working_directory.path() + "/" + relative_path}.canonicalFilePath());
+ }
+ }
+ }
+ }
+}
+
+TEST_CASE(
+ "When a query can be resolved in more than one search directory, it is resolved in the greatest lower bound of the set of directories",
+ "[ResolvingFiles][File][Path][Validation][SpecialCase]"
+) {
+ std::size_t directories_amount = GENERATE(take(10, random(2, 10)));
+
+ QString relative_path = GENERATE(take(10, qdoc::catch_generators::native_relative_file_path()));
+ CAPTURE(relative_path);
+
+ std::vector<QTemporaryDir> working_directories(directories_amount);
+ REQUIRE(std::all_of(working_directories.cbegin(), working_directories.cend(), [](auto& dir){ return dir.isValid(); }));
+
+ for (const auto& directory : working_directories) {
+ CAPTURE(directory.path());
+ REQUIRE(QDir{directory.path()}.mkpath(QFileInfo{relative_path}.path()));
+ REQUIRE(QFile{directory.path() + "/" + relative_path}.open(QIODeviceBase::ReadWrite | QIODeviceBase::NewOnly));
+ }
+
+ std::vector<DirectoryPath> directories;
+ directories.reserve(directories_amount);
+
+ std::transform(
+ working_directories.begin(), working_directories.end(),
+ std::back_inserter(directories),
+ [](auto& dir){ return *DirectoryPath::refine(dir.path()); }
+ );
+
+ FileResolver file_resolver{std::move(directories)};
+ auto resolved_file{*file_resolver.resolve(relative_path)};
+
+ auto& greatest_lower_bound{*std::min_element(file_resolver.get_search_directories().cbegin(), file_resolver.get_search_directories().cend())};
+
+ REQUIRE(
+ resolved_file.get_path()
+ ==
+ QFileInfo{greatest_lower_bound.value() + "/" + relative_path}.canonicalFilePath()
+ );
+}
diff --git a/src/qdoc/qdoc/tests/qdoc/main.cpp b/src/qdoc/qdoc/tests/qdoc/main.cpp
new file mode 100644
index 000000000..5f85451f6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/qdoc/main.cpp
@@ -0,0 +1,12 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#define CATCH_CONFIG_RUNNER
+#include <catch/catch.hpp>
+
+// A custom main was provided to avoid linking errors when using minGW
+// that were appearing in CI.
+// See https://github.com/catchorg/Catch2/issues/1287
+int main(int argc, char* argv[]) {
+ return Catch::Session().run(argc, argv);
+}
diff --git a/src/qdoc/qdoc/tests/qdoccommandlineparser/CMakeLists.txt b/src/qdoc/qdoc/tests/qdoccommandlineparser/CMakeLists.txt
new file mode 100644
index 000000000..4adb376d9
--- /dev/null
+++ b/src/qdoc/qdoc/tests/qdoccommandlineparser/CMakeLists.txt
@@ -0,0 +1,41 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+
+#####################################################################
+## tst_qdoccommandlineparser Test:
+#####################################################################
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_qdoccommandlineparser LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_test(tst_qdoccommandlineparser
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+ SOURCES
+ ${CMAKE_CURRENT_LIST_DIR}/tst_qdoccommandlineparser.cpp
+
+ ${CMAKE_CURRENT_LIST_DIR}/../../src/qdoc/qdoccommandlineparser.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/../../src/qdoc/utilities.cpp
+ INCLUDE_DIRECTORIES
+ ${CMAKE_CURRENT_LIST_DIR}/../../src/
+)
+
+# Resources:
+set_source_files_properties("${CMAKE_CURRENT_LIST_DIR}/tst_arguments.txt"
+ PROPERTIES QT_RESOURCE_ALIAS "tst_arguments.txt"
+)
+
+set(tst_qdoccommandlineparser_resource_files
+ "${CMAKE_CURRENT_LIST_DIR}/tst_arguments.txt"
+)
+
+qt_internal_add_resource(tst_qdoccommandlineparser "tst_qdoccommandlineparser"
+ PREFIX
+ "/"
+ FILES
+ ${tst_qdoccommandlineparser_resource_files}
+)
+
diff --git a/src/qdoc/qdoc/tests/qdoccommandlineparser/tst_arguments.txt b/src/qdoc/qdoc/tests/qdoccommandlineparser/tst_arguments.txt
new file mode 100644
index 000000000..5797de394
--- /dev/null
+++ b/src/qdoc/qdoc/tests/qdoccommandlineparser/tst_arguments.txt
@@ -0,0 +1,22 @@
+-outputdir
+/src/qt5/qtbase/doc/qtgamepad
+-installdir
+/src/qt5/qtbase/doc
+/src/qt5/qtgamepad/src/gamepad/doc/qtgamepad.qdocconf
+-prepare
+-indexdir
+/src/qt5/qtbase/doc
+-no-link-errors
+-I.
+-I/src/qt5/qtbase/include
+-I/src/qt5/qtbase/include/QtGamepad
+-I/src/qt5/qtbase/include/QtGamepad/5.14.0
+-I/src/qt5/qtbase/include/QtGamepad/5.14.0/QtGamepad
+-I/src/qt5/qtbase/include/QtCore/5.14.0
+-I/src/qt5/qtbase/include/QtCore/5.14.0/QtCore
+-I/src/qt5/qtbase/include/QtGui
+-I/src/qt5/qtbase/include/QtCore
+-I.moc
+-isystem
+/usr/include/libdrm
+-I/src/qt5/qtbase/mkspecs/linux-g++
diff --git a/src/qdoc/qdoc/tests/qdoccommandlineparser/tst_qdoccommandlineparser.cpp b/src/qdoc/qdoc/tests/qdoccommandlineparser/tst_qdoccommandlineparser.cpp
new file mode 100644
index 000000000..e78de0425
--- /dev/null
+++ b/src/qdoc/qdoc/tests/qdoccommandlineparser/tst_qdoccommandlineparser.cpp
@@ -0,0 +1,161 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qdoc/qdoccommandlineparser.h"
+
+#include <QtCore/qstringlist.h>
+#include <QtTest/QtTest>
+
+class tst_QDocCommandLineParser : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void defaultConstructor();
+ void process();
+ void argumentsFromCommandLineAndFile();
+};
+
+void tst_QDocCommandLineParser::defaultConstructor()
+{
+ QDocCommandLineParser parser;
+
+ QVERIFY2(parser.applicationDescription() == QStringLiteral("Qt documentation generator"),
+ "The application description is incorrect.");
+}
+
+void tst_QDocCommandLineParser::process()
+{
+ const QStringList arguments =
+ QStringLiteral("/src/qt5/qtbase/bin/qdoc "
+ "-outputdir "
+ "/src/qt5/qtbase/doc/qtgamepad "
+ "-installdir "
+ "/src/qt5/qtbase/doc "
+ "/src/qt5/qtgamepad/src/gamepad/doc/qtgamepad.qdocconf "
+ "-prepare "
+ "-indexdir "
+ "/src/qt5/qtbase/doc "
+ "-no-link-errors "
+ "-I. "
+ "-I/src/qt5/qtbase/include "
+ "-I/src/qt5/qtbase/include/QtGamepad "
+ "-I/src/qt5/qtbase/include/QtGamepad/5.14.0 "
+ "-I/src/qt5/qtbase/include/QtGamepad/5.14.0/QtGamepad "
+ "-I/src/qt5/qtbase/include/QtCore/5.14.0 "
+ "-I/src/qt5/qtbase/include/QtCore/5.14.0/QtCore "
+ "-I/src/qt5/qtbase/include/QtGui "
+ "-I/src/qt5/qtbase/include/QtCore "
+ "-I.moc "
+ "-isystem "
+ "/usr/include/libdrm "
+ "-I/src/qt5/qtbase/mkspecs/linux-g++"
+ ).split(QString(" "));
+ const QStringList expectedIncludePaths =
+ QStringLiteral(". "
+ "/src/qt5/qtbase/include "
+ "/src/qt5/qtbase/include/QtGamepad "
+ "/src/qt5/qtbase/include/QtGamepad/5.14.0 "
+ "/src/qt5/qtbase/include/QtGamepad/5.14.0/QtGamepad "
+ "/src/qt5/qtbase/include/QtCore/5.14.0 "
+ "/src/qt5/qtbase/include/QtCore/5.14.0/QtCore "
+ "/src/qt5/qtbase/include/QtGui "
+ "/src/qt5/qtbase/include/QtCore "
+ ".moc "
+ "/src/qt5/qtbase/mkspecs/linux-g++"
+ ).split(QString(" "));
+ const QStringList expectedSystemIncludePath(QStringLiteral("/usr/include/libdrm"));
+
+ QDocCommandLineParser parser;
+ parser.process(arguments);
+
+ QVERIFY(parser.isSet(parser.outputDirOption));
+ QCOMPARE(parser.value(parser.outputDirOption), QStringLiteral("/src/qt5/qtbase/doc/qtgamepad"));
+ QVERIFY(parser.isSet(parser.installDirOption));
+ QCOMPARE(parser.value(parser.installDirOption), QStringLiteral("/src/qt5/qtbase/doc"));
+ QVERIFY(parser.isSet(parser.prepareOption));
+ QVERIFY(parser.isSet(parser.indexDirOption));
+ QCOMPARE(parser.value(parser.indexDirOption), QStringLiteral("/src/qt5/qtbase/doc"));
+ QVERIFY(parser.isSet(parser.noLinkErrorsOption));
+ QVERIFY(parser.isSet(parser.includePathOption));
+ QCOMPARE(parser.values(parser.includePathOption), expectedIncludePaths);
+ QVERIFY(parser.isSet(parser.includePathSystemOption));
+ QCOMPARE(parser.values(parser.includePathSystemOption), expectedSystemIncludePath);
+
+ QVERIFY(!parser.isSet(parser.timestampsOption));
+ QVERIFY(!parser.isSet(parser.dependsOption));
+ QVERIFY(!parser.isSet(parser.highlightingOption));
+ QVERIFY(!parser.isSet(parser.showInternalOption));
+ QVERIFY(!parser.isSet(parser.redirectDocumentationToDevNullOption));
+ QVERIFY(!parser.isSet(parser.noExamplesOption));
+ QVERIFY(!parser.isSet(parser.autoLinkErrorsOption));
+ QVERIFY(!parser.isSet(parser.debugOption));
+ QVERIFY(!parser.isSet(parser.generateOption));
+ QVERIFY(!parser.isSet(parser.logProgressOption));
+ QVERIFY(!parser.isSet(parser.singleExecOption));
+ QVERIFY(!parser.isSet(parser.frameworkOption));
+
+ const QStringList expectedPositionalArgument = {
+ QStringLiteral("/src/qt5/qtgamepad/src/gamepad/doc/qtgamepad.qdocconf")
+ };
+ QCOMPARE(parser.positionalArguments(), expectedPositionalArgument);
+}
+
+void tst_QDocCommandLineParser::argumentsFromCommandLineAndFile()
+{
+ const QString atFilePath("@" + QFINDTESTDATA("tst_arguments.txt"));
+ const QStringList arguments { "/src/qt5/qtbase/bin/qdoc", atFilePath };
+
+ QDocCommandLineParser parser;
+ parser.process(arguments);
+
+ const QStringList expectedIncludePaths =
+ QStringLiteral(". "
+ "/src/qt5/qtbase/include "
+ "/src/qt5/qtbase/include/QtGamepad "
+ "/src/qt5/qtbase/include/QtGamepad/5.14.0 "
+ "/src/qt5/qtbase/include/QtGamepad/5.14.0/QtGamepad "
+ "/src/qt5/qtbase/include/QtCore/5.14.0 "
+ "/src/qt5/qtbase/include/QtCore/5.14.0/QtCore "
+ "/src/qt5/qtbase/include/QtGui "
+ "/src/qt5/qtbase/include/QtCore "
+ ".moc "
+ "/src/qt5/qtbase/mkspecs/linux-g++"
+ ).split(QString(" "));
+ const QStringList expectedSystemIncludePath(QStringLiteral("/usr/include/libdrm"));
+ const QStringList expectedPositionalArgument = {
+ QStringLiteral("/src/qt5/qtgamepad/src/gamepad/doc/qtgamepad.qdocconf")
+ };
+
+ QVERIFY(parser.isSet(parser.outputDirOption));
+ QCOMPARE(parser.value(parser.outputDirOption), QStringLiteral("/src/qt5/qtbase/doc/qtgamepad"));
+ QVERIFY(parser.isSet(parser.installDirOption));
+ QCOMPARE(parser.value(parser.installDirOption), QStringLiteral("/src/qt5/qtbase/doc"));
+ QVERIFY(parser.isSet(parser.prepareOption));
+ QVERIFY(parser.isSet(parser.indexDirOption));
+ QCOMPARE(parser.value(parser.indexDirOption), QStringLiteral("/src/qt5/qtbase/doc"));
+ QVERIFY(parser.isSet(parser.noLinkErrorsOption));
+ QVERIFY(parser.isSet(parser.includePathOption));
+ QCOMPARE(parser.values(parser.includePathOption), expectedIncludePaths);
+ QVERIFY(parser.isSet(parser.includePathSystemOption));
+ QCOMPARE(parser.values(parser.includePathSystemOption), expectedSystemIncludePath);
+
+ QVERIFY(!parser.isSet(parser.timestampsOption));
+ QVERIFY(!parser.isSet(parser.dependsOption));
+ QVERIFY(!parser.isSet(parser.highlightingOption));
+ QVERIFY(!parser.isSet(parser.showInternalOption));
+ QVERIFY(!parser.isSet(parser.redirectDocumentationToDevNullOption));
+ QVERIFY(!parser.isSet(parser.noExamplesOption));
+ QVERIFY(!parser.isSet(parser.autoLinkErrorsOption));
+ QVERIFY(!parser.isSet(parser.debugOption));
+ QVERIFY(!parser.isSet(parser.generateOption));
+ QVERIFY(!parser.isSet(parser.logProgressOption));
+ QVERIFY(!parser.isSet(parser.singleExecOption));
+ QVERIFY(!parser.isSet(parser.frameworkOption));
+
+ QCOMPARE(parser.positionalArguments(), expectedPositionalArgument);
+}
+
+QTEST_APPLESS_MAIN(tst_QDocCommandLineParser)
+
+#include "tst_qdoccommandlineparser.moc"
diff --git a/src/qdoc/qdoc/tests/utilities/CMakeLists.txt b/src/qdoc/qdoc/tests/utilities/CMakeLists.txt
new file mode 100644
index 000000000..dd2883d32
--- /dev/null
+++ b/src/qdoc/qdoc/tests/utilities/CMakeLists.txt
@@ -0,0 +1,21 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## tst_utilities Test:
+#####################################################################
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_utilities LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_test(tst_utilities
+ SOURCES
+ ${CMAKE_CURRENT_LIST_DIR}/tst_utilities.cpp
+
+ ${CMAKE_CURRENT_LIST_DIR}/../../src/qdoc/utilities.cpp
+ INCLUDE_DIRECTORIES
+ ${CMAKE_CURRENT_LIST_DIR}/../../src/
+)
diff --git a/src/qdoc/qdoc/tests/utilities/tst_utilities.cpp b/src/qdoc/qdoc/tests/utilities/tst_utilities.cpp
new file mode 100644
index 000000000..160e4d15c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/utilities/tst_utilities.cpp
@@ -0,0 +1,134 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qdoc/utilities.h"
+
+#include <QtTest/QtTest>
+
+class tst_Utilities : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void loggingCategoryName();
+ void loggingCategoryDefaults();
+ void startDebugging();
+ void stopDebugging();
+ void debugging();
+ void callSeparatorForOneWord();
+ void callSeparatorForMoreThanOneWord();
+ void callCommaForOneWord();
+ void callCommaForTwoWords();
+ void callCommaForThreeWords();
+};
+
+void tst_Utilities::loggingCategoryName()
+{
+ const QString expected = "qt.qdoc";
+ QCOMPARE(lcQdoc().categoryName(), expected);
+}
+
+void tst_Utilities::loggingCategoryDefaults()
+{
+ QVERIFY(lcQdoc().isCriticalEnabled());
+ QVERIFY(lcQdoc().isWarningEnabled());
+ QVERIFY(!lcQdoc().isDebugEnabled());
+ QVERIFY(lcQdoc().isInfoEnabled());
+}
+
+void tst_Utilities::startDebugging()
+{
+ QVERIFY(!lcQdoc().isDebugEnabled());
+ Utilities::startDebugging("test");
+ QVERIFY(lcQdoc().isDebugEnabled());
+}
+
+void tst_Utilities::stopDebugging()
+{
+ Utilities::startDebugging("test");
+ QVERIFY(lcQdoc().isDebugEnabled());
+ Utilities::stopDebugging("test");
+ QVERIFY(!lcQdoc().isDebugEnabled());
+}
+
+void tst_Utilities::debugging()
+{
+ QVERIFY(!lcQdoc().isDebugEnabled());
+ QVERIFY(!Utilities::debugging());
+ Utilities::startDebugging("test");
+ QVERIFY(lcQdoc().isDebugEnabled());
+ QVERIFY(Utilities::debugging());
+}
+
+void tst_Utilities::callSeparatorForOneWord()
+{
+ const QStringList listOfWords { "one" };
+ const QString expected = QStringLiteral("one.");
+
+ int index = 0;
+ QString result;
+ for (const auto &word : listOfWords) {
+ result.append(word);
+ result.append(Utilities::separator(index++, listOfWords.size()));
+ }
+ QCOMPARE(result, expected);
+}
+
+void tst_Utilities::callSeparatorForMoreThanOneWord()
+{
+ const QStringList listOfWords { "one", "two" };
+ const QString expected = QStringLiteral("one and two.");
+
+ int index = 0;
+ QString result;
+ for (const auto &word : listOfWords) {
+ result.append(word);
+ result.append(Utilities::separator(index++, listOfWords.size()));
+ }
+ QCOMPARE(result, expected);
+}
+
+void tst_Utilities::callCommaForOneWord()
+{
+ const QStringList listOfWords { "one" };
+ const QString expected = QStringLiteral("one");
+
+ int index = 0;
+ QString result;
+ for (const auto &word : listOfWords) {
+ result.append(word);
+ result.append(Utilities::comma(index++, listOfWords.size()));
+ }
+ QCOMPARE(result, expected);
+}
+void tst_Utilities::callCommaForTwoWords()
+{
+ const QStringList listOfWords { "one", "two" };
+ const QString expected = QStringLiteral("one and two");
+
+ int index = 0;
+ QString result;
+ for (const auto &word : listOfWords) {
+ result.append(word);
+ result.append(Utilities::comma(index++, listOfWords.size()));
+ }
+ QCOMPARE(result, expected);
+}
+
+void tst_Utilities::callCommaForThreeWords()
+{
+ const QStringList listOfWords { "one", "two", "three" };
+ const QString expected = QStringLiteral("one, two, and three");
+
+ int index = 0;
+ QString result;
+ for (const auto &word : listOfWords) {
+ result.append(word);
+ result.append(Utilities::comma(index++, listOfWords.size()));
+ }
+ QCOMPARE(result, expected);
+}
+
+QTEST_APPLESS_MAIN(tst_Utilities)
+
+#include "tst_utilities.moc"
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/CMakeLists.txt b/src/qdoc/qdoc/tests/validateqdocoutputfiles/CMakeLists.txt
new file mode 100644
index 000000000..2d7416f82
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/CMakeLists.txt
@@ -0,0 +1,44 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## tst_validateQdocOutputFiles Test:
+#####################################################################
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_validateQdocOutputFiles LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_test(tst_validateQdocOutputFiles
+ SOURCES
+ ${CMAKE_CURRENT_LIST_DIR}/tst_validateqdocoutputfiles.cpp
+)
+
+# Write relevant Qt include path to a file, to be read in by QDoc
+set(config_subfolder "")
+get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+if(is_multi_config)
+ set(config_subfolder "$<CONFIG>/")
+endif()
+set(includepathsfile "${CMAKE_CURRENT_BINARY_DIR}/${config_subfolder}qdocincludepaths.inc")
+set(framework_path "\n")
+
+find_package(Qt6 COMPONENTS Core REQUIRED)
+if(Qt6Core_FOUND)
+ get_target_property(include_paths Qt6::Core INTERFACE_INCLUDE_DIRECTORIES)
+endif()
+
+while(include_paths)
+ list(POP_BACK include_paths inc_path)
+ if(inc_path MATCHES "(.+)/QtCore\.framework$")
+ string(APPEND framework_path "-F${CMAKE_MATCH_1}")
+ break()
+ endif()
+endwhile()
+
+set (include_paths "$<TARGET_PROPERTY:tst_validateQdocOutputFiles,INCLUDE_DIRECTORIES>")
+file(GENERATE OUTPUT ${includepathsfile} CONTENT "-I$<JOIN:${include_paths},\n-I>${framework_path}")
+target_compile_definitions(tst_validateQdocOutputFiles PRIVATE DOCINCPATH="${includepathsfile}")
+add_dependencies(tst_validateQdocOutputFiles Qt::qdoc)
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/README.md b/src/qdoc/qdoc/tests/validateqdocoutputfiles/README.md
new file mode 100644
index 000000000..728cbee62
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/README.md
@@ -0,0 +1,82 @@
+<!--
+ Copyright (C) 2024 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+-->
+
+# QDoc output test
+This is a test that validates the files QDoc generates. It is not a test for
+QDoc itself. The test calls the qdoc executable for each project in the
+`testdata` directory (see [Test project structure](# Test project structure),
+below), and then compares the output to that in the `expected` directory for
+the test project. Each test is identified in the test output by the name of the
+test project directory *and* the name of the .qdocconf-file.
+
+The test creates a new temporary directory for each test project. The
+comparison is done by running `git diff` on the content in this temporary
+directory (the output directory) and the contents in the test's `expected`
+directory. A non-zero exit code fails the test for that project, and the test
+includes the diff in its output if this happens.
+
+## How to add a new test
+1. Create a new directory in the `testdata` directory. The name of the new
+ directory should be descriptive of the test project.
+2. Create a `.qdocconf` file in the new directory. See
+ [The .qdocconf file](# The .qdocconf file) below for further details.
+3. Add the necessary files (.qdoc, .h/.cpp, .qml, etc) so that the project
+ can be built in a meaningful manner.
+4. Run QDoc on the project.
+5. Verify that the output looks correct.
+6. Copy the output into `[testdata/[new test directory]/expected`.
+7. Run the test executable and verify that the test output includes a **PASS**
+ line for the new test-case.
+8. Push your change upstream.
+
+## Update the expected content for all tests
+If you make a change to QDoc that causes significant changes in output, you may
+need to update the expected output for many, or even all, tests. If you set the
+environment variable `QDOC_REGENERATE_TESTDATA=1` before running the test, all
+current test data will be removed, and QDoc will be run on each project to
+generate new output. Note, however, that the comparison per project is skipped
+in this scenario.
+
+## Test project structure
+The `testdata` directory is where the test looks for projects to test. Each
+project has its own directory, a `qdocconf` file, and an `expected`
+directory that contains the expected file output from QDoc.
+
+The project directory must contain a .qdocconf whose name matches that of the
+project directory, otherwise the project won't be picked up by the test.
+
+### The .qdocconf file
+The `.qdocconf` file contains the configuration necessary to build the
+QDoc project in the directory. Observe the following:
+- **Important!** Make sure to set `locationinfo = false` to avoid test failures
+ due to differences in the location information in the generated output.
+- Test projects should be warning free, so set `warninglimit.enabled = true`.
+- The name of the .qdocconf-file must be identical as that of the directory
+ that contains it. This means that for a project **foo**, it should go into
+ `testdata/foo` and the configuration file should be
+ `testdata/foo/foo.qdocconf`.
+- Any other `.qdocconf` file will not be picked up explicitly by the test.
+ This means that for a test project **foo** that resides in `testdata/foo`,
+ `testdata/foo/foo.qdocconf` will be treated as a test-case, while
+ `testdata/foo/bar.qdocconf` will not. However, `foo.qdocconf` may contain
+ `include(bar.qdocconf)`, and this will work as expected.
+- The `warninglimit` should be set to the number of warnings expected
+ from QDoc for that specific project, if any are to be expected at all.
+- You probably want to configure all output formats, otherwise only the HTML
+ output will be generated. The test will compare all output formats that are
+ generated to the expected output, so remember to specify the output directory
+ for each format. By convention, the output directories are named after the
+ format, for example `html`, `docbook`, `webxml`, etc.
+- Place the sources for your new test in a subdirectory of the test project
+ directory. By convention, the sources are placed in a directory named `src`.
+
+### The `expected` directory
+The `expected` directory contains the expected output from QDoc
+for the project. If QDoc generates an empty directory (for example, it
+always creates the `images/` directory for a project, whether
+the project contains any images or not), that directory isn't tracked
+in the `expected` directory, as **git** doesn't track directories, only
+files.
+
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/bug80259.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/bug80259.qdocconf
new file mode 100644
index 000000000..2b7a1e5f4
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/bug80259.qdocconf
@@ -0,0 +1,31 @@
+project = TestModule
+
+moduleheader = TestModule.h
+
+headerdirs = ./src/inc
+sourcedirs = ./src
+includepaths += ./src/inc/testmodule
+
+sources.fileextensions = "*.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+outputdir = doc
+outputformats = HTML WebXML
+
+# images
+imagedirs = ./src/images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+# By default, use -outputdir directly, no subdir.
+# outputsubdir '.' matches the root of expected_output/
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/first-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/first-members.html
new file mode 100644
index 000000000..db22018c5
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/first-members.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- main.cpp -->
+ <title>List of All Members for First | TestModule</title>
+</head>
+<body>
+<li>First</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for First</h1>
+<p>This is the complete list of members for <a href="first.html">First</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no">class <span class="name"><b><a href="first-nested.html" translate="no">Nested</a></b></span></li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/first-nested.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/first-nested.html
new file mode 100644
index 000000000..9a8266e7a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/first-nested.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- main.cpp -->
+ <title>Nested Class | TestModule</title>
+</head>
+<body>
+<li>Nested</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Nested Class</h1>
+<span class="small-subtitle" translate="no">class <a href="first.html" translate="no">First</a>::Nested</span>
+<!-- $$$Nested-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<p>This is a nested class</p>
+</div>
+<!-- @@@Nested -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/first.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/first.html
new file mode 100644
index 000000000..74033f353
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/first.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- main.cpp -->
+ <title>First Struct | TestModule</title>
+</head>
+<body>
+<li>First</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#public-types">Public Types</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">First Struct</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;First&gt;</span></td></tr>
+</table></div>
+<ul>
+<li><a href="first-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="public-types">Public Types</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> class </td><td class="memItemRight bottomAlign"><b><a href="first-nested.html" translate="no">Nested</a></b></td></tr>
+</table></div>
+<!-- $$$First-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<p>This is a first class</p>
+</div>
+<!-- @@@First -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/index.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/index.html
new file mode 100644
index 000000000..f934cfdca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/index.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- index.qdoc -->
+ <title>doc index | TestModule</title>
+</head>
+<body>
+<h1 class="title">doc index</h1>
+<!-- $$$index.html-description -->
+<div class="descr" id="details">
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="first.html">First</a></p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="first-nested.html">First::Nested</a></p></td></tr>
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="second.html">Second</a></p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="third.html">Third</a></p></td></tr>
+</table></div>
+</div>
+<!-- @@@index.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/second.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/second.html
new file mode 100644
index 000000000..93f492cec
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/second.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- main.cpp -->
+ <title>Second Class | TestModule</title>
+</head>
+<body>
+<li>Second</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Second Class</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Second&gt;</span></td></tr>
+</table></div>
+<!-- $$$Second-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<p>This is a second class</p>
+</div>
+<!-- @@@Second -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/testmodule.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/testmodule.index
new file mode 100644
index 000000000..6e8bf310f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/testmodule.index
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="TestModule Reference Documentation" version="" project="TestModule">
+ <namespace name="" status="active" access="public" module="testmodule">
+ <struct name="First" href="first.html" status="active" access="public" location="bbb.h" documented="true" module="TestModule">
+ <class name="Nested" fullname="First::Nested" href="first-nested.html" status="active" access="public" location="bbb.h" documented="true" module="TestModule"/>
+ </struct>
+ <class name="Second" href="second.html" status="active" access="public" location="ccc.h" documented="true" module="TestModule"/>
+ <class name="Third" href="third.html" status="active" access="public" location="aaa.h" documented="true" module="TestModule"/>
+ <page name="index.html" href="index.html" status="active" location="index.qdoc" documented="true" subtype="page" title="doc index" fulltitle="doc index" subtitle=""/>
+ <module name="TestModule" href="testmodule-module.html" status="internal" seen="false" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/third.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/third.html
new file mode 100644
index 000000000..cc8f68404
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/html/third.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- main.cpp -->
+ <title>Third Class | TestModule</title>
+</head>
+<body>
+<li>Third</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Third Class</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Third&gt;</span></td></tr>
+</table></div>
+<!-- $$$Third-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<p>This is a third class</p>
+</div>
+<!-- @@@Third -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/first-nested.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/first-nested.webxml
new file mode 100644
index 000000000..842570c13
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/first-nested.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="Nested" fullname="First::Nested" href="first-nested.html" status="active" access="public" location="bbb.h" documented="true" module="TestModule">
+ <description>
+ <para>This is a nested class</para>
+ </description>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/first.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/first.webxml
new file mode 100644
index 000000000..526bbbe73
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/first.webxml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <struct name="First" href="first.html" status="active" access="public" location="bbb.h" documented="true" module="TestModule">
+ <description>
+ <para>This is a first class</para>
+ </description>
+ <class name="Nested" fullname="First::Nested" href="first-nested.html" status="active" access="public" location="bbb.h" documented="true" module="TestModule">
+ <description>
+ <para>This is a nested class</para>
+ </description>
+ </class>
+ </struct>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/index.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/index.webxml
new file mode 100644
index 000000000..659d98f22
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/index.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="index.html" href="index.html" status="active" location="index.qdoc" documented="true" subtype="page" title="doc index" fulltitle="doc index" subtitle="">
+ <description>
+ <generatedlist contents="classesbymodule TestModule"/>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/second.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/second.webxml
new file mode 100644
index 000000000..9ecd3c2fe
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/second.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="Second" href="second.html" status="active" access="public" location="ccc.h" documented="true" module="TestModule">
+ <description>
+ <para>This is a second class</para>
+ </description>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/testmodule.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/testmodule.index
new file mode 100644
index 000000000..6e8bf310f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/testmodule.index
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="TestModule Reference Documentation" version="" project="TestModule">
+ <namespace name="" status="active" access="public" module="testmodule">
+ <struct name="First" href="first.html" status="active" access="public" location="bbb.h" documented="true" module="TestModule">
+ <class name="Nested" fullname="First::Nested" href="first-nested.html" status="active" access="public" location="bbb.h" documented="true" module="TestModule"/>
+ </struct>
+ <class name="Second" href="second.html" status="active" access="public" location="ccc.h" documented="true" module="TestModule"/>
+ <class name="Third" href="third.html" status="active" access="public" location="aaa.h" documented="true" module="TestModule"/>
+ <page name="index.html" href="index.html" status="active" location="index.qdoc" documented="true" subtype="page" title="doc index" fulltitle="doc index" subtitle=""/>
+ <module name="TestModule" href="testmodule-module.html" status="internal" seen="false" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/third.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/third.webxml
new file mode 100644
index 000000000..529ffd896
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/expected/webxml/third.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="Third" href="third.html" status="active" access="public" location="aaa.h" documented="true" module="TestModule">
+ <description>
+ <para>This is a third class</para>
+ </description>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/TestModule.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/TestModule.h
new file mode 100644
index 000000000..90adda3e2
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/TestModule.h
@@ -0,0 +1,6 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "aaa.h"
+#include "bbb.h"
+#include "ccc.h"
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/aaa.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/aaa.h
new file mode 100644
index 000000000..5051d60a9
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/aaa.h
@@ -0,0 +1,7 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+class Third
+{
+};
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/bbb.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/bbb.h
new file mode 100644
index 000000000..c17223c2d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/bbb.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+struct First
+{
+ class Nested {};
+};
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/ccc.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/ccc.h
new file mode 100644
index 000000000..8fa00e3e8
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/inc/testmodule/ccc.h
@@ -0,0 +1,7 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+class Second
+{
+};
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/main.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/main.cpp
new file mode 100644
index 000000000..bc92fa921
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/main.cpp
@@ -0,0 +1,29 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+/*!
+\class First
+\inmodule TestModule
+
+This is a first class
+*/
+
+/*!
+\class First::Nested
+\inmodule TestModule
+
+This is a nested class
+*/
+
+/*!
+\class Second
+\inmodule TestModule
+
+This is a second class
+*/
+
+/*!
+\class Third
+\inmodule TestModule
+
+This is a third class
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/qdoc/index.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/qdoc/index.qdoc
new file mode 100644
index 000000000..ae270f378
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/bug80259/src/qdoc/index.qdoc
@@ -0,0 +1,10 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*!
+ \page index.html
+ \title doc index
+
+ \generatelist {classesbymodule TestModule}
+
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/cmakedocumentation.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/cmakedocumentation.qdocconf
new file mode 100644
index 000000000..c62f4938e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/cmakedocumentation.qdocconf
@@ -0,0 +1,29 @@
+project = cmakedocumentation
+
+headerdirs = ./src
+sourcedirs = ./src
+exampledirs = ./src
+
+outputformats = WebXML HTML DocBook
+WebXML.quotinginformation = true
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
+
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+
+DocBook.nosubdirs = true
+DocBook.outputsubdir = docbook
+
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/car.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/car.xml
new file mode 100644
index 000000000..ee439cdd0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/car.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Car Class</db:title>
+<db:productname>cmakedocumentation</db:productname>
+<db:titleabbrev>cmakedocumentation Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>Represents a model of a car.</db:para>
+<db:para>This class was introduced in Qt 6.6.6.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Car</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 6.6.6</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS TestCar)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::TestCar)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcar</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/engine.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/engine.xml
new file mode 100644
index 000000000..06fcbd4bd
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/engine.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Engine Class</db:title>
+<db:productname>cmakedocumentation</db:productname>
+<db:titleabbrev>cmakedocumentation Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>Represents a model of an engine.</db:para>
+<db:para>This class was introduced in Qt 6.6.6.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Engine</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 6.6.6</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS TestCar)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::TestCarPrivate)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcar-private</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/testcar-module.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/testcar-module.xml
new file mode 100644
index 000000000..03f17d47f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/testcar-module.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title></db:title>
+<db:productname>cmakedocumentation</db:productname>
+<db:titleabbrev>cmakedocumentation Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>This module was introduced in Qt 6.6.6.</db:para>
+</db:abstract>
+</db:info>
+<db:para>This module was introduced in Qt 6.6.6.</db:para>
+<db:section xml:id="classes">
+<db:title>Classes</db:title>
+<db:variablelist role="classes">
+<db:varlistentry>
+<db:term><db:link xlink:href="car.xml" xlink:role="class">Car</db:link></db:term>
+<db:listitem>
+<db:para>Represents a model of a car.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/testcarprivate-module.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/testcarprivate-module.xml
new file mode 100644
index 000000000..f89a2679b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/docbook/testcarprivate-module.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title></db:title>
+<db:productname>cmakedocumentation</db:productname>
+<db:titleabbrev>cmakedocumentation Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>This module was introduced in Qt 6.6.6.</db:para>
+</db:abstract>
+</db:info>
+<db:para>This module was introduced in Qt 6.6.6.</db:para>
+<db:section xml:id="classes">
+<db:title>Classes</db:title>
+<db:variablelist role="classes">
+<db:varlistentry>
+<db:term><db:link xlink:href="engine.xml" xlink:role="class">Engine</db:link></db:term>
+<db:listitem>
+<db:para>Represents a model of an engine.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/car.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/car.html
new file mode 100644
index 000000000..c7ab54dee
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/car.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- car.cpp -->
+ <meta name="description" content="Represents a model of a car.">
+ <title>Car Class | cmakedocumentation</title>
+</head>
+<body>
+<li>Car</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Car Class</h1>
+<!-- $$$Car-brief -->
+<p>Represents a model of a car. <a href="#details">More...</a></p>
+<!-- @@@Car -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Car&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS TestCar) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::TestCar)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcar</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 6.6.6</td></tr>
+</table></div>
+<!-- $$$Car-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Car -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/cmakedocumentation.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/cmakedocumentation.index
new file mode 100644
index 000000000..87ef31650
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/cmakedocumentation.index
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="cmakedocumentation Reference Documentation" version="" project="cmakedocumentation">
+ <namespace name="" status="active" access="public" module="cmakedocumentation">
+ <class name="Car" href="car.html" status="active" access="public" location="car.h" since="6.6.6" documented="true" module="TestCar" brief="Represents a model of a car">
+ <function name="drive" fullname="Car::drive" href="car.html#drive" status="active" access="private" location="car.h" documented="true" meta="plain" type="void" brief="Drives the car" signature="void drive()"/>
+ </class>
+ <class name="Engine" href="engine.html" status="active" access="public" location="car.h" since="6.6.6" documented="true" module="TestCarPrivate" brief="Represents a model of an engine">
+ <function name="start" fullname="Engine::start" href="engine.html#start" status="active" access="private" location="car.h" documented="true" meta="plain" type="void" brief="Starts the engine" signature="void start()"/>
+ <function name="stop" fullname="Engine::stop" href="engine.html#stop" status="active" access="private" location="car.h" documented="true" meta="plain" type="void" brief="Stops the engine" signature="void stop()"/>
+ </class>
+ <module name="TestCar" href="testcar-module.html" status="active" since="6.6.6" documented="true" seen="true" title=""/>
+ <module name="TestCarPrivate" href="testcarprivate-module.html" status="active" since="6.6.6" documented="true" seen="true" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/engine.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/engine.html
new file mode 100644
index 000000000..36a2a97da
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/engine.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- car.cpp -->
+ <meta name="description" content="Represents a model of an engine.">
+ <title>Engine Class | cmakedocumentation</title>
+</head>
+<body>
+<li>Engine</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Engine Class</h1>
+<!-- $$$Engine-brief -->
+<p>Represents a model of an engine. <a href="#details">More...</a></p>
+<!-- @@@Engine -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Engine&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS TestCar) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::TestCarPrivate)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcar-private</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 6.6.6</td></tr>
+</table></div>
+<!-- $$$Engine-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Engine -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/testcar-module.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/testcar-module.html
new file mode 100644
index 000000000..5b579cc8a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/testcar-module.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- car.cpp -->
+ <title>cmakedocumentation</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#namespaces">Namespaces</a></li>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<p>This module was introduced in Qt 6.6.6.</p>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="car.html">Car</a></p></td><td class="tblDescr"><p>Represents a model of a car</p></td></tr>
+</table></div>
+<!-- $$$TestCar-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@TestCar -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/testcarprivate-module.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/testcarprivate-module.html
new file mode 100644
index 000000000..762fe3193
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/html/testcarprivate-module.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- car.cpp -->
+ <title>cmakedocumentation</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#namespaces">Namespaces</a></li>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<p>This module was introduced in Qt 6.6.6.</p>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="engine.html">Engine</a></p></td><td class="tblDescr"><p>Represents a model of an engine</p></td></tr>
+</table></div>
+<!-- $$$TestCarPrivate-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@TestCarPrivate -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/car.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/car.webxml
new file mode 100644
index 000000000..76725762e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/car.webxml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="Car" href="car.html" status="active" access="public" location="car.h" since="6.6.6" documented="true" module="TestCar" brief="Represents a model of a car">
+ <description>
+ <brief>Represents a model of a car.</brief>
+ </description>
+ <function name="drive" fullname="Car::drive" href="car.html#drive" status="active" access="private" location="car.h" documented="true" meta="plain" type="void" brief="Drives the car" signature="void drive()">
+ <description>
+ <brief>Drives the car.</brief>
+ </description>
+ </function>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/cmakedocumentation.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/cmakedocumentation.index
new file mode 100644
index 000000000..87ef31650
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/cmakedocumentation.index
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="cmakedocumentation Reference Documentation" version="" project="cmakedocumentation">
+ <namespace name="" status="active" access="public" module="cmakedocumentation">
+ <class name="Car" href="car.html" status="active" access="public" location="car.h" since="6.6.6" documented="true" module="TestCar" brief="Represents a model of a car">
+ <function name="drive" fullname="Car::drive" href="car.html#drive" status="active" access="private" location="car.h" documented="true" meta="plain" type="void" brief="Drives the car" signature="void drive()"/>
+ </class>
+ <class name="Engine" href="engine.html" status="active" access="public" location="car.h" since="6.6.6" documented="true" module="TestCarPrivate" brief="Represents a model of an engine">
+ <function name="start" fullname="Engine::start" href="engine.html#start" status="active" access="private" location="car.h" documented="true" meta="plain" type="void" brief="Starts the engine" signature="void start()"/>
+ <function name="stop" fullname="Engine::stop" href="engine.html#stop" status="active" access="private" location="car.h" documented="true" meta="plain" type="void" brief="Stops the engine" signature="void stop()"/>
+ </class>
+ <module name="TestCar" href="testcar-module.html" status="active" since="6.6.6" documented="true" seen="true" title=""/>
+ <module name="TestCarPrivate" href="testcarprivate-module.html" status="active" since="6.6.6" documented="true" seen="true" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/engine.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/engine.webxml
new file mode 100644
index 000000000..4eee94b89
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/engine.webxml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="Engine" href="engine.html" status="active" access="public" location="car.h" since="6.6.6" documented="true" module="TestCarPrivate" brief="Represents a model of an engine">
+ <description>
+ <brief>Represents a model of an engine.</brief>
+ </description>
+ <function name="start" fullname="Engine::start" href="engine.html#start" status="active" access="private" location="car.h" documented="true" meta="plain" type="void" brief="Starts the engine" signature="void start()">
+ <description>
+ <brief>Starts the engine.</brief>
+ </description>
+ </function>
+ <function name="stop" fullname="Engine::stop" href="engine.html#stop" status="active" access="private" location="car.h" documented="true" meta="plain" type="void" brief="Stops the engine" signature="void stop()">
+ <description>
+ <brief>Stops the engine.</brief>
+ </description>
+ </function>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/testcar-module.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/testcar-module.webxml
new file mode 100644
index 000000000..5d24b3077
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/testcar-module.webxml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document/>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/testcarprivate-module.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/testcarprivate-module.webxml
new file mode 100644
index 000000000..5d24b3077
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/expected/webxml/testcarprivate-module.webxml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document/>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/src/car.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/src/car.cpp
new file mode 100644
index 000000000..78823d7e3
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/src/car.cpp
@@ -0,0 +1,46 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+#include "car.h"
+
+/*!
+ \module TestCar
+ \since 6.6.6
+ \qtvariable testcar
+ \qtcmakepackage TestCar
+*/
+
+/*!
+ \module TestCarPrivate
+ \since 6.6.6
+ \qtvariable testcar-private
+ \qtcmakepackage TestCar
+ \qtcmaketargetitem TestCarPrivate
+*/
+
+/*!
+ \class Car
+ \inmodule TestCar
+ \brief Represents a model of a car.
+*/
+
+/*!
+ \class Engine
+ \inmodule TestCarPrivate
+ \brief Represents a model of an engine.
+*/
+
+/*!
+ \fn void Car::drive()
+ \brief Drives the car.
+*/
+
+/*!
+ \fn void Engine::start()
+ \brief Starts the engine.
+*/
+
+/*!
+ \fn void Engine::stop()
+ \brief Stops the engine.
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/src/car.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/src/car.h
new file mode 100644
index 000000000..c0203b07c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cmakedocumentation/src/car.h
@@ -0,0 +1,17 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+#ifndef CAR_H
+#define CAR_H
+
+class Car {
+ void drive() {};
+};
+
+class Engine {
+ void start() {};
+ void stop() {};
+};
+
+
+#endif // CAR_H
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/compiler_generated_member_functions.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/compiler_generated_member_functions.qdocconf
new file mode 100644
index 000000000..3a369e673
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/compiler_generated_member_functions.qdocconf
@@ -0,0 +1,23 @@
+project = Compiler generated member functions
+
+locationinfo = false
+
+headers.fileextensions = "*.h *.hpp"
+sources.fileextensions = "*.cpp *.qml *.qdoc"
+
+headerdirs = ./src
+sourcedirs = ./src
+
+warninglimit = 0
+warninglimit.enabled = true
+
+outputformats = WebXML HTML DocBook
+WebXML.quotinginformation = true
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
+
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+
+DocBook.nosubdirs = true
+DocBook.outputsubdir = docbook
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/docbook/qdoctests-compilergeneratedmemberfunctions.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/docbook/qdoctests-compilergeneratedmemberfunctions.xml
new file mode 100644
index 000000000..43bf5cdca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/docbook/qdoctests-compilergeneratedmemberfunctions.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>CompilerGeneratedMemberFunctions Class</db:title>
+<db:subtitle>QDocTests::CompilerGeneratedMemberFunctions</db:subtitle>
+<db:productname>Compiler generated member functions</db:productname>
+<db:titleabbrev>Compiler generated member functions Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>A test class for compiler-generated member functions.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>CompilerGeneratedMemberFunctions</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:para>This class is used to test the documentation of compiler-generated member functions.</db:para>
+</db:section>
+<db:section xml:id="member-function-documentation">
+<db:title>Member Function Documentation</db:title>
+<db:section xml:id="CompilerGeneratedMemberFunctions">
+<db:title>[constexpr noexcept default] CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions()</db:title>
+<db:para>Constructs a <db:link xlink:href="qdoctests-compilergeneratedmemberfunctions.xml">CompilerGeneratedMemberFunctions</db:link> object.</db:para>
+</db:section>
+<db:section xml:id="CompilerGeneratedMemberFunctions-1">
+<db:title>[constexpr noexcept default] CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions(const QDocTests::CompilerGeneratedMemberFunctions &amp;<db:emphasis>other</db:emphasis>)</db:title>
+<db:para>Copy-constructs a <db:link xlink:href="qdoctests-compilergeneratedmemberfunctions.xml">CompilerGeneratedMemberFunctions</db:link> object from <db:code role="parameter">other</db:code>.</db:para>
+</db:section>
+<db:section xml:id="CompilerGeneratedMemberFunctions-2">
+<db:title>[constexpr noexcept default] CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions(const QDocTests::CompilerGeneratedMemberFunctions &amp;&amp;<db:emphasis>other</db:emphasis>)</db:title>
+<db:para>Move-constructs a <db:link xlink:href="qdoctests-compilergeneratedmemberfunctions.xml">CompilerGeneratedMemberFunctions</db:link> object from <db:code role="parameter">other</db:code>.</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/docbook/qdoctests-module.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/docbook/qdoctests-module.xml
new file mode 100644
index 000000000..91f92c1c5
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/docbook/qdoctests-module.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title></db:title>
+<db:productname>Compiler generated member functions</db:productname>
+<db:titleabbrev>Compiler generated member functions Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>The <db:link xlink:href="qdoctests-module.xml">QDocTests</db:link> module contains test classes for QDoc.</db:para>
+</db:abstract>
+</db:info>
+<db:para>The <db:link xlink:href="qdoctests-module.xml">QDocTests</db:link> module contains test classes for QDoc.</db:para>
+<db:section xml:id="namespaces">
+<db:title>Namespaces</db:title>
+<db:variablelist role="namespaces">
+<db:varlistentry>
+<db:term><db:link xlink:href="qdoctests.xml" xlink:role="namespace">QDocTests</db:link></db:term>
+<db:listitem>
+<db:para>The QDocTests namespace contains test classes for QDoc.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:section>
+<db:section xml:id="classes">
+<db:title>Classes</db:title>
+<db:variablelist role="classes">
+<db:varlistentry>
+<db:term><db:link xlink:href="qdoctests-compilergeneratedmemberfunctions.xml" xlink:role="class">QDocTests::CompilerGeneratedMemberFunctions</db:link></db:term>
+<db:listitem>
+<db:para>A test class for compiler-generated member functions.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:section>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:para>The <db:link xlink:href="qdoctests-module.xml">QDocTests</db:link> module contains test classes for QDoc.</db:para>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/docbook/qdoctests.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/docbook/qdoctests.xml
new file mode 100644
index 000000000..04ad76f26
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/docbook/qdoctests.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>QDocTests Namespace</db:title>
+<db:productname>Compiler generated member functions</db:productname>
+<db:titleabbrev>Compiler generated member functions Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>The <db:link xlink:href="qdoctests-module.xml">QDocTests</db:link> namespace contains test classes for QDoc.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>QDocTests</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:para>The <db:link xlink:href="qdoctests-module.xml">QDocTests</db:link> namespace contains test classes for QDoc.</db:para>
+</db:section>
+<db:section xml:id="classes">
+<db:title>Classes</db:title>
+<db:section>
+<db:title>class <db:link xlink:href="qdoctests-compilergeneratedmemberfunctions.xml" xlink:role="class">CompilerGeneratedMemberFunctions</db:link></db:title>
+<db:para>A test class for compiler-generated member functions.</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/compiler-generated-member-functions.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/compiler-generated-member-functions.index
new file mode 100644
index 000000000..30a694078
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/compiler-generated-member-functions.index
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="Compiler generated member functions Reference Documentation" version="" project="Compiler generated member functions">
+ <namespace name="" status="active" access="public" module="compiler generated member functions">
+ <namespace name="QDocTests" href="qdoctests.html" status="active" access="public" location="compilergeneratedmemberfunctions.h" documented="true" module="QDocTests" brief="Contains test classes for QDoc">
+ <class name="CompilerGeneratedMemberFunctions" fullname="QDocTests::CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html" status="active" access="public" location="compilergeneratedmemberfunctions.h" documented="true" module="QDocTests" brief="A test class for compiler-generated member functions">
+ <function name="CompilerGeneratedMemberFunctions" fullname="QDocTests::CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions" status="active" access="public" documented="true" meta="constructor" constexpr="true" noexcept="true" signature="CompilerGeneratedMemberFunctions()"/>
+ <function name="CompilerGeneratedMemberFunctions" fullname="QDocTests::CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions-1" status="active" access="public" documented="true" meta="copy-constructor" constexpr="true" noexcept="true" overload="true" overload-number="1" signature="CompilerGeneratedMemberFunctions(const QDocTests::CompilerGeneratedMemberFunctions &amp;other)">
+ <parameter type="const QDocTests::CompilerGeneratedMemberFunctions &amp;" name="other" default=""/>
+ </function>
+ <function name="CompilerGeneratedMemberFunctions" fullname="QDocTests::CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions-2" status="active" access="public" documented="true" meta="move-constructor" constexpr="true" noexcept="true" overload="true" overload-number="2" signature="CompilerGeneratedMemberFunctions(const QDocTests::CompilerGeneratedMemberFunctions &amp;&amp;other)">
+ <parameter type="const QDocTests::CompilerGeneratedMemberFunctions &amp;&amp;" name="other" default=""/>
+ </function>
+ </class>
+ </namespace>
+ <module name="QDocTests" href="qdoctests-module.html" status="active" documented="true" seen="true" title="" brief="Module contains test classes for QDoc"/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests-compilergeneratedmemberfunctions-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests-compilergeneratedmemberfunctions-members.html
new file mode 100644
index 000000000..825a2352a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests-compilergeneratedmemberfunctions-members.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- compilergeneratedmemberfunctions.cpp -->
+ <meta name="description" content="A test class for compiler-generated member functions.">
+ <title>List of All Members for CompilerGeneratedMemberFunctions | Compiler generated member functions</title>
+</head>
+<body>
+<li>CompilerGeneratedMemberFunctions</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for CompilerGeneratedMemberFunctions</h1>
+<p>This is the complete list of members for <a href="qdoctests-compilergeneratedmemberfunctions.html">QDocTests::CompilerGeneratedMemberFunctions</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><span class="name"><b><a href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions" translate="no">CompilerGeneratedMemberFunctions</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions-1" translate="no">CompilerGeneratedMemberFunctions</a></b></span>(const QDocTests::CompilerGeneratedMemberFunctions &amp;)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions-2" translate="no">CompilerGeneratedMemberFunctions</a></b></span>(const QDocTests::CompilerGeneratedMemberFunctions &amp;&amp;)</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests-compilergeneratedmemberfunctions.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests-compilergeneratedmemberfunctions.html
new file mode 100644
index 000000000..6a4fe947f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests-compilergeneratedmemberfunctions.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- compilergeneratedmemberfunctions.cpp -->
+ <meta name="description" content="A test class for compiler-generated member functions.">
+ <title>CompilerGeneratedMemberFunctions Class | Compiler generated member functions</title>
+</head>
+<body>
+<li>CompilerGeneratedMemberFunctions</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#public-functions">Public Functions</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">CompilerGeneratedMemberFunctions Class</h1>
+<span class="small-subtitle" translate="no">class <a href="qdoctests-module.html" translate="no">QDocTests</a>::CompilerGeneratedMemberFunctions</span>
+<!-- $$$CompilerGeneratedMemberFunctions-brief -->
+<p>A test class for compiler-generated member functions. <a href="#details">More...</a></p>
+<!-- @@@CompilerGeneratedMemberFunctions -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;CompilerGeneratedMemberFunctions&gt;</span></td></tr>
+</table></div>
+<ul>
+<li><a href="qdoctests-compilergeneratedmemberfunctions-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="public-functions">Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions" translate="no">CompilerGeneratedMemberFunctions</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions-1" translate="no">CompilerGeneratedMemberFunctions</a></b>(const QDocTests::CompilerGeneratedMemberFunctions &amp;<i>other</i>)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions-2" translate="no">CompilerGeneratedMemberFunctions</a></b>(const QDocTests::CompilerGeneratedMemberFunctions &amp;&amp;<i>other</i>)</td></tr>
+</table></div>
+<!-- $$$CompilerGeneratedMemberFunctions-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<p>This class is used to test the documentation of compiler-generated member functions.</p>
+</div>
+<!-- @@@CompilerGeneratedMemberFunctions -->
+<div class="func">
+<h2>Member Function Documentation</h2>
+<!-- $$$CompilerGeneratedMemberFunctions[overload1]$$$CompilerGeneratedMemberFunctions -->
+<h3 class="fn" translate="no" id="CompilerGeneratedMemberFunctions"><code class="details extra" translate="no">[constexpr noexcept default]</code> CompilerGeneratedMemberFunctions::<span class="name">CompilerGeneratedMemberFunctions</span>()</h3>
+<p>Constructs a CompilerGeneratedMemberFunctions object.</p>
+<!-- @@@CompilerGeneratedMemberFunctions -->
+<!-- $$$CompilerGeneratedMemberFunctions$$$CompilerGeneratedMemberFunctionsconstQDocTests::CompilerGeneratedMemberFunctions& -->
+<h3 class="fn" translate="no" id="CompilerGeneratedMemberFunctions-1"><code class="details extra" translate="no">[constexpr noexcept default]</code> CompilerGeneratedMemberFunctions::<span class="name">CompilerGeneratedMemberFunctions</span>(const <span class="type"><a href="qdoctests-compilergeneratedmemberfunctions.html" translate="no">QDocTests::CompilerGeneratedMemberFunctions</a></span> &amp;<i>other</i>)</h3>
+<p>Copy-constructs a CompilerGeneratedMemberFunctions object from <i translate="no">other</i>.</p>
+<!-- @@@CompilerGeneratedMemberFunctions -->
+<!-- $$$CompilerGeneratedMemberFunctions$$$CompilerGeneratedMemberFunctionsconstQDocTests::CompilerGeneratedMemberFunctions&& -->
+<h3 class="fn" translate="no" id="CompilerGeneratedMemberFunctions-2"><code class="details extra" translate="no">[constexpr noexcept default]</code> CompilerGeneratedMemberFunctions::<span class="name">CompilerGeneratedMemberFunctions</span>(const <span class="type"><a href="qdoctests-compilergeneratedmemberfunctions.html" translate="no">QDocTests::CompilerGeneratedMemberFunctions</a></span> &amp;&amp;<i>other</i>)</h3>
+<p>Move-constructs a CompilerGeneratedMemberFunctions object from <i translate="no">other</i>.</p>
+<!-- @@@CompilerGeneratedMemberFunctions -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests-module.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests-module.html
new file mode 100644
index 000000000..d85654134
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests-module.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- compilergeneratedmemberfunctions.cpp -->
+ <meta name="description" content="The QDocTests module contains test classes for QDoc.">
+ <title>Compiler generated member functions</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#namespaces">Namespaces</a></li>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<!-- $$$QDocTests-brief -->
+<p>The QDocTests module contains test classes for QDoc. <a href="#details">More...</a></p>
+<!-- @@@QDocTests -->
+<h2 id="namespaces">Namespaces</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qdoctests.html">QDocTests</a></p></td><td class="tblDescr"><p>Contains test classes for QDoc</p></td></tr>
+</table></div>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qdoctests-compilergeneratedmemberfunctions.html">QDocTests::CompilerGeneratedMemberFunctions</a></p></td><td class="tblDescr"><p>A test class for compiler-generated member functions</p></td></tr>
+</table></div>
+<!-- $$$QDocTests-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<p>The QDocTests module contains test classes for QDoc.</p>
+</div>
+<!-- @@@QDocTests -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests.html
new file mode 100644
index 000000000..2f6741fa0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/html/qdoctests.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- compilergeneratedmemberfunctions.cpp -->
+ <meta name="description" content="The QDocTests namespace contains test classes for QDoc.">
+ <title>QDocTests Namespace | Compiler generated member functions</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">QDocTests Namespace</h1>
+<!-- $$$QDocTests-brief -->
+<p>The QDocTests namespace contains test classes for QDoc. <a href="#details">More...</a></p>
+<!-- @@@QDocTests -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;QDocTests&gt;</span></td></tr>
+</table></div>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> class </td><td class="memItemRight bottomAlign"><b><a href="qdoctests-compilergeneratedmemberfunctions.html" translate="no">CompilerGeneratedMemberFunctions</a></b></td></tr>
+</table></div>
+<!-- $$$QDocTests-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<p>The QDocTests namespace contains test classes for QDoc.</p>
+</div>
+<!-- @@@QDocTests -->
+<div class="classes">
+<h2>Classes</h2>
+<h3> class <a href="qdoctests-compilergeneratedmemberfunctions.html">CompilerGeneratedMemberFunctions</a></h3><!-- $$$CompilerGeneratedMemberFunctions-brief -->
+<p>A test class for compiler-generated member functions. <a href="qdoctests-compilergeneratedmemberfunctions.html#details">More...</a></p>
+<!-- @@@CompilerGeneratedMemberFunctions -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/compiler-generated-member-functions.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/compiler-generated-member-functions.index
new file mode 100644
index 000000000..30a694078
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/compiler-generated-member-functions.index
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="Compiler generated member functions Reference Documentation" version="" project="Compiler generated member functions">
+ <namespace name="" status="active" access="public" module="compiler generated member functions">
+ <namespace name="QDocTests" href="qdoctests.html" status="active" access="public" location="compilergeneratedmemberfunctions.h" documented="true" module="QDocTests" brief="Contains test classes for QDoc">
+ <class name="CompilerGeneratedMemberFunctions" fullname="QDocTests::CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html" status="active" access="public" location="compilergeneratedmemberfunctions.h" documented="true" module="QDocTests" brief="A test class for compiler-generated member functions">
+ <function name="CompilerGeneratedMemberFunctions" fullname="QDocTests::CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions" status="active" access="public" documented="true" meta="constructor" constexpr="true" noexcept="true" signature="CompilerGeneratedMemberFunctions()"/>
+ <function name="CompilerGeneratedMemberFunctions" fullname="QDocTests::CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions-1" status="active" access="public" documented="true" meta="copy-constructor" constexpr="true" noexcept="true" overload="true" overload-number="1" signature="CompilerGeneratedMemberFunctions(const QDocTests::CompilerGeneratedMemberFunctions &amp;other)">
+ <parameter type="const QDocTests::CompilerGeneratedMemberFunctions &amp;" name="other" default=""/>
+ </function>
+ <function name="CompilerGeneratedMemberFunctions" fullname="QDocTests::CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions-2" status="active" access="public" documented="true" meta="move-constructor" constexpr="true" noexcept="true" overload="true" overload-number="2" signature="CompilerGeneratedMemberFunctions(const QDocTests::CompilerGeneratedMemberFunctions &amp;&amp;other)">
+ <parameter type="const QDocTests::CompilerGeneratedMemberFunctions &amp;&amp;" name="other" default=""/>
+ </function>
+ </class>
+ </namespace>
+ <module name="QDocTests" href="qdoctests-module.html" status="active" documented="true" seen="true" title="" brief="Module contains test classes for QDoc"/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/qdoctests-compilergeneratedmemberfunctions.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/qdoctests-compilergeneratedmemberfunctions.webxml
new file mode 100644
index 000000000..2baf5256e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/qdoctests-compilergeneratedmemberfunctions.webxml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="CompilerGeneratedMemberFunctions" fullname="QDocTests::CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html" status="active" access="public" location="compilergeneratedmemberfunctions.h" documented="true" module="QDocTests" brief="A test class for compiler-generated member functions">
+ <description>
+ <brief>A test class for compiler-generated member functions.</brief>
+ <para>This class is used to test the documentation of compiler-generated member functions.</para>
+ </description>
+ <function name="CompilerGeneratedMemberFunctions" fullname="QDocTests::CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions" status="active" access="public" documented="true" meta="constructor" constexpr="true" noexcept="true" signature="CompilerGeneratedMemberFunctions()">
+ <description>
+ <para>Constructs a <link raw="CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html" type="class">CompilerGeneratedMemberFunctions</link> object.</para>
+ </description>
+ </function>
+ <function name="CompilerGeneratedMemberFunctions" fullname="QDocTests::CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions-1" status="active" access="public" documented="true" meta="copy-constructor" constexpr="true" noexcept="true" overload="true" overload-number="1" signature="CompilerGeneratedMemberFunctions(const QDocTests::CompilerGeneratedMemberFunctions &amp;other)">
+ <parameter type="const QDocTests::CompilerGeneratedMemberFunctions &amp;" name="other" default=""/>
+ <description>
+ <para>Copy-constructs a <link raw="CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html" type="class">CompilerGeneratedMemberFunctions</link> object from <argument>other</argument>.</para>
+ </description>
+ </function>
+ <function name="CompilerGeneratedMemberFunctions" fullname="QDocTests::CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions-2" status="active" access="public" documented="true" meta="move-constructor" constexpr="true" noexcept="true" overload="true" overload-number="2" signature="CompilerGeneratedMemberFunctions(const QDocTests::CompilerGeneratedMemberFunctions &amp;&amp;other)">
+ <parameter type="const QDocTests::CompilerGeneratedMemberFunctions &amp;&amp;" name="other" default=""/>
+ <description>
+ <para>Move-constructs a <link raw="CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html" type="class">CompilerGeneratedMemberFunctions</link> object from <argument>other</argument>.</para>
+ </description>
+ </function>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/qdoctests-module.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/qdoctests-module.webxml
new file mode 100644
index 000000000..5d24b3077
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/qdoctests-module.webxml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document/>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/qdoctests.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/qdoctests.webxml
new file mode 100644
index 000000000..0485b5efd
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/expected/webxml/qdoctests.webxml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <namespace name="QDocTests" href="qdoctests.html" status="active" access="public" location="compilergeneratedmemberfunctions.h" documented="true" module="QDocTests" brief="Contains test classes for QDoc">
+ <description>
+ <brief>The <link raw="QDocTests" href="qdoctests-module.html" type="module">QDocTests</link> namespace contains test classes for QDoc.</brief>
+ <para>The <link raw="QDocTests" href="qdoctests-module.html" type="module">QDocTests</link> namespace contains test classes for QDoc.</para>
+ </description>
+ <class name="CompilerGeneratedMemberFunctions" fullname="QDocTests::CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html" status="active" access="public" location="compilergeneratedmemberfunctions.h" documented="true" module="QDocTests" brief="A test class for compiler-generated member functions">
+ <description>
+ <brief>A test class for compiler-generated member functions.</brief>
+ <para>This class is used to test the documentation of compiler-generated member functions.</para>
+ </description>
+ <function name="CompilerGeneratedMemberFunctions" fullname="QDocTests::CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions" status="active" access="public" documented="true" meta="constructor" constexpr="true" noexcept="true" signature="CompilerGeneratedMemberFunctions()">
+ <description>
+ <para>Constructs a <link raw="CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html" type="class">CompilerGeneratedMemberFunctions</link> object.</para>
+ </description>
+ </function>
+ <function name="CompilerGeneratedMemberFunctions" fullname="QDocTests::CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions-1" status="active" access="public" documented="true" meta="copy-constructor" constexpr="true" noexcept="true" overload="true" overload-number="1" signature="CompilerGeneratedMemberFunctions(const QDocTests::CompilerGeneratedMemberFunctions &amp;other)">
+ <parameter type="const QDocTests::CompilerGeneratedMemberFunctions &amp;" name="other" default=""/>
+ <description>
+ <para>Copy-constructs a <link raw="CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html" type="class">CompilerGeneratedMemberFunctions</link> object from <argument>other</argument>.</para>
+ </description>
+ </function>
+ <function name="CompilerGeneratedMemberFunctions" fullname="QDocTests::CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html#CompilerGeneratedMemberFunctions-2" status="active" access="public" documented="true" meta="move-constructor" constexpr="true" noexcept="true" overload="true" overload-number="2" signature="CompilerGeneratedMemberFunctions(const QDocTests::CompilerGeneratedMemberFunctions &amp;&amp;other)">
+ <parameter type="const QDocTests::CompilerGeneratedMemberFunctions &amp;&amp;" name="other" default=""/>
+ <description>
+ <para>Move-constructs a <link raw="CompilerGeneratedMemberFunctions" href="qdoctests-compilergeneratedmemberfunctions.html" type="class">CompilerGeneratedMemberFunctions</link> object from <argument>other</argument>.</para>
+ </description>
+ </function>
+ </class>
+ </namespace>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/src/compilergeneratedmemberfunctions.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/src/compilergeneratedmemberfunctions.cpp
new file mode 100644
index 000000000..fb846fb0c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/src/compilergeneratedmemberfunctions.cpp
@@ -0,0 +1,45 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "compilergeneratedmemberfunctions.h"
+
+/*!
+ \module QDocTests
+ \brief The QDocTests module contains test classes for QDoc.
+
+ The QDocTests module contains test classes for QDoc.
+*/
+
+/*!
+ \namespace QDocTests
+ \inmodule QDocTests
+ \brief The QDocTests namespace contains test classes for QDoc.
+
+ The QDocTests namespace contains test classes for QDoc.
+*/
+
+/*!
+ \class QDocTests::CompilerGeneratedMemberFunctions
+ \inmodule QDocTests
+ \brief A test class for compiler-generated member functions.
+
+ This class is used to test the documentation of compiler-generated member functions.
+*/
+
+/*!
+ \fn constexpr QDocTests::CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions() noexcept
+
+ Constructs a CompilerGeneratedMemberFunctions object.
+*/
+
+/*!
+ \fn constexpr QDocTests::CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions(const CompilerGeneratedMemberFunctions &other) noexcept
+
+ Copy-constructs a CompilerGeneratedMemberFunctions object from \a other.
+*/
+
+/*!
+ \fn constexpr QDocTests::CompilerGeneratedMemberFunctions::CompilerGeneratedMemberFunctions(const CompilerGeneratedMemberFunctions &&other) noexcept
+
+ Move-constructs a CompilerGeneratedMemberFunctions object from \a other.
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/src/compilergeneratedmemberfunctions.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/src/compilergeneratedmemberfunctions.h
new file mode 100644
index 000000000..fce05b727
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/compiler_generated_member_functions/src/compilergeneratedmemberfunctions.h
@@ -0,0 +1,20 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef COMPILERGENERATEDMEMBERFUNCTIONS_H
+#define COMPILERGENERATEDMEMBERFUNCTIONS_H
+
+namespace QDocTests {
+
+class CompilerGeneratedMemberFunctions {
+public:
+ int number() const { return m_number; }
+
+private:
+ int m_number {42};
+
+};
+
+} // QDocTests
+
+#endif // COMPILERGENERATEDMEMBERFUNCTIONS_H
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/comprehensiveproject.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/comprehensiveproject.qdocconf
new file mode 100644
index 000000000..e6751e2b7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/comprehensiveproject.qdocconf
@@ -0,0 +1,105 @@
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ./src/images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+outputformats = HTML WebXML DocBook
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
+DocBook.nosubdirs = true
+DocBook.outputsubdir = docbook
+DocBook.its = true
+DocBook.usedocbookextensions = true
+
+includepaths += -I./src
+
+headerdirs = ./src \
+ ./src/qml
+sourcedirs = ./src \
+ ./src/qml
+exampledirs = ./src/snippets \
+ ./src/qml
+
+macro.CMDFN = \\\\fn
+macro.nothing = \\dontdocument ()
+macro.testnoautolist = \\if defined(test_noautolist)\n\\noautolist\n\\endif
+
+navigation.cppclassespage = "QDoc Test C++ Classes"
+
+defines += test_ignoresince
+defines += test_nestedmacro
+defines += test_properties
+
+macro.ver = "\1"
+macro.ver.match = "^(\\d+\\.\\d+)"
+macro.versionnote.HTML = "<p><b>This \1 was introduced in version \2.</b></p>\n"
+macro.versionnote.DocBook = "<db:para>This \1 was introduced in version \2.</db:para>\n"
+
+moduleheader = TestCPP
+
+project = Test
+description = "A test project for QDoc build artifacts"
+
+examples.fileextensions = "*.qml *.cpp"
+
+macro.begincomment = "\\c{/*}"
+macro.QDocTestVer = "1.1"
+
+navigation.qmltypespage = "QDoc.Test QML Module"
+navigation.qmltypestitle = "Types"
+warninglimit += 1
+
+manifestmeta.examplecategories = "Application Examples" \
+ "Desktop" \
+ "Mobile" \
+ "Embedded"
+tagfile = testtagfile.tags
+version = 6.2.11
+
+examplesinstallpath = test
+
+exampledirs += ./src/examples
+
+excludedirs += ./src/examples/demos/demo/excludes
+
+# Configure .qhp generation
+qhp.projects = Test
+
+qhp.Test.file = test.qhp
+qhp.Test.namespace = org.qt-project.test.001
+qhp.Test.virtualFolder = test
+qhp.Test.indexTitle = UI Components
+qhp.Test.indexRoot =
+
+qhp.Test.subprojects = test classes qmltypes undefined
+qhp.Test.subprojects.test.title = Test
+qhp.Test.subprojects.test.indexTitle = UI Components
+qhp.Test.subprojects.test.selectors = doc:page fake:example module qmlmodule
+qhp.Test.subprojects.test.sortPages = true
+
+qhp.Test.subprojects.classes.title = Classes
+qhp.Test.subprojects.classes.indexTitle = QDoc Test C++ Classes
+qhp.Test.subprojects.classes.selectors = class namespace doc:headerfile boop:whatever
+qhp.Test.subprojects.classes.sortPages = true
+
+qhp.Test.subprojects.qmltypes.title = QML Types
+qhp.Test.subprojects.qmltypes.indexTitle = UI Components
+qhp.Test.subprojects.qmltypes.selectors = qmlmodule:UIComponents,QDoc.Test
+qhp.Test.subprojects.qmltypes.sortPages = true
+
+# Add some meta-data to the example
+manifestmeta.filters = test
+
+manifestmeta.test.names = *
+manifestmeta.test.attributes = isTest:true
+manifestmeta.test.tags = test
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/autolinking.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/autolinking.xml
new file mode 100644
index 000000000..1fde6fac7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/autolinking.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Autolinking</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:section xml:id="testqdoc">
+<db:title>TestQDoc</db:title>
+<db:para>The string <db:link xlink:href="testqdoc.xml">TestQDoc</db:link> links to the C++ namespace unless linking explicitly, <db:link xlink:href="autolinking.xml#testqdoc">like this</db:link>, or <db:link xlink:href="testqdoc.xml">this</db:link>. Also,</db:para>
+<db:para>Autolinks:</db:para>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-testderived.xml">TestQDoc::TestDerived</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+<db:para>Explicit links:</db:para>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-testderived.xml">TestQDoc::TestDerived</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="obsolete-classes.xml#testqdoc">Obsolete Classes#TestQDoc</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+<db:section xml:id="someprop">
+<db:title>someProp</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/classes.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/classes.xml
new file mode 100644
index 000000000..68d7a4616
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/classes.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Classes</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:variablelist role="annotatedclasses">
+<db:varlistentry>
+<db:term><db:link xlink:href="seenclass.xml" xlink:role="class">SeenClass</db:link></db:term>
+<db:listitem>
+<db:para>A public but undocumented class.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="testqdoc-test.xml" xlink:role="class">TestQDoc::Test</db:link></db:term>
+<db:listitem>
+<db:para>A class in a namespace.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="testqdoc-testderived.xml" xlink:role="class">TestQDoc::TestDerived</db:link></db:term>
+<db:listitem>
+<db:para>A class in a namespace, derived from Test.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/cpptypes.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/cpptypes.xml
new file mode 100644
index 000000000..727d59fd0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/cpptypes.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Test C++ Types</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:anchor xml:id="details"/>
+<db:itemizedlist role="testgroup">
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml" xlink:role="class">TestQDoc::Test</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#QDOCTEST_MACRO2" xlink:role="function">TestQDoc::Test::QDOCTEST_MACRO2</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#operator-eq" xlink:role="function">TestQDoc::Test::operator=()</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#someFunctionDefaultArg" xlink:role="function">TestQDoc::Test::someFunctionDefaultArg()</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+<db:variablelist role="members">
+<db:varlistentry>
+<db:term><db:link xlink:href="testqdoc-test.xml" xlink:role="class">TestQDoc::Test</db:link></db:term>
+<db:listitem>
+<db:para>A class in a namespace.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/crossmoduleref.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/crossmoduleref.xml
new file mode 100644
index 000000000..19e6e28f2
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/crossmoduleref.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">CrossModuleRef Namespace</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>Namespace that has documented functions in multiple modules.</db:para>
+<db:para>This namespace was introduced in Qt 3.0.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>CrossModuleRef</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 3.0</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="function-documentation">
+<db:title>Function Documentation</db:title>
+<db:section xml:id="documentMe">
+<db:title its:translate="no">void CrossModuleRef::documentMe()</db:title>
+<db:methodsynopsis>
+<db:void/>
+<db:methodname>documentMe</db:methodname>
+<db:void/>
+<db:synopsisinfo role="meta">plain</db:synopsisinfo>
+<db:synopsisinfo role="signature">void documentMe()</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>Document me!</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/images/leonardo-da-vinci.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/images/leonardo-da-vinci.png
new file mode 100644
index 000000000..854acb4ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/images/leonardo-da-vinci.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/obsolete-classes.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/obsolete-classes.xml
new file mode 100644
index 000000000..f90bcc5ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/obsolete-classes.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Obsolete Classes</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:section xml:id="classes-with-obsolete-members">
+<db:title>Classes with obsolete members</db:title>
+<db:variablelist role="obsoletecppmembers">
+<db:varlistentry>
+<db:term><db:emphasis role="bold">T</db:emphasis></db:term>
+<db:listitem>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml" role="class">Test</db:link> (<db:link xlink:href="testqdoc.xml" xlink:role="namespace">TestQDoc</db:link>)</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-testderived.xml" role="class">TestDerived</db:link> (<db:link xlink:href="testqdoc.xml" xlink:role="namespace">TestQDoc</db:link>)</db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="testqdoc">
+<db:title>TestQDoc</db:title>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qdoc-test-qmlmodule.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qdoc-test-qmlmodule.xml
new file mode 100644
index 000000000..793dc3ea1
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qdoc-test-qmlmodule.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">QDoc.Test QML Module</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>QML Types for the Test module.</db:para>
+<db:para><db:emphasis role="bold">This module is under development and is subject to change.</db:emphasis></db:para>
+<db:para>This module was introduced in Qt 1.1.</db:para>
+</db:abstract>
+</db:info>
+<db:para><db:emphasis role="bold">This module is under development and is subject to change.</db:emphasis></db:para>
+<db:para>This module was introduced in Qt 1.1.</db:para>
+<db:anchor xml:id="details"/>
+<db:variablelist role="members">
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-qdoc-test-abstractparent.xml" xlink:role="">AbstractParent</db:link></db:term>
+<db:listitem>
+<db:para>Abstract base QML type.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-qdoc-test-child.xml" xlink:role="">Child</db:link></db:term>
+<db:listitem>
+<db:para>A Child inheriting its parent.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-qdoc-test-doctest.xml" xlink:role="">DocTest</db:link></db:term>
+<db:listitem>
+<db:para>Represents a doc test case.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-qdoc-test-type.xml" xlink:role="">Type</db:link></db:term>
+<db:listitem>
+<db:para>A QML type documented in a .cpp file.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-qdoc-test-yetanotherchild.xml" xlink:role="">YetAnotherChild</db:link></db:term>
+<db:listitem>
+<db:para>A type inheriting from internal abstract parent.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-int.xml" xlink:role="">int</db:link></db:term>
+<db:listitem>
+<db:para>An integer value type.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-int.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-int.xml
new file mode 100644
index 000000000..f5e99f6ab
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-int.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">int QML Value Type</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>An integer value type.</db:para>
+<db:para>This type was introduced in Qt 1.1.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import QDoc.Test 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since:</db:term>
+<db:listitem>
+<db:para>Qt 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="method-documentation">
+<db:title>Method Documentation</db:title>
+<db:section xml:id="abs-method">
+<db:title>int abs()</db:title>
+<db:para>Returns the absolute value of this integer.</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-abstractparent.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-abstractparent.xml
new file mode 100644
index 000000000..695c7bfe4
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-abstractparent.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">AbstractParent QML Type</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>Abstract base QML type.</db:para>
+<db:para>This type was introduced in Qt 1.1.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import QDoc.Test 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since:</db:term>
+<db:listitem>
+<db:para>Qt 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Inherited By:</db:term>
+<db:listitem>
+<db:para><db:link xlink:href="qml-qdoc-test-child.xml" xlink:role="">Child</db:link></db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="property-documentation">
+<db:title>Property Documentation</db:title>
+<db:section xml:id="children-prop">
+<db:title>[default] children : list&lt;Child&gt;</db:title>
+<db:fieldsynopsis>
+<db:type>list&lt;Child&gt;</db:type>
+<db:varname>children</db:varname>
+<db:modifier>writable</db:modifier>
+<db:modifier>[default]</db:modifier>
+
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>Children of the type.</db:para>
+</db:section>
+<db:section xml:id="name-prop">
+<db:title>name : string</db:title>
+<db:fieldsynopsis>
+<db:type>string</db:type>
+<db:varname>name</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>Name of this parent.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="method-documentation">
+<db:title>Method Documentation</db:title>
+<db:section xml:id="name-method">
+<db:title>void name()</db:title>
+<db:para>Name all children with random names.</db:para>
+</db:section>
+<db:section xml:id="name-method-1">
+<db:title>void name(Child <db:emphasis>child</db:emphasis>, <db:emphasis>name</db:emphasis>)</db:title>
+<db:para>Name a <db:code its:translate="no" role="parameter">child</db:code> using <db:code its:translate="no" role="parameter">name</db:code>.</db:para>
+</db:section>
+<db:section xml:id="rear-method">
+<db:title>void rear(Child <db:emphasis>child</db:emphasis>, var <db:emphasis>method</db:emphasis> = Strict)</db:title>
+<db:para>Do some abstract parenting on <db:code its:translate="no" role="parameter">child</db:code> using a specific <db:code its:translate="no" role="parameter">method</db:code>.</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-child.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-child.xml
new file mode 100644
index 000000000..26a78038e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-child.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">Child QML Type</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A Child inheriting its parent.</db:para>
+<db:para>This type was introduced in Qt 1.1.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import QDoc.Test 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since:</db:term>
+<db:listitem>
+<db:para>Qt 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Inherits:</db:term>
+<db:listitem>
+<db:para><db:link xlink:href="">AbstractParent</db:link></db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="property-documentation">
+<db:title>Property Documentation</db:title>
+<db:section xml:id="children-prop">
+<db:title>[default] children : list&lt;Child&gt;</db:title>
+<db:fieldsynopsis>
+<db:type>list&lt;Child&gt;</db:type>
+<db:varname>children</db:varname>
+<db:modifier>writable</db:modifier>
+<db:modifier>[default]</db:modifier>
+
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>Children of the type.</db:para>
+</db:section>
+<db:section xml:id="name-prop">
+<db:title>name : string</db:title>
+<db:fieldsynopsis>
+<db:type>string</db:type>
+<db:varname>name</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>Name of this child.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="method-documentation">
+<db:title>Method Documentation</db:title>
+<db:section xml:id="name-method">
+<db:title>void name(Child <db:emphasis>child</db:emphasis>, <db:emphasis>name</db:emphasis>)</db:title>
+<db:para>Name a <db:code its:translate="no" role="parameter">child</db:code> of this child using <db:code its:translate="no" role="parameter">name</db:code>.</db:para>
+</db:section>
+<db:section xml:id="name-method">
+<db:title>void name()</db:title>
+<db:para>Name all children with random names.</db:para>
+</db:section>
+<db:section xml:id="rear-method">
+<db:title>void rear(Child <db:emphasis>child</db:emphasis>, var <db:emphasis>method</db:emphasis> = Strict)</db:title>
+<db:para>Do some abstract parenting on <db:code its:translate="no" role="parameter">child</db:code> using a specific <db:code its:translate="no" role="parameter">method</db:code>.</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-doctest.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-doctest.xml
new file mode 100644
index 000000000..9a021a6fc
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-doctest.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">DocTest QML Type</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>Represents a doc test case.</db:para>
+<db:para>This type was introduced in QDoc.Test 0.9.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import QDoc.Test 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since:</db:term>
+<db:listitem>
+<db:para>QDoc.Test 0.9</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:section xml:id="introduction">
+<db:title>Introduction</db:title>
+<db:para>A documentation test case, itself documented inline in DocTest.qml.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="property-documentation">
+<db:title>Property Documentation</db:title>
+<db:section xml:id="active-prop">
+<db:title>[default: true] active : bool</db:title>
+<db:fieldsynopsis>
+<db:type>bool</db:type>
+<db:varname>active</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>Whether the test is active.</db:para>
+<db:section>
+<db:title>See Also</db:title>
+<db:para><db:emphasis>See also </db:emphasis>
+<db:simplelist type="vert" role="see-also">
+<db:member><db:link xlink:href="qml-qdoc-test-doctest.xml#name-prop">name</db:link></db:member>
+</db:simplelist>
+</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="name-prop">
+<db:title>[required] name : string</db:title>
+<db:fieldsynopsis>
+<db:type>string</db:type>
+<db:varname>name</db:varname>
+<db:modifier>writable</db:modifier>
+<db:modifier>required</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>Name of the test.</db:para>
+<db:programlisting language="qml" its:translate="no">DocTest {
+ name: &amp;quot;test&amp;quot;
+ // ...
+}
+</db:programlisting>
+</db:section>
+</db:section>
+<db:section xml:id="signal-documentation">
+<db:title>Signal Documentation</db:title>
+<db:section xml:id="completed-signal">
+<db:title>completed()</db:title>
+<db:note>
+<db:para>The corresponding handler is <db:code>onCompleted</db:code>.</db:para>
+</db:note>
+</db:section>
+<db:section xml:id="foo-signal">
+<db:title>foo(var <db:emphasis>bar</db:emphasis>)</db:title>
+<db:para>Signal with parameter <db:code its:translate="no" role="parameter">bar</db:code>.</db:para>
+<db:note>
+<db:para>The corresponding handler is <db:code>onFoo</db:code>.</db:para>
+</db:note>
+</db:section>
+<db:section xml:id="itsHappening-signal">
+<db:title>itsHappening(bool <db:emphasis>really</db:emphasis>)</db:title>
+<db:para>Signals that something is <db:code its:translate="no" role="parameter">really</db:code> happening.</db:para>
+<db:note>
+<db:para>The corresponding handler is <db:code>onItsHappening</db:code>.</db:para>
+</db:note>
+</db:section>
+</db:section>
+<db:section xml:id="method-documentation">
+<db:title>Method Documentation</db:title>
+<db:section xml:id="fail-method">
+<db:title>[since QDoc.Test 1.0] fail(<db:emphasis>message</db:emphasis> = &quot;oops&quot;)</db:title>
+<db:para>Fails the current test case, with the optional <db:code its:translate="no" role="parameter">message</db:code>.</db:para>
+<db:para>This method was introduced in QDoc.Test 1.0.</db:para>
+</db:section>
+<db:section xml:id="fail_hard-method">
+<db:title>fail_hard(<db:emphasis>msg</db:emphasis> = &quot;facepalm&quot;, <db:emphasis>option</db:emphasis> = 123)</db:title>
+<db:para>Fails the current test case, hard.</db:para>
+<db:itemizedlist>
+<db:listitem>
+<db:para>Prints out <db:code its:translate="no" role="parameter">msg</db:code>.</db:para>
+</db:listitem>
+<db:listitem>
+<db:para>Accepts a random <db:code its:translate="no" role="parameter">option</db:code>.</db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-oldtype.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-oldtype.xml
new file mode 100644
index 000000000..e2d1059b4
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-oldtype.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">OldType QML Type</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>Deprecated old type.</db:para>
+<db:para><db:emphasis role="bold">This type is deprecated since QDoc.Test 1.0. We strongly advise against using it in new code.</db:emphasis></db:para>
+<db:para>This type was introduced in Qt 1.1.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import QDoc.Test 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since:</db:term>
+<db:listitem>
+<db:para>Qt 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Status:</db:term>
+<db:listitem>
+<db:para>Deprecated since 1.0</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:para><db:emphasis role="bold">This type is deprecated since QDoc.Test 1.0. We strongly advise against using it in new code.</db:emphasis></db:para>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-type.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-type.xml
new file mode 100644
index 000000000..826abfe3b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-type.xml
@@ -0,0 +1,237 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">Type QML Type</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A QML type documented in a .cpp file.</db:para>
+<db:para>This type was introduced in Qt 1.1.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import QDoc.Test 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since:</db:term>
+<db:listitem>
+<db:para>Qt 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>In C++:</db:term>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml">Test</db:link></db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Status:</db:term>
+<db:listitem>
+<db:para>&lt;Work In Progress&gt;</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="property-documentation">
+<db:title>Property Documentation</db:title>
+<db:section xml:id="fifth-prop">
+<db:title>fifth : int</db:title>
+<db:fieldsynopsis>
+<db:type>int</db:type>
+<db:varname>fifth</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:bridgehead renderas="sect2">fourth : int</db:bridgehead><db:fieldsynopsis>
+<db:type>int</db:type>
+<db:varname>fourth</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>A group of properties sharing a documentation comment.</db:para>
+</db:section>
+<db:section xml:id="group-prop">
+<db:title>group group</db:title>
+<db:bridgehead renderas="sect2" xml:id="group.first-prop">group.first : int</db:bridgehead>
+<db:fieldsynopsis>
+<db:type>int</db:type>
+<db:varname>group.first</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:bridgehead renderas="sect2" xml:id="group.second-prop">group.second : int</db:bridgehead>
+<db:fieldsynopsis>
+<db:type>int</db:type>
+<db:varname>group.second</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:bridgehead renderas="sect2" xml:id="group.third-prop">group.third : int</db:bridgehead>
+<db:fieldsynopsis>
+<db:type>int</db:type>
+<db:varname>group.third</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>A property group.</db:para>
+</db:section>
+<db:section xml:id="id-prop">
+<db:title>[read-only] id : int</db:title>
+<db:fieldsynopsis>
+<db:type>int</db:type>
+<db:varname>id</db:varname>
+<db:modifier>[read-only]</db:modifier>
+
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>A read-only property.</db:para>
+</db:section>
+<db:section xml:id="name-prop">
+<db:title>[required] name : string</db:title>
+<db:fieldsynopsis>
+<db:type>string</db:type>
+<db:varname>name</db:varname>
+<db:modifier>writable</db:modifier>
+<db:modifier>required</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>Name of the Test.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="attached-property-documentation">
+<db:title>Attached Property Documentation</db:title>
+<db:section xml:id="type-attached-prop">
+<db:title>[default: Type.NoType] Type.type : enumeration</db:title>
+<db:fieldsynopsis>
+<db:type>enumeration</db:type>
+<db:varname>Type.type</db:varname>
+<db:modifier>attached</db:modifier>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:informaltable>
+<db:thead>
+<db:tr>
+<db:th>Constant</db:th>
+<db:th>Description</db:th>
+</db:tr>
+</db:thead>
+<db:tr>
+<db:td>
+<db:para its:translate="no">Type.NoType</db:para>
+</db:td>
+<db:td>
+<db:para>Nothing</db:para>
+</db:td>
+</db:tr>
+<db:tr>
+<db:td>
+<db:para its:translate="no">Type.SomeType</db:para>
+</db:td>
+<db:td>
+<db:para>Something</db:para>
+</db:td>
+</db:tr>
+</db:informaltable>
+</db:section>
+</db:section>
+<db:section xml:id="signal-documentation">
+<db:title>Signal Documentation</db:title>
+<db:section xml:id="completed-signal">
+<db:title>completed(int <db:emphasis>status</db:emphasis>)</db:title>
+<db:para>This signal is emitted when the operation completed with <db:code its:translate="no" role="parameter">status</db:code>.</db:para>
+<db:note>
+<db:para>The corresponding handler is <db:code>onCompleted</db:code>.</db:para>
+</db:note>
+</db:section>
+<db:section xml:id="group.created-signal">
+<db:title>group.created()</db:title>
+<db:para>This signal is prefixed with <db:emphasis>group</db:emphasis>.</db:para>
+<db:note>
+<db:para>The corresponding handler is <db:code>group.onCreated</db:code>.</db:para>
+</db:note>
+</db:section>
+</db:section>
+<db:section xml:id="attached-signal-documentation">
+<db:title>Attached Signal Documentation</db:title>
+<db:section xml:id="configured-signal">
+<db:title>configured()</db:title>
+<db:para>This attached signal is emitted when the type was configured.</db:para>
+<db:note>
+<db:para>The corresponding handler is <db:code>onConfigured</db:code>.</db:para>
+</db:note>
+</db:section>
+</db:section>
+<db:section xml:id="method-documentation">
+<db:title>Method Documentation</db:title>
+<db:section xml:id="disable-method">
+<db:title>disable()</db:title>
+<db:methodsynopsis>
+<db:type></db:type>
+<db:methodname>disable</db:methodname>
+<db:void/>
+<db:synopsisinfo role="meta">qmlmethod</db:synopsisinfo>
+<db:synopsisinfo role="signature">disable()</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:bridgehead renderas="sect2">enable()</db:bridgehead><db:methodsynopsis>
+<db:type></db:type>
+<db:methodname>enable</db:methodname>
+<db:void/>
+<db:synopsisinfo role="meta">qmlmethod</db:synopsisinfo>
+<db:synopsisinfo role="signature">enable()</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>Enables or disables this type.</db:para>
+</db:section>
+<db:section xml:id="copy-method">
+<db:title>Type copy(<db:emphasis>a</db:emphasis>)</db:title>
+<db:para>Returns another Type based on <db:code its:translate="no" role="parameter">a</db:code>.</db:para>
+</db:section>
+<db:section xml:id="futureDeprecated-method">
+<db:title>[until 6.3] futureDeprecated()</db:title>
+<db:para>This method is scheduled for deprecation in version 6.3.</db:para>
+<db:para>Use something else instead.</db:para>
+<db:para>This is a method that's marked for deprecation in a future version.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="obsolete">
+<db:title>Obsolete Members for Type</db:title>
+<db:para><db:emphasis role="bold">The following members of QML type <db:link xlink:href="qml-qdoc-test-type.xml">Type</db:link> are deprecated.</db:emphasis> We strongly advise against using them in new code.</db:para>
+<db:section xml:id="obsolete-method-documentation">
+<db:title>Obsolete Method Documentation</db:title>
+<db:section xml:id="deprecatedMethod-method">
+<db:title>[deprecated in 6.2] deprecatedMethod()</db:title>
+<db:para>This method is deprecated since QDoc.Test 6.2. We strongly advise against using it in new code.</db:para>
+<db:para>This method has no replacement.</db:para>
+<db:para>This is a method that should include information about being deprecated and that it has been so since 6.2 in its docs.</db:para>
+</db:section>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-yetanotherchild.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-yetanotherchild.xml
new file mode 100644
index 000000000..040c7360b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-qdoc-test-yetanotherchild.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">YetAnotherChild QML Type</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A type inheriting from internal abstract parent.</db:para>
+<db:para>This type was introduced in Qt 1.1.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import QDoc.Test 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since:</db:term>
+<db:listitem>
+<db:para>Qt 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="property-documentation">
+<db:title>Property Documentation</db:title>
+<db:section xml:id="prop-prop">
+<db:title>prop : int</db:title>
+<db:fieldsynopsis>
+<db:type>int</db:type>
+<db:varname>prop</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>Propagated to inheriting type docs.</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-test-nover-doctest.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-test-nover-doctest.xml
new file mode 100644
index 000000000..d719d9f6f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-test-nover-doctest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">DocTest QML Type</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>Shadows the type name in QDoc.Test module.</db:para>
+<db:para>This type was introduced in Qt 1.1.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import Test.NoVer</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since:</db:term>
+<db:listitem>
+<db:para>Qt 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Status:</db:term>
+<db:listitem>
+<db:para>Tech Preview</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-test-nover-typenoversion.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-test-nover-typenoversion.xml
new file mode 100644
index 000000000..581207402
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-test-nover-typenoversion.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">TypeNoVersion QML Type</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>Another QML type documented in a .cpp file.</db:para>
+<db:para>This type was introduced in Qt 1.1.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import Test.NoVer</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since:</db:term>
+<db:listitem>
+<db:para>Qt 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Status:</db:term>
+<db:listitem>
+<db:para>Tech Preview</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-themodule-thetype.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-themodule-thetype.xml
new file mode 100644
index 000000000..b8a31eb9f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-themodule-thetype.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">TheType QML Type</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import TheModule</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>In C++:</db:term>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-testderived.xml">TestDerived</db:link></db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="property-documentation">
+<db:title>Property Documentation</db:title>
+<db:section xml:id="name-prop">
+<db:title>[read-only] name : string</db:title>
+<db:fieldsynopsis>
+<db:type>string</db:type>
+<db:varname>name</db:varname>
+<db:modifier>[read-only]</db:modifier>
+
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>Read-only status of this property is resolved from Q_PROPERTY.</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-uicomponents-progressbar.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-uicomponents-progressbar.xml
new file mode 100644
index 000000000..f1a8d4129
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-uicomponents-progressbar.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">ProgressBar QML Type</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A component that shows the progress of an event.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import UIComponents 1.0</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:para>A <db:link xlink:href="qml-uicomponents-progressbar.xml">ProgressBar</db:link> shows the linear progress of an event as its <db:link xlink:href="qml-uicomponents-progressbar.xml#value-prop">value</db:link>. The range is specified using the <db:link xlink:href="qml-uicomponents-progressbar.xml#minimum-prop">minimum</db:link> and the <db:link xlink:href="qml-uicomponents-progressbar.xml#maximum-prop">maximum</db:link> values.</db:para>
+<db:para>The <db:link xlink:href="qml-uicomponents-progressbar.xml">ProgressBar</db:link> component is part of the <db:link xlink:href="uicomponents-qmlmodule.xml">UI Components</db:link> module.</db:para>
+<db:para>This documentation is part of the <db:link xlink:href="test-componentset-example.xml">UIComponents</db:link> example.</db:para>
+</db:section>
+<db:section xml:id="property-documentation">
+<db:title>Property Documentation</db:title>
+<db:section xml:id="color-prop">
+<db:title>color : color</db:title>
+<db:fieldsynopsis>
+<db:type>color</db:type>
+<db:varname>color</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>The color of the <db:link xlink:href="qml-uicomponents-progressbar.xml">ProgressBar</db:link>'s gradient. Must bind to a color type.</db:para>
+<db:section>
+<db:title>See Also</db:title>
+<db:para><db:emphasis>See also </db:emphasis>
+<db:simplelist type="vert" role="see-also">
+<db:member><db:link xlink:href="qml-uicomponents-progressbar.xml#secondColor-prop">secondColor</db:link></db:member>
+</db:simplelist>
+</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="maximum-prop">
+<db:title>maximum : int</db:title>
+<db:fieldsynopsis>
+<db:type>int</db:type>
+<db:varname>maximum</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>The maximum value of the <db:link xlink:href="qml-uicomponents-progressbar.xml">ProgressBar</db:link> range. The <db:link xlink:href="qml-uicomponents-progressbar.xml#value-prop">value</db:link> must not be more than this value.</db:para>
+</db:section>
+<db:section xml:id="minimum-prop">
+<db:title>minimum : int</db:title>
+<db:fieldsynopsis>
+<db:type>int</db:type>
+<db:varname>minimum</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>The minimum value of the <db:link xlink:href="qml-uicomponents-progressbar.xml">ProgressBar</db:link> range. The <db:link xlink:href="qml-uicomponents-progressbar.xml#value-prop">value</db:link> must not be less than this value.</db:para>
+</db:section>
+<db:section xml:id="secondColor-prop">
+<db:title>secondColor : color</db:title>
+<db:fieldsynopsis>
+<db:type>color</db:type>
+<db:varname>secondColor</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>The second color of the <db:link xlink:href="qml-uicomponents-progressbar.xml">ProgressBar</db:link>'s gradient. Must bind to a color type.</db:para>
+<db:section>
+<db:title>See Also</db:title>
+<db:para><db:emphasis>See also </db:emphasis>
+<db:simplelist type="vert" role="see-also">
+<db:member><db:link xlink:href="qml-uicomponents-progressbar.xml#color-prop">color</db:link></db:member>
+</db:simplelist>
+</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="value-prop">
+<db:title>value : int</db:title>
+<db:fieldsynopsis>
+<db:type>int</db:type>
+<db:varname>value</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>The value of the progress.</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-uicomponents-switch.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-uicomponents-switch.xml
new file mode 100644
index 000000000..2ad3a31b5
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-uicomponents-switch.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">Switch QML Type</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A component that can be turned on or off.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import UIComponents 1.0</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:para>A toggle switch has two states: an <db:code>on</db:code> and an <db:code>off</db:code> state. The <db:code>off</db:code> state is when the <db:link xlink:href="qml-uicomponents-switch.xml#on-prop">on</db:link> property is set to <db:code>false</db:code>.</db:para>
+<db:para>The ToggleSwitch component is part of the <db:link xlink:href="uicomponents-qmlmodule.xml">UI Components</db:link> module.</db:para>
+<db:para>This documentation is part of the <db:link xlink:href="test-componentset-example.xml">UIComponents</db:link> example.</db:para>
+</db:section>
+<db:section xml:id="property-documentation">
+<db:title>Property Documentation</db:title>
+<db:section xml:id="on-prop">
+<db:title>on : bool</db:title>
+<db:fieldsynopsis>
+<db:type>bool</db:type>
+<db:varname>on</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>Indicates the state of the switch. If <db:code>false</db:code>, then the switch is in the <db:code>off</db:code> state.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="method-documentation">
+<db:title>Method Documentation</db:title>
+<db:section xml:id="toggle-method">
+<db:title>toggle()</db:title>
+<db:para>A method to toggle the switch. If the switch is <db:code>on</db:code>, the toggling it will turn it <db:code>off</db:code>. Toggling a switch in the <db:code>off</db:code> position will turn it <db:code>on</db:code>.</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-uicomponents-tabwidget.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-uicomponents-tabwidget.xml
new file mode 100644
index 000000000..f0bdf582a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qml-uicomponents-tabwidget.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">TabWidget QML Type</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A widget that places its children as tabs.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import UIComponents 1.0</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:para>A <db:link xlink:href="qml-uicomponents-tabwidget.xml">TabWidget</db:link> places its children as tabs in a view. Selecting a tab involves selecting the tab at the top.</db:para>
+<db:para>The <db:link xlink:href="qml-uicomponents-tabwidget.xml">TabWidget</db:link> component is part of the <db:link xlink:href="uicomponents-qmlmodule.xml">UI Components</db:link> module.</db:para>
+<db:para>This documentation is part of the <db:link xlink:href="test-componentset-example.xml">UIComponents</db:link> example.</db:para>
+<db:section xml:id="adding-tabs">
+<db:title>Adding Tabs</db:title>
+<db:para>To add a tab, declare the tab as a child of the <db:link xlink:href="qml-uicomponents-tabwidget.xml">TabWidget</db:link>.</db:para>
+<db:programlisting language="cpp" its:translate="no">TabWidget {
+ id: tabwidget
+
+ Rectangle {
+ id: tab1
+ color: &amp;quot;red&amp;quot;
+ //... omitted
+ }
+ Rectangle {
+ id: tab2
+ color: &amp;quot;blue&amp;quot;
+ //... omitted
+ }
+
+}
+</db:programlisting>
+</db:section>
+</db:section>
+<db:section xml:id="property-documentation">
+<db:title>Property Documentation</db:title>
+<db:section xml:id="current-prop">
+<db:title>current : int</db:title>
+<db:fieldsynopsis>
+<db:type>int</db:type>
+<db:varname>current</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>The currently active tab in the <db:link xlink:href="qml-uicomponents-tabwidget.xml">TabWidget</db:link>.</db:para>
+</db:section>
+<db:section xml:id="sampleReadOnlyProperty-prop">
+<db:title>[read-only] sampleReadOnlyProperty : int</db:title>
+<db:fieldsynopsis>
+<db:type>int</db:type>
+<db:varname>sampleReadOnlyProperty</db:varname>
+<db:modifier>[read-only]</db:modifier>
+
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>A sample <db:code>read-only</db:code> property. A contrived property to demonstrate QDoc's ability to detect read-only properties.</db:para>
+<db:para>The signature is:</db:para>
+<db:programlisting language="cpp" its:translate="no">readonly property int sampleReadOnlyProperty: 0
+</db:programlisting>
+<db:para>Note that the property must be initialized to a value.</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qmlmodules.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qmlmodules.xml
new file mode 100644
index 000000000..af130ca0c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/qmlmodules.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>QML Modules</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:variablelist role="qml-modules">
+<db:varlistentry>
+<db:term><db:link xlink:href="test-empty-qmlmodule.xml" xlink:role="">No QML Types Here</db:link></db:term>
+<db:listitem>
+<db:para>A QML module with no member types.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="qdoc-test-qmlmodule.xml" xlink:role="">QDoc.Test QML Module</db:link></db:term>
+<db:listitem>
+<db:para>QML Types for the Test module.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="themodule-qmlmodule.xml" xlink:role="">TheModule</db:link></db:term>
+<db:listitem>
+<db:para></db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="uicomponents-qmlmodule.xml" xlink:role="">UI Components</db:link></db:term>
+<db:listitem>
+<db:para>Basic set of UI components.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="test-nover-qmlmodule.xml" xlink:role="">Versionless QML Module</db:link></db:term>
+<db:listitem>
+<db:para>QML Types for the Test module without version.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="qml-types">
+<db:title>QML types</db:title>
+<db:variablelist role="qmltypesbymodule QDoc.Test">
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-qdoc-test-abstractparent.xml" xlink:role="">AbstractParent</db:link></db:term>
+<db:listitem>
+<db:para>Abstract base QML type.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-qdoc-test-child.xml" xlink:role="">Child</db:link></db:term>
+<db:listitem>
+<db:para>A Child inheriting its parent.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-qdoc-test-doctest.xml" xlink:role="">DocTest</db:link></db:term>
+<db:listitem>
+<db:para>Represents a doc test case.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-qdoc-test-type.xml" xlink:role="">Type</db:link></db:term>
+<db:listitem>
+<db:para>A QML type documented in a .cpp file.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-qdoc-test-yetanotherchild.xml" xlink:role="">YetAnotherChild</db:link></db:term>
+<db:listitem>
+<db:para>A type inheriting from internal abstract parent.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:section>
+<db:section xml:id="qml-value-types">
+<db:title>QML value types</db:title>
+<db:simplelist>
+<db:member><db:link xlink:href="i">I</db:link></db:member>
+</db:simplelist>
+<db:variablelist role="qmlvaluetypes">
+<db:varlistentry xml:id="i">
+<db:term><db:emphasis role="bold">I</db:emphasis></db:term>
+<db:listitem>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="qml-int.xml">int</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:variablelist role="qmlvaluetypesbymodule QDoc.Test">
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-int.xml" xlink:role="">int</db:link></db:term>
+<db:listitem>
+<db:para>An integer value type.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/seenclass.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/seenclass.xml
new file mode 100644
index 000000000..abb697567
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/seenclass.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">SeenClass Class</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A public but undocumented class.</db:para>
+<db:para>This class was introduced in Qt 2.0.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>SeenClass</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 2.0</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-cmaketest-cmakelists-txt.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-cmaketest-cmakelists-txt.xml
new file mode 100644
index 000000000..a29ba9a12
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-cmaketest-cmakelists-txt.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>CMake Example Project</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:programlisting language="cpp" its:translate="no">cmake_minimum_required(VERSION 3.16)
+project (QDOCTEST)
+
+</db:programlisting>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-cmaketest-example.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-cmaketest-example.xml
new file mode 100644
index 000000000..549aa1fec
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-cmaketest-example.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>CMake Example Project</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:mediaobject>
+<db:imageobject>
+<db:imagedata fileref="images/leonardo-da-vinci.png"/>
+</db:imageobject>
+</db:mediaobject>
+<db:section>
+<db:title>List of Files</db:title>
+<db:para>Files:</db:para>
+<db:section>
+<db:title>List of Files</db:title>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="cmaketest/CMakeLists.txt">cmaketest/CMakeLists.txt</db:link></db:para></db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="cmaketest/main.cpp">cmaketest/main.cpp</db:link></db:para></db:listitem>
+</db:itemizedlist>
+</db:section>
+</db:section></db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-cmaketest-main-cpp.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-cmaketest-main-cpp.xml
new file mode 100644
index 000000000..d7e7029a2
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-cmaketest-main-cpp.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>CMake Example Project</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:programlisting language="cpp" its:translate="no">void main(){}
+
+</db:programlisting>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-componentset-pro.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-componentset-pro.xml
new file mode 100644
index 000000000..606a5ac94
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-componentset-pro.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>QML Documentation Example</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>Example for documenting QML types.</db:para>
+</db:abstract>
+</db:info>
+<db:programlisting language="cpp" its:translate="no">SOURCES = componentset.pro \
+ ProgressBar.qml \
+ Switch.qml \
+ TabWidget.qml \
+ uicomponents.qdoc
+
+</db:programlisting>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-componentset-qml.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-componentset-qml.xml
new file mode 100644
index 000000000..34f5e59b0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-componentset-qml.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>QML Documentation Example</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>Example for documenting QML types.</db:para>
+</db:abstract>
+</db:info>
+<db:programlisting language="qml" its:translate="no">// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import QtQuick 2.0
+
+Item {
+}
+
+</db:programlisting>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-example.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-example.xml
new file mode 100644
index 000000000..a5ffd1cd0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-example.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>QML Documentation Example</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>Example for documenting QML types.</db:para>
+</db:abstract>
+</db:info>
+<db:para>This example demonstrates one of the ways to document QML types. It also generates a warning about a missing example image, on purpose.</db:para>
+<db:para>In particular, there are sample types that are documented with QDoc commands comments. There are documentation comments for the QML types and their public interfaces. The types are grouped into a module, the <db:link xlink:href="uicomponents-qmlmodule.xml">UI Components</db:link> module.</db:para>
+<db:para>The uicomponents.qdoc file generates the overview page for the <db:link xlink:href="uicomponents-qmlmodule.xml">UI Components</db:link> module page.</db:para>
+<db:para>The generated documentation is available in the <db:link xlink:href="uicomponents-qmlmodule.xml">UI Components</db:link> module.</db:para>
+<db:section xml:id="qml-class">
+<db:title>QML Class</db:title>
+<db:para>The QML types use the \qmltype to document the type. In addition, they have the \inmodule command in order for QDoc to associate them to the <db:code>UIComponents</db:code> module.</db:para>
+<db:para>QDoc uses the \brief command to place a basic description when listing the types.</db:para>
+</db:section>
+<db:section xml:id="properties-signals-handlers-and-methods">
+<db:title>Properties, Signals, Handlers, and Methods</db:title>
+<db:para>The types have their properties, signals, handlers, and methods defined in their respective QML files. QDoc associates the properties and methods to the types, therefore, you only need to place the documentation above the property, method, or signal.</db:para>
+<db:para>To document the type of a <db:emphasis>property alias</db:emphasis>, you must use the \qmlproperty command to specify the data type.</db:para>
+<db:programlisting language="cpp" its:translate="no">\qmlproperty int anAliasedProperty
+An aliased property of type int.
+</db:programlisting>
+<db:section xml:id="internal-documentation">
+<db:title>Internal Documentation</db:title>
+<db:para>You may declare that a documentation is for internal use by placing the \internal command after the beginning QDoc comment <db:code>/*</db:code>. QDoc will prevent the internal documentation from appearing in the public API.</db:para>
+<db:para>If you wish to omit certain parts of the documentation, you may use the \omit and \endomit command.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="qml-types-with-c-implementation">
+<db:title>QML Types with C++ Implementation</db:title>
+<db:para>This example only demonstrates the documentation for types in QML files, but the regular QML commands may be placed inside C++ classes to define the public API of the QML type.</db:para>
+</db:section>
+<db:section>
+<db:title>List of Files</db:title>
+<db:para>Files:</db:para>
+<db:section>
+<db:title>List of Files</db:title>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="componentset/ProgressBar.qml">componentset/ProgressBar.qml</db:link></db:para></db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="componentset/Switch.qml">componentset/Switch.qml</db:link></db:para></db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="componentset/TabWidget.qml">componentset/TabWidget.qml</db:link></db:para></db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="componentset/componentset.pro">componentset/componentset.pro</db:link></db:para></db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="componentset/componentset.qml">componentset/componentset.qml</db:link></db:para></db:listitem>
+</db:itemizedlist>
+</db:section>
+</db:section></db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-progressbar-qml.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-progressbar-qml.xml
new file mode 100644
index 000000000..e284bd51a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-progressbar-qml.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>QML Documentation Example</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>Example for documenting QML types.</db:para>
+</db:abstract>
+</db:info>
+<db:programlisting language="qml" its:translate="no">// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick 1.0
+
+/*!
+ \qmltype ProgressBar
+ \inqmlmodule UIComponents
+ \brief A component that shows the progress of an event.
+
+ A ProgressBar shows the linear progress of an event as its \l value.
+ The range is specified using the \l {minimum} and the \l{maximum} values.
+
+ The ProgressBar component is part of the \l {UI Components} module.
+
+ This documentation is part of the \l{componentset}{UIComponents} example.
+*/
+Item {
+ id: progressbar
+
+ /*!
+ The minimum value of the ProgressBar range.
+ The \l value must not be less than this value.
+ */
+ property int minimum: 0
+
+ /*!
+ The maximum value of the ProgressBar range.
+ The \l value must not be more than this value.
+ */
+ property int maximum: 100
+
+ /*!
+ The value of the progress.
+ */
+ property int value: 0
+
+ /*!
+ \qmlproperty color ProgressBar::color
+ The color of the ProgressBar's gradient. Must bind to a color type.
+
+ \omit
+ The &amp;quot;\qmlproperty &amp;lt;type&amp;gt; &amp;lt;property name&amp;gt;&amp;quot; is needed because
+ property alias need to have their types manually entered.
+
+ QDoc will not publish the documentation within omit and endomit.
+ \endomit
+
+ \sa secondColor
+ */
+ property alias color: gradient1.color
+
+ /*!
+ \qmlproperty color ProgressBar::secondColor
+ The second color of the ProgressBar's gradient.
+ Must bind to a color type.
+
+ \omit
+ The &amp;quot;\qmlproperty &amp;lt;type&amp;gt; &amp;lt;property name&amp;gt;&amp;quot; is needed because
+ property alias need to have their types manually entered.
+
+ QDoc will not publish the documentation within omit and endomit.
+ \endomit
+
+ \sa color
+ */
+ property alias secondColor: gradient2.color
+
+ width: 250; height: 23
+ clip: true
+
+ Rectangle {
+ id: highlight
+
+ /*!
+ An internal documentation comment. The widthDest property is not
+ a public API and therefore will not be exposed.
+ */
+ property int widthDest: ((progressbar.width * (value - minimum)) / (maximum - minimum) - 6)
+
+ width: highlight.widthDest
+ Behavior on width { SmoothedAnimation { velocity: 1200 } }
+
+ anchors { left: parent.left; top: parent.top; bottom: parent.bottom; margins: 3 }
+ radius: 1
+ gradient: Gradient {
+ GradientStop { id: gradient1; position: 0.0 }
+ GradientStop { id: gradient2; position: 1.0 }
+ }
+
+ }
+ Text {
+ anchors { right: highlight.right; rightMargin: 6; verticalCenter: parent.verticalCenter }
+ color: &amp;quot;white&amp;quot;
+ font.bold: true
+ text: Math.floor((value - minimum) / (maximum - minimum) * 100) + '%'
+ }
+}
+
+</db:programlisting>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-switch-qml.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-switch-qml.xml
new file mode 100644
index 000000000..93083a18a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-switch-qml.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>QML Documentation Example</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>Example for documenting QML types.</db:para>
+</db:abstract>
+</db:info>
+<db:programlisting language="qml" its:translate="no">// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick 1.0
+
+/*!
+ \qmltype ToggleSwitch
+ \inqmlmodule UIComponents
+ \brief A component that can be turned on or off.
+
+ A toggle switch has two states: an \c on and an \c off state. The \c off
+ state is when the \l on property is set to \c false.
+
+ The ToggleSwitch component is part of the \l {UI Components} module.
+
+ This documentation is part of the \l{componentset}{UIComponents} example.
+
+*/
+Item {
+ id: toggleswitch
+ width: background.width; height: background.height
+
+ /*!
+ Indicates the state of the switch. If \c false, then the switch is in
+ the \c off state.
+
+ \omit
+ The \qmlproperty &amp;lt;type&amp;gt; &amp;lt;propertyname&amp;gt; is not necessary as QDoc
+ will associate this property to the ToggleSwitch
+
+ QDoc will not publish the documentation within omit and endomit.
+ \endomit
+ */
+ property bool on: false
+
+ /*!
+ A method to toggle the switch. If the switch is \c on, the toggling it
+ will turn it \c off. Toggling a switch in the \c off position will
+ turn it \c on.
+ */
+ function toggle() {
+ if (toggleswitch.state == &amp;quot;on&amp;quot;)
+ toggleswitch.state = &amp;quot;off&amp;quot;;
+ else
+ toggleswitch.state = &amp;quot;on&amp;quot;;
+ }
+
+ /*!
+ \internal
+
+ An internal function to synchronize the switch's internals. This
+ function is not for public access. The \internal command will
+ prevent QDoc from publishing this comment in the public API.
+ */
+ function releaseSwitch() {
+ if (knob.x == 1) {
+ if (toggleswitch.state == &amp;quot;off&amp;quot;) return;
+ }
+ if (knob.x == 78) {
+ if (toggleswitch.state == &amp;quot;on&amp;quot;) return;
+ }
+ toggle();
+ }
+
+ Rectangle {
+ id: background
+ width: 130; height: 48
+ radius: 48
+ color: &amp;quot;lightsteelblue&amp;quot;
+ MouseArea { anchors.fill: parent; onClicked: toggle() }
+ }
+
+ Rectangle {
+ id: knob
+ width: 48; height: 48
+ radius: width
+ color: &amp;quot;lightblue&amp;quot;
+
+ MouseArea {
+ anchors.fill: parent
+ drag.target: knob; drag.axis: Drag.XAxis; drag.minimumX: 1; drag.maximumX: 78
+ onClicked: toggle()
+ onReleased: releaseSwitch()
+ }
+ }
+
+ states: [
+ State {
+ name: &amp;quot;on&amp;quot;
+ PropertyChanges { target: knob; x: 78 }
+ PropertyChanges { target: toggleswitch; on: true }
+ },
+ State {
+ name: &amp;quot;off&amp;quot;
+ PropertyChanges { target: knob; x: 1 }
+ PropertyChanges { target: toggleswitch; on: false }
+ }
+ ]
+
+ transitions: Transition {
+ NumberAnimation { properties: &amp;quot;x&amp;quot;; easing.type: Easing.InOutQuad; duration: 200 }
+ }
+}
+
+</db:programlisting>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-tabwidget-qml.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-tabwidget-qml.xml
new file mode 100644
index 000000000..24c672b9f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-componentset-tabwidget-qml.xml
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>QML Documentation Example</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>Example for documenting QML types.</db:para>
+</db:abstract>
+</db:info>
+<db:programlisting language="qml" its:translate="no">// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick 1.0
+
+/*!
+ \qmltype TabWidget
+ \inqmlmodule UIComponents
+ \brief A widget that places its children as tabs.
+
+ A TabWidget places its children as tabs in a view. Selecting
+ a tab involves selecting the tab at the top.
+
+ The TabWidget component is part of the \l {UI Components} module.
+
+ This documentation is part of the \l{componentset}{UIComponents} example.
+
+ \section1 Adding Tabs
+
+ To add a tab, declare the tab as a child of the TabWidget.
+
+ \code
+ TabWidget {
+ id: tabwidget
+
+ Rectangle {
+ id: tab1
+ color: &amp;quot;red&amp;quot;
+ //... omitted
+ }
+ Rectangle {
+ id: tab2
+ color: &amp;quot;blue&amp;quot;
+ //... omitted
+ }
+
+ }
+ \endcode
+
+*/
+Item {
+ id: tabWidget
+
+ /*!
+ \internal
+
+ Setting the default property to stack.children means any child items
+ of the TabWidget are actually added to the 'stack' item's children.
+
+ See the \l{&amp;quot;Property Binding in QML&amp;quot;}
+ documentation for details on default properties.
+
+ This is an implementation detail, not meant for public knowledge. Putting
+ the \internal command at the beginning will cause QDoc to not publish this
+ documentation in the public API page.
+
+ Normally, a property alias needs to have a
+ &amp;quot;\qmlproperty &amp;lt;type&amp;gt; &amp;lt;propertyname&amp;gt;&amp;quot; to assign the alias a type.
+
+ */
+ default property alias content: stack.children
+
+ /*!
+ The currently active tab in the TabWidget.
+ */
+ property int current: 0
+
+ /*!
+ A sample \c{read-only} property.
+ A contrived property to demonstrate QDoc's ability to detect
+ read-only properties.
+
+ The signature is:
+ \code
+ readonly property int sampleReadOnlyProperty: 0
+ \endcode
+
+ Note that the property must be initialized to a value.
+
+ */
+ readonly property int sampleReadOnlyProperty: 0
+
+ /*!
+ \internal
+
+ This handler is an implementation
+ detail. The \c{\internal} command will prevent QDoc from publishing this
+ documentation on the public API.
+ */
+ onCurrentChanged: setOpacities()
+ Component.onCompleted: setOpacities()
+
+ /*!
+ \internal
+
+ An internal function to set the opacity.
+ The \internal command will prevent QDoc from publishing this
+ documentation on the public API.
+ */
+ function setOpacities() {
+ for (var i = 0; i &amp;lt; stack.children.length; ++i) {
+ stack.children[i].opacity = (i == current ? 1 : 0)
+ }
+ }
+
+ Row {
+ id: header
+
+ Repeater {
+ model: stack.children.length
+ delegate: Rectangle {
+ width: tabWidget.width / stack.children.length; height: 36
+
+ Rectangle {
+ width: parent.width; height: 1
+ anchors { bottom: parent.bottom; bottomMargin: 1 }
+ color: &amp;quot;#acb2c2&amp;quot;
+ }
+ BorderImage {
+ anchors { fill: parent; leftMargin: 2; topMargin: 5; rightMargin: 1 }
+ border { left: 7; right: 7 }
+ source: &amp;quot;tab.png&amp;quot;
+ visible: tabWidget.current == index
+ }
+ Text {
+ horizontalAlignment: Qt.AlignHCenter; verticalAlignment: Qt.AlignVCenter
+ anchors.fill: parent
+ text: stack.children[index].title
+ elide: Text.ElideRight
+ font.bold: tabWidget.current == index
+ }
+ MouseArea {
+ anchors.fill: parent
+ onClicked: tabWidget.current = index
+ }
+ }
+ }
+ }
+
+ Item {
+ id: stack
+ width: tabWidget.width
+ anchors.top: header.bottom; anchors.bottom: tabWidget.bottom
+ }
+}
+
+</db:programlisting>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-demo-cpp.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-demo-cpp.xml
new file mode 100644
index 000000000..e10690185
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-demo-cpp.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Demo</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:programlisting language="cpp" its:translate="no">// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+bool isOverThousand(int n)
+{
+ if (n &amp;gt; 1'000)
+ return true;
+ return false;
+}
+
+</db:programlisting>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-demo-pro.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-demo-pro.xml
new file mode 100644
index 000000000..a51cb5a2e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-demo-pro.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Demo</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:programlisting language="cpp" its:translate="no">TEMPLATE = aux
+message(&amp;quot;Nothing to see here.&amp;quot;)
+
+</db:programlisting>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-dontxclude-cmakelists-txt.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-dontxclude-cmakelists-txt.xml
new file mode 100644
index 000000000..4cfd84878
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-dontxclude-cmakelists-txt.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Demo</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:programlisting language="cpp" its:translate="no">cmake_minimum_required(VERSION 3.16)
+project (DONTXCLUDEDIRS_QDOCTEST)
+
+</db:programlisting>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-example.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-example.xml
new file mode 100644
index 000000000..b81f636ba
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-demo-example.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Demo</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:mediaobject>
+<db:imageobject>
+<db:imagedata fileref="images/leonardo-da-vinci.png"/>
+</db:imageobject>
+</db:mediaobject>
+<db:programlisting language="cpp" its:translate="no"> if (n &amp;gt; 1'000)
+ return true;
+</db:programlisting>
+<db:section>
+<db:title>List of Files</db:title>
+<db:para>Files:</db:para>
+<db:section>
+<db:title>List of Files</db:title>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="demos/demo/demo.cpp">demos/demo/demo.cpp</db:link></db:para></db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="demos/demo/demo.pro">demos/demo/demo.pro</db:link></db:para></db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="demos/demo/dontxclude/CMakeLists.txt">demos/demo/dontxclude/CMakeLists.txt</db:link></db:para></db:listitem>
+</db:itemizedlist>
+</db:section>
+</db:section></db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-hidden-example.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-hidden-example.xml
new file mode 100644
index 000000000..b3a1e7996
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-demos-hidden-example.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Hidden Demo</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>Tagged 'broken', does not appear in examples-manifest.xml.</db:para>
+</db:abstract>
+</db:info>
+<db:para>Also missing an image, but that's OK as it's broken anyway.</db:para>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-empty-qmlmodule.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-empty-qmlmodule.xml
new file mode 100644
index 000000000..f58917730
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-empty-qmlmodule.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">No QML Types Here</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A QML module with no member types.</db:para>
+</db:abstract>
+</db:info>
+<db:anchor xml:id="details"/>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-nover-qmlmodule.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-nover-qmlmodule.xml
new file mode 100644
index 000000000..091fe4358
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/test-nover-qmlmodule.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">Versionless QML Module</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>QML Types for the Test module without version.</db:para>
+<db:para>This module is in <db:emphasis>Tech Preview</db:emphasis> state.</db:para>
+<db:para>This module was introduced in Qt 1.1.</db:para>
+</db:abstract>
+</db:info>
+<db:para>This module is in <db:emphasis>Tech Preview</db:emphasis> state.</db:para>
+<db:para>This module was introduced in Qt 1.1.</db:para>
+<db:anchor xml:id="details"/>
+<db:variablelist role="members">
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-test-nover-doctest.xml" xlink:role="">DocTest</db:link></db:term>
+<db:listitem>
+<db:para>Shadows the type name in QDoc.Test module.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-test-nover-typenoversion.xml" xlink:role="">TypeNoVersion</db:link></db:term>
+<db:listitem>
+<db:para>Another QML type documented in a .cpp file.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testcpp-module.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testcpp-module.xml
new file mode 100644
index 000000000..d8e4647ac
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testcpp-module.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">QDoc Test C++ Classes</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test module page.</db:para>
+<db:para>This module was introduced in Qt 2.0.</db:para>
+</db:abstract>
+</db:info>
+<db:para>A test module page.</db:para>
+<db:para>This module was introduced in Qt 2.0.</db:para>
+<db:section xml:id="namespaces">
+<db:title>Namespaces</db:title>
+<db:variablelist role="namespaces">
+<db:varlistentry>
+<db:term><db:link xlink:href="crossmoduleref.xml" xlink:role="namespace">CrossModuleRef</db:link></db:term>
+<db:listitem>
+<db:para>Namespace that has documented functions in multiple modules.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="testqdoc.xml" xlink:role="namespace">TestQDoc</db:link></db:term>
+<db:listitem>
+<db:para>A namespace.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:section>
+<db:section xml:id="classes">
+<db:title>Classes</db:title>
+<db:variablelist role="classes">
+<db:varlistentry>
+<db:term><db:link xlink:href="seenclass.xml" xlink:role="class">SeenClass</db:link></db:term>
+<db:listitem>
+<db:para>A public but undocumented class.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="testqdoc-test.xml" xlink:role="class">TestQDoc::Test</db:link></db:term>
+<db:listitem>
+<db:para>A class in a namespace.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="testqdoc-testderived.xml" xlink:role="class">TestQDoc::TestDerived</db:link></db:term>
+<db:listitem>
+<db:para>A class in a namespace, derived from Test.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:section>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:note>
+<db:para>This is just a test. /* Look, Ma! {I'm made of arguments!} */</db:para>
+</db:note>
+<db:para>This module was introduced in version 5.15.</db:para>
+<db:para>1.0</db:para>
+<db:section xml:id="linking-to-function-like-things">
+<db:title>Linking to function-like things</db:title>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#someFunctionDefaultArg">someFunctionDefaultArg</db:link>()</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#QDOCTEST_MACRO2">QDOCTEST_MACRO2</db:link>()</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#QDOCTEST_MACRO2">QDOCTEST_MACRO2</db:link>(int &amp;x)</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testcpp-module.xml#section">section()</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testcpp-module.xml#section">section() is a section title</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#Test">open( parenthesis</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="https://en.cppreference.com/w/cpp/utility/move">C++11 added std::move(T&amp;&amp; t)</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+<db:section xml:id="section">
+<db:title>section()</db:title>
+</db:section>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testqdoc-test.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testqdoc-test.xml
new file mode 100644
index 000000000..bcb583839
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testqdoc-test.xml
@@ -0,0 +1,376 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">Test Class</db:title>
+<db:subtitle its:translate="no">TestQDoc::Test</db:subtitle>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A class in a namespace.</db:para>
+<db:para>This class was introduced in Qt 1.1.</db:para>
+<db:note>
+<db:para>All functions in this class are <db:link xlink:href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</db:link> with the following exceptions:</db:para>
+<db:para>These functions are not <db:link xlink:href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</db:link>:</db:para>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#someFunctionDefaultArg">someFunctionDefaultArg(int i, bool b) const</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:note>
+</db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Test</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 1.1</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>In QML</db:term>
+<db:listitem>
+<db:para><db:link xlink:href="qml-qdoc-test-type.xml" xlink:role="">Type</db:link></db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Inherited By</db:term>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-testderived.xml" xlink:role="class">TestQDoc::TestDerived</db:link></db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Group</db:term>
+<db:listitem>
+<db:para>Test is part of <db:simplelist><db:member>testgroup</db:member><db:member><db:link xlink:href="cpptypes.xml">Test C++ Types</db:link></db:member></db:simplelist>
+</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="member-type-documentation">
+<db:title>Member Type Documentation</db:title>
+<db:section xml:id="SomeType-typedef">
+<db:title its:translate="no">Test::SomeType</db:title>
+<db:typedefsynopsis>
+<db:typedefname>SomeType</db:typedefname>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:typedefsynopsis>
+<db:para>A typedef.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="member-function-documentation">
+<db:title>Member Function Documentation</db:title>
+<db:section xml:id="overload">
+<db:title its:translate="no">[protected] void Test::overload()</db:title>
+<db:bridgehead renderas="sect2" xml:id="overload-1" its:translate="no">[protected, since Test 1.2] void Test::overload(bool <db:emphasis>b</db:emphasis>)</db:bridgehead>
+<db:para>Overloads that share a documentation comment, optionally taking a parameter <db:code its:translate="no" role="parameter">b</db:code>.</db:para>
+</db:section>
+<db:section xml:id="Test">
+<db:title its:translate="no">Test::Test()</db:title>
+<db:constructorsynopsis>
+<db:methodname>Test</db:methodname>
+<db:void/>
+<db:synopsisinfo role="meta">constructor</db:synopsisinfo>
+<db:synopsisinfo role="signature">Test()</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:constructorsynopsis>
+<db:para>The constructor is deleted.</db:para>
+</db:section>
+<db:section xml:id="funcPtr">
+<db:title its:translate="no">void (*)(bool) Test::funcPtr(bool <db:emphasis>b</db:emphasis>, const char *<db:emphasis>s</db:emphasis>)</db:title>
+<db:methodsynopsis>
+<db:type>void (*)(bool)</db:type>
+<db:methodname>funcPtr</db:methodname>
+<db:methodparam>
+<db:type>bool</db:type>
+<db:parameter>b</db:parameter>
+</db:methodparam>
+<db:methodparam>
+<db:type>const char *</db:type>
+<db:parameter>s</db:parameter>
+</db:methodparam>
+<db:synopsisinfo role="meta">plain</db:synopsisinfo>
+<db:synopsisinfo role="signature">void (*)(bool) funcPtr(bool b, const char *s)</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>Returns a pointer to a function that takes a boolean. Uses <db:code its:translate="no" role="parameter">b</db:code> and <db:code its:translate="no" role="parameter">s</db:code>.</db:para>
+</db:section>
+<db:section xml:id="inlineFunction">
+<db:title its:translate="no">void Test::inlineFunction()</db:title>
+<db:methodsynopsis>
+<db:void/>
+<db:methodname>inlineFunction</db:methodname>
+<db:void/>
+<db:synopsisinfo role="meta">plain</db:synopsisinfo>
+<db:synopsisinfo role="signature">void inlineFunction()</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>An inline function, documented using the \fn QDoc command.</db:para>
+</db:section>
+<db:section xml:id="methodWithEmDashInItsDocs">
+<db:title its:translate="no">void Test::methodWithEmDashInItsDocs()</db:title>
+<db:methodsynopsis>
+<db:void/>
+<db:methodname>methodWithEmDashInItsDocs</db:methodname>
+<db:void/>
+<db:synopsisinfo role="meta">plain</db:synopsisinfo>
+<db:synopsisinfo role="signature">void methodWithEmDashInItsDocs()</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>This method has em dashes in its documentation—as you'll find represented by <db:code>---</db:code> in the sources—here and there. The important bit to note is that when passed e.g. to the \c command, the three hyphens are processed as input to the command and not replaced by an em dash.</db:para>
+<db:para>-----------------------------------------------------------------------</db:para>
+<db:para>People can still add a bunch of dashes, though, without QDoc replacing them all with a series of em dashes.</db:para>
+<db:para>—You can also start a new paragraph with an em dash, if you want to.</db:para>
+<db:section>
+<db:title>See Also</db:title>
+<db:para><db:emphasis>See also </db:emphasis>
+<db:simplelist type="vert" role="see-also">
+<db:member><db:link xlink:href="testqdoc-test.xml#methodWithEnDashInItsDocs">methodWithEnDashInItsDocs</db:link></db:member>
+</db:simplelist>
+</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="methodWithEnDashInItsDocs">
+<db:title its:translate="no">void Test::methodWithEnDashInItsDocs()</db:title>
+<db:methodsynopsis>
+<db:void/>
+<db:methodname>methodWithEnDashInItsDocs</db:methodname>
+<db:void/>
+<db:synopsisinfo role="meta">plain</db:synopsisinfo>
+<db:synopsisinfo role="signature">void methodWithEnDashInItsDocs()</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>This method has en dashes in its documentation – as you'll find represented by <db:code>--</db:code> in the sources – here and there. The important bit to note is that when passed e.g. to the \c command, the two hyphens are processed as input to the command and not replaced by an en dash. This also applies to code blocks, where otherwise, the decrement operator would get completely borked:</db:para>
+<db:programlisting language="cpp" its:translate="no">for (int i = 42; i &amp;gt; 0; --i)
+ // Do something cool during countdown.
+</db:programlisting>
+<db:para>...as it would be silly if this would output –i instead of <db:code>--i</db:code>.</db:para>
+<db:para>-----------------------------------------------------------------------</db:para>
+<db:para>It still allows people to add a bunch of dashes, though, without replacing them all with a series of en dashes. Of course, they might want to use the \hr command instead, like this:</db:para>
+<db:para>– You can also start a new paragraph with an en dash, if you want to.</db:para>
+<db:section>
+<db:title>See Also</db:title>
+<db:para><db:emphasis>See also </db:emphasis>
+<db:simplelist type="vert" role="see-also">
+<db:member><db:link xlink:href="">methodWithEnDashInItsDocs</db:link></db:member>
+</db:simplelist>
+</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="someFunction">
+<db:title its:translate="no">[since Test 1.0] int Test::someFunction(<db:emphasis>int</db:emphasis>, int <db:emphasis>v</db:emphasis> = 0)</db:title>
+<db:methodsynopsis>
+<db:type>int</db:type>
+<db:methodname>someFunction</db:methodname>
+<db:methodparam>
+<db:type>int</db:type>
+<db:parameter></db:parameter>
+</db:methodparam>
+<db:methodparam>
+<db:type>int</db:type>
+<db:parameter>v</db:parameter>
+<db:initializer>0</db:initializer>
+</db:methodparam>
+<db:synopsisinfo role="meta">plain</db:synopsisinfo>
+<db:synopsisinfo role="signature">int someFunction(int, int v)</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>Function that takes a parameter <db:code its:translate="no" role="parameter">v</db:code>. Also returns the value of <db:code its:translate="no" role="parameter">v</db:code>.</db:para>
+<db:para>This function was introduced in Test 1.0.</db:para>
+</db:section>
+<db:section xml:id="someFunctionDefaultArg">
+<db:title its:translate="no">[since 2.0] void Test::someFunctionDefaultArg(int <db:emphasis>i</db:emphasis>, bool <db:emphasis>b</db:emphasis> = false) const</db:title>
+<db:methodsynopsis>
+<db:modifier>const</db:modifier>
+<db:void/>
+<db:methodname>someFunctionDefaultArg</db:methodname>
+<db:methodparam>
+<db:type>int</db:type>
+<db:parameter>i</db:parameter>
+</db:methodparam>
+<db:methodparam>
+<db:type>bool</db:type>
+<db:parameter>b</db:parameter>
+<db:initializer>false</db:initializer>
+</db:methodparam>
+<db:synopsisinfo role="meta">plain</db:synopsisinfo>
+<db:synopsisinfo role="signature">void someFunctionDefaultArg(int i, bool b) const</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">non-reentrant</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>Function that takes a parameter <db:code its:translate="no" role="parameter">i</db:code> and <db:code its:translate="no" role="parameter">b</db:code>.</db:para>
+<db:warning>
+<db:para>This function is not <db:link xlink:href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</db:link>.</db:para>
+</db:warning><db:para>This function was introduced in Qt 2.0.</db:para>
+</db:section>
+<db:section xml:id="virtualFun">
+<db:title its:translate="no">[virtual] void Test::virtualFun()</db:title>
+<db:methodsynopsis>
+<db:modifier>virtual</db:modifier>
+<db:void/>
+<db:methodname>virtualFun</db:methodname>
+<db:void/>
+<db:synopsisinfo role="meta">plain</db:synopsisinfo>
+<db:synopsisinfo role="signature">void virtualFun()</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>Function that must be reimplemented.</db:para>
+</db:section>
+<db:section xml:id="operator-eq">
+<db:title its:translate="no">TestQDoc::Test &amp;Test::operator=(TestQDoc::Test &amp;&amp;<db:emphasis>other</db:emphasis>)</db:title>
+<db:methodsynopsis>
+<db:type>TestQDoc::Test &amp;</db:type>
+<db:methodname>operator=</db:methodname>
+<db:methodparam>
+<db:type>TestQDoc::Test &amp;&amp;</db:type>
+<db:parameter>other</db:parameter>
+</db:methodparam>
+<db:synopsisinfo role="meta">move-assign</db:synopsisinfo>
+<db:synopsisinfo role="signature">TestQDoc::Test &amp; operator=(TestQDoc::Test &amp;&amp;other)</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>The move assignment operator is deleted. <db:code its:translate="no" role="parameter">other</db:code> cannot be moved from.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="related-non-members">
+<db:title>Related Non-Members</db:title>
+<db:section xml:id="operator-eq-eq">
+<db:title its:translate="no">bool operator==(const TestQDoc::Test &amp;<db:emphasis>lhs</db:emphasis>, const TestQDoc::Test &amp;<db:emphasis>rhs</db:emphasis>)</db:title>
+<db:methodsynopsis>
+<db:type>bool</db:type>
+<db:methodname>operator==</db:methodname>
+<db:methodparam>
+<db:type>const TestQDoc::Test &amp;</db:type>
+<db:parameter>lhs</db:parameter>
+</db:methodparam>
+<db:methodparam>
+<db:type>const TestQDoc::Test &amp;</db:type>
+<db:parameter>rhs</db:parameter>
+</db:methodparam>
+<db:synopsisinfo role="meta">plain</db:synopsisinfo>
+<db:synopsisinfo role="signature">bool operator==(const TestQDoc::Test &amp;lhs, const TestQDoc::Test &amp;rhs)</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>Returns true if <db:code its:translate="no" role="parameter">lhs</db:code> and <db:code its:translate="no" role="parameter">rhs</db:code> are equal.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="macro-documentation">
+<db:title>Macro Documentation</db:title>
+<db:section xml:id="QDOCTEST_MACRO2">
+<db:title its:translate="no">[since Test 1.1] QDOCTEST_MACRO2(int &amp;<db:emphasis>x</db:emphasis>)</db:title>
+<db:methodsynopsis>
+<db:methodname>QDOCTEST_MACRO2</db:methodname>
+<db:methodparam>
+<db:type>int &amp;</db:type>
+<db:parameter>x</db:parameter>
+</db:methodparam>
+<db:synopsisinfo role="meta">macrowithparams</db:synopsisinfo>
+<db:synopsisinfo role="signature">QDOCTEST_MACRO2(int &amp;x)</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>A macro with argument <db:code its:translate="no" role="parameter">x</db:code>.</db:para>
+<db:para>This macro was introduced in Test 1.1.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="obsolete">
+<db:title>Obsolete Members for Test</db:title>
+<db:para><db:emphasis role="bold">The following members of class <db:link xlink:href="testqdoc-test.xml">Test</db:link> are deprecated.</db:emphasis> We strongly advise against using them in new code.</db:para>
+<db:section xml:id="obsolete-member-function-documentation">
+<db:title>Obsolete Member Function Documentation</db:title>
+<db:section xml:id="operator-2b-2b">
+<db:title its:translate="no">[deprecated] TestQDoc::Test &amp;Test::operator++()</db:title>
+<db:bridgehead renderas="sect2" xml:id="operator--" its:translate="no">[deprecated] TestQDoc::Test &amp;Test::operator--()</db:bridgehead>
+<db:para>This function is deprecated. We strongly advise against using it in new code.</db:para>
+</db:section>
+<db:section xml:id="anotherObsoleteMember">
+<db:title its:translate="no">[deprecated] void Test::anotherObsoleteMember()</db:title>
+<db:methodsynopsis>
+<db:void/>
+<db:methodname>anotherObsoleteMember</db:methodname>
+<db:void/>
+<db:synopsisinfo role="meta">plain</db:synopsisinfo>
+<db:synopsisinfo role="signature">void anotherObsoleteMember()</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">deprecated</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>This function is deprecated. We strongly advise against using it in new code.</db:para>
+<db:para>Use <db:link xlink:href="testqdoc-test.xml#obsoleteMember">obsoleteMember</db:link>() instead.</db:para>
+</db:section>
+<db:section xml:id="deprecatedMember">
+<db:title its:translate="no">[deprecated in 6.0] void Test::deprecatedMember()</db:title>
+<db:methodsynopsis>
+<db:void/>
+<db:methodname>deprecatedMember</db:methodname>
+<db:void/>
+<db:synopsisinfo role="meta">plain</db:synopsisinfo>
+<db:synopsisinfo role="signature">void deprecatedMember()</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">deprecated</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>This function is deprecated since 6.0. We strongly advise against using it in new code.</db:para>
+<db:para>Use <db:link xlink:href="testqdoc-test.xml#someFunction">someFunction</db:link>() instead.</db:para>
+</db:section>
+<db:section xml:id="obsoleteMember">
+<db:title its:translate="no">[deprecated] void Test::obsoleteMember()</db:title>
+<db:methodsynopsis>
+<db:void/>
+<db:methodname>obsoleteMember</db:methodname>
+<db:void/>
+<db:synopsisinfo role="meta">plain</db:synopsisinfo>
+<db:synopsisinfo role="signature">void obsoleteMember()</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">deprecated</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>This function is deprecated. We strongly advise against using it in new code.</db:para>
+<db:para>Use <db:link xlink:href="testqdoc-test.xml#someFunction">someFunction</db:link>() instead.</db:para>
+</db:section>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testqdoc-testderived.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testqdoc-testderived.xml
new file mode 100644
index 000000000..c6272cc7d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testqdoc-testderived.xml
@@ -0,0 +1,308 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">TestDerived Class</db:title>
+<db:subtitle its:translate="no">TestQDoc::TestDerived</db:subtitle>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A class in a namespace, derived from <db:link xlink:href="testqdoc-test.xml">Test</db:link>.</db:para>
+<db:para>This class was introduced in Qt 2.0.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>TestDerived</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 2.0</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>In QML</db:term>
+<db:listitem>
+<db:para><db:link xlink:href="qml-themodule-thetype.xml" xlink:role="">TheType</db:link></db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Inherits</db:term>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml" xlink:role="class">TestQDoc::Test</db:link></db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="member-type-documentation">
+<db:title>Member Type Documentation</db:title>
+<db:section xml:id="DerivedType-typedef">
+<db:title its:translate="no">[alias] TestDerived::DerivedType</db:title>
+<db:typedefsynopsis>
+<db:typedefname>DerivedType</db:typedefname>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:typedefsynopsis>
+<db:para>An aliased typedef.</db:para>
+</db:section>
+<db:section xml:id="NotTypedef-typedef">
+<db:title its:translate="no">[alias] TestDerived::NotTypedef</db:title>
+<db:typedefsynopsis>
+<db:typedefname>NotTypedef</db:typedefname>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:typedefsynopsis>
+<db:para>I'm an alias, not a typedef.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="property-documentation">
+<db:title>Property Documentation</db:title>
+<db:section xml:id="bindableProp-prop">
+<db:title its:translate="no">[bindable] bindableProp : QString</db:title>
+<db:fieldsynopsis>
+<db:modifier>(Qt property)</db:modifier>
+<db:type>QString</db:type>
+<db:varname>bindableProp</db:varname>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+<db:synopsisinfo role="getter">bindableProp</db:synopsisinfo>
+<db:synopsisinfo role="setter">setBindableProp</db:synopsisinfo>
+<db:synopsisinfo role="notifier">bindablePropChanged</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>This property supports <db:link xlink:href="https://wiki.qt.io/QProperty">QProperty</db:link> bindings.</db:para>
+<db:para>Some property.</db:para>
+<db:section>
+<db:title>See Also</db:title>
+<db:para><db:emphasis>See also </db:emphasis>
+<db:simplelist type="vert" role="see-also">
+<db:member><db:link xlink:href="testqdoc-testderived.xml#someProp-prop">someProp</db:link></db:member>
+</db:simplelist>
+</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="boolProp-prop">
+<db:title its:translate="no">boolProp : bool</db:title>
+<db:fieldsynopsis>
+<db:modifier>(Qt property)</db:modifier>
+<db:type>bool</db:type>
+<db:varname>boolProp</db:varname>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+<db:synopsisinfo role="getter">boolProp</db:synopsisinfo>
+<db:synopsisinfo role="setter">setBoolProp</db:synopsisinfo>
+<db:synopsisinfo role="resetter">resetBoolProp</db:synopsisinfo>
+<db:synopsisinfo role="notifier">boolPropChanged</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>A boolean property.</db:para>
+<db:para>
+<db:emphasis role="bold">Access functions:
+</db:emphasis>
+</db:para>
+<db:itemizedlist its:translate="no">
+<db:listitem>
+<db:para><db:type>bool</db:type> <db:emphasis role="bold"><db:link xlink:href="">boolProp</db:link></db:emphasis>()</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:type>void</db:type> <db:emphasis role="bold"><db:link xlink:href="">setBoolProp</db:link></db:emphasis>(<db:type>bool</db:type> <db:emphasis>b</db:emphasis>)</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:type>void</db:type> <db:emphasis role="bold"><db:link xlink:href="">resetBoolProp</db:link></db:emphasis>()</db:para>
+</db:listitem>
+</db:itemizedlist>
+<db:para>
+<db:emphasis role="bold">Notifier signal:
+</db:emphasis>
+</db:para>
+<db:itemizedlist its:translate="no">
+<db:listitem>
+<db:para><db:type>void</db:type> <db:emphasis role="bold"><db:link xlink:href="">boolPropChanged</db:link></db:emphasis>()</db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+<db:section xml:id="intProp-prop">
+<db:title its:translate="no">[read-only] intProp : int* const</db:title>
+<db:fieldsynopsis>
+<db:modifier>(Qt property)</db:modifier>
+<db:type>int*</db:type>
+<db:varname>intProp</db:varname>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+<db:synopsisinfo role="getter">getInt</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>An integer property.</db:para>
+<db:para>
+<db:emphasis role="bold">Access functions:
+</db:emphasis>
+</db:para>
+<db:itemizedlist its:translate="no">
+<db:listitem>
+<db:para><db:type>int</db:type> *<db:emphasis role="bold"><db:link xlink:href="">getInt</db:link></db:emphasis>()</db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+<db:section xml:id="name-prop">
+<db:title its:translate="no">[read-only] name : const QString*</db:title>
+<db:fieldsynopsis>
+<db:modifier>(Qt property)</db:modifier>
+<db:type>const QString*</db:type>
+<db:varname>name</db:varname>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+<db:synopsisinfo role="getter">name</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>This property holds a name.</db:para>
+<db:para>
+<db:emphasis role="bold">Access functions:
+</db:emphasis>
+</db:para>
+<db:itemizedlist its:translate="no">
+<db:listitem>
+<db:para>const <db:type>QString</db:type> *<db:emphasis role="bold"><db:link xlink:href="">name</db:link></db:emphasis>() const</db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+<db:section xml:id="someProp-prop">
+<db:title its:translate="no">[bindable read-only] someProp : QString</db:title>
+<db:fieldsynopsis>
+<db:modifier>(Qt property)</db:modifier>
+<db:type>QString</db:type>
+<db:varname>someProp</db:varname>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+<db:synopsisinfo role="getter">someProp</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>This property supports <db:link xlink:href="https://wiki.qt.io/QProperty">QProperty</db:link> bindings.</db:para>
+<db:para>Another property.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="member-function-documentation">
+<db:title>Member Function Documentation</db:title>
+<db:section xml:id="emitSomething">
+<db:title its:translate="no">[private signal] void TestDerived::emitSomething()</db:title>
+<db:methodsynopsis>
+<db:void/>
+<db:methodname>emitSomething</db:methodname>
+<db:void/>
+<db:synopsisinfo role="meta">signal</db:synopsisinfo>
+<db:synopsisinfo role="signature">void emitSomething()</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>Emitted when things happen.</db:para>
+<db:note>
+<db:para>This is a private signal. It can be used in signal connections but cannot be emitted by the user.</db:para></db:note>
+</db:section>
+<db:section xml:id="id">
+<db:title its:translate="no">[override virtual] int TestDerived::id()</db:title>
+<db:methodsynopsis>
+<db:modifier>virtual</db:modifier>
+<db:type>int</db:type>
+<db:methodname>id</db:methodname>
+<db:void/>
+<db:modifier>override</db:modifier>
+<db:synopsisinfo role="meta">plain</db:synopsisinfo>
+<db:synopsisinfo role="signature">int id() override</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+</db:section>
+<db:section xml:id="invokeMe">
+<db:title its:translate="no">[invokable] void TestDerived::invokeMe() const</db:title>
+<db:methodsynopsis>
+<db:modifier>const</db:modifier>
+<db:void/>
+<db:methodname>invokeMe</db:methodname>
+<db:void/>
+<db:synopsisinfo role="meta">plain</db:synopsisinfo>
+<db:synopsisinfo role="signature">void invokeMe() const</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>Something invokable.</db:para>
+<db:note>
+<db:para>This function can be invoked via the meta-object system and from QML. See <db:link xlink:href="">Q_INVOKABLE</db:link>.</db:para>
+</db:note>
+</db:section>
+<db:section xml:id="someValue">
+<db:title its:translate="no">TestQDoc::TestDerived::NotTypedef TestDerived::someValue()</db:title>
+<db:methodsynopsis>
+<db:type>TestQDoc::TestDerived::NotTypedef</db:type>
+<db:methodname>someValue</db:methodname>
+<db:void/>
+<db:synopsisinfo role="meta">plain</db:synopsisinfo>
+<db:synopsisinfo role="signature">TestQDoc::TestDerived::NotTypedef someValue()</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>Returns a value using an aliases type.</db:para>
+</db:section>
+<db:section xml:id="virtualFun">
+<db:title its:translate="no">[override virtual] void TestDerived::virtualFun()</db:title>
+<db:methodsynopsis>
+<db:modifier>virtual</db:modifier>
+<db:void/>
+<db:methodname>virtualFun</db:methodname>
+<db:void/>
+<db:modifier>override</db:modifier>
+<db:synopsisinfo role="meta">plain</db:synopsisinfo>
+<db:synopsisinfo role="signature">void virtualFun() override</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>Reimplements: <db:link xlink:href="testqdoc-test.xml#virtualFun" role="function">Test::virtualFun()</db:link>.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="obsolete">
+<db:title>Obsolete Members for TestDerived</db:title>
+<db:para><db:emphasis role="bold">The following members of class <db:link xlink:href="testqdoc-testderived.xml">TestDerived</db:link> are deprecated.</db:emphasis> We strongly advise against using them in new code.</db:para>
+<db:section xml:id="obsolete-member-function-documentation">
+<db:title>Obsolete Member Function Documentation</db:title>
+<db:section xml:id="staticObsoleteMember">
+<db:title its:translate="no">[static, deprecated] void TestDerived::staticObsoleteMember()</db:title>
+<db:methodsynopsis>
+<db:modifier>static</db:modifier>
+<db:void/>
+<db:methodname>staticObsoleteMember</db:methodname>
+<db:void/>
+<db:synopsisinfo role="meta">plain</db:synopsisinfo>
+<db:synopsisinfo role="signature">void staticObsoleteMember()</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">deprecated</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>This function is deprecated. We strongly advise against using it in new code.</db:para>
+<db:para>Static obsolete method.</db:para>
+</db:section>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testqdoc.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testqdoc.xml
new file mode 100644
index 000000000..2322302a0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/testqdoc.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">TestQDoc Namespace</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A namespace.</db:para>
+<db:para>This namespace was introduced in Qt 2.0.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>TestCPP</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 2.0</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:section xml:id="usage">
+<db:title>Usage</db:title>
+<db:para>This namespace is for testing QDoc output.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="classes">
+<db:title>Classes</db:title>
+<db:section>
+<db:title>class <db:link xlink:href="testqdoc-test.xml" xlink:role="class">Test</db:link></db:title>
+<db:para>A class in a namespace.</db:para>
+</db:section>
+<db:section>
+<db:title>class <db:link xlink:href="testqdoc-testderived.xml" xlink:role="class">TestDerived</db:link></db:title>
+<db:para>A class in a namespace, derived from <db:link xlink:href="testqdoc-test.xml">Test</db:link>.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="macro-documentation">
+<db:title>Macro Documentation</db:title>
+<db:section xml:id="QDOCTEST_MACRO">
+<db:title its:translate="no">[since Test 0.9] QDOCTEST_MACRO</db:title>
+<db:methodsynopsis>
+<db:methodname>QDOCTEST_MACRO</db:methodname>
+<db:void/>
+<db:synopsisinfo role="meta">macrowithoutparams</db:synopsisinfo>
+<db:synopsisinfo role="signature">QDOCTEST_MACRO</db:synopsisinfo>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:methodsynopsis>
+<db:para>This macro was introduced in Test 0.9.</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/themodule-qmlmodule.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/themodule-qmlmodule.xml
new file mode 100644
index 000000000..d3c4ed894
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/themodule-qmlmodule.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no"></db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:anchor xml:id="details"/>
+<db:itemizedlist role="members">
+<db:listitem>
+<db:para><db:link xlink:href="qml-themodule-thetype.xml" xlink:role="">TheType</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/uicomponents-qmlmodule.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/uicomponents-qmlmodule.xml
new file mode 100644
index 000000000..80e5b0cdc
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/docbook/uicomponents-qmlmodule.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">UI Components</db:title>
+<db:productname>Test</db:productname>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>Basic set of UI components.</db:para>
+</db:abstract>
+</db:info>
+<db:anchor xml:id="details"/>
+<db:para>This is a listing of a list of UI components implemented by QML types. These files are available for general import and they are based on the Qt Quick Code Samples.</db:para>
+<db:para>This module is part of the <db:link xlink:href="test-componentset-example.xml">UIComponents</db:link> example.</db:para>
+<db:variablelist role="members">
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-uicomponents-progressbar.xml" xlink:role="">ProgressBar</db:link></db:term>
+<db:listitem>
+<db:para>A component that shows the progress of an event.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-uicomponents-switch.xml" xlink:role="">Switch</db:link></db:term>
+<db:listitem>
+<db:para>A component that can be turned on or off.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-uicomponents-tabwidget.xml" xlink:role="">TabWidget</db:link></db:term>
+<db:listitem>
+<db:para>A widget that places its children as tabs.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/autolinking.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/autolinking.html
new file mode 100644
index 000000000..ead0896fe
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/autolinking.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classlists.qdoc -->
+ <title>Autolinking | Test 6.2.11</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#testqdoc">TestQDoc</a></li>
+<li class="level1"><a href="#someprop">someProp</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Autolinking</h1>
+<!-- $$$autolinking.html-description -->
+<div class="descr" id="details">
+<h2 id="testqdoc">TestQDoc</h2>
+<p>The string <a href="testqdoc.html" translate="no">TestQDoc</a> links to the C++ namespace unless linking explicitly, <a href="autolinking.html#testqdoc">like this</a>, or <a href="testqdoc.html" translate="no">this</a>. Also,</p>
+<p>Autolinks:</p>
+<ul>
+<li><a href="testqdoc-testderived.html" translate="no">TestQDoc::TestDerived</a></li>
+</ul>
+<p>Explicit links:</p>
+<ul>
+<li><a href="testqdoc-testderived.html" translate="no">TestQDoc::TestDerived</a></li>
+<li><a href="obsolete-classes.html#testqdoc">Obsolete Classes#TestQDoc</a></li>
+</ul>
+<h2 id="someprop">someProp</h2>
+</div>
+<!-- @@@autolinking.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/classes.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/classes.html
new file mode 100644
index 000000000..6a8df3334
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/classes.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- unseenclass.qdoc -->
+ <title>Classes | Test 6.2.11</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Classes</h1>
+<!-- $$$classes.html-description -->
+<div class="descr" id="details">
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="seenclass.html">SeenClass</a></p></td><td class="tblDescr"><p>A public but undocumented class</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html">TestQDoc::Test</a></p></td><td class="tblDescr"><p>A class in a namespace</p></td></tr>
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-testderived.html">TestQDoc::TestDerived</a></p></td><td class="tblDescr"><p>A class in a namespace, derived from Test</p></td></tr>
+</table></div>
+</div>
+<!-- @@@classes.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/cpptypes.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/cpptypes.html
new file mode 100644
index 000000000..3bd7789ff
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/cpptypes.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classlists.qdoc -->
+ <title>Test C++ Types | Test 6.2.11</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Test C++ Types</h1>
+<!-- $$$cpptypes-description -->
+<div class="descr" id="details">
+<ul>
+<li translate="no"><a href="testqdoc-test.html">TestQDoc::Test</a></li>
+<li translate="no"><a href="testqdoc-test.html#QDOCTEST_MACRO2">TestQDoc::Test::QDOCTEST_MACRO2</a></li>
+<li translate="no"><a href="testqdoc-test.html#operator-eq">TestQDoc::Test::operator=()</a></li>
+<li translate="no"><a href="testqdoc-test.html#someFunctionDefaultArg">TestQDoc::Test::someFunctionDefaultArg()</a></li>
+</ul>
+</div>
+<!-- @@@cpptypes -->
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html">TestQDoc::Test</a></p></td><td class="tblDescr"><p>A class in a namespace</p></td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/crossmoduleref.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/crossmoduleref.html
new file mode 100644
index 000000000..f681dbd69
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/crossmoduleref.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="Namespace that has documented functions in multiple modules.">
+ <title>CrossModuleRef Namespace | Test 6.2.11</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#functions">Functions</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">CrossModuleRef Namespace</h1>
+<!-- $$$CrossModuleRef-brief -->
+<p>Namespace that has documented functions in multiple modules. <a href="#details">More...</a></p>
+<!-- @@@CrossModuleRef -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;CrossModuleRef&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 3.0</td></tr>
+</table></div>
+<h2 id="functions">Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="crossmoduleref.html#documentMe" translate="no">documentMe</a></b>()</td></tr>
+</table></div>
+<!-- $$$CrossModuleRef-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@CrossModuleRef -->
+<div class="func">
+<h2>Function Documentation</h2>
+<!-- $$$documentMe[overload1]$$$documentMe -->
+<h3 class="fn" translate="no" id="documentMe"><span class="type">void</span> CrossModuleRef::<span class="name">documentMe</span>()</h3>
+<p>Document me!</p>
+<!-- @@@documentMe -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/images/leonardo-da-vinci.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/images/leonardo-da-vinci.png
new file mode 100644
index 000000000..854acb4ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/images/leonardo-da-vinci.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/obsolete-classes.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/obsolete-classes.html
new file mode 100644
index 000000000..575b6e45c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/obsolete-classes.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classlists.qdoc -->
+ <title>Obsolete Classes | Test 6.2.11</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#classes-with-obsolete-members">Classes with obsolete members</a></li>
+<li class="level2"><a href="#testqdoc">TestQDoc</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Obsolete Classes</h1>
+<!-- $$$obsolete-classes.html-description -->
+<div class="descr" id="details">
+<h2 id="classes-with-obsolete-members">Classes with obsolete members</h2>
+<div class="flowListDiv" translate="no">
+<dl class="flowList odd"><dt class="alphaChar"><b>T</b></dt>
+<dd><a href="testqdoc-test-obsolete.html">Test</a> (<a href="testqdoc.html">TestQDoc</a>)</dd>
+<dd><a href="testqdoc-testderived-obsolete.html">TestDerived</a> (<a href="testqdoc.html">TestQDoc</a>)</dd>
+</dl>
+</div>
+<h3 id="testqdoc">TestQDoc</h3>
+</div>
+<!-- @@@obsolete-classes.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qdoc-test-qmlmodule.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qdoc-test-qmlmodule.html
new file mode 100644
index 000000000..6be64618b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qdoc-test-qmlmodule.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- type.cpp -->
+ <meta name="description" content="QML Types for the Test module.">
+ <title>QDoc.Test QML Module | Test 6.2.11</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">QDoc.Test QML Module</h1>
+<p><b>This module is under development and is subject to change.</b></p>
+<p>This module was introduced in Qt 1.1.</p>
+<!-- $$$QDoc.Test-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@QDoc.Test -->
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qml-qdoc-test-abstractparent.html">AbstractParent</a></p></td><td class="tblDescr"><p>Abstract base QML type</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="qml-qdoc-test-child.html">Child</a></p></td><td class="tblDescr"><p>A Child inheriting its parent</p></td></tr>
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qml-qdoc-test-doctest.html">DocTest</a></p></td><td class="tblDescr"><p>Represents a doc test case</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="qml-qdoc-test-type.html">Type</a></p></td><td class="tblDescr"><p>A QML type documented in a .cpp file</p></td></tr>
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qml-qdoc-test-yetanotherchild.html">YetAnotherChild</a></p></td><td class="tblDescr"><p>A type inheriting from internal abstract parent</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="qml-int.html">int</a></p></td><td class="tblDescr"><p>An integer value type</p></td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-int.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-int.html
new file mode 100644
index 000000000..63ea49e7d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-int.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- parent.qdoc -->
+ <meta name="description" content="An integer value type.">
+ <title>int QML Value Type | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>int</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#methods">Methods</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">int QML Value Type</h1>
+<!-- $$$int-brief -->
+<p>An integer value type. <a href="#details">More...</a></p>
+<!-- @@@int -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import QDoc.Test 1.1</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 1.1</td></tr></table></div><h2 id="methods">Methods</h2>
+<ul>
+<li class="fn" translate="no">int <b><a href="qml-int.html#abs-method" translate="no">abs</a></b>()</li>
+</ul>
+<!-- $$$int-description -->
+<h2 id="details">Detailed Description</h2>
+<!-- @@@int -->
+<h2>Method Documentation</h2>
+<!-- $$$abs[overload1]$$$abs -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="abs-method">
+<td class="tblQmlFuncNode"><p>
+<span class="type"><a href="qml-int.html" translate="no">int</a></span> <span class="name">abs</span>()</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Returns the absolute value of this integer.</p>
+</div></div><!-- @@@abs -->
+<br/>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-abstractparent-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-abstractparent-members.html
new file mode 100644
index 000000000..af49329a3
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-abstractparent-members.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- parent.qdoc -->
+ <meta name="description" content="Abstract base QML type.">
+ <title>List of All Members for AbstractParent | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>AbstractParent</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for AbstractParent</h1>
+<p>This is the complete list of members for <a href="qml-qdoc-test-abstractparent.html">AbstractParent</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-abstractparent.html#children-prop" translate="no">children</a></b> : list&lt;Child&gt; [default]</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-abstractparent.html#name-prop" translate="no">name</a></b> : string</li>
+<li class="fn" translate="no">void <b><a href="qml-qdoc-test-abstractparent.html#name-method" translate="no">name</a></b>()</li>
+<li class="fn" translate="no">void <b><a href="qml-qdoc-test-abstractparent.html#name-method-1" translate="no">name</a></b>(Child <i>child</i>, <i>name</i>)</li>
+<li class="fn" translate="no">void <b><a href="qml-qdoc-test-abstractparent.html#rear-method" translate="no">rear</a></b>(Child <i>child</i>, var <i>method</i>)</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-abstractparent.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-abstractparent.html
new file mode 100644
index 000000000..8fe88bee7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-abstractparent.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- parent.qdoc -->
+ <meta name="description" content="Abstract base QML type.">
+ <title>AbstractParent QML Type | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>AbstractParent</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#properties">Properties</a></li>
+<li class="level1"><a href="#methods">Methods</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">AbstractParent QML Type</h1>
+<!-- $$$AbstractParent-brief -->
+<p>Abstract base QML type. <a href="#details">More...</a></p>
+<!-- @@@AbstractParent -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import QDoc.Test 1.1</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 1.1</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Inherited By:</td><td class="memItemRight bottomAlign"> <p><a href="qml-qdoc-test-child.html" translate="no">Child</a></p>
+</td></tr></table></div><ul>
+<li><a href="qml-qdoc-test-abstractparent-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="properties">Properties</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-abstractparent.html#children-prop" translate="no">children</a></b> : list&lt;Child&gt;</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-abstractparent.html#name-prop" translate="no">name</a></b> : string</li>
+</ul>
+<h2 id="methods">Methods</h2>
+<ul>
+<li class="fn" translate="no">void <b><a href="qml-qdoc-test-abstractparent.html#name-method" translate="no">name</a></b>()</li>
+<li class="fn" translate="no">void <b><a href="qml-qdoc-test-abstractparent.html#name-method-1" translate="no">name</a></b>(Child <i>child</i>, <i>name</i>)</li>
+<li class="fn" translate="no">void <b><a href="qml-qdoc-test-abstractparent.html#rear-method" translate="no">rear</a></b>(Child <i>child</i>, var <i>method</i>)</li>
+</ul>
+<!-- $$$AbstractParent-description -->
+<h2 id="details">Detailed Description</h2>
+<!-- @@@AbstractParent -->
+<h2>Property Documentation</h2>
+<!-- $$$children -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="children-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">children</span> : <span class="type">list</span>&lt;<span class="type"><a href="qml-qdoc-test-child.html" translate="no">Child</a></span>&gt; <code class="details extra" translate="no">[default]</code></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Children of the type.</p>
+</div></div><!-- @@@children -->
+<br/>
+<!-- $$$name -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="name-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">name</span> : <span class="type">string</span></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Name of this parent.</p>
+</div></div><!-- @@@name -->
+<br/>
+<h2>Method Documentation</h2>
+<!-- $$$name[overload1]$$$name -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="name-method">
+<td class="tblQmlFuncNode"><p>
+<span class="type">void</span> <span class="name">name</span>()</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Name all children with random names.</p>
+</div></div><!-- @@@name -->
+<br/>
+<!-- $$$name$$$nameChild -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="name-method-1">
+<td class="tblQmlFuncNode"><p>
+<span class="type">void</span> <span class="name">name</span>(<span class="type"><a href="qml-qdoc-test-child.html" translate="no">Child</a></span> <i>child</i>, <i>name</i>)</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Name a <i translate="no">child</i> using <i translate="no">name</i>.</p>
+</div></div><!-- @@@name -->
+<br/>
+<!-- $$$rear[overload1]$$$rearChildvar -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="rear-method">
+<td class="tblQmlFuncNode"><p>
+<span class="type">void</span> <span class="name">rear</span>(<span class="type"><a href="qml-qdoc-test-child.html" translate="no">Child</a></span> <i>child</i>, <span class="type">var</span> <i>method</i> = Strict)</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Do some abstract parenting on <i translate="no">child</i> using a specific <i translate="no">method</i>.</p>
+</div></div><!-- @@@rear -->
+<br/>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-child-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-child-members.html
new file mode 100644
index 000000000..c9a9b0a5c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-child-members.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- parent.qdoc -->
+ <meta name="description" content="A Child inheriting its parent.">
+ <title>List of All Members for Child | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>Child</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for Child</h1>
+<p>This is the complete list of members for <a href="qml-qdoc-test-child.html">Child</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-child.html#children-prop" translate="no">children</a></b> : list&lt;Child&gt; [default]</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-child.html#name-prop" translate="no">name</a></b> : string</li>
+<li class="fn" translate="no">void <b><a href="qml-qdoc-test-child.html#name-method" translate="no">name</a></b>(Child <i>child</i>, <i>name</i>)</li>
+<li class="fn" translate="no">void <b><a href="qml-qdoc-test-child.html#name-method" translate="no">name</a></b>()</li>
+<li class="fn" translate="no">void <b><a href="qml-qdoc-test-child.html#rear-method" translate="no">rear</a></b>(Child <i>child</i>, var <i>method</i>)</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-child.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-child.html
new file mode 100644
index 000000000..578c2f829
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-child.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- parent.qdoc -->
+ <meta name="description" content="A Child inheriting its parent.">
+ <title>Child QML Type | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>Child</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#properties">Properties</a></li>
+<li class="level1"><a href="#methods">Methods</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Child QML Type</h1>
+<!-- $$$Child-brief -->
+<p>A Child inheriting its parent. <a href="#details">More...</a></p>
+<!-- @@@Child -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import QDoc.Test 1.1</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 1.1</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Inherits:</td><td class="memItemRight bottomAlign"> <p><a href="qml-qdoc-test-abstractparent.html" translate="no">AbstractParent</a></p>
+</td></tr></table></div><ul>
+<li><a href="qml-qdoc-test-child-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="properties">Properties</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-child.html#children-prop" translate="no">children</a></b> : list&lt;Child&gt;</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-child.html#name-prop" translate="no">name</a></b> : string</li>
+</ul>
+<h2 id="methods">Methods</h2>
+<ul>
+<li class="fn" translate="no">void <b><a href="qml-qdoc-test-child.html#name-method" translate="no">name</a></b>(Child <i>child</i>, <i>name</i>)</li>
+<li class="fn" translate="no">void <b><a href="qml-qdoc-test-child.html#name-method" translate="no">name</a></b>()</li>
+<li class="fn" translate="no">void <b><a href="qml-qdoc-test-child.html#rear-method" translate="no">rear</a></b>(Child <i>child</i>, var <i>method</i>)</li>
+</ul>
+<!-- $$$Child-description -->
+<h2 id="details">Detailed Description</h2>
+<!-- @@@Child -->
+<h2>Property Documentation</h2>
+<!-- $$$children -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="children-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">children</span> : <span class="type">list</span>&lt;<span class="type"><a href="qml-qdoc-test-child.html" translate="no">Child</a></span>&gt; <code class="details extra" translate="no">[default]</code></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Children of the type.</p>
+</div></div><!-- @@@children -->
+<br/>
+<!-- $$$name -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="name-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">name</span> : <span class="type">string</span></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Name of this child.</p>
+</div></div><!-- @@@name -->
+<br/>
+<h2>Method Documentation</h2>
+<!-- $$$name[overload1]$$$nameChild -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="name-method">
+<td class="tblQmlFuncNode"><p>
+<span class="type">void</span> <span class="name">name</span>(<span class="type"><a href="qml-qdoc-test-child.html" translate="no">Child</a></span> <i>child</i>, <i>name</i>)</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Name a <i translate="no">child</i> of this child using <i translate="no">name</i>.</p>
+</div></div><!-- @@@name -->
+<br/>
+<!-- $$$name[overload1]$$$name -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="name-method">
+<td class="tblQmlFuncNode"><p>
+<span class="type">void</span> <span class="name">name</span>()</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Name all children with random names.</p>
+</div></div><!-- @@@name -->
+<br/>
+<!-- $$$rear[overload1]$$$rearChildvar -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="rear-method">
+<td class="tblQmlFuncNode"><p>
+<span class="type">void</span> <span class="name">rear</span>(<span class="type"><a href="qml-qdoc-test-child.html" translate="no">Child</a></span> <i>child</i>, <span class="type">var</span> <i>method</i> = Strict)</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Do some abstract parenting on <i translate="no">child</i> using a specific <i translate="no">method</i>.</p>
+</div></div><!-- @@@rear -->
+<br/>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-doctest-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-doctest-members.html
new file mode 100644
index 000000000..706b65b20
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-doctest-members.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- DocTest.qml -->
+ <meta name="description" content="Represents a doc test case.">
+ <title>List of All Members for DocTest | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>DocTest</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for DocTest</h1>
+<p>This is the complete list of members for <a href="qml-qdoc-test-doctest.html">DocTest</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-doctest.html#active-prop" translate="no">active</a></b> : bool</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-doctest.html#name-prop" translate="no">name</a></b> : string</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-doctest.html#completed-signal" translate="no">completed</a></b>()</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-doctest.html#fail-method" translate="no">fail</a></b>(<i>message</i>) <code class="summary extra" translate="no">(since QDoc.Test 1.0)</code></li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-doctest.html#fail_hard-method" translate="no">fail_hard</a></b>(<i>msg</i>, <i>option</i>)</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-doctest.html#foo-signal" translate="no">foo</a></b>(var <i>bar</i>)</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-doctest.html#itsHappening-signal" translate="no">itsHappening</a></b>(bool <i>really</i>)</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-doctest.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-doctest.html
new file mode 100644
index 000000000..37174fb59
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-doctest.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- DocTest.qml -->
+ <meta name="description" content="Represents a doc test case.">
+ <title>DocTest QML Type | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>DocTest</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#properties">Properties</a></li>
+<li class="level1"><a href="#signals">Signals</a></li>
+<li class="level1"><a href="#methods">Methods</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#introduction">Introduction</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">DocTest QML Type</h1>
+<!-- $$$DocTest-brief -->
+<p>Represents a doc test case. <a href="#details">More...</a></p>
+<!-- @@@DocTest -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import QDoc.Test 1.1</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> QDoc.Test 0.9</td></tr></table></div><ul>
+<li><a href="qml-qdoc-test-doctest-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="properties">Properties</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-doctest.html#active-prop" translate="no">active</a></b> : bool</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-doctest.html#name-prop" translate="no">name</a></b> : string</li>
+</ul>
+<h2 id="signals">Signals</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-doctest.html#completed-signal" translate="no">completed</a></b>()</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-doctest.html#foo-signal" translate="no">foo</a></b>(var <i>bar</i>)</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-doctest.html#itsHappening-signal" translate="no">itsHappening</a></b>(bool <i>really</i>)</li>
+</ul>
+<h2 id="methods">Methods</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-doctest.html#fail-method" translate="no">fail</a></b>(<i>message</i>) <code class="summary extra" translate="no">(since QDoc.Test 1.0)</code></li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-doctest.html#fail_hard-method" translate="no">fail_hard</a></b>(<i>msg</i>, <i>option</i>)</li>
+</ul>
+<!-- $$$DocTest-description -->
+<h2 id="details">Detailed Description</h2>
+<h2 id="introduction">Introduction</h2>
+<p>A documentation test case, itself documented inline in DocTest.qml.</p>
+<!-- @@@DocTest -->
+<h2>Property Documentation</h2>
+<!-- $$$active -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="active-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">active</span> : <span class="type">bool</span> <code class="details extra" translate="no">[default: true]</code></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Whether the test is active.</p>
+<p><b>See also </b><a href="qml-qdoc-test-doctest.html#name-prop" translate="no">name</a>.</p>
+</div></div><!-- @@@active -->
+<br/>
+<!-- $$$name -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="name-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">name</span> : <span class="type">string</span> <code class="details extra" translate="no">[required]</code></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Name of the test.</p>
+<pre class="qml" translate="no"><span class="type"><a href="qml-test-nover-doctest.html" translate="no">DocTest</a></span> {
+ <span class="name">name</span>: <span class="string">&quot;test&quot;</span>
+ <span class="comment">// ...</span>
+}</pre>
+</div></div><!-- @@@name -->
+<br/>
+<h2>Signal Documentation</h2>
+<!-- $$$completed[overload1]$$$completed -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="completed-signal">
+<td class="tblQmlFuncNode"><p>
+<span class="name">completed</span>()</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><div class="admonition note"><p><b>Note: </b>The corresponding handler is <code translate="no">onCompleted</code>.</p>
+</div></div></div><!-- @@@completed -->
+<br/>
+<!-- $$$foo[overload1]$$$foovar -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="foo-signal">
+<td class="tblQmlFuncNode"><p>
+<span class="name">foo</span>(<span class="type">var</span> <i>bar</i>)</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Signal with parameter <i translate="no">bar</i>.</p>
+<div class="admonition note"><p><b>Note: </b>The corresponding handler is <code translate="no">onFoo</code>.</p>
+</div></div></div><!-- @@@foo -->
+<br/>
+<!-- $$$itsHappening[overload1]$$$itsHappeningbool -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="itsHappening-signal">
+<td class="tblQmlFuncNode"><p>
+<span class="name">itsHappening</span>(<span class="type">bool</span> <i>really</i>)</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Signals that something is <i translate="no">really</i> happening.</p>
+<div class="admonition note"><p><b>Note: </b>The corresponding handler is <code translate="no">onItsHappening</code>.</p>
+</div></div></div><!-- @@@itsHappening -->
+<br/>
+<h2>Method Documentation</h2>
+<!-- $$$fail[overload1]$$$fail -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="fail-method">
+<td class="tblQmlFuncNode"><p>
+<code class="details extra" translate="no">[since QDoc.Test 1.0]</code> <span class="name">fail</span>(<i>message</i> = &quot;oops&quot;)</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Fails the current test case, with the optional <i translate="no">message</i>.</p>
+<p>This method was introduced in QDoc.Test 1.0.</p>
+</div></div><!-- @@@fail -->
+<br/>
+<!-- $$$fail_hard[overload1]$$$fail_hard -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="fail_hard-method">
+<td class="tblQmlFuncNode"><p>
+<span class="name">fail_hard</span>(<i>msg</i> = &quot;facepalm&quot;, <i>option</i> = 123)</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Fails the current test case, hard.</p>
+<ul>
+<li>Prints out <i translate="no">msg</i>.</li>
+<li>Accepts a random <i translate="no">option</i>.</li>
+</ul>
+</div></div><!-- @@@fail_hard -->
+<br/>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-oldtype-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-oldtype-members.html
new file mode 100644
index 000000000..6527f5a6c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-oldtype-members.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- type.cpp -->
+ <meta name="description" content="Deprecated old type.">
+ <title>List of All Members for OldType | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>OldType</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for OldType</h1>
+<p>This is the complete list of members for <a href="qml-qdoc-test-oldtype.html" class="obsolete">OldType</a>, including inherited members.</p>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-oldtype.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-oldtype.html
new file mode 100644
index 000000000..4fe99b259
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-oldtype.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- type.cpp -->
+ <meta name="description" content="Deprecated old type.">
+ <title>OldType QML Type | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>OldType</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">OldType QML Type</h1>
+<!-- $$$OldType-brief -->
+<p>Deprecated old type. <a href="#details">More...</a></p>
+<!-- @@@OldType -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import QDoc.Test 1.1</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 1.1</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Status:</td><td class="memItemRight bottomAlign"> Deprecated since 1.0<span class="status deprecated"></span></td></tr></table></div><p><b>This type is deprecated since QDoc.Test 1.0. We strongly advise against using it in new code.</b></p>
+<ul>
+<li><a href="qml-qdoc-test-oldtype-members.html">List of all members, including inherited members</a></li>
+</ul>
+<!-- $$$OldType-description -->
+<h2 id="details">Detailed Description</h2>
+<!-- @@@OldType -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-type-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-type-members.html
new file mode 100644
index 000000000..eb3180a88
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-type-members.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- type.cpp -->
+ <meta name="description" content="A QML type documented in a .cpp file.">
+ <title>List of All Members for Type | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>Type</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for Type</h1>
+<p>This is the complete list of members for <a href="qml-qdoc-test-type.html">Type</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#fifth-prop" translate="no">fifth</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#fourth-prop" translate="no">fourth</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#group-prop" translate="no">group</a></b><ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#group.first-prop" translate="no">group.first</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#group.second-prop" translate="no">group.second</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#group.third-prop" translate="no">group.third</a></b> : int</li>
+</ul>
+</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#id-prop" translate="no">id</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#name-prop" translate="no">name</a></b> : string</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#type-attached-prop" translate="no">type</a></b> : enumeration [attached]</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#completed-signal" translate="no">completed</a></b>(int <i>status</i>)</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#configured-signal" translate="no">configured</a></b>() [attached]</li>
+<li class="fn" translate="no">Type <b><a href="qml-qdoc-test-type.html#copy-method" translate="no">copy</a></b>(<i>a</i>)</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type-obsolete.html#deprecatedMethod-method" translate="no">deprecatedMethod</a></b>() <code class="summary extra" translate="no">(deprecated in 6.2)</code></li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#disable-method" translate="no">disable</a></b>()</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#enable-method" translate="no">enable</a></b>()</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#futureDeprecated-method" translate="no">futureDeprecated</a></b>() <code class="summary extra" translate="no">(until 6.3)</code></li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#group.created-signal" translate="no">group.created</a></b>()</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-type-obsolete.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-type-obsolete.html
new file mode 100644
index 000000000..acd56759a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-type-obsolete.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- type.cpp -->
+ <meta name="description" content="A QML type documented in a .cpp file.">
+ <title>Obsolete Members for Type | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>Type</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Obsolete Members for Type</h1>
+<p><b>The following members of QML type <a href="qml-qdoc-test-type.html">Type</a> are deprecated.</b> They are provided to keep old source code working. We strongly advise against using them in new code.</p>
+<h2 id="methods">Methods</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type-obsolete.html#deprecatedMethod-method" translate="no">deprecatedMethod</a></b>() <code class="summary extra" translate="no">(deprecated in 6.2)</code></li>
+</ul>
+<h2>Method Documentation</h2>
+<!-- $$$deprecatedMethod[overload1]$$$deprecatedMethod -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="deprecatedMethod-method">
+<td class="tblQmlFuncNode"><p>
+<code class="details extra" translate="no">[deprecated in 6.2]</code> <span class="name">deprecatedMethod</span>()</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>This method is deprecated since QDoc.Test 6.2. We strongly advise against using it in new code.</p>
+<p>This method has no replacement.</p>
+<p>This is a method that should include information about being deprecated and that it has been so since 6.2 in its docs.</p>
+</div></div><!-- @@@deprecatedMethod -->
+<br/>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-type.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-type.html
new file mode 100644
index 000000000..7fb53266d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-type.html
@@ -0,0 +1,209 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- type.cpp -->
+ <meta name="description" content="A QML type documented in a .cpp file.">
+ <title>Type QML Type | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>Type</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#properties">Properties</a></li>
+<li class="level1"><a href="#attached-properties">Attached Properties</a></li>
+<li class="level1"><a href="#signals">Signals</a></li>
+<li class="level1"><a href="#attached-signals">Attached Signals</a></li>
+<li class="level1"><a href="#methods">Methods</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Type QML Type</h1>
+<!-- $$$Type-brief -->
+<p>A QML type documented in a .cpp file. <a href="#details">More...</a></p>
+<!-- @@@Type -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import QDoc.Test 1.1</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 1.1</td></tr><tr><td class="memItemLeft rightAlign topAlign"> In C++:</td><td class="memItemRight bottomAlign"> <a href="testqdoc-test.html" translate="no">Test</a></td></tr><tr><td class="memItemLeft rightAlign topAlign"> Status:</td><td class="memItemRight bottomAlign"> &lt;Work In Progress&gt;<span class="status work-in-progress"></span></td></tr></table></div><ul>
+<li><a href="qml-qdoc-test-type-members.html">List of all members, including inherited members</a></li>
+<li><a href="qml-qdoc-test-type-obsolete.html">Deprecated members</a></li>
+</ul>
+<h2 id="properties">Properties</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#fifth-prop" translate="no">fifth</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#fourth-prop" translate="no">fourth</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#group-prop" translate="no">group</a></b><ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#group.first-prop" translate="no">group.first</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#group.second-prop" translate="no">group.second</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#group.third-prop" translate="no">group.third</a></b> : int</li>
+</ul>
+</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#id-prop" translate="no">id</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#name-prop" translate="no">name</a></b> : string</li>
+</ul>
+<h2 id="attached-properties">Attached Properties</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#type-attached-prop" translate="no">type</a></b> : enumeration</li>
+</ul>
+<h2 id="signals">Signals</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#completed-signal" translate="no">completed</a></b>(int <i>status</i>)</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#group.created-signal" translate="no">group.created</a></b>()</li>
+</ul>
+<h2 id="attached-signals">Attached Signals</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#configured-signal" translate="no">configured</a></b>()</li>
+</ul>
+<h2 id="methods">Methods</h2>
+<ul>
+<li class="fn" translate="no">Type <b><a href="qml-qdoc-test-type.html#copy-method" translate="no">copy</a></b>(<i>a</i>)</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#disable-method" translate="no">disable</a></b>()</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#enable-method" translate="no">enable</a></b>()</li>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-type.html#futureDeprecated-method" translate="no">futureDeprecated</a></b>() <code class="summary extra" translate="no">(until 6.3)</code></li>
+</ul>
+<!-- $$$Type-description -->
+<h2 id="details">Detailed Description</h2>
+<!-- @@@Type -->
+<h2>Property Documentation</h2>
+<!-- $$$ -->
+<div class="qmlitem"><div class="fngroup">
+<div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="fifth-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">fifth</span> : <span class="type"><a href="qml-int.html" translate="no">int</a></span></p></td></tr>
+<tr valign="top" class="odd" id="fourth-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">fourth</span> : <span class="type"><a href="qml-int.html" translate="no">int</a></span></p></td></tr>
+</table></div></div>
+</div><div class="qmldoc"><p>A group of properties sharing a documentation comment.</p>
+</div></div><!-- @@@ -->
+<br/>
+<!-- $$$group -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="even" id="group-prop"><th class="centerAlign"><p><b>group group</b></p></th></tr>
+<tr valign="top" class="odd" id="group.first-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">group.first</span> : <span class="type"><a href="qml-int.html" translate="no">int</a></span></p></td></tr>
+<tr valign="top" class="odd" id="group.second-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">group.second</span> : <span class="type"><a href="qml-int.html" translate="no">int</a></span></p></td></tr>
+<tr valign="top" class="odd" id="group.third-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">group.third</span> : <span class="type"><a href="qml-int.html" translate="no">int</a></span></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>A property group.</p>
+</div></div><!-- @@@group -->
+<br/>
+<!-- $$$id -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="id-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">id</span> : <span class="type"><a href="qml-int.html" translate="no">int</a></span> <code class="details extra" translate="no">[read-only]</code></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>A read-only property.</p>
+</div></div><!-- @@@id -->
+<br/>
+<!-- $$$name -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="name-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">name</span> : <span class="type">string</span> <code class="details extra" translate="no">[required]</code></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Name of the Test.</p>
+</div></div><!-- @@@name -->
+<br/>
+<h2>Attached Property Documentation</h2>
+<!-- $$$type -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="type-attached-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">Type.type</span> : <span class="type">enumeration</span> <code class="details extra" translate="no">[default: Type.NoType]</code></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><div class="table"><table class="valuelist"><tr valign="top" class="odd"><th class="tblConst">Constant</th><th class="tbldscr">Description</th></tr>
+<tr><td class="topAlign"><code translate="no">Type.NoType</code></td><td class="topAlign">Nothing</td></tr>
+<tr><td class="topAlign"><code translate="no">Type.SomeType</code></td><td class="topAlign">Something</td></tr>
+</table></div>
+</div></div><!-- @@@type -->
+<br/>
+<h2>Signal Documentation</h2>
+<!-- $$$completed[overload1]$$$completedint -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="completed-signal">
+<td class="tblQmlFuncNode"><p>
+<span class="name">completed</span>(<span class="type"><a href="qml-int.html" translate="no">int</a></span> <i>status</i>)</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>This signal is emitted when the operation completed with <i translate="no">status</i>.</p>
+<div class="admonition note"><p><b>Note: </b>The corresponding handler is <code translate="no">onCompleted</code>.</p>
+</div></div></div><!-- @@@completed -->
+<br/>
+<!-- $$$group.created[overload1]$$$group.created -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="group.created-signal">
+<td class="tblQmlFuncNode"><p>
+<span class="name">group.created</span>()</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>This signal is prefixed with <i>group</i>.</p>
+<div class="admonition note"><p><b>Note: </b>The corresponding handler is <code translate="no">group.onCreated</code>.</p>
+</div></div></div><!-- @@@group.created -->
+<br/>
+<h2>Attached Signal Documentation</h2>
+<!-- $$$configured[overload1]$$$configured -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="configured-signal">
+<td class="tblQmlFuncNode"><p>
+<span class="name">configured</span>()</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>This attached signal is emitted when the type was configured.</p>
+<div class="admonition note"><p><b>Note: </b>The corresponding handler is <code translate="no">onConfigured</code>.</p>
+</div></div></div><!-- @@@configured -->
+<br/>
+<h2>Method Documentation</h2>
+<!-- $$$ -->
+<div class="qmlitem"><div class="fngroup">
+<div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="disable-method">
+<td class="tblQmlFuncNode"><p>
+<span class="name">disable</span>()</p></td></tr>
+<tr valign="top" class="odd" id="enable-method">
+<td class="tblQmlFuncNode"><p>
+<span class="name">enable</span>()</p></td></tr>
+</table></div></div>
+</div><div class="qmldoc"><p>Enables or disables this type.</p>
+</div></div><!-- @@@ -->
+<br/>
+<!-- $$$copy[overload1]$$$copy -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="copy-method">
+<td class="tblQmlFuncNode"><p>
+<span class="type"><a href="qml-qdoc-test-type.html" translate="no">Type</a></span> <span class="name">copy</span>(<i>a</i>)</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Returns another Type based on <i translate="no">a</i>.</p>
+</div></div><!-- @@@copy -->
+<br/>
+<!-- $$$futureDeprecated[overload1]$$$futureDeprecated -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="futureDeprecated-method">
+<td class="tblQmlFuncNode"><p>
+<code class="details extra" translate="no">[until 6.3]</code> <span class="name">futureDeprecated</span>()</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>This method is scheduled for deprecation in version 6.3.</p>
+<p>Use something else instead.</p>
+<p>This is a method that's marked for deprecation in a future version.</p>
+</div></div><!-- @@@futureDeprecated -->
+<br/>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-yetanotherchild-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-yetanotherchild-members.html
new file mode 100644
index 000000000..fbab41876
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-yetanotherchild-members.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- parent.qdoc -->
+ <meta name="description" content="A type inheriting from internal abstract parent.">
+ <title>List of All Members for YetAnotherChild | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>YetAnotherChild</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for YetAnotherChild</h1>
+<p>This is the complete list of members for <a href="qml-qdoc-test-yetanotherchild.html">YetAnotherChild</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-yetanotherchild.html#prop-prop" translate="no">prop</a></b> : int</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-yetanotherchild.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-yetanotherchild.html
new file mode 100644
index 000000000..2ecc87355
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-qdoc-test-yetanotherchild.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- parent.qdoc -->
+ <meta name="description" content="A type inheriting from internal abstract parent.">
+ <title>YetAnotherChild QML Type | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li>YetAnotherChild</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#properties">Properties</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">YetAnotherChild QML Type</h1>
+<!-- $$$YetAnotherChild-brief -->
+<p>A type inheriting from internal abstract parent. <a href="#details">More...</a></p>
+<!-- @@@YetAnotherChild -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import QDoc.Test 1.1</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 1.1</td></tr></table></div><ul>
+<li><a href="qml-qdoc-test-yetanotherchild-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="properties">Properties</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qdoc-test-yetanotherchild.html#prop-prop" translate="no">prop</a></b> : int</li>
+</ul>
+<!-- $$$YetAnotherChild-description -->
+<h2 id="details">Detailed Description</h2>
+<!-- @@@YetAnotherChild -->
+<h2>Property Documentation</h2>
+<!-- $$$prop -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="prop-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">prop</span> : <span class="type"><a href="qml-int.html" translate="no">int</a></span></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Propagated to inheriting type docs.</p>
+</div></div><!-- @@@prop -->
+<br/>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-doctest-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-doctest-members.html
new file mode 100644
index 000000000..491427692
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-doctest-members.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- DocTest.qml -->
+ <meta name="description" content="Shadows the type name in QDoc.Test module.">
+ <title>List of All Members for DocTest | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li><a href="test-nover-qmlmodule.html" translate="no">Test.NoVer (Tech Preview)</a></li>
+<li>DocTest</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for DocTest</h1>
+<p>This is the complete list of members for <a href="qml-test-nover-doctest.html">DocTest</a>, including inherited members.</p>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-doctest.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-doctest.html
new file mode 100644
index 000000000..2543b3ce3
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-doctest.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- DocTest.qml -->
+ <meta name="description" content="Shadows the type name in QDoc.Test module.">
+ <title>DocTest QML Type | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li><a href="test-nover-qmlmodule.html" translate="no">Test.NoVer (Tech Preview)</a></li>
+<li>DocTest</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">DocTest QML Type</h1>
+<!-- $$$DocTest-brief -->
+<p>Shadows the type name in QDoc.Test module. <a href="#details">More...</a></p>
+<!-- @@@DocTest -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import Test.NoVer</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 1.1</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Status:</td><td class="memItemRight bottomAlign"> Tech Preview<span class="status tech-preview"></span></td></tr></table></div><ul>
+<li><a href="qml-test-nover-doctest-members.html">List of all members, including inherited members</a></li>
+</ul>
+<!-- $$$DocTest-description -->
+<h2 id="details">Detailed Description</h2>
+<!-- @@@DocTest -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-typenoversion-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-typenoversion-members.html
new file mode 100644
index 000000000..ca2308045
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-typenoversion-members.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- type.cpp -->
+ <meta name="description" content="Another QML type documented in a .cpp file.">
+ <title>List of All Members for TypeNoVersion | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li><a href="test-nover-qmlmodule.html" translate="no">Test.NoVer (Tech Preview)</a></li>
+<li>TypeNoVersion</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for TypeNoVersion</h1>
+<p>This is the complete list of members for <a href="qml-test-nover-typenoversion.html">TypeNoVersion</a>, including inherited members.</p>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-typenoversion.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-typenoversion.html
new file mode 100644
index 000000000..b754038d9
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-test-nover-typenoversion.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- type.cpp -->
+ <meta name="description" content="Another QML type documented in a .cpp file.">
+ <title>TypeNoVersion QML Type | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li><a href="test-nover-qmlmodule.html" translate="no">Test.NoVer (Tech Preview)</a></li>
+<li>TypeNoVersion</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">TypeNoVersion QML Type</h1>
+<!-- $$$TypeNoVersion-brief -->
+<p>Another QML type documented in a .cpp file. <a href="#details">More...</a></p>
+<!-- @@@TypeNoVersion -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import Test.NoVer</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 1.1</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Status:</td><td class="memItemRight bottomAlign"> Tech Preview<span class="status tech-preview"></span></td></tr></table></div><ul>
+<li><a href="qml-test-nover-typenoversion-members.html">List of all members, including inherited members</a></li>
+</ul>
+<!-- $$$TypeNoVersion-description -->
+<h2 id="details">Detailed Description</h2>
+<!-- @@@TypeNoVersion -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-themodule-thetype-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-themodule-thetype-members.html
new file mode 100644
index 000000000..de3143219
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-themodule-thetype-members.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- properties.qdoc -->
+ <title>List of All Members for TheType | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li><a href="themodule-qmlmodule.html" translate="no">TheModule</a></li>
+<li>TheType</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for TheType</h1>
+<p>This is the complete list of members for <a href="qml-themodule-thetype.html">TheType</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-themodule-thetype.html#name-prop" translate="no">name</a></b> : string</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-themodule-thetype.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-themodule-thetype.html
new file mode 100644
index 000000000..86b8fd066
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-themodule-thetype.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- properties.qdoc -->
+ <title>TheType QML Type | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li><a href="themodule-qmlmodule.html" translate="no">TheModule</a></li>
+<li>TheType</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#properties">Properties</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">TheType QML Type</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import TheModule</td></tr><tr><td class="memItemLeft rightAlign topAlign"> In C++:</td><td class="memItemRight bottomAlign"> <a href="testqdoc-testderived.html" translate="no">TestDerived</a></td></tr></table></div><ul>
+<li><a href="qml-themodule-thetype-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="properties">Properties</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-themodule-thetype.html#name-prop" translate="no">name</a></b> : string</li>
+</ul>
+<!-- $$$TheType-description -->
+<h2 id="details">Detailed Description</h2>
+<!-- @@@TheType -->
+<h2>Property Documentation</h2>
+<!-- $$$name -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="name-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">name</span> : <span class="type">string</span> <code class="details extra" translate="no">[read-only]</code></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Read-only status of this property is resolved from Q_PROPERTY.</p>
+</div></div><!-- @@@name -->
+<br/>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-progressbar-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-progressbar-members.html
new file mode 100644
index 000000000..77c4ef164
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-progressbar-members.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- ProgressBar.qml -->
+ <meta name="description" content="A component that shows the progress of an event.">
+ <title>List of All Members for ProgressBar | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li><a href="uicomponents-qmlmodule.html" translate="no">UIComponents</a></li>
+<li>ProgressBar</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for ProgressBar</h1>
+<p>This is the complete list of members for <a href="qml-uicomponents-progressbar.html">ProgressBar</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-progressbar.html#color-prop" translate="no">color</a></b> : color</li>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-progressbar.html#maximum-prop" translate="no">maximum</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-progressbar.html#minimum-prop" translate="no">minimum</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-progressbar.html#secondColor-prop" translate="no">secondColor</a></b> : color</li>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-progressbar.html#value-prop" translate="no">value</a></b> : int</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-progressbar.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-progressbar.html
new file mode 100644
index 000000000..b491e2eec
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-progressbar.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- ProgressBar.qml -->
+ <meta name="description" content="A component that shows the progress of an event.">
+ <title>ProgressBar QML Type | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li><a href="uicomponents-qmlmodule.html" translate="no">UIComponents</a></li>
+<li>ProgressBar</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#properties">Properties</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">ProgressBar QML Type</h1>
+<!-- $$$ProgressBar-brief -->
+<p>A component that shows the progress of an event. <a href="#details">More...</a></p>
+<!-- @@@ProgressBar -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import UIComponents 1.0</td></tr></table></div><ul>
+<li><a href="qml-uicomponents-progressbar-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="properties">Properties</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-progressbar.html#color-prop" translate="no">color</a></b> : color</li>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-progressbar.html#maximum-prop" translate="no">maximum</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-progressbar.html#minimum-prop" translate="no">minimum</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-progressbar.html#secondColor-prop" translate="no">secondColor</a></b> : color</li>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-progressbar.html#value-prop" translate="no">value</a></b> : int</li>
+</ul>
+<!-- $$$ProgressBar-description -->
+<h2 id="details">Detailed Description</h2>
+<p>A ProgressBar shows the linear progress of an event as its <a href="qml-uicomponents-progressbar.html#value-prop" translate="no">value</a>. The range is specified using the <a href="qml-uicomponents-progressbar.html#minimum-prop" translate="no">minimum</a> and the <a href="qml-uicomponents-progressbar.html#maximum-prop" translate="no">maximum</a> values.</p>
+<p>The ProgressBar component is part of the <a href="uicomponents-qmlmodule.html" translate="no">UI Components</a> module.</p>
+<p>This documentation is part of the <a href="test-componentset-example.html">UIComponents</a> example.</p>
+<!-- @@@ProgressBar -->
+<h2>Property Documentation</h2>
+<!-- $$$color -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="color-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">color</span> : <span class="type">color</span></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>The color of the <a href="qml-uicomponents-progressbar.html" translate="no">ProgressBar</a>'s gradient. Must bind to a color type.</p>
+<p><b>See also </b><a href="qml-uicomponents-progressbar.html#secondColor-prop" translate="no">secondColor</a>.</p>
+</div></div><!-- @@@color -->
+<br/>
+<!-- $$$maximum -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="maximum-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">maximum</span> : <span class="type"><a href="qml-int.html" translate="no">int</a></span></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>The maximum value of the <a href="qml-uicomponents-progressbar.html" translate="no">ProgressBar</a> range. The <a href="qml-uicomponents-progressbar.html#value-prop" translate="no">value</a> must not be more than this value.</p>
+</div></div><!-- @@@maximum -->
+<br/>
+<!-- $$$minimum -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="minimum-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">minimum</span> : <span class="type"><a href="qml-int.html" translate="no">int</a></span></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>The minimum value of the <a href="qml-uicomponents-progressbar.html" translate="no">ProgressBar</a> range. The <a href="qml-uicomponents-progressbar.html#value-prop" translate="no">value</a> must not be less than this value.</p>
+</div></div><!-- @@@minimum -->
+<br/>
+<!-- $$$secondColor -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="secondColor-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">secondColor</span> : <span class="type">color</span></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>The second color of the <a href="qml-uicomponents-progressbar.html" translate="no">ProgressBar</a>'s gradient. Must bind to a color type.</p>
+<p><b>See also </b><a href="qml-uicomponents-progressbar.html#color-prop" translate="no">color</a>.</p>
+</div></div><!-- @@@secondColor -->
+<br/>
+<!-- $$$value -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="value-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">value</span> : <span class="type"><a href="qml-int.html" translate="no">int</a></span></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>The value of the progress.</p>
+</div></div><!-- @@@value -->
+<br/>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-switch-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-switch-members.html
new file mode 100644
index 000000000..df1cd735f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-switch-members.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- Switch.qml -->
+ <meta name="description" content="A component that can be turned on or off.">
+ <title>List of All Members for Switch | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li><a href="uicomponents-qmlmodule.html" translate="no">UIComponents</a></li>
+<li>Switch</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for Switch</h1>
+<p>This is the complete list of members for <a href="qml-uicomponents-switch.html">Switch</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-switch.html#on-prop" translate="no">on</a></b> : bool</li>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-switch.html#toggle-method" translate="no">toggle</a></b>()</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-switch.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-switch.html
new file mode 100644
index 000000000..8e3917a21
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-switch.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- Switch.qml -->
+ <meta name="description" content="A component that can be turned on or off.">
+ <title>Switch QML Type | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li><a href="uicomponents-qmlmodule.html" translate="no">UIComponents</a></li>
+<li>Switch</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#properties">Properties</a></li>
+<li class="level1"><a href="#methods">Methods</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Switch QML Type</h1>
+<!-- $$$Switch-brief -->
+<p>A component that can be turned on or off. <a href="#details">More...</a></p>
+<!-- @@@Switch -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import UIComponents 1.0</td></tr></table></div><ul>
+<li><a href="qml-uicomponents-switch-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="properties">Properties</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-switch.html#on-prop" translate="no">on</a></b> : bool</li>
+</ul>
+<h2 id="methods">Methods</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-switch.html#toggle-method" translate="no">toggle</a></b>()</li>
+</ul>
+<!-- $$$Switch-description -->
+<h2 id="details">Detailed Description</h2>
+<p>A toggle switch has two states: an <code translate="no">on</code> and an <code translate="no">off</code> state. The <code translate="no">off</code> state is when the <a href="qml-uicomponents-switch.html#on-prop" translate="no">on</a> property is set to <code translate="no">false</code>.</p>
+<p>The ToggleSwitch component is part of the <a href="uicomponents-qmlmodule.html" translate="no">UI Components</a> module.</p>
+<p>This documentation is part of the <a href="test-componentset-example.html">UIComponents</a> example.</p>
+<!-- @@@Switch -->
+<h2>Property Documentation</h2>
+<!-- $$$on -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="on-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">on</span> : <span class="type">bool</span></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>Indicates the state of the switch. If <code translate="no">false</code>, then the switch is in the <code translate="no">off</code> state.</p>
+</div></div><!-- @@@on -->
+<br/>
+<h2>Method Documentation</h2>
+<!-- $$$toggle[overload1]$$$toggle -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="toggle-method">
+<td class="tblQmlFuncNode"><p>
+<span class="name">toggle</span>()</p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>A method to toggle the switch. If the switch is <code translate="no">on</code>, the toggling it will turn it <code translate="no">off</code>. Toggling a switch in the <code translate="no">off</code> position will turn it <code translate="no">on</code>.</p>
+</div></div><!-- @@@toggle -->
+<br/>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-tabwidget-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-tabwidget-members.html
new file mode 100644
index 000000000..cd459ceff
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-tabwidget-members.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- TabWidget.qml -->
+ <meta name="description" content="A widget that places its children as tabs.">
+ <title>List of All Members for TabWidget | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li><a href="uicomponents-qmlmodule.html" translate="no">UIComponents</a></li>
+<li>TabWidget</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for TabWidget</h1>
+<p>This is the complete list of members for <a href="qml-uicomponents-tabwidget.html">TabWidget</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-tabwidget.html#current-prop" translate="no">current</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-tabwidget.html#sampleReadOnlyProperty-prop" translate="no">sampleReadOnlyProperty</a></b> : int</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-tabwidget.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-tabwidget.html
new file mode 100644
index 000000000..466262eca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qml-uicomponents-tabwidget.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- TabWidget.qml -->
+ <meta name="description" content="A widget that places its children as tabs.">
+ <title>TabWidget QML Type | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="qdoc-test-qmlmodule.html" translate="no">Types</a></li>
+<li><a href="uicomponents-qmlmodule.html" translate="no">UIComponents</a></li>
+<li>TabWidget</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#properties">Properties</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#adding-tabs">Adding Tabs</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">TabWidget QML Type</h1>
+<!-- $$$TabWidget-brief -->
+<p>A widget that places its children as tabs. <a href="#details">More...</a></p>
+<!-- @@@TabWidget -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import UIComponents 1.0</td></tr></table></div><ul>
+<li><a href="qml-uicomponents-tabwidget-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="properties">Properties</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-tabwidget.html#current-prop" translate="no">current</a></b> : int</li>
+<li class="fn" translate="no"><b><a href="qml-uicomponents-tabwidget.html#sampleReadOnlyProperty-prop" translate="no">sampleReadOnlyProperty</a></b> : int</li>
+</ul>
+<!-- $$$TabWidget-description -->
+<h2 id="details">Detailed Description</h2>
+<p>A TabWidget places its children as tabs in a view. Selecting a tab involves selecting the tab at the top.</p>
+<p>The TabWidget component is part of the <a href="uicomponents-qmlmodule.html" translate="no">UI Components</a> module.</p>
+<p>This documentation is part of the <a href="test-componentset-example.html">UIComponents</a> example.</p>
+<h2 id="adding-tabs">Adding Tabs</h2>
+<p>To add a tab, declare the tab as a child of the TabWidget.</p>
+<pre class="cpp" translate="no"><span class="type"><a href="qml-uicomponents-tabwidget.html" translate="no">TabWidget</a></span> {
+ <span class="name">id</span>: <span class="name">tabwidget</span>
+
+ <span class="type">Rectangle</span> {
+ <span class="name">id</span>: <span class="name">tab1</span>
+ <span class="name">color</span>: <span class="string">&quot;red&quot;</span>
+ <span class="comment">//... omitted</span>
+ }
+ <span class="type">Rectangle</span> {
+ <span class="name">id</span>: <span class="name">tab2</span>
+ <span class="name">color</span>: <span class="string">&quot;blue&quot;</span>
+ <span class="comment">//... omitted</span>
+ }
+
+}</pre>
+<!-- @@@TabWidget -->
+<h2>Property Documentation</h2>
+<!-- $$$current -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="current-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">current</span> : <span class="type"><a href="qml-int.html" translate="no">int</a></span></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>The currently active tab in the <a href="qml-uicomponents-tabwidget.html" translate="no">TabWidget</a>.</p>
+</div></div><!-- @@@current -->
+<br/>
+<!-- $$$sampleReadOnlyProperty -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="sampleReadOnlyProperty-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">sampleReadOnlyProperty</span> : <span class="type"><a href="qml-int.html" translate="no">int</a></span> <code class="details extra" translate="no">[read-only]</code></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>A sample <code translate="no">read-only</code> property. A contrived property to demonstrate QDoc's ability to detect read-only properties.</p>
+<p>The signature is:</p>
+<pre class="cpp" translate="no">readonly property <span class="type"><a href="qml-int.html" translate="no">int</a></span> sampleReadOnlyProperty: <span class="number">0</span></pre>
+<p>Note that the property must be initialized to a value.</p>
+</div></div><!-- @@@sampleReadOnlyProperty -->
+<br/>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qmlmodules.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qmlmodules.html
new file mode 100644
index 000000000..bd0238d40
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/qmlmodules.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- modules.qdoc -->
+ <title>QML Modules | Test 6.2.11</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#qml-types">QML types</a></li>
+<li class="level1"><a href="#qml-value-types">QML value types</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">QML Modules</h1>
+<!-- $$$qmlmodules.html-description -->
+<div class="descr" id="details">
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="test-empty-qmlmodule.html">No QML Types Here</a></p></td><td class="tblDescr"><p>A QML module with no member types.</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="qdoc-test-qmlmodule.html">QDoc.Test QML Module</a></p></td><td class="tblDescr"><p>QML Types for the Test module.</p></td></tr>
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="themodule-qmlmodule.html">TheModule</a></p></td><td class="tblDescr"><p></p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="uicomponents-qmlmodule.html">UI Components</a></p></td><td class="tblDescr"><p>Basic set of UI components.</p></td></tr>
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="test-nover-qmlmodule.html">Versionless QML Module</a></p></td><td class="tblDescr"><p>QML Types for the Test module without version.</p></td></tr>
+</table></div>
+<h2 id="qml-types">QML types</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qml-qdoc-test-abstractparent.html">AbstractParent</a></p></td><td class="tblDescr"><p>Abstract base QML type</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="qml-qdoc-test-child.html">Child</a></p></td><td class="tblDescr"><p>A Child inheriting its parent</p></td></tr>
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qml-qdoc-test-doctest.html">DocTest</a></p></td><td class="tblDescr"><p>Represents a doc test case</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="qml-qdoc-test-type.html">Type</a></p></td><td class="tblDescr"><p>A QML type documented in a .cpp file</p></td></tr>
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qml-qdoc-test-yetanotherchild.html">YetAnotherChild</a></p></td><td class="tblDescr"><p>A type inheriting from internal abstract parent</p></td></tr>
+</table></div>
+<h2 id="qml-value-types">QML value types</h2>
+<p class="centerAlign functionIndex" translate="no"><b><a href="#i">I</a>&nbsp;</b></p>
+<div class="flowListDiv" translate="no">
+<dl class="flowList odd"><dt class="alphaChar" id="i"><b>I</b></dt>
+<dd><a href="qml-int.html">int</a></dd>
+</dl>
+</div>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qml-int.html">int</a></p></td><td class="tblDescr"><p>An integer value type</p></td></tr>
+</table></div>
+</div>
+<!-- @@@qmlmodules.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/seenclass.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/seenclass.html
new file mode 100644
index 000000000..ece82ceb7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/seenclass.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- dont.cpp -->
+ <meta name="description" content="A public but undocumented class.">
+ <title>SeenClass Class | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>SeenClass</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">SeenClass Class</h1>
+<!-- $$$SeenClass-brief -->
+<p>A public but undocumented class. <a href="#details">More...</a></p>
+<!-- @@@SeenClass -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;SeenClass&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+</table></div>
+<!-- $$$SeenClass-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@SeenClass -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-cmaketest-cmakelists-txt.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-cmaketest-cmakelists-txt.html
new file mode 100644
index 000000000..8072df6db
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-cmaketest-cmakelists-txt.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- cmaketest.qdoc -->
+ <title>CMake Example Project | Test 6.2.11</title>
+</head>
+<body>
+<h1 class="title">CMake Example Project</h1>
+<pre class="cpp" translate="no">cmake_minimum_required(VERSION 3.16)
+project (QDOCTEST)</pre>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-cmaketest-example.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-cmaketest-example.html
new file mode 100644
index 000000000..a9ebe9784
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-cmaketest-example.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- cmaketest.qdoc -->
+ <title>CMake Example Project | Test 6.2.11</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">CMake Example Project</h1>
+<!-- $$$cmaketest-description -->
+<div class="descr" id="details">
+<p class="centerAlign"><img src="images/leonardo-da-vinci.png" alt="" /></p><p>Files:</p>
+<ul>
+<li><a href="test-cmaketest-cmakelists-txt.html" translate="no">cmaketest/CMakeLists.txt</a></li>
+<li><a href="test-cmaketest-main-cpp.html" translate="no">cmaketest/main.cpp</a></li>
+</ul>
+</div>
+<!-- @@@cmaketest -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-cmaketest-main-cpp.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-cmaketest-main-cpp.html
new file mode 100644
index 000000000..0acb90579
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-cmaketest-main-cpp.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- cmaketest.qdoc -->
+ <title>CMake Example Project | Test 6.2.11</title>
+</head>
+<body>
+<h1 class="title">CMake Example Project</h1>
+<pre class="cpp" translate="no"><span class="type">void</span> main(){}</pre>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-componentset-pro.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-componentset-pro.html
new file mode 100644
index 000000000..0589f516e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-componentset-pro.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- examples.qdoc -->
+ <meta name="description" content="Example for documenting QML types.">
+ <title>QML Documentation Example | Test 6.2.11</title>
+</head>
+<body>
+<h1 class="title">QML Documentation Example</h1>
+<pre class="cpp" translate="no">SOURCES = componentset.pro \
+ ProgressBar.qml \
+ Switch.qml \
+ TabWidget.qml \
+ uicomponents.qdoc</pre>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-componentset-qml.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-componentset-qml.html
new file mode 100644
index 000000000..8d50b2a3a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-componentset-qml.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- examples.qdoc -->
+ <meta name="description" content="Example for documenting QML types.">
+ <title>QML Documentation Example | Test 6.2.11</title>
+</head>
+<body>
+<h1 class="title">QML Documentation Example</h1>
+<pre class="qml" translate="no"><span class="comment">// Copyright (C) 2023 The Qt Company Ltd.</span>
+<span class="comment">// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0</span>
+
+import QtQuick 2.0
+
+<span class="type">Item</span> {
+}</pre>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-example.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-example.html
new file mode 100644
index 000000000..8227ff937
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-example.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- examples.qdoc -->
+ <meta name="description" content="Example for documenting QML types.">
+ <title>QML Documentation Example | Test 6.2.11</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#qml-class">QML Class</a></li>
+<li class="level1"><a href="#properties-signals-handlers-and-methods">Properties, Signals, Handlers, and Methods</a></li>
+<li class="level2"><a href="#internal-documentation">Internal Documentation</a></li>
+<li class="level1"><a href="#qml-types-with-c-implementation">QML Types with C++ Implementation</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">QML Documentation Example</h1>
+<!-- $$$componentset-brief -->
+<p>Example for documenting QML types.</p>
+<!-- @@@componentset -->
+<!-- $$$componentset-description -->
+<div class="descr" id="details">
+<p>This example demonstrates one of the ways to document QML types. It also generates a warning about a missing example image, on purpose.</p>
+<p>In particular, there are sample types that are documented with QDoc commands comments. There are documentation comments for the QML types and their public interfaces. The types are grouped into a module, the <a href="uicomponents-qmlmodule.html" translate="no">UI Components</a> module.</p>
+<p>The uicomponents.qdoc file generates the overview page for the <a href="uicomponents-qmlmodule.html" translate="no">UI Components</a> module page.</p>
+<p>The generated documentation is available in the <a href="uicomponents-qmlmodule.html" translate="no">UI Components</a> module.</p>
+<h4 id="qml-class">QML Class</h4>
+<p>The QML types use the \qmltype to document the type. In addition, they have the \inmodule command in order for QDoc to associate them to the <code translate="no">UIComponents</code> module.</p>
+<p>QDoc uses the \brief command to place a basic description when listing the types.</p>
+<h4 id="properties-signals-handlers-and-methods">Properties, Signals, Handlers, and Methods</h4>
+<p>The types have their properties, signals, handlers, and methods defined in their respective QML files. QDoc associates the properties and methods to the types, therefore, you only need to place the documentation above the property, method, or signal.</p>
+<p>To document the type of a <i>property alias</i>, you must use the \qmlproperty command to specify the data type.</p>
+<pre class="cpp" translate="no">\qmlproperty <span class="type">int</span> anAliasedProperty
+An aliased property of type <span class="type">int</span><span class="operator">.</span></pre>
+<h5 id="internal-documentation">Internal Documentation</h5>
+<p>You may declare that a documentation is for internal use by placing the \internal command after the beginning QDoc comment <code translate="no">/*</code>. QDoc will prevent the internal documentation from appearing in the public API.</p>
+<p>If you wish to omit certain parts of the documentation, you may use the \omit and \endomit command.</p>
+<h4 id="qml-types-with-c-implementation">QML Types with C++ Implementation</h4>
+<p>This example only demonstrates the documentation for types in QML files, but the regular QML commands may be placed inside C++ classes to define the public API of the QML type.</p>
+<p>Files:</p>
+<ul>
+<li><a href="test-componentset-progressbar-qml.html" translate="no">componentset/ProgressBar.qml</a></li>
+<li><a href="test-componentset-switch-qml.html" translate="no">componentset/Switch.qml</a></li>
+<li><a href="test-componentset-tabwidget-qml.html" translate="no">componentset/TabWidget.qml</a></li>
+<li><a href="test-componentset-componentset-pro.html" translate="no">componentset/componentset.pro</a></li>
+<li><a href="test-componentset-componentset-qml.html" translate="no">componentset/componentset.qml</a></li>
+</ul>
+</div>
+<!-- @@@componentset -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-progressbar-qml.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-progressbar-qml.html
new file mode 100644
index 000000000..41c9c506a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-progressbar-qml.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- examples.qdoc -->
+ <meta name="description" content="Example for documenting QML types.">
+ <title>QML Documentation Example | Test 6.2.11</title>
+</head>
+<body>
+<h1 class="title">QML Documentation Example</h1>
+<pre class="qml" translate="no"><span class="comment">// Copyright (C) 2016 The Qt Company Ltd.</span>
+<span class="comment">// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause</span>
+
+import QtQuick 1.0
+
+<span class="comment">/*!
+ \qmltype ProgressBar
+ \inqmlmodule UIComponents
+ \brief A component that shows the progress of an event.
+
+ A ProgressBar shows the linear progress of an event as its \l value.
+ The range is specified using the \l {minimum} and the \l{maximum} values.
+
+ The ProgressBar component is part of the \l {UI Components} module.
+
+ This documentation is part of the \l{componentset}{UIComponents} example.
+*/</span>
+<span class="type">Item</span> {
+ <span class="name">id</span>: <span class="name">progressbar</span>
+
+ <span class="comment">/*!
+ The minimum value of the ProgressBar range.
+ The \l value must not be less than this value.
+ */</span>
+ property <span class="type"><a href="qml-int.html" translate="no">int</a></span> <span class="name">minimum</span>: <span class="number">0</span>
+
+ <span class="comment">/*!
+ The maximum value of the ProgressBar range.
+ The \l value must not be more than this value.
+ */</span>
+ property <span class="type"><a href="qml-int.html" translate="no">int</a></span> <span class="name">maximum</span>: <span class="number">100</span>
+
+ <span class="comment">/*!
+ The value of the progress.
+ */</span>
+ property <span class="type"><a href="qml-int.html" translate="no">int</a></span> <span class="name">value</span>: <span class="number">0</span>
+
+ <span class="comment">/*!
+ \qmlproperty color ProgressBar::color
+ The color of the ProgressBar's gradient. Must bind to a color type.
+
+ \omit
+ The &quot;\qmlproperty &lt;type&gt; &lt;property name&gt;&quot; is needed because
+ property alias need to have their types manually entered.
+
+ QDoc will not publish the documentation within omit and endomit.
+ \endomit
+
+ \sa secondColor
+ */</span>
+ property <span class="type">alias</span> <span class="name">color</span>: <span class="name">gradient1</span>.<span class="name">color</span>
+
+ <span class="comment">/*!
+ \qmlproperty color ProgressBar::secondColor
+ The second color of the ProgressBar's gradient.
+ Must bind to a color type.
+
+ \omit
+ The &quot;\qmlproperty &lt;type&gt; &lt;property name&gt;&quot; is needed because
+ property alias need to have their types manually entered.
+
+ QDoc will not publish the documentation within omit and endomit.
+ \endomit
+
+ \sa color
+ */</span>
+ property <span class="type">alias</span> <span class="name">secondColor</span>: <span class="name">gradient2</span>.<span class="name">color</span>
+
+ <span class="name">width</span>: <span class="number">250</span>; <span class="name">height</span>: <span class="number">23</span>
+ <span class="name">clip</span>: <span class="number">true</span>
+
+ <span class="type">Rectangle</span> {
+ <span class="name">id</span>: <span class="name">highlight</span>
+
+ <span class="comment">/*!
+ An internal documentation comment. The widthDest property is not
+ a public API and therefore will not be exposed.
+ */</span>
+ property <span class="type"><a href="qml-int.html" translate="no">int</a></span> <span class="name">widthDest</span>: ((<span class="name">progressbar</span>.<span class="name">width</span> <span class="operator">*</span> (<span class="name">value</span> <span class="operator">-</span> <span class="name">minimum</span>)) <span class="operator">/</span> (<span class="name">maximum</span> <span class="operator">-</span> <span class="name">minimum</span>) <span class="operator">-</span> <span class="number">6</span>)
+
+ <span class="name">width</span>: <span class="name">highlight</span>.<span class="name">widthDest</span>
+ Behavior on <span class="name">width</span> { <span class="type">SmoothedAnimation</span> { <span class="name">velocity</span>: <span class="number">1200</span> } }
+
+ <span class="type">anchors</span> { <span class="name">left</span>: <span class="name">parent</span>.<span class="name">left</span>; <span class="name">top</span>: <span class="name">parent</span>.<span class="name">top</span>; <span class="name">bottom</span>: <span class="name">parent</span>.<span class="name">bottom</span>; <span class="name">margins</span>: <span class="number">3</span> }
+ <span class="name">radius</span>: <span class="number">1</span>
+ <span class="name">gradient</span>: <span class="name">Gradient</span> {
+ <span class="type">GradientStop</span> { <span class="name">id</span>: <span class="name">gradient1</span>; <span class="name">position</span>: <span class="number">0.0</span> }
+ <span class="type">GradientStop</span> { <span class="name">id</span>: <span class="name">gradient2</span>; <span class="name">position</span>: <span class="number">1.0</span> }
+ }
+
+ }
+ <span class="type">Text</span> {
+ <span class="type">anchors</span> { <span class="name">right</span>: <span class="name">highlight</span>.<span class="name">right</span>; <span class="name">rightMargin</span>: <span class="number">6</span>; <span class="name">verticalCenter</span>: <span class="name">parent</span>.<span class="name">verticalCenter</span> }
+ <span class="name">color</span>: <span class="string">&quot;white&quot;</span>
+ <span class="name">font</span>.bold: <span class="number">true</span>
+ <span class="name">text</span>: <span class="name">Math</span>.<span class="name">floor</span>((<span class="name">value</span> <span class="operator">-</span> <span class="name">minimum</span>) <span class="operator">/</span> (<span class="name">maximum</span> <span class="operator">-</span> <span class="name">minimum</span>) <span class="operator">*</span> <span class="number">100</span>) <span class="operator">+</span> <span class="string">'%'</span>
+ }
+}</pre>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-switch-qml.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-switch-qml.html
new file mode 100644
index 000000000..958f2f19b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-switch-qml.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- examples.qdoc -->
+ <meta name="description" content="Example for documenting QML types.">
+ <title>QML Documentation Example | Test 6.2.11</title>
+</head>
+<body>
+<h1 class="title">QML Documentation Example</h1>
+<pre class="qml" translate="no"><span class="comment">// Copyright (C) 2016 The Qt Company Ltd.</span>
+<span class="comment">// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause</span>
+
+import QtQuick 1.0
+
+<span class="comment">/*!
+ \qmltype ToggleSwitch
+ \inqmlmodule UIComponents
+ \brief A component that can be turned on or off.
+
+ A toggle switch has two states: an \c on and an \c off state. The \c off
+ state is when the \l on property is set to \c false.
+
+ The ToggleSwitch component is part of the \l {UI Components} module.
+
+ This documentation is part of the \l{componentset}{UIComponents} example.
+
+*/</span>
+<span class="type">Item</span> {
+ <span class="name">id</span>: <span class="name">toggleswitch</span>
+ <span class="name">width</span>: <span class="name">background</span>.<span class="name">width</span>; <span class="name">height</span>: <span class="name">background</span>.<span class="name">height</span>
+
+ <span class="comment">/*!
+ Indicates the state of the switch. If \c false, then the switch is in
+ the \c off state.
+
+ \omit
+ The \qmlproperty &lt;type&gt; &lt;propertyname&gt; is not necessary as QDoc
+ will associate this property to the ToggleSwitch
+
+ QDoc will not publish the documentation within omit and endomit.
+ \endomit
+ */</span>
+ property <span class="type">bool</span> <span class="name">on</span>: <span class="number">false</span>
+
+ <span class="comment">/*!
+ A method to toggle the switch. If the switch is \c on, the toggling it
+ will turn it \c off. Toggling a switch in the \c off position will
+ turn it \c on.
+ */</span>
+ <span class="keyword">function </span><span class="name">toggle</span>() {
+ <span class="keyword">if</span> (<span class="name">toggleswitch</span>.<span class="name">state</span> <span class="operator">==</span> <span class="string">&quot;on&quot;</span>)
+ <span class="name">toggleswitch</span>.<span class="name">state</span> <span class="operator">=</span> <span class="string">&quot;off&quot;</span>;
+ <span class="keyword">else</span>
+ <span class="name">toggleswitch</span>.<span class="name">state</span> <span class="operator">=</span> <span class="string">&quot;on&quot;</span>;
+ }
+
+ <span class="comment">/*!
+ \internal
+
+ An internal function to synchronize the switch's internals. This
+ function is not for public access. The \internal command will
+ prevent QDoc from publishing this comment in the public API.
+ */</span>
+ <span class="keyword">function </span><span class="name">releaseSwitch</span>() {
+ <span class="keyword">if</span> (<span class="name">knob</span>.<span class="name">x</span> <span class="operator">==</span> <span class="number">1</span>) {
+ <span class="keyword">if</span> (<span class="name">toggleswitch</span>.<span class="name">state</span> <span class="operator">==</span> <span class="string">&quot;off&quot;</span>) <span class="keyword">return</span>;
+ }
+ <span class="keyword">if</span> (<span class="name">knob</span>.<span class="name">x</span> <span class="operator">==</span> <span class="number">78</span>) {
+ <span class="keyword">if</span> (<span class="name">toggleswitch</span>.<span class="name">state</span> <span class="operator">==</span> <span class="string">&quot;on&quot;</span>) <span class="keyword">return</span>;
+ }
+ <span class="name">toggle</span>();
+ }
+
+ <span class="type">Rectangle</span> {
+ <span class="name">id</span>: <span class="name">background</span>
+ <span class="name">width</span>: <span class="number">130</span>; <span class="name">height</span>: <span class="number">48</span>
+ <span class="name">radius</span>: <span class="number">48</span>
+ <span class="name">color</span>: <span class="string">&quot;lightsteelblue&quot;</span>
+ <span class="type">MouseArea</span> { <span class="name">anchors</span>.fill: <span class="name">parent</span>; <span class="name">onClicked</span>: <span class="name">toggle</span>() }
+ }
+
+ <span class="type">Rectangle</span> {
+ <span class="name">id</span>: <span class="name">knob</span>
+ <span class="name">width</span>: <span class="number">48</span>; <span class="name">height</span>: <span class="number">48</span>
+ <span class="name">radius</span>: <span class="name">width</span>
+ <span class="name">color</span>: <span class="string">&quot;lightblue&quot;</span>
+
+ <span class="type">MouseArea</span> {
+ <span class="name">anchors</span>.fill: <span class="name">parent</span>
+ <span class="name">drag</span>.target: <span class="name">knob</span>; <span class="name">drag</span>.axis: <span class="name">Drag</span>.<span class="name">XAxis</span>; <span class="name">drag</span>.minimumX: <span class="number">1</span>; <span class="name">drag</span>.maximumX: <span class="number">78</span>
+ <span class="name">onClicked</span>: <span class="name">toggle</span>()
+ <span class="name">onReleased</span>: <span class="name">releaseSwitch</span>()
+ }
+ }
+
+ <span class="name">states</span>: [
+ <span class="type">State</span> {
+ <span class="name">name</span>: <span class="string">&quot;on&quot;</span>
+ <span class="type">PropertyChanges</span> { <span class="name">target</span>: <span class="name">knob</span>; <span class="name">x</span>: <span class="number">78</span> }
+ <span class="type">PropertyChanges</span> { <span class="name">target</span>: <span class="name">toggleswitch</span>; <span class="name">on</span>: <span class="number">true</span> }
+ },
+ <span class="type">State</span> {
+ <span class="name">name</span>: <span class="string">&quot;off&quot;</span>
+ <span class="type">PropertyChanges</span> { <span class="name">target</span>: <span class="name">knob</span>; <span class="name">x</span>: <span class="number">1</span> }
+ <span class="type">PropertyChanges</span> { <span class="name">target</span>: <span class="name">toggleswitch</span>; <span class="name">on</span>: <span class="number">false</span> }
+ }
+ ]
+
+ <span class="name">transitions</span>: <span class="name">Transition</span> {
+ <span class="type">NumberAnimation</span> { <span class="name">properties</span>: <span class="string">&quot;x&quot;</span>; <span class="name">easing</span>.type: <span class="name">Easing</span>.<span class="name">InOutQuad</span>; <span class="name">duration</span>: <span class="number">200</span> }
+ }
+}</pre>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-tabwidget-qml.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-tabwidget-qml.html
new file mode 100644
index 000000000..ea6116dca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-componentset-tabwidget-qml.html
@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- examples.qdoc -->
+ <meta name="description" content="Example for documenting QML types.">
+ <title>QML Documentation Example | Test 6.2.11</title>
+</head>
+<body>
+<h1 class="title">QML Documentation Example</h1>
+<pre class="qml" translate="no"><span class="comment">// Copyright (C) 2016 The Qt Company Ltd.</span>
+<span class="comment">// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause</span>
+
+import QtQuick 1.0
+
+<span class="comment">/*!
+ \qmltype TabWidget
+ \inqmlmodule UIComponents
+ \brief A widget that places its children as tabs.
+
+ A TabWidget places its children as tabs in a view. Selecting
+ a tab involves selecting the tab at the top.
+
+ The TabWidget component is part of the \l {UI Components} module.
+
+ This documentation is part of the \l{componentset}{UIComponents} example.
+
+ \section1 Adding Tabs
+
+ To add a tab, declare the tab as a child of the TabWidget.
+
+ \code
+ TabWidget {
+ id: tabwidget
+
+ Rectangle {
+ id: tab1
+ color: &quot;red&quot;
+ //... omitted
+ }
+ Rectangle {
+ id: tab2
+ color: &quot;blue&quot;
+ //... omitted
+ }
+
+ }
+ \endcode
+
+*/</span>
+<span class="type">Item</span> {
+ <span class="name">id</span>: <span class="name">tabWidget</span>
+
+ <span class="comment">/*!
+ \internal
+
+ Setting the default property to stack.children means any child items
+ of the TabWidget are actually added to the 'stack' item's children.
+
+ See the \l{&quot;Property Binding in QML&quot;}
+ documentation for details on default properties.
+
+ This is an implementation detail, not meant for public knowledge. Putting
+ the \internal command at the beginning will cause QDoc to not publish this
+ documentation in the public API page.
+
+ Normally, a property alias needs to have a
+ &quot;\qmlproperty &lt;type&gt; &lt;propertyname&gt;&quot; to assign the alias a type.
+
+ */</span>
+ default property <span class="type">alias</span> <span class="name">content</span>: <span class="name">stack</span>.<span class="name">children</span>
+
+ <span class="comment">/*!
+ The currently active tab in the TabWidget.
+ */</span>
+ property <span class="type"><a href="qml-int.html" translate="no">int</a></span> <span class="name">current</span>: <span class="number">0</span>
+
+ <span class="comment">/*!
+ A sample \c{read-only} property.
+ A contrived property to demonstrate QDoc's ability to detect
+ read-only properties.
+
+ The signature is:
+ \code
+ readonly property int sampleReadOnlyProperty: 0
+ \endcode
+
+ Note that the property must be initialized to a value.
+
+ */</span>
+ readonly property <span class="type"><a href="qml-int.html" translate="no">int</a></span> <span class="name">sampleReadOnlyProperty</span>: <span class="number">0</span>
+
+ <span class="comment">/*!
+ \internal
+
+ This handler is an implementation
+ detail. The \c{\internal} command will prevent QDoc from publishing this
+ documentation on the public API.
+ */</span>
+ <span class="name">onCurrentChanged</span>: <span class="name">setOpacities</span>()
+ <span class="name">Component</span>.onCompleted: <span class="name">setOpacities</span>()
+
+ <span class="comment">/*!
+ \internal
+
+ An internal function to set the opacity.
+ The \internal command will prevent QDoc from publishing this
+ documentation on the public API.
+ */</span>
+ <span class="keyword">function </span><span class="name">setOpacities</span>() {
+ <span class="keyword">for</span> (var i = 0; <span class="name">i</span> <span class="operator">&lt;</span> <span class="name">stack</span>.<span class="name">children</span>.<span class="name">length</span>; ++<span class="name">i</span>) {
+ <span class="name">stack</span>.<span class="name">children</span>[<span class="name">i</span>].<span class="name">opacity</span> <span class="operator">=</span> (<span class="name">i</span> <span class="operator">==</span> <span class="name">current</span> ? <span class="number">1</span> : <span class="number">0</span>)
+ }
+ }
+
+ <span class="type">Row</span> {
+ <span class="name">id</span>: <span class="name">header</span>
+
+ <span class="type">Repeater</span> {
+ <span class="name">model</span>: <span class="name">stack</span>.<span class="name">children</span>.<span class="name">length</span>
+ <span class="name">delegate</span>: <span class="name">Rectangle</span> {
+ <span class="name">width</span>: <span class="name">tabWidget</span>.<span class="name">width</span> <span class="operator">/</span> <span class="name">stack</span>.<span class="name">children</span>.<span class="name">length</span>; <span class="name">height</span>: <span class="number">36</span>
+
+ <span class="type">Rectangle</span> {
+ <span class="name">width</span>: <span class="name">parent</span>.<span class="name">width</span>; <span class="name">height</span>: <span class="number">1</span>
+ <span class="type">anchors</span> { <span class="name">bottom</span>: <span class="name">parent</span>.<span class="name">bottom</span>; <span class="name">bottomMargin</span>: <span class="number">1</span> }
+ <span class="name">color</span>: <span class="string">&quot;#acb2c2&quot;</span>
+ }
+ <span class="type">BorderImage</span> {
+ <span class="type">anchors</span> { <span class="name">fill</span>: <span class="name">parent</span>; <span class="name">leftMargin</span>: <span class="number">2</span>; <span class="name">topMargin</span>: <span class="number">5</span>; <span class="name">rightMargin</span>: <span class="number">1</span> }
+ <span class="type">border</span> { <span class="name">left</span>: <span class="number">7</span>; <span class="name">right</span>: <span class="number">7</span> }
+ <span class="name">source</span>: <span class="string">&quot;tab.png&quot;</span>
+ <span class="name">visible</span>: <span class="name">tabWidget</span>.<span class="name">current</span> <span class="operator">==</span> <span class="name">index</span>
+ }
+ <span class="type">Text</span> {
+ <span class="name">horizontalAlignment</span>: <span class="name">Qt</span>.<span class="name">AlignHCenter</span>; <span class="name">verticalAlignment</span>: <span class="name">Qt</span>.<span class="name">AlignVCenter</span>
+ <span class="name">anchors</span>.fill: <span class="name">parent</span>
+ <span class="name">text</span>: <span class="name">stack</span>.<span class="name">children</span>[<span class="name">index</span>].<span class="name">title</span>
+ <span class="name">elide</span>: <span class="name">Text</span>.<span class="name">ElideRight</span>
+ <span class="name">font</span>.bold: <span class="name">tabWidget</span>.<span class="name">current</span> <span class="operator">==</span> <span class="name">index</span>
+ }
+ <span class="type">MouseArea</span> {
+ <span class="name">anchors</span>.fill: <span class="name">parent</span>
+ <span class="name">onClicked</span>: <span class="name">tabWidget</span>.<span class="name">current</span> <span class="operator">=</span> <span class="name">index</span>
+ }
+ }
+ }
+ }
+
+ <span class="type">Item</span> {
+ <span class="name">id</span>: <span class="name">stack</span>
+ <span class="name">width</span>: <span class="name">tabWidget</span>.<span class="name">width</span>
+ <span class="name">anchors</span>.top: <span class="name">header</span>.<span class="name">bottom</span>; <span class="name">anchors</span>.bottom: <span class="name">tabWidget</span>.<span class="name">bottom</span>
+ }
+}</pre>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-demo-cpp.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-demo-cpp.html
new file mode 100644
index 000000000..6e1f68232
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-demo-cpp.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- demo.qdoc -->
+ <title>Demo | Test 6.2.11</title>
+</head>
+<body>
+<h1 class="title">Demo</h1>
+<pre class="cpp" translate="no"><span class="comment">// Copyright (C) 2023 The Qt Company Ltd.</span>
+<span class="comment">// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0</span>
+
+<span class="type">bool</span> isOverThousand(<span class="type">int</span> n)
+{
+ <span class="keyword">if</span> (n <span class="operator">&gt;</span> <span class="number">1'000</span>)
+ <span class="keyword">return</span> <span class="keyword">true</span>;
+ <span class="keyword">return</span> <span class="keyword">false</span>;
+}</pre>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-demo-pro.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-demo-pro.html
new file mode 100644
index 000000000..3d59c65b3
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-demo-pro.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- demo.qdoc -->
+ <title>Demo | Test 6.2.11</title>
+</head>
+<body>
+<h1 class="title">Demo</h1>
+<pre class="cpp" translate="no">TEMPLATE = aux
+message(&quot;Nothing to see here.&quot;)</pre>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-dontxclude-cmakelists-txt.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-dontxclude-cmakelists-txt.html
new file mode 100644
index 000000000..c654a3768
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-dontxclude-cmakelists-txt.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- demo.qdoc -->
+ <title>Demo | Test 6.2.11</title>
+</head>
+<body>
+<h1 class="title">Demo</h1>
+<pre class="cpp" translate="no">cmake_minimum_required(VERSION 3.16)
+project (DONTXCLUDEDIRS_QDOCTEST)</pre>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-example.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-example.html
new file mode 100644
index 000000000..8f7c9ace8
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-demo-example.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- demo.qdoc -->
+ <title>Demo | Test 6.2.11</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Demo</h1>
+<!-- $$$demos/demo-description -->
+<div class="descr" id="details">
+<p class="centerAlign"><img src="images/leonardo-da-vinci.png" alt="" /></p><pre class="cpp" translate="no"> <span class="keyword">if</span> (n <span class="operator">&gt;</span> <span class="number">1'000</span>)
+ <span class="keyword">return</span> <span class="keyword">true</span>;</pre>
+<p>Files:</p>
+<ul>
+<li><a href="test-demos-demo-demo-cpp.html" translate="no">demos/demo/demo.cpp</a></li>
+<li><a href="test-demos-demo-demo-pro.html" translate="no">demos/demo/demo.pro</a></li>
+<li><a href="test-demos-demo-dontxclude-cmakelists-txt.html" translate="no">demos/demo/dontxclude/CMakeLists.txt</a></li>
+</ul>
+</div>
+<!-- @@@demos/demo -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-hidden-example.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-hidden-example.html
new file mode 100644
index 000000000..fc123ba60
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-demos-hidden-example.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- hidden.qdoc -->
+ <meta name="description" content="Tagged 'broken', does not appear in examples-manifest.xml.">
+ <title>Hidden Demo | Test 6.2.11</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Hidden Demo</h1>
+<!-- $$$demos/hidden-brief -->
+<p>Tagged 'broken', does not appear in examples-manifest.xml.</p>
+<!-- @@@demos/hidden -->
+<!-- $$$demos/hidden-description -->
+<div class="descr" id="details">
+<p>Also missing an image, but that's OK as it's broken anyway.</p>
+</div>
+<!-- @@@demos/hidden -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-empty-qmlmodule.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-empty-qmlmodule.html
new file mode 100644
index 000000000..eabda8b7a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-empty-qmlmodule.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- type.cpp -->
+ <meta name="description" content="A QML module with no member types.">
+ <title>No QML Types Here | Test 6.2.11</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">No QML Types Here</h1>
+<!-- $$$Test.Empty-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@Test.Empty -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-nover-qmlmodule.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-nover-qmlmodule.html
new file mode 100644
index 000000000..68cdf82d4
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test-nover-qmlmodule.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- type.cpp -->
+ <meta name="description" content="QML Types for the Test module without version.">
+ <title>Versionless QML Module | Test 6.2.11</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Versionless QML Module</h1>
+<p>This module is in <i>Tech Preview</i> state.</p>
+<p>This module was introduced in Qt 1.1.</p>
+<!-- $$$Test.NoVer-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@Test.NoVer -->
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qml-test-nover-doctest.html">DocTest</a></p></td><td class="tblDescr"><p>Shadows the type name in QDoc.Test module</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="qml-test-nover-typenoversion.html">TypeNoVersion</a></p></td><td class="tblDescr"><p>Another QML type documented in a .cpp file</p></td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test.index
new file mode 100644
index 000000000..55637861e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test.index
@@ -0,0 +1,238 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="A test project for QDoc build artifacts" version="6.2.11" project="Test">
+ <namespace name="" status="active" access="public" module="test">
+ <function name="QDOCTEST_MACRO" href="testqdoc.html#QDOCTEST_MACRO" status="active" access="public" documented="true" related="0" since="Test 0.9" meta="macrowithoutparams" signature="QDOCTEST_MACRO"/>
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ </function>
+ <qmlclass name="AbstractParent" qml-module-name="QDoc.Test" fullname="QDoc.Test.AbstractParent" href="qml-qdoc-test-abstractparent.html" status="active" access="public" abstract="true" location="parent.qdoc" since="1.1" documented="true" title="AbstractParent" fulltitle="AbstractParent" subtitle="" brief="Abstract base QML type">
+ <function name="name" fullname="QDoc.Test.AbstractParent.name" href="qml-qdoc-test-abstractparent.html#name-method" status="active" access="public" location="parent.qdoc" documented="true" meta="qmlmethod" type="void"/>
+ <function name="name" fullname="QDoc.Test.AbstractParent.name" href="qml-qdoc-test-abstractparent.html#name-method" status="active" access="public" location="parent.qdoc" documented="true" meta="qmlmethod" type="void">
+ <parameter type="Child" name="child" default=""/>
+ <parameter type="" name="name" default=""/>
+ </function>
+ <function name="rear" fullname="QDoc.Test.AbstractParent.rear" href="qml-qdoc-test-abstractparent.html#rear-method" status="active" access="public" location="parent.qdoc" documented="true" meta="qmlmethod" type="void">
+ <parameter type="Child" name="child" default=""/>
+ <parameter type="var" name="method" default="Strict"/>
+ </function>
+ <qmlproperty name="children" fullname="QDoc.Test.AbstractParent.children" status="active" access="public" location="parent.qdoc" documented="true" type="list&lt;Child&gt;" attached="false" writable="true" brief="Children of the type"/>
+ <qmlproperty name="name" fullname="QDoc.Test.AbstractParent.name" status="active" access="public" location="parent.qdoc" documented="true" type="string" attached="false" writable="true" brief="Name of this parent"/>
+ </qmlclass>
+ <page name="autolinking.html" href="autolinking.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Autolinking" fulltitle="Autolinking" subtitle="">
+ <contents name="testqdoc" title="TestQDoc" level="1"/>
+ <contents name="someprop" title="someProp" level="1"/>
+ </page>
+ <page name="cmaketest" href="test-cmaketest-example.html" status="active" location="cmaketest.qdoc" documented="true" subtype="example" title="CMake Example Project" fulltitle="CMake Example Project" subtitle="">
+ <page name="cmaketest/main.cpp" href="test-cmaketest-main-cpp.html" status="active" subtype="file" title="" fulltitle="main.cpp Example File" subtitle="cmaketest/main.cpp"/>
+ <page name="cmaketest/CMakeLists.txt" href="test-cmaketest-cmakelists-txt.html" status="active" subtype="file" title="" fulltitle="CMakeLists.txt Example File" subtitle="cmaketest/CMakeLists.txt"/>
+ </page>
+ <qmlclass name="Child" qml-module-name="QDoc.Test" qml-base-type="QDoc.Test::AbstractParent" fullname="QDoc.Test.Child" href="qml-qdoc-test-child.html" status="active" access="public" location="parent.qdoc" since="1.1" documented="true" title="Child" fulltitle="Child" subtitle="" brief="A Child inheriting its parent">
+ <function name="name" fullname="QDoc.Test.Child.name" href="qml-qdoc-test-child.html#name-method" status="active" access="public" location="parent.qdoc" documented="true" meta="qmlmethod" type="void">
+ <parameter type="Child" name="child" default=""/>
+ <parameter type="" name="name" default=""/>
+ </function>
+ <qmlproperty name="name" fullname="QDoc.Test.Child.name" href="qml-qdoc-test-child.html#name-prop" status="active" access="public" location="parent.qdoc" documented="true" type="string" attached="false" writable="true" brief="Name of this child"/>
+ </qmlclass>
+ <page name="classes.html" href="classes.html" status="active" location="unseenclass.qdoc" documented="true" subtype="page" title="Classes" fulltitle="Classes" subtitle=""/>
+ <namespace name="CrossModuleRef" href="crossmoduleref.html" status="active" access="public" location="testcpp.h" since="3.0" documented="true" module="TestCPP" brief="Namespace that has documented functions in multiple modules">
+ <function name="documentMe" fullname="CrossModuleRef::documentMe" href="crossmoduleref.html#documentMe" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void documentMe()"/>
+ </namespace>
+ <page name="demos/demo" href="test-demos-demo-example.html" status="active" location="demo.qdoc" documented="true" subtype="example" title="Demo" fulltitle="Demo" subtitle="">
+ <page name="demos/demo/demo.cpp" href="test-demos-demo-demo-cpp.html" status="active" subtype="file" title="" fulltitle="demo.cpp Example File" subtitle="demos/demo/demo.cpp"/>
+ <page name="demos/demo/demo.pro" href="test-demos-demo-demo-pro.html" status="active" subtype="file" title="" fulltitle="demo.pro Example File" subtitle="demos/demo/demo.pro"/>
+ <page name="demos/demo/dontxclude/CMakeLists.txt" href="test-demos-demo-dontxclude-cmakelists-txt.html" status="active" subtype="file" title="" fulltitle="CMakeLists.txt Example File" subtitle="demos/demo/dontxclude/CMakeLists.txt"/>
+ </page>
+ <qmlclass name="DocTest" qml-module-name="QDoc.Test" fullname="QDoc.Test.DocTest" href="qml-qdoc-test-doctest.html" status="active" access="public" location="DocTest.qml" since="QDoc.Test 0.9" documented="true" title="DocTest" fulltitle="DocTest" subtitle="" brief="Represents a doc test case">
+ <contents name="introduction" title="Introduction" level="1"/>
+ <function name="completed" fullname="QDoc.Test.DocTest.completed" href="qml-qdoc-test-doctest.html#completed-signal" status="active" access="public" documented="true" meta="qmlsignal"/>
+ <function name="fail" fullname="QDoc.Test.DocTest.fail" href="qml-qdoc-test-doctest.html#fail-method" status="active" access="public" location="DocTest.qml" documented="true" since="QDoc.Test 1.0" meta="qmlmethod">
+ <parameter type="" name="message" default="&quot;oops&quot;"/>
+ </function>
+ <function name="fail_hard" fullname="QDoc.Test.DocTest.fail_hard" href="qml-qdoc-test-doctest.html#fail_hard-method" status="active" access="public" documented="true" meta="qmlmethod">
+ <parameter type="" name="msg" default="&quot;facepalm&quot;"/>
+ <parameter type="" name="option" default="123"/>
+ </function>
+ <function name="foo" fullname="QDoc.Test.DocTest.foo" href="qml-qdoc-test-doctest.html#foo-signal" status="active" access="public" location="DocTest.qml" documented="true" meta="qmlsignal">
+ <parameter type="var" name="bar" default=""/>
+ </function>
+ <function name="itsHappening" fullname="QDoc.Test.DocTest.itsHappening" href="qml-qdoc-test-doctest.html#itsHappening-signal" status="active" access="public" documented="true" meta="qmlsignal">
+ <parameter type="bool" name="really" default=""/>
+ </function>
+ <qmlproperty name="active" fullname="QDoc.Test.DocTest.active" href="qml-qdoc-test-doctest.html#active-prop" status="active" access="public" documented="true" type="bool" attached="false" writable="true"/>
+ <qmlproperty name="name" fullname="QDoc.Test.DocTest.name" href="qml-qdoc-test-doctest.html#name-prop" status="active" access="public" documented="true" type="string" attached="false" writable="true" required="true"/>
+ </qmlclass>
+ <qmlclass name="DocTest" qml-module-name="Test.NoVer" fullname="Test.NoVer.DocTest" href="qml-test-nover-doctest.html" status="active" access="public" location="DocTest.qml" since="1.1" documented="true" title="DocTest" fulltitle="DocTest" subtitle="" brief="Shadows the type name in QDoc.Test module"/>
+ <class name="DontLinkToMe" href="dontlinktome.html" status="ignored" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="Class that does not generate documentation"/>
+ <page name="demos/hidden" href="test-demos-hidden-example.html" status="active" location="hidden.qdoc" documented="true" subtype="example" title="Hidden Demo" fulltitle="Hidden Demo" subtitle="" brief="Tagged 'broken', does not appear in examples-manifest.xml"/>
+ <page name="obsolete-classes.html" href="obsolete-classes.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Obsolete Classes" fulltitle="Obsolete Classes" subtitle="">
+ <contents name="classes-with-obsolete-members" title="Classes with obsolete members" level="1"/>
+ <contents name="testqdoc" title="TestQDoc" level="2"/>
+ </page>
+ <qmlclass name="OldType" qml-module-name="QDoc.Test" fullname="QDoc.Test.OldType" href="qml-qdoc-test-oldtype.html" status="deprecated" access="public" since="1.1" documented="true" title="OldType" fulltitle="OldType" subtitle="" brief="Deprecated old type"/>
+ <qmlclass name="ProgressBar" qml-module-name="UIComponents" fullname="UIComponents.ProgressBar" href="qml-uicomponents-progressbar.html" status="active" access="public" location="ProgressBar.qml" documented="true" title="ProgressBar" fulltitle="ProgressBar" subtitle="" brief="A component that shows the progress of an event">
+ <qmlproperty name="color" fullname="UIComponents.ProgressBar.color" href="qml-uicomponents-progressbar.html#color-prop" status="active" access="public" documented="true" type="color" attached="false" writable="true"/>
+ <qmlproperty name="maximum" fullname="UIComponents.ProgressBar.maximum" href="qml-uicomponents-progressbar.html#maximum-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true"/>
+ <qmlproperty name="minimum" fullname="UIComponents.ProgressBar.minimum" href="qml-uicomponents-progressbar.html#minimum-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true"/>
+ <qmlproperty name="secondColor" fullname="UIComponents.ProgressBar.secondColor" href="qml-uicomponents-progressbar.html#secondColor-prop" status="active" access="public" documented="true" type="color" attached="false" writable="true"/>
+ <qmlproperty name="value" fullname="UIComponents.ProgressBar.value" href="qml-uicomponents-progressbar.html#value-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true"/>
+ </qmlclass>
+ <page name="componentset" href="test-componentset-example.html" status="active" location="examples.qdoc" documented="true" subtype="example" title="QML Documentation Example" fulltitle="QML Documentation Example" subtitle="" brief="Example for documenting QML types">
+ <contents name="qml-class" title="QML Class" level="1"/>
+ <contents name="properties-signals-handlers-and-methods" title="Properties, Signals, Handlers, and Methods" level="1"/>
+ <contents name="internal-documentation" title="Internal Documentation" level="2"/>
+ <contents name="qml-types-with-c-implementation" title="QML Types with C++ Implementation" level="1"/>
+ <page name="componentset/ProgressBar.qml" href="test-componentset-progressbar-qml.html" status="active" subtype="file" title="" fulltitle="ProgressBar.qml Example File" subtitle="componentset/ProgressBar.qml"/>
+ <page name="componentset/Switch.qml" href="test-componentset-switch-qml.html" status="active" subtype="file" title="" fulltitle="Switch.qml Example File" subtitle="componentset/Switch.qml"/>
+ <page name="componentset/TabWidget.qml" href="test-componentset-tabwidget-qml.html" status="active" subtype="file" title="" fulltitle="TabWidget.qml Example File" subtitle="componentset/TabWidget.qml"/>
+ <page name="componentset/componentset.qml" href="test-componentset-componentset-qml.html" status="active" subtype="file" title="" fulltitle="componentset.qml Example File" subtitle="componentset/componentset.qml"/>
+ <page name="componentset/componentset.pro" href="test-componentset-componentset-pro.html" status="active" subtype="file" title="" fulltitle="componentset.pro Example File" subtitle="componentset/componentset.pro"/>
+ </page>
+ <page name="qmlmodules.html" href="qmlmodules.html" status="active" location="modules.qdoc" documented="true" subtype="page" title="QML Modules" fulltitle="QML Modules" subtitle="">
+ <contents name="qml-types" title="QML types" level="1"/>
+ <contents name="qml-value-types" title="QML value types" level="1"/>
+ </page>
+ <page name="https://wiki.qt.io/QProperty" href="https://wiki.qt.io/QProperty" status="active" location="properties.qdoc" documented="true" subtype="externalpage" title="QProperty" fulltitle="QProperty" subtitle=""/>
+ <class name="SeenClass" href="seenclass.html" status="active" access="public" location="dont.h" since="2.0" documented="true" module="TestCPP" brief="A public but undocumented class"/>
+ <qmlclass name="Switch" qml-module-name="UIComponents" fullname="UIComponents.Switch" href="qml-uicomponents-switch.html" status="active" access="public" location="Switch.qml" documented="true" title="Switch" fulltitle="Switch" subtitle="" brief="A component that can be turned on or off">
+ <function name="toggle" fullname="UIComponents.Switch.toggle" href="qml-uicomponents-switch.html#toggle-method" status="active" access="public" documented="true" meta="qmlmethod"/>
+ <qmlproperty name="on" fullname="UIComponents.Switch.on" href="qml-uicomponents-switch.html#on-prop" status="active" access="public" documented="true" type="bool" attached="false" writable="true"/>
+ </qmlclass>
+ <qmlclass name="TabWidget" qml-module-name="UIComponents" fullname="UIComponents.TabWidget" href="qml-uicomponents-tabwidget.html" status="active" access="public" location="TabWidget.qml" documented="true" title="TabWidget" fulltitle="TabWidget" subtitle="" brief="A widget that places its children as tabs">
+ <contents name="adding-tabs" title="Adding Tabs" level="1"/>
+ <qmlproperty name="current" fullname="UIComponents.TabWidget.current" href="qml-uicomponents-tabwidget.html#current-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true"/>
+ <qmlproperty name="sampleReadOnlyProperty" fullname="UIComponents.TabWidget.sampleReadOnlyProperty" href="qml-uicomponents-tabwidget.html#sampleReadOnlyProperty-prop" status="active" access="public" documented="true" type="int" attached="false" writable="false"/>
+ </qmlclass>
+ <namespace name="TestQDoc" href="testqdoc.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="A namespace">
+ <contents name="usage" title="Usage" level="1"/>
+ <function name="QDOCTEST_MACRO" href="testqdoc.html#QDOCTEST_MACRO" status="active" access="public" documented="true" related="0" since="Test 0.9" meta="macrowithoutparams" signature="QDOCTEST_MACRO"/>
+ <class threadsafety="reentrant" name="Test" fullname="TestQDoc::Test" href="testqdoc-test.html" status="active" access="public" location="testcpp.h" since="1.1" documented="true" groups="cpptypes,testgroup" module="TestCPP" brief="A class in a namespace">
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ </function>
+ <function name="Test" fullname="TestQDoc::Test::Test" href="testqdoc-test.html#Test" status="active" access="public" location="testcpp.h" documented="true" meta="constructor" signature="Test()"/>
+ <function name="anotherObsoleteMember" fullname="TestQDoc::Test::anotherObsoleteMember" href="testqdoc-test-obsolete.html#anotherObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void anotherObsoleteMember()"/>
+ <function name="deprecatedMember" fullname="TestQDoc::Test::deprecatedMember" href="testqdoc-test-obsolete.html#deprecatedMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void deprecatedMember()"/>
+ <function name="funcPtr" fullname="TestQDoc::Test::funcPtr" href="testqdoc-test.html#funcPtr" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void (*)(bool)" signature="void (*)(bool) funcPtr(bool b, const char *s)">
+ <parameter type="bool" name="b" default=""/>
+ <parameter type="const char *" name="s" default=""/>
+ </function>
+ <function name="inlineFunction" fullname="TestQDoc::Test::inlineFunction" href="testqdoc-test.html#inlineFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" brief="An inline function, documented using the \fn QDoc command" signature="void inlineFunction()"/>
+ <function name="methodWithEmDashInItsDocs" fullname="TestQDoc::Test::methodWithEmDashInItsDocs" href="testqdoc-test.html#methodWithEmDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEmDashInItsDocs()"/>
+ <function name="methodWithEnDashInItsDocs" fullname="TestQDoc::Test::methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEnDashInItsDocs()"/>
+ <function name="obsoleteMember" fullname="TestQDoc::Test::obsoleteMember" href="testqdoc-test-obsolete.html#obsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void obsoleteMember()"/>
+ <function name="operator++" fullname="TestQDoc::Test::operator++" href="testqdoc-test-obsolete.html#operator-2b-2b" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator++()"/>
+ <function name="operator--" fullname="TestQDoc::Test::operator--" href="testqdoc-test-obsolete.html#operator--" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator--()"/>
+ <function name="operator=" fullname="TestQDoc::Test::operator=" href="testqdoc-test.html#operator-eq" status="active" access="public" location="testcpp.h" documented="true" meta="move-assign" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator=(TestQDoc::Test &amp;&amp;other)" groups="testgroup">
+ <parameter type="TestQDoc::Test &amp;&amp;" name="other" default=""/>
+ </function>
+ <function name="operator==" href="testqdoc-test.html#operator-eq-eq" status="active" access="public" location="testcpp.h" documented="true" related="2" meta="plain" type="bool" signature="bool operator==(const TestQDoc::Test &amp;lhs, const TestQDoc::Test &amp;rhs)">
+ <parameter type="const TestQDoc::Test &amp;" name="lhs" default=""/>
+ <parameter type="const TestQDoc::Test &amp;" name="rhs" default=""/>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload" status="active" access="protected" location="testcpp.h" documented="true" meta="plain" type="void" signature="void overload()"/>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload-1" status="active" access="protected" location="testcpp.h" documented="true" since="Test 1.2" meta="plain" overload="true" overload-number="1" type="void" signature="void overload(bool b)">
+ <parameter type="bool" name="b" default=""/>
+ </function>
+ <function name="someFunction" fullname="TestQDoc::Test::someFunction" href="testqdoc-test.html#someFunction" status="active" access="public" location="testcpp.h" documented="true" since="Test 1.0" meta="plain" type="int" signature="int someFunction(int, int v)">
+ <parameter type="int" name="" default=""/>
+ <parameter type="int" name="v" default="0"/>
+ </function>
+ <function name="someFunctionDefaultArg" fullname="TestQDoc::Test::someFunctionDefaultArg" href="testqdoc-test.html#someFunctionDefaultArg" threadsafety="non-reentrant" status="active" access="public" location="testcpp.h" documented="true" since="2.0" meta="plain" const="true" type="void" signature="void someFunctionDefaultArg(int i, bool b) const" groups="testgroup">
+ <parameter type="int" name="i" default=""/>
+ <parameter type="bool" name="b" default="false"/>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::Test::virtualFun" href="testqdoc-test.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" type="void" signature="void virtualFun()"/>
+ <typedef name="SomeType" fullname="TestQDoc::Test::SomeType" href="testqdoc-test.html#SomeType-typedef" status="active" access="public" location="testcpp.h" documented="true"/>
+ </class>
+ <class name="TestDerived" fullname="TestQDoc::TestDerived" href="testqdoc-testderived.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" bases="TestQDoc::Test" module="TestCPP" brief="A class in a namespace, derived from Test">
+ <function name="bindableProp" fullname="TestQDoc::TestDerived::bindableProp" href="testqdoc-testderived.html#bindableProp-prop" status="active" access="public" location="testcpp.h" meta="plain" associated-property="bindableProp" type="QBindable&lt;QString&gt;" signature="QBindable&lt;QString&gt; bindableProp()"/>
+ <function name="bindablePropChanged" fullname="TestQDoc::TestDerived::bindablePropChanged" href="testqdoc-testderived.html#bindableProp-prop" status="active" access="public" location="testcpp.h" meta="signal" associated-property="bindableProp" type="void" signature="void bindablePropChanged()"/>
+ <function name="boolProp" fullname="TestQDoc::TestDerived::boolProp" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" meta="plain" associated-property="boolProp" type="bool" signature="bool boolProp()"/>
+ <function name="boolPropChanged" fullname="TestQDoc::TestDerived::boolPropChanged" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" meta="signal" associated-property="boolProp" type="void" signature="void boolPropChanged()"/>
+ <function name="emitSomething" fullname="TestQDoc::TestDerived::emitSomething" href="testqdoc-testderived.html#emitSomething" status="active" access="public" location="testcpp.h" documented="true" meta="signal" type="void" signature="void emitSomething()"/>
+ <function name="getInt" fullname="TestQDoc::TestDerived::getInt" href="testqdoc-testderived.html#intProp-prop" status="active" access="public" location="testcpp.h" meta="plain" associated-property="intProp" type="int *" signature="int * getInt()"/>
+ <function name="id" fullname="TestQDoc::TestDerived::id" href="testqdoc-testderived.html#id" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" override="true" type="int" signature="int id() override"/>
+ <function name="invokeMe" fullname="TestQDoc::TestDerived::invokeMe" href="testqdoc-testderived.html#invokeMe" status="active" access="public" location="testcpp.h" documented="true" meta="plain" const="true" type="void" brief="Something invokable" signature="void invokeMe() const"/>
+ <function name="name" fullname="TestQDoc::TestDerived::name" href="testqdoc-testderived.html#name-prop" status="active" access="public" location="testcpp.h" meta="plain" const="true" associated-property="name" type="const QString *" signature="const QString * name() const"/>
+ <function name="resetBoolProp" fullname="TestQDoc::TestDerived::resetBoolProp" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" meta="slot" associated-property="boolProp" type="void" signature="void resetBoolProp()"/>
+ <function name="setBindableProp" fullname="TestQDoc::TestDerived::setBindableProp" href="testqdoc-testderived.html#bindableProp-prop" status="active" access="public" location="testcpp.h" meta="slot" associated-property="bindableProp" type="void" signature="void setBindableProp(const QString &amp;s)">
+ <parameter type="const QString &amp;" name="s" default=""/>
+ </function>
+ <function name="setBoolProp" fullname="TestQDoc::TestDerived::setBoolProp" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" meta="slot" associated-property="boolProp" type="void" signature="void setBoolProp(bool b)">
+ <parameter type="bool" name="b" default=""/>
+ </function>
+ <function name="someProp" fullname="TestQDoc::TestDerived::someProp" href="testqdoc-testderived.html#someProp-prop" status="active" access="public" location="testcpp.h" meta="plain" associated-property="someProp" type="const QString &amp;" signature="const QString &amp; someProp()"/>
+ <function name="someValue" fullname="TestQDoc::TestDerived::someValue" href="testqdoc-testderived.html#someValue" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::TestDerived::NotTypedef" signature="TestQDoc::TestDerived::NotTypedef someValue()"/>
+ <function name="staticObsoleteMember" fullname="TestQDoc::TestDerived::staticObsoleteMember" href="testqdoc-testderived-obsolete.html#staticObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" static="true" type="void" signature="void staticObsoleteMember()"/>
+ <function name="virtualFun" fullname="TestQDoc::TestDerived::virtualFun" href="testqdoc-testderived.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" override="true" type="void" signature="void virtualFun() override"/>
+ <typedef name="DerivedType" fullname="TestQDoc::TestDerived::DerivedType" href="testqdoc-testderived.html#DerivedType-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="Test::SomeType"/>
+ <typedef name="NotTypedef" fullname="TestQDoc::TestDerived::NotTypedef" href="testqdoc-testderived.html#NotTypedef-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="int"/>
+ <property name="bindableProp" fullname="TestQDoc::TestDerived::bindableProp" href="testqdoc-testderived.html#bindableProp-prop" status="active" access="public" location="testcpp.h" documented="true" bindable="true">
+ <getter name="bindableProp"/>
+ <setter name="setBindableProp"/>
+ <notifier name="bindablePropChanged"/>
+ </property>
+ <property name="boolProp" fullname="TestQDoc::TestDerived::boolProp" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" documented="true">
+ <getter name="boolProp"/>
+ <setter name="setBoolProp"/>
+ <resetter name="resetBoolProp"/>
+ <notifier name="boolPropChanged"/>
+ </property>
+ <property name="intProp" fullname="TestQDoc::TestDerived::intProp" href="testqdoc-testderived.html#intProp-prop" status="active" access="public" location="testcpp.h" documented="true" writable="false">
+ <getter name="getInt"/>
+ </property>
+ <property name="name" fullname="TestQDoc::TestDerived::name" href="testqdoc-testderived.html#name-prop" status="active" access="public" location="testcpp.h" documented="true" writable="false" brief="This property holds a name">
+ <getter name="name"/>
+ </property>
+ <property name="someProp" fullname="TestQDoc::TestDerived::someProp" href="testqdoc-testderived.html#someProp-prop" status="active" access="public" location="testcpp.h" documented="true" bindable="true" writable="false">
+ <getter name="someProp"/>
+ </property>
+ </class>
+ </namespace>
+ <qmlclass name="TheType" qml-module-name="TheModule" fullname="TheModule.TheType" href="qml-themodule-thetype.html" status="active" access="public" location="properties.qdoc" documented="true" title="TheType" fulltitle="TheType" subtitle="">
+ <qmlproperty name="name" fullname="TheModule.TheType.name" href="qml-themodule-thetype.html#name-prop" status="active" access="public" location="properties.qdoc" documented="true" type="string" attached="false" writable="false" brief="Read-only status of this property is resolved from Q_PROPERTY"/>
+ </qmlclass>
+ <qmlclass name="Type" qml-module-name="QDoc.Test" fullname="QDoc.Test.Type" href="qml-qdoc-test-type.html" status="active" access="public" since="1.1" documented="true" title="Type" fulltitle="Type" subtitle="" brief="A QML type documented in a .cpp file">
+ <function name="completed" fullname="QDoc.Test.Type.completed" href="qml-qdoc-test-type.html#completed-signal" status="active" access="public" documented="true" meta="qmlsignal">
+ <parameter type="int" name="status" default=""/>
+ </function>
+ <function name="configured" fullname="QDoc.Test.Type.configured" href="qml-qdoc-test-type.html#configured-signal" status="active" access="public" documented="true" meta="qmlsignal"/>
+ <function name="copy" fullname="QDoc.Test.Type.copy" href="qml-qdoc-test-type.html#copy-method" status="active" access="public" documented="true" meta="qmlmethod" type="Type">
+ <parameter type="" name="a" default=""/>
+ </function>
+ <function name="deprecatedMethod" fullname="QDoc.Test.Type.deprecatedMethod" href="qml-qdoc-test-type-obsolete.html#deprecatedMethod-method" status="deprecated" access="public" documented="true" meta="qmlmethod"/>
+ <function name="disable" fullname="QDoc.Test.Type.disable" href="qml-qdoc-test-type.html#disable-method" status="active" access="public" documented="true" meta="qmlmethod"/>
+ <function name="enable" fullname="QDoc.Test.Type.enable" href="qml-qdoc-test-type.html#enable-method" status="active" access="public" documented="true" meta="qmlmethod"/>
+ <function name="futureDeprecated" fullname="QDoc.Test.Type.futureDeprecated" href="qml-qdoc-test-type.html#futureDeprecated-method" status="active" access="public" documented="true" meta="qmlmethod"/>
+ <function name="group.created" fullname="QDoc.Test.Type.group.created" href="qml-qdoc-test-type.html#group.created-signal" status="active" access="public" documented="true" meta="qmlsignal"/>
+ <qmlproperty name="fifth" fullname="QDoc.Test.Type.fifth" href="qml-qdoc-test-type.html#fifth-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true" brief="A group of properties sharing a documentation comment"/>
+ <qmlproperty name="fourth" fullname="QDoc.Test.Type.fourth" href="qml-qdoc-test-type.html#fourth-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true" brief="A group of properties sharing a documentation comment"/>
+ <qmlproperty name="group.first" fullname="QDoc.Test.Type.group.first" href="qml-qdoc-test-type.html#group.first-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true" brief="A property group"/>
+ <qmlproperty name="group.second" fullname="QDoc.Test.Type.group.second" href="qml-qdoc-test-type.html#group.second-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true" brief="A property group"/>
+ <qmlproperty name="group.third" fullname="QDoc.Test.Type.group.third" href="qml-qdoc-test-type.html#group.third-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true" brief="A property group"/>
+ <qmlproperty name="id" fullname="QDoc.Test.Type.id" href="qml-qdoc-test-type.html#id-prop" status="active" access="public" documented="true" type="int" attached="false" writable="false" brief="A read-only property"/>
+ <qmlproperty name="name" fullname="QDoc.Test.Type.name" href="qml-qdoc-test-type.html#name-prop" status="active" access="public" documented="true" type="string" attached="false" writable="true" required="true" brief="Name of the Test"/>
+ <qmlproperty name="type" fullname="QDoc.Test.Type.type" href="qml-qdoc-test-type.html#type-attached-prop" status="active" access="public" documented="true" type="enumeration" attached="true" writable="true"/>
+ <qmlproperty name="group" fullname="QDoc.Test.Type.group" href="qml-qdoc-test-type.html#group-prop" status="active" access="public" documented="true"/>
+ </qmlclass>
+ <qmlclass name="TypeNoVersion" qml-module-name="Test.NoVer" fullname="Test.NoVer.TypeNoVersion" href="qml-test-nover-typenoversion.html" status="active" access="public" since="1.1" documented="true" title="TypeNoVersion" fulltitle="TypeNoVersion" subtitle="" brief="Another QML type documented in a .cpp file"/>
+ <class name="UnseenClass" href="unseenclass.html" status="ignored" access="public" location="dont.h" since="2.0" documented="true" module="TestCPP" brief="A public but undocumented class"/>
+ <qmlclass name="YetAnotherChild" qml-module-name="QDoc.Test" qml-base-type="QDoc.Test::InternParent" fullname="QDoc.Test.YetAnotherChild" href="qml-qdoc-test-yetanotherchild.html" status="active" access="public" location="parent.qdoc" since="1.1" documented="true" title="YetAnotherChild" fulltitle="YetAnotherChild" subtitle="" brief="A type inheriting from internal abstract parent"/>
+ <qmlvaluetype name="int" qml-module-name="QDoc.Test" fullname="QDoc.Test.int" href="qml-int.html" status="active" access="public" location="parent.qdoc" since="1.1" documented="true" title="int" fulltitle="int" subtitle="" brief="An integer value type">
+ <function name="abs" fullname="QDoc.Test.int.abs" href="qml-int.html#abs-method" status="active" access="public" location="parent.qdoc" documented="true" meta="qmlmethod" type="int"/>
+ </qmlvaluetype>
+ <page name="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command" href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command" status="active" location="classlists.qdoc" documented="true" subtype="externalpage" title="reentrant" fulltitle="reentrant" subtitle=""/>
+ <group name="cpptypes" href="cpptypes.html" status="active" location="classlists.qdoc" documented="true" seen="true" title="Test C++ Types"/>
+ <group name="testgroup" href="testgroup.html" status="internal" seen="false" title=""/>
+ <module name="TestCPP" href="testcpp-module.html" status="active" since="2.0" documented="true" seen="true" title="QDoc Test C++ Classes" brief="A test module page">
+ <contents name="linking-to-function-like-things" title="Linking to function-like things" level="1"/>
+ <contents name="section" title="section()" level="2"/>
+ </module>
+ <qmlmodule name="QDoc.Test" qml-module-name="QDoc.Test" qml-module-version="1.1" href="qdoc-test-qmlmodule.html" status="preliminary" since="1.1" documented="true" seen="true" title="QDoc.Test QML Module" brief="QML Types for the Test module"/>
+ <qmlmodule name="Test.Empty" qml-module-name="Test.Empty" qml-module-version="1.0" href="test-empty-qmlmodule.html" status="active" documented="true" seen="true" title="No QML Types Here" brief="A QML module with no member types"/>
+ <qmlmodule name="Test.NoVer" qml-module-name="Test.NoVer" href="test-nover-qmlmodule.html" status="active" since="1.1" documented="true" seen="true" title="Versionless QML Module" brief="QML Types for the Test module without version"/>
+ <qmlmodule name="TheModule" qml-module-name="TheModule" href="themodule-qmlmodule.html" status="active" location="properties.qdoc" documented="true" seen="true" title=""/>
+ <qmlmodule name="UIComponents" qml-module-name="UIComponents" qml-module-version="1.0" href="uicomponents-qmlmodule.html" status="active" location="examples.qdoc" documented="true" seen="true" title="UI Components" brief="Basic set of UI components"/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test.qhp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test.qhp
new file mode 100644
index 000000000..ba54e85ea
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/test.qhp
@@ -0,0 +1,260 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<QtHelpProject version="1.0">
+ <namespace>org.qt-project.test.001</namespace>
+ <virtualFolder>test</virtualFolder>
+ <metaData name="version" value="6.2.11"/>
+ <filterSection>
+ <toc>
+ <section ref="uicomponents-qmlmodule.html" title="UI Components">
+ <section ref="uicomponents-qmlmodule.html" title="Test">
+ <section ref="themodule-qmlmodule.html" title=""/>
+ <section ref="autolinking.html" title="Autolinking"/>
+ <section ref="test-cmaketest-example.html" title="CMake Example Project"/>
+ <section ref="classes.html" title="Classes"/>
+ <section ref="test-demos-demo-example.html" title="Demo"/>
+ <section ref="test-demos-hidden-example.html" title="Hidden Demo"/>
+ <section ref="test-empty-qmlmodule.html" title="No QML Types Here"/>
+ <section ref="obsolete-classes.html" title="Obsolete Classes"/>
+ <section ref="testcpp-module.html" title="QDoc Test C++ Classes"/>
+ <section ref="qdoc-test-qmlmodule.html" title="QDoc.Test QML Module"/>
+ <section ref="test-componentset-example.html" title="QML Documentation Example"/>
+ <section ref="qmlmodules.html" title="QML Modules"/>
+ <section ref="uicomponents-qmlmodule.html" title="UI Components"/>
+ <section ref="test-nover-qmlmodule.html" title="Versionless QML Module"/>
+ </section>
+ <section ref="testcpp-module.html" title="Classes">
+ <section ref="crossmoduleref.html" title="CrossModuleRef"/>
+ <section ref="seenclass.html" title="SeenClass Class Reference"/>
+ <section ref="testqdoc.html" title="TestQDoc"/>
+ <section ref="testqdoc-test.html" title="TestQDoc::Test Class Reference">
+ <section ref="testqdoc-test-members.html" title="List of all members"/>
+ <section ref="testqdoc-test-obsolete.html" title="Obsolete members"/>
+ </section>
+ <section ref="testqdoc-testderived.html" title="TestQDoc::TestDerived Class Reference">
+ <section ref="testqdoc-testderived-members.html" title="List of all members"/>
+ <section ref="testqdoc-testderived-obsolete.html" title="Obsolete members"/>
+ </section>
+ </section>
+ <section ref="uicomponents-qmlmodule.html" title="QML Types">
+ <section ref="qml-qdoc-test-abstractparent.html" title="AbstractParent Type Reference">
+ <section ref="qml-qdoc-test-abstractparent-members.html" title="List of all members"/>
+ </section>
+ <section ref="qml-qdoc-test-child.html" title="Child Type Reference">
+ <section ref="qml-qdoc-test-child-members.html" title="List of all members"/>
+ </section>
+ <section ref="qml-qdoc-test-doctest.html" title="DocTest Type Reference">
+ <section ref="qml-qdoc-test-doctest-members.html" title="List of all members"/>
+ </section>
+ <section ref="qml-qdoc-test-oldtype.html" title="OldType Type Reference">
+ <section ref="qml-qdoc-test-oldtype-members.html" title="List of all members"/>
+ </section>
+ <section ref="qml-qdoc-test-type.html" title="Type Type Reference">
+ <section ref="qml-qdoc-test-type-members.html" title="List of all members"/>
+ <section ref="qml-qdoc-test-type-obsolete.html" title="Obsolete members"/>
+ </section>
+ <section ref="qml-qdoc-test-yetanotherchild.html" title="YetAnotherChild Type Reference">
+ <section ref="qml-qdoc-test-yetanotherchild-members.html" title="List of all members"/>
+ </section>
+ <section ref="qml-int.html" title="int Type Reference"/>
+ <section ref="qml-uicomponents-progressbar.html" title="ProgressBar Type Reference">
+ <section ref="qml-uicomponents-progressbar-members.html" title="List of all members"/>
+ </section>
+ <section ref="qml-uicomponents-switch.html" title="Switch Type Reference">
+ <section ref="qml-uicomponents-switch-members.html" title="List of all members"/>
+ </section>
+ <section ref="qml-uicomponents-tabwidget.html" title="TabWidget Type Reference">
+ <section ref="qml-uicomponents-tabwidget-members.html" title="List of all members"/>
+ </section>
+ </section>
+ </section>
+ </toc>
+ <keywords>
+ <keyword name="AbstractParent" id="QML.AbstractParent" ref="qml-qdoc-test-abstractparent.html"/>
+ <keyword name="AbstractParent" id="QML.QDoc.Test1.AbstractParent" ref="qml-qdoc-test-abstractparent.html"/>
+ <keyword name="Autolinking" id="Autolinking" ref="autolinking.html"/>
+ <keyword name="Child" id="QML.Child" ref="qml-qdoc-test-child.html"/>
+ <keyword name="Child" id="QML.QDoc.Test1.Child" ref="qml-qdoc-test-child.html"/>
+ <keyword name="Classes" id="Classes" ref="classes.html"/>
+ <keyword name="CrossModuleRef" id="CrossModuleRef" ref="crossmoduleref.html"/>
+ <keyword name="DocTest" id="QML.DocTest" ref="qml-qdoc-test-doctest.html"/>
+ <keyword name="DocTest" id="QML.QDoc.Test1.DocTest" ref="qml-qdoc-test-doctest.html"/>
+ <keyword name="DocTest" id="QML.DocTest" ref="qml-test-nover-doctest.html"/>
+ <keyword name="DocTest" id="QML.Test.NoVer.DocTest" ref="qml-test-nover-doctest.html"/>
+ <keyword name="Obsolete Classes" id="Obsolete Classes" ref="obsolete-classes.html"/>
+ <keyword name="OldType" id="QML.OldType" ref="qml-qdoc-test-oldtype.html"/>
+ <keyword name="OldType" id="QML.QDoc.Test1.OldType" ref="qml-qdoc-test-oldtype.html"/>
+ <keyword name="ProgressBar" id="QML.ProgressBar" ref="qml-uicomponents-progressbar.html"/>
+ <keyword name="ProgressBar" id="QML.UIComponents1.ProgressBar" ref="qml-uicomponents-progressbar.html"/>
+ <keyword name="QDOCTEST_MACRO" id="QDOCTEST_MACRO" ref="testqdoc.html#QDOCTEST_MACRO"/>
+ <keyword name="QDOCTEST_MACRO2" id="QDOCTEST_MACRO2" ref="testqdoc-test.html#QDOCTEST_MACRO2"/>
+ <keyword name="QDoc Test C++ Classes" id="QDoc Test C++ Classes" ref="testcpp-module.html"/>
+ <keyword name="QDoc.Test" id="QML.Test.QDoc" ref="qdoc-test-qmlmodule.html"/>
+ <keyword name="QML Modules" id="QML Modules" ref="qmlmodules.html"/>
+ <keyword name="SeenClass" id="SeenClass" ref="seenclass.html"/>
+ <keyword name="Switch" id="QML.Switch" ref="qml-uicomponents-switch.html"/>
+ <keyword name="Switch" id="QML.UIComponents1.Switch" ref="qml-uicomponents-switch.html"/>
+ <keyword name="TabWidget" id="QML.TabWidget" ref="qml-uicomponents-tabwidget.html"/>
+ <keyword name="TabWidget" id="QML.UIComponents1.TabWidget" ref="qml-uicomponents-tabwidget.html"/>
+ <keyword name="Test" id="TestQDoc::Test" ref="testqdoc-test.html"/>
+ <keyword name="Test C++ Types" id="Test C++ Types" ref="cpptypes.html"/>
+ <keyword name="Test.Empty" id="QML.Empty.Test" ref="test-empty-qmlmodule.html"/>
+ <keyword name="Test.NoVer" id="QML.NoVer.Test" ref="test-nover-qmlmodule.html"/>
+ <keyword name="Test::SomeType" id="Test::SomeType" ref="testqdoc-test.html#SomeType-typedef"/>
+ <keyword name="TestDerived" id="TestQDoc::TestDerived" ref="testqdoc-testderived.html"/>
+ <keyword name="TestDerived::DerivedType" id="TestDerived::DerivedType" ref="testqdoc-testderived.html#DerivedType-typedef"/>
+ <keyword name="TestDerived::NotTypedef" id="TestDerived::NotTypedef" ref="testqdoc-testderived.html#NotTypedef-typedef"/>
+ <keyword name="TestQDoc" id="TestQDoc" ref="testqdoc.html"/>
+ <keyword name="TheType" id="QML.TheType" ref="qml-themodule-thetype.html"/>
+ <keyword name="TheType" id="QML.TheModule.TheType" ref="qml-themodule-thetype.html"/>
+ <keyword name="Type" id="QML.Type" ref="qml-qdoc-test-type.html"/>
+ <keyword name="Type" id="QML.QDoc.Test1.Type" ref="qml-qdoc-test-type.html"/>
+ <keyword name="TypeNoVersion" id="QML.TypeNoVersion" ref="qml-test-nover-typenoversion.html"/>
+ <keyword name="TypeNoVersion" id="QML.Test.NoVer.TypeNoVersion" ref="qml-test-nover-typenoversion.html"/>
+ <keyword name="UIComponents" id="QML.UIComponents" ref="uicomponents-qmlmodule.html"/>
+ <keyword name="YetAnotherChild" id="QML.YetAnotherChild" ref="qml-qdoc-test-yetanotherchild.html"/>
+ <keyword name="YetAnotherChild" id="QML.QDoc.Test1.YetAnotherChild" ref="qml-qdoc-test-yetanotherchild.html"/>
+ <keyword name="abs" id="int::abs" ref="qml-int.html#abs-method"/>
+ <keyword name="active" id="DocTest::active" ref="qml-qdoc-test-doctest.html#active-prop"/>
+ <keyword name="anotherObsoleteMember" id="Test::anotherObsoleteMember" ref="testqdoc-test-obsolete.html#anotherObsoleteMember"/>
+ <keyword name="bindableProp" id="TestDerived::bindableProp" ref="testqdoc-testderived.html#bindableProp-prop"/>
+ <keyword name="bindableProp" id="TestDerived::bindableProp" ref="testqdoc-testderived.html#bindableProp-prop"/>
+ <keyword name="bindablePropChanged" id="TestDerived::bindablePropChanged" ref="testqdoc-testderived.html#bindableProp-prop"/>
+ <keyword name="boolProp" id="TestDerived::boolProp" ref="testqdoc-testderived.html#boolProp-prop"/>
+ <keyword name="boolProp" id="TestDerived::boolProp" ref="testqdoc-testderived.html#boolProp-prop"/>
+ <keyword name="boolPropChanged" id="TestDerived::boolPropChanged" ref="testqdoc-testderived.html#boolProp-prop"/>
+ <keyword name="children" id="AbstractParent::children" ref="qml-qdoc-test-abstractparent.html#children-prop"/>
+ <keyword name="color" id="ProgressBar::color" ref="qml-uicomponents-progressbar.html#color-prop"/>
+ <keyword name="completed" id="DocTest::completed" ref="qml-qdoc-test-doctest.html#completed-signal"/>
+ <keyword name="completed" id="Type::completed" ref="qml-qdoc-test-type.html#completed-signal"/>
+ <keyword name="configured" id="Type::configured" ref="qml-qdoc-test-type.html#configured-signal"/>
+ <keyword name="copy" id="Type::copy" ref="qml-qdoc-test-type.html#copy-method"/>
+ <keyword name="current" id="TabWidget::current" ref="qml-uicomponents-tabwidget.html#current-prop"/>
+ <keyword name="deprecatedMember" id="Test::deprecatedMember" ref="testqdoc-test-obsolete.html#deprecatedMember"/>
+ <keyword name="deprecatedMethod" id="Type::deprecatedMethod" ref="qml-qdoc-test-type-obsolete.html#deprecatedMethod-method"/>
+ <keyword name="disable" id="Type::disable" ref="qml-qdoc-test-type.html#disable-method"/>
+ <keyword name="documentMe" id="CrossModuleRef::documentMe" ref="crossmoduleref.html#documentMe"/>
+ <keyword name="emitSomething" id="TestDerived::emitSomething" ref="testqdoc-testderived.html#emitSomething"/>
+ <keyword name="enable" id="Type::enable" ref="qml-qdoc-test-type.html#enable-method"/>
+ <keyword name="fail" id="DocTest::fail" ref="qml-qdoc-test-doctest.html#fail-method"/>
+ <keyword name="fail_hard" id="DocTest::fail_hard" ref="qml-qdoc-test-doctest.html#fail_hard-method"/>
+ <keyword name="fifth" id="Type::fifth" ref="qml-qdoc-test-type.html#fifth-prop"/>
+ <keyword name="foo" id="DocTest::foo" ref="qml-qdoc-test-doctest.html#foo-signal"/>
+ <keyword name="fourth" id="Type::fourth" ref="qml-qdoc-test-type.html#fourth-prop"/>
+ <keyword name="funcPtr" id="Test::funcPtr" ref="testqdoc-test.html#funcPtr"/>
+ <keyword name="futureDeprecated" id="Type::futureDeprecated" ref="qml-qdoc-test-type.html#futureDeprecated-method"/>
+ <keyword name="getInt" id="TestDerived::getInt" ref="testqdoc-testderived.html#intProp-prop"/>
+ <keyword name="group.created" id="Type::group.created" ref="qml-qdoc-test-type.html#group.created-signal"/>
+ <keyword name="group.first" id="Type::group.first" ref="qml-qdoc-test-type.html#group.first-prop"/>
+ <keyword name="group.second" id="Type::group.second" ref="qml-qdoc-test-type.html#group.second-prop"/>
+ <keyword name="group.third" id="Type::group.third" ref="qml-qdoc-test-type.html#group.third-prop"/>
+ <keyword name="id" id="Type::id" ref="qml-qdoc-test-type.html#id-prop"/>
+ <keyword name="id" id="TestDerived::id" ref="testqdoc-testderived.html#id"/>
+ <keyword name="inlineFunction" id="Test::inlineFunction" ref="testqdoc-test.html#inlineFunction"/>
+ <keyword name="int" id="QML.int" ref="qml-int.html"/>
+ <keyword name="int" id="QML.QDoc.Test1.int" ref="qml-int.html"/>
+ <keyword name="intProp" id="TestDerived::intProp" ref="testqdoc-testderived.html#intProp-prop"/>
+ <keyword name="invokeMe" id="TestDerived::invokeMe" ref="testqdoc-testderived.html#invokeMe"/>
+ <keyword name="itsHappening" id="DocTest::itsHappening" ref="qml-qdoc-test-doctest.html#itsHappening-signal"/>
+ <keyword name="maximum" id="ProgressBar::maximum" ref="qml-uicomponents-progressbar.html#maximum-prop"/>
+ <keyword name="methodWithEmDashInItsDocs" id="Test::methodWithEmDashInItsDocs" ref="testqdoc-test.html#methodWithEmDashInItsDocs"/>
+ <keyword name="methodWithEnDashInItsDocs" id="Test::methodWithEnDashInItsDocs" ref="testqdoc-test.html#methodWithEnDashInItsDocs"/>
+ <keyword name="minimum" id="ProgressBar::minimum" ref="qml-uicomponents-progressbar.html#minimum-prop"/>
+ <keyword name="name" id="AbstractParent::name" ref="qml-qdoc-test-abstractparent.html#name-method"/>
+ <keyword name="name" id="AbstractParent::name" ref="qml-qdoc-test-abstractparent.html#name-prop"/>
+ <keyword name="name" id="Child::name" ref="qml-qdoc-test-child.html#name-method"/>
+ <keyword name="name" id="Child::name" ref="qml-qdoc-test-child.html#name-prop"/>
+ <keyword name="name" id="DocTest::name" ref="qml-qdoc-test-doctest.html#name-prop"/>
+ <keyword name="name" id="Type::name" ref="qml-qdoc-test-type.html#name-prop"/>
+ <keyword name="name" id="TheType::name" ref="qml-themodule-thetype.html#name-prop"/>
+ <keyword name="name" id="TestDerived::name" ref="testqdoc-testderived.html#name-prop"/>
+ <keyword name="name" id="TestDerived::name" ref="testqdoc-testderived.html#name-prop"/>
+ <keyword name="obsoleteMember" id="Test::obsoleteMember" ref="testqdoc-test-obsolete.html#obsoleteMember"/>
+ <keyword name="on" id="Switch::on" ref="qml-uicomponents-switch.html#on-prop"/>
+ <keyword name="operator++" id="Test::operator++" ref="testqdoc-test-obsolete.html#operator-2b-2b"/>
+ <keyword name="operator--" id="Test::operator--" ref="testqdoc-test-obsolete.html#operator--"/>
+ <keyword name="operator=" id="Test::operator=" ref="testqdoc-test.html#operator-eq"/>
+ <keyword name="operator==" id="operator==" ref="testqdoc-test.html#operator-eq-eq"/>
+ <keyword name="overload" id="Test::overload" ref="testqdoc-test.html#overload"/>
+ <keyword name="rear" id="AbstractParent::rear" ref="qml-qdoc-test-abstractparent.html#rear-method"/>
+ <keyword name="resetBoolProp" id="TestDerived::resetBoolProp" ref="testqdoc-testderived.html#boolProp-prop"/>
+ <keyword name="sampleReadOnlyProperty" id="TabWidget::sampleReadOnlyProperty" ref="qml-uicomponents-tabwidget.html#sampleReadOnlyProperty-prop"/>
+ <keyword name="secondColor" id="ProgressBar::secondColor" ref="qml-uicomponents-progressbar.html#secondColor-prop"/>
+ <keyword name="setBindableProp" id="TestDerived::setBindableProp" ref="testqdoc-testderived.html#bindableProp-prop"/>
+ <keyword name="setBoolProp" id="TestDerived::setBoolProp" ref="testqdoc-testderived.html#boolProp-prop"/>
+ <keyword name="someFunction" id="Test::someFunction" ref="testqdoc-test.html#someFunction"/>
+ <keyword name="someFunctionDefaultArg" id="Test::someFunctionDefaultArg" ref="testqdoc-test.html#someFunctionDefaultArg"/>
+ <keyword name="someProp" id="TestDerived::someProp" ref="testqdoc-testderived.html#someProp-prop"/>
+ <keyword name="someProp" id="TestDerived::someProp" ref="testqdoc-testderived.html#someProp-prop"/>
+ <keyword name="someValue" id="TestDerived::someValue" ref="testqdoc-testderived.html#someValue"/>
+ <keyword name="staticObsoleteMember" id="TestDerived::staticObsoleteMember" ref="testqdoc-testderived-obsolete.html#staticObsoleteMember"/>
+ <keyword name="toggle" id="Switch::toggle" ref="qml-uicomponents-switch.html#toggle-method"/>
+ <keyword name="type" id="Type::type" ref="qml-qdoc-test-type.html#type-attached-prop"/>
+ <keyword name="value" id="ProgressBar::value" ref="qml-uicomponents-progressbar.html#value-prop"/>
+ <keyword name="virtualFun" id="Test::virtualFun" ref="testqdoc-test.html#virtualFun"/>
+ <keyword name="virtualFun" id="TestDerived::virtualFun" ref="testqdoc-testderived.html#virtualFun"/>
+ </keywords>
+ <files>
+ <file>autolinking.html</file>
+ <file>classes.html</file>
+ <file>cpptypes.html</file>
+ <file>crossmoduleref.html</file>
+ <file>images/leonardo-da-vinci.png</file>
+ <file>obsolete-classes.html</file>
+ <file>qdoc-test-qmlmodule.html</file>
+ <file>qml-int.html</file>
+ <file>qml-qdoc-test-abstractparent-members.html</file>
+ <file>qml-qdoc-test-abstractparent.html</file>
+ <file>qml-qdoc-test-child-members.html</file>
+ <file>qml-qdoc-test-child.html</file>
+ <file>qml-qdoc-test-doctest-members.html</file>
+ <file>qml-qdoc-test-doctest.html</file>
+ <file>qml-qdoc-test-oldtype-members.html</file>
+ <file>qml-qdoc-test-oldtype.html</file>
+ <file>qml-qdoc-test-type-members.html</file>
+ <file>qml-qdoc-test-type-obsolete.html</file>
+ <file>qml-qdoc-test-type.html</file>
+ <file>qml-qdoc-test-yetanotherchild-members.html</file>
+ <file>qml-qdoc-test-yetanotherchild.html</file>
+ <file>qml-test-nover-doctest-members.html</file>
+ <file>qml-test-nover-doctest.html</file>
+ <file>qml-test-nover-typenoversion-members.html</file>
+ <file>qml-test-nover-typenoversion.html</file>
+ <file>qml-themodule-thetype-members.html</file>
+ <file>qml-themodule-thetype.html</file>
+ <file>qml-uicomponents-progressbar-members.html</file>
+ <file>qml-uicomponents-progressbar.html</file>
+ <file>qml-uicomponents-switch-members.html</file>
+ <file>qml-uicomponents-switch.html</file>
+ <file>qml-uicomponents-tabwidget-members.html</file>
+ <file>qml-uicomponents-tabwidget.html</file>
+ <file>qmlmodules.html</file>
+ <file>seenclass.html</file>
+ <file>test-cmaketest-cmakelists-txt.html</file>
+ <file>test-cmaketest-example.html</file>
+ <file>test-cmaketest-main-cpp.html</file>
+ <file>test-componentset-componentset-pro.html</file>
+ <file>test-componentset-componentset-qml.html</file>
+ <file>test-componentset-example.html</file>
+ <file>test-componentset-progressbar-qml.html</file>
+ <file>test-componentset-switch-qml.html</file>
+ <file>test-componentset-tabwidget-qml.html</file>
+ <file>test-demos-demo-demo-cpp.html</file>
+ <file>test-demos-demo-demo-pro.html</file>
+ <file>test-demos-demo-dontxclude-cmakelists-txt.html</file>
+ <file>test-demos-demo-example.html</file>
+ <file>test-demos-hidden-example.html</file>
+ <file>test-empty-qmlmodule.html</file>
+ <file>test-nover-qmlmodule.html</file>
+ <file>testcpp-module.html</file>
+ <file>testqdoc-test-members.html</file>
+ <file>testqdoc-test-obsolete.html</file>
+ <file>testqdoc-test.html</file>
+ <file>testqdoc-testderived-members.html</file>
+ <file>testqdoc-testderived-obsolete.html</file>
+ <file>testqdoc-testderived.html</file>
+ <file>testqdoc.html</file>
+ <file>themodule-qmlmodule.html</file>
+ <file>uicomponents-qmlmodule.html</file>
+ </files>
+ </filterSection>
+</QtHelpProject>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testcpp-module.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testcpp-module.html
new file mode 100644
index 000000000..828bfd508
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testcpp-module.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A test module page.">
+ <title>QDoc Test C++ Classes | Test 6.2.11</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#namespaces">Namespaces</a></li>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#linking-to-function-like-things">Linking to function-like things</a></li>
+<li class="level3"><a href="#section">section()</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">QDoc Test C++ Classes</h1>
+<!-- $$$TestCPP-brief -->
+<p>A test module page. <a href="#details">More...</a></p>
+<!-- @@@TestCPP -->
+<p>This module was introduced in Qt 2.0.</p>
+<h2 id="namespaces">Namespaces</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="crossmoduleref.html">CrossModuleRef</a></p></td><td class="tblDescr"><p>Namespace that has documented functions in multiple modules</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc.html">TestQDoc</a></p></td><td class="tblDescr"><p>A namespace</p></td></tr>
+</table></div>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="seenclass.html">SeenClass</a></p></td><td class="tblDescr"><p>A public but undocumented class</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html">TestQDoc::Test</a></p></td><td class="tblDescr"><p>A class in a namespace</p></td></tr>
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-testderived.html">TestQDoc::TestDerived</a></p></td><td class="tblDescr"><p>A class in a namespace, derived from Test</p></td></tr>
+</table></div>
+<!-- $$$TestCPP-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<div class="admonition note">
+<p><b>Note: </b>This is just a test. /* Look, Ma! {I'm made of arguments!} */</p>
+</div>
+<p><b>This module was introduced in version 5.15.</b></p>
+<p>1.0</p>
+<h3 id="linking-to-function-like-things">Linking to function-like things</h3>
+<ul>
+<li><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a>()</li>
+<li><a href="testqdoc-test.html#QDOCTEST_MACRO2" translate="no">QDOCTEST_MACRO2</a>()</li>
+<li><a href="testqdoc-test.html#QDOCTEST_MACRO2" translate="no">QDOCTEST_MACRO2</a>(int &amp;x)</li>
+<li><a href="testcpp-module.html#section" translate="no">section()</a></li>
+<li><a href="testcpp-module.html#section" translate="no">section() is a section title</a></li>
+<li><a href="testqdoc-test.html#Test" translate="no">open( parenthesis</a></li>
+<li><a href="https://en.cppreference.com/w/cpp/utility/move">C++11 added std::move(T&amp;&amp; t)</a></li>
+</ul>
+<h4 id="section">section()</h4>
+</div>
+<!-- @@@TestCPP -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-test-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-test-members.html
new file mode 100644
index 000000000..3b34571c7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-test-members.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace.">
+ <title>List of All Members for Test | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Test</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for Test</h1>
+<p>This is the complete list of members for <a href="testqdoc-test.html">TestQDoc::Test</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#SomeType-typedef" translate="no">SomeType</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#Test" translate="no">Test</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#funcPtr" translate="no">funcPtr</a></b></span>(bool, const char *) : void (*)(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#inlineFunction" translate="no">inlineFunction</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEmDashInItsDocs" translate="no">methodWithEmDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload" translate="no">overload</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload-1" translate="no">overload</a></b></span>(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a></b></span>(int, int) : int</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a></b></span>(int, bool) const</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#virtualFun" translate="no">virtualFun</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#operator-eq" translate="no">operator=</a></b></span>(TestQDoc::Test &amp;&amp;) : TestQDoc::Test &amp;</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-test-obsolete.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-test-obsolete.html
new file mode 100644
index 000000000..81cc19f7d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-test-obsolete.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace.">
+ <title>Obsolete Members for Test | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Test</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Obsolete Members for Test</h1>
+<p><b>The following members of class <a href="testqdoc-test.html" translate="no">Test</a> are deprecated.</b> They are provided to keep old source code working. We strongly advise against using them in new code.</p>
+<h2>Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#anotherObsoleteMember" translate="no">anotherObsoleteMember</a></b>()</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated in 6.0)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#deprecatedMember" translate="no">deprecatedMember</a></b>()</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#obsoleteMember" translate="no">obsoleteMember</a></b>()</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> TestQDoc::Test &amp;</td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#operator-2b-2b" translate="no">operator++</a></b>()</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> TestQDoc::Test &amp;</td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#operator--" translate="no">operator--</a></b>()</td></tr>
+</table></div>
+<h2>Member Function Documentation</h2>
+<!-- $$$ -->
+<div class="fngroup">
+<h3 class="fn fngroupitem" translate="no" id="operator-2b-2b"><code class="details extra" translate="no">[deprecated]</code> <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;Test::<span class="name">operator++</span>()</h3><h3 class="fn fngroupitem" translate="no" id="operator--"><code class="details extra" translate="no">[deprecated]</code> <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;Test::<span class="name">operator--</span>()</h3></div>
+<p>This function is deprecated. We strongly advise against using it in new code.</p>
+<!-- @@@ -->
+<!-- $$$anotherObsoleteMember[overload1]$$$anotherObsoleteMember -->
+<h3 class="fn" translate="no" id="anotherObsoleteMember"><code class="details extra" translate="no">[deprecated]</code> <span class="type">void</span> Test::<span class="name">anotherObsoleteMember</span>()</h3>
+<p>This function is deprecated. We strongly advise against using it in new code.</p>
+<p>Use <a href="testqdoc-test-obsolete.html#obsoleteMember" translate="no">obsoleteMember</a>() instead.</p>
+<!-- @@@anotherObsoleteMember -->
+<!-- $$$deprecatedMember[overload1]$$$deprecatedMember -->
+<h3 class="fn" translate="no" id="deprecatedMember"><code class="details extra" translate="no">[deprecated in 6.0]</code> <span class="type">void</span> Test::<span class="name">deprecatedMember</span>()</h3>
+<p>This function is deprecated since 6.0. We strongly advise against using it in new code.</p>
+<p>Use <a href="testqdoc-test.html#someFunction" translate="no">someFunction</a>() instead.</p>
+<!-- @@@deprecatedMember -->
+<!-- $$$obsoleteMember[overload1]$$$obsoleteMember -->
+<h3 class="fn" translate="no" id="obsoleteMember"><code class="details extra" translate="no">[deprecated]</code> <span class="type">void</span> Test::<span class="name">obsoleteMember</span>()</h3>
+<p>This function is deprecated. We strongly advise against using it in new code.</p>
+<p>Use <a href="testqdoc-test.html#someFunction" translate="no">someFunction</a>() instead.</p>
+<!-- @@@obsoleteMember -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-test.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-test.html
new file mode 100644
index 000000000..25e1966aa
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-test.html
@@ -0,0 +1,165 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace.">
+ <title>Test Class | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Test</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#public-types">Public Types</a></li>
+<li class="level1"><a href="#public-functions">Public Functions</a></li>
+<li class="level1"><a href="#protected-functions">Protected Functions</a></li>
+<li class="level1"><a href="#related-non-members">Related Non-Members</a></li>
+<li class="level1"><a href="#macros">Macros</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Test Class</h1>
+<span class="small-subtitle" translate="no">class <a href="testqdoc.html" translate="no">TestQDoc</a>::Test</span>
+<!-- $$$Test-brief -->
+<p>A class in a namespace. <a href="#details">More...</a></p>
+<!-- @@@Test -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Test&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 1.1</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> In QML:</td><td class="memItemRight bottomAlign"> <a href="qml-qdoc-test-type.html" translate="no">Type</a></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Inherited By:</td><td class="memItemRight bottomAlign"> <p><a href="testqdoc-testderived.html" translate="no">TestQDoc::TestDerived</a></p>
+</td></tr>
+</table></div>
+<ul>
+<li><a href="testqdoc-test-members.html">List of all members, including inherited members</a></li>
+<li><a href="testqdoc-test-obsolete.html">Deprecated members</a></li>
+<li>Test is part of <a href="cpptypes.html">Test C++ Types</a>.</li>
+</ul>
+<p><b>Note:</b> All functions in this class are <a href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</a> with the following exceptions:</p>
+<ul>
+<li><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a>(int i, bool b) const</li>
+</ul>
+<h2 id="public-types">Public Types</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#SomeType-typedef" translate="no">SomeType</a></b></td></tr>
+</table></div>
+<h2 id="public-functions">Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#Test" translate="no">Test</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void (*)(bool) </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#funcPtr" translate="no">funcPtr</a></b>(bool <i>b</i>, const char *<i>s</i>)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#inlineFunction" translate="no">inlineFunction</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#methodWithEmDashInItsDocs" translate="no">methodWithEmDashInItsDocs</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since Test 1.0)</code> int </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a></b>(int, int <i>v</i> = 0)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since 2.0)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a></b>(int <i>i</i>, bool <i>b</i> = false) const</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> virtual void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#virtualFun" translate="no">virtualFun</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> TestQDoc::Test &amp;</td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#operator-eq" translate="no">operator=</a></b>(TestQDoc::Test &amp;&amp;<i>other</i>)</td></tr>
+</table></div>
+<h2 id="protected-functions">Protected Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#overload" translate="no">overload</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since Test 1.2)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#overload-1" translate="no">overload</a></b>(bool <i>b</i>)</td></tr>
+</table></div>
+<h2 id="related-non-members">Related Non-Members</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> bool </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#operator-eq-eq" translate="no">operator==</a></b>(const TestQDoc::Test &amp;<i>lhs</i>, const TestQDoc::Test &amp;<i>rhs</i>)</td></tr>
+</table></div>
+<h2 id="macros">Macros</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since Test 1.1)</code> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#QDOCTEST_MACRO2" translate="no">QDOCTEST_MACRO2</a></b>(int &amp;<i>x</i>)</td></tr>
+</table></div>
+<!-- $$$Test-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Test -->
+<div class="types">
+<h2>Member Type Documentation</h2>
+<!-- $$$SomeType -->
+<h3 class="fn" translate="no" id="SomeType-typedef">Test::<span class="name">SomeType</span></h3>
+<p>A typedef.</p>
+<!-- @@@SomeType -->
+</div>
+<div class="func">
+<h2>Member Function Documentation</h2>
+<!-- $$$ -->
+<div class="fngroup">
+<h3 class="fn fngroupitem" translate="no" id="overload"><code class="details extra" translate="no">[protected]</code> <span class="type">void</span> Test::<span class="name">overload</span>()</h3><h3 class="fn fngroupitem" translate="no" id="overload-1"><code class="details extra" translate="no">[protected, since Test 1.2]</code> <span class="type">void</span> Test::<span class="name">overload</span>(<span class="type">bool</span> <i>b</i>)</h3></div>
+<p>Overloads that share a documentation comment, optionally taking a parameter <i translate="no">b</i>.</p>
+<!-- @@@ -->
+<!-- $$$Test[overload1]$$$Test -->
+<h3 class="fn" translate="no" id="Test">Test::<span class="name">Test</span>()</h3>
+<p>The constructor is deleted.</p>
+<!-- @@@Test -->
+<!-- $$$funcPtr[overload1]$$$funcPtrboolconstchar* -->
+<h3 class="fn" translate="no" id="funcPtr"><span class="type">void</span> (*)(<span class="type">bool</span>) Test::<span class="name">funcPtr</span>(<span class="type">bool</span> <i>b</i>, const <span class="type">char</span> *<i>s</i>)</h3>
+<p>Returns a pointer to a function that takes a boolean. Uses <i translate="no">b</i> and <i translate="no">s</i>.</p>
+<!-- @@@funcPtr -->
+<!-- $$$inlineFunction[overload1]$$$inlineFunction -->
+<h3 class="fn" translate="no" id="inlineFunction"><span class="type">void</span> Test::<span class="name">inlineFunction</span>()</h3>
+<p>An inline function, documented using the \fn QDoc command.</p>
+<!-- @@@inlineFunction -->
+<!-- $$$methodWithEmDashInItsDocs[overload1]$$$methodWithEmDashInItsDocs -->
+<h3 class="fn" translate="no" id="methodWithEmDashInItsDocs"><span class="type">void</span> Test::<span class="name">methodWithEmDashInItsDocs</span>()</h3>
+<p>This method has em dashes in its documentation&mdash;as you'll find represented by <code translate="no">---</code> in the sources&mdash;here and there. The important bit to note is that when passed e.g. to the \c command, the three hyphens are processed as input to the command and not replaced by an em dash.</p>
+<p>-----------------------------------------------------------------------</p>
+<p>People can still add a bunch of dashes, though, without QDoc replacing them all with a series of em dashes.</p>
+<p>&mdash;You can also start a new paragraph with an em dash, if you want to.</p>
+<p><b>See also </b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a>.</p>
+<!-- @@@methodWithEmDashInItsDocs -->
+<!-- $$$methodWithEnDashInItsDocs[overload1]$$$methodWithEnDashInItsDocs -->
+<h3 class="fn" translate="no" id="methodWithEnDashInItsDocs"><span class="type">void</span> Test::<span class="name">methodWithEnDashInItsDocs</span>()</h3>
+<p>This method has en dashes in its documentation &ndash; as you'll find represented by <code translate="no">--</code> in the sources &ndash; here and there. The important bit to note is that when passed e.g. to the \c command, the two hyphens are processed as input to the command and not replaced by an en dash. This also applies to code blocks, where otherwise, the decrement operator would get completely borked:</p>
+<pre class="cpp" translate="no"><span class="keyword">for</span> (<span class="type">int</span> i <span class="operator">=</span> <span class="number">42</span>; i <span class="operator">&gt;</span> <span class="number">0</span>; <span class="operator">-</span><span class="operator">-</span>i)
+ <span class="comment">// Do something cool during countdown.</span></pre>
+<p>...as it would be silly if this would output &ndash;i instead of <code translate="no">--i</code>.</p>
+<p>-----------------------------------------------------------------------</p>
+<p>It still allows people to add a bunch of dashes, though, without replacing them all with a series of en dashes. Of course, they might want to use the \hr command instead, like this:</p>
+<hr />
+<p>&ndash; You can also start a new paragraph with an en dash, if you want to.</p>
+<p><b>See also </b>methodWithEnDashInItsDocs.</p>
+<!-- @@@methodWithEnDashInItsDocs -->
+<!-- $$$someFunction[overload1]$$$someFunctionintint -->
+<h3 class="fn" translate="no" id="someFunction"><code class="details extra" translate="no">[since Test 1.0]</code> <span class="type">int</span> Test::<span class="name">someFunction</span>(<span class="type">int</span>, <span class="type">int</span> <i>v</i> = 0)</h3>
+<p>Function that takes a parameter <i translate="no">v</i>. Also returns the value of <i translate="no">v</i>.</p>
+<p>This function was introduced in Test 1.0.</p>
+<!-- @@@someFunction -->
+<!-- $$$someFunctionDefaultArg[overload1]$$$someFunctionDefaultArgintbool -->
+<h3 class="fn" translate="no" id="someFunctionDefaultArg"><code class="details extra" translate="no">[since 2.0]</code> <span class="type">void</span> Test::<span class="name">someFunctionDefaultArg</span>(<span class="type">int</span> <i>i</i>, <span class="type">bool</span> <i>b</i> = false) const</h3>
+<p>Function that takes a parameter <i translate="no">i</i> and <i translate="no">b</i>.</p>
+<p><b>Warning:</b> This function is not <a href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</a>.</p>
+<p>This function was introduced in Qt 2.0.</p>
+<!-- @@@someFunctionDefaultArg -->
+<!-- $$$virtualFun[overload1]$$$virtualFun -->
+<h3 class="fn" translate="no" id="virtualFun"><code class="details extra" translate="no">[virtual]</code> <span class="type">void</span> Test::<span class="name">virtualFun</span>()</h3>
+<p>Function that must be reimplemented.</p>
+<!-- @@@virtualFun -->
+<!-- $$$operator=[overload1]$$$operator=TestQDoc::Test&& -->
+<h3 class="fn" translate="no" id="operator-eq"><span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;Test::<span class="name">operator=</span>(<span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;&amp;<i>other</i>)</h3>
+<p>The move assignment operator is deleted. <i translate="no">other</i> cannot be moved from.</p>
+<!-- @@@operator= -->
+</div>
+<div class="relnonmem">
+<h2>Related Non-Members</h2>
+<!-- $$$operator==[overload1]$$$operator==constTestQDoc::Test&constTestQDoc::Test& -->
+<h3 class="fn" translate="no" id="operator-eq-eq"><span class="type">bool</span> <span class="name">operator==</span>(const <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;<i>lhs</i>, const <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;<i>rhs</i>)</h3>
+<p>Returns true if <i translate="no">lhs</i> and <i translate="no">rhs</i> are equal.</p>
+<!-- @@@operator== -->
+</div>
+<div class="macros">
+<h2>Macro Documentation</h2>
+<!-- $$$QDOCTEST_MACRO2[overload1]$$$QDOCTEST_MACRO2int& -->
+<h3 class="fn" translate="no" id="QDOCTEST_MACRO2"><code class="details extra" translate="no">[since Test 1.1]</code> <span class="name">QDOCTEST_MACRO2</span>(<span class="type">int</span> &amp;<i>x</i>)</h3>
+<p>A macro with argument <i translate="no">x</i>.</p>
+<p>This macro was introduced in Test 1.1.</p>
+<!-- @@@QDOCTEST_MACRO2 -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-testderived-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-testderived-members.html
new file mode 100644
index 000000000..f263a0b7b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-testderived-members.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace, derived from Test.">
+ <title>List of All Members for TestDerived | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>TestDerived</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for TestDerived</h1>
+<p>This is the complete list of members for <a href="testqdoc-testderived.html">TestQDoc::TestDerived</a>, including inherited members.</p>
+<div class="table"><table class="propsummary" translate="no">
+<tr><td class="topAlign"><ul>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#DerivedType-typedef" translate="no">DerivedType</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#NotTypedef-typedef" translate="no">NotTypedef</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#SomeType-typedef" translate="no">SomeType</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#bindableProp-prop" translate="no">bindableProp</a></b></span>() : QBindable&lt;QString&gt;</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#bindableProp-prop" translate="no">bindablePropChanged</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#boolProp-prop" translate="no">boolProp</a></b></span>() : bool</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#boolProp-prop" translate="no">boolPropChanged</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#emitSomething" translate="no">emitSomething</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#funcPtr" translate="no">funcPtr</a></b></span>(bool, const char *) : void (*)(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#intProp-prop" translate="no">getInt</a></b></span>() : int *</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#id" translate="no">id</a></b></span>() : int</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#inlineFunction" translate="no">inlineFunction</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#invokeMe" translate="no">invokeMe</a></b></span>() const</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEmDashInItsDocs" translate="no">methodWithEmDashInItsDocs</a></b></span>()</li>
+</ul></td><td class="topAlign"><ul>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#name-prop" translate="no">name</a></b></span>() const : const QString *</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload" translate="no">overload</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload-1" translate="no">overload</a></b></span>(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#boolProp-prop" translate="no">resetBoolProp</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#bindableProp-prop" translate="no">setBindableProp</a></b></span>(const QString &amp;)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#boolProp-prop" translate="no">setBoolProp</a></b></span>(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a></b></span>(int, int) : int</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a></b></span>(int, bool) const</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#someProp-prop" translate="no">someProp</a></b></span>() : const QString &amp;</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#someValue" translate="no">someValue</a></b></span>() : TestQDoc::TestDerived::NotTypedef</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#virtualFun" translate="no">virtualFun</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#virtualFun" translate="no">virtualFun</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#operator-eq" translate="no">operator=</a></b></span>(TestQDoc::Test &amp;&amp;) : TestQDoc::Test &amp;</li>
+</ul>
+</td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-testderived-obsolete.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-testderived-obsolete.html
new file mode 100644
index 000000000..e293b8595
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-testderived-obsolete.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace, derived from Test.">
+ <title>Obsolete Members for TestDerived | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>TestDerived</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Obsolete Members for TestDerived</h1>
+<p><b>The following members of class <a href="testqdoc-testderived.html" translate="no">TestDerived</a> are deprecated.</b> They are provided to keep old source code working. We strongly advise against using them in new code.</p>
+<h2>Static Public Members</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived-obsolete.html#staticObsoleteMember" translate="no">staticObsoleteMember</a></b>()</td></tr>
+</table></div>
+<h2>Member Function Documentation</h2>
+<!-- $$$staticObsoleteMember[overload1]$$$staticObsoleteMember -->
+<h3 class="fn" translate="no" id="staticObsoleteMember"><code class="details extra" translate="no">[static, deprecated]</code> <span class="type">void</span> TestDerived::<span class="name">staticObsoleteMember</span>()</h3>
+<p>This function is deprecated. We strongly advise against using it in new code.</p>
+<p>Static obsolete method.</p>
+<!-- @@@staticObsoleteMember -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-testderived.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-testderived.html
new file mode 100644
index 000000000..8c1c895e3
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc-testderived.html
@@ -0,0 +1,173 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace, derived from Test.">
+ <title>TestDerived Class | Test 6.2.11</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>TestDerived</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#public-types">Public Types</a></li>
+<li class="level1"><a href="#properties">Properties</a></li>
+<li class="level1"><a href="#public-functions">Public Functions</a></li>
+<li class="level1"><a href="#reimplemented-public-functions">Reimplemented Public Functions</a></li>
+<li class="level1"><a href="#public-slots">Public Slots</a></li>
+<li class="level1"><a href="#signals">Signals</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">TestDerived Class</h1>
+<span class="small-subtitle" translate="no">class <a href="testqdoc.html" translate="no">TestQDoc</a>::TestDerived</span>
+<!-- $$$TestDerived-brief -->
+<p>A class in a namespace, derived from <a href="testqdoc-test.html" translate="no">Test</a>. <a href="#details">More...</a></p>
+<!-- @@@TestDerived -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;TestDerived&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> In QML:</td><td class="memItemRight bottomAlign"> <a href="qml-themodule-thetype.html" translate="no">TheType</a></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Inherits:</td><td class="memItemRight bottomAlign"> <a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></td></tr>
+</table></div>
+<ul>
+<li><a href="testqdoc-testderived-members.html">List of all members, including inherited members</a></li>
+<li><a href="testqdoc-testderived-obsolete.html">Deprecated members</a></li>
+</ul>
+<h2 id="public-types">Public Types</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#DerivedType-typedef" translate="no">DerivedType</a></b></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#NotTypedef-typedef" translate="no">NotTypedef</a></b></td></tr>
+</table></div>
+<h2 id="properties">Properties</h2>
+<div class="table"><table class="propsummary" translate="no">
+<tr><td class="topAlign"><ul>
+<li class="fn" translate="no"><b><a href="testqdoc-testderived.html#bindableProp-prop" translate="no">bindableProp</a></b> : QString</li>
+<li class="fn" translate="no"><b><a href="testqdoc-testderived.html#boolProp-prop" translate="no">boolProp</a></b> : bool</li>
+<li class="fn" translate="no"><b><a href="testqdoc-testderived.html#intProp-prop" translate="no">intProp</a></b> : int* const</li>
+</ul></td><td class="topAlign"><ul>
+<li class="fn" translate="no"><b><a href="testqdoc-testderived.html#name-prop" translate="no">name</a></b> : const QString*</li>
+<li class="fn" translate="no"><b><a href="testqdoc-testderived.html#someProp-prop" translate="no">someProp</a></b> : QString</li>
+</ul>
+</td></tr>
+</table></div>
+<h2 id="public-functions">Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> QBindable&lt;QString&gt; </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#bindableProp-prop" translate="no">bindableProp</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> bool </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#boolProp-prop" translate="no">boolProp</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> int *</td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#intProp-prop" translate="no">getInt</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#invokeMe" translate="no">invokeMe</a></b>() const</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> const QString *</td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#name-prop" translate="no">name</a></b>() const</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> const QString &amp;</td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#someProp-prop" translate="no">someProp</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> TestQDoc::TestDerived::NotTypedef </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#someValue" translate="no">someValue</a></b>()</td></tr>
+</table></div>
+<h2 id="reimplemented-public-functions">Reimplemented Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> virtual int </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#id" translate="no">id</a></b>() override</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> virtual void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#virtualFun" translate="no">virtualFun</a></b>() override</td></tr>
+</table></div>
+<h2 id="public-slots">Public Slots</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#boolProp-prop" translate="no">resetBoolProp</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#bindableProp-prop" translate="no">setBindableProp</a></b>(const QString &amp;<i>s</i>)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#boolProp-prop" translate="no">setBoolProp</a></b>(bool <i>b</i>)</td></tr>
+</table></div>
+<h2 id="signals">Signals</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#bindableProp-prop" translate="no">bindablePropChanged</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#boolProp-prop" translate="no">boolPropChanged</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#emitSomething" translate="no">emitSomething</a></b>()</td></tr>
+</table></div>
+<!-- $$$TestDerived-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@TestDerived -->
+<div class="types">
+<h2>Member Type Documentation</h2>
+<!-- $$$DerivedType -->
+<h3 class="fn" translate="no" id="DerivedType-typedef"><code class="details extra" translate="no">[alias]</code> TestDerived::<span class="name">DerivedType</span></h3>
+<p>An aliased typedef.</p>
+<!-- @@@DerivedType -->
+<!-- $$$NotTypedef -->
+<h3 class="fn" translate="no" id="NotTypedef-typedef"><code class="details extra" translate="no">[alias]</code> TestDerived::<span class="name">NotTypedef</span></h3>
+<p>I'm an alias, not a typedef.</p>
+<!-- @@@NotTypedef -->
+</div>
+<div class="prop">
+<h2>Property Documentation</h2>
+<!-- $$$bindableProp-prop$$$bindableProp$$$setBindablePropconstQString&$$$bindablePropChanged -->
+<h3 class="fn" translate="no" id="bindableProp-prop"><code class="details extra" translate="no">[bindable]</code> <span class="name">bindableProp</span> : <span class="type">QString</span></h3>
+<div class="admonition note"><p><b>Note: </b>This property supports <a href="https://wiki.qt.io/QProperty">QProperty</a> bindings.</p>
+</div><p>Some property.</p>
+<p><b>See also </b><a href="testqdoc-testderived.html#someProp-prop" translate="no">someProp</a>.</p>
+<!-- @@@bindableProp -->
+<!-- $$$boolProp-prop$$$boolProp$$$setBoolPropbool$$$resetBoolProp$$$boolPropChanged -->
+<h3 class="fn" translate="no" id="boolProp-prop"><span class="name">boolProp</span> : <span class="type">bool</span></h3>
+<p>A boolean property.</p>
+<p><b>Access functions:</b></p>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft topAlign rightAlign"> bool </td><td class="memItemRight bottomAlign"><span class="name"><b>boolProp</b></span>()</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> void </td><td class="memItemRight bottomAlign"><span class="name"><b>setBoolProp</b></span>(bool <i>b</i>)</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> void </td><td class="memItemRight bottomAlign"><span class="name"><b>resetBoolProp</b></span>()</td></tr>
+</table></div>
+<p><b>Notifier signal:</b></p>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft topAlign rightAlign"> void </td><td class="memItemRight bottomAlign"><span class="name"><b>boolPropChanged</b></span>()</td></tr>
+</table></div>
+<!-- @@@boolProp -->
+<!-- $$$intProp-prop$$$getInt -->
+<h3 class="fn" translate="no" id="intProp-prop"><code class="details extra" translate="no">[read-only]</code> <span class="name">intProp</span> : <span class="type">int</span>* const</h3>
+<p>An integer property.</p>
+<p><b>Access functions:</b></p>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft topAlign rightAlign"> int *</td><td class="memItemRight bottomAlign"><span class="name"><b>getInt</b></span>()</td></tr>
+</table></div>
+<!-- @@@intProp -->
+<!-- $$$name-prop$$$name -->
+<h3 class="fn" translate="no" id="name-prop"><code class="details extra" translate="no">[read-only]</code> <span class="name">name</span> : const <span class="type">QString</span>*</h3>
+<p>This property holds a name.</p>
+<p><b>Access functions:</b></p>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft topAlign rightAlign"> const QString *</td><td class="memItemRight bottomAlign"><span class="name"><b>name</b></span>() const</td></tr>
+</table></div>
+<!-- @@@name -->
+<!-- $$$someProp-prop$$$someProp -->
+<h3 class="fn" translate="no" id="someProp-prop"><code class="details extra" translate="no">[bindable read-only]</code> <span class="name">someProp</span> : <span class="type">QString</span></h3>
+<div class="admonition note"><p><b>Note: </b>This property supports <a href="https://wiki.qt.io/QProperty">QProperty</a> bindings.</p>
+</div><p>Another property.</p>
+<!-- @@@someProp -->
+</div>
+<div class="func">
+<h2>Member Function Documentation</h2>
+<!-- $$$emitSomething[overload1]$$$emitSomething -->
+<h3 class="fn" translate="no" id="emitSomething"><code class="details extra" translate="no">[private signal]</code> <span class="type">void</span> TestDerived::<span class="name">emitSomething</span>()</h3>
+<p>Emitted when things happen.</p>
+<div class="admonition note"><p><b>Note: </b>This is a private signal. It can be used in signal connections but cannot be emitted by the user.</p>
+</div><!-- @@@emitSomething -->
+<!-- $$$id[overload1]$$$id -->
+<h3 class="fn" translate="no" id="id"><code class="details extra" translate="no">[override virtual]</code> <span class="type">int</span> TestDerived::<span class="name">id</span>()</h3>
+<!-- @@@id -->
+<!-- $$$invokeMe[overload1]$$$invokeMe -->
+<h3 class="fn" translate="no" id="invokeMe"><code class="details extra" translate="no">[invokable]</code> <span class="type">void</span> TestDerived::<span class="name">invokeMe</span>() const</h3>
+<p>Something invokable.</p>
+<div class="admonition note"><p><b>Note: </b>This function can be invoked via the meta-object system and from QML. See Q_INVOKABLE.</p>
+</div><!-- @@@invokeMe -->
+<!-- $$$someValue[overload1]$$$someValue -->
+<h3 class="fn" translate="no" id="someValue"><span class="type"><a href="testqdoc-testderived.html#NotTypedef-typedef" translate="no">TestQDoc::TestDerived::NotTypedef</a></span> TestDerived::<span class="name">someValue</span>()</h3>
+<p>Returns a value using an aliases type.</p>
+<!-- @@@someValue -->
+<!-- $$$virtualFun[overload1]$$$virtualFun -->
+<h3 class="fn" translate="no" id="virtualFun"><code class="details extra" translate="no">[override virtual]</code> <span class="type">void</span> TestDerived::<span class="name">virtualFun</span>()</h3>
+<p>Reimplements: <a href="testqdoc-test.html#virtualFun" translate="no">Test::virtualFun</a>().</p>
+<!-- @@@virtualFun -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc.html
new file mode 100644
index 000000000..7ccc7f4dd
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testqdoc.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A namespace.">
+ <title>TestQDoc Namespace | Test 6.2.11</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#macros">Macros</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#usage">Usage</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">TestQDoc Namespace</h1>
+<!-- $$$TestQDoc-brief -->
+<p>A namespace. <a href="#details">More...</a></p>
+<!-- @@@TestQDoc -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;TestCPP&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+</table></div>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since 1.1)</code> class </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html" translate="no">Test</a></b></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since 2.0)</code> class </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html" translate="no">TestDerived</a></b></td></tr>
+</table></div>
+<h2 id="macros">Macros</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since Test 0.9)</code> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc.html#QDOCTEST_MACRO" translate="no">QDOCTEST_MACRO</a></b></td></tr>
+</table></div>
+<!-- $$$TestQDoc-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<h3 id="usage">Usage</h3>
+<p>This namespace is for testing QDoc output.</p>
+</div>
+<!-- @@@TestQDoc -->
+<div class="classes">
+<h2>Classes</h2>
+<h3> class <a href="testqdoc-test.html">Test</a></h3><!-- $$$Test-brief -->
+<p>A class in a namespace. <a href="testqdoc-test.html#details">More...</a></p>
+<!-- @@@Test -->
+<h3> class <a href="testqdoc-testderived.html">TestDerived</a></h3><!-- $$$TestDerived-brief -->
+<p>A class in a namespace, derived from <a href="testqdoc-test.html" translate="no">Test</a>. <a href="testqdoc-testderived.html#details">More...</a></p>
+<!-- @@@TestDerived -->
+</div>
+<div class="macros">
+<h2>Macro Documentation</h2>
+<!-- $$$QDOCTEST_MACRO[overload1]$$$QDOCTEST_MACRO -->
+<h3 class="fn" translate="no" id="QDOCTEST_MACRO"><code class="details extra" translate="no">[since Test 0.9]</code> <span class="name">QDOCTEST_MACRO</span></h3>
+<p>This macro was introduced in Test 0.9.</p>
+<!-- @@@QDOCTEST_MACRO -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testtagfile.tags b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testtagfile.tags
new file mode 100644
index 000000000..f68dffed6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/testtagfile.tags
@@ -0,0 +1,500 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<tagfile>
+ <compound kind="class">
+ <name>QDoc.Test.AbstractParent</name>
+ <filename>qml-qdoc-test-abstractparent.html</filename>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>name</name>
+ <anchorfile>qml-qdoc-test-abstractparent.html</anchorfile>
+ <anchor>name-method</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>name</name>
+ <anchorfile>qml-qdoc-test-abstractparent.html</anchorfile>
+ <anchor>name-method</anchor>
+ <arglist>(Child child, name)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>rear</name>
+ <anchorfile>qml-qdoc-test-abstractparent.html</anchorfile>
+ <anchor>rear-method</anchor>
+ <arglist>(Child child, var method)</arglist>
+ </member>
+ </compound>
+ <compound kind="class">
+ <name>QDoc.Test.Child</name>
+ <filename>qml-qdoc-test-child.html</filename>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>name</name>
+ <anchorfile>qml-qdoc-test-child.html</anchorfile>
+ <anchor>name-method</anchor>
+ <arglist>(Child child, name)</arglist>
+ </member>
+ </compound>
+ <compound kind="namespace">
+ <name>CrossModuleRef</name>
+ <filename>crossmoduleref.html</filename>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>documentMe</name>
+ <anchorfile>crossmoduleref.html</anchorfile>
+ <anchor>documentMe</anchor>
+ <arglist>()</arglist>
+ </member>
+ </compound>
+ <compound kind="class">
+ <name>QDoc.Test.DocTest</name>
+ <filename>qml-qdoc-test-doctest.html</filename>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>completed</name>
+ <anchorfile>qml-qdoc-test-doctest.html</anchorfile>
+ <anchor>completed-signal</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>fail</name>
+ <anchorfile>qml-qdoc-test-doctest.html</anchorfile>
+ <anchor>fail-method</anchor>
+ <arglist>(message)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>fail_hard</name>
+ <anchorfile>qml-qdoc-test-doctest.html</anchorfile>
+ <anchor>fail_hard-method</anchor>
+ <arglist>(msg, option)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>foo</name>
+ <anchorfile>qml-qdoc-test-doctest.html</anchorfile>
+ <anchor>foo-signal</anchor>
+ <arglist>(var bar)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>itsHappening</name>
+ <anchorfile>qml-qdoc-test-doctest.html</anchorfile>
+ <anchor>itsHappening-signal</anchor>
+ <arglist>(bool really)</arglist>
+ </member>
+ </compound>
+ <compound kind="class">
+ <name>Test.NoVer.DocTest</name>
+ <filename>qml-test-nover-doctest.html</filename>
+ </compound>
+ <compound kind="class">
+ <name>QDoc.Test.OldType</name>
+ <filename>qml-qdoc-test-oldtype.html</filename>
+ </compound>
+ <compound kind="class">
+ <name>UIComponents.ProgressBar</name>
+ <filename>qml-uicomponents-progressbar.html</filename>
+ </compound>
+ <compound kind="class">
+ <name>SeenClass</name>
+ <filename>seenclass.html</filename>
+ </compound>
+ <compound kind="class">
+ <name>UIComponents.Switch</name>
+ <filename>qml-uicomponents-switch.html</filename>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>toggle</name>
+ <anchorfile>qml-uicomponents-switch.html</anchorfile>
+ <anchor>toggle-method</anchor>
+ <arglist>()</arglist>
+ </member>
+ </compound>
+ <compound kind="class">
+ <name>UIComponents.TabWidget</name>
+ <filename>qml-uicomponents-tabwidget.html</filename>
+ </compound>
+ <compound kind="namespace">
+ <name>TestQDoc</name>
+ <filename>testqdoc.html</filename>
+ <class>TestQDoc::Test</class>
+ <class>TestQDoc::TestDerived</class>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>QDOCTEST_MACRO</name>
+ <anchorfile>testqdoc.html</anchorfile>
+ <anchor>QDOCTEST_MACRO</anchor>
+ <arglist>QDOCTEST_MACRO</arglist>
+ </member>
+ </compound>
+ <compound kind="class">
+ <name>TestQDoc::Test</name>
+ <filename>testqdoc-test.html</filename>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>QDOCTEST_MACRO2</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>QDOCTEST_MACRO2</anchor>
+ <arglist>(int &amp;x)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>Test</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>Test</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>TestQDoc::Test &amp;</type>
+ <name>operator++</name>
+ <anchorfile>testqdoc-test-obsolete.html</anchorfile>
+ <anchor>operator-2b-2b</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>TestQDoc::Test &amp;</type>
+ <name>operator--</name>
+ <anchorfile>testqdoc-test-obsolete.html</anchorfile>
+ <anchor>operator--</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>TestQDoc::Test &amp;</type>
+ <name>operator=</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>operator-eq</anchor>
+ <arglist>(TestQDoc::Test &amp;&amp;other)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>bool</type>
+ <name>operator==</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>operator-eq-eq</anchor>
+ <arglist>(const TestQDoc::Test &amp;lhs, const TestQDoc::Test &amp;rhs)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>int</type>
+ <name>someFunction</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>someFunction</anchor>
+ <arglist>(int, int v)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void (*)(bool)</type>
+ <name>funcPtr</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>funcPtr</anchor>
+ <arglist>(*)(bool) funcPtr(bool b, const char *s)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>anotherObsoleteMember</name>
+ <anchorfile>testqdoc-test-obsolete.html</anchorfile>
+ <anchor>anotherObsoleteMember</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>deprecatedMember</name>
+ <anchorfile>testqdoc-test-obsolete.html</anchorfile>
+ <anchor>deprecatedMember</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>inlineFunction</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>inlineFunction</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>methodWithEmDashInItsDocs</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>methodWithEmDashInItsDocs</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>methodWithEnDashInItsDocs</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>methodWithEnDashInItsDocs</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>obsoleteMember</name>
+ <anchorfile>testqdoc-test-obsolete.html</anchorfile>
+ <anchor>obsoleteMember</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="protected" virtualness="non" static="no">
+ <type>void</type>
+ <name>overload</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>overload</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="protected" virtualness="non" static="no">
+ <type>void</type>
+ <name>overload</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>overload-1</anchor>
+ <arglist>(bool b)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="virtual" static="no">
+ <type>virtual void</type>
+ <name>virtualFun</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>virtualFun</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>someFunctionDefaultArg</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>someFunctionDefaultArg</anchor>
+ <arglist>(int i, bool b) const const</arglist>
+ </member>
+ <member kind="typedef" type="">
+ <name>SomeType</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>SomeType-typedef</anchor>
+ <arglist></arglist>
+ </member>
+ </compound>
+ <compound kind="class">
+ <name>TestQDoc::TestDerived</name>
+ <filename>testqdoc-testderived.html</filename>
+ <base>Test</base>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>QBindable&lt;QString&gt;</type>
+ <name>bindableProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>bindableProp-prop</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>TestQDoc::TestDerived::NotTypedef</type>
+ <name>someValue</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>someValue</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>bool</type>
+ <name>boolProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>boolProp-prop</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>const QString &amp;</type>
+ <name>someProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>someProp-prop</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>int *</type>
+ <name>getInt</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>intProp-prop</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="virtual" static="no">
+ <type>virtual int</type>
+ <name>id</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>id</anchor>
+ <arglist>() override</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>bindablePropChanged</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>bindableProp-prop</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>boolPropChanged</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>boolProp-prop</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>emitSomething</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>emitSomething</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>resetBoolProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>boolProp-prop</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>setBindableProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>bindableProp-prop</anchor>
+ <arglist>(const QString &amp;s)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>setBoolProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>boolProp-prop</anchor>
+ <arglist>(bool b)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="yes">
+ <type>void</type>
+ <name>staticObsoleteMember</name>
+ <anchorfile>testqdoc-testderived-obsolete.html</anchorfile>
+ <anchor>staticObsoleteMember</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="virtual" static="no">
+ <type>virtual void</type>
+ <name>virtualFun</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>virtualFun</anchor>
+ <arglist>() override</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>const QString *</type>
+ <name>name</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>name-prop</anchor>
+ <arglist>() const const</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>invokeMe</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>invokeMe</anchor>
+ <arglist>() const const</arglist>
+ </member>
+ <member kind="typedef" type="">
+ <name>DerivedType</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>DerivedType-typedef</anchor>
+ <arglist></arglist>
+ </member>
+ <member kind="typedef" type="">
+ <name>NotTypedef</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>NotTypedef-typedef</anchor>
+ <arglist></arglist>
+ </member>
+ <member kind="property" type="QString">
+ <name>bindableProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>bindableProp-prop</anchor>
+ <arglist></arglist>
+ </member>
+ <member kind="property" type="bool">
+ <name>boolProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>boolProp-prop</anchor>
+ <arglist></arglist>
+ </member>
+ <member kind="property" type="int*">
+ <name>intProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>intProp-prop</anchor>
+ <arglist></arglist>
+ </member>
+ <member kind="property" type="const QString*">
+ <name>name</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>name-prop</anchor>
+ <arglist></arglist>
+ </member>
+ <member kind="property" type="QString">
+ <name>someProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>someProp-prop</anchor>
+ <arglist></arglist>
+ </member>
+ </compound>
+ <compound kind="class">
+ <name>TheModule.TheType</name>
+ <filename>qml-themodule-thetype.html</filename>
+ </compound>
+ <compound kind="class">
+ <name>QDoc.Test.Type</name>
+ <filename>qml-qdoc-test-type.html</filename>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>Type</type>
+ <name>copy</name>
+ <anchorfile>qml-qdoc-test-type.html</anchorfile>
+ <anchor>copy-method</anchor>
+ <arglist>(a)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>completed</name>
+ <anchorfile>qml-qdoc-test-type.html</anchorfile>
+ <anchor>completed-signal</anchor>
+ <arglist>(int status)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>configured</name>
+ <anchorfile>qml-qdoc-test-type.html</anchorfile>
+ <anchor>configured-signal</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>deprecatedMethod</name>
+ <anchorfile>qml-qdoc-test-type-obsolete.html</anchorfile>
+ <anchor>deprecatedMethod-method</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>disable</name>
+ <anchorfile>qml-qdoc-test-type.html</anchorfile>
+ <anchor>disable-method</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>enable</name>
+ <anchorfile>qml-qdoc-test-type.html</anchorfile>
+ <anchor>enable-method</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>futureDeprecated</name>
+ <anchorfile>qml-qdoc-test-type.html</anchorfile>
+ <anchor>futureDeprecated-method</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>group.created</name>
+ <anchorfile>qml-qdoc-test-type.html</anchorfile>
+ <anchor>group.created-signal</anchor>
+ <arglist>()</arglist>
+ </member>
+ </compound>
+ <compound kind="class">
+ <name>Test.NoVer.TypeNoVersion</name>
+ <filename>qml-test-nover-typenoversion.html</filename>
+ </compound>
+ <compound kind="class">
+ <name>QDoc.Test.YetAnotherChild</name>
+ <filename>qml-qdoc-test-yetanotherchild.html</filename>
+ </compound>
+</tagfile>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/themodule-qmlmodule.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/themodule-qmlmodule.html
new file mode 100644
index 000000000..e3d28561d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/themodule-qmlmodule.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- properties.qdoc -->
+ <title>Test 6.2.11</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<!-- $$$TheModule-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@TheModule -->
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qml-themodule-thetype.html">TheType</a></p></td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/uicomponents-qmlmodule.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/uicomponents-qmlmodule.html
new file mode 100644
index 000000000..e45470a92
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/html/uicomponents-qmlmodule.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- examples.qdoc -->
+ <meta name="description" content="Basic set of UI components.">
+ <title>UI Components | Test 6.2.11</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">UI Components</h1>
+<!-- $$$UIComponents-description -->
+<div class="descr" id="details">
+<p>This is a listing of a list of UI components implemented by QML types. These files are available for general import and they are based on the Qt Quick Code Samples.</p>
+<p>This module is part of the <a href="test-componentset-example.html">UIComponents</a> example.</p>
+</div>
+<!-- @@@UIComponents -->
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qml-uicomponents-progressbar.html">ProgressBar</a></p></td><td class="tblDescr"><p>A component that shows the progress of an event</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="qml-uicomponents-switch.html">Switch</a></p></td><td class="tblDescr"><p>A component that can be turned on or off</p></td></tr>
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qml-uicomponents-tabwidget.html">TabWidget</a></p></td><td class="tblDescr"><p>A widget that places its children as tabs</p></td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/autolinking.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/autolinking.webxml
new file mode 100644
index 000000000..91f35d5d7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/autolinking.webxml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="autolinking.html" href="autolinking.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Autolinking" fulltitle="Autolinking" subtitle="">
+ <contents name="testqdoc" title="TestQDoc" level="1"/>
+ <contents name="someprop" title="someProp" level="1"/>
+ <description>
+ <section id="testqdoc">
+ <heading level="1">TestQDoc</heading>
+ <para>The string <link raw="TestQDoc" href="testqdoc.html" type="namespace">TestQDoc</link> links to the C++ namespace unless linking explicitly, <link raw="#TestQDoc" href="autolinking.html#testqdoc" type="page" page="Autolinking">like this</link>, or <link raw="TestQDoc" href="testqdoc.html" type="namespace">this</link>. Also,</para>
+ <para>Autolinks:</para>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="TestQDoc::TestDerived" href="testqdoc-testderived.html" type="class">TestQDoc::TestDerived</link></para>
+ </item>
+ </list>
+ <para>Explicit links:</para>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="TestQDoc::TestDerived" href="testqdoc-testderived.html" type="class">TestQDoc::TestDerived</link></para>
+ </item>
+ <item>
+ <para>
+ <link raw="Obsolete Classes#TestQDoc" href="obsolete-classes.html#testqdoc" type="page" page="Obsolete Classes">Obsolete Classes#TestQDoc</link></para>
+ </item>
+ </list>
+ </section>
+ <section id="someprop">
+ <heading level="1">someProp</heading>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/classes.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/classes.webxml
new file mode 100644
index 000000000..55faed6d6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/classes.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="classes.html" href="classes.html" status="active" location="unseenclass.qdoc" documented="true" subtype="page" title="Classes" fulltitle="Classes" subtitle="">
+ <description>
+ <generatedlist contents="annotatedclasses"/>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/cpptypes.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/cpptypes.webxml
new file mode 100644
index 000000000..df7cd7024
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/cpptypes.webxml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <group name="cpptypes" href="cpptypes.html" status="active" location="classlists.qdoc" documented="true" seen="true" title="Test C++ Types">
+ <description>
+ <generatedlist contents="testgroup"/>
+ <table width="100%">
+ <row>
+ <item>
+ <para>
+ <link raw="TestQDoc::Test" href="testqdoc-test.html" type="class"/>
+ </para>
+ </item>
+ <item>
+ <para>A class in a namespace.</para>
+ </item>
+ </row>
+ </table>
+ </description>
+ </group>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/crossmoduleref.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/crossmoduleref.webxml
new file mode 100644
index 000000000..682799bd8
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/crossmoduleref.webxml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <namespace name="CrossModuleRef" href="crossmoduleref.html" status="active" access="public" location="testcpp.h" since="3.0" documented="true" module="TestCPP" brief="Namespace that has documented functions in multiple modules">
+ <description>
+ <brief>Namespace that has documented functions in multiple modules.</brief>
+ </description>
+ <function name="documentMe" fullname="CrossModuleRef::documentMe" href="crossmoduleref.html#documentMe" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void documentMe()">
+ <description>
+ <para>Document me!</para>
+ </description>
+ </function>
+ </namespace>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/images/leonardo-da-vinci.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/images/leonardo-da-vinci.png
new file mode 100644
index 000000000..854acb4ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/images/leonardo-da-vinci.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/obsolete-classes.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/obsolete-classes.webxml
new file mode 100644
index 000000000..dda841458
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/obsolete-classes.webxml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="obsolete-classes.html" href="obsolete-classes.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Obsolete Classes" fulltitle="Obsolete Classes" subtitle="">
+ <contents name="classes-with-obsolete-members" title="Classes with obsolete members" level="1"/>
+ <contents name="testqdoc" title="TestQDoc" level="2"/>
+ <description>
+ <section id="classes-with-obsolete-members">
+ <heading level="1">Classes with obsolete members</heading>
+ <generatedlist contents="obsoletecppmembers"/>
+ </section>
+ <section id="testqdoc">
+ <heading level="2">TestQDoc</heading>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/qdoc-test-qmlmodule.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/qdoc-test-qmlmodule.webxml
new file mode 100644
index 000000000..5d24b3077
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/qdoc-test-qmlmodule.webxml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document/>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/qmlmodules.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/qmlmodules.webxml
new file mode 100644
index 000000000..dffd151d2
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/qmlmodules.webxml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="qmlmodules.html" href="qmlmodules.html" status="active" location="modules.qdoc" documented="true" subtype="page" title="QML Modules" fulltitle="QML Modules" subtitle="">
+ <contents name="qml-types" title="QML types" level="1"/>
+ <contents name="qml-value-types" title="QML value types" level="1"/>
+ <description>
+ <generatedlist contents="qml-modules"/>
+ <section id="qml-types">
+ <heading level="1">QML types</heading>
+ <generatedlist contents="qmltypesbymodule QDoc.Test"/>
+ </section>
+ <section id="qml-value-types">
+ <heading level="1">QML value types</heading>
+ <generatedlist contents="qmlvaluetypes"/>
+ <generatedlist contents="qmlvaluetypesbymodule QDoc.Test"/>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/seenclass.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/seenclass.webxml
new file mode 100644
index 000000000..92c2ad8f0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/seenclass.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="SeenClass" href="seenclass.html" status="active" access="public" location="dont.h" since="2.0" documented="true" module="TestCPP" brief="A public but undocumented class">
+ <description>
+ <brief>A public but undocumented class.</brief>
+ </description>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-cmaketest-cmakelists-txt.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-cmaketest-cmakelists-txt.webxml
new file mode 100644
index 000000000..7b4bf2ce6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-cmaketest-cmakelists-txt.webxml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="cmaketest/CMakeLists.txt" href="test-cmaketest-cmakelists-txt.html" title="CMakeLists.txt Example File" fulltitle="CMakeLists.txt Example File" subtitle="cmaketest/CMakeLists.txt">
+ <description>
+ <code>cmake_minimum_required(VERSION 3.16)
+project (QDOCTEST)</code>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-cmaketest-example.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-cmaketest-example.webxml
new file mode 100644
index 000000000..b0cd15b27
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-cmaketest-example.webxml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="cmaketest" href="test-cmaketest-example.html" status="active" location="cmaketest.qdoc" documented="true" subtype="example" title="CMake Example Project" fulltitle="CMake Example Project" subtitle="">
+ <description>
+ <image href="images/leonardo-da-vinci.png"/>
+ <para>Files:</para>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="cmaketest/CMakeLists.txt" href="test-cmaketest-cmakelists-txt.html" type="page" page="CMakeLists.txt Example File">cmaketest/CMakeLists.txt</link>
+ </para>
+ </item>
+ <item>
+ <para>
+ <link raw="cmaketest/main.cpp" href="test-cmaketest-main-cpp.html" type="page" page="main.cpp Example File">cmaketest/main.cpp</link>
+ </para>
+ </item>
+ </list>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-cmaketest-main-cpp.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-cmaketest-main-cpp.webxml
new file mode 100644
index 000000000..7d22ced82
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-cmaketest-main-cpp.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="cmaketest/main.cpp" href="test-cmaketest-main-cpp.html" title="main.cpp Example File" fulltitle="main.cpp Example File" subtitle="cmaketest/main.cpp">
+ <description>
+ <code>&lt;@type&gt;void&lt;/@type&gt; main(){}</code>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-componentset-pro.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-componentset-pro.webxml
new file mode 100644
index 000000000..08be3fa28
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-componentset-pro.webxml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="componentset/componentset.pro" href="test-componentset-componentset-pro.html" title="componentset.pro Example File" fulltitle="componentset.pro Example File" subtitle="componentset/componentset.pro">
+ <description>
+ <code>SOURCES = componentset.pro \
+ ProgressBar.qml \
+ Switch.qml \
+ TabWidget.qml \
+ uicomponents.qdoc</code>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-componentset-qml.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-componentset-qml.webxml
new file mode 100644
index 000000000..0a4297da0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-componentset-qml.webxml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="componentset/componentset.qml" href="test-componentset-componentset-qml.html" title="componentset.qml Example File" fulltitle="componentset.qml Example File" subtitle="componentset/componentset.qml">
+ <description>
+ <code>&lt;@comment&gt;// Copyright (C) 2023 The Qt Company Ltd.&lt;/@comment&gt;
+&lt;@comment&gt;// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0&lt;/@comment&gt;
+
+import QtQuick 2.0
+
+&lt;@type&gt;Item&lt;/@type&gt; {
+}</code>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-example.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-example.webxml
new file mode 100644
index 000000000..600f2f3ed
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-example.webxml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="componentset" href="test-componentset-example.html" status="active" location="examples.qdoc" documented="true" subtype="example" title="QML Documentation Example" fulltitle="QML Documentation Example" subtitle="" brief="Example for documenting QML types">
+ <contents name="qml-class" title="QML Class" level="1"/>
+ <contents name="properties-signals-handlers-and-methods" title="Properties, Signals, Handlers, and Methods" level="1"/>
+ <contents name="internal-documentation" title="Internal Documentation" level="2"/>
+ <contents name="qml-types-with-c-implementation" title="QML Types with C++ Implementation" level="1"/>
+ <description>
+ <brief>Example for documenting QML types.</brief>
+ <para>This example demonstrates one of the ways to document QML types. It also generates a warning about a missing example image, on purpose.</para>
+ <para>In particular, there are sample types that are documented with QDoc commands comments. There are documentation comments for the QML types and their public interfaces. The types are grouped into a module, the <link raw="UI Components" href="uicomponents-qmlmodule.html" type="">UI Components</link> module.</para>
+ <para>The uicomponents.qdoc file generates the overview page for the <link raw="UI Components" href="uicomponents-qmlmodule.html" type="">UI Components</link> module page.</para>
+ <para>The generated documentation is available in the <link raw="UI Components" href="uicomponents-qmlmodule.html" type="">UI Components</link> module.</para>
+ <section id="qml-class">
+ <heading level="1">QML Class</heading>
+ <para>The QML types use the \qmltype to document the type. In addition, they have the \inmodule command in order for QDoc to associate them to the <teletype type="highlighted">UIComponents</teletype> module.</para>
+ <para>QDoc uses the \brief command to place a basic description when listing the types.</para>
+ </section>
+ <section id="properties-signals-handlers-and-methods">
+ <heading level="1">Properties, Signals, Handlers, and Methods</heading>
+ <para>The types have their properties, signals, handlers, and methods defined in their respective QML files. QDoc associates the properties and methods to the types, therefore, you only need to place the documentation above the property, method, or signal.</para>
+ <para>To document the type of a <italic>property alias</italic>, you must use the \qmlproperty command to specify the data type.</para>
+ <code>\qmlproperty int anAliasedProperty
+An aliased property of type int.</code>
+ </section>
+ <section id="internal-documentation">
+ <heading level="2">Internal Documentation</heading>
+ <para>You may declare that a documentation is for internal use by placing the \internal command after the beginning QDoc comment <teletype type="highlighted">/*</teletype>. QDoc will prevent the internal documentation from appearing in the public API.</para>
+ <para>If you wish to omit certain parts of the documentation, you may use the \omit and \endomit command.</para>
+ </section>
+ <section id="qml-types-with-c-implementation">
+ <heading level="1">QML Types with C++ Implementation</heading>
+ <para>This example only demonstrates the documentation for types in QML files, but the regular QML commands may be placed inside C++ classes to define the public API of the QML type.</para>
+ </section>
+ <para>Files:</para>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="componentset/ProgressBar.qml" href="test-componentset-progressbar-qml.html" type="page" page="ProgressBar.qml Example File">componentset/ProgressBar.qml</link>
+ </para>
+ </item>
+ <item>
+ <para>
+ <link raw="componentset/Switch.qml" href="test-componentset-switch-qml.html" type="page" page="Switch.qml Example File">componentset/Switch.qml</link>
+ </para>
+ </item>
+ <item>
+ <para>
+ <link raw="componentset/TabWidget.qml" href="test-componentset-tabwidget-qml.html" type="page" page="TabWidget.qml Example File">componentset/TabWidget.qml</link>
+ </para>
+ </item>
+ <item>
+ <para>
+ <link raw="componentset/componentset.pro" href="test-componentset-componentset-pro.html" type="page" page="componentset.pro Example File">componentset/componentset.pro</link>
+ </para>
+ </item>
+ <item>
+ <para>
+ <link raw="componentset/componentset.qml" href="test-componentset-componentset-qml.html" type="page" page="componentset.qml Example File">componentset/componentset.qml</link>
+ </para>
+ </item>
+ </list>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-progressbar-qml.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-progressbar-qml.webxml
new file mode 100644
index 000000000..6b5d2f8ac
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-progressbar-qml.webxml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="componentset/ProgressBar.qml" href="test-componentset-progressbar-qml.html" title="ProgressBar.qml Example File" fulltitle="ProgressBar.qml Example File" subtitle="componentset/ProgressBar.qml">
+ <description>
+ <code>&lt;@comment&gt;// Copyright (C) 2016 The Qt Company Ltd.&lt;/@comment&gt;
+&lt;@comment&gt;// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause&lt;/@comment&gt;
+
+import QtQuick 1.0
+
+&lt;@comment&gt;/*!
+ \qmltype ProgressBar
+ \inqmlmodule UIComponents
+ \brief A component that shows the progress of an event.
+
+ A ProgressBar shows the linear progress of an event as its \l value.
+ The range is specified using the \l {minimum} and the \l{maximum} values.
+
+ The ProgressBar component is part of the \l {UI Components} module.
+
+ This documentation is part of the \l{componentset}{UIComponents} example.
+*/&lt;/@comment&gt;
+&lt;@type&gt;Item&lt;/@type&gt; {
+ &lt;@name&gt;id&lt;/@name&gt;: &lt;@name&gt;progressbar&lt;/@name&gt;
+
+ &lt;@comment&gt;/*!
+ The minimum value of the ProgressBar range.
+ The \l value must not be less than this value.
+ */&lt;/@comment&gt;
+ property &lt;@type&gt;int&lt;/@type&gt; &lt;@name&gt;minimum&lt;/@name&gt;: &lt;@number&gt;0&lt;/@number&gt;
+
+ &lt;@comment&gt;/*!
+ The maximum value of the ProgressBar range.
+ The \l value must not be more than this value.
+ */&lt;/@comment&gt;
+ property &lt;@type&gt;int&lt;/@type&gt; &lt;@name&gt;maximum&lt;/@name&gt;: &lt;@number&gt;100&lt;/@number&gt;
+
+ &lt;@comment&gt;/*!
+ The value of the progress.
+ */&lt;/@comment&gt;
+ property &lt;@type&gt;int&lt;/@type&gt; &lt;@name&gt;value&lt;/@name&gt;: &lt;@number&gt;0&lt;/@number&gt;
+
+ &lt;@comment&gt;/*!
+ \qmlproperty color ProgressBar::color
+ The color of the ProgressBar's gradient. Must bind to a color type.
+
+ \omit
+ The &amp;quot;\qmlproperty &amp;lt;type&amp;gt; &amp;lt;property name&amp;gt;&amp;quot; is needed because
+ property alias need to have their types manually entered.
+
+ QDoc will not publish the documentation within omit and endomit.
+ \endomit
+
+ \sa secondColor
+ */&lt;/@comment&gt;
+ property &lt;@type&gt;alias&lt;/@type&gt; &lt;@name&gt;color&lt;/@name&gt;: &lt;@name&gt;gradient1&lt;/@name&gt;.&lt;@name&gt;color&lt;/@name&gt;
+
+ &lt;@comment&gt;/*!
+ \qmlproperty color ProgressBar::secondColor
+ The second color of the ProgressBar's gradient.
+ Must bind to a color type.
+
+ \omit
+ The &amp;quot;\qmlproperty &amp;lt;type&amp;gt; &amp;lt;property name&amp;gt;&amp;quot; is needed because
+ property alias need to have their types manually entered.
+
+ QDoc will not publish the documentation within omit and endomit.
+ \endomit
+
+ \sa color
+ */&lt;/@comment&gt;
+ property &lt;@type&gt;alias&lt;/@type&gt; &lt;@name&gt;secondColor&lt;/@name&gt;: &lt;@name&gt;gradient2&lt;/@name&gt;.&lt;@name&gt;color&lt;/@name&gt;
+
+ &lt;@name&gt;width&lt;/@name&gt;: &lt;@number&gt;250&lt;/@number&gt;; &lt;@name&gt;height&lt;/@name&gt;: &lt;@number&gt;23&lt;/@number&gt;
+ &lt;@name&gt;clip&lt;/@name&gt;: &lt;@number&gt;true&lt;/@number&gt;
+
+ &lt;@type&gt;Rectangle&lt;/@type&gt; {
+ &lt;@name&gt;id&lt;/@name&gt;: &lt;@name&gt;highlight&lt;/@name&gt;
+
+ &lt;@comment&gt;/*!
+ An internal documentation comment. The widthDest property is not
+ a public API and therefore will not be exposed.
+ */&lt;/@comment&gt;
+ property &lt;@type&gt;int&lt;/@type&gt; &lt;@name&gt;widthDest&lt;/@name&gt;: ((&lt;@name&gt;progressbar&lt;/@name&gt;.&lt;@name&gt;width&lt;/@name&gt; &lt;@op&gt;*&lt;/@op&gt; (&lt;@name&gt;value&lt;/@name&gt; &lt;@op&gt;-&lt;/@op&gt; &lt;@name&gt;minimum&lt;/@name&gt;)) &lt;@op&gt;/&lt;/@op&gt; (&lt;@name&gt;maximum&lt;/@name&gt; &lt;@op&gt;-&lt;/@op&gt; &lt;@name&gt;minimum&lt;/@name&gt;) &lt;@op&gt;-&lt;/@op&gt; &lt;@number&gt;6&lt;/@number&gt;)
+
+ &lt;@name&gt;width&lt;/@name&gt;: &lt;@name&gt;highlight&lt;/@name&gt;.&lt;@name&gt;widthDest&lt;/@name&gt;
+ Behavior on &lt;@name&gt;width&lt;/@name&gt; { &lt;@type&gt;SmoothedAnimation&lt;/@type&gt; { &lt;@name&gt;velocity&lt;/@name&gt;: &lt;@number&gt;1200&lt;/@number&gt; } }
+
+ &lt;@type&gt;anchors&lt;/@type&gt; { &lt;@name&gt;left&lt;/@name&gt;: &lt;@name&gt;parent&lt;/@name&gt;.&lt;@name&gt;left&lt;/@name&gt;; &lt;@name&gt;top&lt;/@name&gt;: &lt;@name&gt;parent&lt;/@name&gt;.&lt;@name&gt;top&lt;/@name&gt;; &lt;@name&gt;bottom&lt;/@name&gt;: &lt;@name&gt;parent&lt;/@name&gt;.&lt;@name&gt;bottom&lt;/@name&gt;; &lt;@name&gt;margins&lt;/@name&gt;: &lt;@number&gt;3&lt;/@number&gt; }
+ &lt;@name&gt;radius&lt;/@name&gt;: &lt;@number&gt;1&lt;/@number&gt;
+ &lt;@name&gt;gradient&lt;/@name&gt;: &lt;@name&gt;Gradient&lt;/@name&gt; {
+ &lt;@type&gt;GradientStop&lt;/@type&gt; { &lt;@name&gt;id&lt;/@name&gt;: &lt;@name&gt;gradient1&lt;/@name&gt;; &lt;@name&gt;position&lt;/@name&gt;: &lt;@number&gt;0.0&lt;/@number&gt; }
+ &lt;@type&gt;GradientStop&lt;/@type&gt; { &lt;@name&gt;id&lt;/@name&gt;: &lt;@name&gt;gradient2&lt;/@name&gt;; &lt;@name&gt;position&lt;/@name&gt;: &lt;@number&gt;1.0&lt;/@number&gt; }
+ }
+
+ }
+ &lt;@type&gt;Text&lt;/@type&gt; {
+ &lt;@type&gt;anchors&lt;/@type&gt; { &lt;@name&gt;right&lt;/@name&gt;: &lt;@name&gt;highlight&lt;/@name&gt;.&lt;@name&gt;right&lt;/@name&gt;; &lt;@name&gt;rightMargin&lt;/@name&gt;: &lt;@number&gt;6&lt;/@number&gt;; &lt;@name&gt;verticalCenter&lt;/@name&gt;: &lt;@name&gt;parent&lt;/@name&gt;.&lt;@name&gt;verticalCenter&lt;/@name&gt; }
+ &lt;@name&gt;color&lt;/@name&gt;: &lt;@string&gt;&amp;quot;white&amp;quot;&lt;/@string&gt;
+ &lt;@name&gt;font&lt;/@name&gt;.bold: &lt;@number&gt;true&lt;/@number&gt;
+ &lt;@name&gt;text&lt;/@name&gt;: &lt;@name&gt;Math&lt;/@name&gt;.&lt;@name&gt;floor&lt;/@name&gt;((&lt;@name&gt;value&lt;/@name&gt; &lt;@op&gt;-&lt;/@op&gt; &lt;@name&gt;minimum&lt;/@name&gt;) &lt;@op&gt;/&lt;/@op&gt; (&lt;@name&gt;maximum&lt;/@name&gt; &lt;@op&gt;-&lt;/@op&gt; &lt;@name&gt;minimum&lt;/@name&gt;) &lt;@op&gt;*&lt;/@op&gt; &lt;@number&gt;100&lt;/@number&gt;) &lt;@op&gt;+&lt;/@op&gt; &lt;@string&gt;'%'&lt;/@string&gt;
+ }
+}</code>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-switch-qml.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-switch-qml.webxml
new file mode 100644
index 000000000..4e09d9f25
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-switch-qml.webxml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="componentset/Switch.qml" href="test-componentset-switch-qml.html" title="Switch.qml Example File" fulltitle="Switch.qml Example File" subtitle="componentset/Switch.qml">
+ <description>
+ <code>&lt;@comment&gt;// Copyright (C) 2016 The Qt Company Ltd.&lt;/@comment&gt;
+&lt;@comment&gt;// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause&lt;/@comment&gt;
+
+import QtQuick 1.0
+
+&lt;@comment&gt;/*!
+ \qmltype ToggleSwitch
+ \inqmlmodule UIComponents
+ \brief A component that can be turned on or off.
+
+ A toggle switch has two states: an \c on and an \c off state. The \c off
+ state is when the \l on property is set to \c false.
+
+ The ToggleSwitch component is part of the \l {UI Components} module.
+
+ This documentation is part of the \l{componentset}{UIComponents} example.
+
+*/&lt;/@comment&gt;
+&lt;@type&gt;Item&lt;/@type&gt; {
+ &lt;@name&gt;id&lt;/@name&gt;: &lt;@name&gt;toggleswitch&lt;/@name&gt;
+ &lt;@name&gt;width&lt;/@name&gt;: &lt;@name&gt;background&lt;/@name&gt;.&lt;@name&gt;width&lt;/@name&gt;; &lt;@name&gt;height&lt;/@name&gt;: &lt;@name&gt;background&lt;/@name&gt;.&lt;@name&gt;height&lt;/@name&gt;
+
+ &lt;@comment&gt;/*!
+ Indicates the state of the switch. If \c false, then the switch is in
+ the \c off state.
+
+ \omit
+ The \qmlproperty &amp;lt;type&amp;gt; &amp;lt;propertyname&amp;gt; is not necessary as QDoc
+ will associate this property to the ToggleSwitch
+
+ QDoc will not publish the documentation within omit and endomit.
+ \endomit
+ */&lt;/@comment&gt;
+ property &lt;@type&gt;bool&lt;/@type&gt; &lt;@name&gt;on&lt;/@name&gt;: &lt;@number&gt;false&lt;/@number&gt;
+
+ &lt;@comment&gt;/*!
+ A method to toggle the switch. If the switch is \c on, the toggling it
+ will turn it \c off. Toggling a switch in the \c off position will
+ turn it \c on.
+ */&lt;/@comment&gt;
+ &lt;@keyword&gt;function &lt;/@keyword&gt;&lt;@name&gt;toggle&lt;/@name&gt;() {
+ &lt;@keyword&gt;if&lt;/@keyword&gt; (&lt;@name&gt;toggleswitch&lt;/@name&gt;.&lt;@name&gt;state&lt;/@name&gt; &lt;@op&gt;==&lt;/@op&gt; &lt;@string&gt;&amp;quot;on&amp;quot;&lt;/@string&gt;)
+ &lt;@name&gt;toggleswitch&lt;/@name&gt;.&lt;@name&gt;state&lt;/@name&gt; &lt;@op&gt;=&lt;/@op&gt; &lt;@string&gt;&amp;quot;off&amp;quot;&lt;/@string&gt;;
+ &lt;@keyword&gt;else&lt;/@keyword&gt;
+ &lt;@name&gt;toggleswitch&lt;/@name&gt;.&lt;@name&gt;state&lt;/@name&gt; &lt;@op&gt;=&lt;/@op&gt; &lt;@string&gt;&amp;quot;on&amp;quot;&lt;/@string&gt;;
+ }
+
+ &lt;@comment&gt;/*!
+ \internal
+
+ An internal function to synchronize the switch's internals. This
+ function is not for public access. The \internal command will
+ prevent QDoc from publishing this comment in the public API.
+ */&lt;/@comment&gt;
+ &lt;@keyword&gt;function &lt;/@keyword&gt;&lt;@name&gt;releaseSwitch&lt;/@name&gt;() {
+ &lt;@keyword&gt;if&lt;/@keyword&gt; (&lt;@name&gt;knob&lt;/@name&gt;.&lt;@name&gt;x&lt;/@name&gt; &lt;@op&gt;==&lt;/@op&gt; &lt;@number&gt;1&lt;/@number&gt;) {
+ &lt;@keyword&gt;if&lt;/@keyword&gt; (&lt;@name&gt;toggleswitch&lt;/@name&gt;.&lt;@name&gt;state&lt;/@name&gt; &lt;@op&gt;==&lt;/@op&gt; &lt;@string&gt;&amp;quot;off&amp;quot;&lt;/@string&gt;) &lt;@keyword&gt;return&lt;/@keyword&gt;;
+ }
+ &lt;@keyword&gt;if&lt;/@keyword&gt; (&lt;@name&gt;knob&lt;/@name&gt;.&lt;@name&gt;x&lt;/@name&gt; &lt;@op&gt;==&lt;/@op&gt; &lt;@number&gt;78&lt;/@number&gt;) {
+ &lt;@keyword&gt;if&lt;/@keyword&gt; (&lt;@name&gt;toggleswitch&lt;/@name&gt;.&lt;@name&gt;state&lt;/@name&gt; &lt;@op&gt;==&lt;/@op&gt; &lt;@string&gt;&amp;quot;on&amp;quot;&lt;/@string&gt;) &lt;@keyword&gt;return&lt;/@keyword&gt;;
+ }
+ &lt;@name&gt;toggle&lt;/@name&gt;();
+ }
+
+ &lt;@type&gt;Rectangle&lt;/@type&gt; {
+ &lt;@name&gt;id&lt;/@name&gt;: &lt;@name&gt;background&lt;/@name&gt;
+ &lt;@name&gt;width&lt;/@name&gt;: &lt;@number&gt;130&lt;/@number&gt;; &lt;@name&gt;height&lt;/@name&gt;: &lt;@number&gt;48&lt;/@number&gt;
+ &lt;@name&gt;radius&lt;/@name&gt;: &lt;@number&gt;48&lt;/@number&gt;
+ &lt;@name&gt;color&lt;/@name&gt;: &lt;@string&gt;&amp;quot;lightsteelblue&amp;quot;&lt;/@string&gt;
+ &lt;@type&gt;MouseArea&lt;/@type&gt; { &lt;@name&gt;anchors&lt;/@name&gt;.fill: &lt;@name&gt;parent&lt;/@name&gt;; &lt;@name&gt;onClicked&lt;/@name&gt;: &lt;@name&gt;toggle&lt;/@name&gt;() }
+ }
+
+ &lt;@type&gt;Rectangle&lt;/@type&gt; {
+ &lt;@name&gt;id&lt;/@name&gt;: &lt;@name&gt;knob&lt;/@name&gt;
+ &lt;@name&gt;width&lt;/@name&gt;: &lt;@number&gt;48&lt;/@number&gt;; &lt;@name&gt;height&lt;/@name&gt;: &lt;@number&gt;48&lt;/@number&gt;
+ &lt;@name&gt;radius&lt;/@name&gt;: &lt;@name&gt;width&lt;/@name&gt;
+ &lt;@name&gt;color&lt;/@name&gt;: &lt;@string&gt;&amp;quot;lightblue&amp;quot;&lt;/@string&gt;
+
+ &lt;@type&gt;MouseArea&lt;/@type&gt; {
+ &lt;@name&gt;anchors&lt;/@name&gt;.fill: &lt;@name&gt;parent&lt;/@name&gt;
+ &lt;@name&gt;drag&lt;/@name&gt;.target: &lt;@name&gt;knob&lt;/@name&gt;; &lt;@name&gt;drag&lt;/@name&gt;.axis: &lt;@name&gt;Drag&lt;/@name&gt;.&lt;@name&gt;XAxis&lt;/@name&gt;; &lt;@name&gt;drag&lt;/@name&gt;.minimumX: &lt;@number&gt;1&lt;/@number&gt;; &lt;@name&gt;drag&lt;/@name&gt;.maximumX: &lt;@number&gt;78&lt;/@number&gt;
+ &lt;@name&gt;onClicked&lt;/@name&gt;: &lt;@name&gt;toggle&lt;/@name&gt;()
+ &lt;@name&gt;onReleased&lt;/@name&gt;: &lt;@name&gt;releaseSwitch&lt;/@name&gt;()
+ }
+ }
+
+ &lt;@name&gt;states&lt;/@name&gt;: [
+ &lt;@type&gt;State&lt;/@type&gt; {
+ &lt;@name&gt;name&lt;/@name&gt;: &lt;@string&gt;&amp;quot;on&amp;quot;&lt;/@string&gt;
+ &lt;@type&gt;PropertyChanges&lt;/@type&gt; { &lt;@name&gt;target&lt;/@name&gt;: &lt;@name&gt;knob&lt;/@name&gt;; &lt;@name&gt;x&lt;/@name&gt;: &lt;@number&gt;78&lt;/@number&gt; }
+ &lt;@type&gt;PropertyChanges&lt;/@type&gt; { &lt;@name&gt;target&lt;/@name&gt;: &lt;@name&gt;toggleswitch&lt;/@name&gt;; &lt;@name&gt;on&lt;/@name&gt;: &lt;@number&gt;true&lt;/@number&gt; }
+ },
+ &lt;@type&gt;State&lt;/@type&gt; {
+ &lt;@name&gt;name&lt;/@name&gt;: &lt;@string&gt;&amp;quot;off&amp;quot;&lt;/@string&gt;
+ &lt;@type&gt;PropertyChanges&lt;/@type&gt; { &lt;@name&gt;target&lt;/@name&gt;: &lt;@name&gt;knob&lt;/@name&gt;; &lt;@name&gt;x&lt;/@name&gt;: &lt;@number&gt;1&lt;/@number&gt; }
+ &lt;@type&gt;PropertyChanges&lt;/@type&gt; { &lt;@name&gt;target&lt;/@name&gt;: &lt;@name&gt;toggleswitch&lt;/@name&gt;; &lt;@name&gt;on&lt;/@name&gt;: &lt;@number&gt;false&lt;/@number&gt; }
+ }
+ ]
+
+ &lt;@name&gt;transitions&lt;/@name&gt;: &lt;@name&gt;Transition&lt;/@name&gt; {
+ &lt;@type&gt;NumberAnimation&lt;/@type&gt; { &lt;@name&gt;properties&lt;/@name&gt;: &lt;@string&gt;&amp;quot;x&amp;quot;&lt;/@string&gt;; &lt;@name&gt;easing&lt;/@name&gt;.type: &lt;@name&gt;Easing&lt;/@name&gt;.&lt;@name&gt;InOutQuad&lt;/@name&gt;; &lt;@name&gt;duration&lt;/@name&gt;: &lt;@number&gt;200&lt;/@number&gt; }
+ }
+}</code>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-tabwidget-qml.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-tabwidget-qml.webxml
new file mode 100644
index 000000000..6ee3ce5dc
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-componentset-tabwidget-qml.webxml
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="componentset/TabWidget.qml" href="test-componentset-tabwidget-qml.html" title="TabWidget.qml Example File" fulltitle="TabWidget.qml Example File" subtitle="componentset/TabWidget.qml">
+ <description>
+ <code>&lt;@comment&gt;// Copyright (C) 2016 The Qt Company Ltd.&lt;/@comment&gt;
+&lt;@comment&gt;// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause&lt;/@comment&gt;
+
+import QtQuick 1.0
+
+&lt;@comment&gt;/*!
+ \qmltype TabWidget
+ \inqmlmodule UIComponents
+ \brief A widget that places its children as tabs.
+
+ A TabWidget places its children as tabs in a view. Selecting
+ a tab involves selecting the tab at the top.
+
+ The TabWidget component is part of the \l {UI Components} module.
+
+ This documentation is part of the \l{componentset}{UIComponents} example.
+
+ \section1 Adding Tabs
+
+ To add a tab, declare the tab as a child of the TabWidget.
+
+ \code
+ TabWidget {
+ id: tabwidget
+
+ Rectangle {
+ id: tab1
+ color: &amp;quot;red&amp;quot;
+ //... omitted
+ }
+ Rectangle {
+ id: tab2
+ color: &amp;quot;blue&amp;quot;
+ //... omitted
+ }
+
+ }
+ \endcode
+
+*/&lt;/@comment&gt;
+&lt;@type&gt;Item&lt;/@type&gt; {
+ &lt;@name&gt;id&lt;/@name&gt;: &lt;@name&gt;tabWidget&lt;/@name&gt;
+
+ &lt;@comment&gt;/*!
+ \internal
+
+ Setting the default property to stack.children means any child items
+ of the TabWidget are actually added to the 'stack' item's children.
+
+ See the \l{&amp;quot;Property Binding in QML&amp;quot;}
+ documentation for details on default properties.
+
+ This is an implementation detail, not meant for public knowledge. Putting
+ the \internal command at the beginning will cause QDoc to not publish this
+ documentation in the public API page.
+
+ Normally, a property alias needs to have a
+ &amp;quot;\qmlproperty &amp;lt;type&amp;gt; &amp;lt;propertyname&amp;gt;&amp;quot; to assign the alias a type.
+
+ */&lt;/@comment&gt;
+ default property &lt;@type&gt;alias&lt;/@type&gt; &lt;@name&gt;content&lt;/@name&gt;: &lt;@name&gt;stack&lt;/@name&gt;.&lt;@name&gt;children&lt;/@name&gt;
+
+ &lt;@comment&gt;/*!
+ The currently active tab in the TabWidget.
+ */&lt;/@comment&gt;
+ property &lt;@type&gt;int&lt;/@type&gt; &lt;@name&gt;current&lt;/@name&gt;: &lt;@number&gt;0&lt;/@number&gt;
+
+ &lt;@comment&gt;/*!
+ A sample \c{read-only} property.
+ A contrived property to demonstrate QDoc's ability to detect
+ read-only properties.
+
+ The signature is:
+ \code
+ readonly property int sampleReadOnlyProperty: 0
+ \endcode
+
+ Note that the property must be initialized to a value.
+
+ */&lt;/@comment&gt;
+ readonly property &lt;@type&gt;int&lt;/@type&gt; &lt;@name&gt;sampleReadOnlyProperty&lt;/@name&gt;: &lt;@number&gt;0&lt;/@number&gt;
+
+ &lt;@comment&gt;/*!
+ \internal
+
+ This handler is an implementation
+ detail. The \c{\internal} command will prevent QDoc from publishing this
+ documentation on the public API.
+ */&lt;/@comment&gt;
+ &lt;@name&gt;onCurrentChanged&lt;/@name&gt;: &lt;@name&gt;setOpacities&lt;/@name&gt;()
+ &lt;@name&gt;Component&lt;/@name&gt;.onCompleted: &lt;@name&gt;setOpacities&lt;/@name&gt;()
+
+ &lt;@comment&gt;/*!
+ \internal
+
+ An internal function to set the opacity.
+ The \internal command will prevent QDoc from publishing this
+ documentation on the public API.
+ */&lt;/@comment&gt;
+ &lt;@keyword&gt;function &lt;/@keyword&gt;&lt;@name&gt;setOpacities&lt;/@name&gt;() {
+ &lt;@keyword&gt;for&lt;/@keyword&gt; (var i = 0; &lt;@name&gt;i&lt;/@name&gt; &lt;@op&gt;&amp;lt;&lt;/@op&gt; &lt;@name&gt;stack&lt;/@name&gt;.&lt;@name&gt;children&lt;/@name&gt;.&lt;@name&gt;length&lt;/@name&gt;; ++&lt;@name&gt;i&lt;/@name&gt;) {
+ &lt;@name&gt;stack&lt;/@name&gt;.&lt;@name&gt;children&lt;/@name&gt;[&lt;@name&gt;i&lt;/@name&gt;].&lt;@name&gt;opacity&lt;/@name&gt; &lt;@op&gt;=&lt;/@op&gt; (&lt;@name&gt;i&lt;/@name&gt; &lt;@op&gt;==&lt;/@op&gt; &lt;@name&gt;current&lt;/@name&gt; ? &lt;@number&gt;1&lt;/@number&gt; : &lt;@number&gt;0&lt;/@number&gt;)
+ }
+ }
+
+ &lt;@type&gt;Row&lt;/@type&gt; {
+ &lt;@name&gt;id&lt;/@name&gt;: &lt;@name&gt;header&lt;/@name&gt;
+
+ &lt;@type&gt;Repeater&lt;/@type&gt; {
+ &lt;@name&gt;model&lt;/@name&gt;: &lt;@name&gt;stack&lt;/@name&gt;.&lt;@name&gt;children&lt;/@name&gt;.&lt;@name&gt;length&lt;/@name&gt;
+ &lt;@name&gt;delegate&lt;/@name&gt;: &lt;@name&gt;Rectangle&lt;/@name&gt; {
+ &lt;@name&gt;width&lt;/@name&gt;: &lt;@name&gt;tabWidget&lt;/@name&gt;.&lt;@name&gt;width&lt;/@name&gt; &lt;@op&gt;/&lt;/@op&gt; &lt;@name&gt;stack&lt;/@name&gt;.&lt;@name&gt;children&lt;/@name&gt;.&lt;@name&gt;length&lt;/@name&gt;; &lt;@name&gt;height&lt;/@name&gt;: &lt;@number&gt;36&lt;/@number&gt;
+
+ &lt;@type&gt;Rectangle&lt;/@type&gt; {
+ &lt;@name&gt;width&lt;/@name&gt;: &lt;@name&gt;parent&lt;/@name&gt;.&lt;@name&gt;width&lt;/@name&gt;; &lt;@name&gt;height&lt;/@name&gt;: &lt;@number&gt;1&lt;/@number&gt;
+ &lt;@type&gt;anchors&lt;/@type&gt; { &lt;@name&gt;bottom&lt;/@name&gt;: &lt;@name&gt;parent&lt;/@name&gt;.&lt;@name&gt;bottom&lt;/@name&gt;; &lt;@name&gt;bottomMargin&lt;/@name&gt;: &lt;@number&gt;1&lt;/@number&gt; }
+ &lt;@name&gt;color&lt;/@name&gt;: &lt;@string&gt;&amp;quot;#acb2c2&amp;quot;&lt;/@string&gt;
+ }
+ &lt;@type&gt;BorderImage&lt;/@type&gt; {
+ &lt;@type&gt;anchors&lt;/@type&gt; { &lt;@name&gt;fill&lt;/@name&gt;: &lt;@name&gt;parent&lt;/@name&gt;; &lt;@name&gt;leftMargin&lt;/@name&gt;: &lt;@number&gt;2&lt;/@number&gt;; &lt;@name&gt;topMargin&lt;/@name&gt;: &lt;@number&gt;5&lt;/@number&gt;; &lt;@name&gt;rightMargin&lt;/@name&gt;: &lt;@number&gt;1&lt;/@number&gt; }
+ &lt;@type&gt;border&lt;/@type&gt; { &lt;@name&gt;left&lt;/@name&gt;: &lt;@number&gt;7&lt;/@number&gt;; &lt;@name&gt;right&lt;/@name&gt;: &lt;@number&gt;7&lt;/@number&gt; }
+ &lt;@name&gt;source&lt;/@name&gt;: &lt;@string&gt;&amp;quot;tab.png&amp;quot;&lt;/@string&gt;
+ &lt;@name&gt;visible&lt;/@name&gt;: &lt;@name&gt;tabWidget&lt;/@name&gt;.&lt;@name&gt;current&lt;/@name&gt; &lt;@op&gt;==&lt;/@op&gt; &lt;@name&gt;index&lt;/@name&gt;
+ }
+ &lt;@type&gt;Text&lt;/@type&gt; {
+ &lt;@name&gt;horizontalAlignment&lt;/@name&gt;: &lt;@name&gt;Qt&lt;/@name&gt;.&lt;@name&gt;AlignHCenter&lt;/@name&gt;; &lt;@name&gt;verticalAlignment&lt;/@name&gt;: &lt;@name&gt;Qt&lt;/@name&gt;.&lt;@name&gt;AlignVCenter&lt;/@name&gt;
+ &lt;@name&gt;anchors&lt;/@name&gt;.fill: &lt;@name&gt;parent&lt;/@name&gt;
+ &lt;@name&gt;text&lt;/@name&gt;: &lt;@name&gt;stack&lt;/@name&gt;.&lt;@name&gt;children&lt;/@name&gt;[&lt;@name&gt;index&lt;/@name&gt;].&lt;@name&gt;title&lt;/@name&gt;
+ &lt;@name&gt;elide&lt;/@name&gt;: &lt;@name&gt;Text&lt;/@name&gt;.&lt;@name&gt;ElideRight&lt;/@name&gt;
+ &lt;@name&gt;font&lt;/@name&gt;.bold: &lt;@name&gt;tabWidget&lt;/@name&gt;.&lt;@name&gt;current&lt;/@name&gt; &lt;@op&gt;==&lt;/@op&gt; &lt;@name&gt;index&lt;/@name&gt;
+ }
+ &lt;@type&gt;MouseArea&lt;/@type&gt; {
+ &lt;@name&gt;anchors&lt;/@name&gt;.fill: &lt;@name&gt;parent&lt;/@name&gt;
+ &lt;@name&gt;onClicked&lt;/@name&gt;: &lt;@name&gt;tabWidget&lt;/@name&gt;.&lt;@name&gt;current&lt;/@name&gt; &lt;@op&gt;=&lt;/@op&gt; &lt;@name&gt;index&lt;/@name&gt;
+ }
+ }
+ }
+ }
+
+ &lt;@type&gt;Item&lt;/@type&gt; {
+ &lt;@name&gt;id&lt;/@name&gt;: &lt;@name&gt;stack&lt;/@name&gt;
+ &lt;@name&gt;width&lt;/@name&gt;: &lt;@name&gt;tabWidget&lt;/@name&gt;.&lt;@name&gt;width&lt;/@name&gt;
+ &lt;@name&gt;anchors&lt;/@name&gt;.top: &lt;@name&gt;header&lt;/@name&gt;.&lt;@name&gt;bottom&lt;/@name&gt;; &lt;@name&gt;anchors&lt;/@name&gt;.bottom: &lt;@name&gt;tabWidget&lt;/@name&gt;.&lt;@name&gt;bottom&lt;/@name&gt;
+ }
+}</code>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-demo-cpp.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-demo-cpp.webxml
new file mode 100644
index 000000000..fd0268d30
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-demo-cpp.webxml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="demos/demo/demo.cpp" href="test-demos-demo-demo-cpp.html" title="demo.cpp Example File" fulltitle="demo.cpp Example File" subtitle="demos/demo/demo.cpp">
+ <description>
+ <code>&lt;@comment&gt;// Copyright (C) 2023 The Qt Company Ltd.&lt;/@comment&gt;
+&lt;@comment&gt;// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0&lt;/@comment&gt;
+
+&lt;@type&gt;bool&lt;/@type&gt; isOverThousand(&lt;@type&gt;int&lt;/@type&gt; n)
+{
+ &lt;@keyword&gt;if&lt;/@keyword&gt; (n &lt;@op&gt;&amp;gt;&lt;/@op&gt; &lt;@number&gt;1'000&lt;/@number&gt;)
+ &lt;@keyword&gt;return&lt;/@keyword&gt; &lt;@keyword&gt;true&lt;/@keyword&gt;;
+ &lt;@keyword&gt;return&lt;/@keyword&gt; &lt;@keyword&gt;false&lt;/@keyword&gt;;
+}</code>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-demo-pro.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-demo-pro.webxml
new file mode 100644
index 000000000..45f8457e9
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-demo-pro.webxml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="demos/demo/demo.pro" href="test-demos-demo-demo-pro.html" title="demo.pro Example File" fulltitle="demo.pro Example File" subtitle="demos/demo/demo.pro">
+ <description>
+ <code>TEMPLATE = aux
+message(&amp;quot;Nothing to see here.&amp;quot;)</code>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-dontxclude-cmakelists-txt.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-dontxclude-cmakelists-txt.webxml
new file mode 100644
index 000000000..effb3cfb9
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-dontxclude-cmakelists-txt.webxml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="demos/demo/dontxclude/CMakeLists.txt" href="test-demos-demo-dontxclude-cmakelists-txt.html" title="CMakeLists.txt Example File" fulltitle="CMakeLists.txt Example File" subtitle="demos/demo/dontxclude/CMakeLists.txt">
+ <description>
+ <code>cmake_minimum_required(VERSION 3.16)
+project (DONTXCLUDEDIRS_QDOCTEST)</code>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-example.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-example.webxml
new file mode 100644
index 000000000..71472790d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-demo-example.webxml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="demos/demo" href="test-demos-demo-example.html" status="active" location="demo.qdoc" documented="true" subtype="example" title="Demo" fulltitle="Demo" subtitle="">
+ <description>
+ <image href="images/leonardo-da-vinci.png"/>
+ <code> if (n &gt; 1'000)
+ return true;</code>
+ <para>Files:</para>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="demos/demo/demo.cpp" href="test-demos-demo-demo-cpp.html" type="page" page="demo.cpp Example File">demos/demo/demo.cpp</link>
+ </para>
+ </item>
+ <item>
+ <para>
+ <link raw="demos/demo/demo.pro" href="test-demos-demo-demo-pro.html" type="page" page="demo.pro Example File">demos/demo/demo.pro</link>
+ </para>
+ </item>
+ <item>
+ <para>
+ <link raw="demos/demo/dontxclude/CMakeLists.txt" href="test-demos-demo-dontxclude-cmakelists-txt.html" type="page" page="CMakeLists.txt Example File">demos/demo/dontxclude/CMakeLists.txt</link>
+ </para>
+ </item>
+ </list>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-hidden-example.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-hidden-example.webxml
new file mode 100644
index 000000000..4f87887ed
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-demos-hidden-example.webxml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="demos/hidden" href="test-demos-hidden-example.html" status="active" location="hidden.qdoc" documented="true" subtype="example" title="Hidden Demo" fulltitle="Hidden Demo" subtitle="" brief="Tagged 'broken', does not appear in examples-manifest.xml">
+ <description>
+ <brief>Tagged 'broken', does not appear in examples-manifest.xml.</brief>
+ <para>Also missing an image, but that's OK as it's broken anyway.</para>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-empty-qmlmodule.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-empty-qmlmodule.webxml
new file mode 100644
index 000000000..5d24b3077
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-empty-qmlmodule.webxml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document/>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-nover-qmlmodule.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-nover-qmlmodule.webxml
new file mode 100644
index 000000000..5d24b3077
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test-nover-qmlmodule.webxml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document/>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test.index
new file mode 100644
index 000000000..8f26aa523
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/test.index
@@ -0,0 +1,226 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="A test project for QDoc build artifacts" version="6.2.11" project="Test">
+ <namespace name="" status="active" access="public" module="test">
+ <function name="QDOCTEST_MACRO" href="testqdoc.html#QDOCTEST_MACRO" status="active" access="public" documented="true" related="0" since="Test 0.9" meta="macrowithoutparams" signature="QDOCTEST_MACRO"/>
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ </function>
+ <qmlclass name="AbstractParent" qml-module-name="QDoc.Test" fullname="QDoc.Test.AbstractParent" href="qml-qdoc-test-abstractparent.html" status="active" access="public" abstract="true" location="parent.qdoc" since="1.1" documented="true" title="AbstractParent" fulltitle="AbstractParent" subtitle="" brief="Abstract base QML type">
+ <function name="name" fullname="QDoc.Test.AbstractParent.name" href="qml-qdoc-test-abstractparent.html#name-method" status="active" access="public" location="parent.qdoc" documented="true" meta="qmlmethod" type="void"/>
+ <function name="name" fullname="QDoc.Test.AbstractParent.name" href="qml-qdoc-test-abstractparent.html#name-method" status="active" access="public" location="parent.qdoc" documented="true" meta="qmlmethod" type="void">
+ <parameter type="Child" name="child" default=""/>
+ <parameter type="" name="name" default=""/>
+ </function>
+ <function name="rear" fullname="QDoc.Test.AbstractParent.rear" href="qml-qdoc-test-abstractparent.html#rear-method" status="active" access="public" location="parent.qdoc" documented="true" meta="qmlmethod" type="void">
+ <parameter type="Child" name="child" default=""/>
+ <parameter type="var" name="method" default="Strict"/>
+ </function>
+ <qmlproperty name="children" fullname="QDoc.Test.AbstractParent.children" status="active" access="public" location="parent.qdoc" documented="true" type="list&lt;Child&gt;" attached="false" writable="true" brief="Children of the type"/>
+ <qmlproperty name="name" fullname="QDoc.Test.AbstractParent.name" status="active" access="public" location="parent.qdoc" documented="true" type="string" attached="false" writable="true" brief="Name of this parent"/>
+ </qmlclass>
+ <page name="autolinking.html" href="autolinking.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Autolinking" fulltitle="Autolinking" subtitle="">
+ <contents name="testqdoc" title="TestQDoc" level="1"/>
+ <contents name="someprop" title="someProp" level="1"/>
+ </page>
+ <page name="cmaketest" href="test-cmaketest-example.html" status="active" location="cmaketest.qdoc" documented="true" subtype="example" title="CMake Example Project" fulltitle="CMake Example Project" subtitle=""/>
+ <qmlclass name="Child" qml-module-name="QDoc.Test" qml-base-type="QDoc.Test::AbstractParent" fullname="QDoc.Test.Child" href="qml-qdoc-test-child.html" status="active" access="public" location="parent.qdoc" since="1.1" documented="true" title="Child" fulltitle="Child" subtitle="" brief="A Child inheriting its parent">
+ <function name="name" fullname="QDoc.Test.Child.name" href="qml-qdoc-test-child.html#name-method" status="active" access="public" location="parent.qdoc" documented="true" meta="qmlmethod" type="void">
+ <parameter type="Child" name="child" default=""/>
+ <parameter type="" name="name" default=""/>
+ </function>
+ <qmlproperty name="name" fullname="QDoc.Test.Child.name" href="qml-qdoc-test-child.html#name-prop" status="active" access="public" location="parent.qdoc" documented="true" type="string" attached="false" writable="true" brief="Name of this child"/>
+ </qmlclass>
+ <page name="classes.html" href="classes.html" status="active" location="unseenclass.qdoc" documented="true" subtype="page" title="Classes" fulltitle="Classes" subtitle=""/>
+ <namespace name="CrossModuleRef" href="crossmoduleref.html" status="active" access="public" location="testcpp.h" since="3.0" documented="true" module="TestCPP" brief="Namespace that has documented functions in multiple modules">
+ <function name="documentMe" fullname="CrossModuleRef::documentMe" href="crossmoduleref.html#documentMe" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void documentMe()"/>
+ </namespace>
+ <page name="demos/demo" href="test-demos-demo-example.html" status="active" location="demo.qdoc" documented="true" subtype="example" title="Demo" fulltitle="Demo" subtitle=""/>
+ <qmlclass name="DocTest" qml-module-name="QDoc.Test" fullname="QDoc.Test.DocTest" href="qml-qdoc-test-doctest.html" status="active" access="public" location="DocTest.qml" since="QDoc.Test 0.9" documented="true" title="DocTest" fulltitle="DocTest" subtitle="" brief="Represents a doc test case">
+ <contents name="introduction" title="Introduction" level="1"/>
+ <function name="completed" fullname="QDoc.Test.DocTest.completed" href="qml-qdoc-test-doctest.html#completed-signal" status="active" access="public" documented="true" meta="qmlsignal"/>
+ <function name="fail" fullname="QDoc.Test.DocTest.fail" href="qml-qdoc-test-doctest.html#fail-method" status="active" access="public" location="DocTest.qml" documented="true" since="QDoc.Test 1.0" meta="qmlmethod">
+ <parameter type="" name="message" default="&quot;oops&quot;"/>
+ </function>
+ <function name="fail_hard" fullname="QDoc.Test.DocTest.fail_hard" href="qml-qdoc-test-doctest.html#fail_hard-method" status="active" access="public" documented="true" meta="qmlmethod">
+ <parameter type="" name="msg" default="&quot;facepalm&quot;"/>
+ <parameter type="" name="option" default="123"/>
+ </function>
+ <function name="foo" fullname="QDoc.Test.DocTest.foo" href="qml-qdoc-test-doctest.html#foo-signal" status="active" access="public" location="DocTest.qml" documented="true" meta="qmlsignal">
+ <parameter type="var" name="bar" default=""/>
+ </function>
+ <function name="itsHappening" fullname="QDoc.Test.DocTest.itsHappening" href="qml-qdoc-test-doctest.html#itsHappening-signal" status="active" access="public" documented="true" meta="qmlsignal">
+ <parameter type="bool" name="really" default=""/>
+ </function>
+ <qmlproperty name="active" fullname="QDoc.Test.DocTest.active" href="qml-qdoc-test-doctest.html#active-prop" status="active" access="public" documented="true" type="bool" attached="false" writable="true"/>
+ <qmlproperty name="name" fullname="QDoc.Test.DocTest.name" href="qml-qdoc-test-doctest.html#name-prop" status="active" access="public" documented="true" type="string" attached="false" writable="true" required="true"/>
+ </qmlclass>
+ <qmlclass name="DocTest" qml-module-name="Test.NoVer" fullname="Test.NoVer.DocTest" href="qml-test-nover-doctest.html" status="active" access="public" location="DocTest.qml" since="1.1" documented="true" title="DocTest" fulltitle="DocTest" subtitle="" brief="Shadows the type name in QDoc.Test module"/>
+ <class name="DontLinkToMe" href="dontlinktome.html" status="ignored" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="Class that does not generate documentation"/>
+ <page name="demos/hidden" href="test-demos-hidden-example.html" status="active" location="hidden.qdoc" documented="true" subtype="example" title="Hidden Demo" fulltitle="Hidden Demo" subtitle="" brief="Tagged 'broken', does not appear in examples-manifest.xml"/>
+ <page name="obsolete-classes.html" href="obsolete-classes.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Obsolete Classes" fulltitle="Obsolete Classes" subtitle="">
+ <contents name="classes-with-obsolete-members" title="Classes with obsolete members" level="1"/>
+ <contents name="testqdoc" title="TestQDoc" level="2"/>
+ </page>
+ <qmlclass name="OldType" qml-module-name="QDoc.Test" fullname="QDoc.Test.OldType" href="qml-qdoc-test-oldtype.html" status="deprecated" access="public" since="1.1" documented="true" title="OldType" fulltitle="OldType" subtitle="" brief="Deprecated old type"/>
+ <qmlclass name="ProgressBar" qml-module-name="UIComponents" fullname="UIComponents.ProgressBar" href="qml-uicomponents-progressbar.html" status="active" access="public" location="ProgressBar.qml" documented="true" title="ProgressBar" fulltitle="ProgressBar" subtitle="" brief="A component that shows the progress of an event">
+ <qmlproperty name="color" fullname="UIComponents.ProgressBar.color" href="qml-uicomponents-progressbar.html#color-prop" status="active" access="public" documented="true" type="color" attached="false" writable="true"/>
+ <qmlproperty name="maximum" fullname="UIComponents.ProgressBar.maximum" href="qml-uicomponents-progressbar.html#maximum-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true"/>
+ <qmlproperty name="minimum" fullname="UIComponents.ProgressBar.minimum" href="qml-uicomponents-progressbar.html#minimum-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true"/>
+ <qmlproperty name="secondColor" fullname="UIComponents.ProgressBar.secondColor" href="qml-uicomponents-progressbar.html#secondColor-prop" status="active" access="public" documented="true" type="color" attached="false" writable="true"/>
+ <qmlproperty name="value" fullname="UIComponents.ProgressBar.value" href="qml-uicomponents-progressbar.html#value-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true"/>
+ </qmlclass>
+ <page name="componentset" href="test-componentset-example.html" status="active" location="examples.qdoc" documented="true" subtype="example" title="QML Documentation Example" fulltitle="QML Documentation Example" subtitle="" brief="Example for documenting QML types">
+ <contents name="qml-class" title="QML Class" level="1"/>
+ <contents name="properties-signals-handlers-and-methods" title="Properties, Signals, Handlers, and Methods" level="1"/>
+ <contents name="internal-documentation" title="Internal Documentation" level="2"/>
+ <contents name="qml-types-with-c-implementation" title="QML Types with C++ Implementation" level="1"/>
+ </page>
+ <page name="qmlmodules.html" href="qmlmodules.html" status="active" location="modules.qdoc" documented="true" subtype="page" title="QML Modules" fulltitle="QML Modules" subtitle="">
+ <contents name="qml-types" title="QML types" level="1"/>
+ <contents name="qml-value-types" title="QML value types" level="1"/>
+ </page>
+ <page name="https://wiki.qt.io/QProperty" href="https://wiki.qt.io/QProperty" status="active" location="properties.qdoc" documented="true" subtype="externalpage" title="QProperty" fulltitle="QProperty" subtitle=""/>
+ <class name="SeenClass" href="seenclass.html" status="active" access="public" location="dont.h" since="2.0" documented="true" module="TestCPP" brief="A public but undocumented class"/>
+ <qmlclass name="Switch" qml-module-name="UIComponents" fullname="UIComponents.Switch" href="qml-uicomponents-switch.html" status="active" access="public" location="Switch.qml" documented="true" title="Switch" fulltitle="Switch" subtitle="" brief="A component that can be turned on or off">
+ <function name="toggle" fullname="UIComponents.Switch.toggle" href="qml-uicomponents-switch.html#toggle-method" status="active" access="public" documented="true" meta="qmlmethod"/>
+ <qmlproperty name="on" fullname="UIComponents.Switch.on" href="qml-uicomponents-switch.html#on-prop" status="active" access="public" documented="true" type="bool" attached="false" writable="true"/>
+ </qmlclass>
+ <qmlclass name="TabWidget" qml-module-name="UIComponents" fullname="UIComponents.TabWidget" href="qml-uicomponents-tabwidget.html" status="active" access="public" location="TabWidget.qml" documented="true" title="TabWidget" fulltitle="TabWidget" subtitle="" brief="A widget that places its children as tabs">
+ <contents name="adding-tabs" title="Adding Tabs" level="1"/>
+ <qmlproperty name="current" fullname="UIComponents.TabWidget.current" href="qml-uicomponents-tabwidget.html#current-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true"/>
+ <qmlproperty name="sampleReadOnlyProperty" fullname="UIComponents.TabWidget.sampleReadOnlyProperty" href="qml-uicomponents-tabwidget.html#sampleReadOnlyProperty-prop" status="active" access="public" documented="true" type="int" attached="false" writable="false"/>
+ </qmlclass>
+ <namespace name="TestQDoc" href="testqdoc.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="A namespace">
+ <contents name="usage" title="Usage" level="1"/>
+ <function name="QDOCTEST_MACRO" href="testqdoc.html#QDOCTEST_MACRO" status="active" access="public" documented="true" related="0" since="Test 0.9" meta="macrowithoutparams" signature="QDOCTEST_MACRO"/>
+ <class threadsafety="reentrant" name="Test" fullname="TestQDoc::Test" href="testqdoc-test.html" status="active" access="public" location="testcpp.h" since="1.1" documented="true" groups="cpptypes,testgroup" module="TestCPP" brief="A class in a namespace">
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ </function>
+ <function name="Test" fullname="TestQDoc::Test::Test" href="testqdoc-test.html#Test" status="active" access="public" location="testcpp.h" documented="true" meta="constructor" signature="Test()"/>
+ <function name="anotherObsoleteMember" fullname="TestQDoc::Test::anotherObsoleteMember" href="testqdoc-test-obsolete.html#anotherObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void anotherObsoleteMember()"/>
+ <function name="deprecatedMember" fullname="TestQDoc::Test::deprecatedMember" href="testqdoc-test-obsolete.html#deprecatedMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void deprecatedMember()"/>
+ <function name="funcPtr" fullname="TestQDoc::Test::funcPtr" href="testqdoc-test.html#funcPtr" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void (*)(bool)" signature="void (*)(bool) funcPtr(bool b, const char *s)">
+ <parameter type="bool" name="b" default=""/>
+ <parameter type="const char *" name="s" default=""/>
+ </function>
+ <function name="inlineFunction" fullname="TestQDoc::Test::inlineFunction" href="testqdoc-test.html#inlineFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" brief="An inline function, documented using the \fn QDoc command" signature="void inlineFunction()"/>
+ <function name="methodWithEmDashInItsDocs" fullname="TestQDoc::Test::methodWithEmDashInItsDocs" href="testqdoc-test.html#methodWithEmDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEmDashInItsDocs()"/>
+ <function name="methodWithEnDashInItsDocs" fullname="TestQDoc::Test::methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEnDashInItsDocs()"/>
+ <function name="obsoleteMember" fullname="TestQDoc::Test::obsoleteMember" href="testqdoc-test-obsolete.html#obsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void obsoleteMember()"/>
+ <function name="operator++" fullname="TestQDoc::Test::operator++" href="testqdoc-test-obsolete.html#operator-2b-2b" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator++()"/>
+ <function name="operator--" fullname="TestQDoc::Test::operator--" href="testqdoc-test-obsolete.html#operator--" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator--()"/>
+ <function name="operator=" fullname="TestQDoc::Test::operator=" href="testqdoc-test.html#operator-eq" status="active" access="public" location="testcpp.h" documented="true" meta="move-assign" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator=(TestQDoc::Test &amp;&amp;other)" groups="testgroup">
+ <parameter type="TestQDoc::Test &amp;&amp;" name="other" default=""/>
+ </function>
+ <function name="operator==" href="testqdoc-test.html#operator-eq-eq" status="active" access="public" location="testcpp.h" documented="true" related="2" meta="plain" type="bool" signature="bool operator==(const TestQDoc::Test &amp;lhs, const TestQDoc::Test &amp;rhs)">
+ <parameter type="const TestQDoc::Test &amp;" name="lhs" default=""/>
+ <parameter type="const TestQDoc::Test &amp;" name="rhs" default=""/>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload" status="active" access="protected" location="testcpp.h" documented="true" meta="plain" type="void" signature="void overload()"/>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload-1" status="active" access="protected" location="testcpp.h" documented="true" since="Test 1.2" meta="plain" overload="true" overload-number="1" type="void" signature="void overload(bool b)">
+ <parameter type="bool" name="b" default=""/>
+ </function>
+ <function name="someFunction" fullname="TestQDoc::Test::someFunction" href="testqdoc-test.html#someFunction" status="active" access="public" location="testcpp.h" documented="true" since="Test 1.0" meta="plain" type="int" signature="int someFunction(int, int v)">
+ <parameter type="int" name="" default=""/>
+ <parameter type="int" name="v" default="0"/>
+ </function>
+ <function name="someFunctionDefaultArg" fullname="TestQDoc::Test::someFunctionDefaultArg" href="testqdoc-test.html#someFunctionDefaultArg" threadsafety="non-reentrant" status="active" access="public" location="testcpp.h" documented="true" since="2.0" meta="plain" const="true" type="void" signature="void someFunctionDefaultArg(int i, bool b) const" groups="testgroup">
+ <parameter type="int" name="i" default=""/>
+ <parameter type="bool" name="b" default="false"/>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::Test::virtualFun" href="testqdoc-test.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" type="void" signature="void virtualFun()"/>
+ <typedef name="SomeType" fullname="TestQDoc::Test::SomeType" href="testqdoc-test.html#SomeType-typedef" status="active" access="public" location="testcpp.h" documented="true"/>
+ </class>
+ <class name="TestDerived" fullname="TestQDoc::TestDerived" href="testqdoc-testderived.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" bases="TestQDoc::Test" module="TestCPP" brief="A class in a namespace, derived from Test">
+ <function name="bindableProp" fullname="TestQDoc::TestDerived::bindableProp" href="testqdoc-testderived.html#bindableProp-prop" status="active" access="public" location="testcpp.h" meta="plain" associated-property="bindableProp" type="QBindable&lt;QString&gt;" signature="QBindable&lt;QString&gt; bindableProp()"/>
+ <function name="bindablePropChanged" fullname="TestQDoc::TestDerived::bindablePropChanged" href="testqdoc-testderived.html#bindableProp-prop" status="active" access="public" location="testcpp.h" meta="signal" associated-property="bindableProp" type="void" signature="void bindablePropChanged()"/>
+ <function name="boolProp" fullname="TestQDoc::TestDerived::boolProp" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" meta="plain" associated-property="boolProp" type="bool" signature="bool boolProp()"/>
+ <function name="boolPropChanged" fullname="TestQDoc::TestDerived::boolPropChanged" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" meta="signal" associated-property="boolProp" type="void" signature="void boolPropChanged()"/>
+ <function name="emitSomething" fullname="TestQDoc::TestDerived::emitSomething" href="testqdoc-testderived.html#emitSomething" status="active" access="public" location="testcpp.h" documented="true" meta="signal" type="void" signature="void emitSomething()"/>
+ <function name="getInt" fullname="TestQDoc::TestDerived::getInt" href="testqdoc-testderived.html#intProp-prop" status="active" access="public" location="testcpp.h" meta="plain" associated-property="intProp" type="int *" signature="int * getInt()"/>
+ <function name="id" fullname="TestQDoc::TestDerived::id" href="testqdoc-testderived.html#id" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" override="true" type="int" signature="int id() override"/>
+ <function name="invokeMe" fullname="TestQDoc::TestDerived::invokeMe" href="testqdoc-testderived.html#invokeMe" status="active" access="public" location="testcpp.h" documented="true" meta="plain" const="true" type="void" brief="Something invokable" signature="void invokeMe() const"/>
+ <function name="name" fullname="TestQDoc::TestDerived::name" href="testqdoc-testderived.html#name-prop" status="active" access="public" location="testcpp.h" meta="plain" const="true" associated-property="name" type="const QString *" signature="const QString * name() const"/>
+ <function name="resetBoolProp" fullname="TestQDoc::TestDerived::resetBoolProp" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" meta="slot" associated-property="boolProp" type="void" signature="void resetBoolProp()"/>
+ <function name="setBindableProp" fullname="TestQDoc::TestDerived::setBindableProp" href="testqdoc-testderived.html#bindableProp-prop" status="active" access="public" location="testcpp.h" meta="slot" associated-property="bindableProp" type="void" signature="void setBindableProp(const QString &amp;s)">
+ <parameter type="const QString &amp;" name="s" default=""/>
+ </function>
+ <function name="setBoolProp" fullname="TestQDoc::TestDerived::setBoolProp" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" meta="slot" associated-property="boolProp" type="void" signature="void setBoolProp(bool b)">
+ <parameter type="bool" name="b" default=""/>
+ </function>
+ <function name="someProp" fullname="TestQDoc::TestDerived::someProp" href="testqdoc-testderived.html#someProp-prop" status="active" access="public" location="testcpp.h" meta="plain" associated-property="someProp" type="const QString &amp;" signature="const QString &amp; someProp()"/>
+ <function name="someValue" fullname="TestQDoc::TestDerived::someValue" href="testqdoc-testderived.html#someValue" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::TestDerived::NotTypedef" signature="TestQDoc::TestDerived::NotTypedef someValue()"/>
+ <function name="staticObsoleteMember" fullname="TestQDoc::TestDerived::staticObsoleteMember" href="testqdoc-testderived-obsolete.html#staticObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" static="true" type="void" signature="void staticObsoleteMember()"/>
+ <function name="virtualFun" fullname="TestQDoc::TestDerived::virtualFun" href="testqdoc-testderived.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" override="true" type="void" signature="void virtualFun() override"/>
+ <typedef name="DerivedType" fullname="TestQDoc::TestDerived::DerivedType" href="testqdoc-testderived.html#DerivedType-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="Test::SomeType"/>
+ <typedef name="NotTypedef" fullname="TestQDoc::TestDerived::NotTypedef" href="testqdoc-testderived.html#NotTypedef-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="int"/>
+ <property name="bindableProp" fullname="TestQDoc::TestDerived::bindableProp" href="testqdoc-testderived.html#bindableProp-prop" status="active" access="public" location="testcpp.h" documented="true" bindable="true">
+ <getter name="bindableProp"/>
+ <setter name="setBindableProp"/>
+ <notifier name="bindablePropChanged"/>
+ </property>
+ <property name="boolProp" fullname="TestQDoc::TestDerived::boolProp" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" documented="true">
+ <getter name="boolProp"/>
+ <setter name="setBoolProp"/>
+ <resetter name="resetBoolProp"/>
+ <notifier name="boolPropChanged"/>
+ </property>
+ <property name="intProp" fullname="TestQDoc::TestDerived::intProp" href="testqdoc-testderived.html#intProp-prop" status="active" access="public" location="testcpp.h" documented="true" writable="false">
+ <getter name="getInt"/>
+ </property>
+ <property name="name" fullname="TestQDoc::TestDerived::name" href="testqdoc-testderived.html#name-prop" status="active" access="public" location="testcpp.h" documented="true" writable="false" brief="Name">
+ <getter name="name"/>
+ </property>
+ <property name="someProp" fullname="TestQDoc::TestDerived::someProp" href="testqdoc-testderived.html#someProp-prop" status="active" access="public" location="testcpp.h" documented="true" bindable="true" writable="false">
+ <getter name="someProp"/>
+ </property>
+ </class>
+ </namespace>
+ <qmlclass name="TheType" qml-module-name="TheModule" fullname="TheModule.TheType" href="qml-themodule-thetype.html" status="active" access="public" location="properties.qdoc" documented="true" title="TheType" fulltitle="TheType" subtitle="">
+ <qmlproperty name="name" fullname="TheModule.TheType.name" href="qml-themodule-thetype.html#name-prop" status="active" access="public" location="properties.qdoc" documented="true" type="string" attached="false" writable="false" brief="Read-only status of this property is resolved from Q_PROPERTY"/>
+ </qmlclass>
+ <qmlclass name="Type" qml-module-name="QDoc.Test" fullname="QDoc.Test.Type" href="qml-qdoc-test-type.html" status="active" access="public" since="1.1" documented="true" title="Type" fulltitle="Type" subtitle="" brief="A QML type documented in a .cpp file">
+ <function name="completed" fullname="QDoc.Test.Type.completed" href="qml-qdoc-test-type.html#completed-signal" status="active" access="public" documented="true" meta="qmlsignal">
+ <parameter type="int" name="status" default=""/>
+ </function>
+ <function name="configured" fullname="QDoc.Test.Type.configured" href="qml-qdoc-test-type.html#configured-signal" status="active" access="public" documented="true" meta="qmlsignal"/>
+ <function name="copy" fullname="QDoc.Test.Type.copy" href="qml-qdoc-test-type.html#copy-method" status="active" access="public" documented="true" meta="qmlmethod" type="Type">
+ <parameter type="" name="a" default=""/>
+ </function>
+ <function name="deprecatedMethod" fullname="QDoc.Test.Type.deprecatedMethod" href="qml-qdoc-test-type-obsolete.html#deprecatedMethod-method" status="deprecated" access="public" documented="true" meta="qmlmethod"/>
+ <function name="disable" fullname="QDoc.Test.Type.disable" href="qml-qdoc-test-type.html#disable-method" status="active" access="public" documented="true" meta="qmlmethod"/>
+ <function name="enable" fullname="QDoc.Test.Type.enable" href="qml-qdoc-test-type.html#enable-method" status="active" access="public" documented="true" meta="qmlmethod"/>
+ <function name="futureDeprecated" fullname="QDoc.Test.Type.futureDeprecated" href="qml-qdoc-test-type.html#futureDeprecated-method" status="active" access="public" documented="true" meta="qmlmethod"/>
+ <function name="group.created" fullname="QDoc.Test.Type.group.created" href="qml-qdoc-test-type.html#group.created-signal" status="active" access="public" documented="true" meta="qmlsignal"/>
+ <qmlproperty name="fifth" fullname="QDoc.Test.Type.fifth" href="qml-qdoc-test-type.html#fifth-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true" brief="A group of properties sharing a documentation comment"/>
+ <qmlproperty name="fourth" fullname="QDoc.Test.Type.fourth" href="qml-qdoc-test-type.html#fourth-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true" brief="A group of properties sharing a documentation comment"/>
+ <qmlproperty name="group.first" fullname="QDoc.Test.Type.group.first" href="qml-qdoc-test-type.html#group.first-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true" brief="A property group"/>
+ <qmlproperty name="group.second" fullname="QDoc.Test.Type.group.second" href="qml-qdoc-test-type.html#group.second-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true" brief="A property group"/>
+ <qmlproperty name="group.third" fullname="QDoc.Test.Type.group.third" href="qml-qdoc-test-type.html#group.third-prop" status="active" access="public" documented="true" type="int" attached="false" writable="true" brief="A property group"/>
+ <qmlproperty name="id" fullname="QDoc.Test.Type.id" href="qml-qdoc-test-type.html#id-prop" status="active" access="public" documented="true" type="int" attached="false" writable="false" brief="A read-only property"/>
+ <qmlproperty name="name" fullname="QDoc.Test.Type.name" href="qml-qdoc-test-type.html#name-prop" status="active" access="public" documented="true" type="string" attached="false" writable="true" required="true" brief="Name of the Test"/>
+ <qmlproperty name="type" fullname="QDoc.Test.Type.type" href="qml-qdoc-test-type.html#type-attached-prop" status="active" access="public" documented="true" type="enumeration" attached="true" writable="true"/>
+ <qmlproperty name="group" fullname="QDoc.Test.Type.group" href="qml-qdoc-test-type.html#group-prop" status="active" access="public" documented="true"/>
+ </qmlclass>
+ <qmlclass name="TypeNoVersion" qml-module-name="Test.NoVer" fullname="Test.NoVer.TypeNoVersion" href="qml-test-nover-typenoversion.html" status="active" access="public" since="1.1" documented="true" title="TypeNoVersion" fulltitle="TypeNoVersion" subtitle="" brief="Another QML type documented in a .cpp file"/>
+ <class name="UnseenClass" href="unseenclass.html" status="ignored" access="public" location="dont.h" since="2.0" documented="true" module="TestCPP" brief="A public but undocumented class"/>
+ <qmlclass name="YetAnotherChild" qml-module-name="QDoc.Test" qml-base-type="QDoc.Test::InternParent" fullname="QDoc.Test.YetAnotherChild" href="qml-qdoc-test-yetanotherchild.html" status="active" access="public" location="parent.qdoc" since="1.1" documented="true" title="YetAnotherChild" fulltitle="YetAnotherChild" subtitle="" brief="A type inheriting from internal abstract parent"/>
+ <qmlvaluetype name="int" qml-module-name="QDoc.Test" fullname="QDoc.Test.int" href="qml-int.html" status="active" access="public" location="parent.qdoc" since="1.1" documented="true" title="int" fulltitle="int" subtitle="" brief="An integer value type">
+ <function name="abs" fullname="QDoc.Test.int.abs" href="qml-int.html#abs-method" status="active" access="public" location="parent.qdoc" documented="true" meta="qmlmethod" type="int"/>
+ </qmlvaluetype>
+ <page name="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command" href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command" status="active" location="classlists.qdoc" documented="true" subtype="externalpage" title="reentrant" fulltitle="reentrant" subtitle=""/>
+ <group name="cpptypes" href="cpptypes.html" status="active" location="classlists.qdoc" documented="true" seen="true" title="Test C++ Types"/>
+ <group name="testgroup" href="testgroup.html" status="internal" seen="false" title=""/>
+ <module name="TestCPP" href="testcpp-module.html" status="active" since="2.0" documented="true" seen="true" title="QDoc Test C++ Classes" brief="A test module page">
+ <contents name="linking-to-function-like-things" title="Linking to function-like things" level="1"/>
+ <contents name="section" title="section()" level="2"/>
+ </module>
+ <qmlmodule name="QDoc.Test" qml-module-name="QDoc.Test" qml-module-version="1.1" href="qdoc-test-qmlmodule.html" status="preliminary" since="1.1" documented="true" seen="true" title="QDoc.Test QML Module" brief="QML Types for the Test module"/>
+ <qmlmodule name="Test.Empty" qml-module-name="Test.Empty" qml-module-version="1.0" href="test-empty-qmlmodule.html" status="active" documented="true" seen="true" title="No QML Types Here" brief="A QML module with no member types"/>
+ <qmlmodule name="Test.NoVer" qml-module-name="Test.NoVer" href="test-nover-qmlmodule.html" status="active" since="1.1" documented="true" seen="true" title="Versionless QML Module" brief="QML Types for the Test module without version"/>
+ <qmlmodule name="TheModule" qml-module-name="TheModule" href="themodule-qmlmodule.html" status="active" location="properties.qdoc" documented="true" seen="true" title=""/>
+ <qmlmodule name="UIComponents" qml-module-name="UIComponents" qml-module-version="1.0" href="uicomponents-qmlmodule.html" status="active" location="examples.qdoc" documented="true" seen="true" title="UI Components" brief="Basic set of UI components"/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testcpp-module.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testcpp-module.webxml
new file mode 100644
index 000000000..5d24b3077
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testcpp-module.webxml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document/>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testqdoc-test.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testqdoc-test.webxml
new file mode 100644
index 000000000..9c9766dc0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testqdoc-test.webxml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class threadsafety="reentrant" name="Test" fullname="TestQDoc::Test" href="testqdoc-test.html" status="active" access="public" location="testcpp.h" since="1.1" documented="true" groups="cpptypes,testgroup" module="TestCPP" brief="A class in a namespace">
+ <description>
+ <brief>A class in a namespace.</brief>
+ </description>
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ <description>
+ <brief>A macro with argument <argument>x</argument>.</brief>
+ </description>
+ </function>
+ <function name="Test" fullname="TestQDoc::Test::Test" href="testqdoc-test.html#Test" status="active" access="public" location="testcpp.h" documented="true" meta="constructor" signature="Test()">
+ <description>
+ <para>The constructor is deleted.</para>
+ </description>
+ </function>
+ <function name="anotherObsoleteMember" fullname="TestQDoc::Test::anotherObsoleteMember" href="testqdoc-test-obsolete.html#anotherObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void anotherObsoleteMember()">
+ <description>
+ <para>Use <link raw="obsoleteMember()" href="testqdoc-test.html#obsoleteMember" type="function">obsoleteMember()</link> instead.</para>
+ </description>
+ </function>
+ <function name="deprecatedMember" fullname="TestQDoc::Test::deprecatedMember" href="testqdoc-test-obsolete.html#deprecatedMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void deprecatedMember()">
+ <description>
+ <para>Use <link raw="someFunction()" href="testqdoc-test.html#someFunction" type="function">someFunction()</link> instead.</para>
+ </description>
+ </function>
+ <function name="funcPtr" fullname="TestQDoc::Test::funcPtr" href="testqdoc-test.html#funcPtr" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void (*)(bool)" signature="void (*)(bool) funcPtr(bool b, const char *s)">
+ <parameter type="bool" name="b" default=""/>
+ <parameter type="const char *" name="s" default=""/>
+ <description>
+ <para>Returns a pointer to a function that takes a boolean. Uses <argument>b</argument> and <argument>s</argument>.</para>
+ </description>
+ </function>
+ <function name="inlineFunction" fullname="TestQDoc::Test::inlineFunction" href="testqdoc-test.html#inlineFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" brief="An inline function, documented using the \fn QDoc command" signature="void inlineFunction()">
+ <description>
+ <brief>An inline function, documented using the \fn QDoc command.</brief>
+ </description>
+ </function>
+ <function name="methodWithEmDashInItsDocs" fullname="TestQDoc::Test::methodWithEmDashInItsDocs" href="testqdoc-test.html#methodWithEmDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEmDashInItsDocs()">
+ <description>
+ <para>This method has em dashes in its documentation—as you'll find represented by <teletype type="highlighted">---</teletype> in the sources—here and there. The important bit to note is that when passed e.g. to the \c command, the three hyphens are processed as input to the command and not replaced by an em dash.</para>
+ <para>-----------------------------------------------------------------------</para>
+ <para>People can still add a bunch of dashes, though, without QDoc replacing them all with a series of em dashes.</para>
+ <para>—You can also start a new paragraph with an em dash, if you want to.</para>
+ <see-also>
+ <link raw="methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" type="function">methodWithEnDashInItsDocs</link>
+ </see-also>
+ </description>
+ </function>
+ <function name="methodWithEnDashInItsDocs" fullname="TestQDoc::Test::methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEnDashInItsDocs()">
+ <description>
+ <para>This method has en dashes in its documentation – as you'll find represented by <teletype type="highlighted">--</teletype> in the sources – here and there. The important bit to note is that when passed e.g. to the \c command, the two hyphens are processed as input to the command and not replaced by an en dash. This also applies to code blocks, where otherwise, the decrement operator would get completely borked:</para>
+ <code>for (int i = 42; i &gt; 0; --i)
+ // Do something cool during countdown.</code>
+ <para>...as it would be silly if this would output –i instead of <teletype type="highlighted">--i</teletype>.</para>
+ <para>-----------------------------------------------------------------------</para>
+ <para>It still allows people to add a bunch of dashes, though, without replacing them all with a series of en dashes. Of course, they might want to use the \hr command instead, like this:</para>
+ <para>– You can also start a new paragraph with an en dash, if you want to.</para>
+ <see-also>methodWithEnDashInItsDocs</see-also>
+ </description>
+ </function>
+ <function name="obsoleteMember" fullname="TestQDoc::Test::obsoleteMember" href="testqdoc-test-obsolete.html#obsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void obsoleteMember()">
+ <description>
+ <para>Use <link raw="someFunction()" href="testqdoc-test.html#someFunction" type="function">someFunction()</link> instead.</para>
+ </description>
+ </function>
+ <function name="operator++" fullname="TestQDoc::Test::operator++" href="testqdoc-test-obsolete.html#operator-2b-2b" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator++()">
+ <description/>
+ </function>
+ <function name="operator--" fullname="TestQDoc::Test::operator--" href="testqdoc-test-obsolete.html#operator--" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator--()">
+ <description/>
+ </function>
+ <function name="operator=" fullname="TestQDoc::Test::operator=" href="testqdoc-test.html#operator-eq" status="active" access="public" location="testcpp.h" documented="true" meta="move-assign" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator=(TestQDoc::Test &amp;&amp;other)" groups="testgroup">
+ <parameter type="TestQDoc::Test &amp;&amp;" name="other" default=""/>
+ <description>
+ <para>The move assignment operator is deleted. <argument>other</argument> cannot be moved from.</para>
+ </description>
+ </function>
+ <function name="operator==" href="testqdoc-test.html#operator-eq-eq" status="active" access="public" location="testcpp.h" documented="true" related="2" meta="plain" type="bool" signature="bool operator==(const TestQDoc::Test &amp;lhs, const TestQDoc::Test &amp;rhs)">
+ <parameter type="const TestQDoc::Test &amp;" name="lhs" default=""/>
+ <parameter type="const TestQDoc::Test &amp;" name="rhs" default=""/>
+ <description>
+ <para>Returns true if <argument>lhs</argument> and <argument>rhs</argument> are equal.</para>
+ </description>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload" status="active" access="protected" location="testcpp.h" documented="true" meta="plain" type="void" signature="void overload()">
+ <description/>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload-1" status="active" access="protected" location="testcpp.h" documented="true" since="Test 1.2" meta="plain" overload="true" overload-number="1" type="void" signature="void overload(bool b)">
+ <parameter type="bool" name="b" default=""/>
+ <description/>
+ </function>
+ <function name="someFunction" fullname="TestQDoc::Test::someFunction" href="testqdoc-test.html#someFunction" status="active" access="public" location="testcpp.h" documented="true" since="Test 1.0" meta="plain" type="int" signature="int someFunction(int, int v)">
+ <parameter type="int" name="" default=""/>
+ <parameter type="int" name="v" default="0"/>
+ <description>
+ <para>Function that takes a parameter <argument>v</argument>. Also returns the value of <argument>v</argument>.</para>
+ </description>
+ </function>
+ <function name="someFunctionDefaultArg" fullname="TestQDoc::Test::someFunctionDefaultArg" href="testqdoc-test.html#someFunctionDefaultArg" threadsafety="non-reentrant" status="active" access="public" location="testcpp.h" documented="true" since="2.0" meta="plain" const="true" type="void" signature="void someFunctionDefaultArg(int i, bool b) const" groups="testgroup">
+ <parameter type="int" name="i" default=""/>
+ <parameter type="bool" name="b" default="false"/>
+ <description>
+ <para>Function that takes a parameter <argument>i</argument> and <argument>b</argument>.</para>
+ </description>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::Test::virtualFun" href="testqdoc-test.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" type="void" signature="void virtualFun()">
+ <description>
+ <para>Function that must be reimplemented.</para>
+ </description>
+ </function>
+ <typedef name="SomeType" fullname="TestQDoc::Test::SomeType" href="testqdoc-test.html#SomeType-typedef" status="active" access="public" location="testcpp.h" documented="true">
+ <description>
+ <brief>A typedef.</brief>
+ </description>
+ </typedef>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testqdoc-testderived.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testqdoc-testderived.webxml
new file mode 100644
index 000000000..197cacd1d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testqdoc-testderived.webxml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="TestDerived" fullname="TestQDoc::TestDerived" href="testqdoc-testderived.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" bases="TestQDoc::Test" module="TestCPP" brief="A class in a namespace, derived from Test">
+ <description>
+ <brief>A class in a namespace, derived from <link raw="Test" href="testqdoc-test.html" type="class">Test</link>.</brief>
+ </description>
+ <function name="bindableProp" fullname="TestQDoc::TestDerived::bindableProp" href="testqdoc-testderived.html#bindableProp-prop" status="active" access="public" location="testcpp.h" meta="plain" associated-property="bindableProp" type="QBindable&lt;QString&gt;" signature="QBindable&lt;QString&gt; bindableProp()">
+ <description>
+ <see-also>setBindableProp()</see-also>
+ </description>
+ </function>
+ <function name="bindablePropChanged" fullname="TestQDoc::TestDerived::bindablePropChanged" href="testqdoc-testderived.html#bindableProp-prop" status="active" access="public" location="testcpp.h" meta="signal" associated-property="bindableProp" type="void" signature="void bindablePropChanged()">
+ <description/>
+ </function>
+ <function name="boolProp" fullname="TestQDoc::TestDerived::boolProp" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" meta="plain" associated-property="boolProp" type="bool" signature="bool boolProp()">
+ <description>
+ <see-also>setBoolProp()</see-also>
+ </description>
+ </function>
+ <function name="boolPropChanged" fullname="TestQDoc::TestDerived::boolPropChanged" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" meta="signal" associated-property="boolProp" type="void" signature="void boolPropChanged()">
+ <description/>
+ </function>
+ <function name="emitSomething" fullname="TestQDoc::TestDerived::emitSomething" href="testqdoc-testderived.html#emitSomething" status="active" access="public" location="testcpp.h" documented="true" meta="signal" type="void" signature="void emitSomething()">
+ <description>
+ <para>Emitted when things happen.</para>
+ </description>
+ </function>
+ <function name="getInt" fullname="TestQDoc::TestDerived::getInt" href="testqdoc-testderived.html#intProp-prop" status="active" access="public" location="testcpp.h" meta="plain" associated-property="intProp" type="int *" signature="int * getInt()">
+ <description/>
+ </function>
+ <function name="id" fullname="TestQDoc::TestDerived::id" href="testqdoc-testderived.html#id" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" override="true" type="int" signature="int id() override">
+ <description/>
+ </function>
+ <function name="invokeMe" fullname="TestQDoc::TestDerived::invokeMe" href="testqdoc-testderived.html#invokeMe" status="active" access="public" location="testcpp.h" documented="true" meta="plain" const="true" type="void" brief="Something invokable" signature="void invokeMe() const">
+ <description>
+ <brief>Something invokable.</brief>
+ </description>
+ </function>
+ <function name="name" fullname="TestQDoc::TestDerived::name" href="testqdoc-testderived.html#name-prop" status="active" access="public" location="testcpp.h" meta="plain" const="true" associated-property="name" type="const QString *" signature="const QString * name() const">
+ <description/>
+ </function>
+ <function name="resetBoolProp" fullname="TestQDoc::TestDerived::resetBoolProp" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" meta="slot" associated-property="boolProp" type="void" signature="void resetBoolProp()">
+ <description/>
+ </function>
+ <function name="setBindableProp" fullname="TestQDoc::TestDerived::setBindableProp" href="testqdoc-testderived.html#bindableProp-prop" status="active" access="public" location="testcpp.h" meta="slot" associated-property="bindableProp" type="void" signature="void setBindableProp(const QString &amp;s)">
+ <parameter type="const QString &amp;" name="s" default=""/>
+ <description>
+ <see-also>bindableProp()</see-also>
+ </description>
+ </function>
+ <function name="setBoolProp" fullname="TestQDoc::TestDerived::setBoolProp" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" meta="slot" associated-property="boolProp" type="void" signature="void setBoolProp(bool b)">
+ <parameter type="bool" name="b" default=""/>
+ <description>
+ <see-also>boolProp()</see-also>
+ </description>
+ </function>
+ <function name="someProp" fullname="TestQDoc::TestDerived::someProp" href="testqdoc-testderived.html#someProp-prop" status="active" access="public" location="testcpp.h" meta="plain" associated-property="someProp" type="const QString &amp;" signature="const QString &amp; someProp()">
+ <description/>
+ </function>
+ <function name="someValue" fullname="TestQDoc::TestDerived::someValue" href="testqdoc-testderived.html#someValue" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::TestDerived::NotTypedef" signature="TestQDoc::TestDerived::NotTypedef someValue()">
+ <description>
+ <para>Returns a value using an aliases type.</para>
+ </description>
+ </function>
+ <function name="staticObsoleteMember" fullname="TestQDoc::TestDerived::staticObsoleteMember" href="testqdoc-testderived-obsolete.html#staticObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" static="true" type="void" signature="void staticObsoleteMember()">
+ <description>
+ <para>Static obsolete method.</para>
+ </description>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::TestDerived::virtualFun" href="testqdoc-testderived.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" override="true" type="void" signature="void virtualFun() override">
+ <description/>
+ </function>
+ <typedef name="DerivedType" fullname="TestQDoc::TestDerived::DerivedType" href="testqdoc-testderived.html#DerivedType-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="Test::SomeType">
+ <description>
+ <para>An aliased typedef.</para>
+ </description>
+ </typedef>
+ <typedef name="NotTypedef" fullname="TestQDoc::TestDerived::NotTypedef" href="testqdoc-testderived.html#NotTypedef-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="int">
+ <description>
+ <para>I'm an alias, not a typedef.</para>
+ </description>
+ </typedef>
+ <property name="bindableProp" fullname="TestQDoc::TestDerived::bindableProp" href="testqdoc-testderived.html#bindableProp-prop" status="active" access="public" location="testcpp.h" documented="true" bindable="true">
+ <getter name="bindableProp"/>
+ <setter name="setBindableProp"/>
+ <notifier name="bindablePropChanged"/>
+ <description>
+ <para>Some property.</para>
+ <see-also>
+ <link raw="someProp" href="testqdoc-testderived.html#someProp-prop" type="function">someProp</link>
+ </see-also>
+ </description>
+ </property>
+ <property name="boolProp" fullname="TestQDoc::TestDerived::boolProp" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" documented="true">
+ <getter name="boolProp"/>
+ <setter name="setBoolProp"/>
+ <resetter name="resetBoolProp"/>
+ <notifier name="boolPropChanged"/>
+ <description>
+ <para>A boolean property.</para>
+ </description>
+ </property>
+ <property name="intProp" fullname="TestQDoc::TestDerived::intProp" href="testqdoc-testderived.html#intProp-prop" status="active" access="public" location="testcpp.h" documented="true" writable="false">
+ <getter name="getInt"/>
+ <description>
+ <para>An integer property.</para>
+ </description>
+ </property>
+ <property name="name" fullname="TestQDoc::TestDerived::name" href="testqdoc-testderived.html#name-prop" status="active" access="public" location="testcpp.h" documented="true" writable="false" brief="Name">
+ <getter name="name"/>
+ <description>
+ <brief>This property holds a name..</brief>
+ </description>
+ </property>
+ <property name="someProp" fullname="TestQDoc::TestDerived::someProp" href="testqdoc-testderived.html#someProp-prop" status="active" access="public" location="testcpp.h" documented="true" bindable="true" writable="false">
+ <getter name="someProp"/>
+ <description>
+ <para>Another property.</para>
+ </description>
+ </property>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testqdoc.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testqdoc.webxml
new file mode 100644
index 000000000..dfd9eeb9d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testqdoc.webxml
@@ -0,0 +1,253 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <namespace name="TestQDoc" href="testqdoc.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="A namespace">
+ <contents name="usage" title="Usage" level="1"/>
+ <description>
+ <brief>A namespace.</brief>
+ <section id="usage">
+ <heading level="1">Usage</heading>
+ <para>This namespace is for testing QDoc output.</para>
+ </section>
+ </description>
+ <function name="QDOCTEST_MACRO" href="testqdoc.html#QDOCTEST_MACRO" status="active" access="public" documented="true" related="0" since="Test 0.9" meta="macrowithoutparams" signature="QDOCTEST_MACRO">
+ <description/>
+ </function>
+ <class threadsafety="reentrant" name="Test" fullname="TestQDoc::Test" href="testqdoc-test.html" status="active" access="public" location="testcpp.h" since="1.1" documented="true" groups="cpptypes,testgroup" module="TestCPP" brief="A class in a namespace">
+ <description>
+ <brief>A class in a namespace.</brief>
+ </description>
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ <description>
+ <brief>A macro with argument <argument>x</argument>.</brief>
+ </description>
+ </function>
+ <function name="Test" fullname="TestQDoc::Test::Test" href="testqdoc-test.html#Test" status="active" access="public" location="testcpp.h" documented="true" meta="constructor" signature="Test()">
+ <description>
+ <para>The constructor is deleted.</para>
+ </description>
+ </function>
+ <function name="anotherObsoleteMember" fullname="TestQDoc::Test::anotherObsoleteMember" href="testqdoc-test-obsolete.html#anotherObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void anotherObsoleteMember()">
+ <description>
+ <para>Use <link raw="obsoleteMember()" href="testqdoc-test.html#obsoleteMember" type="function">obsoleteMember()</link> instead.</para>
+ </description>
+ </function>
+ <function name="deprecatedMember" fullname="TestQDoc::Test::deprecatedMember" href="testqdoc-test-obsolete.html#deprecatedMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void deprecatedMember()">
+ <description>
+ <para>Use <link raw="someFunction()" href="testqdoc-test.html#someFunction" type="function">someFunction()</link> instead.</para>
+ </description>
+ </function>
+ <function name="funcPtr" fullname="TestQDoc::Test::funcPtr" href="testqdoc-test.html#funcPtr" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void (*)(bool)" signature="void (*)(bool) funcPtr(bool b, const char *s)">
+ <parameter type="bool" name="b" default=""/>
+ <parameter type="const char *" name="s" default=""/>
+ <description>
+ <para>Returns a pointer to a function that takes a boolean. Uses <argument>b</argument> and <argument>s</argument>.</para>
+ </description>
+ </function>
+ <function name="inlineFunction" fullname="TestQDoc::Test::inlineFunction" href="testqdoc-test.html#inlineFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" brief="An inline function, documented using the \fn QDoc command" signature="void inlineFunction()">
+ <description>
+ <brief>An inline function, documented using the \fn QDoc command.</brief>
+ </description>
+ </function>
+ <function name="methodWithEmDashInItsDocs" fullname="TestQDoc::Test::methodWithEmDashInItsDocs" href="testqdoc-test.html#methodWithEmDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEmDashInItsDocs()">
+ <description>
+ <para>This method has em dashes in its documentation—as you'll find represented by <teletype type="highlighted">---</teletype> in the sources—here and there. The important bit to note is that when passed e.g. to the \c command, the three hyphens are processed as input to the command and not replaced by an em dash.</para>
+ <para>-----------------------------------------------------------------------</para>
+ <para>People can still add a bunch of dashes, though, without QDoc replacing them all with a series of em dashes.</para>
+ <para>—You can also start a new paragraph with an em dash, if you want to.</para>
+ <see-also>
+ <link raw="methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" type="function">methodWithEnDashInItsDocs</link>
+ </see-also>
+ </description>
+ </function>
+ <function name="methodWithEnDashInItsDocs" fullname="TestQDoc::Test::methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEnDashInItsDocs()">
+ <description>
+ <para>This method has en dashes in its documentation – as you'll find represented by <teletype type="highlighted">--</teletype> in the sources – here and there. The important bit to note is that when passed e.g. to the \c command, the two hyphens are processed as input to the command and not replaced by an en dash. This also applies to code blocks, where otherwise, the decrement operator would get completely borked:</para>
+ <code>for (int i = 42; i &gt; 0; --i)
+ // Do something cool during countdown.</code>
+ <para>...as it would be silly if this would output –i instead of <teletype type="highlighted">--i</teletype>.</para>
+ <para>-----------------------------------------------------------------------</para>
+ <para>It still allows people to add a bunch of dashes, though, without replacing them all with a series of en dashes. Of course, they might want to use the \hr command instead, like this:</para>
+ <para>– You can also start a new paragraph with an en dash, if you want to.</para>
+ <see-also>methodWithEnDashInItsDocs</see-also>
+ </description>
+ </function>
+ <function name="obsoleteMember" fullname="TestQDoc::Test::obsoleteMember" href="testqdoc-test-obsolete.html#obsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void obsoleteMember()">
+ <description>
+ <para>Use <link raw="someFunction()" href="testqdoc-test.html#someFunction" type="function">someFunction()</link> instead.</para>
+ </description>
+ </function>
+ <function name="operator++" fullname="TestQDoc::Test::operator++" href="testqdoc-test-obsolete.html#operator-2b-2b" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator++()">
+ <description/>
+ </function>
+ <function name="operator--" fullname="TestQDoc::Test::operator--" href="testqdoc-test-obsolete.html#operator--" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator--()">
+ <description/>
+ </function>
+ <function name="operator=" fullname="TestQDoc::Test::operator=" href="testqdoc-test.html#operator-eq" status="active" access="public" location="testcpp.h" documented="true" meta="move-assign" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator=(TestQDoc::Test &amp;&amp;other)" groups="testgroup">
+ <parameter type="TestQDoc::Test &amp;&amp;" name="other" default=""/>
+ <description>
+ <para>The move assignment operator is deleted. <argument>other</argument> cannot be moved from.</para>
+ </description>
+ </function>
+ <function name="operator==" href="testqdoc-test.html#operator-eq-eq" status="active" access="public" location="testcpp.h" documented="true" related="2" meta="plain" type="bool" signature="bool operator==(const TestQDoc::Test &amp;lhs, const TestQDoc::Test &amp;rhs)">
+ <parameter type="const TestQDoc::Test &amp;" name="lhs" default=""/>
+ <parameter type="const TestQDoc::Test &amp;" name="rhs" default=""/>
+ <description>
+ <para>Returns true if <argument>lhs</argument> and <argument>rhs</argument> are equal.</para>
+ </description>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload" status="active" access="protected" location="testcpp.h" documented="true" meta="plain" type="void" signature="void overload()">
+ <description/>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload-1" status="active" access="protected" location="testcpp.h" documented="true" since="Test 1.2" meta="plain" overload="true" overload-number="1" type="void" signature="void overload(bool b)">
+ <parameter type="bool" name="b" default=""/>
+ <description/>
+ </function>
+ <function name="someFunction" fullname="TestQDoc::Test::someFunction" href="testqdoc-test.html#someFunction" status="active" access="public" location="testcpp.h" documented="true" since="Test 1.0" meta="plain" type="int" signature="int someFunction(int, int v)">
+ <parameter type="int" name="" default=""/>
+ <parameter type="int" name="v" default="0"/>
+ <description>
+ <para>Function that takes a parameter <argument>v</argument>. Also returns the value of <argument>v</argument>.</para>
+ </description>
+ </function>
+ <function name="someFunctionDefaultArg" fullname="TestQDoc::Test::someFunctionDefaultArg" href="testqdoc-test.html#someFunctionDefaultArg" threadsafety="non-reentrant" status="active" access="public" location="testcpp.h" documented="true" since="2.0" meta="plain" const="true" type="void" signature="void someFunctionDefaultArg(int i, bool b) const" groups="testgroup">
+ <parameter type="int" name="i" default=""/>
+ <parameter type="bool" name="b" default="false"/>
+ <description>
+ <para>Function that takes a parameter <argument>i</argument> and <argument>b</argument>.</para>
+ </description>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::Test::virtualFun" href="testqdoc-test.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" type="void" signature="void virtualFun()">
+ <description>
+ <para>Function that must be reimplemented.</para>
+ </description>
+ </function>
+ <typedef name="SomeType" fullname="TestQDoc::Test::SomeType" href="testqdoc-test.html#SomeType-typedef" status="active" access="public" location="testcpp.h" documented="true">
+ <description>
+ <brief>A typedef.</brief>
+ </description>
+ </typedef>
+ </class>
+ <class name="TestDerived" fullname="TestQDoc::TestDerived" href="testqdoc-testderived.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" bases="TestQDoc::Test" module="TestCPP" brief="A class in a namespace, derived from Test">
+ <description>
+ <brief>A class in a namespace, derived from <link raw="Test" href="testqdoc-test.html" type="class">Test</link>.</brief>
+ </description>
+ <function name="bindableProp" fullname="TestQDoc::TestDerived::bindableProp" href="testqdoc-testderived.html#bindableProp-prop" status="active" access="public" location="testcpp.h" meta="plain" associated-property="bindableProp" type="QBindable&lt;QString&gt;" signature="QBindable&lt;QString&gt; bindableProp()">
+ <description>
+ <see-also>setBindableProp()</see-also>
+ </description>
+ </function>
+ <function name="bindablePropChanged" fullname="TestQDoc::TestDerived::bindablePropChanged" href="testqdoc-testderived.html#bindableProp-prop" status="active" access="public" location="testcpp.h" meta="signal" associated-property="bindableProp" type="void" signature="void bindablePropChanged()">
+ <description/>
+ </function>
+ <function name="boolProp" fullname="TestQDoc::TestDerived::boolProp" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" meta="plain" associated-property="boolProp" type="bool" signature="bool boolProp()">
+ <description>
+ <see-also>setBoolProp()</see-also>
+ </description>
+ </function>
+ <function name="boolPropChanged" fullname="TestQDoc::TestDerived::boolPropChanged" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" meta="signal" associated-property="boolProp" type="void" signature="void boolPropChanged()">
+ <description/>
+ </function>
+ <function name="emitSomething" fullname="TestQDoc::TestDerived::emitSomething" href="testqdoc-testderived.html#emitSomething" status="active" access="public" location="testcpp.h" documented="true" meta="signal" type="void" signature="void emitSomething()">
+ <description>
+ <para>Emitted when things happen.</para>
+ </description>
+ </function>
+ <function name="getInt" fullname="TestQDoc::TestDerived::getInt" href="testqdoc-testderived.html#intProp-prop" status="active" access="public" location="testcpp.h" meta="plain" associated-property="intProp" type="int *" signature="int * getInt()">
+ <description/>
+ </function>
+ <function name="id" fullname="TestQDoc::TestDerived::id" href="testqdoc-testderived.html#id" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" override="true" type="int" signature="int id() override">
+ <description/>
+ </function>
+ <function name="invokeMe" fullname="TestQDoc::TestDerived::invokeMe" href="testqdoc-testderived.html#invokeMe" status="active" access="public" location="testcpp.h" documented="true" meta="plain" const="true" type="void" brief="Something invokable" signature="void invokeMe() const">
+ <description>
+ <brief>Something invokable.</brief>
+ </description>
+ </function>
+ <function name="name" fullname="TestQDoc::TestDerived::name" href="testqdoc-testderived.html#name-prop" status="active" access="public" location="testcpp.h" meta="plain" const="true" associated-property="name" type="const QString *" signature="const QString * name() const">
+ <description/>
+ </function>
+ <function name="resetBoolProp" fullname="TestQDoc::TestDerived::resetBoolProp" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" meta="slot" associated-property="boolProp" type="void" signature="void resetBoolProp()">
+ <description/>
+ </function>
+ <function name="setBindableProp" fullname="TestQDoc::TestDerived::setBindableProp" href="testqdoc-testderived.html#bindableProp-prop" status="active" access="public" location="testcpp.h" meta="slot" associated-property="bindableProp" type="void" signature="void setBindableProp(const QString &amp;s)">
+ <parameter type="const QString &amp;" name="s" default=""/>
+ <description>
+ <see-also>bindableProp()</see-also>
+ </description>
+ </function>
+ <function name="setBoolProp" fullname="TestQDoc::TestDerived::setBoolProp" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" meta="slot" associated-property="boolProp" type="void" signature="void setBoolProp(bool b)">
+ <parameter type="bool" name="b" default=""/>
+ <description>
+ <see-also>boolProp()</see-also>
+ </description>
+ </function>
+ <function name="someProp" fullname="TestQDoc::TestDerived::someProp" href="testqdoc-testderived.html#someProp-prop" status="active" access="public" location="testcpp.h" meta="plain" associated-property="someProp" type="const QString &amp;" signature="const QString &amp; someProp()">
+ <description/>
+ </function>
+ <function name="someValue" fullname="TestQDoc::TestDerived::someValue" href="testqdoc-testderived.html#someValue" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::TestDerived::NotTypedef" signature="TestQDoc::TestDerived::NotTypedef someValue()">
+ <description>
+ <para>Returns a value using an aliases type.</para>
+ </description>
+ </function>
+ <function name="staticObsoleteMember" fullname="TestQDoc::TestDerived::staticObsoleteMember" href="testqdoc-testderived-obsolete.html#staticObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" static="true" type="void" signature="void staticObsoleteMember()">
+ <description>
+ <para>Static obsolete method.</para>
+ </description>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::TestDerived::virtualFun" href="testqdoc-testderived.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" override="true" type="void" signature="void virtualFun() override">
+ <description/>
+ </function>
+ <typedef name="DerivedType" fullname="TestQDoc::TestDerived::DerivedType" href="testqdoc-testderived.html#DerivedType-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="Test::SomeType">
+ <description>
+ <para>An aliased typedef.</para>
+ </description>
+ </typedef>
+ <typedef name="NotTypedef" fullname="TestQDoc::TestDerived::NotTypedef" href="testqdoc-testderived.html#NotTypedef-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="int">
+ <description>
+ <para>I'm an alias, not a typedef.</para>
+ </description>
+ </typedef>
+ <property name="bindableProp" fullname="TestQDoc::TestDerived::bindableProp" href="testqdoc-testderived.html#bindableProp-prop" status="active" access="public" location="testcpp.h" documented="true" bindable="true">
+ <getter name="bindableProp"/>
+ <setter name="setBindableProp"/>
+ <notifier name="bindablePropChanged"/>
+ <description>
+ <para>Some property.</para>
+ <see-also>
+ <link raw="someProp" href="testqdoc-testderived.html#someProp-prop" type="function">someProp</link>
+ </see-also>
+ </description>
+ </property>
+ <property name="boolProp" fullname="TestQDoc::TestDerived::boolProp" href="testqdoc-testderived.html#boolProp-prop" status="active" access="public" location="testcpp.h" documented="true">
+ <getter name="boolProp"/>
+ <setter name="setBoolProp"/>
+ <resetter name="resetBoolProp"/>
+ <notifier name="boolPropChanged"/>
+ <description>
+ <para>A boolean property.</para>
+ </description>
+ </property>
+ <property name="intProp" fullname="TestQDoc::TestDerived::intProp" href="testqdoc-testderived.html#intProp-prop" status="active" access="public" location="testcpp.h" documented="true" writable="false">
+ <getter name="getInt"/>
+ <description>
+ <para>An integer property.</para>
+ </description>
+ </property>
+ <property name="name" fullname="TestQDoc::TestDerived::name" href="testqdoc-testderived.html#name-prop" status="active" access="public" location="testcpp.h" documented="true" writable="false" brief="Name">
+ <getter name="name"/>
+ <description>
+ <brief>This property holds a name..</brief>
+ </description>
+ </property>
+ <property name="someProp" fullname="TestQDoc::TestDerived::someProp" href="testqdoc-testderived.html#someProp-prop" status="active" access="public" location="testcpp.h" documented="true" bindable="true" writable="false">
+ <getter name="someProp"/>
+ <description>
+ <para>Another property.</para>
+ </description>
+ </property>
+ </class>
+ </namespace>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testtagfile.tags b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testtagfile.tags
new file mode 100644
index 000000000..f68dffed6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/testtagfile.tags
@@ -0,0 +1,500 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<tagfile>
+ <compound kind="class">
+ <name>QDoc.Test.AbstractParent</name>
+ <filename>qml-qdoc-test-abstractparent.html</filename>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>name</name>
+ <anchorfile>qml-qdoc-test-abstractparent.html</anchorfile>
+ <anchor>name-method</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>name</name>
+ <anchorfile>qml-qdoc-test-abstractparent.html</anchorfile>
+ <anchor>name-method</anchor>
+ <arglist>(Child child, name)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>rear</name>
+ <anchorfile>qml-qdoc-test-abstractparent.html</anchorfile>
+ <anchor>rear-method</anchor>
+ <arglist>(Child child, var method)</arglist>
+ </member>
+ </compound>
+ <compound kind="class">
+ <name>QDoc.Test.Child</name>
+ <filename>qml-qdoc-test-child.html</filename>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>name</name>
+ <anchorfile>qml-qdoc-test-child.html</anchorfile>
+ <anchor>name-method</anchor>
+ <arglist>(Child child, name)</arglist>
+ </member>
+ </compound>
+ <compound kind="namespace">
+ <name>CrossModuleRef</name>
+ <filename>crossmoduleref.html</filename>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>documentMe</name>
+ <anchorfile>crossmoduleref.html</anchorfile>
+ <anchor>documentMe</anchor>
+ <arglist>()</arglist>
+ </member>
+ </compound>
+ <compound kind="class">
+ <name>QDoc.Test.DocTest</name>
+ <filename>qml-qdoc-test-doctest.html</filename>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>completed</name>
+ <anchorfile>qml-qdoc-test-doctest.html</anchorfile>
+ <anchor>completed-signal</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>fail</name>
+ <anchorfile>qml-qdoc-test-doctest.html</anchorfile>
+ <anchor>fail-method</anchor>
+ <arglist>(message)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>fail_hard</name>
+ <anchorfile>qml-qdoc-test-doctest.html</anchorfile>
+ <anchor>fail_hard-method</anchor>
+ <arglist>(msg, option)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>foo</name>
+ <anchorfile>qml-qdoc-test-doctest.html</anchorfile>
+ <anchor>foo-signal</anchor>
+ <arglist>(var bar)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>itsHappening</name>
+ <anchorfile>qml-qdoc-test-doctest.html</anchorfile>
+ <anchor>itsHappening-signal</anchor>
+ <arglist>(bool really)</arglist>
+ </member>
+ </compound>
+ <compound kind="class">
+ <name>Test.NoVer.DocTest</name>
+ <filename>qml-test-nover-doctest.html</filename>
+ </compound>
+ <compound kind="class">
+ <name>QDoc.Test.OldType</name>
+ <filename>qml-qdoc-test-oldtype.html</filename>
+ </compound>
+ <compound kind="class">
+ <name>UIComponents.ProgressBar</name>
+ <filename>qml-uicomponents-progressbar.html</filename>
+ </compound>
+ <compound kind="class">
+ <name>SeenClass</name>
+ <filename>seenclass.html</filename>
+ </compound>
+ <compound kind="class">
+ <name>UIComponents.Switch</name>
+ <filename>qml-uicomponents-switch.html</filename>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>toggle</name>
+ <anchorfile>qml-uicomponents-switch.html</anchorfile>
+ <anchor>toggle-method</anchor>
+ <arglist>()</arglist>
+ </member>
+ </compound>
+ <compound kind="class">
+ <name>UIComponents.TabWidget</name>
+ <filename>qml-uicomponents-tabwidget.html</filename>
+ </compound>
+ <compound kind="namespace">
+ <name>TestQDoc</name>
+ <filename>testqdoc.html</filename>
+ <class>TestQDoc::Test</class>
+ <class>TestQDoc::TestDerived</class>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>QDOCTEST_MACRO</name>
+ <anchorfile>testqdoc.html</anchorfile>
+ <anchor>QDOCTEST_MACRO</anchor>
+ <arglist>QDOCTEST_MACRO</arglist>
+ </member>
+ </compound>
+ <compound kind="class">
+ <name>TestQDoc::Test</name>
+ <filename>testqdoc-test.html</filename>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>QDOCTEST_MACRO2</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>QDOCTEST_MACRO2</anchor>
+ <arglist>(int &amp;x)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>Test</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>Test</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>TestQDoc::Test &amp;</type>
+ <name>operator++</name>
+ <anchorfile>testqdoc-test-obsolete.html</anchorfile>
+ <anchor>operator-2b-2b</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>TestQDoc::Test &amp;</type>
+ <name>operator--</name>
+ <anchorfile>testqdoc-test-obsolete.html</anchorfile>
+ <anchor>operator--</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>TestQDoc::Test &amp;</type>
+ <name>operator=</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>operator-eq</anchor>
+ <arglist>(TestQDoc::Test &amp;&amp;other)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>bool</type>
+ <name>operator==</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>operator-eq-eq</anchor>
+ <arglist>(const TestQDoc::Test &amp;lhs, const TestQDoc::Test &amp;rhs)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>int</type>
+ <name>someFunction</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>someFunction</anchor>
+ <arglist>(int, int v)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void (*)(bool)</type>
+ <name>funcPtr</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>funcPtr</anchor>
+ <arglist>(*)(bool) funcPtr(bool b, const char *s)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>anotherObsoleteMember</name>
+ <anchorfile>testqdoc-test-obsolete.html</anchorfile>
+ <anchor>anotherObsoleteMember</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>deprecatedMember</name>
+ <anchorfile>testqdoc-test-obsolete.html</anchorfile>
+ <anchor>deprecatedMember</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>inlineFunction</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>inlineFunction</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>methodWithEmDashInItsDocs</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>methodWithEmDashInItsDocs</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>methodWithEnDashInItsDocs</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>methodWithEnDashInItsDocs</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>obsoleteMember</name>
+ <anchorfile>testqdoc-test-obsolete.html</anchorfile>
+ <anchor>obsoleteMember</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="protected" virtualness="non" static="no">
+ <type>void</type>
+ <name>overload</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>overload</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="protected" virtualness="non" static="no">
+ <type>void</type>
+ <name>overload</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>overload-1</anchor>
+ <arglist>(bool b)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="virtual" static="no">
+ <type>virtual void</type>
+ <name>virtualFun</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>virtualFun</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>someFunctionDefaultArg</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>someFunctionDefaultArg</anchor>
+ <arglist>(int i, bool b) const const</arglist>
+ </member>
+ <member kind="typedef" type="">
+ <name>SomeType</name>
+ <anchorfile>testqdoc-test.html</anchorfile>
+ <anchor>SomeType-typedef</anchor>
+ <arglist></arglist>
+ </member>
+ </compound>
+ <compound kind="class">
+ <name>TestQDoc::TestDerived</name>
+ <filename>testqdoc-testderived.html</filename>
+ <base>Test</base>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>QBindable&lt;QString&gt;</type>
+ <name>bindableProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>bindableProp-prop</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>TestQDoc::TestDerived::NotTypedef</type>
+ <name>someValue</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>someValue</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>bool</type>
+ <name>boolProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>boolProp-prop</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>const QString &amp;</type>
+ <name>someProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>someProp-prop</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>int *</type>
+ <name>getInt</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>intProp-prop</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="virtual" static="no">
+ <type>virtual int</type>
+ <name>id</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>id</anchor>
+ <arglist>() override</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>bindablePropChanged</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>bindableProp-prop</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>boolPropChanged</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>boolProp-prop</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>emitSomething</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>emitSomething</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>resetBoolProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>boolProp-prop</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>setBindableProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>bindableProp-prop</anchor>
+ <arglist>(const QString &amp;s)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>setBoolProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>boolProp-prop</anchor>
+ <arglist>(bool b)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="yes">
+ <type>void</type>
+ <name>staticObsoleteMember</name>
+ <anchorfile>testqdoc-testderived-obsolete.html</anchorfile>
+ <anchor>staticObsoleteMember</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="virtual" static="no">
+ <type>virtual void</type>
+ <name>virtualFun</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>virtualFun</anchor>
+ <arglist>() override</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>const QString *</type>
+ <name>name</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>name-prop</anchor>
+ <arglist>() const const</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>void</type>
+ <name>invokeMe</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>invokeMe</anchor>
+ <arglist>() const const</arglist>
+ </member>
+ <member kind="typedef" type="">
+ <name>DerivedType</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>DerivedType-typedef</anchor>
+ <arglist></arglist>
+ </member>
+ <member kind="typedef" type="">
+ <name>NotTypedef</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>NotTypedef-typedef</anchor>
+ <arglist></arglist>
+ </member>
+ <member kind="property" type="QString">
+ <name>bindableProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>bindableProp-prop</anchor>
+ <arglist></arglist>
+ </member>
+ <member kind="property" type="bool">
+ <name>boolProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>boolProp-prop</anchor>
+ <arglist></arglist>
+ </member>
+ <member kind="property" type="int*">
+ <name>intProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>intProp-prop</anchor>
+ <arglist></arglist>
+ </member>
+ <member kind="property" type="const QString*">
+ <name>name</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>name-prop</anchor>
+ <arglist></arglist>
+ </member>
+ <member kind="property" type="QString">
+ <name>someProp</name>
+ <anchorfile>testqdoc-testderived.html</anchorfile>
+ <anchor>someProp-prop</anchor>
+ <arglist></arglist>
+ </member>
+ </compound>
+ <compound kind="class">
+ <name>TheModule.TheType</name>
+ <filename>qml-themodule-thetype.html</filename>
+ </compound>
+ <compound kind="class">
+ <name>QDoc.Test.Type</name>
+ <filename>qml-qdoc-test-type.html</filename>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type>Type</type>
+ <name>copy</name>
+ <anchorfile>qml-qdoc-test-type.html</anchorfile>
+ <anchor>copy-method</anchor>
+ <arglist>(a)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>completed</name>
+ <anchorfile>qml-qdoc-test-type.html</anchorfile>
+ <anchor>completed-signal</anchor>
+ <arglist>(int status)</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>configured</name>
+ <anchorfile>qml-qdoc-test-type.html</anchorfile>
+ <anchor>configured-signal</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>deprecatedMethod</name>
+ <anchorfile>qml-qdoc-test-type-obsolete.html</anchorfile>
+ <anchor>deprecatedMethod-method</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>disable</name>
+ <anchorfile>qml-qdoc-test-type.html</anchorfile>
+ <anchor>disable-method</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>enable</name>
+ <anchorfile>qml-qdoc-test-type.html</anchorfile>
+ <anchor>enable-method</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>futureDeprecated</name>
+ <anchorfile>qml-qdoc-test-type.html</anchorfile>
+ <anchor>futureDeprecated-method</anchor>
+ <arglist>()</arglist>
+ </member>
+ <member kind="function" protection="public" virtualness="non" static="no">
+ <type></type>
+ <name>group.created</name>
+ <anchorfile>qml-qdoc-test-type.html</anchorfile>
+ <anchor>group.created-signal</anchor>
+ <arglist>()</arglist>
+ </member>
+ </compound>
+ <compound kind="class">
+ <name>Test.NoVer.TypeNoVersion</name>
+ <filename>qml-test-nover-typenoversion.html</filename>
+ </compound>
+ <compound kind="class">
+ <name>QDoc.Test.YetAnotherChild</name>
+ <filename>qml-qdoc-test-yetanotherchild.html</filename>
+ </compound>
+</tagfile>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/themodule-qmlmodule.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/themodule-qmlmodule.webxml
new file mode 100644
index 000000000..5d24b3077
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/themodule-qmlmodule.webxml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document/>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/uicomponents-qmlmodule.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/uicomponents-qmlmodule.webxml
new file mode 100644
index 000000000..5d24b3077
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/expected/webxml/uicomponents-qmlmodule.webxml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document/>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/TestCPP b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/TestCPP
new file mode 100644
index 000000000..4ed786108
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/TestCPP
@@ -0,0 +1,5 @@
+#include "testcpp.h"
+
+#ifdef test_template
+# include "testtemplate.h"
+#endif
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/classlists.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/classlists.qdoc
new file mode 100644
index 000000000..2954e5beb
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/classlists.qdoc
@@ -0,0 +1,51 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*!
+ \page obsolete-classes.html
+ \title Obsolete Classes
+
+ \section1 Classes with obsolete members
+ \generatelist obsoletecppmembers
+
+ \section2 TestQDoc
+*/
+
+/*!
+ \page autolinking.html
+ \title Autolinking
+
+ //! a section title that qualifies for autolinking
+ \section1 TestQDoc
+
+ The string TestQDoc links to the C++ namespace unless linking explicitly,
+ \l {#TestQDoc}{like this}, or \l {TestQDoc}{this}. Also,
+
+ Autolinks:
+
+ \list
+ \li TestQDoc::TestDerived
+ \endlist
+
+ Explicit links:
+
+ \list
+ \li \l [CPP] {TestQDoc::TestDerived}
+ \li \l {Obsolete Classes#TestQDoc}
+ \endlist
+
+ //! a section title shadowing a known property name
+ \section1 someProp
+*/
+
+/*!
+ \group cpptypes
+ \title Test C++ Types
+
+ \generatelist testgroup
+*/
+
+/*!
+ \externalpage https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command
+ \title reentrant
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/dont.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/dont.cpp
new file mode 100644
index 000000000..39be5bbbf
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/dont.cpp
@@ -0,0 +1,22 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "dont.h"
+
+/*!
+ \class UnseenClass
+ \inmodule TestCPP
+ \brief A public but undocumented class.
+*/
+UnseenClass::UnseenClass()
+{
+}
+
+/*!
+ \class SeenClass
+ \inmodule TestCPP
+ \brief A public but undocumented class.
+*/
+SeenClass::SeenClass()
+{
+}
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/dont.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/dont.h
new file mode 100644
index 000000000..0614c713e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/dont.h
@@ -0,0 +1,16 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#pragma once
+
+class UnseenClass
+{
+public:
+ UnseenClass();
+};
+
+class SeenClass : public UnseenClass
+{
+public:
+ SeenClass();
+};
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/demo.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/demo.cpp
new file mode 100644
index 000000000..906c6292f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/demo.cpp
@@ -0,0 +1,11 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+bool isOverThousand(int n)
+{
+//! [integer literal with separator]
+ if (n > 1'000)
+ return true;
+//! [integer literal with separator]
+ return false;
+}
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/demo.pro b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/demo.pro
new file mode 100644
index 000000000..dbe8ff3c0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/demo.pro
@@ -0,0 +1,2 @@
+TEMPLATE = aux
+message("Nothing to see here.")
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/doc/src/demo.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/doc/src/demo.qdoc
new file mode 100644
index 000000000..21ebc5588
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/doc/src/demo.qdoc
@@ -0,0 +1,11 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*!
+ \example demos/demo
+ \title Demo
+ \image leonardo-da-vinci.png
+ //! Icon made by Smashicons from www.flaticon.com
+
+ \snippet demos/demo/demo.cpp integer literal with separator
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/dontxclude/CMakeLists.txt b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/dontxclude/CMakeLists.txt
new file mode 100644
index 000000000..d29157aad
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/dontxclude/CMakeLists.txt
@@ -0,0 +1,2 @@
+cmake_minimum_required(VERSION 3.16)
+project (DONTXCLUDEDIRS_QDOCTEST)
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/excludes/CMakeLists.txt b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/excludes/CMakeLists.txt
new file mode 100644
index 000000000..09b447642
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/demo/excludes/CMakeLists.txt
@@ -0,0 +1,2 @@
+cmake_minimum_required(VERSION 3.16)
+project (EXCLUDEDIR_QDOCTEST)
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/hidden/doc/src/hidden.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/hidden/doc/src/hidden.qdoc
new file mode 100644
index 000000000..dddcbc074
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/hidden/doc/src/hidden.qdoc
@@ -0,0 +1,11 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*!
+ \example demos/hidden
+ \title Hidden Demo
+ \meta tag broken
+ \brief Tagged 'broken', does not appear in examples-manifest.xml.
+
+ Also missing an image, but that's OK as it's broken anyway.
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/hidden/hidden.pro b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/hidden/hidden.pro
new file mode 100644
index 000000000..dbe8ff3c0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/examples/demos/hidden/hidden.pro
@@ -0,0 +1,2 @@
+TEMPLATE = aux
+message("Nothing to see here.")
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/images/01.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/images/01.png
new file mode 100644
index 000000000..d73ab969b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/images/01.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/images/leonardo-da-vinci.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/images/leonardo-da-vinci.png
new file mode 100644
index 000000000..854acb4ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/images/leonardo-da-vinci.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/properties.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/properties.qdoc
new file mode 100644
index 000000000..1370f813b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/properties.qdoc
@@ -0,0 +1,55 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*!
+ \property TestQDoc::TestDerived::bindableProp
+ Some property.
+
+ \sa someProp
+*/
+
+/*!
+ \property TestQDoc::TestDerived::someProp
+ Another property.
+*/
+
+/*!
+ \property TestQDoc::TestDerived::name
+ \brief a name.
+*/
+
+/*!
+ \property TestQDoc::TestDerived::intProp
+ An integer property.
+*/
+
+/*!
+ \property TestQDoc::TestDerived::boolProp
+ A boolean property.
+*/
+
+/*!
+ \fn TestQDoc::TestDerived::invokeMe() const
+ \brief Something invokable.
+*/
+
+/*!
+ \qmlmodule TheModule
+*/
+
+/*!
+ \qmltype TheType
+ \nativetype TestQDoc::TestDerived
+ \inqmlmodule TheModule
+*/
+
+/*!
+ \qmlproperty string TheType::name
+ \brief Read-only status of this property is resolved from Q_PROPERTY.
+*/
+
+/*!
+ //! avoid link warnings for auto-generated links to QProperty
+ \externalpage https://wiki.qt.io/QProperty
+ \title QProperty
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/DocTest.qml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/DocTest.qml
new file mode 100644
index 000000000..dfae6f13d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/DocTest.qml
@@ -0,0 +1,86 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import QtQuick 2.0
+
+/*!
+ \qmltype DocTest
+ \inherits Test
+ \inqmlmodule QDoc.Test
+ \brief Represents a doc test case.
+ \since QDoc.Test 0.9
+
+ \section1 Introduction
+
+ A documentation test case, itself documented inline in \DocTest.qml.
+*/
+Item {
+ id: testCase
+
+ /*!
+ \qmlsignal QDocTest::completed
+ */
+ signal completed
+
+ /*!
+ \qmlsignal DocTest::test(var bar)
+ Signal with parameter \a bar.
+ */
+ signal foo(var bar)
+
+ /*!
+ Signals that something is \a really happening.
+ */
+ signal itsHappening(bool really)
+
+ /*!
+ \qmlproperty string DocTest::name
+
+ Name of the test.
+ \qml
+ DocTest {
+ name: "test"
+ // ...
+ }
+ \endqml
+ */
+ required property string name
+
+ /*!
+ Whether the test is active.
+ \default true
+
+ \sa name
+ */
+ property bool active: true
+
+ /*! \internal */
+ property int doctest_internal: -1
+
+ /*!
+ \qmlmethod DocTest::fail(message = "oops")
+ \since QDoc.Test 1.0
+
+ Fails the current test case, with the optional \a message.
+ */
+ function fail(msg) {
+ if (msg === undefined)
+ msg = "oops";
+ }
+
+ /*! \internal */
+ function doctest_fail(msg) {
+ if (msg === undefined)
+ msg = "";
+ }
+
+ /*!
+ \brief Fails the current test case, hard.
+ \list
+ \li Prints out \a msg.
+ \li Accepts a random \a option.
+ \endlist
+ */
+ function fail_hard(msg = "facepalm", option = 123) {
+ }
+}
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/cmaketest/CMakeLists.txt b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/cmaketest/CMakeLists.txt
new file mode 100644
index 000000000..89eafa300
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/cmaketest/CMakeLists.txt
@@ -0,0 +1,2 @@
+cmake_minimum_required(VERSION 3.16)
+project (QDOCTEST)
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/cmaketest/doc/src/cmaketest.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/cmaketest/doc/src/cmaketest.qdoc
new file mode 100644
index 000000000..0e8a2ab3c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/cmaketest/doc/src/cmaketest.qdoc
@@ -0,0 +1,9 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*!
+ \example cmaketest
+ \title CMake Example Project
+ \image leonardo-da-vinci.png
+ //! Icon made by Smashicons from www.flaticon.com
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/cmaketest/main.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/cmaketest/main.cpp
new file mode 100644
index 000000000..68d71eb71
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/cmaketest/main.cpp
@@ -0,0 +1 @@
+void main(){}
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/ProgressBar.qml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/ProgressBar.qml
new file mode 100644
index 000000000..633a3ff5d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/ProgressBar.qml
@@ -0,0 +1,98 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick 1.0
+
+/*!
+ \qmltype ProgressBar
+ \inqmlmodule UIComponents
+ \brief A component that shows the progress of an event.
+
+ A ProgressBar shows the linear progress of an event as its \l value.
+ The range is specified using the \l {minimum} and the \l{maximum} values.
+
+ The ProgressBar component is part of the \l {UI Components} module.
+
+ This documentation is part of the \l{componentset}{UIComponents} example.
+*/
+Item {
+ id: progressbar
+
+ /*!
+ The minimum value of the ProgressBar range.
+ The \l value must not be less than this value.
+ */
+ property int minimum: 0
+
+ /*!
+ The maximum value of the ProgressBar range.
+ The \l value must not be more than this value.
+ */
+ property int maximum: 100
+
+ /*!
+ The value of the progress.
+ */
+ property int value: 0
+
+ /*!
+ \qmlproperty color ProgressBar::color
+ The color of the ProgressBar's gradient. Must bind to a color type.
+
+ \omit
+ The "\qmlproperty <type> <property name>" is needed because
+ property alias need to have their types manually entered.
+
+ QDoc will not publish the documentation within omit and endomit.
+ \endomit
+
+ \sa secondColor
+ */
+ property alias color: gradient1.color
+
+ /*!
+ \qmlproperty color ProgressBar::secondColor
+ The second color of the ProgressBar's gradient.
+ Must bind to a color type.
+
+ \omit
+ The "\qmlproperty <type> <property name>" is needed because
+ property alias need to have their types manually entered.
+
+ QDoc will not publish the documentation within omit and endomit.
+ \endomit
+
+ \sa color
+ */
+ property alias secondColor: gradient2.color
+
+ width: 250; height: 23
+ clip: true
+
+ Rectangle {
+ id: highlight
+
+ /*!
+ An internal documentation comment. The widthDest property is not
+ a public API and therefore will not be exposed.
+ */
+ property int widthDest: ((progressbar.width * (value - minimum)) / (maximum - minimum) - 6)
+
+ width: highlight.widthDest
+ Behavior on width { SmoothedAnimation { velocity: 1200 } }
+
+ anchors { left: parent.left; top: parent.top; bottom: parent.bottom; margins: 3 }
+ radius: 1
+ gradient: Gradient {
+ GradientStop { id: gradient1; position: 0.0 }
+ GradientStop { id: gradient2; position: 1.0 }
+ }
+
+ }
+ Text {
+ anchors { right: highlight.right; rightMargin: 6; verticalCenter: parent.verticalCenter }
+ color: "white"
+ font.bold: true
+ text: Math.floor((value - minimum) / (maximum - minimum) * 100) + '%'
+ }
+}
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/Switch.qml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/Switch.qml
new file mode 100644
index 000000000..123e86468
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/Switch.qml
@@ -0,0 +1,105 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick 1.0
+
+/*!
+ \qmltype ToggleSwitch
+ \inqmlmodule UIComponents
+ \brief A component that can be turned on or off.
+
+ A toggle switch has two states: an \c on and an \c off state. The \c off
+ state is when the \l on property is set to \c false.
+
+ The ToggleSwitch component is part of the \l {UI Components} module.
+
+ This documentation is part of the \l{componentset}{UIComponents} example.
+
+*/
+Item {
+ id: toggleswitch
+ width: background.width; height: background.height
+
+ /*!
+ Indicates the state of the switch. If \c false, then the switch is in
+ the \c off state.
+
+ \omit
+ The \qmlproperty <type> <propertyname> is not necessary as QDoc
+ will associate this property to the ToggleSwitch
+
+ QDoc will not publish the documentation within omit and endomit.
+ \endomit
+ */
+ property bool on: false
+
+
+ /*!
+ A method to toggle the switch. If the switch is \c on, the toggling it
+ will turn it \c off. Toggling a switch in the \c off position will
+ turn it \c on.
+ */
+ function toggle() {
+ if (toggleswitch.state == "on")
+ toggleswitch.state = "off";
+ else
+ toggleswitch.state = "on";
+ }
+
+
+ /*!
+ \internal
+
+ An internal function to synchronize the switch's internals. This
+ function is not for public access. The \internal command will
+ prevent QDoc from publishing this comment in the public API.
+ */
+ function releaseSwitch() {
+ if (knob.x == 1) {
+ if (toggleswitch.state == "off") return;
+ }
+ if (knob.x == 78) {
+ if (toggleswitch.state == "on") return;
+ }
+ toggle();
+ }
+
+ Rectangle {
+ id: background
+ width: 130; height: 48
+ radius: 48
+ color: "lightsteelblue"
+ MouseArea { anchors.fill: parent; onClicked: toggle() }
+ }
+
+ Rectangle {
+ id: knob
+ width: 48; height: 48
+ radius: width
+ color: "lightblue"
+
+ MouseArea {
+ anchors.fill: parent
+ drag.target: knob; drag.axis: Drag.XAxis; drag.minimumX: 1; drag.maximumX: 78
+ onClicked: toggle()
+ onReleased: releaseSwitch()
+ }
+ }
+
+ states: [
+ State {
+ name: "on"
+ PropertyChanges { target: knob; x: 78 }
+ PropertyChanges { target: toggleswitch; on: true }
+ },
+ State {
+ name: "off"
+ PropertyChanges { target: knob; x: 1 }
+ PropertyChanges { target: toggleswitch; on: false }
+ }
+ ]
+
+ transitions: Transition {
+ NumberAnimation { properties: "x"; easing.type: Easing.InOutQuad; duration: 200 }
+ }
+}
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/TabWidget.qml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/TabWidget.qml
new file mode 100644
index 000000000..57808c1a5
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/TabWidget.qml
@@ -0,0 +1,146 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick 1.0
+
+/*!
+ \qmltype TabWidget
+ \inqmlmodule UIComponents
+ \brief A widget that places its children as tabs.
+
+ A TabWidget places its children as tabs in a view. Selecting
+ a tab involves selecting the tab at the top.
+
+ The TabWidget component is part of the \l {UI Components} module.
+
+ This documentation is part of the \l{componentset}{UIComponents} example.
+
+ \section1 Adding Tabs
+
+ To add a tab, declare the tab as a child of the TabWidget.
+
+ \code
+ TabWidget {
+ id: tabwidget
+
+ Rectangle {
+ id: tab1
+ color: "red"
+ //... omitted
+ }
+ Rectangle {
+ id: tab2
+ color: "blue"
+ //... omitted
+ }
+
+ }
+ \endcode
+
+*/
+Item {
+ id: tabWidget
+
+ /*!
+ \internal
+
+ Setting the default property to stack.children means any child items
+ of the TabWidget are actually added to the 'stack' item's children.
+
+ See the \l{"Property Binding in QML"}
+ documentation for details on default properties.
+
+ This is an implementation detail, not meant for public knowledge. Putting
+ the \internal command at the beginning will cause QDoc to not publish this
+ documentation in the public API page.
+
+ Normally, a property alias needs to have a
+ "\qmlproperty <type> <propertyname>" to assign the alias a type.
+
+ */
+ default property alias content: stack.children
+
+
+ /*!
+ The currently active tab in the TabWidget.
+ */
+ property int current: 0
+
+ /*!
+ A sample \c{read-only} property.
+ A contrived property to demonstrate QDoc's ability to detect
+ read-only properties.
+
+ The signature is:
+ \code
+ readonly property int sampleReadOnlyProperty: 0
+ \endcode
+
+ Note that the property must be initialized to a value.
+
+ */
+ readonly property int sampleReadOnlyProperty: 0
+
+ /*!
+ \internal
+
+ This handler is an implementation
+ detail. The \c{\internal} command will prevent QDoc from publishing this
+ documentation on the public API.
+ */
+ onCurrentChanged: setOpacities()
+ Component.onCompleted: setOpacities()
+
+ /*!
+ \internal
+
+ An internal function to set the opacity.
+ The \internal command will prevent QDoc from publishing this
+ documentation on the public API.
+ */
+ function setOpacities() {
+ for (var i = 0; i < stack.children.length; ++i) {
+ stack.children[i].opacity = (i == current ? 1 : 0)
+ }
+ }
+
+ Row {
+ id: header
+
+ Repeater {
+ model: stack.children.length
+ delegate: Rectangle {
+ width: tabWidget.width / stack.children.length; height: 36
+
+ Rectangle {
+ width: parent.width; height: 1
+ anchors { bottom: parent.bottom; bottomMargin: 1 }
+ color: "#acb2c2"
+ }
+ BorderImage {
+ anchors { fill: parent; leftMargin: 2; topMargin: 5; rightMargin: 1 }
+ border { left: 7; right: 7 }
+ source: "tab.png"
+ visible: tabWidget.current == index
+ }
+ Text {
+ horizontalAlignment: Qt.AlignHCenter; verticalAlignment: Qt.AlignVCenter
+ anchors.fill: parent
+ text: stack.children[index].title
+ elide: Text.ElideRight
+ font.bold: tabWidget.current == index
+ }
+ MouseArea {
+ anchors.fill: parent
+ onClicked: tabWidget.current = index
+ }
+ }
+ }
+ }
+
+ Item {
+ id: stack
+ width: tabWidget.width
+ anchors.top: header.bottom; anchors.bottom: tabWidget.bottom
+ }
+}
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/componentset.pro b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/componentset.pro
new file mode 100644
index 000000000..5b44737c2
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/componentset.pro
@@ -0,0 +1,5 @@
+SOURCES = componentset.pro \
+ ProgressBar.qml \
+ Switch.qml \
+ TabWidget.qml \
+ uicomponents.qdoc
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/componentset.qml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/componentset.qml
new file mode 100644
index 000000000..18b25884a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/componentset.qml
@@ -0,0 +1,7 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import QtQuick 2.0
+
+Item {
+}
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/examples.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/examples.qdoc
new file mode 100644
index 000000000..1069c55fd
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/examples.qdoc
@@ -0,0 +1,82 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example componentset
+ \title QML Documentation Example
+ \brief Example for documenting QML types.
+
+ \testnoautolist
+
+ \meta tags {test,sample}
+ \meta tag {application}
+ \meta category {Application Example}
+ \meta installpath tutorials
+
+ This example demonstrates one of the ways to document QML types. It also
+ generates a warning about a missing example image, on purpose.
+
+ In particular, there are sample types that are documented with QDoc
+ commands comments. There are documentation comments for the QML types
+ and their public interfaces. The types are grouped into a module, the
+ \l{UI Components} module.
+
+ The uicomponents.qdoc file generates
+ the overview page for the \l{UI Components} module page.
+
+ The generated documentation is available in the \l{UI Components} module.
+
+ \section1 QML Class
+
+ The QML types use the \\qmltype to document the
+ type. In addition, they have the \\inmodule
+ command in order for QDoc to associate them to the \c UIComponents module.
+
+ QDoc uses the \\brief command to place a basic
+ description when listing the types.
+
+ \section1 Properties, Signals, Handlers, and Methods
+
+ The types have their properties, signals, handlers, and methods
+ defined in their respective QML files. QDoc associates the properties and
+ methods to the types, therefore, you only need to place the
+ documentation above the property, method, or signal.
+
+ To document the type of a \e {property alias}, you must use the
+ \\qmlproperty command to specify the data type.
+
+ \code
+ \qmlproperty int anAliasedProperty
+ An aliased property of type int.
+ \endcode
+
+ \section2 Internal Documentation
+
+ You may declare that a documentation is for internal use by placing the
+ \\internal command after the beginning QDoc comment
+ \begincomment. QDoc will prevent the internal documentation from appearing
+ in the public API.
+
+ If you wish to omit certain parts of the documentation, you may use the
+ \\omit and \\endomit command.
+
+ \section1 QML Types with C++ Implementation
+
+ This example only demonstrates the documentation for types in QML
+ files, but the regular QML commands may be placed
+ inside C++ classes to define the public API of the QML type.
+
+*/
+
+
+/*!
+ \qmlmodule UIComponents 1.0
+ \title UI Components
+ \brief Basic set of UI components.
+
+ This is a listing of a list of UI components implemented by QML types. These
+ files are available for general import and they are based on the
+ Qt Quick Code Samples.
+
+ This module is part of the \l{componentset}{UIComponents} example.
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/uicomponents.qdoc.sample b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/uicomponents.qdoc.sample
new file mode 100644
index 000000000..efbcd9511
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/componentset/uicomponents.qdoc.sample
@@ -0,0 +1,14 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \qmlmodule UIComponents 1.0
+ \title UI Components
+ \brief Basic set of UI components
+
+ This is a listing of a list of UI components implemented by QML types. These
+ files are available for general import and they are based off the \l{Qt
+ Quick Code Samples}.
+
+ This module is part of the \l{componentset}{UIComponents} example.
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/doctest/DocTest.qml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/doctest/DocTest.qml
new file mode 100644
index 000000000..07201403f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/doctest/DocTest.qml
@@ -0,0 +1,11 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import QtQuick
+
+/*!
+ //! omit the \qmltype command, file name (base) is the type name.
+ \inqmlmodule Test.NoVer
+ \brief Shadows the type name in QDoc.Test module.
+*/
+Item {}
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/modules.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/modules.qdoc
new file mode 100644
index 000000000..3b17b5d92
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/modules.qdoc
@@ -0,0 +1,19 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*!
+ \page qmlmodules.html
+ \title QML Modules
+
+ \generatelist qml-modules
+
+ \section1 QML types
+
+ \generatelist qmltypesbymodule QDoc.Test
+
+ \section1 QML value types
+
+ \generatelist qmlvaluetypes
+
+ \generatelist qmlvaluetypesbymodule QDoc.Test
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/parent.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/parent.qdoc
new file mode 100644
index 000000000..d6bbb5738
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/parent.qdoc
@@ -0,0 +1,87 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*!
+ \qmltype AbstractParent
+ \inqmlmodule QDoc.Test
+ \qmlabstract
+ \brief Abstract base QML type.
+*/
+
+/*!
+ \qmlproperty list<Child> AbstractParent::children
+ \qmldefault
+ \brief Children of the type.
+*/
+
+/*!
+ \qmlmethod void AbstractParent::rear(Child child, var method = Strict)
+ \brief Do some abstract parenting on \a child using a specific \a method.
+*/
+
+/*!
+ \qmlproperty string AbstractParent::name
+ \brief Name of this parent.
+*/
+
+/*!
+ \qmlmethod void AbstractParent::name(Child child, name)
+ \brief Name a \a child using \a name.
+
+*/
+
+/*!
+ \qmlmethod void AbstractParent::name()
+ \brief Name all children with random names.
+*/
+
+/*!
+ \qmltype Child
+ \inqmlmodule QDoc.Test
+ \inherits AbstractParent
+ \brief A Child inheriting its parent.
+*/
+
+/*!
+ //! override from abstract base
+ \qmlproperty string Child::name
+ \brief Name of this child.
+*/
+
+/*!
+ //! override from abstract base
+ \qmlmethod void Child::name(Child child, name)
+ \brief Name a \a child of this child using \a name.
+*/
+
+/*!
+ \qmlvaluetype int
+ \inqmlmodule QDoc.Test
+
+ \brief An integer value type.
+*/
+
+/*!
+ \qmlmethod int int::abs()
+ Returns the absolute value of this integer.
+*/
+
+/*!
+ \qmltype InternParent
+ \inqmlmodule QDoc.Test
+ \internal
+ \qmlabstract
+ \brief Internal abstract base QML type.
+*/
+
+/*!
+ \qmlproperty int InternParent::prop
+ \brief Propagated to inheriting type docs.
+*/
+
+/*!
+ \qmltype YetAnotherChild
+ \inherits InternParent
+ \inqmlmodule QDoc.Test
+ \brief A type inheriting from internal abstract parent.
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/type.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/type.cpp
new file mode 100644
index 000000000..956616ed7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/qml/type.cpp
@@ -0,0 +1,133 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "testcpp.h"
+
+/*!
+ \qmlmodule QDoc.Test \QDocTestVer
+ \title QDoc.Test QML Module
+ \brief QML Types for the Test module.
+ \since 1.1
+ \preliminary
+
+ \testnoautolist
+*/
+
+/*!
+ \qmlmodule Test.Empty 1.0
+ \title No QML Types Here
+ \brief A QML module with no member types.
+*/
+
+/*!
+ \qmlmodule Test.NoVer
+ \title Versionless QML Module
+ \brief QML Types for the Test module without version.
+ \since 1.1
+ \modulestate Tech Preview
+*/
+
+/*!
+ \qmltype Type
+ \nativetype TestQDoc::Test
+ \inqmlmodule QDoc.Test
+ \brief A QML type documented in a .cpp file.
+ \meta status { <Work In Progress> }
+*/
+
+/*!
+ \qmltype TypeNoVersion
+ \inqmlmodule Test.NoVer
+ \brief Another QML type documented in a .cpp file.
+*/
+
+/*!
+ \qmltype OldType
+ \inqmlmodule QDoc.Test
+ \brief Deprecated old type.
+ \deprecated [1.0]
+*/
+
+/*!
+ \qmlproperty int Type::id
+ \readonly
+ \brief A read-only property.
+*/
+
+/*!
+ \qmlproperty string QDoc.Test::Type::name
+ \required
+ \brief Name of the Test.
+*/
+
+/*!
+ \qmlattachedproperty enumeration Type::type
+ \default Type.NoType
+
+ \value Type.NoType
+ Nothing
+ \value Type.SomeType
+ Something
+*/
+
+/*!
+ \qmlproperty int Type::group.first
+ \qmlproperty int Type::group.second
+ \qmlproperty int Type::group.third
+
+ \brief A property group.
+*/
+
+/*!
+ \qmlsignal Type::group.created
+
+ This signal is prefixed with \e group.
+*/
+
+/*!
+ \qmlproperty int Type::fourth
+ \qmlproperty int Type::fifth
+
+ \brief A group of properties sharing a documentation comment.
+*/
+
+/*!
+ \qmlmethod Type Type::copy(a)
+
+ Returns another Type based on \a a.
+*/
+
+/*!
+ \qmlmethod Type::enable()
+ \qmlmethod Type::disable()
+
+ Enables or disables this type.
+*/
+
+/*!
+ \qmlsignal Type::completed(int status)
+
+ This signal is emitted when the operation completed with \a status.
+*/
+
+/*!
+ \qmlattachedsignal Type::configured()
+
+ This attached signal is emitted when the type was configured.
+*/
+
+/*!
+ \qmlmethod Type::deprecatedMethod()
+
+ \deprecated [6.2] This method has no replacement.
+
+ This is a method that should include information about being deprecated
+ and that it has been so since 6.2 in its docs.
+*/
+
+/*!
+ \qmlmethod Type::futureDeprecated()
+ \deprecated [6.3] Use something else instead.
+
+ This is a method that's marked for deprecation in a future version.
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/snippets/snippet_testcpp.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/snippets/snippet_testcpp.cpp
new file mode 100644
index 000000000..1660fbc2b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/snippets/snippet_testcpp.cpp
@@ -0,0 +1,3 @@
+//! [random tag]
+You're not supposed to see this.
+//! [random tag]
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/testcpp.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/testcpp.cpp
new file mode 100644
index 000000000..b14270e45
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/testcpp.cpp
@@ -0,0 +1,418 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include "testcpp.h"
+
+namespace TestQDoc {
+
+/*
+//! [random tag]
+\note This is just a test.
+//! [random tag]
+
+//! [args]
+\1\2 \3 \2\1
+//! [args]
+*/
+
+/*!
+ \module TestCPP
+ \qtvariable testcpp
+ \qtcmakepackage QDocTest
+ \title QDoc Test C++ Classes
+ \brief A test module page.
+ \since 2.0
+
+ \testnoautolist
+
+ \include testcpp.cpp random tag
+ \include testcpp.cpp {args} {/} {*} {Look, Ma! {I'm made of arguments!}}
+
+\if defined(test_nestedmacro)
+ \versionnote {module} {\ver 5.15.0}
+ \ver 1.0.0
+\endif
+
+ \section1 Linking to function-like things
+
+ \list
+ \li \l {TestQDoc::Test::someFunctionDefaultArg}
+ {someFunctionDefaultArg()}
+ \li QDOCTEST_MACRO2()
+ \li \l {TestQDoc::Test::}{QDOCTEST_MACRO2(int &x)}
+ \li \l {section()}
+ \li \l {section()} {section() is a section title}
+ \li \l {TestQDoc::Test::Test()} {open( parenthesis}
+ \li \l {https://en.cppreference.com/w/cpp/utility/move}
+ {C++11 added std::move(T&& t)}
+ \endlist
+
+ \section2 section()
+*/
+
+/*!
+ \namespace TestQDoc
+ \inheaderfile TestCPP
+ \inmodule TestCPP
+ \brief A namespace.
+
+ \section1 Usage
+ This namespace is for testing QDoc output.
+*/
+
+/*!
+ \class TestQDoc::Test
+ \inmodule TestCPP
+ \brief A class in a namespace.
+
+\if defined(test_ignoresince)
+ //! omitted by ignoresince
+ \since 1.1
+\endif
+ \ingroup testgroup
+ \ingroup cpptypes
+ \reentrant
+*/
+
+/*!
+ \fn TestQDoc::Test::Test()
+
+ The constructor is deleted.
+*/
+
+/*!
+ \fn Test &Test::operator=(Test &&other)
+ \ingroup testgroup
+
+ The move assignment operator is deleted. \a other cannot be moved from.
+*/
+
+/*!
+ \class TestQDoc::TestDerived
+ \inmodule TestCPP
+ \brief A class in a namespace, derived from \l [CPP] Test.
+*/
+
+/*!
+ \macro QDOCTEST_MACRO
+ \relates TestQDoc
+\if defined(test_ignoresince)
+ //! omitted by ignoresince.Test
+ \since Test 0.9
+\endif
+*/
+
+/*!
+ \macro QDOCTEST_MACRO2(int &x)
+ \relates TestQDoc::Test
+ \since Test 1.1
+ \brief A macro with argument \a x.
+ \ingroup testgroup
+*/
+
+/*!
+\if defined(test_properties)
+ \property Test::id
+\else
+ \nothing
+\endif
+*/
+
+/*!
+ \deprecated [6.0] Use someFunction() instead.
+*/
+void Test::deprecatedMember()
+{
+ return;
+}
+
+/*!
+ \obsolete
+
+ Use someFunction() instead.
+*/
+void Test::obsoleteMember()
+{
+ return;
+}
+
+/*!
+ \obsolete Use obsoleteMember() instead.
+*/
+void Test::anotherObsoleteMember()
+{
+ return;
+}
+
+/*!
+ \nonreentrant
+ Function that takes a parameter \a i and \a b.
+\if defined(test_ignoresince)
+ \since 2.0
+\endif
+ \ingroup testgroup
+*/
+void Test::someFunctionDefaultArg(int i, bool b = false) const
+{
+ return;
+}
+
+/*!
+ \fn void Test::func(bool)
+ \internal
+*/
+
+/*!
+ \fn [funcPtr] void (*funcPtr(bool b, const char *s))(bool)
+
+ Returns a pointer to a function that takes a boolean. Uses \a b and \a s.
+*/
+
+/*!
+ \fn [op-inc] Test::operator++()
+ \fn [op-dec] Test::operator--()
+ \deprecated
+*/
+
+/*!
+ This method has en dashes in its documentation -- as you'll find
+ represented by \c{--} in the sources -- here and there. The important bit
+ to note is that when passed e.g. to the \\c command, the two hyphens are
+ processed as input to the command and not replaced by an en dash. This also
+ applies to code blocks, where otherwise, the decrement operator would get
+ completely borked:
+
+ \code
+ for (int i = 42; i > 0; --i)
+ // Do something cool during countdown.
+ \endcode
+
+ ...as it would be silly if this would output --i instead of \c {--i}.
+
+ -----------------------------------------------------------------------
+
+ It still allows people to add a bunch of dashes, though, without replacing
+ them all with a series of en dashes. Of course, they might want to use the
+ \\hr command instead, like this:
+ \hr
+
+ -- You can also start a new paragraph with an en dash, if you want to.
+
+ //! Self-referencing \sa-command for tests.
+ \sa methodWithEnDashInItsDocs
+*/
+void Test::methodWithEnDashInItsDocs()
+{
+ // Nothing to see here.
+}
+
+/*!
+ This method has em dashes in its documentation---as you'll find
+ represented by \c{---} in the sources---here and there. The important bit
+ to note is that when passed e.g. to the \\c command, the three hyphens are
+ processed as input to the command and not replaced by an em dash.
+
+ -----------------------------------------------------------------------
+
+ People can still add a bunch of dashes, though, without QDoc replacing
+ them all with a series of em dashes.
+
+ ---You can also start a new paragraph with an em dash, if you want to.
+
+ \sa methodWithEnDashInItsDocs
+
+*/
+void Test::methodWithEmDashInItsDocs()
+{
+ // Woah! Look at that!
+}
+
+// Documented below with an \fn command. Unnecessary but we support it, and it's used.
+int Test::someFunction(int, int v)
+{
+ return v;
+}
+
+/*!
+ \fn void TestQDoc::Test::inlineFunction()
+
+ \brief An inline function, documented using the \CMDFN QDoc command.
+*/
+
+/*!
+ \fn int Test::someFunction(int, int v = 0)
+
+ Function that takes a parameter \a v.
+ Also returns the value of \a v.
+\if defined(test_ignoresince)
+ \since Test 1.0
+\endif
+*/
+
+/*!
+ Function that must be reimplemented.
+*/
+void Test::virtualFun()
+{
+ return;
+}
+
+/*!
+ \fn bool Test::operator==(const Test &lhs, const Test &rhs)
+
+ Returns true if \a lhs and \a rhs are equal.
+*/
+
+/*!
+ \typedef TestQDoc::Test::SomeType
+ \brief A typedef.
+*/
+
+/*!
+ \reimp
+*/
+void TestDerived::virtualFun()
+{
+ return;
+}
+
+/*!
+ \fn TestQDoc::Test::overload()
+ \fn Test::overload(bool b)
+ //! The second overload should match even without the fully qualified path
+
+ Overloads that share a documentation comment, optionally taking
+ a parameter \a b.
+*/
+
+/*!
+ \fn Test::overload(bool b)
+ \since Test 1.2
+*/
+
+/*!
+ \typealias TestQDoc::TestDerived::DerivedType
+ An aliased typedef.
+*/
+
+/*!
+ \typedef TestQDoc::TestDerived::NotTypedef
+ I'm an alias, not a typedef.
+*/
+
+/*!
+ \obsolete
+
+ Static obsolete method.
+*/
+void TestDerived::staticObsoleteMember()
+{
+ return;
+}
+
+/*!
+\if defined(test_properties)
+ \fn void TestDerived::emitSomething()
+ Emitted when things happen.
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_properties)
+ \reimp
+\else
+ \nothing
+\endif
+*/
+int TestDerived::id()
+{
+ return 1;
+}
+
+/*!
+ Returns a value using an aliases type.
+*/
+TestDerived::NotTypedef TestDerived::someValue()
+{
+ return 0;
+}
+
+/*!
+\if defined(test_template)
+ \fn template <typename T1, typename T2> void TestQDoc::Test::funcTemplate(T1 a, T2 b)
+ \brief Function template with two parameters, \a a and \a b.
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_template)
+ \struct TestQDoc::Test::Struct
+ \inmodule TestCPP
+ \brief Templated struct.
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_template)
+ \typealias TestQDoc::Test::Specialized
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_template)
+ \class TestQDoc::Vec
+ \inmodule TestCPP
+ \brief Type alias that has its own reference.
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_template)
+ \macro Q_INVOKABLE
+ \relates TestQDoc::Test
+
+ This is a mock Q_INVOKABLE for the purpose of ensuring QDoc autolink to it
+ as expected.
+\else
+ \nothing
+\endif
+*/
+
+} // namespace TestQDoc
+
+
+/*!
+ \namespace CrossModuleRef
+ \inmodule TestCPP
+ \brief Namespace that has documented functions in multiple modules.
+ \since 3.0
+*/
+namespace CrossModuleRef {
+
+/*!
+ Document me!
+*/
+void documentMe()
+{
+}
+
+} // namespace CrossModuleRef
+
+/*!
+ \class DontLinkToMe
+ \inmodule TestCPP
+ \brief Class that does not generate documentation.
+*/
+
+/*!
+ \dontdocument (DontLinkToMe)
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/testcpp.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/testcpp.h
new file mode 100644
index 000000000..a72367b24
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/testcpp.h
@@ -0,0 +1,140 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#pragma once
+
+#ifdef test_properties
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qproperty.h>
+#include <QtCore/qstring.h>
+#endif
+
+#define QDOCTEST_MACRO test
+#define QDOCTEST_MACRO2(x) (x) < 0 ? 0 : (x)
+
+namespace TestQDoc {
+
+class Test {
+#ifdef test_properties
+ Q_OBJECT
+ Q_PROPERTY(int id READ id)
+#endif
+public:
+
+#ifdef test_template
+template<typename D, typename T> struct Struct {};
+template<typename T>
+using Specialized = Struct<int, T>;
+#endif
+
+#ifdef test_template
+# define Q_INVOKABLE void foo() {};
+#endif
+
+#ifdef test_scopedenum
+ enum ClassicEnum { Yee, Haw, Howdy, Partner };
+
+ enum class ScopedEnum : unsigned char {
+ This = 0x01,
+ That = 0x02,
+ All = This | That,
+ OmittedValue = 99,
+ UselessValue,
+ VeryLastValue
+ };
+#endif
+ typedef struct {
+ int data;
+ } SomeType;
+ int someFunction(int, int v = 0);
+ void someFunctionDefaultArg(int i, bool b) const;
+ void obsoleteMember();
+ void anotherObsoleteMember();
+ void deprecatedMember();
+ void methodWithEnDashInItsDocs();
+ void methodWithEmDashInItsDocs();
+ void func(bool) {};
+ //! [funcPtr]
+ void (*funcPtr(bool b, const char *s))(bool) {
+ return func;
+ }
+ //! [op-inc]
+ Test &operator++() { return *this; }
+ //! [op-dec]
+ Test &operator--() { return *this; }
+
+ void anotherFunc() {};
+ inline void inlineFunction() {};
+ virtual void virtualFun();
+
+ friend bool operator==(const Test &lhs, const Test &rhs) { return false; }
+
+ Test() = delete;
+ Test &operator=(Test &&other) = delete;
+
+protected:
+ void overload() {}
+ void overload(bool b) { if (!b) return; }
+#ifdef test_template
+ template <typename T1, typename T2> void funcTemplate(T1 a, T2 b) {
+ a = b;
+ }
+#endif
+#ifdef test_properties
+ virtual int id() { return 0; }
+#endif
+};
+
+class TestDerived : public Test {
+#ifdef test_properties
+ Q_OBJECT
+
+ Q_PROPERTY(QString bindableProp READ bindableProp WRITE setBindableProp NOTIFY bindablePropChanged BINDABLE bindableProp)
+ Q_PROPERTY(QString someProp READ someProp BINDABLE somBindableProp)
+ Q_PROPERTY(int *intProp READ getInt STORED false CONSTANT FINAL)
+ Q_PROPERTY(const QString *name READ name)
+ QDOC_PROPERTY(bool boolProp READ boolProp WRITE setBoolProp NOTIFY boolPropChanged RESET resetBoolProp REVISION 1)
+#endif
+
+public:
+ using DerivedType = Test::SomeType;
+ using NotTypedef = int;
+ void virtualFun() override;
+ static void staticObsoleteMember();
+ NotTypedef someValue();
+#ifdef test_properties
+ QBindable<QString> bindableProp();
+ QBindable<QString> someBindableProp();
+ const QString &someProp();
+ int *getInt();
+ bool boolProp();
+ const QString *name() const;
+
+ Q_INVOKABLE void invokeMe() const {}
+ int id() override;
+
+Q_SIGNALS:
+ void emitSomething(QPrivateSignal);
+ void bindablePropChanged();
+ Q_REVISION(1) void boolPropChanged();
+
+public Q_SLOTS:
+ void setBindableProp(const QString &s);
+ void setBoolProp(bool b);
+ void resetBoolProp();
+#endif
+};
+
+#ifdef test_template
+template <typename T>
+struct BaseVec {};
+template <typename T>
+using Vec = BaseVec<T>;
+#endif
+
+} // namespace TestQDoc
+
+namespace CrossModuleRef {
+ void documentMe();
+}
+
+class DontLinkToMe {};
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/unseenclass.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/unseenclass.qdoc
new file mode 100644
index 000000000..8798e3147
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/comprehensiveproject/src/unseenclass.qdoc
@@ -0,0 +1,11 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+ \dontdocument (UnseenClass)
+*/
+
+/*! \page classes.html
+ \title Classes
+ \generatelist annotatedclasses
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/cxx20.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/cxx20.qdocconf
new file mode 100644
index 000000000..f8943e417
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/cxx20.qdocconf
@@ -0,0 +1,32 @@
+project = cxx20
+
+headerdirs = ./src
+sourcedirs = ./src
+exampledirs = ./src
+
+macro.equalitycomparesto = "\\compareswith equality \1\n\\endcompareswith"
+
+outputformats = WebXML HTML DocBook
+WebXML.quotinginformation = true
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
+
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+
+DocBook.nosubdirs = true
+DocBook.outputsubdir = docbook
+
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ./src/images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/bar.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/bar.xml
new file mode 100644
index 000000000..5a6754700
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/bar.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Bar Class</db:title>
+<db:productname>cxx20</db:productname>
+<db:titleabbrev>cxx20 Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>cxx20 Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Bar</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/baz.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/baz.xml
new file mode 100644
index 000000000..799e31128
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/baz.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Baz Class</db:title>
+<db:productname>cxx20</db:productname>
+<db:titleabbrev>cxx20 Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>cxx20 Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Baz</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywithoneclassandpartiallywithanother.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywithoneclassandpartiallywithanother.xml
new file mode 100644
index 000000000..bbfadf732
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywithoneclassandpartiallywithanother.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>ComparesStronglyWithOneClassAndPartiallyWithAnother Class</db:title>
+<db:productname>cxx20</db:productname>
+<db:titleabbrev>cxx20 Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>Class with various comparison relationships.</db:para>
+<db:para>This class is <db:emphasis role="bold">strongly comparable</db:emphasis> with <db:link xlink:href="foo.xml">Foo</db:link>.</db:para>
+<db:para>This class is <db:emphasis role="bold">partially comparable</db:emphasis> with <db:link xlink:href="bar.xml">Bar</db:link>.</db:para>
+<db:para>Here we describe partial comparison with Bar.</db:para>
+<db:para>This class is <db:emphasis role="bold">equality-comparable</db:emphasis> with <db:link xlink:href="foo.xml">Foo</db:link>.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>ComparesStronglyWithOneClassAndPartiallyWithAnother</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:para>This class compares strongly with one type, and partially with another.</db:para>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywiththreeclasses.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywiththreeclasses.xml
new file mode 100644
index 000000000..8c39c4fa7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywiththreeclasses.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>ComparesStronglyWithThreeClasses Class</db:title>
+<db:productname>cxx20</db:productname>
+<db:titleabbrev>cxx20 Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>This class is <db:emphasis role="bold">strongly comparable</db:emphasis> with <db:link xlink:href="foo.xml">Foo</db:link>, Bar Bar Jinks, and <db:link xlink:href="baz.xml">Baz</db:link>.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>ComparesStronglyWithThreeClasses</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywiththreeclassesacrossmultiplelines.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywiththreeclassesacrossmultiplelines.xml
new file mode 100644
index 000000000..b216d2bbc
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywiththreeclassesacrossmultiplelines.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>ComparesStronglyWithThreeClassesAcrossMultipleLines Class</db:title>
+<db:productname>cxx20</db:productname>
+<db:titleabbrev>cxx20 Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>This class is <db:emphasis role="bold">strongly comparable</db:emphasis> with <db:link xlink:href="foo.xml">Foo</db:link>, <db:link xlink:href="bar.xml">Bar</db:link>, and <db:link xlink:href="baz.xml">Baz</db:link>.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>ComparesStronglyWithThreeClassesAcrossMultipleLines</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywithtwoclasses.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywithtwoclasses.xml
new file mode 100644
index 000000000..0c72faed6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/comparesstronglywithtwoclasses.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>ComparesStronglyWithTwoClasses Class</db:title>
+<db:productname>cxx20</db:productname>
+<db:titleabbrev>cxx20 Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>This class is <db:emphasis role="bold">strongly comparable</db:emphasis> with <db:link xlink:href="foo.xml">Foo</db:link>.</db:para>
+<db:para>This class is <db:emphasis role="bold">strongly comparable</db:emphasis> with <db:link xlink:href="bar.xml">Bar</db:link>.</db:para>
+<db:warning>
+<db:para>Always compare twice!</db:para>
+</db:warning>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>ComparesStronglyWithTwoClasses</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/equalitycomparableclass.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/equalitycomparableclass.xml
new file mode 100644
index 000000000..ed91bb03b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/equalitycomparableclass.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>EqualityComparableClass Class</db:title>
+<db:productname>cxx20</db:productname>
+<db:titleabbrev>cxx20 Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>This class is <db:emphasis>equality-comparable</db:emphasis>.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>EqualityComparableClass</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/foo.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/foo.xml
new file mode 100644
index 000000000..6eee2e060
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/foo.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Foo Class</db:title>
+<db:productname>cxx20</db:productname>
+<db:titleabbrev>cxx20 Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>cxx20 Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Foo</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/partiallyorderedclass.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/partiallyorderedclass.xml
new file mode 100644
index 000000000..1be6bcba6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/partiallyorderedclass.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>PartiallyOrderedClass Class</db:title>
+<db:productname>cxx20</db:productname>
+<db:titleabbrev>cxx20 Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>This class is <db:emphasis>partially comparable</db:emphasis>.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>PartiallyOrderedClass</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/stronglyorderedclass.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/stronglyorderedclass.xml
new file mode 100644
index 000000000..146860d7b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/stronglyorderedclass.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>StronglyOrderedClass Class</db:title>
+<db:productname>cxx20</db:productname>
+<db:titleabbrev>cxx20 Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>This class is <db:emphasis>strongly comparable</db:emphasis>.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>StronglyOrderedClass</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/weaklyorderedclass.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/weaklyorderedclass.xml
new file mode 100644
index 000000000..39bbdeeb9
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/docbook/weaklyorderedclass.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>WeaklyOrderedClass Class</db:title>
+<db:productname>cxx20</db:productname>
+<db:titleabbrev>cxx20 Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>This class is <db:emphasis>weakly comparable</db:emphasis>.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>WeaklyOrderedClass</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/bar.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/bar.html
new file mode 100644
index 000000000..a21a45105
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/bar.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classes_with_various_ordering.cpp -->
+ <title>Bar Class | cxx20</title>
+</head>
+<body>
+<li>Bar</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Bar Class</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Bar&gt;</span></td></tr>
+</table></div>
+<!-- $$$Bar-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Bar -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/baz.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/baz.html
new file mode 100644
index 000000000..9044101aa
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/baz.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classes_with_various_ordering.cpp -->
+ <title>Baz Class | cxx20</title>
+</head>
+<body>
+<li>Baz</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Baz Class</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Baz&gt;</span></td></tr>
+</table></div>
+<!-- $$$Baz-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Baz -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywithoneclassandpartiallywithanother.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywithoneclassandpartiallywithanother.html
new file mode 100644
index 000000000..42847f896
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywithoneclassandpartiallywithanother.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classes_with_various_ordering.cpp -->
+ <meta name="description" content="Class with various comparison relationships.">
+ <title>ComparesStronglyWithOneClassAndPartiallyWithAnother Class | cxx20</title>
+</head>
+<body>
+<li>ComparesStronglyWithOneClassAndPartiallyWithAnother</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">ComparesStronglyWithOneClassAndPartiallyWithAnother Class</h1>
+<!-- $$$ComparesStronglyWithOneClassAndPartiallyWithAnother-brief -->
+<p>Class with various comparison relationships. <a href="#details">More...</a></p>
+<!-- @@@ComparesStronglyWithOneClassAndPartiallyWithAnother -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;ComparesStronglyWithOneClassAndPartiallyWithAnother&gt;</span></td></tr>
+</table></div>
+<p>This class is <b>strongly comparable</b> with <a href="foo.html" translate="no">Foo</a>.</p>
+<p>This class is <b>partially comparable</b> with <a href="bar.html" translate="no">Bar</a>.</p>
+<p>Here we describe partial comparison with Bar.</p>
+<p>This class is <b>equality-comparable</b> with <a href="foo.html" translate="no">Foo</a>.</p>
+<!-- $$$ComparesStronglyWithOneClassAndPartiallyWithAnother-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<p>This class compares strongly with one type, and partially with another.</p>
+</div>
+<!-- @@@ComparesStronglyWithOneClassAndPartiallyWithAnother -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywiththreeclasses.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywiththreeclasses.html
new file mode 100644
index 000000000..a92815465
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywiththreeclasses.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classes_with_various_ordering.cpp -->
+ <title>ComparesStronglyWithThreeClasses Class | cxx20</title>
+</head>
+<body>
+<li>ComparesStronglyWithThreeClasses</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">ComparesStronglyWithThreeClasses Class</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;ComparesStronglyWithThreeClasses&gt;</span></td></tr>
+</table></div>
+<p>This class is <b>strongly comparable</b> with <a href="foo.html" translate="no">Foo</a>, Bar Bar Jinks, and <a href="baz.html" translate="no">Baz</a>.</p>
+<!-- $$$ComparesStronglyWithThreeClasses-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@ComparesStronglyWithThreeClasses -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywiththreeclassesacrossmultiplelines.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywiththreeclassesacrossmultiplelines.html
new file mode 100644
index 000000000..736e433fd
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywiththreeclassesacrossmultiplelines.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classes_with_various_ordering.cpp -->
+ <title>ComparesStronglyWithThreeClassesAcrossMultipleLines Class | cxx20</title>
+</head>
+<body>
+<li>ComparesStronglyWithThreeClassesAcrossMultipleLines</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">ComparesStronglyWithThreeClassesAcrossMultipleLines Class</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;ComparesStronglyWithThreeClassesAcrossMultipleLines&gt;</span></td></tr>
+</table></div>
+<p>This class is <b>strongly comparable</b> with <a href="foo.html" translate="no">Foo</a>, <a href="bar.html" translate="no">Bar</a>, and <a href="baz.html" translate="no">Baz</a>.</p>
+<!-- $$$ComparesStronglyWithThreeClassesAcrossMultipleLines-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@ComparesStronglyWithThreeClassesAcrossMultipleLines -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywithtwoclasses.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywithtwoclasses.html
new file mode 100644
index 000000000..bd86314ae
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/comparesstronglywithtwoclasses.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classes_with_various_ordering.cpp -->
+ <title>ComparesStronglyWithTwoClasses Class | cxx20</title>
+</head>
+<body>
+<li>ComparesStronglyWithTwoClasses</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">ComparesStronglyWithTwoClasses Class</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;ComparesStronglyWithTwoClasses&gt;</span></td></tr>
+</table></div>
+<p>This class is <b>strongly comparable</b> with <a href="foo.html" translate="no">Foo</a>.</p>
+<p>This class is <b>strongly comparable</b> with <a href="bar.html" translate="no">Bar</a>.</p>
+<div class="admonition warning">
+<p><b>Warning: </b>Always compare twice!</p>
+</div>
+<!-- $$$ComparesStronglyWithTwoClasses-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@ComparesStronglyWithTwoClasses -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/cxx20.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/cxx20.index
new file mode 100644
index 000000000..ab2c3e24f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/cxx20.index
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="cxx20 Reference Documentation" version="" project="cxx20">
+ <namespace name="" status="active" access="public" module="cxx20">
+ <class name="Bar" href="bar.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc"/>
+ <class name="Baz" href="baz.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc"/>
+ <class name="ComparesStronglyWithOneClassAndPartiallyWithAnother" href="comparesstronglywithoneclassandpartiallywithanother.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc" brief="Class with various comparison relationships"/>
+ <class name="ComparesStronglyWithThreeClasses" href="comparesstronglywiththreeclasses.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc"/>
+ <class name="ComparesStronglyWithThreeClassesAcrossMultipleLines" href="comparesstronglywiththreeclassesacrossmultiplelines.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc"/>
+ <class name="ComparesStronglyWithTwoClasses" href="comparesstronglywithtwoclasses.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc"/>
+ <class name="EqualityComparableClass" href="equalitycomparableclass.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc" comparison_category="equality"/>
+ <class name="Foo" href="foo.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc"/>
+ <class name="PartiallyOrderedClass" href="partiallyorderedclass.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc" comparison_category="partial"/>
+ <class name="StronglyOrderedClass" href="stronglyorderedclass.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc" comparison_category="strong"/>
+ <class name="WeaklyOrderedClass" href="weaklyorderedclass.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc" comparison_category="weak"/>
+ <module name="TestQDoc" href="testqdoc-module.html" status="internal" seen="false" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/equalitycomparableclass.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/equalitycomparableclass.html
new file mode 100644
index 000000000..24303bb97
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/equalitycomparableclass.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classes_with_various_ordering.cpp -->
+ <title>EqualityComparableClass Class | cxx20</title>
+</head>
+<body>
+<li>EqualityComparableClass</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">EqualityComparableClass Class</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;EqualityComparableClass&gt;</span></td></tr>
+</table></div>
+<p>This class is <i>equality-comparable</i>.</p>
+<!-- $$$EqualityComparableClass-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@EqualityComparableClass -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/foo.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/foo.html
new file mode 100644
index 000000000..ba40175c6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/foo.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classes_with_various_ordering.cpp -->
+ <title>Foo Class | cxx20</title>
+</head>
+<body>
+<li>Foo</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Foo Class</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Foo&gt;</span></td></tr>
+</table></div>
+<!-- $$$Foo-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Foo -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/partiallyorderedclass.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/partiallyorderedclass.html
new file mode 100644
index 000000000..359742532
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/partiallyorderedclass.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classes_with_various_ordering.cpp -->
+ <title>PartiallyOrderedClass Class | cxx20</title>
+</head>
+<body>
+<li>PartiallyOrderedClass</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">PartiallyOrderedClass Class</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;PartiallyOrderedClass&gt;</span></td></tr>
+</table></div>
+<p>This class is <i>partially comparable</i>.</p>
+<!-- $$$PartiallyOrderedClass-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@PartiallyOrderedClass -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/stronglyorderedclass.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/stronglyorderedclass.html
new file mode 100644
index 000000000..d4972aaa6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/stronglyorderedclass.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classes_with_various_ordering.cpp -->
+ <title>StronglyOrderedClass Class | cxx20</title>
+</head>
+<body>
+<li>StronglyOrderedClass</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">StronglyOrderedClass Class</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;StronglyOrderedClass&gt;</span></td></tr>
+</table></div>
+<p>This class is <i>strongly comparable</i>.</p>
+<!-- $$$StronglyOrderedClass-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@StronglyOrderedClass -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/weaklyorderedclass.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/weaklyorderedclass.html
new file mode 100644
index 000000000..0849b2a1b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/html/weaklyorderedclass.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classes_with_various_ordering.cpp -->
+ <title>WeaklyOrderedClass Class | cxx20</title>
+</head>
+<body>
+<li>WeaklyOrderedClass</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">WeaklyOrderedClass Class</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;WeaklyOrderedClass&gt;</span></td></tr>
+</table></div>
+<p>This class is <i>weakly comparable</i>.</p>
+<!-- $$$WeaklyOrderedClass-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@WeaklyOrderedClass -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/bar.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/bar.webxml
new file mode 100644
index 000000000..a4e70b190
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/bar.webxml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="Bar" href="bar.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc">
+ <description/>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/baz.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/baz.webxml
new file mode 100644
index 000000000..18eff7e9c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/baz.webxml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="Baz" href="baz.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc">
+ <description/>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywithoneclassandpartiallywithanother.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywithoneclassandpartiallywithanother.webxml
new file mode 100644
index 000000000..a296ae73f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywithoneclassandpartiallywithanother.webxml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="ComparesStronglyWithOneClassAndPartiallyWithAnother" href="comparesstronglywithoneclassandpartiallywithanother.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc" brief="Class with various comparison relationships">
+ <description>
+ <brief>Class with various comparison relationships.</brief>
+ <para>This class compares strongly with one type, and partially with another.</para>
+ </description>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywiththreeclasses.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywiththreeclasses.webxml
new file mode 100644
index 000000000..c58fd61a5
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywiththreeclasses.webxml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="ComparesStronglyWithThreeClasses" href="comparesstronglywiththreeclasses.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc">
+ <description/>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywiththreeclassesacrossmultiplelines.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywiththreeclassesacrossmultiplelines.webxml
new file mode 100644
index 000000000..971a554ba
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywiththreeclassesacrossmultiplelines.webxml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="ComparesStronglyWithThreeClassesAcrossMultipleLines" href="comparesstronglywiththreeclassesacrossmultiplelines.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc">
+ <description/>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywithtwoclasses.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywithtwoclasses.webxml
new file mode 100644
index 000000000..524689e0a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/comparesstronglywithtwoclasses.webxml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="ComparesStronglyWithTwoClasses" href="comparesstronglywithtwoclasses.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc">
+ <description/>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/cxx20.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/cxx20.index
new file mode 100644
index 000000000..ab2c3e24f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/cxx20.index
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="cxx20 Reference Documentation" version="" project="cxx20">
+ <namespace name="" status="active" access="public" module="cxx20">
+ <class name="Bar" href="bar.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc"/>
+ <class name="Baz" href="baz.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc"/>
+ <class name="ComparesStronglyWithOneClassAndPartiallyWithAnother" href="comparesstronglywithoneclassandpartiallywithanother.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc" brief="Class with various comparison relationships"/>
+ <class name="ComparesStronglyWithThreeClasses" href="comparesstronglywiththreeclasses.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc"/>
+ <class name="ComparesStronglyWithThreeClassesAcrossMultipleLines" href="comparesstronglywiththreeclassesacrossmultiplelines.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc"/>
+ <class name="ComparesStronglyWithTwoClasses" href="comparesstronglywithtwoclasses.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc"/>
+ <class name="EqualityComparableClass" href="equalitycomparableclass.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc" comparison_category="equality"/>
+ <class name="Foo" href="foo.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc"/>
+ <class name="PartiallyOrderedClass" href="partiallyorderedclass.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc" comparison_category="partial"/>
+ <class name="StronglyOrderedClass" href="stronglyorderedclass.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc" comparison_category="strong"/>
+ <class name="WeaklyOrderedClass" href="weaklyorderedclass.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc" comparison_category="weak"/>
+ <module name="TestQDoc" href="testqdoc-module.html" status="internal" seen="false" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/equalitycomparableclass.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/equalitycomparableclass.webxml
new file mode 100644
index 000000000..32d48bd41
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/equalitycomparableclass.webxml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="EqualityComparableClass" href="equalitycomparableclass.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc" comparison_category="equality">
+ <description/>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/foo.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/foo.webxml
new file mode 100644
index 000000000..e36896ca9
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/foo.webxml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="Foo" href="foo.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc">
+ <description/>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/partiallyorderedclass.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/partiallyorderedclass.webxml
new file mode 100644
index 000000000..fd41aa9a8
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/partiallyorderedclass.webxml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="PartiallyOrderedClass" href="partiallyorderedclass.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc" comparison_category="partial">
+ <description/>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/stronglyorderedclass.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/stronglyorderedclass.webxml
new file mode 100644
index 000000000..7f8440a60
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/stronglyorderedclass.webxml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="StronglyOrderedClass" href="stronglyorderedclass.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc" comparison_category="strong">
+ <description/>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/weaklyorderedclass.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/weaklyorderedclass.webxml
new file mode 100644
index 000000000..d8580e92b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/expected/webxml/weaklyorderedclass.webxml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="WeaklyOrderedClass" href="weaklyorderedclass.html" status="active" access="public" location="classes_with_various_ordering.h" documented="true" module="TestQDoc" comparison_category="weak">
+ <description/>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/src/classes_with_various_ordering.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/src/classes_with_various_ordering.cpp
new file mode 100644
index 000000000..c9029e446
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/src/classes_with_various_ordering.cpp
@@ -0,0 +1,89 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+ \class StronglyOrderedClass
+ \inmodule TestQDoc
+ \compares strong
+*/
+
+/*!
+ \class PartiallyOrderedClass
+ \inmodule TestQDoc
+ \compares partial
+*/
+
+/*!
+ \class WeaklyOrderedClass
+ \inmodule TestQDoc
+ \compares weak
+*/
+
+/*!
+ \class EqualityComparableClass
+ \inmodule TestQDoc
+ \compares equality
+*/
+
+/*!
+ \class ComparesStronglyWithTwoClasses
+ \inmodule TestQDoc
+
+ \compareswith strong Foo
+ \endcompareswith
+
+ \compareswith strong Bar
+ \warning Always compare twice!
+ \endcompareswith
+*/
+
+/*!
+ \class ComparesStronglyWithThreeClasses
+ \inmodule TestQDoc
+
+ \compareswith strong Foo {Bar Bar Jinks} Baz
+ \endcompareswith
+*/
+
+/*!
+ \class ComparesStronglyWithThreeClassesAcrossMultipleLines
+ \inmodule TestQDoc
+
+ \compareswith strong Foo \
+ Bar Baz
+ \endcompareswith
+*/
+
+/*!
+ \class ComparesStronglyWithOneClassAndPartiallyWithAnother
+ \inmodule TestQDoc
+ \brief Class with various comparison relationships.
+
+ \compareswith strong Foo
+ \endcompareswith
+
+ \compareswith partial Bar
+ Here we describe partial comparison with Bar.
+ \endcompareswith
+
+ //! using a macro
+ \equalitycomparesto {Foo}
+
+ This class compares strongly with one type, and partially with another.
+*/
+
+
+/*!
+ \class Foo
+ \inmodule TestQDoc
+*/
+
+/*!
+ \class Bar
+ \inmodule TestQDoc
+*/
+
+/*!
+ \class Baz
+ \inmodule TestQDoc
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/src/classes_with_various_ordering.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/src/classes_with_various_ordering.h
new file mode 100644
index 000000000..4c9f6970e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/cxx20/src/classes_with_various_ordering.h
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef CLASSES_WITH_VARIOUS_ORDERING_H
+#define CLASSES_WITH_VARIOUS_ORDERING_H
+
+class StronglyOrderedClass
+{
+};
+class PartiallyOrderedClass
+{
+};
+class WeaklyOrderedClass
+{
+};
+class EqualityComparableClass
+{
+};
+class ComparesStronglyWithTwoClasses
+{
+};
+class ComparesStronglyWithThreeClasses
+{
+};
+class ComparesStronglyWithThreeClassesAcrossMultipleLines
+{
+};
+class ComparesStronglyWithOneClassAndPartiallyWithAnother
+{
+};
+class Foo
+{
+};
+class Bar
+{
+};
+class Baz
+{
+};
+
+#endif // CLASSES_WITH_VARIOUS_ORDERING_H
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/duplicate_section_titles_have_unique_anchors.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/duplicate_section_titles_have_unique_anchors.qdocconf
new file mode 100644
index 000000000..d1ebe89d0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/duplicate_section_titles_have_unique_anchors.qdocconf
@@ -0,0 +1,25 @@
+project = Duplicate section titles have unique anchors
+
+locationinfo = false
+
+headers.fileextensions = "*.h *.hpp"
+sources.fileextensions = "*.cpp *.qml *.qdoc"
+
+headerdirs = .
+sourcedirs = ./src
+
+warninglimit = 0
+warninglimit.enabled = true
+
+outputformats = WebXML HTML DocBook
+WebXML.quotinginformation = true
+
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
+
+DocBook.nosubdirs = true
+DocBook.outputsubdir = docbook
+
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/docbook/page-one.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/docbook/page-one.xml
new file mode 100644
index 000000000..350130f15
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/docbook/page-one.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Page One</db:title>
+<db:productname>Duplicate section titles have unique anchors</db:productname>
+<db:titleabbrev>Duplicate section titles have unique anchors Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>Duplicate section titles have unique anchors Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:para>This test is for a documentation project with multiple section titles whose names collide.</db:para>
+<db:section xml:id="section-one">
+<db:title>Section One</db:title>
+<db:para>This is the first section.</db:para>
+<db:section xml:id="section-two">
+<db:title>Section Two</db:title>
+<db:para>This is the first sub-section.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="section-one-2">
+<db:title>Section One</db:title>
+<db:para>This is the second section.</db:para>
+<db:section xml:id="section-one-2-section-two-3">
+<db:title>Section Two</db:title>
+<db:para>This is the second sub-section.</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/html/duplicate-section-titles-have-unique-anchors.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/html/duplicate-section-titles-have-unique-anchors.index
new file mode 100644
index 000000000..3f5d2a9af
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/html/duplicate-section-titles-have-unique-anchors.index
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="Duplicate section titles have unique anchors Reference Documentation" version="" project="Duplicate section titles have unique anchors">
+ <namespace name="" status="active" access="public" module="duplicate section titles have unique anchors">
+ <page name="page_one.html" href="page-one.html" status="active" location="page_one.qdoc" documented="true" subtype="page" title="Page One" fulltitle="Page One" subtitle="">
+ <contents name="section-one" title="Section One" level="1"/>
+ <contents name="section-two" title="Section Two" level="2"/>
+ <contents name="section-one-2" title="Section One" level="1"/>
+ <contents name="section-one-2-section-two-3" title="Section Two" level="2"/>
+ </page>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/html/page-one.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/html/page-one.html
new file mode 100644
index 000000000..5f6501bfa
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/html/page-one.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- page_one.qdoc -->
+ <title>Page One | Duplicate section titles have unique anchors</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#section-one">Section One</a></li>
+<li class="level2"><a href="#section-two">Section Two</a></li>
+<li class="level1"><a href="#section-one-2">Section One</a></li>
+<li class="level2"><a href="#section-one-2-section-two-3">Section Two</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Page One</h1>
+<!-- $$$page_one.html-description -->
+<div class="descr" id="details">
+<p>This test is for a documentation project with multiple section titles whose names collide.</p>
+<h2 id="section-one">Section One</h2>
+<p>This is the first section.</p>
+<h3 id="section-two">Section Two</h3>
+<p>This is the first sub-section.</p>
+<h2 id="section-one-2">Section One</h2>
+<p>This is the second section.</p>
+<h3 id="section-one-2-section-two-3">Section Two</h3>
+<p>This is the second sub-section.</p>
+</div>
+<!-- @@@page_one.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/webxml/duplicate-section-titles-have-unique-anchors.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/webxml/duplicate-section-titles-have-unique-anchors.index
new file mode 100644
index 000000000..3f5d2a9af
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/webxml/duplicate-section-titles-have-unique-anchors.index
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="Duplicate section titles have unique anchors Reference Documentation" version="" project="Duplicate section titles have unique anchors">
+ <namespace name="" status="active" access="public" module="duplicate section titles have unique anchors">
+ <page name="page_one.html" href="page-one.html" status="active" location="page_one.qdoc" documented="true" subtype="page" title="Page One" fulltitle="Page One" subtitle="">
+ <contents name="section-one" title="Section One" level="1"/>
+ <contents name="section-two" title="Section Two" level="2"/>
+ <contents name="section-one-2" title="Section One" level="1"/>
+ <contents name="section-one-2-section-two-3" title="Section Two" level="2"/>
+ </page>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/webxml/page-one.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/webxml/page-one.webxml
new file mode 100644
index 000000000..98c92b784
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/expected/webxml/page-one.webxml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="page_one.html" href="page-one.html" status="active" location="page_one.qdoc" documented="true" subtype="page" title="Page One" fulltitle="Page One" subtitle="">
+ <contents name="section-one" title="Section One" level="1"/>
+ <contents name="section-two" title="Section Two" level="2"/>
+ <contents name="section-one-2" title="Section One" level="1"/>
+ <contents name="section-one-2-section-two-3" title="Section Two" level="2"/>
+ <description>
+ <para>This test is for a documentation project with multiple section titles whose names collide.</para>
+ <section id="section-one">
+ <heading level="1">Section One</heading>
+ <para>This is the first section.</para>
+ </section>
+ <section id="section-two">
+ <heading level="2">Section Two</heading>
+ <para>This is the first sub-section.</para>
+ </section>
+ <section id="section-one">
+ <heading level="1">Section One</heading>
+ <para>This is the second section.</para>
+ </section>
+ <section id="section-two">
+ <heading level="2">Section Two</heading>
+ <para>This is the second sub-section.</para>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/src/page_one.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/src/page_one.qdoc
new file mode 100644
index 000000000..4596ffccd
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/duplicate_section_titles_have_unique_anchors/src/page_one.qdoc
@@ -0,0 +1,21 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page page_one.html
+ \title Page One
+
+ This test is for a documentation project with multiple section titles whose names collide.
+
+ \section1 Section One
+ This is the first section.
+
+ \section2 Section Two
+ This is the first sub-section.
+
+ \section1 Section One
+ This is the second section.
+
+ \section2 Section Two
+ This is the second sub-section.
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/expected/html/globals.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/expected/html/globals.html
new file mode 100644
index 000000000..92f6883ee
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/expected/html/globals.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- global.qdoc -->
+ <title>Globals Class | TestGlobals</title>
+</head>
+<body>
+<li>Globals</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#related-non-members">Related Non-Members</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Globals Class</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Globals&gt;</span></td></tr>
+</table></div>
+<h2 id="related-non-members">Related Non-Members</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> int </td><td class="memItemRight bottomAlign"><b><a href="globals.html#foo" translate="no">foo</a></b>(int <i>a</i>)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> int </td><td class="memItemRight bottomAlign"><b><a href="globals.html#foo-1" translate="no">foo</a></b>(int <i>a</i>, bool <i>b</i>)</td></tr>
+</table></div>
+<!-- $$$Globals-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Globals -->
+<div class="relnonmem">
+<h2>Related Non-Members</h2>
+<!-- $$$foo[overload1]$$$fooint -->
+<h3 class="fn" translate="no" id="foo"><span class="type">int</span> <span class="name">foo</span>(<span class="type">int</span> <i>a</i>)</h3>
+<p>Params: <i translate="no">a</i></p>
+<!-- @@@foo -->
+<!-- $$$foo$$$foointbool -->
+<h3 class="fn" translate="no" id="foo-1"><span class="type">int</span> <span class="name">foo</span>(<span class="type">int</span> <i>a</i>, <span class="type">bool</span> <i>b</i>)</h3>
+<p>Params: <i translate="no">b</i>, <i translate="no">b</i></p>
+<!-- @@@foo -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/expected/html/testglobals-module.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/expected/html/testglobals-module.html
new file mode 100644
index 000000000..23f59510c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/expected/html/testglobals-module.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- global.qdoc -->
+ <title>TestGlobals</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#namespaces">Namespaces</a></li>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="globals.html">Globals</a></p></td></tr>
+</table></div>
+<!-- $$$TestGlobals-description -->
+<div class="descr" id="details">
+<p>A collection of <a href="globals.html#foo" translate="no">foo</a>() functions.</p>
+</div>
+<!-- @@@TestGlobals -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/expected/html/testglobals.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/expected/html/testglobals.index
new file mode 100644
index 000000000..f1f0871d3
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/expected/html/testglobals.index
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="TestGlobals Reference Documentation" version="" project="TestGlobals">
+ <namespace name="" status="active" access="public" module="testglobals">
+ <function name="foo" href="globals.html#foo" status="active" access="public" location="global.h" documented="true" related="0" meta="plain" type="int" signature="int foo(int a)">
+ <parameter type="int" name="a" default=""/>
+ </function>
+ <function name="foo" href="globals.html#foo-1" status="active" access="public" location="global.h" documented="true" related="1" meta="plain" overload="true" overload-number="1" type="int" signature="int foo(int a, bool b)">
+ <parameter type="int" name="a" default=""/>
+ <parameter type="bool" name="b" default=""/>
+ </function>
+ <class name="Globals" href="globals.html" status="active" access="public" location="global.h" documented="true" module="TestGlobals">
+ <function name="foo" href="globals.html#foo" status="active" access="public" location="global.h" documented="true" related="0" meta="plain" type="int" signature="int foo(int a)">
+ <parameter type="int" name="a" default=""/>
+ </function>
+ <function name="foo" href="globals.html#foo-1" status="active" access="public" location="global.h" documented="true" related="1" meta="plain" overload="true" overload-number="1" type="int" signature="int foo(int a, bool b)">
+ <parameter type="int" name="a" default=""/>
+ <parameter type="bool" name="b" default=""/>
+ </function>
+ </class>
+ <module name="TestGlobals" href="testglobals-module.html" status="active" location="global.qdoc" documented="true" seen="true" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/globalfunc.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/globalfunc.qdocconf
new file mode 100644
index 000000000..fb28478bf
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/globalfunc.qdocconf
@@ -0,0 +1,23 @@
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ./src/images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+# By default, use -outputdir directly, no subdir.
+# outputsubdir '.' matches the root of expected_output/
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+
+project = TestGlobals
+includepaths += -I./src/globalfunc
+
+headers = ./src/global.h
+sources = ./src/global.qdoc
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/src/TestGlobals b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/src/TestGlobals
new file mode 100644
index 000000000..dc4f98a6e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/src/TestGlobals
@@ -0,0 +1 @@
+#include "global.h"
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/src/global.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/src/global.h
new file mode 100644
index 000000000..8b5290072
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/src/global.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+class Globals {};
+inline int foo(int a) { return a; }
+inline int foo(int a, bool b) { return b ? a : -a; }
+// Undocumented overload
+inline int foo(int a, bool b, bool c) { return b || c ? a : -a; }
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/src/global.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/src/global.qdoc
new file mode 100644
index 000000000..f61ed5f58
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/globalfunc/src/global.qdoc
@@ -0,0 +1,25 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*!
+ \module TestGlobals
+
+ A collection of \l foo() functions.
+*/
+
+/*!
+ \class Globals
+ \inmodule TestGlobals
+*/
+
+/*!
+ \fn int foo(int a)
+ \relates Globals
+ Params: \a a
+*/
+
+/*!
+ \fn int foo(int a, bool b)
+ \relates Globals
+ Params: \a b, \a b
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/docbook/headers.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/docbook/headers.xml
new file mode 100644
index 000000000..052108b2a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/docbook/headers.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Headers</db:title>
+<db:productname>HeaderFile</db:productname>
+<db:titleabbrev>HeaderFile Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>HeaderFile Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:anchor xml:id="details"/>
+<db:variablelist role="members">
+<db:varlistentry>
+<db:term><db:link xlink:href="testheader.xml" xlink:role="">&lt;TestHeader&gt;</db:link></db:term>
+<db:listitem>
+<db:para>A header file.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/docbook/testheader.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/docbook/testheader.xml
new file mode 100644
index 000000000..03a49cb67
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/docbook/testheader.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>&lt;TestHeader&gt; - Test Header</db:title>
+<db:productname>HeaderFile</db:productname>
+<db:titleabbrev>HeaderFile Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>A header file.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>TestHeader</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Group</db:term>
+<db:listitem>
+<db:para>&lt;TestHeader&gt; is part of <db:simplelist><db:member><db:link xlink:href="headers.xml">Headers</db:link></db:member><db:member><db:link xlink:href="tests.xml">Tests</db:link></db:member></db:simplelist>
+</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="type-documentation">
+<db:title>Type Documentation</db:title>
+<db:section xml:id="Globals-enum">
+<db:title>enum Globals</db:title>
+<db:informaltable>
+<db:thead>
+<db:tr>
+<db:th>Constant</db:th>
+</db:tr>
+</db:thead>
+<db:tr>
+<db:td>
+<db:para><db:code>Glo</db:code></db:para>
+</db:td>
+<db:td><db:code>0</db:code></db:td>
+</db:tr>
+<db:tr>
+<db:td>
+<db:para><db:code>Bal</db:code></db:para>
+</db:td>
+<db:td><db:code>1</db:code></db:td>
+</db:tr>
+</db:informaltable>
+</db:section>
+</db:section>
+<db:section xml:id="variable-documentation">
+<db:title>Variable Documentation</db:title>
+<db:section xml:id="globalVar-var">
+<db:title>const int globalVar</db:title>
+<db:para>Global variable.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="function-documentation">
+<db:title>Function Documentation</db:title>
+<db:section xml:id="globalFunc">
+<db:title>void globalFunc()</db:title>
+<db:para>Global function.</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/docbook/tests.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/docbook/tests.xml
new file mode 100644
index 000000000..25f95affd
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/docbook/tests.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Tests</db:title>
+<db:productname>HeaderFile</db:productname>
+<db:titleabbrev>HeaderFile Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>HeaderFile Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:anchor xml:id="details"/>
+<db:variablelist role="members">
+<db:varlistentry>
+<db:term><db:link xlink:href="testheader.xml" xlink:role="">&lt;TestHeader&gt;</db:link></db:term>
+<db:listitem>
+<db:para>A header file.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/headerfile.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/headerfile.index
new file mode 100644
index 000000000..cf72522f9
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/headerfile.index
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="HeaderFile Reference Documentation" version="" project="HeaderFile">
+ <namespace name="" status="active" access="public" module="headerfile">
+ <function name="globalFunc" href="testheader.html#globalFunc" status="active" access="public" location="testheader.h" documented="true" related="0" meta="plain" type="void" brief="Global function" signature="void globalFunc()"/>
+ <header name="&lt;TestHeader&gt;" href="testheader.html" status="active" documented="true" groups="headers,tests" module="TestCPP" brief="A header file" title="Test Header" fulltitle="&lt;TestHeader&gt; - Test Header" subtitle="">
+ <function name="globalFunc" href="testheader.html#globalFunc" status="active" access="public" location="testheader.h" documented="true" related="0" meta="plain" type="void" brief="Global function" signature="void globalFunc()"/>
+ <enum name="Globals" href="testheader.html#Globals-enum" status="active" access="public" location="testheader.h" related="1" documented="true">
+ <value name="Glo" value="0"/>
+ <value name="Bal" value="1"/>
+ </enum>
+ <variable name="globalVar" href="testheader.html#globalVar-var" status="active" access="public" location="testheader.h" related="2" documented="true" type="const int" static="false" brief="Global variable"/>
+ </header>
+ <enum name="Globals" href="testheader.html#Globals-enum" status="active" access="public" location="testheader.h" related="1" documented="true">
+ <value name="Glo" value="0"/>
+ <value name="Bal" value="1"/>
+ </enum>
+ <variable name="globalVar" href="testheader.html#globalVar-var" status="active" access="public" location="testheader.h" related="2" documented="true" type="const int" static="false" brief="Global variable"/>
+ <group name="headers" href="headers.html" status="active" documented="true" seen="true" title="Headers"/>
+ <group name="tests" href="tests.html" status="active" documented="true" seen="true" title="Tests"/>
+ <module name="TestCPP" href="testcpp-module.html" status="internal" seen="false" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/headers.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/headers.html
new file mode 100644
index 000000000..1a60ec30b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/headers.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testheader.cpp -->
+ <title>Headers | HeaderFile</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Headers</h1>
+<!-- $$$headers-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@headers -->
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testheader.html">&lt;TestHeader&gt;</a></p></td><td class="tblDescr"><p>A header file</p></td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/testheader.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/testheader.html
new file mode 100644
index 000000000..a728ddd12
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/testheader.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testheader.cpp -->
+ <meta name="description" content="A header file.">
+ <title>&lt;TestHeader&gt; - Test Header | HeaderFile</title>
+</head>
+<body>
+<li><a href="headers.html">Headers</a></li>
+<li>&lt;TestHeader&gt; - Test Header</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">&lt;TestHeader&gt; - Test Header</h1>
+<!-- $$$<TestHeader>-brief -->
+<p>A header file. <a href="#details">More...</a></p>
+<!-- @@@<TestHeader> -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;TestHeader&gt;</span></td></tr>
+</table></div>
+<ul>
+<li><TestHeader> is part of <a href="headers.html">Headers</a> and <a href="tests.html">Tests</a>.</li>
+</ul>
+<h2 id="types">Types</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> enum </td><td class="memItemRight bottomAlign"><b><a href="testheader.html#Globals-enum" translate="no">Globals</a></b> { Glo, Bal }</td></tr>
+</table></div>
+<h2 id="variables">Variables</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> const int </td><td class="memItemRight bottomAlign"><b><a href="testheader.html#globalVar-var" translate="no">globalVar</a></b></td></tr>
+</table></div>
+<h2 id="functions">Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testheader.html#globalFunc" translate="no">globalFunc</a></b>()</td></tr>
+</table></div>
+<!-- $$$<TestHeader>-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@<TestHeader> -->
+<div class="types">
+<h2>Type Documentation</h2>
+<!-- $$$Globals$$$Glo$$$Bal -->
+<h3 class="fn" translate="no" id="Globals-enum">enum <span class="name">Globals</span></h3>
+<div class="table"><table class="valuelist"><tr><th class="tblConst">Constant</th><th class="tblVal">Value</th></tr>
+<tr><td class="topAlign"><code translate="no">Glo</code></td><td class="topAlign tblval"><code translate="no">0</code></td></tr>
+<tr><td class="topAlign"><code translate="no">Bal</code></td><td class="topAlign tblval"><code translate="no">1</code></td></tr>
+</table></div>
+<!-- @@@Globals -->
+</div>
+<div class="vars">
+<h2>Variable Documentation</h2>
+<!-- $$$globalVar -->
+<h3 class="fn" translate="no" id="globalVar-var">const <span class="type">int</span> <span class="name">globalVar</span></h3>
+<p>Global variable.</p>
+<!-- @@@globalVar -->
+</div>
+<div class="func">
+<h2>Function Documentation</h2>
+<!-- $$$globalFunc[overload1]$$$globalFunc -->
+<h3 class="fn" translate="no" id="globalFunc"><span class="type">void</span> <span class="name">globalFunc</span>()</h3>
+<p>Global function.</p>
+<!-- @@@globalFunc -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/tests.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/tests.html
new file mode 100644
index 000000000..a4221f375
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/html/tests.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testheader.cpp -->
+ <title>Tests | HeaderFile</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Tests</h1>
+<!-- $$$tests-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@tests -->
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testheader.html">&lt;TestHeader&gt;</a></p></td><td class="tblDescr"><p>A header file</p></td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/headerfile.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/headerfile.index
new file mode 100644
index 000000000..cf72522f9
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/headerfile.index
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="HeaderFile Reference Documentation" version="" project="HeaderFile">
+ <namespace name="" status="active" access="public" module="headerfile">
+ <function name="globalFunc" href="testheader.html#globalFunc" status="active" access="public" location="testheader.h" documented="true" related="0" meta="plain" type="void" brief="Global function" signature="void globalFunc()"/>
+ <header name="&lt;TestHeader&gt;" href="testheader.html" status="active" documented="true" groups="headers,tests" module="TestCPP" brief="A header file" title="Test Header" fulltitle="&lt;TestHeader&gt; - Test Header" subtitle="">
+ <function name="globalFunc" href="testheader.html#globalFunc" status="active" access="public" location="testheader.h" documented="true" related="0" meta="plain" type="void" brief="Global function" signature="void globalFunc()"/>
+ <enum name="Globals" href="testheader.html#Globals-enum" status="active" access="public" location="testheader.h" related="1" documented="true">
+ <value name="Glo" value="0"/>
+ <value name="Bal" value="1"/>
+ </enum>
+ <variable name="globalVar" href="testheader.html#globalVar-var" status="active" access="public" location="testheader.h" related="2" documented="true" type="const int" static="false" brief="Global variable"/>
+ </header>
+ <enum name="Globals" href="testheader.html#Globals-enum" status="active" access="public" location="testheader.h" related="1" documented="true">
+ <value name="Glo" value="0"/>
+ <value name="Bal" value="1"/>
+ </enum>
+ <variable name="globalVar" href="testheader.html#globalVar-var" status="active" access="public" location="testheader.h" related="2" documented="true" type="const int" static="false" brief="Global variable"/>
+ <group name="headers" href="headers.html" status="active" documented="true" seen="true" title="Headers"/>
+ <group name="tests" href="tests.html" status="active" documented="true" seen="true" title="Tests"/>
+ <module name="TestCPP" href="testcpp-module.html" status="internal" seen="false" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/headers.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/headers.webxml
new file mode 100644
index 000000000..6fdc7de0b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/headers.webxml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <group name="headers" href="headers.html" status="active" documented="true" seen="true" title="Headers">
+ <description>
+ <table width="100%">
+ <row>
+ <item>
+ <para>
+ <link raw="&lt;TestHeader&gt;" href="testheader.html" type=""/>
+ </para>
+ </item>
+ <item>
+ <para>A header file.</para>
+ </item>
+ </row>
+ </table>
+ </description>
+ </group>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/testheader.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/testheader.webxml
new file mode 100644
index 000000000..fcc37e5ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/testheader.webxml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <header name="&lt;TestHeader&gt;" href="testheader.html" status="active" documented="true" groups="headers,tests" module="TestCPP" brief="A header file" title="Test Header" fulltitle="&lt;TestHeader&gt; - Test Header" subtitle="">
+ <description>
+ <brief>A header file.</brief>
+ </description>
+ <function name="globalFunc" href="testheader.html#globalFunc" status="active" access="public" location="testheader.h" documented="true" related="0" meta="plain" type="void" brief="Global function" signature="void globalFunc()">
+ <description>
+ <brief>Global function.</brief>
+ </description>
+ </function>
+ <enum name="Globals" href="testheader.html#Globals-enum" status="active" access="public" location="testheader.h" related="1" documented="true">
+ <value name="Glo" value="0"/>
+ <value name="Bal" value="1"/>
+ <description>
+ <list type="enum">
+ <definition>
+ <term>Glo</term>Glo</definition>
+ <item/>
+ <definition>
+ <term>Bal</term>Bal</definition>
+ <item/>
+ </list>
+ </description>
+ </enum>
+ <variable name="globalVar" href="testheader.html#globalVar-var" status="active" access="public" location="testheader.h" related="2" documented="true" type="const int" static="false" brief="Global variable">
+ <description>
+ <brief>This variable holds Global variable..</brief>
+ </description>
+ </variable>
+ </header>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/tests.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/tests.webxml
new file mode 100644
index 000000000..7c3deb373
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/expected/webxml/tests.webxml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <group name="tests" href="tests.html" status="active" documented="true" seen="true" title="Tests">
+ <description>
+ <table width="100%">
+ <row>
+ <item>
+ <para>
+ <link raw="&lt;TestHeader&gt;" href="testheader.html" type=""/>
+ </para>
+ </item>
+ <item>
+ <para>A header file.</para>
+ </item>
+ </row>
+ </table>
+ </description>
+ </group>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/headerfile.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/headerfile.qdocconf
new file mode 100644
index 000000000..b449319cc
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/headerfile.qdocconf
@@ -0,0 +1,25 @@
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ./src/images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+project = HeaderFile
+moduleheader = .src/testheader.h
+
+{includepaths,sourcedirs,headerdirs} += ./src
+
+outputformats = HTML WebXML DocBook
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
+DocBook.nosubdirs = true
+DocBook.outputsubdir = docbook
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/src/testheader.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/src/testheader.cpp
new file mode 100644
index 000000000..43c512f36
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/src/testheader.cpp
@@ -0,0 +1,43 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "testheader.h"
+
+const int globalVar = 0;
+
+/*!
+ \headerfile <TestHeader>
+ \title Test Header
+ \inmodule TestCPP
+ \brief A header file.
+ \ingroup headers
+ \ingroup tests
+*/
+
+/*!
+ \group headers
+ \title Headers
+*/
+
+/*! \group tests
+ \title Tests
+*/
+
+/*!
+ \fn void globalFunc()
+ \brief Global function.
+ \relates <TestHeader>
+*/
+
+/*!
+ \variable globalVar
+ \brief Global variable.
+ \relates <TestHeader>
+*/
+
+/*!
+ \enum Globals
+ \relates <TestHeader>
+ \value Glo
+ \value Bal
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/src/testheader.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/src/testheader.h
new file mode 100644
index 000000000..5ea361685
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/headerfile/src/testheader.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+void globalFunc() {};
+enum Globals { Glo, Bal };
+extern const int globalVar;
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/another-page-with-comments-in-the-brief.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/another-page-with-comments-in-the-brief.html
new file mode 100644
index 000000000..71ef2af25
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/another-page-with-comments-in-the-brief.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- brief_adventures.qdoc -->
+ <meta name="description" content="Another test that the \brief command isn't completely borked">
+ <title>There's no end to the possibilities! | IllformattedDocumentation</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">There's no end to the possibilities!</h1>
+<!-- $$$another-page-with-comments-in-the-brief-description -->
+<div class="descr" id="details">
+<p>The brief for this page is: &quot;Another test that the \brief command isn't completely borked&quot;. Notice the lack of a trailing full stop in the brief. QDoc should generate a warning about that when generating HTML.</p>
+</div>
+<!-- @@@another-page-with-comments-in-the-brief -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/brief-adventures.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/brief-adventures.html
new file mode 100644
index 000000000..1600b124c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/brief-adventures.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- brief_adventures.qdoc -->
+ <meta name="description" content="Test that the \brief command doesn't eat content that follows it.">
+ <title>Adventures with QDoc's \brief command | IllformattedDocumentation</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#further-details">Further details</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Adventures with QDoc's \brief command</h1>
+<!-- $$$brief-adventures.html-description -->
+<div class="descr" id="details">
+<p>The purpose of this test data is to provide a regression mechanism as part of QDoc's end-to-end test, tst_generatedOutput, for an issue (QTBUG-70959) that was reported against QDoc's \keyword command. The issue, as experienced by the reporter of the bug, is that if the \keyword command isn't followed by a new command, or is the last command in a paragraph, the following paragraph is &quot;eaten&quot;. That means the entire paragraph is understood by QDoc as keywords and included as html meta information instead of as part of the rendered output.</p>
+<h2 id="further-details">Further details</h2>
+<p>The bug report is at <a href="https://bugreports.qt.io/browse/QTBUG-70959">https://bugreports.qt.io/browse/QTBUG-70959</a>. It refers to a change that bypassed the issue by moving the \keyword command, at <a href="https://codereview.qt-project.org/c/qt/qtdoc/+/242033/">https://codereview.qt-project.org/c/qt/qtdoc/+/242033/</a>.</p>
+</div>
+<!-- @@@brief-adventures.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/illformatted-examples.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/illformatted-examples.html
new file mode 100644
index 000000000..5253ae1d1
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/illformatted-examples.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- illformatted-examples.qdoc -->
+ <meta name="description" content="Demonstrate correctness for example generation.">
+ <title>Test generated output for illformatted examples | IllformattedDocumentation</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#qml-examples">QML Examples</a></li>
+<li class="level1"><a href="#c-examples">C++ Examples</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Test generated output for illformatted examples</h1>
+<!-- $$$illformatted-examples.html-description -->
+<div class="descr" id="details">
+<p>This test includes the following examples:</p>
+<h2 id="qml-examples">QML Examples</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="illformatteddocumentation-someexample-example.html">QDoc: some example</a></p></td><td class="tblDescr"><p></p></td></tr>
+</table></div>
+<h2 id="c-examples">C++ Examples</h2>
+</div>
+<!-- @@@illformatted-examples.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/illformatteddocumentation-someexample-example.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/illformatteddocumentation-someexample-example.html
new file mode 100644
index 000000000..ebeb5a5b4
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/illformatteddocumentation-someexample-example.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- some_example.qdoc -->
+ <title>QDoc: some example | IllformattedDocumentation</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">QDoc: some example</h1>
+<!-- $$$someexample-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@someexample -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/illformatteddocumentation.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/illformatteddocumentation.index
new file mode 100644
index 000000000..e9898cd2e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/illformatteddocumentation.index
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="IllformattedDocumentation Reference Documentation" version="" project="IllformattedDocumentation">
+ <namespace name="" status="active" access="public" module="illformatteddocumentation">
+ <page name="brief-adventures.html" href="brief-adventures.html" status="active" location="brief_adventures.qdoc" documented="true" subtype="page" title="Adventures with QDoc's \brief command" fulltitle="Adventures with QDoc's \brief command" subtitle="" brief="Test that the \brief command doesn't eat content that follows it">
+ <keyword name="some-keyword" title="some_keyword"/>
+ <contents name="further-details" title="Further details" level="1"/>
+ </page>
+ <page name="someexample" href="illformatteddocumentation-someexample-example.html" status="active" location="some_example.qdoc" documented="true" groups="illformatted-examples-qml" subtype="example" title="QDoc: some example" fulltitle="QDoc: some example" subtitle=""/>
+ <page name="illformatted-examples.html" href="illformatted-examples.html" status="active" location="illformatted-examples.qdoc" documented="true" groups="all-examples" subtype="page" title="Test generated output for illformatted examples" fulltitle="Test generated output for illformatted examples" subtitle="" brief="Demonstrate correctness for example generation">
+ <contents name="qml-examples" title="QML Examples" level="1"/>
+ <contents name="c-examples" title="C++ Examples" level="1"/>
+ </page>
+ <page name="another-page-with-comments-in-the-brief" href="another-page-with-comments-in-the-brief.html" status="active" location="brief_adventures.qdoc" documented="true" subtype="page" title="There's no end to the possibilities!" fulltitle="There's no end to the possibilities!" subtitle="" brief="Another test that the \brief command isn't completely borked"/>
+ <page name="page-with-an-image-at-the-top.html" href="page-with-an-image-at-the-top.html" status="active" location="brief_adventures.qdoc" documented="true" subtype="page" title="This page starts with an image" fulltitle="This page starts with an image" subtitle="" brief="This page has an image right at the top"/>
+ <page name="page-with-comment-after-brief.html" href="page-with-comment-after-brief.html" status="active" location="brief_adventures.qdoc" documented="true" subtype="page" title="Yet another adventure with QDoc's \brief command" fulltitle="Yet another adventure with QDoc's \brief command" subtitle="" brief="Another test that the \brief command doesn't eat content that follows it"/>
+ <page name="page-with-comment-in-brief.html" href="page-with-comment-in-brief.html" status="active" location="brief_adventures.qdoc" documented="true" subtype="page" title="Yet, yet another adventure with QDoc's \brief command" fulltitle="Yet, yet another adventure with QDoc's \brief command" subtitle="" brief="Another test that the \brief command isn't entirely broken"/>
+ <group name="all-examples" href="all-examples.html" status="internal" seen="false" title=""/>
+ <group name="illformatted-examples-qml" href="illformatted-examples-qml.html" status="internal" seen="false" title=""/>
+ <module name="sometestgroup" href="sometestgroup-module.html" status="internal" seen="false" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/page-with-an-image-at-the-top.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/page-with-an-image-at-the-top.html
new file mode 100644
index 000000000..e66e6ba51
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/page-with-an-image-at-the-top.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- brief_adventures.qdoc -->
+ <meta name="description" content="This page has an image right at the top.">
+ <title>This page starts with an image | IllformattedDocumentation</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">This page starts with an image</h1>
+<!-- $$$page-with-an-image-at-the-top.html-description -->
+<div class="descr" id="details">
+<p class="centerAlign"><font color="red">[Missing image leonardo-da-vinci.png]</font></p><p>The image should render as expected, and the alternate text should be there, too. This text is contained in its own paragraph following the image.</p>
+<p>This paragraph is a new paragraph, and doesn't contain an image.</p>
+</div>
+<!-- @@@page-with-an-image-at-the-top.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/page-with-comment-after-brief.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/page-with-comment-after-brief.html
new file mode 100644
index 000000000..fc6fa1b92
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/page-with-comment-after-brief.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- brief_adventures.qdoc -->
+ <meta name="description" content="Another test that the \brief command doesn't eat content that follows it.">
+ <title>Yet another adventure with QDoc's \brief command | IllformattedDocumentation</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Yet another adventure with QDoc's \brief command</h1>
+<!-- $$$page-with-comment-after-brief.html-description -->
+<div class="descr" id="details">
+<p>This paragraph should render normally, but might be eaten by the \brief brief because the brief is followed by a QDoc comment (which shouldn't be rendered at all).</p>
+</div>
+<!-- @@@page-with-comment-after-brief.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/page-with-comment-in-brief.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/page-with-comment-in-brief.html
new file mode 100644
index 000000000..4283ae544
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/html/page-with-comment-in-brief.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- brief_adventures.qdoc -->
+ <meta name="description" content="Another test that the \brief command isn't entirely broken.">
+ <title>Yet, yet another adventure with QDoc's \brief command | IllformattedDocumentation</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Yet, yet another adventure with QDoc's \brief command</h1>
+<!-- $$$page-with-comment-in-brief.html-description -->
+<div class="descr" id="details">
+<p>The brief for this page is: &quot;Another test that the \brief command isn't entirely broken.&quot;</p>
+</div>
+<!-- @@@page-with-comment-in-brief.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/another-page-with-comments-in-the-brief.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/another-page-with-comments-in-the-brief.webxml
new file mode 100644
index 000000000..7e82dfad6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/another-page-with-comments-in-the-brief.webxml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="another-page-with-comments-in-the-brief" href="another-page-with-comments-in-the-brief.html" status="active" location="brief_adventures.qdoc" documented="true" subtype="page" title="There's no end to the possibilities!" fulltitle="There's no end to the possibilities!" subtitle="" brief="Another test that the \brief command isn't completely borked">
+ <description>
+ <brief>Another test that the \brief command isn't completely borked</brief>
+ <para>The brief for this page is: &quot;Another test that the \brief command isn't completely borked&quot;. Notice the lack of a trailing full stop in the brief. QDoc should generate a warning about that when generating HTML.</para>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/brief-adventures.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/brief-adventures.webxml
new file mode 100644
index 000000000..3a6f5f2d5
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/brief-adventures.webxml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="brief-adventures.html" href="brief-adventures.html" status="active" location="brief_adventures.qdoc" documented="true" subtype="page" title="Adventures with QDoc's \brief command" fulltitle="Adventures with QDoc's \brief command" subtitle="" brief="Test that the \brief command doesn't eat content that follows it">
+ <keyword name="some-keyword" title="some_keyword"/>
+ <contents name="further-details" title="Further details" level="1"/>
+ <description>
+ <brief>Test that the \brief command doesn't eat content that follows it.</brief>
+ <para>The purpose of this test data is to provide a regression mechanism as part of QDoc's end-to-end test, tst_generatedOutput, for an issue (QTBUG-70959) that was reported against QDoc's \keyword command. The issue, as experienced by the reporter of the bug, is that if the \keyword command isn't followed by a new command, or is the last command in a paragraph, the following paragraph is &quot;eaten&quot;. That means the entire paragraph is understood by QDoc as keywords and included as html meta information instead of as part of the rendered output.</para>
+ <section id="further-details">
+ <heading level="1">Further details</heading>
+ <para>The bug report is at <link raw="https://bugreports.qt.io/browse/QTBUG-70959" href="https://bugreports.qt.io/browse/QTBUG-70959" type="external">https://bugreports.qt.io/browse/QTBUG-70959</link>. It refers to a change that bypassed the issue by moving the \keyword command, at <link raw="https://codereview.qt-project.org/c/qt/qtdoc/+/242033/" href="https://codereview.qt-project.org/c/qt/qtdoc/+/242033/" type="external">https://codereview.qt-project.org/c/qt/qtdoc/+/242033/</link>.</para>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/illformatted-examples.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/illformatted-examples.webxml
new file mode 100644
index 000000000..b49d1b297
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/illformatted-examples.webxml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="illformatted-examples.html" href="illformatted-examples.html" status="active" location="illformatted-examples.qdoc" documented="true" groups="all-examples" subtype="page" title="Test generated output for illformatted examples" fulltitle="Test generated output for illformatted examples" subtitle="" brief="Demonstrate correctness for example generation">
+ <contents name="qml-examples" title="QML Examples" level="1"/>
+ <contents name="c-examples" title="C++ Examples" level="1"/>
+ <description>
+ <brief>Demonstrate correctness for example generation.</brief>
+ <para>This test includes the following examples:</para>
+ <section id="qml-examples">
+ <heading level="1">QML Examples</heading>
+ <table width="100%">
+ <row>
+ <item>
+ <para>
+ <link raw="QDoc: some example" href="illformatteddocumentation-someexample-example.html" type="page" page="QDoc: some example"/>
+ </para>
+ </item>
+ <item>
+ <para></para>
+ </item>
+ </row>
+ </table>
+ </section>
+ <section id="c-examples">
+ <heading level="1">C++ Examples</heading>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/illformatteddocumentation-someexample-example.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/illformatteddocumentation-someexample-example.webxml
new file mode 100644
index 000000000..675e3d74f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/illformatteddocumentation-someexample-example.webxml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="someexample" href="illformatteddocumentation-someexample-example.html" status="active" location="some_example.qdoc" documented="true" groups="illformatted-examples-qml" subtype="example" title="QDoc: some example" fulltitle="QDoc: some example" subtitle="">
+ <description/>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/illformatteddocumentation.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/illformatteddocumentation.index
new file mode 100644
index 000000000..e9898cd2e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/illformatteddocumentation.index
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="IllformattedDocumentation Reference Documentation" version="" project="IllformattedDocumentation">
+ <namespace name="" status="active" access="public" module="illformatteddocumentation">
+ <page name="brief-adventures.html" href="brief-adventures.html" status="active" location="brief_adventures.qdoc" documented="true" subtype="page" title="Adventures with QDoc's \brief command" fulltitle="Adventures with QDoc's \brief command" subtitle="" brief="Test that the \brief command doesn't eat content that follows it">
+ <keyword name="some-keyword" title="some_keyword"/>
+ <contents name="further-details" title="Further details" level="1"/>
+ </page>
+ <page name="someexample" href="illformatteddocumentation-someexample-example.html" status="active" location="some_example.qdoc" documented="true" groups="illformatted-examples-qml" subtype="example" title="QDoc: some example" fulltitle="QDoc: some example" subtitle=""/>
+ <page name="illformatted-examples.html" href="illformatted-examples.html" status="active" location="illformatted-examples.qdoc" documented="true" groups="all-examples" subtype="page" title="Test generated output for illformatted examples" fulltitle="Test generated output for illformatted examples" subtitle="" brief="Demonstrate correctness for example generation">
+ <contents name="qml-examples" title="QML Examples" level="1"/>
+ <contents name="c-examples" title="C++ Examples" level="1"/>
+ </page>
+ <page name="another-page-with-comments-in-the-brief" href="another-page-with-comments-in-the-brief.html" status="active" location="brief_adventures.qdoc" documented="true" subtype="page" title="There's no end to the possibilities!" fulltitle="There's no end to the possibilities!" subtitle="" brief="Another test that the \brief command isn't completely borked"/>
+ <page name="page-with-an-image-at-the-top.html" href="page-with-an-image-at-the-top.html" status="active" location="brief_adventures.qdoc" documented="true" subtype="page" title="This page starts with an image" fulltitle="This page starts with an image" subtitle="" brief="This page has an image right at the top"/>
+ <page name="page-with-comment-after-brief.html" href="page-with-comment-after-brief.html" status="active" location="brief_adventures.qdoc" documented="true" subtype="page" title="Yet another adventure with QDoc's \brief command" fulltitle="Yet another adventure with QDoc's \brief command" subtitle="" brief="Another test that the \brief command doesn't eat content that follows it"/>
+ <page name="page-with-comment-in-brief.html" href="page-with-comment-in-brief.html" status="active" location="brief_adventures.qdoc" documented="true" subtype="page" title="Yet, yet another adventure with QDoc's \brief command" fulltitle="Yet, yet another adventure with QDoc's \brief command" subtitle="" brief="Another test that the \brief command isn't entirely broken"/>
+ <group name="all-examples" href="all-examples.html" status="internal" seen="false" title=""/>
+ <group name="illformatted-examples-qml" href="illformatted-examples-qml.html" status="internal" seen="false" title=""/>
+ <module name="sometestgroup" href="sometestgroup-module.html" status="internal" seen="false" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/page-with-an-image-at-the-top.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/page-with-an-image-at-the-top.webxml
new file mode 100644
index 000000000..da7b4f1d9
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/page-with-an-image-at-the-top.webxml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="page-with-an-image-at-the-top.html" href="page-with-an-image-at-the-top.html" status="active" location="brief_adventures.qdoc" documented="true" subtype="page" title="This page starts with an image" fulltitle="This page starts with an image" subtitle="" brief="This page has an image right at the top">
+ <description>
+ <brief>This page has an image right at the top.</brief>
+ <para>The image should render as expected, and the alternate text should be there, too. This text is contained in its own paragraph following the image.</para>
+ <para>This paragraph is a new paragraph, and doesn't contain an image.</para>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/page-with-comment-after-brief.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/page-with-comment-after-brief.webxml
new file mode 100644
index 000000000..f5b28e302
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/page-with-comment-after-brief.webxml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="page-with-comment-after-brief.html" href="page-with-comment-after-brief.html" status="active" location="brief_adventures.qdoc" documented="true" subtype="page" title="Yet another adventure with QDoc's \brief command" fulltitle="Yet another adventure with QDoc's \brief command" subtitle="" brief="Another test that the \brief command doesn't eat content that follows it">
+ <description>
+ <brief>Another test that the \brief command doesn't eat content that follows it.</brief>
+ <para>This paragraph should render normally, but might be eaten by the \brief brief because the brief is followed by a QDoc comment (which shouldn't be rendered at all).</para>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/page-with-comment-in-brief.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/page-with-comment-in-brief.webxml
new file mode 100644
index 000000000..575cd111d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/expected/webxml/page-with-comment-in-brief.webxml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="page-with-comment-in-brief.html" href="page-with-comment-in-brief.html" status="active" location="brief_adventures.qdoc" documented="true" subtype="page" title="Yet, yet another adventure with QDoc's \brief command" fulltitle="Yet, yet another adventure with QDoc's \brief command" subtitle="" brief="Another test that the \brief command isn't entirely broken">
+ <description>
+ <brief>Another test that the \brief command isn't entirely broken.</brief>
+ <para>The brief for this page is: &quot;Another test that the \brief command isn't entirely broken.&quot;</para>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/illformatted_documentation.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/illformatted_documentation.qdocconf
new file mode 100644
index 000000000..716882f41
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/illformatted_documentation.qdocconf
@@ -0,0 +1,29 @@
+project = IllformattedDocumentation
+
+headerdirs = ./src
+sourcedirs = ./src
+exampledirs = ./src
+
+outputformats = WebXML HTML
+WebXML.quotinginformation = true
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
+
+warninglimit = 3 # The broken example generates three warnings
+
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ./src/images
+
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+# By default, use -outputdir directly, no subdir.
+# outputsubdir '.' matches the root of expected_output/
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/src/brief_adventures.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/src/brief_adventures.qdoc
new file mode 100644
index 000000000..9bad0106b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/src/brief_adventures.qdoc
@@ -0,0 +1,75 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page brief-adventures.html
+ \title Adventures with QDoc's \\brief command
+ \brief Test that the \\brief command doesn't eat content that follows it.
+ \inmodule sometestgroup
+ \keyword some_keyword
+
+ The purpose of this test data is to provide a regression mechanism as part
+ of QDoc's end-to-end test, tst_generatedOutput, for an issue (QTBUG-70959)
+ that was reported against QDoc's \\keyword command. The issue, as
+ experienced by the reporter of the bug, is that if the \\keyword command
+ isn't followed by a new command, or is the last command in a paragraph,
+ the following paragraph is "eaten". That means the entire paragraph is
+ understood by QDoc as keywords and included as html meta information
+ instead of as part of the rendered output.
+
+ \section1 Further details
+ The bug report is at \l https://bugreports.qt.io/browse/QTBUG-70959.
+ It refers to a change that bypassed the issue by moving the \\keyword
+ command, at \l https://codereview.qt-project.org/c/qt/qtdoc/+/242033/.
+*/
+
+/*!
+ \page page-with-an-image-at-the-top.html
+ \title This page starts with an image
+ \brief This page has an image right at the top.
+ \image leonardo-da-vinci.png This is the alternate text for the image
+ The image should render as expected, and the alternate text should be
+ there, too. This text is contained in its own paragraph following the
+ image.
+
+ This paragraph is a new paragraph, and doesn't contain an image.
+*/
+
+/*!
+ \page page-with-comment-after-brief.html
+ \title Yet another adventure with QDoc's \\brief command
+ \brief Another test that the \\brief command doesn't eat content that follows it.
+ //! This QDoc comment might cause an issue, let's try it!
+
+ This paragraph should render normally, but might be eaten by the \\brief
+ brief because the brief is followed by a QDoc comment (which shouldn't be
+ rendered at all).
+*/
+
+/*!
+ \page page-with-comment-in-brief.html
+ \title Yet, yet another adventure with QDoc's \\brief command
+ \brief Another test that the \\brief command //! Brief continues below
+ isn't entirely broken.
+
+ The brief for this page is:
+ "Another test that the \\brief command isn't entirely broken."
+*/
+
+/*!
+ \page another-page-with-comments-in-the-brief
+ \title There's no end to the possibilities!
+ \brief Another test that the \\brief command //! Doesn't contain a full stop.
+ //! QDoc's CMD_BRIEF should issue a warning when it doesn't end with a full
+ //! stop. However, if a \\brief is followed by a line comment (i.e. `//!`),
+ //! the warning isn't issued. This is tied to how `CMD_BRIEF` performs
+ //! macro expansion and command processing; in the case of line comments in
+ //! the argument, the last character of the brief's atom isn't a full stop.
+ isn't completely borked
+
+ The brief for this page is:
+ "Another test that the \\brief command isn't completely borked". Notice the
+ lack of a trailing full stop in the brief. QDoc should generate a warning
+ about that when generating HTML.
+
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/src/illformatted-examples.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/src/illformatted-examples.qdoc
new file mode 100644
index 000000000..5bfa9e7f6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/src/illformatted-examples.qdoc
@@ -0,0 +1,17 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page illformatted-examples.html
+ \ingroup all-examples
+ \title Test generated output for illformatted examples
+ \brief Demonstrate correctness for example generation.
+
+ This test includes the following examples:
+
+ \section1 QML Examples
+ \annotatedlist illformatted-examples-qml
+
+ \section1 C++ Examples
+ \annotatedlist illformatted-examples-cpp
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/src/some_example.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/src/some_example.qdoc
new file mode 100644
index 000000000..a64605b87
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/illformatted_documentation/src/some_example.qdoc
@@ -0,0 +1,8 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example someexample
+ \title QDoc: some example
+ \ingroup illformatted-examples-qml
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/a-page-with-a-line-comment-in-the-see-also-command.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/a-page-with-a-line-comment-in-the-see-also-command.xml
new file mode 100644
index 000000000..45be05ac6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/a-page-with-a-line-comment-in-the-see-also-command.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>The source for this page has a line comment in its sa command</db:title>
+<db:productname>LineComment</db:productname>
+<db:titleabbrev>LineComment Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>LineComment Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:para>QDoc's \sa command doesn't respect QDoc's line comments.</db:para>
+<db:section>
+<db:title>See Also</db:title>
+<db:para><db:emphasis>See also </db:emphasis>
+<db:simplelist type="vert" role="see-also">
+<db:member><db:link xlink:href="another-page-with-an-image-at-the-top.xml">This page starts with an image</db:link></db:member>
+<db:member><db:link xlink:href="line-comment-adventures.xml">Adventures with QDoc's line comments</db:link></db:member>
+</db:simplelist>
+</db:para>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/another-page-with-an-image-at-the-top.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/another-page-with-an-image-at-the-top.xml
new file mode 100644
index 000000000..c905f9c45
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/another-page-with-an-image-at-the-top.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>This page starts with an image</db:title>
+<db:productname>LineComment</db:productname>
+<db:titleabbrev>LineComment Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>This page has an image right at the top.</db:para>
+</db:abstract>
+</db:info>
+<db:mediaobject>
+<db:alt>This is the alternate text for the image</db:alt>
+<db:imageobject>
+<db:imagedata fileref="images/leonardo-da-vinci.png"/>
+</db:imageobject>
+</db:mediaobject>
+<db:para>The image should render as expected, and the alternate text should be there, too. This text is contained in its own paragraph following the image.</db:para>
+<db:para>This paragraph is a new paragraph, and doesn't contain an image.</db:para>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/images/leonardo-da-vinci.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/images/leonardo-da-vinci.png
new file mode 100644
index 000000000..854acb4ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/images/leonardo-da-vinci.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/line-comment-adventures.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/line-comment-adventures.xml
new file mode 100644
index 000000000..e78104e3d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/docbook/line-comment-adventures.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Adventures with QDoc's line comments</db:title>
+<db:productname>LineComment</db:productname>
+<db:titleabbrev>LineComment Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>Test that line comments don't break documentation.</db:para>
+</db:abstract>
+</db:info>
+<db:para>The purpose of this test data is to provide a regression mechanism as part of QDoc's end-to-end test, tst_generatedOutput, for an issue (QTBUG-105754) that was reported against QDoc's \sa command. The issue, as experienced by the reporter of the bug, is that if the \sa command is followed by a line comment, QDoc generates a series of &quot;Missing comma in \sa&quot; warnings.</db:para>
+<db:section xml:id="further-details">
+<db:title>Further details</db:title>
+<db:para>The bug report is at <db:link xlink:href="https://bugreports.qt.io/browse/QTBUG-105754">https://bugreports.qt.io/browse/QTBUG-105754</db:link>.</db:para>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/a-page-with-a-line-comment-in-the-see-also-command.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/a-page-with-a-line-comment-in-the-see-also-command.html
new file mode 100644
index 000000000..a7b95776d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/a-page-with-a-line-comment-in-the-see-also-command.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- line_comment_adventures.qdoc -->
+ <title>The source for this page has a line comment in its sa command | LineComment</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">The source for this page has a line comment in its sa command</h1>
+<!-- $$$a-page-with-a-line-comment-in-the-see-also-command.html-description -->
+<div class="descr" id="details">
+<p>QDoc's \sa command doesn't respect QDoc's line comments.</p>
+</div>
+<p><b>See also </b><a href="another-page-with-an-image-at-the-top.html">This page starts with an image</a> and <a href="line-comment-adventures.html">Adventures with QDoc's line comments</a>.</p>
+<!-- @@@a-page-with-a-line-comment-in-the-see-also-command.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/another-page-with-an-image-at-the-top.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/another-page-with-an-image-at-the-top.html
new file mode 100644
index 000000000..27c53b8ac
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/another-page-with-an-image-at-the-top.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- line_comment_adventures.qdoc -->
+ <meta name="description" content="This page has an image right at the top.">
+ <title>This page starts with an image | LineComment</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">This page starts with an image</h1>
+<!-- $$$another-page-with-an-image-at-the-top.html-description -->
+<div class="descr" id="details">
+<p class="centerAlign"><img src="images/leonardo-da-vinci.png" alt="This is the alternate text for the image" /></p><p>The image should render as expected, and the alternate text should be there, too. This text is contained in its own paragraph following the image.</p>
+<p>This paragraph is a new paragraph, and doesn't contain an image.</p>
+</div>
+<!-- @@@another-page-with-an-image-at-the-top.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/images/leonardo-da-vinci.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/images/leonardo-da-vinci.png
new file mode 100644
index 000000000..854acb4ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/images/leonardo-da-vinci.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/line-comment-adventures.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/line-comment-adventures.html
new file mode 100644
index 000000000..89ab9e527
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/line-comment-adventures.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- line_comment_adventures.qdoc -->
+ <meta name="description" content="Test that line comments don't break documentation.">
+ <title>Adventures with QDoc's line comments | LineComment</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#further-details">Further details</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Adventures with QDoc's line comments</h1>
+<!-- $$$line-comment-adventures.html-description -->
+<div class="descr" id="details">
+<p>The purpose of this test data is to provide a regression mechanism as part of QDoc's end-to-end test, tst_generatedOutput, for an issue (QTBUG-105754) that was reported against QDoc's \sa command. The issue, as experienced by the reporter of the bug, is that if the \sa command is followed by a line comment, QDoc generates a series of &quot;Missing comma in \sa&quot; warnings.</p>
+<h2 id="further-details">Further details</h2>
+<p>The bug report is at <a href="https://bugreports.qt.io/browse/QTBUG-105754">https://bugreports.qt.io/browse/QTBUG-105754</a>.</p>
+</div>
+<!-- @@@line-comment-adventures.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/linecomment.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/linecomment.index
new file mode 100644
index 000000000..f81bef147
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/html/linecomment.index
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="LineComment Reference Documentation" version="" project="LineComment">
+ <namespace name="" status="active" access="public" module="linecomment">
+ <page name="line-comment-adventures.html" href="line-comment-adventures.html" status="active" location="line_comment_adventures.qdoc" documented="true" subtype="page" title="Adventures with QDoc's line comments" fulltitle="Adventures with QDoc's line comments" subtitle="" brief="Test that line comments don't break documentation">
+ <contents name="further-details" title="Further details" level="1"/>
+ </page>
+ <page name="a-page-with-a-line-comment-in-the-see-also-command.html" href="a-page-with-a-line-comment-in-the-see-also-command.html" status="active" location="line_comment_adventures.qdoc" documented="true" subtype="page" title="The source for this page has a line comment in its sa command" fulltitle="The source for this page has a line comment in its sa command" subtitle=""/>
+ <page name="another-page-with-an-image-at-the-top.html" href="another-page-with-an-image-at-the-top.html" status="active" location="line_comment_adventures.qdoc" documented="true" subtype="page" title="This page starts with an image" fulltitle="This page starts with an image" subtitle="" brief="This page has an image right at the top"/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/a-page-with-a-line-comment-in-the-see-also-command.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/a-page-with-a-line-comment-in-the-see-also-command.webxml
new file mode 100644
index 000000000..cb0caedf9
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/a-page-with-a-line-comment-in-the-see-also-command.webxml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="a-page-with-a-line-comment-in-the-see-also-command.html" href="a-page-with-a-line-comment-in-the-see-also-command.html" status="active" location="line_comment_adventures.qdoc" documented="true" subtype="page" title="The source for this page has a line comment in its sa command" fulltitle="The source for this page has a line comment in its sa command" subtitle="">
+ <description>
+ <para>QDoc's \sa command doesn't respect QDoc's line comments.</para>
+ <see-also>
+ <link raw="This page starts with an image" href="another-page-with-an-image-at-the-top.html" type="page" page="This page starts with an image">This page starts with an image</link>
+ <link raw="Adventures with QDoc's line comments" href="line-comment-adventures.html" type="page" page="Adventures with QDoc's line comments">Adventures with QDoc's line comments</link>
+ </see-also>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/another-page-with-an-image-at-the-top.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/another-page-with-an-image-at-the-top.webxml
new file mode 100644
index 000000000..b2551df18
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/another-page-with-an-image-at-the-top.webxml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="another-page-with-an-image-at-the-top.html" href="another-page-with-an-image-at-the-top.html" status="active" location="line_comment_adventures.qdoc" documented="true" subtype="page" title="This page starts with an image" fulltitle="This page starts with an image" subtitle="" brief="This page has an image right at the top">
+ <description>
+ <brief>This page has an image right at the top.</brief>
+ <image href="images/leonardo-da-vinci.png"/>
+ <para>The image should render as expected, and the alternate text should be there, too. This text is contained in its own paragraph following the image.</para>
+ <para>This paragraph is a new paragraph, and doesn't contain an image.</para>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/images/leonardo-da-vinci.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/images/leonardo-da-vinci.png
new file mode 100644
index 000000000..854acb4ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/images/leonardo-da-vinci.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/line-comment-adventures.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/line-comment-adventures.webxml
new file mode 100644
index 000000000..b84706e96
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/line-comment-adventures.webxml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="line-comment-adventures.html" href="line-comment-adventures.html" status="active" location="line_comment_adventures.qdoc" documented="true" subtype="page" title="Adventures with QDoc's line comments" fulltitle="Adventures with QDoc's line comments" subtitle="" brief="Test that line comments don't break documentation">
+ <contents name="further-details" title="Further details" level="1"/>
+ <description>
+ <brief>Test that line comments don't break documentation.</brief>
+ <para>The purpose of this test data is to provide a regression mechanism as part of QDoc's end-to-end test, tst_generatedOutput, for an issue (QTBUG-105754) that was reported against QDoc's \sa command. The issue, as experienced by the reporter of the bug, is that if the \sa command is followed by a line comment, QDoc generates a series of &quot;Missing comma in \sa&quot; warnings.</para>
+ <section id="further-details">
+ <heading level="1">Further details</heading>
+ <para>The bug report is at <link raw="https://bugreports.qt.io/browse/QTBUG-105754" href="https://bugreports.qt.io/browse/QTBUG-105754" type="external">https://bugreports.qt.io/browse/QTBUG-105754</link>.</para>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/linecomment.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/linecomment.index
new file mode 100644
index 000000000..f81bef147
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/expected/webxml/linecomment.index
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="LineComment Reference Documentation" version="" project="LineComment">
+ <namespace name="" status="active" access="public" module="linecomment">
+ <page name="line-comment-adventures.html" href="line-comment-adventures.html" status="active" location="line_comment_adventures.qdoc" documented="true" subtype="page" title="Adventures with QDoc's line comments" fulltitle="Adventures with QDoc's line comments" subtitle="" brief="Test that line comments don't break documentation">
+ <contents name="further-details" title="Further details" level="1"/>
+ </page>
+ <page name="a-page-with-a-line-comment-in-the-see-also-command.html" href="a-page-with-a-line-comment-in-the-see-also-command.html" status="active" location="line_comment_adventures.qdoc" documented="true" subtype="page" title="The source for this page has a line comment in its sa command" fulltitle="The source for this page has a line comment in its sa command" subtitle=""/>
+ <page name="another-page-with-an-image-at-the-top.html" href="another-page-with-an-image-at-the-top.html" status="active" location="line_comment_adventures.qdoc" documented="true" subtype="page" title="This page starts with an image" fulltitle="This page starts with an image" subtitle="" brief="This page has an image right at the top"/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/line_comments.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/line_comments.qdocconf
new file mode 100644
index 000000000..db5b91b55
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/line_comments.qdocconf
@@ -0,0 +1,27 @@
+project = LineComment
+
+{headerdirs,sourcedirs,exampledirs} = ./src
+
+outputformats = WebXML HTML DocBook
+WebXML.quotinginformation = true
+
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
+
+DocBook.nosubdirs = true
+DocBook.outputsubdir = docbook
+
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+imagedirs = ./src/images
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/src/images/leonardo-da-vinci.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/src/images/leonardo-da-vinci.png
new file mode 100644
index 000000000..854acb4ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/src/images/leonardo-da-vinci.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/src/line_comment_adventures.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/src/line_comment_adventures.qdoc
new file mode 100644
index 000000000..28dfe5c90
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/line_comments/src/line_comment_adventures.qdoc
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page line-comment-adventures.html
+ \title Adventures with QDoc's line comments
+ \brief Test that line comments don't break documentation.
+
+ The purpose of this test data is to provide a regression mechanism as part
+ of QDoc's end-to-end test, tst_generatedOutput, for an issue (QTBUG-105754)
+ that was reported against QDoc's \\sa command. The issue, as experienced by
+ the reporter of the bug, is that if the \\sa command is followed by a line
+ comment, QDoc generates a series of "Missing comma in \\sa" warnings.
+
+ \section1 Further details
+ The bug report is at \l https://bugreports.qt.io/browse/QTBUG-105754.
+*/
+
+/*!
+ \page another-page-with-an-image-at-the-top.html
+ \title This page starts with an image
+ \brief This page has an image right at the top.
+ \image leonardo-da-vinci.png This is the alternate text for the image
+ The image should render as expected, and the alternate text should be
+ there, too. This text is contained in its own paragraph following the
+ image.
+
+ This paragraph is a new paragraph, and doesn't contain an image.
+*/
+
+/*!
+ \page a-page-with-a-line-comment-in-the-see-also-command.html
+ \title The source for this page has a line comment in its sa command
+
+ QDoc's \\sa command doesn't respect QDoc's line comments.
+
+ \sa {This page starts with an image}, //! let's go to bar after this
+ {Adventures with QDoc's line comments}
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/expected/a-minimal-qdoc-configuration.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/expected/a-minimal-qdoc-configuration.index
new file mode 100644
index 000000000..037f4bc73
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/expected/a-minimal-qdoc-configuration.index
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="A minimal QDoc configuration Reference Documentation" version="" project="A minimal QDoc configuration">
+ <namespace name="" status="active" access="public" module="a minimal qdoc configuration">
+ <page name="readme.html" href="readme.html" status="active" location="README.qdoc" documented="true" subtype="page" title="README" fulltitle="README" subtitle=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/expected/readme.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/expected/readme.html
new file mode 100644
index 000000000..59c54c1b5
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/expected/readme.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- README.qdoc -->
+ <title>README | A minimal QDoc configuration</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">README</h1>
+<!-- $$$readme.html-description -->
+<div class="descr" id="details">
+<p>This test is for a minimal .qdocconf file.</p>
+</div>
+<!-- @@@readme.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/minimal_configuration.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/minimal_configuration.qdocconf
new file mode 100644
index 000000000..6fc9c025e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/minimal_configuration.qdocconf
@@ -0,0 +1,12 @@
+project = A minimal QDoc configuration
+
+locationinfo = false
+
+headers.fileextensions = "*.h *.hpp"
+sources.fileextensions = "*.cpp *.qml *.qdoc"
+
+headerdirs = ./src
+sourcedirs = ./src
+
+warninglimit = 0
+warninglimit.enabled = true
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/src/README.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/src/README.qdoc
new file mode 100644
index 000000000..879370377
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/minimal_configuration/src/README.qdoc
@@ -0,0 +1,9 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page readme.html
+ \title README
+
+ This test is for a minimal .qdocconf file.
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/cppmodule-module-suffix.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/cppmodule-module-suffix.xml
new file mode 100644
index 000000000..3f02a1a2c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/cppmodule-module-suffix.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title></db:title>
+<db:productname>ModifiedOutputFilenames</db:productname>
+<db:titleabbrev>ModifiedOutputFilenames Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>ModifiedOutputFilenames Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:section xml:id="namespaces">
+<db:title>Namespaces</db:title>
+<db:itemizedlist role="namespaces">
+<db:listitem>
+<db:para><db:link xlink:href="prefix-namespace-suffix.xml" xlink:role="namespace">Namespace</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+<db:section xml:id="classes">
+<db:title>Classes</db:title>
+<db:itemizedlist role="classes">
+<db:listitem>
+<db:para><db:link xlink:href="prefix-namespace-class-suffix.xml" xlink:role="class">Namespace::Class</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/group.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/group.xml
new file mode 100644
index 000000000..f3f0b072d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/group.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title></db:title>
+<db:productname>ModifiedOutputFilenames</db:productname>
+<db:titleabbrev>ModifiedOutputFilenames Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>ModifiedOutputFilenames Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:anchor xml:id="details"/>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/modifiedoutputfilenames-test-example.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/modifiedoutputfilenames-test-example.xml
new file mode 100644
index 000000000..0cd81f137
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/modifiedoutputfilenames-test-example.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title></db:title>
+<db:productname>ModifiedOutputFilenames</db:productname>
+<db:titleabbrev>ModifiedOutputFilenames Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>ModifiedOutputFilenames Reference Documentation.</db:para></db:abstract>
+</db:info>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/page.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/page.xml
new file mode 100644
index 000000000..0cd81f137
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/page.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title></db:title>
+<db:productname>ModifiedOutputFilenames</db:productname>
+<db:titleabbrev>ModifiedOutputFilenames Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>ModifiedOutputFilenames Reference Documentation.</db:para></db:abstract>
+</db:info>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/prefix-header-suffix.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/prefix-header-suffix.xml
new file mode 100644
index 000000000..f58083323
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/prefix-header-suffix.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>&lt;header&gt;</db:title>
+<db:productname>ModifiedOutputFilenames</db:productname>
+<db:titleabbrev>ModifiedOutputFilenames Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>ModifiedOutputFilenames Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>header</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/prefix-namespace-class-suffix.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/prefix-namespace-class-suffix.xml
new file mode 100644
index 000000000..74bddeb56
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/prefix-namespace-class-suffix.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Class Class</db:title>
+<db:subtitle>Namespace::Class</db:subtitle>
+<db:productname>ModifiedOutputFilenames</db:productname>
+<db:titleabbrev>ModifiedOutputFilenames Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>ModifiedOutputFilenames Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Class</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/prefix-namespace-suffix.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/prefix-namespace-suffix.xml
new file mode 100644
index 000000000..7b4cc8313
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/prefix-namespace-suffix.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Namespace Namespace</db:title>
+<db:productname>ModifiedOutputFilenames</db:productname>
+<db:titleabbrev>ModifiedOutputFilenames Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>ModifiedOutputFilenames Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Namespace</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="classes">
+<db:title>Classes</db:title>
+<db:section>
+<db:title>class <db:link xlink:href="prefix-namespace-class-suffix.xml" xlink:role="class">Class</db:link></db:title>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/qml-qmlmodule-suffix-type.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/qml-qmlmodule-suffix-type.xml
new file mode 100644
index 000000000..3f6ae500d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/qml-qmlmodule-suffix-type.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Type QML Type</db:title>
+<db:productname>ModifiedOutputFilenames</db:productname>
+<db:titleabbrev>ModifiedOutputFilenames Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>ModifiedOutputFilenames Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import QmlModule</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/qmlmodule-qmlmodule-suffix.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/qmlmodule-qmlmodule-suffix.xml
new file mode 100644
index 000000000..ce4324812
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/docbook/qmlmodule-qmlmodule-suffix.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title></db:title>
+<db:productname>ModifiedOutputFilenames</db:productname>
+<db:titleabbrev>ModifiedOutputFilenames Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>ModifiedOutputFilenames Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:anchor xml:id="details"/>
+<db:itemizedlist role="members">
+<db:listitem>
+<db:para><db:link xlink:href="qml-qmlmodule-suffix-type.xml" xlink:role="">Type</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/cppmodule-module-suffix.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/cppmodule-module-suffix.html
new file mode 100644
index 000000000..4ee779cd8
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/cppmodule-module-suffix.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- test.cpp -->
+ <title>ModifiedOutputFilenames</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#namespaces">Namespaces</a></li>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h2 id="namespaces">Namespaces</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="prefix-namespace-suffix.html">Namespace</a></p></td></tr>
+</table></div>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="prefix-namespace-class-suffix.html">Namespace::Class</a></p></td></tr>
+</table></div>
+<!-- $$$CppModule-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@CppModule -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/group.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/group.html
new file mode 100644
index 000000000..ba2903f2d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/group.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- test.cpp -->
+ <title>ModifiedOutputFilenames</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<!-- $$$group-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@group -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/modifiedoutputfilenames-test-example.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/modifiedoutputfilenames-test-example.html
new file mode 100644
index 000000000..cfea1c3bd
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/modifiedoutputfilenames-test-example.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- test.cpp -->
+ <title>ModifiedOutputFilenames</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<!-- $$$test-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@test -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/modifiedoutputfilenames.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/modifiedoutputfilenames.index
new file mode 100644
index 000000000..0f67183b2
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/modifiedoutputfilenames.index
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="ModifiedOutputFilenames Reference Documentation" version="" project="ModifiedOutputFilenames">
+ <namespace name="" href="prefix-suffix.html" status="active" access="public" module="modifiedoutputfilenames">
+ <header name="&lt;header&gt;" href="prefix-header-suffix.html" status="active" documented="true" module="CppModule" title="&lt;header&gt;" fulltitle="&lt;header&gt;" subtitle=""/>
+ <namespace name="Namespace" href="prefix-namespace-suffix.html" status="active" access="public" location="test.h" documented="true" module="CppModule">
+ <class name="Class" fullname="Namespace::Class" href="prefix-namespace-class-suffix.html" status="active" access="public" location="test.h" documented="true" module="CppModule"/>
+ </namespace>
+ <qmlclass name="Type" qml-module-name="QmlModule" fullname="QmlModule.Type" href="qml-qmlmodule-suffix-type.html" status="active" access="public" documented="true" title="Type" fulltitle="Type" subtitle=""/>
+ <page name="page.html" href="page.html" status="active" documented="true" subtype="page" title="" fulltitle="" subtitle=""/>
+ <page name="test" href="modifiedoutputfilenames-test-example.html" status="active" documented="true" subtype="example" title="" fulltitle="" subtitle=""/>
+ <group name="group" href="group.html" status="active" documented="true" seen="true" title=""/>
+ <module name="CppModule" href="cppmodule-module-suffix.html" status="active" documented="true" seen="true" title=""/>
+ <qmlmodule name="QmlModule" qml-module-name="QmlModule" href="qmlmodule-qmlmodule-suffix.html" status="active" documented="true" seen="true" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/page.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/page.html
new file mode 100644
index 000000000..a5067395b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/page.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- test.cpp -->
+ <title>ModifiedOutputFilenames</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<!-- $$$page.html-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@page.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/prefix-header-suffix.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/prefix-header-suffix.html
new file mode 100644
index 000000000..75ce2b54b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/prefix-header-suffix.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- test.cpp -->
+ <title>&lt;header&gt; | ModifiedOutputFilenames</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">&lt;header&gt;</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;header&gt;</span></td></tr>
+</table></div>
+<!-- $$$<header>-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@<header> -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/prefix-namespace-class-suffix.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/prefix-namespace-class-suffix.html
new file mode 100644
index 000000000..757a4aed4
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/prefix-namespace-class-suffix.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- test.cpp -->
+ <title>Class Class | ModifiedOutputFilenames</title>
+</head>
+<body>
+<li>Class</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Class Class</h1>
+<span class="small-subtitle" translate="no">class <a href="prefix-namespace-suffix.html" translate="no">Namespace</a>::Class</span>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Class&gt;</span></td></tr>
+</table></div>
+<!-- $$$Class-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Class -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/prefix-namespace-suffix.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/prefix-namespace-suffix.html
new file mode 100644
index 000000000..e3f4342d7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/prefix-namespace-suffix.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- test.cpp -->
+ <title>Namespace Namespace | ModifiedOutputFilenames</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Namespace Namespace</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Namespace&gt;</span></td></tr>
+</table></div>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> class </td><td class="memItemRight bottomAlign"><b><a href="prefix-namespace-class-suffix.html" translate="no">Class</a></b></td></tr>
+</table></div>
+<!-- $$$Namespace-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Namespace -->
+<div class="classes">
+<h2>Classes</h2>
+<h3> class <a href="prefix-namespace-class-suffix.html">Class</a></h3></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/qml-qmlmodule-suffix-type-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/qml-qmlmodule-suffix-type-members.html
new file mode 100644
index 000000000..c3ba341de
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/qml-qmlmodule-suffix-type-members.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- test.cpp -->
+ <title>List of All Members for Type | ModifiedOutputFilenames</title>
+</head>
+<body>
+<li>Type</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for Type</h1>
+<p>This is the complete list of members for <a href="qml-qmlmodule-suffix-type.html">Type</a>, including inherited members.</p>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/qml-qmlmodule-suffix-type.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/qml-qmlmodule-suffix-type.html
new file mode 100644
index 000000000..f2324bbee
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/qml-qmlmodule-suffix-type.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- test.cpp -->
+ <title>Type QML Type | ModifiedOutputFilenames</title>
+</head>
+<body>
+<li>Type</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Type QML Type</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import QmlModule</td></tr></table></div><ul>
+<li><a href="qml-qmlmodule-suffix-type-members.html">List of all members, including inherited members</a></li>
+</ul>
+<!-- $$$Type-description -->
+<h2 id="details">Detailed Description</h2>
+<!-- @@@Type -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/qmlmodule-qmlmodule-suffix.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/qmlmodule-qmlmodule-suffix.html
new file mode 100644
index 000000000..2fe3ae3d6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/html/qmlmodule-qmlmodule-suffix.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- test.cpp -->
+ <title>ModifiedOutputFilenames</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<!-- $$$QmlModule-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@QmlModule -->
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qml-qmlmodule-suffix-type.html">Type</a></p></td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/cppmodule-module-suffix.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/cppmodule-module-suffix.webxml
new file mode 100644
index 000000000..5d24b3077
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/cppmodule-module-suffix.webxml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document/>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/group.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/group.webxml
new file mode 100644
index 000000000..8b8147e6a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/group.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <group name="group" href="group.html" status="active" documented="true" seen="true" title="">
+ <description>
+ <table width="100%"/>
+ </description>
+ </group>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/modifiedoutputfilenames-test-example.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/modifiedoutputfilenames-test-example.webxml
new file mode 100644
index 000000000..2f92a9490
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/modifiedoutputfilenames-test-example.webxml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="test" href="modifiedoutputfilenames-test-example.html" status="active" documented="true" subtype="example" title="" fulltitle="" subtitle="">
+ <description/>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/modifiedoutputfilenames.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/modifiedoutputfilenames.index
new file mode 100644
index 000000000..0f67183b2
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/modifiedoutputfilenames.index
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="ModifiedOutputFilenames Reference Documentation" version="" project="ModifiedOutputFilenames">
+ <namespace name="" href="prefix-suffix.html" status="active" access="public" module="modifiedoutputfilenames">
+ <header name="&lt;header&gt;" href="prefix-header-suffix.html" status="active" documented="true" module="CppModule" title="&lt;header&gt;" fulltitle="&lt;header&gt;" subtitle=""/>
+ <namespace name="Namespace" href="prefix-namespace-suffix.html" status="active" access="public" location="test.h" documented="true" module="CppModule">
+ <class name="Class" fullname="Namespace::Class" href="prefix-namespace-class-suffix.html" status="active" access="public" location="test.h" documented="true" module="CppModule"/>
+ </namespace>
+ <qmlclass name="Type" qml-module-name="QmlModule" fullname="QmlModule.Type" href="qml-qmlmodule-suffix-type.html" status="active" access="public" documented="true" title="Type" fulltitle="Type" subtitle=""/>
+ <page name="page.html" href="page.html" status="active" documented="true" subtype="page" title="" fulltitle="" subtitle=""/>
+ <page name="test" href="modifiedoutputfilenames-test-example.html" status="active" documented="true" subtype="example" title="" fulltitle="" subtitle=""/>
+ <group name="group" href="group.html" status="active" documented="true" seen="true" title=""/>
+ <module name="CppModule" href="cppmodule-module-suffix.html" status="active" documented="true" seen="true" title=""/>
+ <qmlmodule name="QmlModule" qml-module-name="QmlModule" href="qmlmodule-qmlmodule-suffix.html" status="active" documented="true" seen="true" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/page.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/page.webxml
new file mode 100644
index 000000000..77523428b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/page.webxml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="page.html" href="page.html" status="active" documented="true" subtype="page" title="" fulltitle="" subtitle="">
+ <description/>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/prefix-header-suffix.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/prefix-header-suffix.webxml
new file mode 100644
index 000000000..bc42d90dd
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/prefix-header-suffix.webxml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <header name="&lt;header&gt;" href="prefix-header-suffix.html" status="active" documented="true" module="CppModule" title="&lt;header&gt;" fulltitle="&lt;header&gt;" subtitle="">
+ <description/>
+ </header>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/prefix-namespace-class-suffix.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/prefix-namespace-class-suffix.webxml
new file mode 100644
index 000000000..4ee5dee79
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/prefix-namespace-class-suffix.webxml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="Class" fullname="Namespace::Class" href="prefix-namespace-class-suffix.html" status="active" access="public" location="test.h" documented="true" module="CppModule">
+ <description/>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/prefix-namespace-suffix.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/prefix-namespace-suffix.webxml
new file mode 100644
index 000000000..e46a0cfb3
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/prefix-namespace-suffix.webxml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <namespace name="Namespace" href="prefix-namespace-suffix.html" status="active" access="public" location="test.h" documented="true" module="CppModule">
+ <description/>
+ <class name="Class" fullname="Namespace::Class" href="prefix-namespace-class-suffix.html" status="active" access="public" location="test.h" documented="true" module="CppModule">
+ <description/>
+ </class>
+ </namespace>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/qmlmodule-qmlmodule-suffix.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/qmlmodule-qmlmodule-suffix.webxml
new file mode 100644
index 000000000..5d24b3077
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/expected/webxml/qmlmodule-qmlmodule-suffix.webxml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document/>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/modifiedoutputfilenames.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/modifiedoutputfilenames.qdocconf
new file mode 100644
index 000000000..5cf3ce867
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/modifiedoutputfilenames.qdocconf
@@ -0,0 +1,22 @@
+project = ModifiedOutputFilenames
+
+{sourcedirs,headerdirs} = ./src
+exampledirs = ./src/example
+
+outputprefixes = CPP
+outputprefixes.CPP = prefix_
+
+outputsuffixes = QML CPP
+{outputsuffixes.CPP,outputsuffixes.QML} = _suffix
+
+locationinfo = false
+warninglimit = 0
+warninglimit.enabled = true
+
+outputformats = WebXML HTML DocBook
+{WebXML.nosubdirs,HTML.nosubdirs,DocBook.nosubdirs} = true
+WebXML.quotinginformation = true
+
+WebXML.outputsubdir = webxml
+HTML.outputsubdir = html
+DocBook.outputsubdir = docbook
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/src/example/test/CMakeLists.txt b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/src/example/test/CMakeLists.txt
new file mode 100644
index 000000000..b2a4ba591
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/src/example/test/CMakeLists.txt
@@ -0,0 +1 @@
+# nothing here
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/src/test.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/src/test.cpp
new file mode 100644
index 000000000..81f5b725f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/src/test.cpp
@@ -0,0 +1,24 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*! \module CppModule */
+
+/*! \qmlmodule QmlModule */
+
+/*! \headerfile <header>
+ \inmodule CppModule */
+
+/*! \namespace Namespace
+ \inmodule CppModule */
+
+/*! \class Namespace::Class
+ \inmodule CppModule */
+
+/*! \qmltype Type
+ \inqmlmodule QmlModule */
+
+/*! \page page.html */
+
+/*! \group group */
+
+/*! \example test */
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/src/test.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/src/test.h
new file mode 100644
index 000000000..3d52569d2
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modifiedoutputfilenames/src/test.h
@@ -0,0 +1,12 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace Namespace {
+
+class Class {};
+
+} // namespace
+
+void func() {}
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/docbook/boringclass.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/docbook/boringclass.xml
new file mode 100644
index 000000000..df3505cdf
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/docbook/boringclass.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>BoringClass Class</db:title>
+<db:productname>ModuleState</db:productname>
+<db:titleabbrev>ModuleState Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>ModuleState Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>BoringClass</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Status</db:term>
+<db:listitem>
+<db:para>Technical Preview</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:para>There's not much to say about <db:link xlink:href="boringclass.xml">BoringClass</db:link>, really, apart from that it is quite boring.</db:para>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/docbook/excitingclass.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/docbook/excitingclass.xml
new file mode 100644
index 000000000..29fcfa679
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/docbook/excitingclass.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>ExcitingClass Class</db:title>
+<db:productname>ModuleState</db:productname>
+<db:titleabbrev>ModuleState Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>ModuleState Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>ExcitingClass</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Status</db:term>
+<db:listitem>
+<db:para>Technical Preview</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:para>There's a lot to say about <db:link xlink:href="excitingclass.xml">ExcitingClass</db:link>. Let's dive into the details!</db:para>
+<db:section xml:id="what-makes-excitingclass-more-exciting-than-boringclass">
+<db:title>What makes ExcitingClass more exciting than BoringClass?</db:title>
+<db:para>Well, for one, it has a \section1 heading in it's documentation! How exciting is that?!</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/docbook/moduleinstate-module.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/docbook/moduleinstate-module.xml
new file mode 100644
index 000000000..776969a64
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/docbook/moduleinstate-module.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title></db:title>
+<db:productname>ModuleState</db:productname>
+<db:titleabbrev>ModuleState Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>This module is in <db:emphasis>Technical Preview</db:emphasis> state.</db:para>
+</db:abstract>
+</db:info>
+<db:para>This module is in <db:emphasis>Technical Preview</db:emphasis> state.</db:para>
+<db:section xml:id="classes">
+<db:title>Classes</db:title>
+<db:itemizedlist role="classes">
+<db:listitem>
+<db:para><db:link xlink:href="boringclass.xml" xlink:role="class">BoringClass</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="excitingclass.xml" xlink:role="class">ExcitingClass</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/boringclass.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/boringclass.html
new file mode 100644
index 000000000..ecee60195
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/boringclass.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- module_in_a_state.qdoc -->
+ <title>BoringClass Class | ModuleState</title>
+</head>
+<body>
+<li><a href="moduleinstate-module.html" translate="no">ModuleInState (Technical Preview)</a></li>
+<li>BoringClass</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">BoringClass Class</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;BoringClass&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Status:</td><td class="memItemRight bottomAlign"> Technical Preview<span class="status technical-preview"></span></td></tr>
+</table></div>
+<!-- $$$BoringClass-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<p>There's not much to say about BoringClass, really, apart from that it is quite boring.</p>
+</div>
+<!-- @@@BoringClass -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/excitingclass.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/excitingclass.html
new file mode 100644
index 000000000..7d3d13943
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/excitingclass.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- module_in_a_state.qdoc -->
+ <title>ExcitingClass Class | ModuleState</title>
+</head>
+<body>
+<li><a href="moduleinstate-module.html" translate="no">ModuleInState (Technical Preview)</a></li>
+<li>ExcitingClass</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#what-makes-excitingclass-more-exciting-than-boringclass">What makes ExcitingClass more exciting than BoringClass?</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">ExcitingClass Class</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;ExcitingClass&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Status:</td><td class="memItemRight bottomAlign"> Technical Preview<span class="status technical-preview"></span></td></tr>
+</table></div>
+<!-- $$$ExcitingClass-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<p>There's a lot to say about ExcitingClass. Let's dive into the details!</p>
+<h3 id="what-makes-excitingclass-more-exciting-than-boringclass">What makes ExcitingClass more exciting than BoringClass?</h3>
+<p>Well, for one, it has a \section1 heading in it's documentation! How exciting is that?!</p>
+</div>
+<!-- @@@ExcitingClass -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/moduleinstate-module.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/moduleinstate-module.html
new file mode 100644
index 000000000..065e85812
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/moduleinstate-module.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- module_in_a_state.qdoc -->
+ <title>ModuleState</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#namespaces">Namespaces</a></li>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<p>This module is in <i>Technical Preview</i> state.</p>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="boringclass.html">BoringClass</a></p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="excitingclass.html">ExcitingClass</a></p></td></tr>
+</table></div>
+<!-- $$$ModuleInState-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@ModuleInState -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/modulestate.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/modulestate.index
new file mode 100644
index 000000000..3ab02a363
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/html/modulestate.index
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="ModuleState Reference Documentation" version="" project="ModuleState">
+ <namespace name="" status="active" access="public" module="modulestate">
+ <class name="BoringClass" href="boringclass.html" status="active" access="public" location="classes_in_stateful_module.h" documented="true" module="ModuleInState"/>
+ <class name="ExcitingClass" href="excitingclass.html" status="active" access="public" location="classes_in_stateful_module.h" documented="true" module="ModuleInState">
+ <contents name="what-makes-excitingclass-more-exciting-than-boringclass" title="What makes ExcitingClass more exciting than BoringClass?" level="1"/>
+ </class>
+ <module name="ModuleInState" href="moduleinstate-module.html" status="active" location="module_in_a_state.qdoc" documented="true" seen="true" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/boringclass.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/boringclass.webxml
new file mode 100644
index 000000000..2e1ea77d8
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/boringclass.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="BoringClass" href="boringclass.html" status="active" access="public" location="classes_in_stateful_module.h" documented="true" module="ModuleInState">
+ <description>
+ <para>There's not much to say about <link raw="BoringClass" href="boringclass.html" type="class">BoringClass</link>, really, apart from that it is quite boring.</para>
+ </description>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/excitingclass.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/excitingclass.webxml
new file mode 100644
index 000000000..5e4c8ff8b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/excitingclass.webxml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="ExcitingClass" href="excitingclass.html" status="active" access="public" location="classes_in_stateful_module.h" documented="true" module="ModuleInState">
+ <contents name="what-makes-excitingclass-more-exciting-than-boringclass" title="What makes ExcitingClass more exciting than BoringClass?" level="1"/>
+ <description>
+ <para>There's a lot to say about <link raw="ExcitingClass" href="excitingclass.html" type="class">ExcitingClass</link>. Let's dive into the details!</para>
+ <section id="what-makes-excitingclass-more-exciting-than-boringclass">
+ <heading level="1">What makes ExcitingClass more exciting than BoringClass?</heading>
+ <para>Well, for one, it has a \section1 heading in it's documentation! How exciting is that?!</para>
+ </section>
+ </description>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/moduleinstate-module.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/moduleinstate-module.webxml
new file mode 100644
index 000000000..5d24b3077
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/moduleinstate-module.webxml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document/>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/modulestate.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/modulestate.index
new file mode 100644
index 000000000..3ab02a363
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/expected/webxml/modulestate.index
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="ModuleState Reference Documentation" version="" project="ModuleState">
+ <namespace name="" status="active" access="public" module="modulestate">
+ <class name="BoringClass" href="boringclass.html" status="active" access="public" location="classes_in_stateful_module.h" documented="true" module="ModuleInState"/>
+ <class name="ExcitingClass" href="excitingclass.html" status="active" access="public" location="classes_in_stateful_module.h" documented="true" module="ModuleInState">
+ <contents name="what-makes-excitingclass-more-exciting-than-boringclass" title="What makes ExcitingClass more exciting than BoringClass?" level="1"/>
+ </class>
+ <module name="ModuleInState" href="moduleinstate-module.html" status="active" location="module_in_a_state.qdoc" documented="true" seen="true" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/modulestate.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/modulestate.qdocconf
new file mode 100644
index 000000000..1f75c8036
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/modulestate.qdocconf
@@ -0,0 +1,29 @@
+project = ModuleState
+
+headerdirs = ./src
+sourcedirs = ./src
+exampledirs = ./src
+
+outputformats = WebXML HTML DocBook
+WebXML.quotinginformation = true
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
+
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+
+DocBook.nosubdirs = true
+DocBook.outputsubdir = docbook
+
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ./src/images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/src/classes_in_stateful_module.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/src/classes_in_stateful_module.h
new file mode 100644
index 000000000..21578e91b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/src/classes_in_stateful_module.h
@@ -0,0 +1,15 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef CLASSES_IN_STATEFUL_MODULE_H
+#define CLASSES_IN_STATEFUL_MODULE_H
+
+class BoringClass
+{
+};
+
+class ExcitingClass
+{
+};
+
+#endif // CLASSES_IN_STATEFUL_MODULE_H
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/src/module_in_a_state.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/src/module_in_a_state.qdoc
new file mode 100644
index 000000000..3bdb12503
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/modulestate/src/module_in_a_state.qdoc
@@ -0,0 +1,26 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \module ModuleInState
+ \modulestate Technical Preview
+*/
+
+/*!
+ \class BoringClass
+ \inmodule ModuleInState
+
+ There's not much to say about BoringClass, really, apart from that it is
+ quite boring.
+*/
+
+/*!
+ \class ExcitingClass
+ \inmodule ModuleInState
+
+ There's a lot to say about ExcitingClass. Let's dive into the details!
+
+ \section1 What makes ExcitingClass more exciting than BoringClass?
+ Well, for one, it has a \\section1 heading in it's documentation! How
+ exciting is that?!
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/8b5c72eb.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/8b5c72eb.html
new file mode 100644
index 000000000..f52b65618
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/8b5c72eb.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- adventures_with_non_ascii_characters.qdoc -->
+ <title>NonAsciiCharacterInput</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<!-- $$$圣马苏里拉.html-description -->
+<div class="descr" id="details">
+<p>This page exists solely to understand how QDoc will generate the file name for a page with non-latin characters in its name.</p>
+</div>
+<!-- @@@圣马苏里拉.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/adventures-with-non-ascii-characters.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/adventures-with-non-ascii-characters.html
new file mode 100644
index 000000000..9552c867f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/adventures-with-non-ascii-characters.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- adventures_with_non_ascii_characters.qdoc -->
+ <meta name="description" content="Test that non-ascii characters work as input to QDoc commands.">
+ <title>Adventures with non-ascii characters in QDoc | NonAsciiCharacterInput</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#a-713da3e8">A 大纲视图</a></li>
+<li class="level1"><a href="#3d-42faee45">3D场景视图</a></li>
+<li class="level1"><a href="#3d-c57e864e">这就是3D场景视图</a></li>
+<li class="level1"><a href="#662952c1">属性视图</a></li>
+<li class="level1"><a href="#further-details">Further details</a></li>
+<li class="level2"><a href="#ascii-characters-that-are-non-printable-ascii-such-as-or-521d09f0">Ascii characters that are non-printable ascii, such as ß, ü, or ø</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Adventures with non-ascii characters in QDoc</h1>
+<!-- $$$adventures_with_non_ascii_characters.html-description -->
+<div class="descr" id="details">
+<p>The purpose of this test data is to provide a regression mechanism as part of QDoc's end-to-end test, tst_generatedOutput, for an issue (QTBUG-64506) that was reported against QDoc's \section1 command. The issue, as experienced by the reporter of the bug, is that if the \section1 command is followed by a non-ascii character (for example Chinese characters), navigation links aren't generated for the section title. The bug was filed against Qt 5.9.2. This test case aims at reproducing the issue as filed by the reporter.</p>
+<p>Such this document snippet:</p>
+<h2 id="a-713da3e8">A 大纲视图</h2>
+<p>The reporter states that this link works, presumably because it begins with the ascii character &quot;A&quot;.</p>
+<p>这就是大纲视图</p>
+<h2 id="3d-42faee45">3D场景视图</h2>
+<p>The reporter states that this link works, presumably because it begins with the digit &quot;3&quot;.</p>
+<p>这就是3D场景视图</p>
+<h2 id="3d-c57e864e">这就是3D场景视图</h2>
+<p>If this section generates a duplicate anchor, &quot;3D&quot;, it's because 3D is the only part of the section title QDoc recognizes. This is an error caused by the same bug, and the link should somehow reflect that QDoc encountered the Chinese (or any non-ascii) characters instead.</p>
+<h2 id="662952c1">属性视图</h2>
+<p>The reporter states that this link doesn't work, presumably because it begins with the Chinese character &quot;属&quot;.</p>
+<p>这就是属性视图</p>
+<h2 id="further-details">Further details</h2>
+<p>The bug report is at <a href="https://bugreports.qt.io/browse/QTBUG-64506">https://bugreports.qt.io/browse/QTBUG-64506</a>. It contains the content used to trigger the behavior in this test case. The Chinese characters are copied verbatim from the report.</p>
+<h3 id="ascii-characters-that-are-non-printable-ascii-such-as-or-521d09f0">Ascii characters that are non-printable ascii, such as ß, ü, or ø</h3>
+<p>A whole range of ascii characters are not printable ascii characters. These could also cause issues for QDoc. This section is here to confirm linking to such section titles works as expected. It's made a section2 to exercise the behavior for other section levels than 1.</p>
+</div>
+<!-- @@@adventures_with_non_ascii_characters.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/e85685de.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/e85685de.html
new file mode 100644
index 000000000..ea52873e7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/e85685de.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- adventures_with_non_ascii_characters.qdoc -->
+ <title>NonAsciiCharacterInput</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<!-- $$$موزاريلا-description -->
+<div class="descr" id="details">
+<p>This page exists solely to understand how QDoc will generate the file name for a page with right-to-left script in its name.</p>
+</div>
+<!-- @@@موزاريلا -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/8b5c72eb.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/8b5c72eb.webxml
new file mode 100644
index 000000000..375c43732
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/8b5c72eb.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="圣马苏里拉.html" href="8b5c72eb.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle="">
+ <description>
+ <para>This page exists solely to understand how QDoc will generate the file name for a page with non-latin characters in its name.</para>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/adventures-with-non-ascii-characters.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/adventures-with-non-ascii-characters.webxml
new file mode 100644
index 000000000..91059c165
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/adventures-with-non-ascii-characters.webxml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="adventures_with_non_ascii_characters.html" href="adventures-with-non-ascii-characters.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="Adventures with non-ascii characters in QDoc" fulltitle="Adventures with non-ascii characters in QDoc" subtitle="" brief="Test that non-ascii characters work as input to QDoc commands">
+ <contents name="a-713da3e8" title="A 大纲视图" level="1"/>
+ <contents name="3d-42faee45" title="3D场景视图" level="1"/>
+ <contents name="3d-c57e864e" title="这就是3D场景视图" level="1"/>
+ <contents name="662952c1" title="属性视图" level="1"/>
+ <contents name="further-details" title="Further details" level="1"/>
+ <contents name="ascii-characters-that-are-non-printable-ascii-such-as-or-521d09f0" title="Ascii characters that are non-printable ascii, such as ß, ü, or ø" level="2"/>
+ <description>
+ <brief>Test that non-ascii characters work as input to QDoc commands.</brief>
+ <para>The purpose of this test data is to provide a regression mechanism as part of QDoc's end-to-end test, tst_generatedOutput, for an issue (QTBUG-64506) that was reported against QDoc's \section1 command. The issue, as experienced by the reporter of the bug, is that if the \section1 command is followed by a non-ascii character (for example Chinese characters), navigation links aren't generated for the section title. The bug was filed against Qt 5.9.2. This test case aims at reproducing the issue as filed by the reporter.</para>
+ <para>Such this document snippet:</para>
+ <section id="a-713da3e8">
+ <heading level="1">A 大纲视图</heading>
+ <para>The reporter states that this link works, presumably because it begins with the ascii character &quot;A&quot;.</para>
+ <para>这就是大纲视图</para>
+ </section>
+ <section id="3d-42faee45">
+ <heading level="1">3D场景视图</heading>
+ <para>The reporter states that this link works, presumably because it begins with the digit &quot;3&quot;.</para>
+ <para>这就是3D场景视图</para>
+ </section>
+ <section id="3d-c57e864e">
+ <heading level="1">这就是3D场景视图</heading>
+ <para>If this section generates a duplicate anchor, &quot;3D&quot;, it's because 3D is the only part of the section title QDoc recognizes. This is an error caused by the same bug, and the link should somehow reflect that QDoc encountered the Chinese (or any non-ascii) characters instead.</para>
+ </section>
+ <section id="662952c1">
+ <heading level="1">属性视图</heading>
+ <para>The reporter states that this link doesn't work, presumably because it begins with the Chinese character &quot;属&quot;.</para>
+ <para>这就是属性视图</para>
+ </section>
+ <section id="further-details">
+ <heading level="1">Further details</heading>
+ <para>The bug report is at <link raw="https://bugreports.qt.io/browse/QTBUG-64506" href="https://bugreports.qt.io/browse/QTBUG-64506" type="external">https://bugreports.qt.io/browse/QTBUG-64506</link>. It contains the content used to trigger the behavior in this test case. The Chinese characters are copied verbatim from the report.</para>
+ </section>
+ <section id="ascii-characters-that-are-non-printable-ascii-such-as-or-521d09f0">
+ <heading level="2">Ascii characters that are non-printable ascii, such as ß, ü, or ø</heading>
+ <para>A whole range of ascii characters are not printable ascii characters. These could also cause issues for QDoc. This section is here to confirm linking to such section titles works as expected. It's made a section2 to exercise the behavior for other section levels than 1.</para>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/e85685de.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/e85685de.webxml
new file mode 100644
index 000000000..beb4df518
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/e85685de.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="موزاريلا" href="e85685de.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle="">
+ <description>
+ <para>This page exists solely to understand how QDoc will generate the file name for a page with right-to-left script in its name.</para>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/mozzarella-7c883eff.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/mozzarella-7c883eff.webxml
new file mode 100644
index 000000000..13ce91b72
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/mozzarella-7c883eff.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="桑塔mozzarella.html" href="mozzarella-7c883eff.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle="">
+ <description>
+ <para>This page exists solely to understand how QDoc will generate the file name for a page that mixes printable ascii with non-latin characters in its name.</para>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/nonasciicharacterinput.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/nonasciicharacterinput.index
new file mode 100644
index 000000000..aa306d7eb
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/nonasciicharacterinput.index
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="NonAsciiCharacterInput Reference Documentation" version="" project="NonAsciiCharacterInput">
+ <namespace name="" status="active" access="public" module="nonasciicharacterinput">
+ <page name="adventures_with_non_ascii_characters.html" href="adventures-with-non-ascii-characters.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="Adventures with non-ascii characters in QDoc" fulltitle="Adventures with non-ascii characters in QDoc" subtitle="" brief="Test that non-ascii characters work as input to QDoc commands">
+ <contents name="a-713da3e8" title="A 大纲视图" level="1"/>
+ <contents name="3d-42faee45" title="3D场景视图" level="1"/>
+ <contents name="3d-c57e864e" title="这就是3D场景视图" level="1"/>
+ <contents name="662952c1" title="属性视图" level="1"/>
+ <contents name="further-details" title="Further details" level="1"/>
+ <contents name="ascii-characters-that-are-non-printable-ascii-such-as-or-521d09f0" title="Ascii characters that are non-printable ascii, such as ß, ü, or ø" level="2"/>
+ </page>
+ <page name="SEITE_MIT_AUSSCHLIEßLICH_GROßBUCHSTABEN_IM_TITEL_ÜBERSCHRIFT.htm" href="seite-mit-ausschlie-lich-gro-buchstaben-im-titel-berschrift-htm-bfa91582.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle=""/>
+ <page name="santaموزاريلا.html" href="santa-14209312.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle=""/>
+ <page name="موزاريلا" href="e85685de.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle=""/>
+ <page name="圣马苏里拉.html" href="8b5c72eb.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle=""/>
+ <page name="桑塔mozzarella.html" href="mozzarella-7c883eff.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/santa-14209312.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/santa-14209312.webxml
new file mode 100644
index 000000000..9d07c9da8
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/santa-14209312.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="santaموزاريلا.html" href="santa-14209312.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle="">
+ <description>
+ <para>This page exists solely to understand how QDoc will generate the file name for a page that mixes printable ascii with right-to-left script in its name.</para>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/seite-mit-ausschlie-lich-gro-buchstaben-im-titel-berschrift-htm-bfa91582.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/seite-mit-ausschlie-lich-gro-buchstaben-im-titel-berschrift-htm-bfa91582.webxml
new file mode 100644
index 000000000..8fe3c93ee
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/html/seite-mit-ausschlie-lich-gro-buchstaben-im-titel-berschrift-htm-bfa91582.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="SEITE_MIT_AUSSCHLIEßLICH_GROßBUCHSTABEN_IM_TITEL_ÜBERSCHRIFT.htm" href="seite-mit-ausschlie-lich-gro-buchstaben-im-titel-berschrift-htm-bfa91582.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle="">
+ <description>
+ <para>This page exists solely to understand how QDoc will generate the file name for a page with non-ascii-printable latin characters in its name.</para>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/mozzarella-7c883eff.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/mozzarella-7c883eff.html
new file mode 100644
index 000000000..bb4b3651d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/mozzarella-7c883eff.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- adventures_with_non_ascii_characters.qdoc -->
+ <title>NonAsciiCharacterInput</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<!-- $$$桑塔mozzarella.html-description -->
+<div class="descr" id="details">
+<p>This page exists solely to understand how QDoc will generate the file name for a page that mixes printable ascii with non-latin characters in its name.</p>
+</div>
+<!-- @@@桑塔mozzarella.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/nonasciicharacterinput.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/nonasciicharacterinput.index
new file mode 100644
index 000000000..aa306d7eb
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/nonasciicharacterinput.index
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="NonAsciiCharacterInput Reference Documentation" version="" project="NonAsciiCharacterInput">
+ <namespace name="" status="active" access="public" module="nonasciicharacterinput">
+ <page name="adventures_with_non_ascii_characters.html" href="adventures-with-non-ascii-characters.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="Adventures with non-ascii characters in QDoc" fulltitle="Adventures with non-ascii characters in QDoc" subtitle="" brief="Test that non-ascii characters work as input to QDoc commands">
+ <contents name="a-713da3e8" title="A 大纲视图" level="1"/>
+ <contents name="3d-42faee45" title="3D场景视图" level="1"/>
+ <contents name="3d-c57e864e" title="这就是3D场景视图" level="1"/>
+ <contents name="662952c1" title="属性视图" level="1"/>
+ <contents name="further-details" title="Further details" level="1"/>
+ <contents name="ascii-characters-that-are-non-printable-ascii-such-as-or-521d09f0" title="Ascii characters that are non-printable ascii, such as ß, ü, or ø" level="2"/>
+ </page>
+ <page name="SEITE_MIT_AUSSCHLIEßLICH_GROßBUCHSTABEN_IM_TITEL_ÜBERSCHRIFT.htm" href="seite-mit-ausschlie-lich-gro-buchstaben-im-titel-berschrift-htm-bfa91582.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle=""/>
+ <page name="santaموزاريلا.html" href="santa-14209312.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle=""/>
+ <page name="موزاريلا" href="e85685de.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle=""/>
+ <page name="圣马苏里拉.html" href="8b5c72eb.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle=""/>
+ <page name="桑塔mozzarella.html" href="mozzarella-7c883eff.html" status="active" location="adventures_with_non_ascii_characters.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/santa-14209312.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/santa-14209312.html
new file mode 100644
index 000000000..f40feed36
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/santa-14209312.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- adventures_with_non_ascii_characters.qdoc -->
+ <title>NonAsciiCharacterInput</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<!-- $$$santaموزاريلا.html-description -->
+<div class="descr" id="details">
+<p>This page exists solely to understand how QDoc will generate the file name for a page that mixes printable ascii with right-to-left script in its name.</p>
+</div>
+<!-- @@@santaموزاريلا.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/seite-mit-ausschlie-lich-gro-buchstaben-im-titel-berschrift-htm-bfa91582.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/seite-mit-ausschlie-lich-gro-buchstaben-im-titel-berschrift-htm-bfa91582.html
new file mode 100644
index 000000000..16df49755
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/expected/seite-mit-ausschlie-lich-gro-buchstaben-im-titel-berschrift-htm-bfa91582.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- adventures_with_non_ascii_characters.qdoc -->
+ <title>NonAsciiCharacterInput</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<!-- $$$SEITE_MIT_AUSSCHLIEßLICH_GROßBUCHSTABEN_IM_TITEL_ÜBERSCHRIFT.htm-description -->
+<div class="descr" id="details">
+<p>This page exists solely to understand how QDoc will generate the file name for a page with non-ascii-printable latin characters in its name.</p>
+</div>
+<!-- @@@SEITE_MIT_AUSSCHLIEßLICH_GROßBUCHSTABEN_IM_TITEL_ÜBERSCHRIFT.htm -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/non_ascii_character_input.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/non_ascii_character_input.qdocconf
new file mode 100644
index 000000000..0177b51ac
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/non_ascii_character_input.qdocconf
@@ -0,0 +1,30 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+project = NonAsciiCharacterInput
+
+headerdirs = ./src
+sourcedirs = ./src
+exampledirs = ./src
+
+outputformats = WebXML HTML
+WebXML.quotinginformation = true
+WebXML.nosubdirs = true
+
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ./src/images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+# By default, use -outputdir directly, no subdir.
+# outputsubdir '.' matches the root of expected_output/
+HTML.nosubdirs = true
+HTML.outputsubdir = .
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/src/adventures_with_non_ascii_characters.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/src/adventures_with_non_ascii_characters.qdoc
new file mode 100644
index 000000000..c5f09cb1c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/non_ascii_character_input/src/adventures_with_non_ascii_characters.qdoc
@@ -0,0 +1,89 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \page adventures_with_non_ascii_characters.html
+ \title Adventures with non-ascii characters in QDoc
+ \brief Test that non-ascii characters work as input to QDoc commands.
+
+ The purpose of this test data is to provide a regression mechanism as part
+ of QDoc's end-to-end test, tst_generatedOutput, for an issue (QTBUG-64506)
+ that was reported against QDoc's \\section1 command. The issue, as
+ experienced by the reporter of the bug, is that if the \\section1 command
+ is followed by a non-ascii character (for example Chinese characters),
+ navigation links aren't generated for the section title. The bug was filed
+ against Qt 5.9.2. This test case aims at reproducing the issue as filed by
+ the reporter.
+
+ Such this document snippet:
+
+ \section1 A 大纲视图
+ The reporter states that this link works, presumably because it begins with
+ the ascii character "A".
+
+ 这就是大纲视图
+
+ \section1 3D场景视图
+ The reporter states that this link works, presumably because it begins with
+ the digit "3".
+
+ 这就是3D场景视图
+
+ \section1 这就是3D场景视图
+ If this section generates a duplicate anchor, "3D", it's because 3D
+ is the only part of the section title QDoc recognizes. This is an error
+ caused by the same bug, and the link should somehow reflect that QDoc
+ encountered the Chinese (or any non-ascii) characters instead.
+
+ \section1 属性视图
+ The reporter states that this link doesn't work, presumably because it
+ begins with the Chinese character "属".
+
+ 这就是属性视图
+
+ \section1 Further details
+ The bug report is at \l https://bugreports.qt.io/browse/QTBUG-64506. It
+ contains the content used to trigger the behavior in this test case. The
+ Chinese characters are copied verbatim from the report.
+
+ \section2 Ascii characters that are non-printable ascii, such as ß, ü, or ø
+ A whole range of ascii characters are not printable ascii characters. These
+ could also cause issues for QDoc. This section is here to confirm linking
+ to such section titles works as expected. It's made a section2 to exercise
+ the behavior for other section levels than 1.
+*/
+
+/*!
+ \page SEITE_MIT_AUSSCHLIEßLICH_GROßBUCHSTABEN_IM_TITEL_ÜBERSCHRIFT.htm
+
+ This page exists solely to understand how QDoc will generate the file name
+ for a page with non-ascii-printable latin characters in its name.
+*/
+
+/*!
+ \page موزاريلا سانتا.html
+
+ This page exists solely to understand how QDoc will generate the file name
+ for a page with right-to-left script in its name.
+*/
+
+/*!
+ \page 圣马苏里拉.html
+
+ This page exists solely to understand how QDoc will generate the file name
+ for a page with non-latin characters in its name.
+*/
+
+/*!
+ \page santaموزاريلا.html
+
+ This page exists solely to understand how QDoc will generate the file name
+ for a page that mixes printable ascii with right-to-left script in its name.
+*/
+
+/*!
+ \page 桑塔mozzarella.html
+
+ This page exists solely to understand how QDoc will generate the file name
+ for a page that mixes printable ascii with non-latin characters in its name.
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/crash.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/crash.xml
new file mode 100644
index 000000000..04e27b4a3
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/crash.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title></db:title>
+<db:productname>OutputFromQDocFiles</db:productname>
+<db:edition>OutputFromQDocFiles - A test project for QDoc build artifacts</db:edition>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:para><db:link xlink:href=""></db:link></db:para>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/images/01.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/images/01.png
new file mode 100644
index 000000000..d73ab969b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/images/01.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/images/leonardo-da-vinci.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/images/leonardo-da-vinci.png
new file mode 100644
index 000000000..854acb4ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/images/leonardo-da-vinci.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocfileoutput-exhaustive.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocfileoutput-exhaustive.xml
new file mode 100644
index 000000000..237896629
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocfileoutput-exhaustive.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Exhaustive testing of QDoc commands</db:title>
+<db:productname>OutputFromQDocFiles</db:productname>
+<db:edition>OutputFromQDocFiles - A test project for QDoc build artifacts</db:edition>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>This page is a dumping ground for QDoc commands under test.</db:para>
+</db:abstract>
+</db:info>
+<db:section xml:id="this-is-a-section1">
+<db:title>This is a section1</db:title>
+<db:section xml:id="this-is-a-section2">
+<db:title>This is a section2</db:title>
+<db:section xml:id="this-is-a-section3">
+<db:title>This is a section3</db:title>
+<db:section xml:id="this-is-a-section4">
+<db:title>This is a section4</db:title>
+<db:programlisting language="cpp" role="bad">This is bad code
+</db:programlisting>
+<db:para>This text should have a line break riiiiight noooow.</db:para>
+<db:para><db:emphasis role="bold">All your text belong to bold</db:emphasis> ...And this is an examble of only <db:emphasis role="bold">bold</db:emphasis> being, well, bold.</db:para>
+<db:programlisting language="cpp"> ...
+</db:programlisting>
+<db:title>This a caption</db:title>
+<db:para>Lorem legal ipsum</db:para>
+<db:blockquote><db:para>This is a quotation.</db:para>
+</db:blockquote>
+<db:sidebar><db:para>Look, ma! I made a sidebar!</db:para>
+</db:sidebar>
+<db:informaltable style="generic">
+<db:tr valign="top">
+<db:td>
+<db:para>Table item in a table row</db:para>
+</db:td>
+</db:tr>
+<db:tr valign="top">
+<db:td>
+<db:para>Another item in a different row</db:para>
+</db:td>
+</db:tr>
+</db:informaltable>
+<db:important>
+<db:para>This is really important.</db:para>
+</db:important>
+<db:note>
+<db:para>The code above doesn't compile</db:para>
+</db:note>
+</db:section>
+</db:section>
+</db:section>
+</db:section>
+<db:section xml:id="images">
+<db:title>Images</db:title>
+<db:para>An image without any text:</db:para>
+<db:mediaobject>
+<db:imageobject>
+<db:imagedata fileref="images/leonardo-da-vinci.png"/>
+</db:imageobject>
+</db:mediaobject>
+<db:para>An image with just an alternative text:</db:para>
+<db:mediaobject>
+<db:alt>Image alt</db:alt>
+<db:imageobject>
+<db:imagedata fileref="images/leonardo-da-vinci.png"/>
+</db:imageobject>
+</db:mediaobject>
+<db:para>An image with alternative text and 1-atom caption:</db:para>
+<db:figure>
+<db:title>Image caption</db:title>
+<db:mediaobject>
+<db:alt>Image alt</db:alt>
+<db:imageobject>
+<db:imagedata fileref="images/leonardo-da-vinci.png"/>
+</db:imageobject>
+</db:mediaobject>
+</db:figure>
+<db:para>An image with alternative text and 2-atom caption:</db:para>
+<db:figure>
+<db:title>Image caption with <db:emphasis role="bold">bold</db:emphasis> text</db:title>
+<db:mediaobject>
+<db:alt>Image alt</db:alt>
+<db:imageobject>
+<db:imagedata fileref="images/leonardo-da-vinci.png"/>
+</db:imageobject>
+</db:mediaobject>
+</db:figure>
+<db:para>A bordered image:</db:para>
+<db:mediaobject>
+<db:imageobject>
+<db:imagedata fileref="images/leonardo-da-vinci.png"/>
+</db:imageobject>
+</db:mediaobject>
+<db:para>A bordered image with a caption:</db:para>
+<db:figure>
+<db:title>Screenshot of the System Tray Icon</db:title>
+<db:mediaobject>
+<db:imageobject>
+<db:imagedata fileref="images/leonardo-da-vinci.png"/>
+</db:imageobject>
+</db:mediaobject>
+</db:figure>
+<db:para>An inline image:</db:para>
+<db:para>The is a paragraph containing an <db:inlinemediaobject>
+<db:imageobject>
+<db:imagedata fileref="images/01.png"/>
+</db:imageobject>
+</db:inlinemediaobject> inline image to test if qdoc handles them properly, without considering rest of the line as alt text for the image.</db:para>
+<db:para>An inline image with alt text:</db:para>
+<db:para>Here is another example of <db:inlinemediaobject>
+<db:alt>No. 1</db:alt>
+<db:imageobject>
+<db:imagedata fileref="images/01.png"/>
+</db:imageobject>
+</db:inlinemediaobject> inline image with alternative text, which should be added as an attribute to the inline image.</db:para>
+<db:para>File quoting:</db:para>
+<db:programlisting language="cpp"> if (false) {
+ return 1;
+ }
+</db:programlisting>
+</db:section>
+<db:section xml:id="commands-not-yet-tested">
+<db:title>Commands not yet tested</db:title>
+<db:warning>
+<db:para>The following commands have yet to be tested: footnote link sincelist header index topicref // or just don’t care, remove it inlineimage printline printto quotefile skipline skipuntil span snippet codeline overload sub sup tableofcontents tt uicontrol endmapref endomit underline unicode</db:para>
+</db:warning>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocfileoutput-linking.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocfileoutput-linking.xml
new file mode 100644
index 000000000..51b7e62b3
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocfileoutput-linking.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Testing QDoc's link command</db:title>
+<db:productname>OutputFromQDocFiles</db:productname>
+<db:edition>OutputFromQDocFiles - A test project for QDoc build artifacts</db:edition>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:extendedlink xlink:type="extended"><db:link xlink:to="qdoctests-qdocfileoutput.xml" xlink:type="arc" xlink:arcrole="prev" xlink:title="QDoc Testing"/></db:extendedlink>
+<db:extendedlink xlink:type="extended"><db:link xlink:to="toc.xml" xlink:type="arc" xlink:arcrole="next" xlink:title="Table of Contents"/></db:extendedlink>
+<db:abstract>
+<db:para>This is a page for testing QDoc's link command.</db:para>
+</db:abstract>
+</db:info>
+<db:anchor xml:id="link-test-target"/>
+<db:section xml:id="link-targets">
+<db:title>Link targets</db:title>
+<db:para>Valid parameters for the link command (<db:code>\l</db:code>) are page and section titles, targets defined with \target or \keyword commands, and API reference keywords (types, methods, namespaces, and so on).</db:para>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocfileoutput.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocfileoutput.xml
new file mode 100644
index 000000000..72905681d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocfileoutput.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Testing QDoc output from .qdoc files</db:title>
+<db:productname>OutputFromQDocFiles</db:productname>
+<db:edition>OutputFromQDocFiles - A test project for QDoc build artifacts</db:edition>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:extendedlink xlink:type="extended"><db:link xlink:to="qdoctests-qdocfileoutput-linking.xml" xlink:type="arc" xlink:arcrole="next" xlink:title="QDoc Linking Test"/></db:extendedlink>
+<db:abstract>
+<db:para>This is a simple page for testing purposes only.</db:para>
+</db:abstract>
+</db:info>
+<db:para>QDoc generates documentation for software projects. It does this by extracting <db:emphasis>QDoc comments</db:emphasis> from project source files. QDoc comments are signified by a C-style-like comment tag followed by an exclamation point, like this: <db:code>/*!</db:code> <db:code>This text is contained within QDoc comment tags.</db:code> <db:code>*/</db:code>.</db:para>
+<db:section xml:id="supported-file-types">
+<db:title>Supported file types</db:title>
+<db:para>QDoc parses <db:code>.cpp</db:code> and <db:code>.qdoc</db:code> files. It does extract comments from header (<db:code>.h</db:code>) files.</db:para>
+</db:section>
+<db:section xml:id="further-information">
+<db:title>Further information</db:title>
+<db:para>This test document is written with the purpose of testing the output QDoc generates when parsing <db:code>.qdoc</db:code> files. It is fairly simple and makes use of a limited subset of QDoc's command. Those commands are:</db:para>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:code>\page</db:code></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:code>\title</db:code></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:code>\brief</db:code></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:code>\e</db:code> (for emphasizing &quot;QDoc comments&quot;)</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:code>\c</db:code> (for multiple monospace-formatted entries)</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:code>\section1</db:code></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:code>\list</db:code></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:code>\li</db:code></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:code>\endlist</db:code></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+<db:section xml:id="linking">
+<db:title>Linking</db:title>
+<db:para>There are multiple ways to create hyperlinks to other topics:</db:para>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="qdoctests-qdocfileoutput-linking.xml">Linking to a page title</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="qdoctests-qdocfileoutput-linking.xml#link-targets">Linking to a section title</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="qdoctests-qdocfileoutput-linking.xml#link-test-target">Linking using a \target string</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="qdoctests-qdocfileoutput-linking.xml">Linking using a \keyword string</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+<db:section xml:id="qdoc-linking-test">
+<db:title>QDoc Linking Test</db:title>
+<db:para>This section title is overridden by another target which takes precedence.</db:para>
+</db:section>
+<db:section xml:id="linking-to-something-in-a-section-title">
+<db:title>Linking to <db:link xlink:href="qdoctests-qdocfileoutput.xml#further-information">something</db:link> in a section title</db:title>
+<db:para>This is allowed but a questionable practice.</db:para>
+<db:note>
+<db:para>You're looking at detailed information.</db:para>
+</db:note>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocmanuallikefileoutput.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocmanuallikefileoutput.xml
new file mode 100644
index 000000000..38e3887ef
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/qdoctests-qdocmanuallikefileoutput.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Document Navigation</db:title>
+<db:productname>OutputFromQDocFiles</db:productname>
+<db:edition>OutputFromQDocFiles - A test project for QDoc build artifacts</db:edition>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:para>The navigation commands...</db:para>
+<db:blockquote/>
+<db:programlisting language="cpp">&amp;lt;head&amp;gt;
+ ...
+ &amp;lt;link rel=&amp;quot;start&amp;quot; href=&amp;quot;basicqt.html&amp;quot; /&amp;gt;
+ ...
+&amp;lt;/head&amp;gt;
+</db:programlisting>
+<db:section xml:id="commands">
+<db:title>Commands</db:title>
+<db:anchor xml:id="previouspage-command"/>
+<db:section xml:id="previouspage">
+<db:title>\previouspage</db:title>
+<db:para>The \previouspage command...</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/toc.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/toc.xml
new file mode 100644
index 000000000..14ccd795d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/docbook/toc.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Table of Contents</db:title>
+<db:productname>OutputFromQDocFiles</db:productname>
+<db:edition>OutputFromQDocFiles - A test project for QDoc build artifacts</db:edition>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:extendedlink xlink:type="extended"><db:link xlink:to="qdoctests-qdocfileoutput-linking.xml" xlink:type="arc" xlink:arcrole="prev" xlink:title="QDoc Linking Test"/></db:extendedlink>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="qdoctests-qdocfileoutput.xml">QDoc Testing</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="qdoctests-qdocfileoutput-linking.xml">QDoc Linking Test</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="toc.xml">Table of Contents</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/crash.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/crash.html
new file mode 100644
index 000000000..7ac452663
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/crash.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- qdoctests-outputfromqdocfiles.qdoc -->
+ <title>OutputFromQDocFiles</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<!-- $$$crash.html-description -->
+<div class="descr" id="details">
+<p></p>
+</div>
+<!-- @@@crash.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/images/01.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/images/01.png
new file mode 100644
index 000000000..d73ab969b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/images/01.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/images/leonardo-da-vinci.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/images/leonardo-da-vinci.png
new file mode 100644
index 000000000..854acb4ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/images/leonardo-da-vinci.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/outputfromqdocfiles.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/outputfromqdocfiles.index
new file mode 100644
index 000000000..eb63b02c2
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/outputfromqdocfiles.index
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="A test project for QDoc build artifacts" version="" project="OutputFromQDocFiles">
+ <namespace name="" status="active" access="public" module="outputfromqdocfiles">
+ <page name="qdoctests-qdocmanuallikefileoutput.html" href="qdoctests-qdocmanuallikefileoutput.html" status="active" location="qdoctests-outputfromqdocmanuallikefiles.qdoc" documented="true" subtype="page" title="Document Navigation" fulltitle="Document Navigation" subtitle="">
+ <target name="previouspage-command"/>
+ <contents name="commands" title="Commands" level="1"/>
+ <contents name="previouspage" title="\previouspage" level="2"/>
+ </page>
+ <page name="qdoctests-qdocfileoutput-exhaustive.html" href="qdoctests-qdocfileoutput-exhaustive.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Exhaustive testing of QDoc commands" fulltitle="Exhaustive testing of QDoc commands" subtitle="" brief="This page is a dumping ground for QDoc commands under test">
+ <contents name="this-is-a-section1" title="This is a section1" level="1"/>
+ <contents name="this-is-a-section2" title="This is a section2" level="2"/>
+ <contents name="this-is-a-section3" title="This is a section3" level="3"/>
+ <contents name="this-is-a-section4" title="This is a section4" level="4"/>
+ <contents name="images" title="Images" level="1"/>
+ <contents name="commands-not-yet-tested" title="Commands not yet tested" level="1"/>
+ </page>
+ <page name="toc.html" href="toc.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Table of Contents" fulltitle="Table of Contents" subtitle=""/>
+ <page name="qdoctests-qdocfileoutput.html" href="qdoctests-qdocfileoutput.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Testing QDoc output from .qdoc files" fulltitle="Testing QDoc output from .qdoc files" subtitle="" brief="This is a simple page for testing purposes only">
+ <contents name="supported-file-types" title="Supported file types" level="1"/>
+ <contents name="further-information" title="Further information" level="1"/>
+ <contents name="linking" title="Linking" level="1"/>
+ <contents name="qdoc-linking-test" title="QDoc Linking Test" level="1"/>
+ <contents name="linking-to-something-in-a-section-title" title="Linking to something in a section title" level="1"/>
+ </page>
+ <page name="qdoctests-qdocfileoutput-linking.html" href="qdoctests-qdocfileoutput-linking.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Testing QDoc's link command" fulltitle="Testing QDoc's link command" subtitle="" brief="This is a page for testing QDoc's link command">
+ <target name="link-test-target"/>
+ <keyword name="qdoc-linking-test" title="QDoc Linking Test"/>
+ <contents name="link-targets" title="Link targets" level="1"/>
+ </page>
+ <page name="crash.html" href="crash.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocfileoutput-exhaustive.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocfileoutput-exhaustive.html
new file mode 100644
index 000000000..c4cb46759
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocfileoutput-exhaustive.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- qdoctests-outputfromqdocfiles.qdoc -->
+ <meta name="description" content="This page is a dumping ground for QDoc commands under test.">
+ <title>Exhaustive testing of QDoc commands | OutputFromQDocFiles</title>
+</head>
+<body>
+<li id="buildversion">OutputFromQDocFiles - A test project for QDoc build artifacts</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#this-is-a-section1">This is a section1</a></li>
+<li class="level2"><a href="#this-is-a-section2">This is a section2</a></li>
+<li class="level3"><a href="#this-is-a-section3">This is a section3</a></li>
+<li class="level4"><a href="#this-is-a-section4">This is a section4</a></li>
+<li class="level1"><a href="#images">Images</a></li>
+<li class="level1"><a href="#commands-not-yet-tested">Commands not yet tested</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Exhaustive testing of QDoc commands</h1>
+<!-- $$$qdoctests-qdocfileoutput-exhaustive.html-description -->
+<div class="descr" id="details">
+<h2 id="this-is-a-section1">This is a section1</h2>
+<h3 id="this-is-a-section2">This is a section2</h3>
+<h4 id="this-is-a-section3">This is a section3</h4>
+<h5 id="this-is-a-section4">This is a section4</h5>
+<pre class="cpp plain" translate="no">This is bad code</pre>
+<p>This text should have a line break riiiiight <br />
+ noooow.</p>
+<p><b>All your text belong to bold</b> ...And this is an examble of only <b>bold</b> being, well, bold.</p>
+<pre class="cpp" translate="no"> ...</pre>
+<p class="figCaption">This a caption</p>
+<div class="LegaleseLeft"><p>Lorem legal ipsum</p>
+</div><blockquote><p>This is a quotation.</p>
+</blockquote>
+ <html><body>This is <b>raw</b>. Like the <h1>Eddie Murphy</h1> movie. Just not as funny.</body></html>
+ <p>Look, ma! I made a sidebar!</p>
+<div class="table"><table class="generic">
+ <tr valign="top" class="odd"><td >Table item in a table row</td></tr>
+<tr valign="top" class="even"><td >Another item in a different row</td></tr>
+</table></div>
+<div class="admonition important">
+<p><b>Important: </b>This is really important.</p>
+</div>
+<div class="admonition note">
+<p><b>Note: </b>The code above doesn't compile</p>
+</div>
+<hr />
+<h2 id="images">Images</h2>
+<p>An image without any text:</p>
+<p class="centerAlign"><img src="images/leonardo-da-vinci.png" alt="" /></p><p>An image with just an alternative text:</p>
+<p class="centerAlign"><img src="images/leonardo-da-vinci.png" alt="Image alt" /></p><p>An image with alternative text and 1-atom caption:</p>
+<p class="centerAlign"><img src="images/leonardo-da-vinci.png" alt="Image alt" /></p><p class="figCaption">Image caption</p>
+<p>An image with alternative text and 2-atom caption:</p>
+<p class="centerAlign"><img src="images/leonardo-da-vinci.png" alt="Image alt" /></p><p class="figCaption">Image caption with <b>bold</b> text</p>
+<p>A bordered image:</p>
+<div class="border"><p class="centerAlign"><img src="images/leonardo-da-vinci.png" alt="" /></p></div><p>A bordered image with a caption:</p>
+<div class="border"><p class="centerAlign"><img src="images/leonardo-da-vinci.png" alt="" /></p></div><p class="figCaption">Screenshot of the System Tray Icon</p>
+<p>An inline image:</p>
+<p>The is a paragraph containing an <img src="images/01.png" alt="" /> inline image to test if qdoc handles them properly, without considering rest of the line as alt text for the image.</p>
+<p>An inline image with alt text:</p>
+<p>Here is another example of <img src="images/01.png" alt="No. 1" /> inline image with alternative text, which should be added as an attribute to the inline image.</p>
+<p>File quoting:</p>
+<pre class="cpp" translate="no"> <span class="keyword">if</span> (<span class="keyword">false</span>) {
+ <span class="keyword">return</span> <span class="number">1</span>;
+ }</pre>
+<h2 id="commands-not-yet-tested">Commands not yet tested</h2>
+<div class="admonition warning">
+<p><b>Warning: </b>The following commands have yet to be tested: footnote link sincelist header index topicref // or just don’t care, remove it inlineimage printline printto quotefile skipline skipuntil span snippet codeline overload sub sup tableofcontents tt uicontrol endmapref endomit underline unicode</p>
+</div>
+</div>
+<!-- @@@qdoctests-qdocfileoutput-exhaustive.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocfileoutput-linking.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocfileoutput-linking.html
new file mode 100644
index 000000000..b4d63e76e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocfileoutput-linking.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- qdoctests-outputfromqdocfiles.qdoc -->
+ <meta name="description" content="This is a page for testing QDoc's link command.">
+ <title>Testing QDoc's link command | OutputFromQDocFiles</title>
+</head>
+<body>
+<li id="buildversion">OutputFromQDocFiles - A test project for QDoc build artifacts</li>
+ <link rel="prev" href="qdoctests-qdocfileoutput.html" />
+ <link rel="next" href="toc.html" />
+<p class="naviNextPrevious headerNavi">
+<a class="prevPage" href="qdoctests-qdocfileoutput.html">QDoc Testing</a>
+<a class="nextPage" href="toc.html">Table of Contents</a>
+</p>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#link-targets">Link targets</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Testing QDoc's link command</h1>
+<!-- $$$qdoctests-qdocfileoutput-linking.html-description -->
+<div class="descr" id="details">
+<span id="link-test-target"></span><h2 id="link-targets">Link targets</h2>
+<p>Valid parameters for the link command (<code translate="no">\l</code>) are page and section titles, targets defined with \target or \keyword commands, and API reference keywords (types, methods, namespaces, and so on).</p>
+</div>
+<!-- @@@qdoctests-qdocfileoutput-linking.html -->
+<p class="naviNextPrevious footerNavi">
+<a class="prevPage" href="qdoctests-qdocfileoutput.html">QDoc Testing</a>
+<a class="nextPage" href="toc.html">Table of Contents</a>
+</p>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocfileoutput.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocfileoutput.html
new file mode 100644
index 000000000..a38d4e3b4
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocfileoutput.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- qdoctests-outputfromqdocfiles.qdoc -->
+ <meta name="description" content="This is a simple page for testing purposes only.">
+ <title>Testing QDoc output from .qdoc files | OutputFromQDocFiles</title>
+</head>
+<body>
+<li id="buildversion">OutputFromQDocFiles - A test project for QDoc build artifacts</li>
+ <link rel="next" href="qdoctests-qdocfileoutput-linking.html" />
+<p class="naviNextPrevious headerNavi">
+<a class="nextPage" href="qdoctests-qdocfileoutput-linking.html">QDoc Linking Test</a>
+</p>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#supported-file-types">Supported file types</a></li>
+<li class="level1"><a href="#further-information">Further information</a></li>
+<li class="level1"><a href="#linking">Linking</a></li>
+<li class="level1"><a href="#qdoc-linking-test">QDoc Linking Test</a></li>
+<li class="level1"><a href="#linking-to-something-in-a-section-title">Linking to something in a section title</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Testing QDoc output from .qdoc files</h1>
+<!-- $$$qdoctests-qdocfileoutput.html-description -->
+<div class="descr" id="details">
+<p>QDoc generates documentation for software projects. It does this by extracting <i>QDoc comments</i> from project source files. QDoc comments are signified by a C-style-like comment tag followed by an exclamation point, like this: <code translate="no">/*!</code> <code translate="no">This text is contained within QDoc comment tags.</code> <code translate="no">*/</code>.</p>
+<h2 id="supported-file-types">Supported file types</h2>
+<p>QDoc parses <code translate="no">.cpp</code> and <code translate="no">.qdoc</code> files. It does extract comments from header (<code translate="no">.h</code>) files.</p>
+<h2 id="further-information">Further information</h2>
+<p>This test document is written with the purpose of testing the output QDoc generates when parsing <code translate="no">.qdoc</code> files. It is fairly simple and makes use of a limited subset of QDoc's command. Those commands are:</p>
+<ul>
+<li><code translate="no">\page</code></li>
+<li><code translate="no">\title</code></li>
+<li><code translate="no">\brief</code></li>
+<li><code translate="no">\e</code> (for emphasizing &quot;QDoc comments&quot;)</li>
+<li><code translate="no">\c</code> (for multiple monospace-formatted entries)</li>
+<li><code translate="no">\section1</code></li>
+<li><code translate="no">\list</code></li>
+<li><code translate="no">\li</code></li>
+<li><code translate="no">\endlist</code></li>
+</ul>
+<h2 id="linking">Linking</h2>
+<p>There are multiple ways to create hyperlinks to other topics:</p>
+<ul>
+<li><a href="qdoctests-qdocfileoutput-linking.html">Linking to a page title</a></li>
+<li><a href="qdoctests-qdocfileoutput-linking.html#link-targets">Linking to a section title</a></li>
+<li><a href="qdoctests-qdocfileoutput-linking.html#link-test-target">Linking using a \target string</a></li>
+<li><a href="qdoctests-qdocfileoutput-linking.html">Linking using a \keyword string</a></li>
+</ul>
+<h2 id="qdoc-linking-test">QDoc Linking Test</h2>
+<p>This section title is overridden by another target which takes precedence.</p>
+<h2 id="linking-to-something-in-a-section-title">Linking to <a href="qdoctests-qdocfileoutput.html#further-information">something</a> in a section title</h2>
+<p>This is allowed but a questionable practice.</p>
+<details>
+<summary>QDoc details</summary>
+<div class="admonition note">
+<p><b>Note: </b>You're looking at detailed information.</p>
+</div>
+</details>
+</div>
+<!-- @@@qdoctests-qdocfileoutput.html -->
+<p class="naviNextPrevious footerNavi">
+<a class="nextPage" href="qdoctests-qdocfileoutput-linking.html">QDoc Linking Test</a>
+</p>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocmanuallikefileoutput.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocmanuallikefileoutput.html
new file mode 100644
index 000000000..e4b120bd0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/qdoctests-qdocmanuallikefileoutput.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- qdoctests-outputfromqdocmanuallikefiles.qdoc -->
+ <title>Document Navigation | OutputFromQDocFiles</title>
+</head>
+<body>
+<li id="buildversion">OutputFromQDocFiles - A test project for QDoc build artifacts</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#commands">Commands</a></li>
+<li class="level2"><a href="#previouspage">\previouspage</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Document Navigation</h1>
+<!-- $$$qdoctests-qdocmanuallikefileoutput.html-description -->
+<div class="descr" id="details">
+<p>The navigation commands...</p>
+<blockquote> <table border="0" cellpadding="0" cellspacing="5" width="100%">
+
+ <tr>
+ <p>
+ [Previous: <a href="15-qdoc-commands-navigation.html#deadlink">
+ Basic Qt</a>]
+ [<a href="15-qdoc-commands-navigation.html#deadlink">Contents</a>]
+ [Next: <a href="15-qdoc-commands-navigation.html#deadlink">
+ Creating Dialogs</a>]
+ </p>
+
+ <h1 align="center">Getting Started<br /></h1>
+
+ <p>
+ This chapter shows how to combine basic C++ with the
+ functionality provided by Qt to create a few small graphical
+ interface (GUI) applications.
+ </p>
+
+ <p>
+ [Previous: <a href="15-qdoc-commands-navigation.html#deadlink">
+ Basic Qt</a>]
+ [<a href="15-qdoc-commands-navigation.html#deadlink">Contents</a>]
+ [Next: <a href="15-qdoc-commands-navigation.html#deadlink">
+ Creating Dialogs</a>]
+ </p>
+
+ </table>
+ </blockquote>
+<pre class="cpp" translate="no"><span class="operator">&lt;</span>head<span class="operator">&gt;</span>
+ <span class="operator">.</span><span class="operator">.</span><span class="operator">.</span>
+ <span class="operator">&lt;</span>link rel<span class="operator">=</span><span class="string">&quot;start&quot;</span> href<span class="operator">=</span><span class="string">&quot;basicqt.html&quot;</span> <span class="operator">/</span><span class="operator">&gt;</span>
+ <span class="operator">.</span><span class="operator">.</span><span class="operator">.</span>
+<span class="operator">&lt;</span><span class="operator">/</span>head<span class="operator">&gt;</span></pre>
+<h2 id="commands">Commands</h2>
+<span id="previouspage-command"></span><h3 id="previouspage">\previouspage</h3>
+<p>The \previouspage command...</p>
+</div>
+<!-- @@@qdoctests-qdocmanuallikefileoutput.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/toc.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/toc.html
new file mode 100644
index 000000000..f34ce0db6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/html/toc.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- qdoctests-outputfromqdocfiles.qdoc -->
+ <title>Table of Contents | OutputFromQDocFiles</title>
+</head>
+<body>
+<li id="buildversion">OutputFromQDocFiles - A test project for QDoc build artifacts</li>
+ <link rel="prev" href="qdoctests-qdocfileoutput-linking.html" />
+<p class="naviNextPrevious headerNavi">
+<a class="prevPage" href="qdoctests-qdocfileoutput-linking.html">QDoc Linking Test</a>
+</p>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Table of Contents</h1>
+<!-- $$$toc.html-description -->
+<div class="descr" id="details">
+<ul>
+<li><a href="qdoctests-qdocfileoutput.html">QDoc Testing</a></li>
+<li><a href="qdoctests-qdocfileoutput-linking.html">QDoc Linking Test</a></li>
+<li><a href="toc.html">Table of Contents</a></li>
+</ul>
+</div>
+<!-- @@@toc.html -->
+<p class="naviNextPrevious footerNavi">
+<a class="prevPage" href="qdoctests-qdocfileoutput-linking.html">QDoc Linking Test</a>
+</p>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/crash.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/crash.webxml
new file mode 100644
index 000000000..38c761f7d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/crash.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="crash.html" href="crash.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle="">
+ <description>
+ <para></para>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/images/01.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/images/01.png
new file mode 100644
index 000000000..d73ab969b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/images/01.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/images/leonardo-da-vinci.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/images/leonardo-da-vinci.png
new file mode 100644
index 000000000..854acb4ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/images/leonardo-da-vinci.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/outputfromqdocfiles.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/outputfromqdocfiles.index
new file mode 100644
index 000000000..eb63b02c2
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/outputfromqdocfiles.index
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="A test project for QDoc build artifacts" version="" project="OutputFromQDocFiles">
+ <namespace name="" status="active" access="public" module="outputfromqdocfiles">
+ <page name="qdoctests-qdocmanuallikefileoutput.html" href="qdoctests-qdocmanuallikefileoutput.html" status="active" location="qdoctests-outputfromqdocmanuallikefiles.qdoc" documented="true" subtype="page" title="Document Navigation" fulltitle="Document Navigation" subtitle="">
+ <target name="previouspage-command"/>
+ <contents name="commands" title="Commands" level="1"/>
+ <contents name="previouspage" title="\previouspage" level="2"/>
+ </page>
+ <page name="qdoctests-qdocfileoutput-exhaustive.html" href="qdoctests-qdocfileoutput-exhaustive.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Exhaustive testing of QDoc commands" fulltitle="Exhaustive testing of QDoc commands" subtitle="" brief="This page is a dumping ground for QDoc commands under test">
+ <contents name="this-is-a-section1" title="This is a section1" level="1"/>
+ <contents name="this-is-a-section2" title="This is a section2" level="2"/>
+ <contents name="this-is-a-section3" title="This is a section3" level="3"/>
+ <contents name="this-is-a-section4" title="This is a section4" level="4"/>
+ <contents name="images" title="Images" level="1"/>
+ <contents name="commands-not-yet-tested" title="Commands not yet tested" level="1"/>
+ </page>
+ <page name="toc.html" href="toc.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Table of Contents" fulltitle="Table of Contents" subtitle=""/>
+ <page name="qdoctests-qdocfileoutput.html" href="qdoctests-qdocfileoutput.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Testing QDoc output from .qdoc files" fulltitle="Testing QDoc output from .qdoc files" subtitle="" brief="This is a simple page for testing purposes only">
+ <contents name="supported-file-types" title="Supported file types" level="1"/>
+ <contents name="further-information" title="Further information" level="1"/>
+ <contents name="linking" title="Linking" level="1"/>
+ <contents name="qdoc-linking-test" title="QDoc Linking Test" level="1"/>
+ <contents name="linking-to-something-in-a-section-title" title="Linking to something in a section title" level="1"/>
+ </page>
+ <page name="qdoctests-qdocfileoutput-linking.html" href="qdoctests-qdocfileoutput-linking.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Testing QDoc's link command" fulltitle="Testing QDoc's link command" subtitle="" brief="This is a page for testing QDoc's link command">
+ <target name="link-test-target"/>
+ <keyword name="qdoc-linking-test" title="QDoc Linking Test"/>
+ <contents name="link-targets" title="Link targets" level="1"/>
+ </page>
+ <page name="crash.html" href="crash.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocfileoutput-exhaustive.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocfileoutput-exhaustive.webxml
new file mode 100644
index 000000000..05d560a7e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocfileoutput-exhaustive.webxml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="qdoctests-qdocfileoutput-exhaustive.html" href="qdoctests-qdocfileoutput-exhaustive.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Exhaustive testing of QDoc commands" fulltitle="Exhaustive testing of QDoc commands" subtitle="" brief="This page is a dumping ground for QDoc commands under test">
+ <contents name="this-is-a-section1" title="This is a section1" level="1"/>
+ <contents name="this-is-a-section2" title="This is a section2" level="2"/>
+ <contents name="this-is-a-section3" title="This is a section3" level="3"/>
+ <contents name="this-is-a-section4" title="This is a section4" level="4"/>
+ <contents name="images" title="Images" level="1"/>
+ <contents name="commands-not-yet-tested" title="Commands not yet tested" level="1"/>
+ <description>
+ <brief>This page is a dumping ground for QDoc commands under test.</brief>
+ <section id="this-is-a-section1">
+ <heading level="1">This is a section1</heading>
+ </section>
+ <section id="this-is-a-section2">
+ <heading level="2">This is a section2</heading>
+ </section>
+ <section id="this-is-a-section3">
+ <heading level="3">This is a section3</heading>
+ </section>
+ <section id="this-is-a-section4">
+ <heading level="4">This is a section4</heading>
+ </section>
+ </description>
+ </page>
+ </document>
+ <badcode>This is bad code</badcode>
+ <para>This text should have a line break riiiiight noooow.</para>
+ <para>
+ <bold>All your text belong to bold</bold> ...And this is an examble of only <bold>bold</bold> being, well, bold.</para>
+ <dots indent="4">...</dots>
+ <para>This a caption</para>
+ <legalese>
+ <para>Lorem legal ipsum</para>
+ </legalese>
+ <quote>
+ <para>This is a quotation.</para>
+ </quote>
+ <raw format="HTML"> &lt;html&gt;&lt;body&gt;This is &lt;b&gt;raw&lt;/b&gt;. Like the &lt;h1&gt;Eddie Murphy&lt;/h1&gt; movie. Just not as funny.&lt;/body&gt;&lt;/html&gt;
+ </raw>
+ <para>Look, ma! I made a sidebar!</para>
+ <table>
+ <row>
+ <item>
+ <para>Table item in a table row</para>
+ </item>
+ </row>
+ <row>
+ <item>
+ <para>Another item in a different row</para>
+ </item>
+ </row>
+ </table>
+ <para>
+ <bold>Important:</bold> This is really important.</para>
+ <para>
+ <bold>Note:</bold> The code above doesn't compile</para>
+ <section id="images">
+ <heading level="1">Images</heading>
+ <para>An image without any text:</para>
+ <image href="images/leonardo-da-vinci.png"/>
+ <para>An image with just an alternative text:</para>
+ <image href="images/leonardo-da-vinci.png"/>
+ <para>An image with alternative text and 1-atom caption:</para>
+ <image href="images/leonardo-da-vinci.png"/>
+ <para>Image caption</para>
+ <para>An image with alternative text and 2-atom caption:</para>
+ <image href="images/leonardo-da-vinci.png"/>
+ <para>Image caption with <bold>bold</bold> text</para>
+ <para>A bordered image:</para>
+ <image href="images/leonardo-da-vinci.png"/>
+ <para>A bordered image with a caption:</para>
+ <image href="images/leonardo-da-vinci.png"/>
+ <para>Screenshot of the System Tray Icon</para>
+ <para>An inline image:</para>
+ <para>The is a paragraph containing an <inlineimage href="images/01.png"/> inline image to test if qdoc handles them properly, without considering rest of the line as alt text for the image.</para>
+ <para>An inline image with alt text:</para>
+ <para>Here is another example of <inlineimage href="images/01.png"/> inline image with alternative text, which should be added as an attribute to the inline image.</para>
+ <para>File quoting:</para>
+ <quotefromfile>main.cpp</quotefromfile>
+ <skipto>/if \(/</skipto>
+ <printuntil>/^ \}/</printuntil>
+ </section>
+ <section id="commands-not-yet-tested">
+ <heading level="1">Commands not yet tested</heading>
+ <para>
+ <bold>Warning:</bold> The following commands have yet to be tested: footnote link sincelist header index topicref // or just don’t care, remove it inlineimage printline printto quotefile skipline skipuntil span snippet codeline overload sub sup tableofcontents tt uicontrol endmapref endomit underline unicode</para>
+ </section>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocfileoutput-linking.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocfileoutput-linking.webxml
new file mode 100644
index 000000000..1bb19e10e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocfileoutput-linking.webxml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="qdoctests-qdocfileoutput-linking.html" href="qdoctests-qdocfileoutput-linking.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Testing QDoc's link command" fulltitle="Testing QDoc's link command" subtitle="" brief="This is a page for testing QDoc's link command">
+ <target name="link-test-target"/>
+ <keyword name="qdoc-linking-test" title="QDoc Linking Test"/>
+ <contents name="link-targets" title="Link targets" level="1"/>
+ <description>
+ <relation href="toc.html" type="page" meta="next" description="Table of Contents"/>
+ <relation href="qdoctests-qdocfileoutput.html" type="page" meta="previous" description="Testing QDoc output from .qdoc files"/>
+ <brief>This is a page for testing QDoc's link command.</brief>
+ <target name="link-test-target"/>
+ <section id="link-targets">
+ <heading level="1">Link targets</heading>
+ <para>Valid parameters for the link command (<teletype type="highlighted">\l</teletype>) are page and section titles, targets defined with \target or \keyword commands, and API reference keywords (types, methods, namespaces, and so on).</para>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocfileoutput.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocfileoutput.webxml
new file mode 100644
index 000000000..13b3e5c51
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocfileoutput.webxml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="qdoctests-qdocfileoutput.html" href="qdoctests-qdocfileoutput.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Testing QDoc output from .qdoc files" fulltitle="Testing QDoc output from .qdoc files" subtitle="" brief="This is a simple page for testing purposes only">
+ <contents name="supported-file-types" title="Supported file types" level="1"/>
+ <contents name="further-information" title="Further information" level="1"/>
+ <contents name="linking" title="Linking" level="1"/>
+ <contents name="qdoc-linking-test" title="QDoc Linking Test" level="1"/>
+ <contents name="linking-to-something-in-a-section-title" title="Linking to something in a section title" level="1"/>
+ <description>
+ <relation href="qdoctests-qdocfileoutput-linking.html" type="page" meta="next" description="Testing QDoc's link command"/>
+ <brief>This is a simple page for testing purposes only.</brief>
+ <para>QDoc generates documentation for software projects. It does this by extracting <italic>QDoc comments</italic> from project source files. QDoc comments are signified by a C-style-like comment tag followed by an exclamation point, like this: <teletype type="highlighted">/*!</teletype> <teletype type="highlighted">This text is contained within QDoc comment tags.</teletype> <teletype type="highlighted">*/</teletype>.</para>
+ <section id="supported-file-types">
+ <heading level="1">Supported file types</heading>
+ <para>QDoc parses <teletype type="highlighted">.cpp</teletype> and <teletype type="highlighted">.qdoc</teletype> files. It does extract comments from header (<teletype type="highlighted">.h</teletype>) files.</para>
+ </section>
+ <section id="further-information">
+ <heading level="1">Further information</heading>
+ <para>This test document is written with the purpose of testing the output QDoc generates when parsing <teletype type="highlighted">.qdoc</teletype> files. It is fairly simple and makes use of a limited subset of QDoc's command. Those commands are:</para>
+ <list type="bullet">
+ <item>
+ <para>
+ <teletype type="highlighted">\page</teletype></para>
+ </item>
+ <item>
+ <para>
+ <teletype type="highlighted">\title</teletype></para>
+ </item>
+ <item>
+ <para>
+ <teletype type="highlighted">\brief</teletype></para>
+ </item>
+ <item>
+ <para>
+ <teletype type="highlighted">\e</teletype> (for emphasizing &quot;QDoc comments&quot;)</para>
+ </item>
+ <item>
+ <para>
+ <teletype type="highlighted">\c</teletype> (for multiple monospace-formatted entries)</para>
+ </item>
+ <item>
+ <para>
+ <teletype type="highlighted">\section1</teletype></para>
+ </item>
+ <item>
+ <para>
+ <teletype type="highlighted">\list</teletype></para>
+ </item>
+ <item>
+ <para>
+ <teletype type="highlighted">\li</teletype></para>
+ </item>
+ <item>
+ <para>
+ <teletype type="highlighted">\endlist</teletype></para>
+ </item>
+ </list>
+ </section>
+ <section id="linking">
+ <heading level="1">Linking</heading>
+ <para>There are multiple ways to create hyperlinks to other topics:</para>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="Testing QDoc's link command" href="qdoctests-qdocfileoutput-linking.html" type="page" page="Testing QDoc's link command">Linking to a page title</link></para>
+ </item>
+ <item>
+ <para>
+ <link raw="Link targets" href="qdoctests-qdocfileoutput-linking.html#link-targets" type="page" page="Testing QDoc's link command">Linking to a section title</link></para>
+ </item>
+ <item>
+ <para>
+ <link raw="link-test-target" href="qdoctests-qdocfileoutput-linking.html#link-test-target" type="page" page="Testing QDoc's link command">Linking using a \target string</link></para>
+ </item>
+ <item>
+ <para>
+ <link raw="QDoc Linking Test" href="qdoctests-qdocfileoutput-linking.html" type="page" page="Testing QDoc's link command">Linking using a \keyword string</link></para>
+ </item>
+ </list>
+ </section>
+ <section id="qdoc-linking-test">
+ <heading level="1">QDoc Linking Test</heading>
+ <para>This section title is overridden by another target which takes precedence.</para>
+ </section>
+ <section id="linking-to-something-in-a-section-title">
+ <heading level="1">Linking to <link raw="Further information" href="qdoctests-qdocfileoutput.html#further-information" type="page" page="Testing QDoc output from .qdoc files">something</link> in a section title</heading>
+ <para>This is allowed but a questionable practice.</para>
+ <para>
+ <bold>Note:</bold> You're looking at detailed information.</para>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocmanuallikefileoutput.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocmanuallikefileoutput.webxml
new file mode 100644
index 000000000..4502dcf7f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/qdoctests-qdocmanuallikefileoutput.webxml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="qdoctests-qdocmanuallikefileoutput.html" href="qdoctests-qdocmanuallikefileoutput.html" status="active" location="qdoctests-outputfromqdocmanuallikefiles.qdoc" documented="true" subtype="page" title="Document Navigation" fulltitle="Document Navigation" subtitle="">
+ <target name="previouspage-command"/>
+ <contents name="commands" title="Commands" level="1"/>
+ <contents name="previouspage" title="\previouspage" level="2"/>
+ <description>
+ <para>The navigation commands...</para>
+ <quote>
+ <raw format="HTML"> &lt;table border=&quot;0&quot; cellpadding=&quot;0&quot; cellspacing=&quot;5&quot; width=&quot;100%&quot;&gt;
+
+ &lt;tr&gt;
+ &lt;p&gt;
+ [Previous: &lt;a href=&quot;15-qdoc-commands-navigation.html#deadlink&quot;&gt;
+ Basic Qt&lt;/a&gt;]
+ [&lt;a href=&quot;15-qdoc-commands-navigation.html#deadlink&quot;&gt;Contents&lt;/a&gt;]
+ [Next: &lt;a href=&quot;15-qdoc-commands-navigation.html#deadlink&quot;&gt;
+ Creating Dialogs&lt;/a&gt;]
+ &lt;/p&gt;
+
+ &lt;h1 align=&quot;center&quot;&gt;Getting Started&lt;br /&gt;&lt;/h1&gt;
+
+ &lt;p&gt;
+ This chapter shows how to combine basic C++ with the
+ functionality provided by Qt to create a few small graphical
+ interface (GUI) applications.
+ &lt;/p&gt;
+
+ &lt;p&gt;
+ [Previous: &lt;a href=&quot;15-qdoc-commands-navigation.html#deadlink&quot;&gt;
+ Basic Qt&lt;/a&gt;]
+ [&lt;a href=&quot;15-qdoc-commands-navigation.html#deadlink&quot;&gt;Contents&lt;/a&gt;]
+ [Next: &lt;a href=&quot;15-qdoc-commands-navigation.html#deadlink&quot;&gt;
+ Creating Dialogs&lt;/a&gt;]
+ &lt;/p&gt;
+
+ &lt;/table&gt;
+ </raw>
+ </quote>
+ <code>&lt;head&gt;
+ ...
+ &lt;link rel=&quot;start&quot; href=&quot;basicqt.html&quot; /&gt;
+ ...
+&lt;/head&gt;</code>
+ <section id="commands">
+ <heading level="1">Commands</heading>
+ <target name="previouspage-command"/>
+ </section>
+ <section id="previouspage">
+ <heading level="2">\previouspage</heading>
+ <para>The \previouspage command...</para>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/toc.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/toc.webxml
new file mode 100644
index 000000000..bef07db18
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/expected/webxml/toc.webxml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="toc.html" href="toc.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Table of Contents" fulltitle="Table of Contents" subtitle="">
+ <description>
+ <relation href="qdoctests-qdocfileoutput-linking.html" type="page" meta="previous" description="Testing QDoc's link command"/>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="Testing QDoc output from .qdoc files" href="qdoctests-qdocfileoutput.html" type="page" page="Testing QDoc output from .qdoc files">QDoc Testing</link></para>
+ </item>
+ <item>
+ <para>
+ <link raw="QDoc Linking Test" href="qdoctests-qdocfileoutput-linking.html" type="page" page="Testing QDoc's link command">QDoc Linking Test</link></para>
+ </item>
+ <item>
+ <para>
+ <link raw="Table of Contents" href="toc.html" type="page" page="Table of Contents">Table of Contents</link></para>
+ </item>
+ </list>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/outputfromqdocfiles.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/outputfromqdocfiles.qdocconf
new file mode 100644
index 000000000..8a053de44
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/outputfromqdocfiles.qdocconf
@@ -0,0 +1,43 @@
+project = OutputFromQDocFiles
+description = "A test project for QDoc build artifacts"
+buildversion = "$project - $description"
+moduleheader =
+
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ./src/images
+
+# zero warning policy -- here; allow one which is (qdoc) warning: Can't link to ''
+warninglimit = 1
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+outputformats = HTML WebXML DocBook
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+WebXML.quotinginformation = true
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
+DocBook.nosubdirs = true
+DocBook.outputsubdir = docbook
+DocBook.usedocbookextensions = true
+
+sources = ./src/qdoctests-outputfromqdocfiles.qdoc \
+ ./src/qdoctests-outputfromqdocmanuallikefiles.qdoc
+
+exampledirs = ./src/snippets
+
+macro.beginqdoc = "\\c {/*!}"
+macro.endqdoc = "\\c */"
+macro.PROD = QDoc
+
+# Macro from qtbase/doc/global/macros.qdocconf
+# The file cannot be included directly, because it requires many
+# variables to be set, like QT_VER
+macro.borderedimage = "\\div {class=\"border\"} \\image \1\n\\enddiv"
+
+defines = test_navigation
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/images/01.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/images/01.png
new file mode 100644
index 000000000..d73ab969b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/images/01.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/images/leonardo-da-vinci.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/images/leonardo-da-vinci.png
new file mode 100644
index 000000000..854acb4ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/images/leonardo-da-vinci.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/qdoctests-outputfromqdocfiles.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/qdoctests-outputfromqdocfiles.qdoc
new file mode 100644
index 000000000..b19905b7e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/qdoctests-outputfromqdocfiles.qdoc
@@ -0,0 +1,241 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+\if defined(test_navigation)
+ \nextpage {qdoctests-qdocfileoutput-linking.html}{QDoc Linking Test}
+\endif
+
+ \page qdoctests-qdocfileoutput.html
+ \title Testing \PROD output from .qdoc files
+ \brief This is a simple page for testing purposes only.
+
+ QDoc generates documentation for software projects. It does this by
+ extracting \e {QDoc comments} from project source files. QDoc comments are
+ signified by a C-style-like comment tag followed by an exclamation point,
+ like this:
+ \beginqdoc \c {This text is contained within QDoc comment tags.} \endqdoc.
+
+ \section1 Supported file types
+ QDoc parses \c .cpp and \c .qdoc files. It does extract comments from
+ header (\c {.h}) files.
+
+ \section1 Further information
+ This test document is written with the purpose of testing the output QDoc
+ generates when parsing \c .qdoc files. It is fairly simple and makes use of
+ a limited subset of QDoc's command. Those commands are:
+ \list
+ \li \c {\page}
+ \li \c {\title}
+ \li \c {\brief}
+ \li \c {\e} (for emphasizing "QDoc comments")
+ \li \c {\c} (for multiple monospace-formatted entries)
+ \li \c {\section1}
+ \li \c {\list}
+ \li \c {\li}
+ \li \c {\endlist}
+ \endlist
+
+ \section1 Linking
+
+ There are multiple ways to create hyperlinks to other topics:
+
+ \list
+ \li \l {Testing QDoc's link command}{Linking to a page title}
+ \li \l {Link targets}{Linking to a section title}
+ \li \l {link-test-target}{Linking using a \\target string}
+ \li \l {QDoc Linking Test}{Linking using a \\keyword string}
+ \endlist
+
+ \section1 QDoc Linking Test
+
+ This section title is overridden by another target which takes
+ precedence.
+
+ \section1 Linking to \l {Further information}{something} in a section title
+
+ This is allowed but a questionable practice.
+
+ \details {\PROD details}
+ \note You're looking at detailed information.
+ \enddetails
+*/
+
+/*!
+\if defined(test_navigation)
+ \previouspage qdoctests-qdocfileoutput.html \PROD Testing
+ \nextpage Table of Contents
+\endif
+
+ \keyword QDoc Linking Test
+ \page qdoctests-qdocfileoutput-linking.html
+ \title Testing QDoc's link command
+ \brief This is a page for testing QDoc's link command.
+
+ \target link-test-target
+ \section1 Link targets
+
+ Valid parameters for the link command (\c {\l}) are page and section
+ titles, targets defined with \\target or \\keyword commands, and API
+ reference keywords (types, methods, namespaces, and so on).
+*/
+
+/*!
+\if defined(test_navigation)
+ \previouspage {Testing QDoc's link command}{QDoc Linking Test}
+\endif
+
+ \page toc.html
+ \title Table of Contents
+
+ \list
+ \li \l {Testing \PROD output from .qdoc files}{\PROD Testing}
+ \li \l {QDoc Linking Test}
+ \li \l {Table of Contents}
+ \endlist
+*/
+
+/*!
+ \page qdoctests-qdocfileoutput-exhaustive.html
+ \title Exhaustive testing of QDoc commands
+ \brief This page is a dumping ground for QDoc commands under test.
+
+ \section1 This is a section1
+ \section2 This is a section2
+ \section3 This is a section3
+ \section4 This is a section4
+ \endsection4
+ \endsection3
+ \endsection2
+ \endsection1
+
+ \badcode
+ This is bad code
+ \endcode
+
+ This text should have a line break riiiiight \br noooow.
+
+ \b{All your text belong to bold}
+ ...And this is an examble of only \b bold being, well, bold.
+
+ \dots
+
+ \caption This a caption
+
+ \legalese
+ Lorem legal ipsum
+ \endlegalese
+
+ \quotation
+ This is a quotation.
+ \endquotation
+
+ \raw HTML
+ <html><body>This is <b>raw</b>. Like the <h1>Eddie Murphy</h1> movie. Just not as funny.</body></html>
+ \endraw
+
+ \sidebar
+ Look, ma! I made a sidebar!
+ \endsidebar
+
+ \table
+ \row \li Table item in a table row
+ \row \li Another item in a different row
+ \endtable
+
+ \important This is really important.
+
+ \note The code above doesn't compile
+
+ \hr
+
+ \section1 Images
+
+ An image without any text:
+
+ \image leonardo-da-vinci.png
+
+ An image with just an alternative text:
+
+ \image leonardo-da-vinci.png Image alt
+
+ An image with alternative text and 1-atom caption:
+
+ \image leonardo-da-vinci.png Image alt
+ \caption Image caption
+
+ An image with alternative text and 2-atom caption:
+
+ \image leonardo-da-vinci.png Image alt
+ \caption Image caption with \b {bold} text
+
+ A bordered image:
+
+ \borderedimage leonardo-da-vinci.png
+
+ //! A bordered image with alternative text:
+ //!
+ //! \borderedimage leonardo-da-vinci.png Screenshot of the Drill Down Example
+ //! It looks like this macro is not written to handle alternative text (no \2)
+
+ A bordered image with a caption:
+
+ \borderedimage leonardo-da-vinci.png
+ \caption Screenshot of the System Tray Icon
+
+ An inline image:
+
+ The is a paragraph containing an \inlineimage 01.png inline image to test
+ if qdoc handles them properly, without considering rest of the line as
+ alt text for the image.
+
+ An inline image with alt text:
+
+ Here is another example of \inlineimage 01.png {No. 1} inline image with
+ alternative text, which should be added as an attribute to the inline
+ image.
+
+ File quoting:
+
+ \quotefromfile main.cpp
+ \skipto /if \(/
+ \printuntil /^ \}/
+
+ \section1 Commands not yet tested
+
+ \warning The following commands have yet to be tested:
+ footnote
+ link
+ //! Check why above two (when used in this order) cause missing linefeeds on Windows/webxml
+ sincelist
+ header
+ index
+ topicref // or just don’t care, remove it
+ inlineimage
+ printline
+ printto
+ quotefile
+ skipline
+ skipuntil
+ span
+ snippet
+ codeline
+ overload
+ sub
+ sup
+ tableofcontents
+ tt
+ uicontrol
+ endmapref
+ endomit
+ underline
+ unicode
+
+*/
+
+// Empty link target that was known to assert
+/*!
+ \page crash.html
+
+ \l {}
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/qdoctests-outputfromqdocmanuallikefiles.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/qdoctests-outputfromqdocmanuallikefiles.qdoc
new file mode 100644
index 000000000..23f229745
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/qdoctests-outputfromqdocmanuallikefiles.qdoc
@@ -0,0 +1,59 @@
+// Copyright (C) 2022 Thibaut Cuvelier
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+// Excerpts from src/qdoc/doc/qdoc-guide.qdoc
+
+/*!
+ \page qdoctests-qdocmanuallikefileoutput.html
+
+ \title Document Navigation
+
+ The navigation commands...
+
+ \quotation
+ \raw HTML
+ <table border="0" cellpadding="0" cellspacing="5" width="100%">
+
+ <tr>
+ <p>
+ [Previous: <a href="15-qdoc-commands-navigation.html#deadlink">
+ Basic Qt</a>]
+ [<a href="15-qdoc-commands-navigation.html#deadlink">Contents</a>]
+ [Next: <a href="15-qdoc-commands-navigation.html#deadlink">
+ Creating Dialogs</a>]
+ </p>
+
+ <h1 align="center">Getting Started<br /></h1>
+
+ <p>
+ This chapter shows how to combine basic C++ with the
+ functionality provided by Qt to create a few small graphical
+ interface (GUI) applications.
+ </p>
+
+ <p>
+ [Previous: <a href="15-qdoc-commands-navigation.html#deadlink">
+ Basic Qt</a>]
+ [<a href="15-qdoc-commands-navigation.html#deadlink">Contents</a>]
+ [Next: <a href="15-qdoc-commands-navigation.html#deadlink">
+ Creating Dialogs</a>]
+ </p>
+
+ </table>
+ \endraw
+ \endquotation
+
+ \code
+ <head>
+ ...
+ <link rel="start" href="basicqt.html" />
+ ...
+ </head>
+ \endcode
+
+ \section1 Commands
+
+ \target previouspage-command
+ \section2 \\previouspage
+
+ The \\previouspage command...
+*/ \ No newline at end of file
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/snippets/main.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/snippets/main.cpp
new file mode 100644
index 000000000..1c886ace4
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/outputfromqdocfiles/src/snippets/main.cpp
@@ -0,0 +1,10 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+int main()
+{
+ if (false) {
+ return 1;
+ }
+ return 0;
+}
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/expected/docbook/stdpair-proxypage-proxy.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/expected/docbook/stdpair-proxypage-proxy.xml
new file mode 100644
index 000000000..5476d7a21
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/expected/docbook/stdpair-proxypage-proxy.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>StdPair</db:title>
+<db:productname>ProxyPage</db:productname>
+<db:titleabbrev>ProxyPage Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>ProxyPage Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:section xml:id="type-documentation">
+<db:title>Type Documentation</db:title>
+<db:section xml:id="StdPair-typedef">
+<db:title>[alias] template &lt;typename T1, typename T2&gt; StdPair</db:title>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/expected/html/proxypage.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/expected/html/proxypage.index
new file mode 100644
index 000000000..96a5a3d55
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/expected/html/proxypage.index
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="ProxyPage Reference Documentation" version="" project="ProxyPage">
+ <namespace name="" status="active" access="public" module="proxypage">
+ <typedef name="StdPair" href="stdpair-proxypage-proxy.html#StdPair-typedef" status="active" access="public" location="proxy.h" related="0" documented="true" aliasedtype="std::pair&lt;T1, T2&gt;"/>
+ <proxy name="StdPair" href="stdpair-proxypage-proxy.html" status="active" access="public">
+ <typedef name="StdPair" href="stdpair-proxypage-proxy.html#StdPair-typedef" status="active" access="public" location="proxy.h" related="0" documented="true" aliasedtype="std::pair&lt;T1, T2&gt;"/>
+ </proxy>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/expected/html/stdpair-proxypage-proxy.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/expected/html/stdpair-proxypage-proxy.html
new file mode 100644
index 000000000..25241ddcb
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/expected/html/stdpair-proxypage-proxy.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>StdPair Proxy Page | ProxyPage</title>
+</head>
+<body>
+<h1 class="title">StdPair Proxy Page</h1>
+<h2 id="types">Types</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="stdpair-proxypage-proxy.html#StdPair-typedef" translate="no">StdPair</a></b></td></tr>
+</table></div>
+<div class="types">
+<h2>Type Documentation</h2>
+<!-- $$$StdPair -->
+<h3 class="fn" translate="no" id="StdPair-typedef"><code class="details extra" translate="no">[alias]</code> template &lt;typename T1, typename T2&gt; <span class="name">StdPair</span></h3>
+<!-- @@@StdPair -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/proxypage.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/proxypage.qdocconf
new file mode 100644
index 000000000..2f686a44c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/proxypage.qdocconf
@@ -0,0 +1,24 @@
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ./src/images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+project = ProxyPage
+includepaths += ./src
+{sourcedirs,headerdirs} = ./src
+
+moduleheader = proxy.h
+
+outputformats = HTML DocBook
+{HTML.nosubdirs,DocBook.nosubdirs} = true
+HTML.outputsubdir = html
+DocBook.outputsubdir = docbook
+
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/src/proxy.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/src/proxy.h
new file mode 100644
index 000000000..05a17f826
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/src/proxy.h
@@ -0,0 +1,11 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#pragma once
+
+// dummy declaration
+namespace std {
+ template<class T1, class T2> struct pair;
+}
+
+template <class T1, class T2>
+using StdPair = std::pair<T1, T2>;
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/src/proxy.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/src/proxy.qdoc
new file mode 100644
index 000000000..43bf8a013
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/proxypage/src/proxy.qdoc
@@ -0,0 +1,9 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*!
+ //! Misuse of QDoc commands that results in the generation of an orphaned
+ //! proxy page; Relate the topic command to itself.
+ \typealias StdPair
+ \relates StdPair
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/cppcar.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/cppcar.xml
new file mode 100644
index 000000000..0780ec93b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/cppcar.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>CppCar Class</db:title>
+<db:productname>QML nativetype synopsis test</db:productname>
+<db:titleabbrev>QML nativetype synopsis test Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>A test class with an inventive name.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>CppCar</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>In QML</db:term>
+<db:listitem>
+<db:para><db:link xlink:href="qml-qmlnativetypesynopsis-car.xml" xlink:role="">Car</db:link> and <db:link xlink:href="qml-qmlnativetypesynopsis-truck.xml" xlink:role="">Truck</db:link></db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/qml-qmlnativetypesynopsis-car.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/qml-qmlnativetypesynopsis-car.xml
new file mode 100644
index 000000000..196c705a4
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/qml-qmlnativetypesynopsis-car.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Car QML Type</db:title>
+<db:productname>QML nativetype synopsis test</db:productname>
+<db:titleabbrev>QML nativetype synopsis test Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>A car.</db:para>
+<db:para>This type was introduced in Qt 6.8.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import QmlNativeTypeSynopsis</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since:</db:term>
+<db:listitem>
+<db:para>Qt 6.8</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>In C++:</db:term>
+<db:listitem>
+<db:para><db:link xlink:href="cppcar.xml">CppCar</db:link></db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Group</db:term>
+<db:listitem>
+<db:para>Car is part of <db:simplelist><db:member>qml_nativetype_synopsis</db:member></db:simplelist>
+</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:section xml:id="properties">
+<db:title>Properties</db:title>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:code>color</db:code> The color of the car.</db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+<db:section xml:id="signals">
+<db:title>Signals</db:title>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:code>engineStarted()</db:code> Emitted when the engine is started.</db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+<db:section xml:id="methods">
+<db:title>Methods</db:title>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:code>startEngine()</db:code> Starts the engine.</db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/qml-qmlnativetypesynopsis-truck.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/qml-qmlnativetypesynopsis-truck.xml
new file mode 100644
index 000000000..c4203a54d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/qml-qmlnativetypesynopsis-truck.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Truck QML Type</db:title>
+<db:productname>QML nativetype synopsis test</db:productname>
+<db:titleabbrev>QML nativetype synopsis test Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>A big car.</db:para>
+<db:para>This type was introduced in Qt 6.8.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import QmlNativeTypeSynopsis</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since:</db:term>
+<db:listitem>
+<db:para>Qt 6.8</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>In C++:</db:term>
+<db:listitem>
+<db:para><db:link xlink:href="cppcar.xml">CppCar</db:link></db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Group</db:term>
+<db:listitem>
+<db:para>Truck is part of <db:simplelist><db:member>qml_nativetype_synopsis</db:member></db:simplelist>
+</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:section xml:id="properties">
+<db:title>Properties</db:title>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:code>color</db:code> The color of the big car.</db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+<db:section xml:id="signals">
+<db:title>Signals</db:title>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:code>engineStarted()</db:code> Emitted when the big engine is started, along with more emissions than from a <db:link xlink:href="qml-qmlnativetypesynopsis-car.xml">Car</db:link>.</db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+<db:section xml:id="methods">
+<db:title>Methods</db:title>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:code>startEngine()</db:code> Starts the big engine.</db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/qmlnativetypesynopsis-qmlmodule.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/qmlnativetypesynopsis-qmlmodule.xml
new file mode 100644
index 000000000..b00c11267
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/docbook/qmlnativetypesynopsis-qmlmodule.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title></db:title>
+<db:productname>QML nativetype synopsis test</db:productname>
+<db:titleabbrev>QML nativetype synopsis test Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>QML nativetype synopsis test Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:anchor xml:id="details"/>
+<db:variablelist role="members">
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-qmlnativetypesynopsis-car.xml" xlink:role="">Car</db:link></db:term>
+<db:listitem>
+<db:para>A car.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="qml-qmlnativetypesynopsis-truck.xml" xlink:role="">Truck</db:link></db:term>
+<db:listitem>
+<db:para>A big car.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/cppcar.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/cppcar.html
new file mode 100644
index 000000000..fcee3e277
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/cppcar.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- cppcar.cpp -->
+ <meta name="description" content="A test class with an inventive name.">
+ <title>CppCar Class | QML nativetype synopsis test</title>
+</head>
+<body>
+<li>CppCar</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">CppCar Class</h1>
+<!-- $$$CppCar-brief -->
+<p>A test class with an inventive name. <a href="#details">More...</a></p>
+<!-- @@@CppCar -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;CppCar&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> In QML:</td><td class="memItemRight bottomAlign"> <a href="qml-qmlnativetypesynopsis-car.html" translate="no">Car</a> and <a href="qml-qmlnativetypesynopsis-truck.html" translate="no">Truck</a></td></tr>
+</table></div>
+<!-- $$$CppCar-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@CppCar -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-nativetype-synopsis-test.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-nativetype-synopsis-test.index
new file mode 100644
index 000000000..a34218849
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-nativetype-synopsis-test.index
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="QML nativetype synopsis test Reference Documentation" version="" project="QML nativetype synopsis test">
+ <namespace name="" status="active" access="public" module="qml nativetype synopsis test">
+ <qmlclass name="Car" qml-module-name="QmlNativeTypeSynopsis" fullname="QmlNativeTypeSynopsis.Car" href="qml-qmlnativetypesynopsis-car.html" status="active" access="public" since="6.8" documented="true" groups="qml_nativetype_synopsis" title="Car" fulltitle="Car" subtitle="" brief="A car">
+ <contents name="properties" title="Properties" level="1"/>
+ <contents name="signals" title="Signals" level="1"/>
+ <contents name="methods" title="Methods" level="1"/>
+ </qmlclass>
+ <class name="CppCar" href="cppcar.html" status="active" access="public" location="cppcar.h" documented="true" module="QmlNativeTypeSynopsis" brief="A test class with an inventive name"/>
+ <qmlclass name="Truck" qml-module-name="QmlNativeTypeSynopsis" fullname="QmlNativeTypeSynopsis.Truck" href="qml-qmlnativetypesynopsis-truck.html" status="active" access="public" since="6.8" documented="true" groups="qml_nativetype_synopsis" title="Truck" fulltitle="Truck" subtitle="" brief="A big car">
+ <contents name="properties" title="Properties" level="1"/>
+ <contents name="signals" title="Signals" level="1"/>
+ <contents name="methods" title="Methods" level="1"/>
+ </qmlclass>
+ <group name="qml_nativetype_synopsis" href="qml-nativetype-synopsis.html" status="internal" seen="false" title=""/>
+ <module name="QmlNativeTypeSynopsis" href="qmlnativetypesynopsis-module.html" status="internal" seen="false" title=""/>
+ <qmlmodule name="QmlNativeTypeSynopsis" qml-module-name="QmlNativeTypeSynopsis" href="qmlnativetypesynopsis-qmlmodule.html" status="active" documented="true" seen="true" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-car-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-car-members.html
new file mode 100644
index 000000000..0b42d6968
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-car-members.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- cppcar.cpp -->
+ <meta name="description" content="A car.">
+ <title>List of All Members for Car | QML nativetype synopsis test</title>
+</head>
+<body>
+<li>Car</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for Car</h1>
+<p>This is the complete list of members for <a href="qml-qmlnativetypesynopsis-car.html">Car</a>, including inherited members.</p>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-car.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-car.html
new file mode 100644
index 000000000..5a97c514d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-car.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- cppcar.cpp -->
+ <meta name="description" content="A car.">
+ <title>Car QML Type | QML nativetype synopsis test</title>
+</head>
+<body>
+<li>Car</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#properties">Properties</a></li>
+<li class="level2"><a href="#signals">Signals</a></li>
+<li class="level2"><a href="#methods">Methods</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Car QML Type</h1>
+<!-- $$$Car-brief -->
+<p>A car. <a href="#details">More...</a></p>
+<!-- @@@Car -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import QmlNativeTypeSynopsis</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 6.8</td></tr><tr><td class="memItemLeft rightAlign topAlign"> In C++:</td><td class="memItemRight bottomAlign"> <a href="cppcar.html" translate="no">CppCar</a></td></tr></table></div><ul>
+<li><a href="qml-qmlnativetypesynopsis-car-members.html">List of all members, including inherited members</a></li>
+</ul>
+<!-- $$$Car-description -->
+<h2 id="details">Detailed Description</h2>
+<h2 id="properties">Properties</h2>
+<ul>
+<li><code translate="no">color</code> The color of the car.</li>
+</ul>
+<h2 id="signals">Signals</h2>
+<ul>
+<li><code translate="no">engineStarted()</code> Emitted when the engine is started.</li>
+</ul>
+<h2 id="methods">Methods</h2>
+<ul>
+<li><code translate="no">startEngine()</code> Starts the engine.</li>
+</ul>
+<!-- @@@Car -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-truck-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-truck-members.html
new file mode 100644
index 000000000..bd7527632
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-truck-members.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- cppcar.cpp -->
+ <meta name="description" content="A big car.">
+ <title>List of All Members for Truck | QML nativetype synopsis test</title>
+</head>
+<body>
+<li>Truck</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for Truck</h1>
+<p>This is the complete list of members for <a href="qml-qmlnativetypesynopsis-truck.html">Truck</a>, including inherited members.</p>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-truck.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-truck.html
new file mode 100644
index 000000000..35d2954e2
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qml-qmlnativetypesynopsis-truck.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- cppcar.cpp -->
+ <meta name="description" content="A big car.">
+ <title>Truck QML Type | QML nativetype synopsis test</title>
+</head>
+<body>
+<li>Truck</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#properties">Properties</a></li>
+<li class="level2"><a href="#signals">Signals</a></li>
+<li class="level2"><a href="#methods">Methods</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Truck QML Type</h1>
+<!-- $$$Truck-brief -->
+<p>A big car. <a href="#details">More...</a></p>
+<!-- @@@Truck -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import QmlNativeTypeSynopsis</td></tr><tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 6.8</td></tr><tr><td class="memItemLeft rightAlign topAlign"> In C++:</td><td class="memItemRight bottomAlign"> <a href="cppcar.html" translate="no">CppCar</a></td></tr></table></div><ul>
+<li><a href="qml-qmlnativetypesynopsis-truck-members.html">List of all members, including inherited members</a></li>
+</ul>
+<!-- $$$Truck-description -->
+<h2 id="details">Detailed Description</h2>
+<h2 id="properties">Properties</h2>
+<ul>
+<li><code translate="no">color</code> The color of the big car.</li>
+</ul>
+<h2 id="signals">Signals</h2>
+<ul>
+<li><code translate="no">engineStarted()</code> Emitted when the big engine is started, along with more emissions than from a <a href="qml-qmlnativetypesynopsis-car.html" translate="no">Car</a>.</li>
+</ul>
+<h2 id="methods">Methods</h2>
+<ul>
+<li><code translate="no">startEngine()</code> Starts the big engine.</li>
+</ul>
+<!-- @@@Truck -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qmlnativetypesynopsis-qmlmodule.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qmlnativetypesynopsis-qmlmodule.html
new file mode 100644
index 000000000..51b6bc311
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/html/qmlnativetypesynopsis-qmlmodule.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- cppcar.cpp -->
+ <title>QML nativetype synopsis test</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<!-- $$$QmlNativeTypeSynopsis-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@QmlNativeTypeSynopsis -->
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qml-qmlnativetypesynopsis-car.html">Car</a></p></td><td class="tblDescr"><p>A car</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="qml-qmlnativetypesynopsis-truck.html">Truck</a></p></td><td class="tblDescr"><p>A big car</p></td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/webxml/cppcar.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/webxml/cppcar.webxml
new file mode 100644
index 000000000..4ff09cfa1
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/webxml/cppcar.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="CppCar" href="cppcar.html" status="active" access="public" location="cppcar.h" documented="true" module="QmlNativeTypeSynopsis" brief="A test class with an inventive name">
+ <description>
+ <brief>A test class with an inventive name.</brief>
+ </description>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/webxml/qml-nativetype-synopsis-test.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/webxml/qml-nativetype-synopsis-test.index
new file mode 100644
index 000000000..a34218849
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/webxml/qml-nativetype-synopsis-test.index
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="QML nativetype synopsis test Reference Documentation" version="" project="QML nativetype synopsis test">
+ <namespace name="" status="active" access="public" module="qml nativetype synopsis test">
+ <qmlclass name="Car" qml-module-name="QmlNativeTypeSynopsis" fullname="QmlNativeTypeSynopsis.Car" href="qml-qmlnativetypesynopsis-car.html" status="active" access="public" since="6.8" documented="true" groups="qml_nativetype_synopsis" title="Car" fulltitle="Car" subtitle="" brief="A car">
+ <contents name="properties" title="Properties" level="1"/>
+ <contents name="signals" title="Signals" level="1"/>
+ <contents name="methods" title="Methods" level="1"/>
+ </qmlclass>
+ <class name="CppCar" href="cppcar.html" status="active" access="public" location="cppcar.h" documented="true" module="QmlNativeTypeSynopsis" brief="A test class with an inventive name"/>
+ <qmlclass name="Truck" qml-module-name="QmlNativeTypeSynopsis" fullname="QmlNativeTypeSynopsis.Truck" href="qml-qmlnativetypesynopsis-truck.html" status="active" access="public" since="6.8" documented="true" groups="qml_nativetype_synopsis" title="Truck" fulltitle="Truck" subtitle="" brief="A big car">
+ <contents name="properties" title="Properties" level="1"/>
+ <contents name="signals" title="Signals" level="1"/>
+ <contents name="methods" title="Methods" level="1"/>
+ </qmlclass>
+ <group name="qml_nativetype_synopsis" href="qml-nativetype-synopsis.html" status="internal" seen="false" title=""/>
+ <module name="QmlNativeTypeSynopsis" href="qmlnativetypesynopsis-module.html" status="internal" seen="false" title=""/>
+ <qmlmodule name="QmlNativeTypeSynopsis" qml-module-name="QmlNativeTypeSynopsis" href="qmlnativetypesynopsis-qmlmodule.html" status="active" documented="true" seen="true" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/webxml/qmlnativetypesynopsis-qmlmodule.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/webxml/qmlnativetypesynopsis-qmlmodule.webxml
new file mode 100644
index 000000000..5d24b3077
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/expected/webxml/qmlnativetypesynopsis-qmlmodule.webxml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document/>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/qml_nativetype_synopsis.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/qml_nativetype_synopsis.qdocconf
new file mode 100644
index 000000000..522746eb6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/qml_nativetype_synopsis.qdocconf
@@ -0,0 +1,23 @@
+project = QML nativetype synopsis test
+
+locationinfo = false
+
+headers.fileextensions = "*.h *.hpp"
+sources.fileextensions = "*.cpp *.qml *.qdoc"
+
+headerdirs = ./src
+sourcedirs = ./src
+
+warninglimit = 0
+warninglimit.enabled = true
+
+outputformats = WebXML HTML DocBook
+WebXML.quotinginformation = true
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
+
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+
+DocBook.nosubdirs = true
+DocBook.outputsubdir = docbook
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/src/cppcar.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/src/cppcar.cpp
new file mode 100644
index 000000000..451ec6010
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/src/cppcar.cpp
@@ -0,0 +1,68 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "cppcar.h"
+
+/*!
+ \qmlmodule QmlNativeTypeSynopsis
+ */
+
+/*!
+ \qmltype Car
+ \nativetype CppCar
+ \inqmlmodule QmlNativeTypeSynopsis
+ \ingroup qml_nativetype_synopsis
+ \since 6.8
+ \brief A car.
+
+ \section1 Properties
+
+ \list
+ \li \c{color} The color of the car.
+ \endlist
+
+ \section1 Signals
+
+ \list
+ \li \c{engineStarted()} Emitted when the engine is started.
+ \endlist
+
+ \section1 Methods
+
+ \list
+ \li \c{startEngine()} Starts the engine.
+ \endlist
+ */
+
+/*!
+ \class CppCar
+ \inmodule QmlNativeTypeSynopsis
+ \brief A test class with an inventive name.
+ */
+
+/*!
+ \qmltype Truck
+ \nativetype CppCar
+ \inqmlmodule QmlNativeTypeSynopsis
+ \ingroup qml_nativetype_synopsis
+ \since 6.8
+ \brief A big car.
+
+ \section1 Properties
+
+ \list
+ \li \c{color} The color of the big car.
+ \endlist
+
+ \section1 Signals
+
+ \list
+ \li \c{engineStarted()} Emitted when the big engine is started, along with more emissions than from a \l Car.
+ \endlist
+
+ \section1 Methods
+
+ \list
+ \li \c{startEngine()} Starts the big engine.
+ \endlist
+ */
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/src/cppcar.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/src/cppcar.h
new file mode 100644
index 000000000..dce5afb32
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qml_nativetype_synopsis/src/cppcar.h
@@ -0,0 +1,10 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef CAR_H
+#define CAR_H
+
+class CppCar {
+};
+
+#endif // CAR_H
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/class.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/class.xml
new file mode 100644
index 000000000..d0bf4bac1
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/class.xml
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">Class Class</db:title>
+<db:productname>QmlEnumValuesFromCpp</db:productname>
+<db:titleabbrev>QmlEnumValuesFromCpp Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>QmlEnumValuesFromCpp Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Class</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="member-type-documentation">
+<db:title>Member Type Documentation</db:title>
+<db:section xml:id="MoreValues-enum">
+<db:title its:translate="no">enum Class::MoreValues</db:title>
+<db:enumsynopsis>
+<db:enumname>MoreValues</db:enumname>
+<db:enumitem>
+<db:enumidentifier>Something</db:enumidentifier>
+<db:enumvalue>0</db:enumvalue>
+</db:enumitem>
+<db:enumitem>
+<db:enumidentifier>Else</db:enumidentifier>
+<db:enumvalue>1</db:enumvalue>
+</db:enumitem>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:enumsynopsis>
+<db:informaltable>
+<db:thead>
+<db:tr>
+<db:th>Constant</db:th>
+<db:th>Value</db:th>
+<db:th>Description</db:th>
+</db:tr>
+</db:thead>
+<db:tr>
+<db:td>
+<db:para its:translate="no"><db:code><db:emphasis role="bold"><db:link xlink:href="class.xml">Class</db:link></db:emphasis>::<db:emphasis role="bold"><db:link xlink:href="">MoreValues</db:link></db:emphasis>::Something</db:code></db:para>
+</db:td>
+<db:td><db:code its:translate="no">0</db:code></db:td>
+<db:td>
+<db:para>something</db:para>
+</db:td>
+</db:tr>
+<db:tr>
+<db:td>
+<db:para its:translate="no"><db:code><db:emphasis role="bold"><db:link xlink:href="class.xml">Class</db:link></db:emphasis>::<db:emphasis role="bold"><db:link xlink:href="">MoreValues</db:link></db:emphasis>::Else</db:code></db:para>
+</db:td>
+<db:td><db:code its:translate="no">1</db:code></db:td>
+<db:td>
+<db:para>entirely</db:para>
+</db:td>
+</db:tr>
+</db:informaltable>
+</db:section>
+<db:section xml:id="Values-enum">
+<db:title its:translate="no">enum Class::Values</db:title>
+<db:enumsynopsis>
+<db:enumname>Values</db:enumname>
+<db:enumitem>
+<db:enumidentifier>One</db:enumidentifier>
+<db:enumvalue>0</db:enumvalue>
+</db:enumitem>
+<db:enumitem>
+<db:enumidentifier>Two</db:enumidentifier>
+<db:enumvalue>1</db:enumvalue>
+</db:enumitem>
+<db:enumitem>
+<db:enumidentifier>Three</db:enumidentifier>
+<db:enumvalue>2</db:enumvalue>
+</db:enumitem>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:enumsynopsis>
+<db:para>Try now these exciting values in your C++ code:</db:para>
+<db:informaltable>
+<db:thead>
+<db:tr>
+<db:th>Constant</db:th>
+<db:th>Value</db:th>
+<db:th>Description</db:th>
+</db:tr>
+</db:thead>
+<db:tr>
+<db:td>
+<db:para its:translate="no"><db:code><db:emphasis role="bold"><db:link xlink:href="class.xml">Class</db:link></db:emphasis>::One</db:code></db:para>
+</db:td>
+<db:td><db:code its:translate="no">0</db:code></db:td>
+<db:td>
+<db:para>One value</db:para>
+</db:td>
+</db:tr>
+</db:informaltable>
+<db:para>Wait, that's not all!</db:para>
+<db:informaltable>
+<db:thead>
+<db:tr>
+<db:th>Constant</db:th>
+<db:th>Value</db:th>
+<db:th>Description</db:th>
+</db:tr>
+</db:thead>
+<db:tr>
+<db:td>
+<db:para its:translate="no"><db:code><db:emphasis role="bold"><db:link xlink:href="class.xml">Class</db:link></db:emphasis>::Two</db:code></db:para>
+</db:td>
+<db:td><db:code its:translate="no">1</db:code></db:td>
+<db:td>
+<db:para>2nd value</db:para>
+</db:td>
+</db:tr>
+<db:tr>
+<db:td>
+<db:para its:translate="no"><db:code><db:emphasis role="bold"><db:link xlink:href="class.xml">Class</db:link></db:emphasis>::Three</db:code></db:para>
+</db:td>
+<db:td><db:code its:translate="no">2</db:code></db:td>
+<db:td>
+<db:para>3rd value</db:para>
+</db:td>
+</db:tr>
+</db:informaltable>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/module-module.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/module-module.xml
new file mode 100644
index 000000000..972603249
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/module-module.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no"></db:title>
+<db:productname>QmlEnumValuesFromCpp</db:productname>
+<db:titleabbrev>QmlEnumValuesFromCpp Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>QmlEnumValuesFromCpp Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:section xml:id="classes">
+<db:title>Classes</db:title>
+<db:itemizedlist role="classes">
+<db:listitem>
+<db:para><db:link xlink:href="class.xml" xlink:role="class">Class</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/qml-qmlmodule-type.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/qml-qmlmodule-type.xml
new file mode 100644
index 000000000..e98a0ff68
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/qml-qmlmodule-type.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no">Type QML Type</db:title>
+<db:productname>QmlEnumValuesFromCpp</db:productname>
+<db:titleabbrev>QmlEnumValuesFromCpp Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>QmlEnumValuesFromCpp Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:variablelist its:translate="no">
+<db:varlistentry>
+<db:term>Import Statement</db:term>
+<db:listitem>
+<db:para>import QmlModule</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="property-documentation">
+<db:title>Property Documentation</db:title>
+<db:section xml:id="something-prop">
+<db:title>something : enumeration</db:title>
+<db:fieldsynopsis>
+<db:type>enumeration</db:type>
+<db:varname>something</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:informaltable>
+<db:thead>
+<db:tr>
+<db:th>Constant</db:th>
+<db:th>Description</db:th>
+</db:tr>
+</db:thead>
+<db:tr>
+<db:td>
+<db:para its:translate="no">SomeType.Something</db:para>
+</db:td>
+<db:td>
+<db:para>something</db:para>
+</db:td>
+</db:tr>
+<db:tr>
+<db:td>
+<db:para its:translate="no">SomeType.Else</db:para>
+</db:td>
+<db:td>
+<db:para>entirely</db:para>
+</db:td>
+</db:tr>
+</db:informaltable>
+</db:section>
+<db:section xml:id="values-prop">
+<db:title>values : enumeration</db:title>
+<db:fieldsynopsis>
+<db:type>enumeration</db:type>
+<db:varname>values</db:varname>
+<db:modifier>writable</db:modifier>
+<db:synopsisinfo role="access">public</db:synopsisinfo>
+<db:synopsisinfo role="status">active</db:synopsisinfo>
+<db:synopsisinfo role="threadsafeness">unspecified</db:synopsisinfo>
+</db:fieldsynopsis>
+<db:para>You can even use these values in QML.</db:para>
+<db:informaltable>
+<db:thead>
+<db:tr>
+<db:th>Constant</db:th>
+<db:th>Description</db:th>
+</db:tr>
+</db:thead>
+<db:tr>
+<db:td>
+<db:para its:translate="no">Type.One</db:para>
+</db:td>
+<db:td>
+<db:para>One value</db:para>
+</db:td>
+</db:tr>
+<db:tr>
+<db:td>
+<db:para its:translate="no">Type.Two</db:para>
+</db:td>
+<db:td>
+<db:para>2nd value</db:para>
+</db:td>
+</db:tr>
+<db:tr>
+<db:td>
+<db:para its:translate="no">Type.Three</db:para>
+</db:td>
+<db:td>
+<db:para>3rd value</db:para>
+</db:td>
+</db:tr>
+</db:informaltable>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/qmlmodule-qmlmodule.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/qmlmodule-qmlmodule.xml
new file mode 100644
index 000000000..12466c566
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/docbook/qmlmodule-qmlmodule.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:its="http://www.w3.org/2005/11/its" version="5.2" xml:lang="en">
+<db:info>
+<db:title its:translate="no"></db:title>
+<db:productname>QmlEnumValuesFromCpp</db:productname>
+<db:titleabbrev>QmlEnumValuesFromCpp Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>QmlEnumValuesFromCpp Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:anchor xml:id="details"/>
+<db:itemizedlist role="members">
+<db:listitem>
+<db:para><db:link xlink:href="qml-qmlmodule-type.xml" xlink:role="">Type</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/class-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/class-members.html
new file mode 100644
index 000000000..8b5efb662
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/class-members.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- class.cpp -->
+ <title>List of All Members for Class | QmlEnumValuesFromCpp</title>
+</head>
+<body>
+<li>Class</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for Class</h1>
+<p>This is the complete list of members for <a href="class.html">Class</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no">enum class <span class="name"><b><a href="class.html#MoreValues-enum" translate="no">MoreValues</a></b></span></li>
+<li class="fn" translate="no">enum <span class="name"><b><a href="class.html#Values-enum" translate="no">Values</a></b></span></li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/class.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/class.html
new file mode 100644
index 000000000..e7d4ddc3d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/class.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- class.cpp -->
+ <title>Class Class | QmlEnumValuesFromCpp</title>
+</head>
+<body>
+<li>Class</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#public-types">Public Types</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Class Class</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Class&gt;</span></td></tr>
+</table></div>
+<ul>
+<li><a href="class-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="public-types">Public Types</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> enum class </td><td class="memItemRight bottomAlign"><b><a href="class.html#MoreValues-enum" translate="no">MoreValues</a></b> { Something, Else }</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> enum </td><td class="memItemRight bottomAlign"><b><a href="class.html#Values-enum" translate="no">Values</a></b> { One, Two, Three }</td></tr>
+</table></div>
+<!-- $$$Class-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Class -->
+<div class="types">
+<h2>Member Type Documentation</h2>
+<!-- $$$MoreValues$$$Something$$$Else -->
+<h3 class="fn" translate="no" id="MoreValues-enum">enum class Class::<span class="name">MoreValues</span></h3>
+<div class="table"><table class="valuelist"><tr valign="top" class="odd"><th class="tblConst">Constant</th><th class="tblval">Value</th><th class="tbldscr">Description</th></tr>
+<tr><td class="topAlign"><code translate="no">Class::MoreValues::Something</code></td><td class="topAlign tblval"><code translate="no">0</code></td><td class="topAlign">something</td></tr>
+<tr><td class="topAlign"><code translate="no">Class::MoreValues::Else</code></td><td class="topAlign tblval"><code translate="no">1</code></td><td class="topAlign">entirely</td></tr>
+</table></div>
+<!-- @@@MoreValues -->
+<!-- $$$Values$$$One$$$Two$$$Three -->
+<h3 class="fn" translate="no" id="Values-enum">enum Class::<span class="name">Values</span></h3>
+<p>Try now these exciting values in your C++ code:</p>
+<div class="table"><table class="valuelist"><tr valign="top" class="odd"><th class="tblConst">Constant</th><th class="tblval">Value</th><th class="tbldscr">Description</th></tr>
+<tr><td class="topAlign"><code translate="no">Class::One</code></td><td class="topAlign tblval"><code translate="no">0</code></td><td class="topAlign">One value</td></tr>
+</table></div>
+<p>Wait, that's not all!</p>
+<div class="table"><table class="valuelist"><tr valign="top" class="even"><th class="tblConst">Constant</th><th class="tblval">Value</th><th class="tbldscr">Description</th></tr>
+<tr><td class="topAlign"><code translate="no">Class::Two</code></td><td class="topAlign tblval"><code translate="no">1</code></td><td class="topAlign">2nd value</td></tr>
+<tr><td class="topAlign"><code translate="no">Class::Three</code></td><td class="topAlign tblval"><code translate="no">2</code></td><td class="topAlign">3rd value</td></tr>
+</table></div>
+<!-- @@@Values -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/module-module.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/module-module.html
new file mode 100644
index 000000000..8bcf61f6b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/module-module.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- class.cpp -->
+ <title>QmlEnumValuesFromCpp</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#namespaces">Namespaces</a></li>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="class.html">Class</a></p></td></tr>
+</table></div>
+<!-- $$$Module-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@Module -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qml-qmlmodule-type-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qml-qmlmodule-type-members.html
new file mode 100644
index 000000000..3270d7158
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qml-qmlmodule-type-members.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- qmltype.qdoc -->
+ <title>List of All Members for Type | QmlEnumValuesFromCpp</title>
+</head>
+<body>
+<li>Type</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for Type</h1>
+<p>This is the complete list of members for <a href="qml-qmlmodule-type.html">Type</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qmlmodule-type.html#something-prop" translate="no">something</a></b> : enumeration</li>
+<li class="fn" translate="no"><b><a href="qml-qmlmodule-type.html#values-prop" translate="no">values</a></b> : enumeration</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qml-qmlmodule-type.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qml-qmlmodule-type.html
new file mode 100644
index 000000000..509dd2431
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qml-qmlmodule-type.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- qmltype.qdoc -->
+ <title>Type QML Type | QmlEnumValuesFromCpp</title>
+</head>
+<body>
+<li>Type</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#properties">Properties</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Type QML Type</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Import Statement:</td><td class="memItemRight bottomAlign"> import QmlModule</td></tr></table></div><ul>
+<li><a href="qml-qmlmodule-type-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="properties">Properties</h2>
+<ul>
+<li class="fn" translate="no"><b><a href="qml-qmlmodule-type.html#something-prop" translate="no">something</a></b> : enumeration</li>
+<li class="fn" translate="no"><b><a href="qml-qmlmodule-type.html#values-prop" translate="no">values</a></b> : enumeration</li>
+</ul>
+<!-- $$$Type-description -->
+<h2 id="details">Detailed Description</h2>
+<!-- @@@Type -->
+<h2>Property Documentation</h2>
+<!-- $$$something -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="something-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">something</span> : <span class="type">enumeration</span></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><div class="table"><table class="valuelist"><tr valign="top" class="odd"><th class="tblConst">Constant</th><th class="tbldscr">Description</th></tr>
+<tr><td class="topAlign"><code translate="no">SomeType.Something</code></td><td class="topAlign">something</td></tr>
+<tr><td class="topAlign"><code translate="no">SomeType.Else</code></td><td class="topAlign">entirely</td></tr>
+</table></div>
+</div></div><!-- @@@something -->
+<br/>
+<!-- $$$values -->
+<div class="qmlitem"><div class="qmlproto" translate="no">
+<div class="table"><table class="qmlname">
+<tr valign="top" class="odd" id="values-prop">
+<td class="tblQmlPropNode"><p>
+<span class="name">values</span> : <span class="type">enumeration</span></p></td></tr>
+</table></div></div>
+<div class="qmldoc"><p>You can even use these values in QML.</p>
+<div class="table"><table class="valuelist"><tr valign="top" class="odd"><th class="tblConst">Constant</th><th class="tbldscr">Description</th></tr>
+<tr><td class="topAlign"><code translate="no">Type.One</code></td><td class="topAlign">One value</td></tr>
+<tr><td class="topAlign"><code translate="no">Type.Two</code></td><td class="topAlign">2nd value</td></tr>
+<tr><td class="topAlign"><code translate="no">Type.Three</code></td><td class="topAlign">3rd value</td></tr>
+</table></div>
+</div></div><!-- @@@values -->
+<br/>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qmlenumvaluesfromcpp.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qmlenumvaluesfromcpp.index
new file mode 100644
index 000000000..3d9e172a1
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qmlenumvaluesfromcpp.index
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="QmlEnumValuesFromCpp Reference Documentation" version="" project="QmlEnumValuesFromCpp">
+ <namespace name="" status="active" access="public" module="qmlenumvaluesfromcpp">
+ <class name="Class" href="class.html" status="active" access="public" location="class.h" documented="true" module="Module">
+ <enum name="MoreValues" fullname="Class::MoreValues" href="class.html#MoreValues-enum" status="active" access="public" location="class.h" documented="true" scoped="true">
+ <value name="Something" value="0"/>
+ <value name="Else" value="1"/>
+ </enum>
+ <enum name="Values" fullname="Class::Values" href="class.html#Values-enum" status="active" access="public" location="class.h" documented="true">
+ <value name="One" value="0"/>
+ <value name="Two" value="1"/>
+ <value name="Three" value="2"/>
+ </enum>
+ </class>
+ <qmlclass name="Type" qml-module-name="QmlModule" fullname="QmlModule.Type" href="qml-qmlmodule-type.html" status="active" access="public" location="qmltype.qdoc" documented="true" title="Type" fulltitle="Type" subtitle="">
+ <qmlproperty name="something" fullname="QmlModule.Type.something" href="qml-qmlmodule-type.html#something-prop" status="active" access="public" location="qmltype.qdoc" documented="true" type="enumeration" attached="false" writable="true"/>
+ <qmlproperty name="values" fullname="QmlModule.Type.values" href="qml-qmlmodule-type.html#values-prop" status="active" access="public" location="qmltype.qdoc" documented="true" type="enumeration" attached="false" writable="true"/>
+ </qmlclass>
+ <module name="Module" href="module-module.html" status="active" documented="true" seen="true" title=""/>
+ <qmlmodule name="QmlModule" qml-module-name="QmlModule" href="qmlmodule-qmlmodule.html" status="active" location="qmltype.qdoc" documented="true" seen="true" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qmlmodule-qmlmodule.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qmlmodule-qmlmodule.html
new file mode 100644
index 000000000..9cbff451f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/html/qmlmodule-qmlmodule.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- qmltype.qdoc -->
+ <title>QmlEnumValuesFromCpp</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<!-- $$$QmlModule-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@QmlModule -->
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="qml-qmlmodule-type.html">Type</a></p></td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/class.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/class.webxml
new file mode 100644
index 000000000..d6b062fa7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/class.webxml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="Class" href="class.html" status="active" access="public" location="class.h" documented="true" module="Module">
+ <description/>
+ <enum name="MoreValues" fullname="Class::MoreValues" href="class.html#MoreValues-enum" status="active" access="public" location="class.h" documented="true" scoped="true">
+ <value name="Something" value="0"/>
+ <value name="Else" value="1"/>
+ <description>
+ <list type="enum">
+ <definition>
+ <term>Class::MoreValues::Something</term>Something</definition>
+ <item>
+ <para>something</para>
+ </item>
+ <definition>
+ <term>Class::MoreValues::Else</term>Else</definition>
+ <item>
+ <para>entirely</para>
+ </item>
+ </list>
+ </description>
+ </enum>
+ <enum name="Values" fullname="Class::Values" href="class.html#Values-enum" status="active" access="public" location="class.h" documented="true">
+ <value name="One" value="0"/>
+ <value name="Two" value="1"/>
+ <value name="Three" value="2"/>
+ <description>
+ <para>Try now these exciting values in your C++ code:</para>
+ <list type="enum">
+ <definition>
+ <term>Class::One</term>One</definition>
+ <item>
+ <para>One value</para>
+ </item>
+ </list>
+ <para>Wait, that's not all!</para>
+ <list type="enum">
+ <definition>
+ <term>Class::Two</term>Two</definition>
+ <item>
+ <para>2nd value</para>
+ </item>
+ <definition>
+ <term>Class::Three</term>Three</definition>
+ <item>
+ <para>3rd value</para>
+ </item>
+ </list>
+ </description>
+ </enum>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/module-module.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/module-module.webxml
new file mode 100644
index 000000000..5d24b3077
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/module-module.webxml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document/>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/qmlenumvaluesfromcpp.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/qmlenumvaluesfromcpp.index
new file mode 100644
index 000000000..3d9e172a1
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/qmlenumvaluesfromcpp.index
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="QmlEnumValuesFromCpp Reference Documentation" version="" project="QmlEnumValuesFromCpp">
+ <namespace name="" status="active" access="public" module="qmlenumvaluesfromcpp">
+ <class name="Class" href="class.html" status="active" access="public" location="class.h" documented="true" module="Module">
+ <enum name="MoreValues" fullname="Class::MoreValues" href="class.html#MoreValues-enum" status="active" access="public" location="class.h" documented="true" scoped="true">
+ <value name="Something" value="0"/>
+ <value name="Else" value="1"/>
+ </enum>
+ <enum name="Values" fullname="Class::Values" href="class.html#Values-enum" status="active" access="public" location="class.h" documented="true">
+ <value name="One" value="0"/>
+ <value name="Two" value="1"/>
+ <value name="Three" value="2"/>
+ </enum>
+ </class>
+ <qmlclass name="Type" qml-module-name="QmlModule" fullname="QmlModule.Type" href="qml-qmlmodule-type.html" status="active" access="public" location="qmltype.qdoc" documented="true" title="Type" fulltitle="Type" subtitle="">
+ <qmlproperty name="something" fullname="QmlModule.Type.something" href="qml-qmlmodule-type.html#something-prop" status="active" access="public" location="qmltype.qdoc" documented="true" type="enumeration" attached="false" writable="true"/>
+ <qmlproperty name="values" fullname="QmlModule.Type.values" href="qml-qmlmodule-type.html#values-prop" status="active" access="public" location="qmltype.qdoc" documented="true" type="enumeration" attached="false" writable="true"/>
+ </qmlclass>
+ <module name="Module" href="module-module.html" status="active" documented="true" seen="true" title=""/>
+ <qmlmodule name="QmlModule" qml-module-name="QmlModule" href="qmlmodule-qmlmodule.html" status="active" location="qmltype.qdoc" documented="true" seen="true" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/qmlmodule-qmlmodule.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/qmlmodule-qmlmodule.webxml
new file mode 100644
index 000000000..5d24b3077
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/expected/webxml/qmlmodule-qmlmodule.webxml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document/>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/qmlenumvaluesfromcpp.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/qmlenumvaluesfromcpp.qdocconf
new file mode 100644
index 000000000..218005310
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/qmlenumvaluesfromcpp.qdocconf
@@ -0,0 +1,22 @@
+project = QmlEnumValuesFromCpp
+moduleheader = class.h
+includepaths = ./src
+
+{sourcedirs,headerdirs} = ./src
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+outputformats = HTML WebXML DocBook
+{HTML.nosubdirs,WebXML.nosubdirs,DocBook.nosubdirs} = true
+
+HTML.outputsubdir = html
+WebXML.outputsubdir = webxml
+DocBook.outputsubdir = docbook
+
+DocBook.its = true
+DocBook.usedocbookextensions = true
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/src/class.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/src/class.cpp
new file mode 100644
index 000000000..fa3cc1e31
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/src/class.cpp
@@ -0,0 +1,36 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "class.h"
+
+/*!
+ \module Module
+*/
+
+/*!
+ \class Class
+ \inmodule Module
+*/
+
+/*!
+ \enum Class::Values
+
+ Try now these exciting values in your C++ code:
+
+ \value One
+ One value
+
+ Wait, that's not all!
+
+ \value Two
+ 2nd value
+ \value Three
+ 3rd value
+*/
+
+/*!
+ \enum Class::MoreValues
+
+ \value Something something
+ \value Else entirely
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/src/class.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/src/class.h
new file mode 100644
index 000000000..8c4967a69
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/src/class.h
@@ -0,0 +1,10 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#pragma once
+
+class Class {
+
+public:
+ enum Values { One, Two, Three };
+ enum class MoreValues { Something, Else };
+};
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/src/qmltype.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/src/qmltype.qdoc
new file mode 100644
index 000000000..3cd739c29
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/qmlenumvaluesfromcpp/src/qmltype.qdoc
@@ -0,0 +1,23 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*!
+ \qmlmodule QmlModule
+*/
+
+/*!
+ \qmltype Type
+ \inqmlmodule QmlModule
+*/
+
+/*!
+ \qmlproperty enumeration Type::values
+ \qmlenumeratorsfrom Class::Values
+
+ You can even use these values in QML.
+*/
+
+/*!
+ \qmlproperty enumeration Type::something
+ \qmlenumeratorsfrom [SomeType] Class::MoreValues
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/a.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/a.cpp
new file mode 100644
index 000000000..c2dd411d7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/a.cpp
@@ -0,0 +1,11 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "a.h"
+
+/*!
+ \typealias foo
+ \relates <Bar>
+
+ Related to a header file whose source might be parsed later.
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/a.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/a.h
new file mode 100644
index 000000000..40c4add88
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/a.h
@@ -0,0 +1,5 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#pragma once
+
+using foo = int;
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/b.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/b.cpp
new file mode 100644
index 000000000..7e3df2f24
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/b.cpp
@@ -0,0 +1,12 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*!
+ \module Module
+*/
+
+/*!
+ \headerfile <Bar>
+ \title A header
+ \inmodule Module
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/bar.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/bar.html
new file mode 100644
index 000000000..b8dc9412a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/bar.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- b.cpp -->
+ <title>&lt;Bar&gt; - A header | RelatesOrdering</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">&lt;Bar&gt; - A header</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Bar&gt;</span></td></tr>
+</table></div>
+<h2 id="types">Types</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="bar.html#foo-typedef" translate="no">foo</a></b></td></tr>
+</table></div>
+<!-- $$$<Bar>-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@<Bar> -->
+<div class="types">
+<h2>Type Documentation</h2>
+<!-- $$$foo -->
+<h3 class="fn" translate="no" id="foo-typedef"><code class="details extra" translate="no">[alias]</code> <span class="name">foo</span></h3>
+<p>Related to a header file whose source might be parsed later.</p>
+<!-- @@@foo -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/module-module.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/module-module.html
new file mode 100644
index 000000000..3f123509c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/module-module.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- b.cpp -->
+ <title>RelatesOrdering</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<!-- $$$Module-description -->
+<div class="descr" id="details">
+</div>
+<!-- @@@Module -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/relatesordering.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/relatesordering.index
new file mode 100644
index 000000000..905c657aa
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/relatesordering.index
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="RelatesOrdering Reference Documentation" version="" project="RelatesOrdering">
+ <namespace name="" status="active" access="public" module="relatesordering">
+ <header name="&lt;Bar&gt;" href="bar.html" status="active" documented="true" module="Module" title="A header" fulltitle="&lt;Bar&gt; - A header" subtitle="">
+ <typedef name="foo" href="bar.html#foo-typedef" status="active" access="public" location="a.h" related="0" documented="true" aliasedtype="int"/>
+ </header>
+ <typedef name="foo" href="bar.html#foo-typedef" status="active" access="public" location="a.h" related="0" documented="true" aliasedtype="int"/>
+ <module name="Module" href="module-module.html" status="active" documented="true" seen="true" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/relatesordering.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/relatesordering.qdocconf
new file mode 100644
index 000000000..b943729a3
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/relatesordering.qdocconf
@@ -0,0 +1,7 @@
+project = RelatesOrdering
+
+{sourcedirs,headerdirs} = .
+
+locationinfo = false
+warninglimit = 0
+warninglimit.enabled = true
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/autolinking.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/autolinking.xml
new file mode 100644
index 000000000..b976dd6fe
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/autolinking.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Autolinking</db:title>
+<db:productname>TestCPP</db:productname>
+<db:titleabbrev>TestCPP Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>TestCPP Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:section xml:id="testqdoc">
+<db:title>TestQDoc</db:title>
+<db:para>The string <db:link xlink:href="testqdoc.xml">TestQDoc</db:link> links to the C++ namespace unless linking explicitly, <db:link xlink:href="autolinking.xml#testqdoc">like this</db:link>, or <db:link xlink:href="testqdoc.xml">this</db:link>. Also,</db:para>
+<db:para>Autolinks:</db:para>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-testderived.xml">TestQDoc::TestDerived</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+<db:para>Explicit links:</db:para>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-testderived.xml">TestQDoc::TestDerived</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="obsolete-classes.xml#testqdoc">Obsolete Classes#TestQDoc</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+<db:section xml:id="someprop">
+<db:title>someProp</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/cpptypes.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/cpptypes.xml
new file mode 100644
index 000000000..4c4d39790
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/cpptypes.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Test C++ Types</db:title>
+<db:productname>TestCPP</db:productname>
+<db:titleabbrev>TestCPP Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>TestCPP Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:anchor xml:id="details"/>
+<db:itemizedlist role="testgroup">
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml" xlink:role="class">TestQDoc::Test</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#QDOCTEST_MACRO2" xlink:role="function">TestQDoc::Test::QDOCTEST_MACRO2</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#someFunctionDefaultArg" xlink:role="function">TestQDoc::Test::someFunctionDefaultArg()</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+<db:variablelist role="members">
+<db:varlistentry>
+<db:term><db:link xlink:href="testqdoc-test.xml" xlink:role="class">TestQDoc::Test</db:link></db:term>
+<db:listitem>
+<db:para>A class in a namespace.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/crossmoduleref.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/crossmoduleref.xml
new file mode 100644
index 000000000..6a16d52f6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/crossmoduleref.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>CrossModuleRef Namespace</db:title>
+<db:productname>TestCPP</db:productname>
+<db:titleabbrev>TestCPP Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>Namespace that has documented functions in multiple modules.</db:para>
+<db:para>This namespace was introduced in Qt 3.0.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>CrossModuleRef</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 3.0</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="function-documentation">
+<db:title>Function Documentation</db:title>
+<db:section xml:id="documentMe">
+<db:title>void CrossModuleRef::documentMe()</db:title>
+<db:para>Document me!</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/obsolete-classes.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/obsolete-classes.xml
new file mode 100644
index 000000000..62928d5f8
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/obsolete-classes.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Obsolete Classes</db:title>
+<db:productname>TestCPP</db:productname>
+<db:titleabbrev>TestCPP Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>TestCPP Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:section xml:id="classes-with-obsolete-members">
+<db:title>Classes with obsolete members</db:title>
+<db:variablelist role="obsoletecppmembers">
+<db:varlistentry>
+<db:term><db:emphasis role="bold">T</db:emphasis></db:term>
+<db:listitem>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml" role="class">Test</db:link> (<db:link xlink:href="testqdoc.xml" xlink:role="namespace">TestQDoc</db:link>)</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-testderived.xml" role="class">TestDerived</db:link> (<db:link xlink:href="testqdoc.xml" xlink:role="namespace">TestQDoc</db:link>)</db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="testqdoc">
+<db:title>TestQDoc</db:title>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/scoped-enum-linking.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/scoped-enum-linking.xml
new file mode 100644
index 000000000..f5c4a05cd
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/scoped-enum-linking.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Enum Linking</db:title>
+<db:productname>TestCPP</db:productname>
+<db:titleabbrev>TestCPP Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>TestCPP Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:para>Linking to <db:link xlink:href="testqdoc-test.xml#ScopedEnum-enum">All</db:link>.</db:para>
+<db:para>TestQDoc::Test::ClassicEnum::Howdy does not link, but <db:link xlink:href="testqdoc-test.xml#ClassicEnum-enum">TestQDoc::Test::Howdy</db:link> might.</db:para>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testcpp-module.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testcpp-module.xml
new file mode 100644
index 000000000..14eddeb8d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testcpp-module.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>QDoc Test C++ Classes</db:title>
+<db:productname>TestCPP</db:productname>
+<db:titleabbrev>TestCPP Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>A test module page.</db:para>
+<db:para>This module was introduced in Qt 2.0.</db:para>
+</db:abstract>
+</db:info>
+<db:para>A test module page.</db:para>
+<db:para>This module was introduced in Qt 2.0.</db:para>
+<db:section xml:id="namespaces">
+<db:title>Namespaces</db:title>
+<db:variablelist role="namespaces">
+<db:varlistentry>
+<db:term><db:link xlink:href="crossmoduleref.xml" xlink:role="namespace">CrossModuleRef</db:link></db:term>
+<db:listitem>
+<db:para>Namespace that has documented functions in multiple modules.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="testqdoc.xml" xlink:role="namespace">TestQDoc</db:link></db:term>
+<db:listitem>
+<db:para>A namespace.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:section>
+<db:section xml:id="classes">
+<db:title>Classes</db:title>
+<db:variablelist role="classes">
+<db:varlistentry>
+<db:term><db:link xlink:href="testqdoc-test.xml" xlink:role="class">TestQDoc::Test</db:link></db:term>
+<db:listitem>
+<db:para>A class in a namespace.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="testqdoc-testderived.xml" xlink:role="class">TestQDoc::TestDerived</db:link></db:term>
+<db:listitem>
+<db:para>A class in a namespace, derived from Test.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:section>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:note>
+<db:para>This is just a test. /* Look, Ma! {I'm made of arguments!} */</db:para>
+</db:note>
+<db:section xml:id="linking-to-function-like-things">
+<db:title>Linking to function-like things</db:title>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#someFunctionDefaultArg">someFunctionDefaultArg</db:link>()</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#QDOCTEST_MACRO2">QDOCTEST_MACRO2</db:link>()</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#QDOCTEST_MACRO2">QDOCTEST_MACRO2</db:link>(int &amp;x)</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testcpp-module.xml#section">section()</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testcpp-module.xml#section">section() is a section title</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+<db:section xml:id="section">
+<db:title>section()</db:title>
+</db:section>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testqdoc-test.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testqdoc-test.xml
new file mode 100644
index 000000000..1603be377
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testqdoc-test.xml
@@ -0,0 +1,253 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Test Class</db:title>
+<db:subtitle>TestQDoc::Test</db:subtitle>
+<db:productname>TestCPP</db:productname>
+<db:titleabbrev>TestCPP Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>A class in a namespace.</db:para>
+<db:para>This class was introduced in Qt 2.0.</db:para>
+<db:note>
+<db:para>All functions in this class are <db:link xlink:href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</db:link> with the following exceptions:</db:para>
+<db:para>These functions are not <db:link xlink:href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</db:link>:</db:para>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#someFunctionDefaultArg">someFunctionDefaultArg(int i, bool b) const</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:note>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Test</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 2.0</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Inherited By</db:term>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-testderived.xml" xlink:role="class">TestQDoc::TestDerived</db:link></db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Group</db:term>
+<db:listitem>
+<db:para>Test is part of <db:simplelist><db:member>testgroup</db:member><db:member><db:link xlink:href="cpptypes.xml">Test C++ Types</db:link></db:member></db:simplelist>
+</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="member-type-documentation">
+<db:title>Member Type Documentation</db:title>
+<db:section xml:id="ClassicEnum-enum">
+<db:title>enum Test::ClassicEnum</db:title>
+<db:informaltable>
+<db:thead>
+<db:tr>
+<db:th>Constant</db:th>
+</db:tr>
+</db:thead>
+<db:tr>
+<db:td>
+<db:para><db:code><db:emphasis role="bold"><db:link xlink:href="testqdoc.xml">TestQDoc</db:link></db:emphasis>::<db:emphasis role="bold"><db:link xlink:href="testqdoc-test.xml">Test</db:link></db:emphasis>::Yee</db:code></db:para>
+</db:td>
+<db:td><db:code>0</db:code></db:td>
+</db:tr>
+<db:tr>
+<db:td>
+<db:para><db:code><db:emphasis role="bold"><db:link xlink:href="testqdoc.xml">TestQDoc</db:link></db:emphasis>::<db:emphasis role="bold"><db:link xlink:href="testqdoc-test.xml">Test</db:link></db:emphasis>::Haw</db:code></db:para>
+</db:td>
+<db:td><db:code>1</db:code></db:td>
+</db:tr>
+<db:tr>
+<db:td>
+<db:para><db:code><db:emphasis role="bold"><db:link xlink:href="testqdoc.xml">TestQDoc</db:link></db:emphasis>::<db:emphasis role="bold"><db:link xlink:href="testqdoc-test.xml">Test</db:link></db:emphasis>::Howdy</db:code></db:para>
+</db:td>
+<db:td><db:code>2</db:code></db:td>
+</db:tr>
+<db:tr>
+<db:td>
+<db:para><db:code><db:emphasis role="bold"><db:link xlink:href="testqdoc.xml">TestQDoc</db:link></db:emphasis>::<db:emphasis role="bold"><db:link xlink:href="testqdoc-test.xml">Test</db:link></db:emphasis>::Partner</db:code></db:para>
+</db:td>
+<db:td><db:code>3</db:code></db:td>
+</db:tr>
+</db:informaltable>
+</db:section>
+<db:section xml:id="ScopedEnum-enum">
+<db:title>enum Test::ScopedEnum</db:title>
+<db:para>This enum has a brief to trigger a bug in CMD_BRIEF.</db:para>
+<db:informaltable>
+<db:thead>
+<db:tr>
+<db:th>Constant</db:th>
+<db:th>Value</db:th>
+<db:th>Description</db:th>
+</db:tr>
+</db:thead>
+<db:tr>
+<db:td>
+<db:para><db:code><db:emphasis role="bold"><db:link xlink:href="testqdoc.xml">TestQDoc</db:link></db:emphasis>::<db:emphasis role="bold"><db:link xlink:href="testqdoc-test.xml">Test</db:link></db:emphasis>::<db:emphasis role="bold"><db:link xlink:href="">ScopedEnum</db:link></db:emphasis>::This</db:code></db:para>
+</db:td>
+<db:td><db:code>0x01</db:code></db:td>
+<db:td>
+<db:para>Something</db:para>
+</db:td>
+</db:tr>
+<db:tr>
+<db:td>
+<db:para><db:code><db:emphasis role="bold"><db:link xlink:href="testqdoc.xml">TestQDoc</db:link></db:emphasis>::<db:emphasis role="bold"><db:link xlink:href="testqdoc-test.xml">Test</db:link></db:emphasis>::<db:emphasis role="bold"><db:link xlink:href="">ScopedEnum</db:link></db:emphasis>::That</db:code></db:para>
+</db:td>
+<db:td><db:code>0x02</db:code></db:td>
+<db:td>
+<db:para>Something else</db:para>
+</db:td>
+</db:tr>
+<db:tr>
+<db:td>
+<db:para><db:code><db:emphasis role="bold"><db:link xlink:href="testqdoc.xml">TestQDoc</db:link></db:emphasis>::<db:emphasis role="bold"><db:link xlink:href="testqdoc-test.xml">Test</db:link></db:emphasis>::<db:emphasis role="bold"><db:link xlink:href="">ScopedEnum</db:link></db:emphasis>::All (since Qt 2.0)</db:code></db:para>
+</db:td>
+<db:td><db:code>This | That</db:code></db:td>
+<db:td>
+<db:para>Everything</db:para>
+</db:td>
+</db:tr>
+</db:informaltable>
+<db:para>A scoped enum.</db:para>
+</db:section>
+<db:section xml:id="SomeType-typedef">
+<db:title>Test::SomeType</db:title>
+<db:para>A typedef.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="member-function-documentation">
+<db:title>Member Function Documentation</db:title>
+<db:section xml:id="overload">
+<db:title>[protected] void Test::overload()</db:title>
+<db:bridgehead renderas="sect2" xml:id="overload-1">[protected, since Test 1.2] void Test::overload(bool <db:emphasis>b</db:emphasis>)</db:bridgehead>
+<db:para>Overloads that share a documentation comment, optionally taking a parameter <db:code role="parameter">b</db:code>.</db:para>
+</db:section>
+<db:section xml:id="funcPtr">
+<db:title>void (*)(bool) Test::funcPtr(bool <db:emphasis>b</db:emphasis>, const char *<db:emphasis>s</db:emphasis>)</db:title>
+<db:para>Returns a pointer to a function that takes a boolean. Uses <db:code role="parameter">b</db:code> and <db:code role="parameter">s</db:code>.</db:para>
+</db:section>
+<db:section xml:id="inlineFunction">
+<db:title>void Test::inlineFunction()</db:title>
+<db:para>An inline function, documented using the \fn QDoc command.</db:para>
+</db:section>
+<db:section xml:id="methodWithEmDashInItsDocs">
+<db:title>void Test::methodWithEmDashInItsDocs()</db:title>
+<db:para>This method has em dashes in its documentation—as you'll find represented by <db:code>---</db:code> in the sources—here and there. The important bit to note is that when passed e.g. to the \c command, the three hyphens are processed as input to the command and not replaced by an em dash.</db:para>
+<db:para>-----------------------------------------------------------------------</db:para>
+<db:para>People can still add a bunch of dashes, though, without QDoc replacing them all with a series of em dashes.</db:para>
+<db:para>—You can also start a new paragraph with an em dash, if you want to.</db:para>
+<db:section>
+<db:title>See Also</db:title>
+<db:para><db:emphasis>See also </db:emphasis>
+<db:simplelist type="vert" role="see-also">
+<db:member><db:link xlink:href="testqdoc-test.xml#methodWithEnDashInItsDocs">methodWithEnDashInItsDocs</db:link></db:member>
+</db:simplelist>
+</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="methodWithEnDashInItsDocs">
+<db:title>void Test::methodWithEnDashInItsDocs()</db:title>
+<db:para>This method has en dashes in its documentation – as you'll find represented by <db:code>--</db:code> in the sources – here and there. The important bit to note is that when passed e.g. to the \c command, the two hyphens are processed as input to the command and not replaced by an en dash. This also applies to code blocks, where otherwise, the decrement operator would get completely borked:</db:para>
+<db:programlisting language="cpp">for (int i = 42; i &amp;gt; 0; --i)
+ // Do something cool during countdown.
+</db:programlisting>
+<db:para>...as it would be silly if this would output –i instead of <db:code>--i</db:code>.</db:para>
+<db:para>-----------------------------------------------------------------------</db:para>
+<db:para>It still allows people to add a bunch of dashes, though, without replacing them all with a series of en dashes. Of course, they might want to use the \hr command instead, like this:</db:para>
+<db:para>– You can also start a new paragraph with an en dash, if you want to.</db:para>
+<db:section>
+<db:title>See Also</db:title>
+<db:para><db:emphasis>See also </db:emphasis>
+<db:simplelist type="vert" role="see-also">
+<db:member><db:link xlink:href="">methodWithEnDashInItsDocs</db:link></db:member>
+</db:simplelist>
+</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="someFunction">
+<db:title>int Test::someFunction(<db:emphasis>int</db:emphasis>, int <db:emphasis>v</db:emphasis> = 0)</db:title>
+<db:para>Function that takes a parameter <db:code role="parameter">v</db:code>. Also returns the value of <db:code role="parameter">v</db:code>.</db:para>
+</db:section>
+<db:section xml:id="someFunctionDefaultArg">
+<db:title>void Test::someFunctionDefaultArg(int <db:emphasis>i</db:emphasis>, bool <db:emphasis>b</db:emphasis> = false) const</db:title>
+<db:para>Function that takes a parameter <db:code role="parameter">i</db:code> and <db:code role="parameter">b</db:code>.</db:para>
+<db:warning>
+<db:para>This function is not <db:link xlink:href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</db:link>.</db:para>
+</db:warning></db:section>
+<db:section xml:id="virtualFun">
+<db:title>[virtual] void Test::virtualFun()</db:title>
+<db:para>Function that must be reimplemented.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="related-non-members">
+<db:title>Related Non-Members</db:title>
+<db:section xml:id="operator-eq-eq">
+<db:title>bool operator==(const TestQDoc::Test &amp;<db:emphasis>lhs</db:emphasis>, const TestQDoc::Test &amp;<db:emphasis>rhs</db:emphasis>)</db:title>
+<db:para>Returns true if <db:code role="parameter">lhs</db:code> and <db:code role="parameter">rhs</db:code> are equal.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="macro-documentation">
+<db:title>Macro Documentation</db:title>
+<db:section xml:id="QDOCTEST_MACRO2">
+<db:title>[since Test 1.1] QDOCTEST_MACRO2(int &amp;<db:emphasis>x</db:emphasis>)</db:title>
+<db:para>A macro with argument <db:code role="parameter">x</db:code>.</db:para>
+<db:para>This macro was introduced in Test 1.1.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="obsolete">
+<db:title>Obsolete Members for Test</db:title>
+<db:para><db:emphasis role="bold">The following members of class <db:link xlink:href="testqdoc-test.xml">Test</db:link> are deprecated.</db:emphasis> We strongly advise against using them in new code.</db:para>
+<db:section xml:id="obsolete-member-function-documentation">
+<db:title>Obsolete Member Function Documentation</db:title>
+<db:section xml:id="operator-2b-2b">
+<db:title>[deprecated] TestQDoc::Test &amp;Test::operator++()</db:title>
+<db:bridgehead renderas="sect2" xml:id="operator--">[deprecated] TestQDoc::Test &amp;Test::operator--()</db:bridgehead>
+<db:para>This function is deprecated. We strongly advise against using it in new code.</db:para>
+</db:section>
+<db:section xml:id="anotherObsoleteMember">
+<db:title>[deprecated] void Test::anotherObsoleteMember()</db:title>
+<db:para>This function is deprecated. We strongly advise against using it in new code.</db:para>
+<db:para>Use <db:link xlink:href="testqdoc-test.xml#obsoleteMember">obsoleteMember</db:link>() instead.</db:para>
+</db:section>
+<db:section xml:id="deprecatedMember">
+<db:title>[deprecated in 6.0] void Test::deprecatedMember()</db:title>
+<db:para>This function is deprecated since 6.0. We strongly advise against using it in new code.</db:para>
+<db:para>Use <db:link xlink:href="testqdoc-test.xml#someFunction">someFunction</db:link>() instead.</db:para>
+</db:section>
+<db:section xml:id="obsoleteMember">
+<db:title>[deprecated] void Test::obsoleteMember()</db:title>
+<db:para>This function is deprecated. We strongly advise against using it in new code.</db:para>
+<db:para>Use <db:link xlink:href="testqdoc-test.xml#someFunction">someFunction</db:link>() instead.</db:para>
+</db:section>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testqdoc-testderived.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testqdoc-testderived.xml
new file mode 100644
index 000000000..a54ecaf6f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testqdoc-testderived.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>TestDerived Class</db:title>
+<db:subtitle>TestQDoc::TestDerived</db:subtitle>
+<db:productname>TestCPP</db:productname>
+<db:titleabbrev>TestCPP Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>A class in a namespace, derived from <db:link xlink:href="testqdoc-test.xml">Test</db:link>.</db:para>
+<db:para>This class was introduced in Qt 2.0.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>TestDerived</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 2.0</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Inherits</db:term>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml" xlink:role="class">TestQDoc::Test</db:link></db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="member-type-documentation">
+<db:title>Member Type Documentation</db:title>
+<db:section xml:id="DerivedType-typedef">
+<db:title>[alias] TestDerived::DerivedType</db:title>
+<db:para>An aliased typedef.</db:para>
+</db:section>
+<db:section xml:id="NotTypedef-typedef">
+<db:title>[alias] TestDerived::NotTypedef</db:title>
+<db:para>I'm an alias, not a typedef.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="member-function-documentation">
+<db:title>Member Function Documentation</db:title>
+<db:section xml:id="someValue">
+<db:title>TestQDoc::TestDerived::NotTypedef TestDerived::someValue()</db:title>
+<db:para>Returns a value using an aliases type.</db:para>
+</db:section>
+<db:section xml:id="virtualFun">
+<db:title>[override virtual] void TestDerived::virtualFun()</db:title>
+<db:para>Reimplements: <db:link xlink:href="testqdoc-test.xml#virtualFun" role="function">Test::virtualFun()</db:link>.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="obsolete">
+<db:title>Obsolete Members for TestDerived</db:title>
+<db:para><db:emphasis role="bold">The following members of class <db:link xlink:href="testqdoc-testderived.xml">TestDerived</db:link> are deprecated.</db:emphasis> We strongly advise against using them in new code.</db:para>
+<db:section xml:id="obsolete-member-function-documentation">
+<db:title>Obsolete Member Function Documentation</db:title>
+<db:section xml:id="staticObsoleteMember">
+<db:title>[static, deprecated] void TestDerived::staticObsoleteMember()</db:title>
+<db:para>This function is deprecated. We strongly advise against using it in new code.</db:para>
+<db:para>Static obsolete method.</db:para>
+</db:section>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testqdoc.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testqdoc.xml
new file mode 100644
index 000000000..99a2422ee
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/testqdoc.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>TestQDoc Namespace</db:title>
+<db:productname>TestCPP</db:productname>
+<db:titleabbrev>TestCPP Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>A namespace.</db:para>
+<db:para>This namespace was introduced in Qt 2.0.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>TestCPP</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 2.0</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:section xml:id="usage">
+<db:title>Usage</db:title>
+<db:para>This namespace is for testing QDoc output.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="classes">
+<db:title>Classes</db:title>
+<db:section>
+<db:title>class <db:link xlink:href="testqdoc-test.xml" xlink:role="class">Test</db:link></db:title>
+<db:para>A class in a namespace.</db:para>
+</db:section>
+<db:section>
+<db:title>class <db:link xlink:href="testqdoc-testderived.xml" xlink:role="class">TestDerived</db:link></db:title>
+<db:para>A class in a namespace, derived from <db:link xlink:href="testqdoc-test.xml">Test</db:link>.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="macro-documentation">
+<db:title>Macro Documentation</db:title>
+<db:section xml:id="QDOCTEST_MACRO">
+<db:title>QDOCTEST_MACRO</db:title>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/whatsnew.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/whatsnew.xml
new file mode 100644
index 000000000..b87618d59
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/docbook/whatsnew.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>New Classes and Functions</db:title>
+<db:productname>TestCPP</db:productname>
+<db:titleabbrev>TestCPP Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>New classes documentation.</db:para>
+</db:abstract>
+</db:info>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/autolinking.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/autolinking.html
new file mode 100644
index 000000000..ac04c33cd
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/autolinking.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classlists.qdoc -->
+ <title>Autolinking | TestCPP</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#testqdoc">TestQDoc</a></li>
+<li class="level1"><a href="#someprop">someProp</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Autolinking</h1>
+<!-- $$$autolinking.html-description -->
+<div class="descr" id="details">
+<h2 id="testqdoc">TestQDoc</h2>
+<p>The string <a href="testqdoc.html" translate="no">TestQDoc</a> links to the C++ namespace unless linking explicitly, <a href="autolinking.html#testqdoc">like this</a>, or <a href="testqdoc.html" translate="no">this</a>. Also,</p>
+<p>Autolinks:</p>
+<ul>
+<li><a href="testqdoc-testderived.html" translate="no">TestQDoc::TestDerived</a></li>
+</ul>
+<p>Explicit links:</p>
+<ul>
+<li><a href="testqdoc-testderived.html" translate="no">TestQDoc::TestDerived</a></li>
+<li><a href="obsolete-classes.html#testqdoc">Obsolete Classes#TestQDoc</a></li>
+</ul>
+<h2 id="someprop">someProp</h2>
+</div>
+<!-- @@@autolinking.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/cpptypes.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/cpptypes.html
new file mode 100644
index 000000000..633ca939b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/cpptypes.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classlists.qdoc -->
+ <title>Test C++ Types | TestCPP</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Test C++ Types</h1>
+<!-- $$$cpptypes-description -->
+<div class="descr" id="details">
+<ul>
+<li translate="no"><a href="testqdoc-test.html">TestQDoc::Test</a></li>
+<li translate="no"><a href="testqdoc-test.html#QDOCTEST_MACRO2">TestQDoc::Test::QDOCTEST_MACRO2</a></li>
+<li translate="no"><a href="testqdoc-test.html#someFunctionDefaultArg">TestQDoc::Test::someFunctionDefaultArg()</a></li>
+</ul>
+</div>
+<!-- @@@cpptypes -->
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html">TestQDoc::Test</a></p></td><td class="tblDescr"><p>A class in a namespace</p></td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/crossmoduleref.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/crossmoduleref.html
new file mode 100644
index 000000000..2e938d336
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/crossmoduleref.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="Namespace that has documented functions in multiple modules.">
+ <title>CrossModuleRef Namespace | TestCPP</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#functions">Functions</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">CrossModuleRef Namespace</h1>
+<!-- $$$CrossModuleRef-brief -->
+<p>Namespace that has documented functions in multiple modules. <a href="#details">More...</a></p>
+<!-- @@@CrossModuleRef -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;CrossModuleRef&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 3.0</td></tr>
+</table></div>
+<h2 id="functions">Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="crossmoduleref.html#documentMe" translate="no">documentMe</a></b>()</td></tr>
+</table></div>
+<!-- $$$CrossModuleRef-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@CrossModuleRef -->
+<div class="func">
+<h2>Function Documentation</h2>
+<!-- $$$documentMe[overload1]$$$documentMe -->
+<h3 class="fn" translate="no" id="documentMe"><span class="type">void</span> CrossModuleRef::<span class="name">documentMe</span>()</h3>
+<p>Document me!</p>
+<!-- @@@documentMe -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/obsolete-classes.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/obsolete-classes.html
new file mode 100644
index 000000000..01198c82d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/obsolete-classes.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classlists.qdoc -->
+ <title>Obsolete Classes | TestCPP</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#classes-with-obsolete-members">Classes with obsolete members</a></li>
+<li class="level2"><a href="#testqdoc">TestQDoc</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Obsolete Classes</h1>
+<!-- $$$obsolete-classes.html-description -->
+<div class="descr" id="details">
+<h2 id="classes-with-obsolete-members">Classes with obsolete members</h2>
+<div class="flowListDiv" translate="no">
+<dl class="flowList odd"><dt class="alphaChar"><b>T</b></dt>
+<dd><a href="testqdoc-test-obsolete.html">Test</a> (<a href="testqdoc.html">TestQDoc</a>)</dd>
+<dd><a href="testqdoc-testderived-obsolete.html">TestDerived</a> (<a href="testqdoc.html">TestQDoc</a>)</dd>
+</dl>
+</div>
+<h3 id="testqdoc">TestQDoc</h3>
+</div>
+<!-- @@@obsolete-classes.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/scoped-enum-linking.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/scoped-enum-linking.html
new file mode 100644
index 000000000..c6229dac2
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/scoped-enum-linking.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- scopedenum.qdoc -->
+ <title>Enum Linking | TestCPP</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Enum Linking</h1>
+<!-- $$$scoped-enum-linking.html-description -->
+<div class="descr" id="details">
+<p>Linking to <a href="testqdoc-test.html#ScopedEnum-enum" translate="no">All</a>.</p>
+<p>TestQDoc::Test::ClassicEnum::Howdy does not link, but <a href="testqdoc-test.html#ClassicEnum-enum" translate="no">TestQDoc::Test::Howdy</a> might.</p>
+</div>
+<!-- @@@scoped-enum-linking.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testcpp-module.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testcpp-module.html
new file mode 100644
index 000000000..56f7f5a08
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testcpp-module.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A test module page.">
+ <title>QDoc Test C++ Classes | TestCPP</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#namespaces">Namespaces</a></li>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#linking-to-function-like-things">Linking to function-like things</a></li>
+<li class="level3"><a href="#section">section()</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">QDoc Test C++ Classes</h1>
+<!-- $$$TestCPP-brief -->
+<p>A test module page. <a href="#details">More...</a></p>
+<!-- @@@TestCPP -->
+<p>This module was introduced in Qt 2.0.</p>
+<h2 id="namespaces">Namespaces</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="crossmoduleref.html">CrossModuleRef</a></p></td><td class="tblDescr"><p>Namespace that has documented functions in multiple modules</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc.html">TestQDoc</a></p></td><td class="tblDescr"><p>A namespace</p></td></tr>
+</table></div>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html">TestQDoc::Test</a></p></td><td class="tblDescr"><p>A class in a namespace</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-testderived.html">TestQDoc::TestDerived</a></p></td><td class="tblDescr"><p>A class in a namespace, derived from Test</p></td></tr>
+</table></div>
+<!-- $$$TestCPP-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<div class="admonition note">
+<p><b>Note: </b>This is just a test. /* Look, Ma! {I'm made of arguments!} */</p>
+</div>
+<h3 id="linking-to-function-like-things">Linking to function-like things</h3>
+<ul>
+<li><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a>()</li>
+<li><a href="testqdoc-test.html#QDOCTEST_MACRO2" translate="no">QDOCTEST_MACRO2</a>()</li>
+<li><a href="testqdoc-test.html#QDOCTEST_MACRO2" translate="no">QDOCTEST_MACRO2</a>(int &amp;x)</li>
+<li><a href="testcpp-module.html#section" translate="no">section()</a></li>
+<li><a href="testcpp-module.html#section" translate="no">section() is a section title</a></li>
+</ul>
+<h4 id="section">section()</h4>
+</div>
+<!-- @@@TestCPP -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testcpp.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testcpp.index
new file mode 100644
index 000000000..0ea9571f0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testcpp.index
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="TestCPP Reference Documentation" version="" project="TestCPP">
+ <namespace name="" status="active" access="public" module="testcpp">
+ <function name="QDOCTEST_MACRO" href="testqdoc.html#QDOCTEST_MACRO" status="active" access="public" documented="true" related="0" meta="macrowithoutparams" signature="QDOCTEST_MACRO"/>
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ </function>
+ <page name="autolinking.html" href="autolinking.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Autolinking" fulltitle="Autolinking" subtitle="">
+ <contents name="testqdoc" title="TestQDoc" level="1"/>
+ <contents name="someprop" title="someProp" level="1"/>
+ </page>
+ <namespace name="CrossModuleRef" href="crossmoduleref.html" status="active" access="public" location="testcpp.h" since="3.0" documented="true" module="TestCPP" brief="Namespace that has documented functions in multiple modules">
+ <function name="documentMe" fullname="CrossModuleRef::documentMe" href="crossmoduleref.html#documentMe" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void documentMe()"/>
+ </namespace>
+ <class name="DontLinkToMe" href="dontlinktome.html" status="ignored" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="Class that does not generate documentation"/>
+ <page name="scoped-enum-linking.html" href="scoped-enum-linking.html" status="active" location="scopedenum.qdoc" documented="true" subtype="page" title="Enum Linking" fulltitle="Enum Linking" subtitle=""/>
+ <page name="whatsnew.html" href="whatsnew.html" status="active" location="scopedenum.qdoc" documented="true" subtype="page" title="New Classes and Functions" fulltitle="New Classes and Functions" subtitle="" brief="New classes documentation"/>
+ <page name="obsolete-classes.html" href="obsolete-classes.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Obsolete Classes" fulltitle="Obsolete Classes" subtitle="">
+ <contents name="classes-with-obsolete-members" title="Classes with obsolete members" level="1"/>
+ <contents name="testqdoc" title="TestQDoc" level="2"/>
+ </page>
+ <namespace name="TestQDoc" href="testqdoc.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="A namespace">
+ <contents name="usage" title="Usage" level="1"/>
+ <function name="QDOCTEST_MACRO" href="testqdoc.html#QDOCTEST_MACRO" status="active" access="public" documented="true" related="0" meta="macrowithoutparams" signature="QDOCTEST_MACRO"/>
+ <class threadsafety="reentrant" name="Test" fullname="TestQDoc::Test" href="testqdoc-test.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" groups="cpptypes,testgroup" module="TestCPP" brief="A class in a namespace">
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ </function>
+ <function name="anotherObsoleteMember" fullname="TestQDoc::Test::anotherObsoleteMember" href="testqdoc-test-obsolete.html#anotherObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void anotherObsoleteMember()"/>
+ <function name="deprecatedMember" fullname="TestQDoc::Test::deprecatedMember" href="testqdoc-test-obsolete.html#deprecatedMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void deprecatedMember()"/>
+ <function name="funcPtr" fullname="TestQDoc::Test::funcPtr" href="testqdoc-test.html#funcPtr" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void (*)(bool)" signature="void (*)(bool) funcPtr(bool b, const char *s)">
+ <parameter type="bool" name="b" default=""/>
+ <parameter type="const char *" name="s" default=""/>
+ </function>
+ <function name="inlineFunction" fullname="TestQDoc::Test::inlineFunction" href="testqdoc-test.html#inlineFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" brief="An inline function, documented using the \fn QDoc command" signature="void inlineFunction()"/>
+ <function name="methodWithEmDashInItsDocs" fullname="TestQDoc::Test::methodWithEmDashInItsDocs" href="testqdoc-test.html#methodWithEmDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEmDashInItsDocs()"/>
+ <function name="methodWithEnDashInItsDocs" fullname="TestQDoc::Test::methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEnDashInItsDocs()"/>
+ <function name="obsoleteMember" fullname="TestQDoc::Test::obsoleteMember" href="testqdoc-test-obsolete.html#obsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void obsoleteMember()"/>
+ <function name="operator++" fullname="TestQDoc::Test::operator++" href="testqdoc-test-obsolete.html#operator-2b-2b" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator++()"/>
+ <function name="operator--" fullname="TestQDoc::Test::operator--" href="testqdoc-test-obsolete.html#operator--" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator--()"/>
+ <function name="operator==" href="testqdoc-test.html#operator-eq-eq" status="active" access="public" location="testcpp.h" documented="true" related="2" meta="plain" type="bool" signature="bool operator==(const TestQDoc::Test &amp;lhs, const TestQDoc::Test &amp;rhs)">
+ <parameter type="const TestQDoc::Test &amp;" name="lhs" default=""/>
+ <parameter type="const TestQDoc::Test &amp;" name="rhs" default=""/>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload" status="active" access="protected" location="testcpp.h" documented="true" meta="plain" type="void" signature="void overload()"/>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload-1" status="active" access="protected" location="testcpp.h" documented="true" since="Test 1.2" meta="plain" overload="true" overload-number="1" type="void" signature="void overload(bool b)">
+ <parameter type="bool" name="b" default=""/>
+ </function>
+ <function name="someFunction" fullname="TestQDoc::Test::someFunction" href="testqdoc-test.html#someFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="int" signature="int someFunction(int, int v)">
+ <parameter type="int" name="" default=""/>
+ <parameter type="int" name="v" default="0"/>
+ </function>
+ <function name="someFunctionDefaultArg" fullname="TestQDoc::Test::someFunctionDefaultArg" href="testqdoc-test.html#someFunctionDefaultArg" threadsafety="non-reentrant" status="active" access="public" location="testcpp.h" documented="true" meta="plain" const="true" type="void" signature="void someFunctionDefaultArg(int i, bool b) const" groups="testgroup">
+ <parameter type="int" name="i" default=""/>
+ <parameter type="bool" name="b" default="false"/>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::Test::virtualFun" href="testqdoc-test.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" type="void" signature="void virtualFun()"/>
+ <enum name="ClassicEnum" fullname="TestQDoc::Test::ClassicEnum" href="testqdoc-test.html#ClassicEnum-enum" status="active" access="public" location="testcpp.h" documented="true">
+ <value name="Yee" value="0"/>
+ <value name="Haw" value="1"/>
+ <value name="Howdy" value="2"/>
+ <value name="Partner" value="3"/>
+ </enum>
+ <enum name="ScopedEnum" fullname="TestQDoc::Test::ScopedEnum" href="testqdoc-test.html#ScopedEnum-enum" status="active" access="public" location="testcpp.h" documented="true" scoped="true">
+ <value name="This" value="0x01"/>
+ <value name="That" value="0x02"/>
+ <value name="All" value="This | That" since="2.0"/>
+ <value name="OmittedValue" value="99"/>
+ <value name="UselessValue" value="100"/>
+ <value name="VeryLastValue" value="101"/>
+ </enum>
+ <typedef name="SomeType" fullname="TestQDoc::Test::SomeType" href="testqdoc-test.html#SomeType-typedef" status="active" access="public" location="testcpp.h" documented="true"/>
+ </class>
+ <class name="TestDerived" fullname="TestQDoc::TestDerived" href="testqdoc-testderived.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" bases="TestQDoc::Test" module="TestCPP" brief="A class in a namespace, derived from Test">
+ <function name="someValue" fullname="TestQDoc::TestDerived::someValue" href="testqdoc-testderived.html#someValue" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::TestDerived::NotTypedef" signature="TestQDoc::TestDerived::NotTypedef someValue()"/>
+ <function name="staticObsoleteMember" fullname="TestQDoc::TestDerived::staticObsoleteMember" href="testqdoc-testderived-obsolete.html#staticObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" static="true" type="void" signature="void staticObsoleteMember()"/>
+ <function name="virtualFun" fullname="TestQDoc::TestDerived::virtualFun" href="testqdoc-testderived.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" override="true" type="void" signature="void virtualFun() override"/>
+ <typedef name="DerivedType" fullname="TestQDoc::TestDerived::DerivedType" href="testqdoc-testderived.html#DerivedType-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="Test::SomeType"/>
+ <typedef name="NotTypedef" fullname="TestQDoc::TestDerived::NotTypedef" href="testqdoc-testderived.html#NotTypedef-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="int"/>
+ </class>
+ </namespace>
+ <page name="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command" href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command" status="active" location="classlists.qdoc" documented="true" subtype="externalpage" title="reentrant" fulltitle="reentrant" subtitle=""/>
+ <group name="cpptypes" href="cpptypes.html" status="active" location="classlists.qdoc" documented="true" seen="true" title="Test C++ Types"/>
+ <group name="testgroup" href="testgroup.html" status="internal" seen="false" title=""/>
+ <module name="TestCPP" href="testcpp-module.html" status="active" since="2.0" documented="true" seen="true" title="QDoc Test C++ Classes" brief="A test module page">
+ <contents name="linking-to-function-like-things" title="Linking to function-like things" level="1"/>
+ <contents name="section" title="section()" level="2"/>
+ </module>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-test-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-test-members.html
new file mode 100644
index 000000000..8f065c3b0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-test-members.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace.">
+ <title>List of All Members for Test | TestCPP</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Test</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for Test</h1>
+<p>This is the complete list of members for <a href="testqdoc-test.html">TestQDoc::Test</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no">enum <span class="name"><b><a href="testqdoc-test.html#ClassicEnum-enum" translate="no">ClassicEnum</a></b></span></li>
+<li class="fn" translate="no">enum class <span class="name"><b><a href="testqdoc-test.html#ScopedEnum-enum" translate="no">ScopedEnum</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#SomeType-typedef" translate="no">SomeType</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#funcPtr" translate="no">funcPtr</a></b></span>(bool, const char *) : void (*)(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#inlineFunction" translate="no">inlineFunction</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEmDashInItsDocs" translate="no">methodWithEmDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload" translate="no">overload</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload-1" translate="no">overload</a></b></span>(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a></b></span>(int, int) : int</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a></b></span>(int, bool) const</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#virtualFun" translate="no">virtualFun</a></b></span>()</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-test-obsolete.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-test-obsolete.html
new file mode 100644
index 000000000..2b5192da1
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-test-obsolete.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace.">
+ <title>Obsolete Members for Test | TestCPP</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Test</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Obsolete Members for Test</h1>
+<p><b>The following members of class <a href="testqdoc-test.html" translate="no">Test</a> are deprecated.</b> They are provided to keep old source code working. We strongly advise against using them in new code.</p>
+<h2>Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#anotherObsoleteMember" translate="no">anotherObsoleteMember</a></b>()</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated in 6.0)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#deprecatedMember" translate="no">deprecatedMember</a></b>()</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#obsoleteMember" translate="no">obsoleteMember</a></b>()</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> TestQDoc::Test &amp;</td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#operator-2b-2b" translate="no">operator++</a></b>()</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> TestQDoc::Test &amp;</td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#operator--" translate="no">operator--</a></b>()</td></tr>
+</table></div>
+<h2>Member Function Documentation</h2>
+<!-- $$$ -->
+<div class="fngroup">
+<h3 class="fn fngroupitem" translate="no" id="operator-2b-2b"><code class="details extra" translate="no">[deprecated]</code> <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;Test::<span class="name">operator++</span>()</h3><h3 class="fn fngroupitem" translate="no" id="operator--"><code class="details extra" translate="no">[deprecated]</code> <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;Test::<span class="name">operator--</span>()</h3></div>
+<p>This function is deprecated. We strongly advise against using it in new code.</p>
+<!-- @@@ -->
+<!-- $$$anotherObsoleteMember[overload1]$$$anotherObsoleteMember -->
+<h3 class="fn" translate="no" id="anotherObsoleteMember"><code class="details extra" translate="no">[deprecated]</code> <span class="type">void</span> Test::<span class="name">anotherObsoleteMember</span>()</h3>
+<p>This function is deprecated. We strongly advise against using it in new code.</p>
+<p>Use <a href="testqdoc-test-obsolete.html#obsoleteMember" translate="no">obsoleteMember</a>() instead.</p>
+<!-- @@@anotherObsoleteMember -->
+<!-- $$$deprecatedMember[overload1]$$$deprecatedMember -->
+<h3 class="fn" translate="no" id="deprecatedMember"><code class="details extra" translate="no">[deprecated in 6.0]</code> <span class="type">void</span> Test::<span class="name">deprecatedMember</span>()</h3>
+<p>This function is deprecated since 6.0. We strongly advise against using it in new code.</p>
+<p>Use <a href="testqdoc-test.html#someFunction" translate="no">someFunction</a>() instead.</p>
+<!-- @@@deprecatedMember -->
+<!-- $$$obsoleteMember[overload1]$$$obsoleteMember -->
+<h3 class="fn" translate="no" id="obsoleteMember"><code class="details extra" translate="no">[deprecated]</code> <span class="type">void</span> Test::<span class="name">obsoleteMember</span>()</h3>
+<p>This function is deprecated. We strongly advise against using it in new code.</p>
+<p>Use <a href="testqdoc-test.html#someFunction" translate="no">someFunction</a>() instead.</p>
+<!-- @@@obsoleteMember -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-test.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-test.html
new file mode 100644
index 000000000..b89711932
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-test.html
@@ -0,0 +1,173 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace.">
+ <title>Test Class | TestCPP</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Test</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#public-types">Public Types</a></li>
+<li class="level1"><a href="#public-functions">Public Functions</a></li>
+<li class="level1"><a href="#protected-functions">Protected Functions</a></li>
+<li class="level1"><a href="#related-non-members">Related Non-Members</a></li>
+<li class="level1"><a href="#macros">Macros</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Test Class</h1>
+<span class="small-subtitle" translate="no">class <a href="testqdoc.html" translate="no">TestQDoc</a>::Test</span>
+<!-- $$$Test-brief -->
+<p>A class in a namespace. <a href="#details">More...</a></p>
+<!-- @@@Test -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Test&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Inherited By:</td><td class="memItemRight bottomAlign"> <p><a href="testqdoc-testderived.html" translate="no">TestQDoc::TestDerived</a></p>
+</td></tr>
+</table></div>
+<ul>
+<li><a href="testqdoc-test-members.html">List of all members, including inherited members</a></li>
+<li><a href="testqdoc-test-obsolete.html">Deprecated members</a></li>
+<li>Test is part of <a href="cpptypes.html">Test C++ Types</a>.</li>
+</ul>
+<p><b>Note:</b> All functions in this class are <a href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</a> with the following exceptions:</p>
+<ul>
+<li><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a>(int i, bool b) const</li>
+</ul>
+<h2 id="public-types">Public Types</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> enum </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#ClassicEnum-enum" translate="no">ClassicEnum</a></b> { Yee, Haw, Howdy, Partner }</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> enum class </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#ScopedEnum-enum" translate="no">ScopedEnum</a></b> { This, That, All }</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#SomeType-typedef" translate="no">SomeType</a></b></td></tr>
+</table></div>
+<h2 id="public-functions">Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void (*)(bool) </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#funcPtr" translate="no">funcPtr</a></b>(bool <i>b</i>, const char *<i>s</i>)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#inlineFunction" translate="no">inlineFunction</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#methodWithEmDashInItsDocs" translate="no">methodWithEmDashInItsDocs</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> int </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a></b>(int, int <i>v</i> = 0)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a></b>(int <i>i</i>, bool <i>b</i> = false) const</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> virtual void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#virtualFun" translate="no">virtualFun</a></b>()</td></tr>
+</table></div>
+<h2 id="protected-functions">Protected Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#overload" translate="no">overload</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since Test 1.2)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#overload-1" translate="no">overload</a></b>(bool <i>b</i>)</td></tr>
+</table></div>
+<h2 id="related-non-members">Related Non-Members</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> bool </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#operator-eq-eq" translate="no">operator==</a></b>(const TestQDoc::Test &amp;<i>lhs</i>, const TestQDoc::Test &amp;<i>rhs</i>)</td></tr>
+</table></div>
+<h2 id="macros">Macros</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since Test 1.1)</code> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#QDOCTEST_MACRO2" translate="no">QDOCTEST_MACRO2</a></b>(int &amp;<i>x</i>)</td></tr>
+</table></div>
+<!-- $$$Test-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Test -->
+<div class="types">
+<h2>Member Type Documentation</h2>
+<!-- $$$ClassicEnum$$$Yee$$$Haw$$$Howdy$$$Partner -->
+<h3 class="fn" translate="no" id="ClassicEnum-enum">enum Test::<span class="name">ClassicEnum</span></h3>
+<div class="table"><table class="valuelist"><tr><th class="tblConst">Constant</th><th class="tblVal">Value</th></tr>
+<tr><td class="topAlign"><code translate="no">TestQDoc::Test::Yee</code></td><td class="topAlign tblval"><code translate="no">0</code></td></tr>
+<tr><td class="topAlign"><code translate="no">TestQDoc::Test::Haw</code></td><td class="topAlign tblval"><code translate="no">1</code></td></tr>
+<tr><td class="topAlign"><code translate="no">TestQDoc::Test::Howdy</code></td><td class="topAlign tblval"><code translate="no">2</code></td></tr>
+<tr><td class="topAlign"><code translate="no">TestQDoc::Test::Partner</code></td><td class="topAlign tblval"><code translate="no">3</code></td></tr>
+</table></div>
+<!-- @@@ClassicEnum -->
+<!-- $$$ScopedEnum$$$This$$$That$$$All$$$OmittedValue$$$UselessValue$$$VeryLastValue -->
+<h3 class="fn" translate="no" id="ScopedEnum-enum">enum class Test::<span class="name">ScopedEnum</span></h3>
+<p>This enum has a brief to trigger a bug in CMD_BRIEF.</p>
+<div class="table"><table class="valuelist"><tr valign="top" class="odd"><th class="tblConst">Constant</th><th class="tblval">Value</th><th class="tbldscr">Description</th></tr>
+<tr><td class="topAlign"><code translate="no">TestQDoc::Test::ScopedEnum::This</code></td><td class="topAlign tblval"><code translate="no">0x01</code></td><td class="topAlign">Something</td></tr>
+<tr><td class="topAlign"><code translate="no">TestQDoc::Test::ScopedEnum::That</code></td><td class="topAlign tblval"><code translate="no">0x02</code></td><td class="topAlign">Something else</td></tr>
+<tr><td class="topAlign"><code translate="no">TestQDoc::Test::ScopedEnum::All (since Qt 2.0)</code></td><td class="topAlign tblval"><code translate="no">This | That</code></td><td class="topAlign">Everything</td></tr>
+</table></div>
+<p>A scoped enum.</p>
+<!-- @@@ScopedEnum -->
+<!-- $$$SomeType -->
+<h3 class="fn" translate="no" id="SomeType-typedef">Test::<span class="name">SomeType</span></h3>
+<p>A typedef.</p>
+<!-- @@@SomeType -->
+</div>
+<div class="func">
+<h2>Member Function Documentation</h2>
+<!-- $$$ -->
+<div class="fngroup">
+<h3 class="fn fngroupitem" translate="no" id="overload"><code class="details extra" translate="no">[protected]</code> <span class="type">void</span> Test::<span class="name">overload</span>()</h3><h3 class="fn fngroupitem" translate="no" id="overload-1"><code class="details extra" translate="no">[protected, since Test 1.2]</code> <span class="type">void</span> Test::<span class="name">overload</span>(<span class="type">bool</span> <i>b</i>)</h3></div>
+<p>Overloads that share a documentation comment, optionally taking a parameter <i translate="no">b</i>.</p>
+<!-- @@@ -->
+<!-- $$$funcPtr[overload1]$$$funcPtrboolconstchar* -->
+<h3 class="fn" translate="no" id="funcPtr"><span class="type">void</span> (*)(<span class="type">bool</span>) Test::<span class="name">funcPtr</span>(<span class="type">bool</span> <i>b</i>, const <span class="type">char</span> *<i>s</i>)</h3>
+<p>Returns a pointer to a function that takes a boolean. Uses <i translate="no">b</i> and <i translate="no">s</i>.</p>
+<!-- @@@funcPtr -->
+<!-- $$$inlineFunction[overload1]$$$inlineFunction -->
+<h3 class="fn" translate="no" id="inlineFunction"><span class="type">void</span> Test::<span class="name">inlineFunction</span>()</h3>
+<p>An inline function, documented using the \fn QDoc command.</p>
+<!-- @@@inlineFunction -->
+<!-- $$$methodWithEmDashInItsDocs[overload1]$$$methodWithEmDashInItsDocs -->
+<h3 class="fn" translate="no" id="methodWithEmDashInItsDocs"><span class="type">void</span> Test::<span class="name">methodWithEmDashInItsDocs</span>()</h3>
+<p>This method has em dashes in its documentation&mdash;as you'll find represented by <code translate="no">---</code> in the sources&mdash;here and there. The important bit to note is that when passed e.g. to the \c command, the three hyphens are processed as input to the command and not replaced by an em dash.</p>
+<p>-----------------------------------------------------------------------</p>
+<p>People can still add a bunch of dashes, though, without QDoc replacing them all with a series of em dashes.</p>
+<p>&mdash;You can also start a new paragraph with an em dash, if you want to.</p>
+<p><b>See also </b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a>.</p>
+<!-- @@@methodWithEmDashInItsDocs -->
+<!-- $$$methodWithEnDashInItsDocs[overload1]$$$methodWithEnDashInItsDocs -->
+<h3 class="fn" translate="no" id="methodWithEnDashInItsDocs"><span class="type">void</span> Test::<span class="name">methodWithEnDashInItsDocs</span>()</h3>
+<p>This method has en dashes in its documentation &ndash; as you'll find represented by <code translate="no">--</code> in the sources &ndash; here and there. The important bit to note is that when passed e.g. to the \c command, the two hyphens are processed as input to the command and not replaced by an en dash. This also applies to code blocks, where otherwise, the decrement operator would get completely borked:</p>
+<pre class="cpp" translate="no"><span class="keyword">for</span> (<span class="type">int</span> i <span class="operator">=</span> <span class="number">42</span>; i <span class="operator">&gt;</span> <span class="number">0</span>; <span class="operator">-</span><span class="operator">-</span>i)
+ <span class="comment">// Do something cool during countdown.</span></pre>
+<p>...as it would be silly if this would output &ndash;i instead of <code translate="no">--i</code>.</p>
+<p>-----------------------------------------------------------------------</p>
+<p>It still allows people to add a bunch of dashes, though, without replacing them all with a series of en dashes. Of course, they might want to use the \hr command instead, like this:</p>
+<hr />
+<p>&ndash; You can also start a new paragraph with an en dash, if you want to.</p>
+<p><b>See also </b>methodWithEnDashInItsDocs.</p>
+<!-- @@@methodWithEnDashInItsDocs -->
+<!-- $$$someFunction[overload1]$$$someFunctionintint -->
+<h3 class="fn" translate="no" id="someFunction"><span class="type">int</span> Test::<span class="name">someFunction</span>(<span class="type">int</span>, <span class="type">int</span> <i>v</i> = 0)</h3>
+<p>Function that takes a parameter <i translate="no">v</i>. Also returns the value of <i translate="no">v</i>.</p>
+<!-- @@@someFunction -->
+<!-- $$$someFunctionDefaultArg[overload1]$$$someFunctionDefaultArgintbool -->
+<h3 class="fn" translate="no" id="someFunctionDefaultArg"><span class="type">void</span> Test::<span class="name">someFunctionDefaultArg</span>(<span class="type">int</span> <i>i</i>, <span class="type">bool</span> <i>b</i> = false) const</h3>
+<p>Function that takes a parameter <i translate="no">i</i> and <i translate="no">b</i>.</p>
+<p><b>Warning:</b> This function is not <a href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</a>.</p>
+<!-- @@@someFunctionDefaultArg -->
+<!-- $$$virtualFun[overload1]$$$virtualFun -->
+<h3 class="fn" translate="no" id="virtualFun"><code class="details extra" translate="no">[virtual]</code> <span class="type">void</span> Test::<span class="name">virtualFun</span>()</h3>
+<p>Function that must be reimplemented.</p>
+<!-- @@@virtualFun -->
+</div>
+<div class="relnonmem">
+<h2>Related Non-Members</h2>
+<!-- $$$operator==[overload1]$$$operator==constTestQDoc::Test&constTestQDoc::Test& -->
+<h3 class="fn" translate="no" id="operator-eq-eq"><span class="type">bool</span> <span class="name">operator==</span>(const <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;<i>lhs</i>, const <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;<i>rhs</i>)</h3>
+<p>Returns true if <i translate="no">lhs</i> and <i translate="no">rhs</i> are equal.</p>
+<!-- @@@operator== -->
+</div>
+<div class="macros">
+<h2>Macro Documentation</h2>
+<!-- $$$QDOCTEST_MACRO2[overload1]$$$QDOCTEST_MACRO2int& -->
+<h3 class="fn" translate="no" id="QDOCTEST_MACRO2"><code class="details extra" translate="no">[since Test 1.1]</code> <span class="name">QDOCTEST_MACRO2</span>(<span class="type">int</span> &amp;<i>x</i>)</h3>
+<p>A macro with argument <i translate="no">x</i>.</p>
+<p>This macro was introduced in Test 1.1.</p>
+<!-- @@@QDOCTEST_MACRO2 -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-testderived-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-testderived-members.html
new file mode 100644
index 000000000..201aba603
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-testderived-members.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace, derived from Test.">
+ <title>List of All Members for TestDerived | TestCPP</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>TestDerived</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for TestDerived</h1>
+<p>This is the complete list of members for <a href="testqdoc-testderived.html">TestQDoc::TestDerived</a>, including inherited members.</p>
+<div class="table"><table class="propsummary" translate="no">
+<tr><td class="topAlign"><ul>
+<li class="fn" translate="no">enum <span class="name"><b><a href="testqdoc-test.html#ClassicEnum-enum" translate="no">ClassicEnum</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#DerivedType-typedef" translate="no">DerivedType</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#NotTypedef-typedef" translate="no">NotTypedef</a></b></span></li>
+<li class="fn" translate="no">enum class <span class="name"><b><a href="testqdoc-test.html#ScopedEnum-enum" translate="no">ScopedEnum</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#SomeType-typedef" translate="no">SomeType</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#funcPtr" translate="no">funcPtr</a></b></span>(bool, const char *) : void (*)(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#inlineFunction" translate="no">inlineFunction</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEmDashInItsDocs" translate="no">methodWithEmDashInItsDocs</a></b></span>()</li>
+</ul></td><td class="topAlign"><ul>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload" translate="no">overload</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload-1" translate="no">overload</a></b></span>(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a></b></span>(int, int) : int</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a></b></span>(int, bool) const</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#someValue" translate="no">someValue</a></b></span>() : TestQDoc::TestDerived::NotTypedef</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#virtualFun" translate="no">virtualFun</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#virtualFun" translate="no">virtualFun</a></b></span>()</li>
+</ul>
+</td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-testderived-obsolete.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-testderived-obsolete.html
new file mode 100644
index 000000000..42b536e09
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-testderived-obsolete.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace, derived from Test.">
+ <title>Obsolete Members for TestDerived | TestCPP</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>TestDerived</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Obsolete Members for TestDerived</h1>
+<p><b>The following members of class <a href="testqdoc-testderived.html" translate="no">TestDerived</a> are deprecated.</b> They are provided to keep old source code working. We strongly advise against using them in new code.</p>
+<h2>Static Public Members</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived-obsolete.html#staticObsoleteMember" translate="no">staticObsoleteMember</a></b>()</td></tr>
+</table></div>
+<h2>Member Function Documentation</h2>
+<!-- $$$staticObsoleteMember[overload1]$$$staticObsoleteMember -->
+<h3 class="fn" translate="no" id="staticObsoleteMember"><code class="details extra" translate="no">[static, deprecated]</code> <span class="type">void</span> TestDerived::<span class="name">staticObsoleteMember</span>()</h3>
+<p>This function is deprecated. We strongly advise against using it in new code.</p>
+<p>Static obsolete method.</p>
+<!-- @@@staticObsoleteMember -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-testderived.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-testderived.html
new file mode 100644
index 000000000..04f789abb
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc-testderived.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace, derived from Test.">
+ <title>TestDerived Class | TestCPP</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>TestDerived</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#public-types">Public Types</a></li>
+<li class="level1"><a href="#public-functions">Public Functions</a></li>
+<li class="level1"><a href="#reimplemented-public-functions">Reimplemented Public Functions</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">TestDerived Class</h1>
+<span class="small-subtitle" translate="no">class <a href="testqdoc.html" translate="no">TestQDoc</a>::TestDerived</span>
+<!-- $$$TestDerived-brief -->
+<p>A class in a namespace, derived from <a href="testqdoc-test.html" translate="no">Test</a>. <a href="#details">More...</a></p>
+<!-- @@@TestDerived -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;TestDerived&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Inherits:</td><td class="memItemRight bottomAlign"> <a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></td></tr>
+</table></div>
+<ul>
+<li><a href="testqdoc-testderived-members.html">List of all members, including inherited members</a></li>
+<li><a href="testqdoc-testderived-obsolete.html">Deprecated members</a></li>
+</ul>
+<h2 id="public-types">Public Types</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#DerivedType-typedef" translate="no">DerivedType</a></b></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#NotTypedef-typedef" translate="no">NotTypedef</a></b></td></tr>
+</table></div>
+<h2 id="public-functions">Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> TestQDoc::TestDerived::NotTypedef </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#someValue" translate="no">someValue</a></b>()</td></tr>
+</table></div>
+<h2 id="reimplemented-public-functions">Reimplemented Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> virtual void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#virtualFun" translate="no">virtualFun</a></b>() override</td></tr>
+</table></div>
+<!-- $$$TestDerived-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@TestDerived -->
+<div class="types">
+<h2>Member Type Documentation</h2>
+<!-- $$$DerivedType -->
+<h3 class="fn" translate="no" id="DerivedType-typedef"><code class="details extra" translate="no">[alias]</code> TestDerived::<span class="name">DerivedType</span></h3>
+<p>An aliased typedef.</p>
+<!-- @@@DerivedType -->
+<!-- $$$NotTypedef -->
+<h3 class="fn" translate="no" id="NotTypedef-typedef"><code class="details extra" translate="no">[alias]</code> TestDerived::<span class="name">NotTypedef</span></h3>
+<p>I'm an alias, not a typedef.</p>
+<!-- @@@NotTypedef -->
+</div>
+<div class="func">
+<h2>Member Function Documentation</h2>
+<!-- $$$someValue[overload1]$$$someValue -->
+<h3 class="fn" translate="no" id="someValue"><span class="type"><a href="testqdoc-testderived.html#NotTypedef-typedef" translate="no">TestQDoc::TestDerived::NotTypedef</a></span> TestDerived::<span class="name">someValue</span>()</h3>
+<p>Returns a value using an aliases type.</p>
+<!-- @@@someValue -->
+<!-- $$$virtualFun[overload1]$$$virtualFun -->
+<h3 class="fn" translate="no" id="virtualFun"><code class="details extra" translate="no">[override virtual]</code> <span class="type">void</span> TestDerived::<span class="name">virtualFun</span>()</h3>
+<p>Reimplements: <a href="testqdoc-test.html#virtualFun" translate="no">Test::virtualFun</a>().</p>
+<!-- @@@virtualFun -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc.html
new file mode 100644
index 000000000..425c7b396
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/testqdoc.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A namespace.">
+ <title>TestQDoc Namespace | TestCPP</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#macros">Macros</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#usage">Usage</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">TestQDoc Namespace</h1>
+<!-- $$$TestQDoc-brief -->
+<p>A namespace. <a href="#details">More...</a></p>
+<!-- @@@TestQDoc -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;TestCPP&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+</table></div>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since 2.0)</code> class </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html" translate="no">Test</a></b></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since 2.0)</code> class </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html" translate="no">TestDerived</a></b></td></tr>
+</table></div>
+<h2 id="macros">Macros</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc.html#QDOCTEST_MACRO" translate="no">QDOCTEST_MACRO</a></b></td></tr>
+</table></div>
+<!-- $$$TestQDoc-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<h3 id="usage">Usage</h3>
+<p>This namespace is for testing QDoc output.</p>
+</div>
+<!-- @@@TestQDoc -->
+<div class="classes">
+<h2>Classes</h2>
+<h3> class <a href="testqdoc-test.html">Test</a></h3><!-- $$$Test-brief -->
+<p>A class in a namespace. <a href="testqdoc-test.html#details">More...</a></p>
+<!-- @@@Test -->
+<h3> class <a href="testqdoc-testderived.html">TestDerived</a></h3><!-- $$$TestDerived-brief -->
+<p>A class in a namespace, derived from <a href="testqdoc-test.html" translate="no">Test</a>. <a href="testqdoc-testderived.html#details">More...</a></p>
+<!-- @@@TestDerived -->
+</div>
+<div class="macros">
+<h2>Macro Documentation</h2>
+<!-- $$$QDOCTEST_MACRO[overload1]$$$QDOCTEST_MACRO -->
+<h3 class="fn" translate="no" id="QDOCTEST_MACRO"><span class="name">QDOCTEST_MACRO</span></h3>
+<!-- @@@QDOCTEST_MACRO -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/whatsnew.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/whatsnew.html
new file mode 100644
index 000000000..356731918
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/html/whatsnew.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- scopedenum.qdoc -->
+ <meta name="description" content="New classes documentation">
+ <title>New Classes and Functions | TestCPP</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">New Classes and Functions</h1>
+<!-- $$$whatsnew.html-description -->
+<div class="descr" id="details">
+<ul>
+<li><a href="#new-namespaces">New Namespaces</a></li>
+<li><a href="#new-classes">New Classes</a></li>
+<li><a href="#new-enum-values">New Enum Values</a></li>
+</ul>
+<h3 id="new-namespaces">New Namespaces</h3>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since 2.0)</code> namespace </td><td class="memItemRight bottomAlign"><b><a href="testqdoc.html" translate="no">TestQDoc</a></b></td></tr>
+</table></div>
+<h3 id="new-classes">New Classes</h3>
+<div class="flowListDiv" translate="no">
+<dl class="flowList odd"><dt class="alphaChar"><b>T</b></dt>
+<dd><a href="testqdoc-test.html">Test</a> (<a href="testqdoc.html">TestQDoc</a>)</dd>
+<dd><a href="testqdoc-testderived.html">TestDerived</a> (<a href="testqdoc.html">TestQDoc</a>)</dd>
+</dl>
+</div>
+<h3 id="new-enum-values">New Enum Values</h3>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft"> enum value </td><td class="memItemRight"><b><a href="testqdoc-test.html#ScopedEnum-enum">ScopedEnum::All</a></b></td></tr>
+</table></div>
+</div>
+<!-- @@@whatsnew.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/autolinking.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/autolinking.webxml
new file mode 100644
index 000000000..91f35d5d7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/autolinking.webxml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="autolinking.html" href="autolinking.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Autolinking" fulltitle="Autolinking" subtitle="">
+ <contents name="testqdoc" title="TestQDoc" level="1"/>
+ <contents name="someprop" title="someProp" level="1"/>
+ <description>
+ <section id="testqdoc">
+ <heading level="1">TestQDoc</heading>
+ <para>The string <link raw="TestQDoc" href="testqdoc.html" type="namespace">TestQDoc</link> links to the C++ namespace unless linking explicitly, <link raw="#TestQDoc" href="autolinking.html#testqdoc" type="page" page="Autolinking">like this</link>, or <link raw="TestQDoc" href="testqdoc.html" type="namespace">this</link>. Also,</para>
+ <para>Autolinks:</para>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="TestQDoc::TestDerived" href="testqdoc-testderived.html" type="class">TestQDoc::TestDerived</link></para>
+ </item>
+ </list>
+ <para>Explicit links:</para>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="TestQDoc::TestDerived" href="testqdoc-testderived.html" type="class">TestQDoc::TestDerived</link></para>
+ </item>
+ <item>
+ <para>
+ <link raw="Obsolete Classes#TestQDoc" href="obsolete-classes.html#testqdoc" type="page" page="Obsolete Classes">Obsolete Classes#TestQDoc</link></para>
+ </item>
+ </list>
+ </section>
+ <section id="someprop">
+ <heading level="1">someProp</heading>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/cpptypes.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/cpptypes.webxml
new file mode 100644
index 000000000..df7cd7024
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/cpptypes.webxml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <group name="cpptypes" href="cpptypes.html" status="active" location="classlists.qdoc" documented="true" seen="true" title="Test C++ Types">
+ <description>
+ <generatedlist contents="testgroup"/>
+ <table width="100%">
+ <row>
+ <item>
+ <para>
+ <link raw="TestQDoc::Test" href="testqdoc-test.html" type="class"/>
+ </para>
+ </item>
+ <item>
+ <para>A class in a namespace.</para>
+ </item>
+ </row>
+ </table>
+ </description>
+ </group>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/crossmoduleref.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/crossmoduleref.webxml
new file mode 100644
index 000000000..682799bd8
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/crossmoduleref.webxml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <namespace name="CrossModuleRef" href="crossmoduleref.html" status="active" access="public" location="testcpp.h" since="3.0" documented="true" module="TestCPP" brief="Namespace that has documented functions in multiple modules">
+ <description>
+ <brief>Namespace that has documented functions in multiple modules.</brief>
+ </description>
+ <function name="documentMe" fullname="CrossModuleRef::documentMe" href="crossmoduleref.html#documentMe" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void documentMe()">
+ <description>
+ <para>Document me!</para>
+ </description>
+ </function>
+ </namespace>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/obsolete-classes.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/obsolete-classes.webxml
new file mode 100644
index 000000000..dda841458
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/obsolete-classes.webxml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="obsolete-classes.html" href="obsolete-classes.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Obsolete Classes" fulltitle="Obsolete Classes" subtitle="">
+ <contents name="classes-with-obsolete-members" title="Classes with obsolete members" level="1"/>
+ <contents name="testqdoc" title="TestQDoc" level="2"/>
+ <description>
+ <section id="classes-with-obsolete-members">
+ <heading level="1">Classes with obsolete members</heading>
+ <generatedlist contents="obsoletecppmembers"/>
+ </section>
+ <section id="testqdoc">
+ <heading level="2">TestQDoc</heading>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/scoped-enum-linking.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/scoped-enum-linking.webxml
new file mode 100644
index 000000000..62091e656
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/scoped-enum-linking.webxml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="scoped-enum-linking.html" href="scoped-enum-linking.html" status="active" location="scopedenum.qdoc" documented="true" subtype="page" title="Enum Linking" fulltitle="Enum Linking" subtitle="">
+ <description>
+ <para>Linking to <link raw="TestQDoc::Test::ScopedEnum::All" href="testqdoc-test.html#ScopedEnum-enum" type="enum" enum="TestQDoc::Test::ScopedEnum">All</link>.</para>
+ <para>TestQDoc::Test::ClassicEnum::Howdy does not link, but <link raw="TestQDoc::Test::Howdy" href="testqdoc-test.html#ClassicEnum-enum" type="enum" enum="TestQDoc::Test::ClassicEnum">TestQDoc::Test::Howdy</link> might.</para>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testcpp-module.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testcpp-module.webxml
new file mode 100644
index 000000000..5d24b3077
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testcpp-module.webxml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document/>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testcpp.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testcpp.index
new file mode 100644
index 000000000..0ea9571f0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testcpp.index
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="TestCPP Reference Documentation" version="" project="TestCPP">
+ <namespace name="" status="active" access="public" module="testcpp">
+ <function name="QDOCTEST_MACRO" href="testqdoc.html#QDOCTEST_MACRO" status="active" access="public" documented="true" related="0" meta="macrowithoutparams" signature="QDOCTEST_MACRO"/>
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ </function>
+ <page name="autolinking.html" href="autolinking.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Autolinking" fulltitle="Autolinking" subtitle="">
+ <contents name="testqdoc" title="TestQDoc" level="1"/>
+ <contents name="someprop" title="someProp" level="1"/>
+ </page>
+ <namespace name="CrossModuleRef" href="crossmoduleref.html" status="active" access="public" location="testcpp.h" since="3.0" documented="true" module="TestCPP" brief="Namespace that has documented functions in multiple modules">
+ <function name="documentMe" fullname="CrossModuleRef::documentMe" href="crossmoduleref.html#documentMe" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void documentMe()"/>
+ </namespace>
+ <class name="DontLinkToMe" href="dontlinktome.html" status="ignored" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="Class that does not generate documentation"/>
+ <page name="scoped-enum-linking.html" href="scoped-enum-linking.html" status="active" location="scopedenum.qdoc" documented="true" subtype="page" title="Enum Linking" fulltitle="Enum Linking" subtitle=""/>
+ <page name="whatsnew.html" href="whatsnew.html" status="active" location="scopedenum.qdoc" documented="true" subtype="page" title="New Classes and Functions" fulltitle="New Classes and Functions" subtitle="" brief="New classes documentation"/>
+ <page name="obsolete-classes.html" href="obsolete-classes.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Obsolete Classes" fulltitle="Obsolete Classes" subtitle="">
+ <contents name="classes-with-obsolete-members" title="Classes with obsolete members" level="1"/>
+ <contents name="testqdoc" title="TestQDoc" level="2"/>
+ </page>
+ <namespace name="TestQDoc" href="testqdoc.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="A namespace">
+ <contents name="usage" title="Usage" level="1"/>
+ <function name="QDOCTEST_MACRO" href="testqdoc.html#QDOCTEST_MACRO" status="active" access="public" documented="true" related="0" meta="macrowithoutparams" signature="QDOCTEST_MACRO"/>
+ <class threadsafety="reentrant" name="Test" fullname="TestQDoc::Test" href="testqdoc-test.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" groups="cpptypes,testgroup" module="TestCPP" brief="A class in a namespace">
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ </function>
+ <function name="anotherObsoleteMember" fullname="TestQDoc::Test::anotherObsoleteMember" href="testqdoc-test-obsolete.html#anotherObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void anotherObsoleteMember()"/>
+ <function name="deprecatedMember" fullname="TestQDoc::Test::deprecatedMember" href="testqdoc-test-obsolete.html#deprecatedMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void deprecatedMember()"/>
+ <function name="funcPtr" fullname="TestQDoc::Test::funcPtr" href="testqdoc-test.html#funcPtr" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void (*)(bool)" signature="void (*)(bool) funcPtr(bool b, const char *s)">
+ <parameter type="bool" name="b" default=""/>
+ <parameter type="const char *" name="s" default=""/>
+ </function>
+ <function name="inlineFunction" fullname="TestQDoc::Test::inlineFunction" href="testqdoc-test.html#inlineFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" brief="An inline function, documented using the \fn QDoc command" signature="void inlineFunction()"/>
+ <function name="methodWithEmDashInItsDocs" fullname="TestQDoc::Test::methodWithEmDashInItsDocs" href="testqdoc-test.html#methodWithEmDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEmDashInItsDocs()"/>
+ <function name="methodWithEnDashInItsDocs" fullname="TestQDoc::Test::methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEnDashInItsDocs()"/>
+ <function name="obsoleteMember" fullname="TestQDoc::Test::obsoleteMember" href="testqdoc-test-obsolete.html#obsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void obsoleteMember()"/>
+ <function name="operator++" fullname="TestQDoc::Test::operator++" href="testqdoc-test-obsolete.html#operator-2b-2b" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator++()"/>
+ <function name="operator--" fullname="TestQDoc::Test::operator--" href="testqdoc-test-obsolete.html#operator--" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator--()"/>
+ <function name="operator==" href="testqdoc-test.html#operator-eq-eq" status="active" access="public" location="testcpp.h" documented="true" related="2" meta="plain" type="bool" signature="bool operator==(const TestQDoc::Test &amp;lhs, const TestQDoc::Test &amp;rhs)">
+ <parameter type="const TestQDoc::Test &amp;" name="lhs" default=""/>
+ <parameter type="const TestQDoc::Test &amp;" name="rhs" default=""/>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload" status="active" access="protected" location="testcpp.h" documented="true" meta="plain" type="void" signature="void overload()"/>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload-1" status="active" access="protected" location="testcpp.h" documented="true" since="Test 1.2" meta="plain" overload="true" overload-number="1" type="void" signature="void overload(bool b)">
+ <parameter type="bool" name="b" default=""/>
+ </function>
+ <function name="someFunction" fullname="TestQDoc::Test::someFunction" href="testqdoc-test.html#someFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="int" signature="int someFunction(int, int v)">
+ <parameter type="int" name="" default=""/>
+ <parameter type="int" name="v" default="0"/>
+ </function>
+ <function name="someFunctionDefaultArg" fullname="TestQDoc::Test::someFunctionDefaultArg" href="testqdoc-test.html#someFunctionDefaultArg" threadsafety="non-reentrant" status="active" access="public" location="testcpp.h" documented="true" meta="plain" const="true" type="void" signature="void someFunctionDefaultArg(int i, bool b) const" groups="testgroup">
+ <parameter type="int" name="i" default=""/>
+ <parameter type="bool" name="b" default="false"/>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::Test::virtualFun" href="testqdoc-test.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" type="void" signature="void virtualFun()"/>
+ <enum name="ClassicEnum" fullname="TestQDoc::Test::ClassicEnum" href="testqdoc-test.html#ClassicEnum-enum" status="active" access="public" location="testcpp.h" documented="true">
+ <value name="Yee" value="0"/>
+ <value name="Haw" value="1"/>
+ <value name="Howdy" value="2"/>
+ <value name="Partner" value="3"/>
+ </enum>
+ <enum name="ScopedEnum" fullname="TestQDoc::Test::ScopedEnum" href="testqdoc-test.html#ScopedEnum-enum" status="active" access="public" location="testcpp.h" documented="true" scoped="true">
+ <value name="This" value="0x01"/>
+ <value name="That" value="0x02"/>
+ <value name="All" value="This | That" since="2.0"/>
+ <value name="OmittedValue" value="99"/>
+ <value name="UselessValue" value="100"/>
+ <value name="VeryLastValue" value="101"/>
+ </enum>
+ <typedef name="SomeType" fullname="TestQDoc::Test::SomeType" href="testqdoc-test.html#SomeType-typedef" status="active" access="public" location="testcpp.h" documented="true"/>
+ </class>
+ <class name="TestDerived" fullname="TestQDoc::TestDerived" href="testqdoc-testderived.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" bases="TestQDoc::Test" module="TestCPP" brief="A class in a namespace, derived from Test">
+ <function name="someValue" fullname="TestQDoc::TestDerived::someValue" href="testqdoc-testderived.html#someValue" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::TestDerived::NotTypedef" signature="TestQDoc::TestDerived::NotTypedef someValue()"/>
+ <function name="staticObsoleteMember" fullname="TestQDoc::TestDerived::staticObsoleteMember" href="testqdoc-testderived-obsolete.html#staticObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" static="true" type="void" signature="void staticObsoleteMember()"/>
+ <function name="virtualFun" fullname="TestQDoc::TestDerived::virtualFun" href="testqdoc-testderived.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" override="true" type="void" signature="void virtualFun() override"/>
+ <typedef name="DerivedType" fullname="TestQDoc::TestDerived::DerivedType" href="testqdoc-testderived.html#DerivedType-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="Test::SomeType"/>
+ <typedef name="NotTypedef" fullname="TestQDoc::TestDerived::NotTypedef" href="testqdoc-testderived.html#NotTypedef-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="int"/>
+ </class>
+ </namespace>
+ <page name="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command" href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command" status="active" location="classlists.qdoc" documented="true" subtype="externalpage" title="reentrant" fulltitle="reentrant" subtitle=""/>
+ <group name="cpptypes" href="cpptypes.html" status="active" location="classlists.qdoc" documented="true" seen="true" title="Test C++ Types"/>
+ <group name="testgroup" href="testgroup.html" status="internal" seen="false" title=""/>
+ <module name="TestCPP" href="testcpp-module.html" status="active" since="2.0" documented="true" seen="true" title="QDoc Test C++ Classes" brief="A test module page">
+ <contents name="linking-to-function-like-things" title="Linking to function-like things" level="1"/>
+ <contents name="section" title="section()" level="2"/>
+ </module>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testqdoc-test.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testqdoc-test.webxml
new file mode 100644
index 000000000..b96225ee4
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testqdoc-test.webxml
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class threadsafety="reentrant" name="Test" fullname="TestQDoc::Test" href="testqdoc-test.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" groups="cpptypes,testgroup" module="TestCPP" brief="A class in a namespace">
+ <description>
+ <brief>A class in a namespace.</brief>
+ </description>
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ <description>
+ <brief>A macro with argument <argument>x</argument>.</brief>
+ </description>
+ </function>
+ <function name="anotherObsoleteMember" fullname="TestQDoc::Test::anotherObsoleteMember" href="testqdoc-test-obsolete.html#anotherObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void anotherObsoleteMember()">
+ <description>
+ <para>Use <link raw="obsoleteMember()" href="testqdoc-test.html#obsoleteMember" type="function">obsoleteMember()</link> instead.</para>
+ </description>
+ </function>
+ <function name="deprecatedMember" fullname="TestQDoc::Test::deprecatedMember" href="testqdoc-test-obsolete.html#deprecatedMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void deprecatedMember()">
+ <description>
+ <para>Use <link raw="someFunction()" href="testqdoc-test.html#someFunction" type="function">someFunction()</link> instead.</para>
+ </description>
+ </function>
+ <function name="funcPtr" fullname="TestQDoc::Test::funcPtr" href="testqdoc-test.html#funcPtr" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void (*)(bool)" signature="void (*)(bool) funcPtr(bool b, const char *s)">
+ <parameter type="bool" name="b" default=""/>
+ <parameter type="const char *" name="s" default=""/>
+ <description>
+ <para>Returns a pointer to a function that takes a boolean. Uses <argument>b</argument> and <argument>s</argument>.</para>
+ </description>
+ </function>
+ <function name="inlineFunction" fullname="TestQDoc::Test::inlineFunction" href="testqdoc-test.html#inlineFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" brief="An inline function, documented using the \fn QDoc command" signature="void inlineFunction()">
+ <description>
+ <brief>An inline function, documented using the \fn QDoc command.</brief>
+ </description>
+ </function>
+ <function name="methodWithEmDashInItsDocs" fullname="TestQDoc::Test::methodWithEmDashInItsDocs" href="testqdoc-test.html#methodWithEmDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEmDashInItsDocs()">
+ <description>
+ <para>This method has em dashes in its documentation—as you'll find represented by <teletype type="highlighted">---</teletype> in the sources—here and there. The important bit to note is that when passed e.g. to the \c command, the three hyphens are processed as input to the command and not replaced by an em dash.</para>
+ <para>-----------------------------------------------------------------------</para>
+ <para>People can still add a bunch of dashes, though, without QDoc replacing them all with a series of em dashes.</para>
+ <para>—You can also start a new paragraph with an em dash, if you want to.</para>
+ <see-also>
+ <link raw="methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" type="function">methodWithEnDashInItsDocs</link>
+ </see-also>
+ </description>
+ </function>
+ <function name="methodWithEnDashInItsDocs" fullname="TestQDoc::Test::methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEnDashInItsDocs()">
+ <description>
+ <para>This method has en dashes in its documentation – as you'll find represented by <teletype type="highlighted">--</teletype> in the sources – here and there. The important bit to note is that when passed e.g. to the \c command, the two hyphens are processed as input to the command and not replaced by an en dash. This also applies to code blocks, where otherwise, the decrement operator would get completely borked:</para>
+ <code>for (int i = 42; i &gt; 0; --i)
+ // Do something cool during countdown.</code>
+ <para>...as it would be silly if this would output –i instead of <teletype type="highlighted">--i</teletype>.</para>
+ <para>-----------------------------------------------------------------------</para>
+ <para>It still allows people to add a bunch of dashes, though, without replacing them all with a series of en dashes. Of course, they might want to use the \hr command instead, like this:</para>
+ <para>– You can also start a new paragraph with an en dash, if you want to.</para>
+ <see-also>methodWithEnDashInItsDocs</see-also>
+ </description>
+ </function>
+ <function name="obsoleteMember" fullname="TestQDoc::Test::obsoleteMember" href="testqdoc-test-obsolete.html#obsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void obsoleteMember()">
+ <description>
+ <para>Use <link raw="someFunction()" href="testqdoc-test.html#someFunction" type="function">someFunction()</link> instead.</para>
+ </description>
+ </function>
+ <function name="operator++" fullname="TestQDoc::Test::operator++" href="testqdoc-test-obsolete.html#operator-2b-2b" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator++()">
+ <description/>
+ </function>
+ <function name="operator--" fullname="TestQDoc::Test::operator--" href="testqdoc-test-obsolete.html#operator--" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator--()">
+ <description/>
+ </function>
+ <function name="operator==" href="testqdoc-test.html#operator-eq-eq" status="active" access="public" location="testcpp.h" documented="true" related="2" meta="plain" type="bool" signature="bool operator==(const TestQDoc::Test &amp;lhs, const TestQDoc::Test &amp;rhs)">
+ <parameter type="const TestQDoc::Test &amp;" name="lhs" default=""/>
+ <parameter type="const TestQDoc::Test &amp;" name="rhs" default=""/>
+ <description>
+ <para>Returns true if <argument>lhs</argument> and <argument>rhs</argument> are equal.</para>
+ </description>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload" status="active" access="protected" location="testcpp.h" documented="true" meta="plain" type="void" signature="void overload()">
+ <description/>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload-1" status="active" access="protected" location="testcpp.h" documented="true" since="Test 1.2" meta="plain" overload="true" overload-number="1" type="void" signature="void overload(bool b)">
+ <parameter type="bool" name="b" default=""/>
+ <description/>
+ </function>
+ <function name="someFunction" fullname="TestQDoc::Test::someFunction" href="testqdoc-test.html#someFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="int" signature="int someFunction(int, int v)">
+ <parameter type="int" name="" default=""/>
+ <parameter type="int" name="v" default="0"/>
+ <description>
+ <para>Function that takes a parameter <argument>v</argument>. Also returns the value of <argument>v</argument>.</para>
+ </description>
+ </function>
+ <function name="someFunctionDefaultArg" fullname="TestQDoc::Test::someFunctionDefaultArg" href="testqdoc-test.html#someFunctionDefaultArg" threadsafety="non-reentrant" status="active" access="public" location="testcpp.h" documented="true" meta="plain" const="true" type="void" signature="void someFunctionDefaultArg(int i, bool b) const" groups="testgroup">
+ <parameter type="int" name="i" default=""/>
+ <parameter type="bool" name="b" default="false"/>
+ <description>
+ <para>Function that takes a parameter <argument>i</argument> and <argument>b</argument>.</para>
+ </description>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::Test::virtualFun" href="testqdoc-test.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" type="void" signature="void virtualFun()">
+ <description>
+ <para>Function that must be reimplemented.</para>
+ </description>
+ </function>
+ <enum name="ClassicEnum" fullname="TestQDoc::Test::ClassicEnum" href="testqdoc-test.html#ClassicEnum-enum" status="active" access="public" location="testcpp.h" documented="true">
+ <value name="Yee" value="0"/>
+ <value name="Haw" value="1"/>
+ <value name="Howdy" value="2"/>
+ <value name="Partner" value="3"/>
+ <description>
+ <list type="enum">
+ <definition>
+ <term>TestQDoc::Test::Yee</term>Yee</definition>
+ <item/>
+ <definition>
+ <term>TestQDoc::Test::Haw</term>Haw</definition>
+ <item/>
+ <definition>
+ <term>TestQDoc::Test::Howdy</term>Howdy</definition>
+ <item/>
+ <definition>
+ <term>TestQDoc::Test::Partner</term>Partner</definition>
+ <item/>
+ </list>
+ </description>
+ </enum>
+ <enum name="ScopedEnum" fullname="TestQDoc::Test::ScopedEnum" href="testqdoc-test.html#ScopedEnum-enum" status="active" access="public" location="testcpp.h" documented="true" scoped="true">
+ <value name="This" value="0x01"/>
+ <value name="That" value="0x02"/>
+ <value name="All" value="This | That" since="2.0"/>
+ <value name="OmittedValue" value="99"/>
+ <value name="UselessValue" value="100"/>
+ <value name="VeryLastValue" value="101"/>
+ <description>
+ <brief>This enum has a brief to trigger a bug in CMD_BRIEF.</brief>
+ <list type="enum">
+ <definition>
+ <term>TestQDoc::Test::ScopedEnum::This</term>This</definition>
+ <item>
+ <para>Something</para>
+ </item>
+ <definition>
+ <term>TestQDoc::Test::ScopedEnum::That</term>That</definition>
+ <item>
+ <para>Something else</para>
+ </item>
+ <definition>
+ <term>TestQDoc::Test::ScopedEnum::All</term>All</definition>2.0<item>
+ <para>Everything</para>
+ </item>
+ </list>
+ <para>A scoped enum.</para>
+ </description>
+ </enum>
+ <typedef name="SomeType" fullname="TestQDoc::Test::SomeType" href="testqdoc-test.html#SomeType-typedef" status="active" access="public" location="testcpp.h" documented="true">
+ <description>
+ <brief>A typedef.</brief>
+ </description>
+ </typedef>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testqdoc-testderived.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testqdoc-testderived.webxml
new file mode 100644
index 000000000..3752e9950
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testqdoc-testderived.webxml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="TestDerived" fullname="TestQDoc::TestDerived" href="testqdoc-testderived.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" bases="TestQDoc::Test" module="TestCPP" brief="A class in a namespace, derived from Test">
+ <description>
+ <brief>A class in a namespace, derived from <link raw="Test" href="testqdoc-test.html" type="class">Test</link>.</brief>
+ </description>
+ <function name="someValue" fullname="TestQDoc::TestDerived::someValue" href="testqdoc-testderived.html#someValue" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::TestDerived::NotTypedef" signature="TestQDoc::TestDerived::NotTypedef someValue()">
+ <description>
+ <para>Returns a value using an aliases type.</para>
+ </description>
+ </function>
+ <function name="staticObsoleteMember" fullname="TestQDoc::TestDerived::staticObsoleteMember" href="testqdoc-testderived-obsolete.html#staticObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" static="true" type="void" signature="void staticObsoleteMember()">
+ <description>
+ <para>Static obsolete method.</para>
+ </description>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::TestDerived::virtualFun" href="testqdoc-testderived.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" override="true" type="void" signature="void virtualFun() override">
+ <description/>
+ </function>
+ <typedef name="DerivedType" fullname="TestQDoc::TestDerived::DerivedType" href="testqdoc-testderived.html#DerivedType-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="Test::SomeType">
+ <description>
+ <para>An aliased typedef.</para>
+ </description>
+ </typedef>
+ <typedef name="NotTypedef" fullname="TestQDoc::TestDerived::NotTypedef" href="testqdoc-testderived.html#NotTypedef-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="int">
+ <description>
+ <para>I'm an alias, not a typedef.</para>
+ </description>
+ </typedef>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testqdoc.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testqdoc.webxml
new file mode 100644
index 000000000..780b35080
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/testqdoc.webxml
@@ -0,0 +1,201 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <namespace name="TestQDoc" href="testqdoc.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="A namespace">
+ <contents name="usage" title="Usage" level="1"/>
+ <description>
+ <brief>A namespace.</brief>
+ <section id="usage">
+ <heading level="1">Usage</heading>
+ <para>This namespace is for testing QDoc output.</para>
+ </section>
+ </description>
+ <function name="QDOCTEST_MACRO" href="testqdoc.html#QDOCTEST_MACRO" status="active" access="public" documented="true" related="0" meta="macrowithoutparams" signature="QDOCTEST_MACRO">
+ <description/>
+ </function>
+ <class threadsafety="reentrant" name="Test" fullname="TestQDoc::Test" href="testqdoc-test.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" groups="cpptypes,testgroup" module="TestCPP" brief="A class in a namespace">
+ <description>
+ <brief>A class in a namespace.</brief>
+ </description>
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ <description>
+ <brief>A macro with argument <argument>x</argument>.</brief>
+ </description>
+ </function>
+ <function name="anotherObsoleteMember" fullname="TestQDoc::Test::anotherObsoleteMember" href="testqdoc-test-obsolete.html#anotherObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void anotherObsoleteMember()">
+ <description>
+ <para>Use <link raw="obsoleteMember()" href="testqdoc-test.html#obsoleteMember" type="function">obsoleteMember()</link> instead.</para>
+ </description>
+ </function>
+ <function name="deprecatedMember" fullname="TestQDoc::Test::deprecatedMember" href="testqdoc-test-obsolete.html#deprecatedMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void deprecatedMember()">
+ <description>
+ <para>Use <link raw="someFunction()" href="testqdoc-test.html#someFunction" type="function">someFunction()</link> instead.</para>
+ </description>
+ </function>
+ <function name="funcPtr" fullname="TestQDoc::Test::funcPtr" href="testqdoc-test.html#funcPtr" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void (*)(bool)" signature="void (*)(bool) funcPtr(bool b, const char *s)">
+ <parameter type="bool" name="b" default=""/>
+ <parameter type="const char *" name="s" default=""/>
+ <description>
+ <para>Returns a pointer to a function that takes a boolean. Uses <argument>b</argument> and <argument>s</argument>.</para>
+ </description>
+ </function>
+ <function name="inlineFunction" fullname="TestQDoc::Test::inlineFunction" href="testqdoc-test.html#inlineFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" brief="An inline function, documented using the \fn QDoc command" signature="void inlineFunction()">
+ <description>
+ <brief>An inline function, documented using the \fn QDoc command.</brief>
+ </description>
+ </function>
+ <function name="methodWithEmDashInItsDocs" fullname="TestQDoc::Test::methodWithEmDashInItsDocs" href="testqdoc-test.html#methodWithEmDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEmDashInItsDocs()">
+ <description>
+ <para>This method has em dashes in its documentation—as you'll find represented by <teletype type="highlighted">---</teletype> in the sources—here and there. The important bit to note is that when passed e.g. to the \c command, the three hyphens are processed as input to the command and not replaced by an em dash.</para>
+ <para>-----------------------------------------------------------------------</para>
+ <para>People can still add a bunch of dashes, though, without QDoc replacing them all with a series of em dashes.</para>
+ <para>—You can also start a new paragraph with an em dash, if you want to.</para>
+ <see-also>
+ <link raw="methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" type="function">methodWithEnDashInItsDocs</link>
+ </see-also>
+ </description>
+ </function>
+ <function name="methodWithEnDashInItsDocs" fullname="TestQDoc::Test::methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEnDashInItsDocs()">
+ <description>
+ <para>This method has en dashes in its documentation – as you'll find represented by <teletype type="highlighted">--</teletype> in the sources – here and there. The important bit to note is that when passed e.g. to the \c command, the two hyphens are processed as input to the command and not replaced by an en dash. This also applies to code blocks, where otherwise, the decrement operator would get completely borked:</para>
+ <code>for (int i = 42; i &gt; 0; --i)
+ // Do something cool during countdown.</code>
+ <para>...as it would be silly if this would output –i instead of <teletype type="highlighted">--i</teletype>.</para>
+ <para>-----------------------------------------------------------------------</para>
+ <para>It still allows people to add a bunch of dashes, though, without replacing them all with a series of en dashes. Of course, they might want to use the \hr command instead, like this:</para>
+ <para>– You can also start a new paragraph with an en dash, if you want to.</para>
+ <see-also>methodWithEnDashInItsDocs</see-also>
+ </description>
+ </function>
+ <function name="obsoleteMember" fullname="TestQDoc::Test::obsoleteMember" href="testqdoc-test-obsolete.html#obsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void obsoleteMember()">
+ <description>
+ <para>Use <link raw="someFunction()" href="testqdoc-test.html#someFunction" type="function">someFunction()</link> instead.</para>
+ </description>
+ </function>
+ <function name="operator++" fullname="TestQDoc::Test::operator++" href="testqdoc-test-obsolete.html#operator-2b-2b" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator++()">
+ <description/>
+ </function>
+ <function name="operator--" fullname="TestQDoc::Test::operator--" href="testqdoc-test-obsolete.html#operator--" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator--()">
+ <description/>
+ </function>
+ <function name="operator==" href="testqdoc-test.html#operator-eq-eq" status="active" access="public" location="testcpp.h" documented="true" related="2" meta="plain" type="bool" signature="bool operator==(const TestQDoc::Test &amp;lhs, const TestQDoc::Test &amp;rhs)">
+ <parameter type="const TestQDoc::Test &amp;" name="lhs" default=""/>
+ <parameter type="const TestQDoc::Test &amp;" name="rhs" default=""/>
+ <description>
+ <para>Returns true if <argument>lhs</argument> and <argument>rhs</argument> are equal.</para>
+ </description>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload" status="active" access="protected" location="testcpp.h" documented="true" meta="plain" type="void" signature="void overload()">
+ <description/>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload-1" status="active" access="protected" location="testcpp.h" documented="true" since="Test 1.2" meta="plain" overload="true" overload-number="1" type="void" signature="void overload(bool b)">
+ <parameter type="bool" name="b" default=""/>
+ <description/>
+ </function>
+ <function name="someFunction" fullname="TestQDoc::Test::someFunction" href="testqdoc-test.html#someFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="int" signature="int someFunction(int, int v)">
+ <parameter type="int" name="" default=""/>
+ <parameter type="int" name="v" default="0"/>
+ <description>
+ <para>Function that takes a parameter <argument>v</argument>. Also returns the value of <argument>v</argument>.</para>
+ </description>
+ </function>
+ <function name="someFunctionDefaultArg" fullname="TestQDoc::Test::someFunctionDefaultArg" href="testqdoc-test.html#someFunctionDefaultArg" threadsafety="non-reentrant" status="active" access="public" location="testcpp.h" documented="true" meta="plain" const="true" type="void" signature="void someFunctionDefaultArg(int i, bool b) const" groups="testgroup">
+ <parameter type="int" name="i" default=""/>
+ <parameter type="bool" name="b" default="false"/>
+ <description>
+ <para>Function that takes a parameter <argument>i</argument> and <argument>b</argument>.</para>
+ </description>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::Test::virtualFun" href="testqdoc-test.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" type="void" signature="void virtualFun()">
+ <description>
+ <para>Function that must be reimplemented.</para>
+ </description>
+ </function>
+ <enum name="ClassicEnum" fullname="TestQDoc::Test::ClassicEnum" href="testqdoc-test.html#ClassicEnum-enum" status="active" access="public" location="testcpp.h" documented="true">
+ <value name="Yee" value="0"/>
+ <value name="Haw" value="1"/>
+ <value name="Howdy" value="2"/>
+ <value name="Partner" value="3"/>
+ <description>
+ <list type="enum">
+ <definition>
+ <term>TestQDoc::Test::Yee</term>Yee</definition>
+ <item/>
+ <definition>
+ <term>TestQDoc::Test::Haw</term>Haw</definition>
+ <item/>
+ <definition>
+ <term>TestQDoc::Test::Howdy</term>Howdy</definition>
+ <item/>
+ <definition>
+ <term>TestQDoc::Test::Partner</term>Partner</definition>
+ <item/>
+ </list>
+ </description>
+ </enum>
+ <enum name="ScopedEnum" fullname="TestQDoc::Test::ScopedEnum" href="testqdoc-test.html#ScopedEnum-enum" status="active" access="public" location="testcpp.h" documented="true" scoped="true">
+ <value name="This" value="0x01"/>
+ <value name="That" value="0x02"/>
+ <value name="All" value="This | That" since="2.0"/>
+ <value name="OmittedValue" value="99"/>
+ <value name="UselessValue" value="100"/>
+ <value name="VeryLastValue" value="101"/>
+ <description>
+ <brief>This enum has a brief to trigger a bug in CMD_BRIEF.</brief>
+ <list type="enum">
+ <definition>
+ <term>TestQDoc::Test::ScopedEnum::This</term>This</definition>
+ <item>
+ <para>Something</para>
+ </item>
+ <definition>
+ <term>TestQDoc::Test::ScopedEnum::That</term>That</definition>
+ <item>
+ <para>Something else</para>
+ </item>
+ <definition>
+ <term>TestQDoc::Test::ScopedEnum::All</term>All</definition>2.0<item>
+ <para>Everything</para>
+ </item>
+ </list>
+ <para>A scoped enum.</para>
+ </description>
+ </enum>
+ <typedef name="SomeType" fullname="TestQDoc::Test::SomeType" href="testqdoc-test.html#SomeType-typedef" status="active" access="public" location="testcpp.h" documented="true">
+ <description>
+ <brief>A typedef.</brief>
+ </description>
+ </typedef>
+ </class>
+ <class name="TestDerived" fullname="TestQDoc::TestDerived" href="testqdoc-testderived.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" bases="TestQDoc::Test" module="TestCPP" brief="A class in a namespace, derived from Test">
+ <description>
+ <brief>A class in a namespace, derived from <link raw="Test" href="testqdoc-test.html" type="class">Test</link>.</brief>
+ </description>
+ <function name="someValue" fullname="TestQDoc::TestDerived::someValue" href="testqdoc-testderived.html#someValue" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::TestDerived::NotTypedef" signature="TestQDoc::TestDerived::NotTypedef someValue()">
+ <description>
+ <para>Returns a value using an aliases type.</para>
+ </description>
+ </function>
+ <function name="staticObsoleteMember" fullname="TestQDoc::TestDerived::staticObsoleteMember" href="testqdoc-testderived-obsolete.html#staticObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" static="true" type="void" signature="void staticObsoleteMember()">
+ <description>
+ <para>Static obsolete method.</para>
+ </description>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::TestDerived::virtualFun" href="testqdoc-testderived.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" override="true" type="void" signature="void virtualFun() override">
+ <description/>
+ </function>
+ <typedef name="DerivedType" fullname="TestQDoc::TestDerived::DerivedType" href="testqdoc-testderived.html#DerivedType-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="Test::SomeType">
+ <description>
+ <para>An aliased typedef.</para>
+ </description>
+ </typedef>
+ <typedef name="NotTypedef" fullname="TestQDoc::TestDerived::NotTypedef" href="testqdoc-testderived.html#NotTypedef-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="int">
+ <description>
+ <para>I'm an alias, not a typedef.</para>
+ </description>
+ </typedef>
+ </class>
+ </namespace>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/whatsnew.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/whatsnew.webxml
new file mode 100644
index 000000000..99a8c473e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/expected/webxml/whatsnew.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="whatsnew.html" href="whatsnew.html" status="active" location="scopedenum.qdoc" documented="true" subtype="page" title="New Classes and Functions" fulltitle="New Classes and Functions" subtitle="" brief="New classes documentation">
+ <description>
+ <brief>New classes documentation</brief>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/scopedenum.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/scopedenum.qdocconf
new file mode 100644
index 000000000..f20a92472
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/scopedenum.qdocconf
@@ -0,0 +1,37 @@
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ./src/images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+project = TestCPP
+includepaths += -I./src
+
+headerdirs = ./src
+sources = ./src/testcpp.cpp \
+ ./src/classlists.qdoc \
+ ./src/scopedenum.qdoc
+exampledirs = snippets
+
+macro.CMDFN = \\\\fn
+macro.nothing = \\dontdocument ()
+macro.testnoautolist = \\if defined(test_noautolist)\n\\noautolist\n\\endif
+
+navigation.cppclassespage = "QDoc Test C++ Classes"
+
+defines += test_scopedenum
+
+outputformats = HTML WebXML DocBook
+
+{HTML.nosubdirs,DocBook.nosubdirs} = true
+HTML.outputsubdir = html
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
+DocBook.outputsubdir = docbook
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/classlists.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/classlists.qdoc
new file mode 100644
index 000000000..2954e5beb
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/classlists.qdoc
@@ -0,0 +1,51 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*!
+ \page obsolete-classes.html
+ \title Obsolete Classes
+
+ \section1 Classes with obsolete members
+ \generatelist obsoletecppmembers
+
+ \section2 TestQDoc
+*/
+
+/*!
+ \page autolinking.html
+ \title Autolinking
+
+ //! a section title that qualifies for autolinking
+ \section1 TestQDoc
+
+ The string TestQDoc links to the C++ namespace unless linking explicitly,
+ \l {#TestQDoc}{like this}, or \l {TestQDoc}{this}. Also,
+
+ Autolinks:
+
+ \list
+ \li TestQDoc::TestDerived
+ \endlist
+
+ Explicit links:
+
+ \list
+ \li \l [CPP] {TestQDoc::TestDerived}
+ \li \l {Obsolete Classes#TestQDoc}
+ \endlist
+
+ //! a section title shadowing a known property name
+ \section1 someProp
+*/
+
+/*!
+ \group cpptypes
+ \title Test C++ Types
+
+ \generatelist testgroup
+*/
+
+/*!
+ \externalpage https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command
+ \title reentrant
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/scopedenum.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/scopedenum.qdoc
new file mode 100644
index 000000000..22ef9e3f3
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/scopedenum.qdoc
@@ -0,0 +1,43 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \enum TestQDoc::Test::ClassicEnum
+
+ \value Yee
+ \value Haw
+ \value Howdy
+ \value Partner
+*/
+
+/*!
+ \enum TestQDoc::Test::ScopedEnum
+ \brief This enum has a brief to trigger a bug in CMD_BRIEF.
+ \omitvalue UselessValue
+ \value This Something
+ \value That Something else
+ \omitvalue OmittedValue \omit Unused -
+ This decription is omitted \endomit
+ \value [since 2.0] All Everything
+ \omitvalue VeryLastValue
+ Nothing here
+
+ A scoped enum.
+*/
+
+/*!
+ \page scoped-enum-linking.html
+ \title Enum Linking
+
+ Linking to \l {TestQDoc::Test::ScopedEnum::}{All}.
+
+ TestQDoc::Test::ClassicEnum::Howdy does not link,
+ but TestQDoc::Test::Howdy might.
+*/
+
+/*!
+ \page whatsnew.html
+ \title New Classes and Functions
+ \brief New classes documentation
+ \sincelist 2.0
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/snippets/snippet_testcpp.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/snippets/snippet_testcpp.cpp
new file mode 100644
index 000000000..1660fbc2b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/snippets/snippet_testcpp.cpp
@@ -0,0 +1,3 @@
+//! [random tag]
+You're not supposed to see this.
+//! [random tag]
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/testcpp.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/testcpp.cpp
new file mode 100644
index 000000000..25dc960ff
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/testcpp.cpp
@@ -0,0 +1,402 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include "testcpp.h"
+
+namespace TestQDoc {
+
+/*
+//! [random tag]
+\note This is just a test.
+//! [random tag]
+
+//! [args]
+\1\2 \3 \2\1
+//! [args]
+*/
+
+/*!
+ \module TestCPP
+ \qtvariable testcpp
+ \qtcmakepackage QDocTest
+ \title QDoc Test C++ Classes
+ \brief A test module page.
+ \since 2.0
+
+ \testnoautolist
+
+ \include testcpp.cpp random tag
+ \include testcpp.cpp {args} {/} {*} {Look, Ma! {I'm made of arguments!}}
+
+\if defined(test_nestedmacro)
+ \versionnote {module} {\ver 5.15.0}
+ \ver 1.0.0
+\endif
+
+ \section1 Linking to function-like things
+
+ \list
+ \li \l {TestQDoc::Test::someFunctionDefaultArg}
+ {someFunctionDefaultArg()}
+ \li QDOCTEST_MACRO2()
+ \li \l {TestQDoc::Test::}{QDOCTEST_MACRO2(int &x)}
+ \li \l {section()}
+ \li \l {section()} {section() is a section title}
+ \endlist
+
+ \section2 section()
+*/
+
+/*!
+ \namespace TestQDoc
+ \inheaderfile TestCPP
+ \inmodule TestCPP
+ \brief A namespace.
+
+ \section1 Usage
+ This namespace is for testing QDoc output.
+*/
+
+/*!
+ \class TestQDoc::Test
+ \inmodule TestCPP
+ \brief A class in a namespace.
+
+\if defined(test_ignoresince)
+ //! omitted by ignoresince
+ \since 1.1
+\endif
+ \ingroup testgroup
+ \ingroup cpptypes
+ \reentrant
+*/
+
+/*!
+ \class TestQDoc::TestDerived
+ \inmodule TestCPP
+ \brief A class in a namespace, derived from \l [CPP] Test.
+*/
+
+/*!
+ \macro QDOCTEST_MACRO
+ \relates TestQDoc
+\if defined(test_ignoresince)
+ //! omitted by ignoresince.Test
+ \since Test 0.9
+\endif
+*/
+
+/*!
+ \macro QDOCTEST_MACRO2(int &x)
+ \relates TestQDoc::Test
+ \since Test 1.1
+ \brief A macro with argument \a x.
+ \ingroup testgroup
+*/
+
+/*!
+\if defined(test_properties)
+ \property Test::id
+\else
+ \nothing
+\endif
+*/
+
+/*!
+ \deprecated [6.0] Use someFunction() instead.
+*/
+void Test::deprecatedMember()
+{
+ return;
+}
+
+/*!
+ \obsolete
+
+ Use someFunction() instead.
+*/
+void Test::obsoleteMember()
+{
+ return;
+}
+
+/*!
+ \obsolete Use obsoleteMember() instead.
+*/
+void Test::anotherObsoleteMember()
+{
+ return;
+}
+
+/*!
+ \nonreentrant
+ Function that takes a parameter \a i and \a b.
+\if defined(test_ignoresince)
+ \since 2.0
+\endif
+ \ingroup testgroup
+*/
+void Test::someFunctionDefaultArg(int i, bool b = false) const
+{
+ return;
+}
+
+/*!
+ \fn void Test::func(bool)
+ \internal
+*/
+
+/*!
+ \fn [funcPtr] void (*funcPtr(bool b, const char *s))(bool)
+
+ Returns a pointer to a function that takes a boolean. Uses \a b and \a s.
+*/
+
+/*!
+ \fn [op-inc] Test::operator++()
+ \fn [op-dec] Test::operator--()
+ \deprecated
+*/
+
+/*!
+ This method has en dashes in its documentation -- as you'll find
+ represented by \c{--} in the sources -- here and there. The important bit
+ to note is that when passed e.g. to the \\c command, the two hyphens are
+ processed as input to the command and not replaced by an en dash. This also
+ applies to code blocks, where otherwise, the decrement operator would get
+ completely borked:
+
+ \code
+ for (int i = 42; i > 0; --i)
+ // Do something cool during countdown.
+ \endcode
+
+ ...as it would be silly if this would output --i instead of \c {--i}.
+
+ -----------------------------------------------------------------------
+
+ It still allows people to add a bunch of dashes, though, without replacing
+ them all with a series of en dashes. Of course, they might want to use the
+ \\hr command instead, like this:
+ \hr
+
+ -- You can also start a new paragraph with an en dash, if you want to.
+
+ //! Self-referencing \sa-command for tests.
+ \sa methodWithEnDashInItsDocs
+*/
+void Test::methodWithEnDashInItsDocs()
+{
+ // Nothing to see here.
+}
+
+/*!
+ This method has em dashes in its documentation---as you'll find
+ represented by \c{---} in the sources---here and there. The important bit
+ to note is that when passed e.g. to the \\c command, the three hyphens are
+ processed as input to the command and not replaced by an em dash.
+
+ -----------------------------------------------------------------------
+
+ People can still add a bunch of dashes, though, without QDoc replacing
+ them all with a series of em dashes.
+
+ ---You can also start a new paragraph with an em dash, if you want to.
+
+ \sa methodWithEnDashInItsDocs
+
+*/
+void Test::methodWithEmDashInItsDocs()
+{
+ // Woah! Look at that!
+}
+
+// Documented below with an \fn command. Unnecessary but we support it, and it's used.
+int Test::someFunction(int, int v)
+{
+ return v;
+}
+
+/*!
+ \fn void TestQDoc::Test::inlineFunction()
+
+ \brief An inline function, documented using the \CMDFN QDoc command.
+*/
+
+/*!
+ \fn int Test::someFunction(int, int v = 0)
+
+ Function that takes a parameter \a v.
+ Also returns the value of \a v.
+\if defined(test_ignoresince)
+ \since Test 1.0
+\endif
+*/
+
+/*!
+ Function that must be reimplemented.
+*/
+void Test::virtualFun()
+{
+ return;
+}
+
+/*!
+ \fn bool Test::operator==(const Test &lhs, const Test &rhs)
+
+ Returns true if \a lhs and \a rhs are equal.
+*/
+
+/*!
+ \typedef TestQDoc::Test::SomeType
+ \brief A typedef.
+*/
+
+/*!
+ \reimp
+*/
+void TestDerived::virtualFun()
+{
+ return;
+}
+
+/*!
+ \fn TestQDoc::Test::overload()
+ \fn Test::overload(bool b)
+ //! The second overload should match even without the fully qualified path
+
+ Overloads that share a documentation comment, optionally taking
+ a parameter \a b.
+*/
+
+/*!
+ \fn Test::overload(bool b)
+ \since Test 1.2
+*/
+
+/*!
+ \typealias TestQDoc::TestDerived::DerivedType
+ An aliased typedef.
+*/
+
+/*!
+ \typedef TestQDoc::TestDerived::NotTypedef
+ I'm an alias, not a typedef.
+*/
+
+/*!
+ \obsolete
+
+ Static obsolete method.
+*/
+void TestDerived::staticObsoleteMember()
+{
+ return;
+}
+
+/*!
+\if defined(test_properties)
+ \fn void TestDerived::emitSomething()
+ Emitted when things happen.
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_properties)
+ \reimp
+\else
+ \nothing
+\endif
+*/
+int TestDerived::id()
+{
+ return 1;
+}
+
+/*!
+ Returns a value using an aliases type.
+*/
+TestDerived::NotTypedef TestDerived::someValue()
+{
+ return 0;
+}
+
+/*!
+\if defined(test_template)
+ \fn template <typename T1, typename T2> void TestQDoc::Test::funcTemplate(T1 a, T2 b)
+ \brief Function template with two parameters, \a a and \a b.
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_template)
+ \struct TestQDoc::Test::Struct
+ \inmodule TestCPP
+ \brief Templated struct.
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_template)
+ \typealias TestQDoc::Test::Specialized
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_template)
+ \class TestQDoc::Vec
+ \inmodule TestCPP
+ \brief Type alias that has its own reference.
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_template)
+ \macro Q_INVOKABLE
+ \relates TestQDoc::Test
+
+ This is a mock Q_INVOKABLE for the purpose of ensuring QDoc autolink to it
+ as expected.
+\else
+ \nothing
+\endif
+*/
+
+} // namespace TestQDoc
+
+
+/*!
+ \namespace CrossModuleRef
+ \inmodule TestCPP
+ \brief Namespace that has documented functions in multiple modules.
+ \since 3.0
+*/
+namespace CrossModuleRef {
+
+/*!
+ Document me!
+*/
+void documentMe()
+{
+}
+
+} // namespace CrossModuleRef
+
+/*!
+ \class DontLinkToMe
+ \inmodule TestCPP
+ \brief Class that does not generate documentation.
+*/
+
+/*!
+ \dontdocument (DontLinkToMe)
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/testcpp.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/testcpp.h
new file mode 100644
index 000000000..9b29eff25
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/scopedenum/src/testcpp.h
@@ -0,0 +1,139 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#pragma once
+
+#ifdef test_properties
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qproperty.h>
+#include <QtCore/qstring.h>
+#endif
+
+#define QDOCTEST_MACRO test
+#define QDOCTEST_MACRO2(x) (x) < 0 ? 0 : (x))
+
+namespace TestQDoc {
+
+class Test {
+#ifdef test_properties
+ Q_OBJECT
+ Q_PROPERTY(int id READ id)
+#endif
+public:
+
+#ifdef test_template
+template<typename D, typename T> struct Struct {};
+template<typename T>
+using Specialized = Struct<int, T>;
+#endif
+
+#ifdef test_template
+# define Q_INVOKABLE void foo() {};
+#endif
+
+#ifdef test_scopedenum
+ enum ClassicEnum { Yee, Haw, Howdy, Partner };
+
+ enum class ScopedEnum : unsigned char {
+ This = 0x01,
+ That = 0x02,
+ All = This | That,
+ OmittedValue = 99,
+ UselessValue,
+ VeryLastValue
+ };
+#endif
+ typedef struct {
+ int data;
+ } SomeType;
+ int someFunction(int, int v = 0);
+ void someFunctionDefaultArg(int i, bool b) const;
+ void obsoleteMember();
+ void anotherObsoleteMember();
+ void deprecatedMember();
+ void methodWithEnDashInItsDocs();
+ void methodWithEmDashInItsDocs();
+ void func(bool) {};
+ //! [funcPtr]
+ void (*funcPtr(bool b, const char *s))(bool) {
+ return func;
+ }
+ //! [op-inc]
+ Test &operator++() { return *this; }
+ //! [op-dec]
+ Test &operator--() { return *this; }
+
+ void anotherFunc() {};
+ inline void inlineFunction() {};
+ virtual void virtualFun();
+
+ friend bool operator==(const Test &lhs, const Test &rhs) { return false; }
+
+ Test() = delete;
+
+protected:
+ void overload() {}
+ void overload(bool b) { if (!b) return; }
+#ifdef test_template
+ template <typename T1, typename T2> void funcTemplate(T1 a, T2 b) {
+ a = b;
+ }
+#endif
+#ifdef test_properties
+ virtual int id() { return 0; }
+#endif
+};
+
+class TestDerived : public Test {
+#ifdef test_properties
+ Q_OBJECT
+
+ Q_PROPERTY(QString bindableProp READ bindableProp WRITE setBindableProp NOTIFY bindablePropChanged BINDABLE bindableProp)
+ Q_PROPERTY(QString someProp READ someProp BINDABLE somBindableProp)
+ Q_PROPERTY(int *intProp READ getInt STORED false CONSTANT FINAL)
+ Q_PROPERTY(const QString *name READ name)
+ QDOC_PROPERTY(bool boolProp READ boolProp WRITE setBoolProp NOTIFY boolPropChanged RESET resetBoolProp REVISION 1)
+#endif
+
+public:
+ using DerivedType = Test::SomeType;
+ using NotTypedef = int;
+ void virtualFun() override;
+ static void staticObsoleteMember();
+ NotTypedef someValue();
+#ifdef test_properties
+ QBindable<QString> bindableProp();
+ QBindable<QString> someBindableProp();
+ const QString &someProp();
+ int *getInt();
+ bool boolProp();
+ const QString *name() const;
+
+ Q_INVOKABLE void invokeMe() const {}
+ int id() override;
+
+Q_SIGNALS:
+ void emitSomething(QPrivateSignal);
+ void bindablePropChanged();
+ Q_REVISION(1) void boolPropChanged();
+
+public Q_SLOTS:
+ void setBindableProp(const QString &s);
+ void setBoolProp(bool b);
+ void resetBoolProp();
+#endif
+};
+
+#ifdef test_template
+template <typename T>
+struct BaseVec {};
+template <typename T>
+using Vec = BaseVec<T>;
+#endif
+
+} // namespace TestQDoc
+
+namespace CrossModuleRef {
+ void documentMe();
+}
+
+class DontLinkToMe {};
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/docbok/tableaftervalue.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/docbok/tableaftervalue.xml
new file mode 100644
index 000000000..99b8fe13a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/docbok/tableaftervalue.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>TableAfterValue Class</db:title>
+<db:productname>TableAfterValue</db:productname>
+<db:titleabbrev>TableAfterValue Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>Test that the \table command can follow a \value command.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>TableAfterValue</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:section xml:id="background-for-this-test-content">
+<db:title>Background for this test content</db:title>
+<db:para>According to QTBUG-115720, whenever a \value is followed by \table, the contents of the two commands end up being merged. This is likely surprising. The bug report suggests a workaround, which is adding \br between \value and \table.</db:para>
+</db:section>
+<db:section>
+<db:title>See Also</db:title>
+<db:para><db:emphasis>See also </db:emphasis>
+<db:simplelist type="vert" role="see-also">
+<db:member><db:link xlink:href="tableaftervalue.xml#Reproduces-enum">TableAfterValue::Reproduces</db:link></db:member>
+</db:simplelist>
+</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="member-type-documentation">
+<db:title>Member Type Documentation</db:title>
+<db:section xml:id="Reproduces-enum">
+<db:title>enum TableAfterValue::Reproduces</db:title>
+<db:informaltable>
+<db:thead>
+<db:tr>
+<db:th>Constant</db:th>
+</db:tr>
+</db:thead>
+<db:tr>
+<db:td>
+<db:para><db:code><db:emphasis role="bold"><db:link xlink:href="tableaftervalue.xml">TableAfterValue</db:link></db:emphasis>::Problem</db:code></db:para>
+</db:td>
+<db:td><db:code>0</db:code></db:td>
+</db:tr>
+</db:informaltable>
+<db:informaltable style="generic">
+<db:tr valign="top">
+<db:td>
+<db:para>This table shouldn't be mangled by the previous \value command.</db:para>
+</db:td>
+</db:tr>
+</db:informaltable>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/html/tableaftervalue-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/html/tableaftervalue-members.html
new file mode 100644
index 000000000..328848c33
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/html/tableaftervalue-members.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- table-after-value.cpp -->
+ <meta name="description" content="Test that the \table command can follow a \value command.">
+ <title>List of All Members for TableAfterValue | TableAfterValue</title>
+</head>
+<body>
+<li>TableAfterValue</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for TableAfterValue</h1>
+<p>This is the complete list of members for <a href="tableaftervalue.html">TableAfterValue</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no">enum <span class="name"><b><a href="tableaftervalue.html#Reproduces-enum" translate="no">Reproduces</a></b></span></li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/html/tableaftervalue.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/html/tableaftervalue.html
new file mode 100644
index 000000000..d27d77ef6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/html/tableaftervalue.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- table-after-value.cpp -->
+ <meta name="description" content="Test that the \table command can follow a \value command.">
+ <title>TableAfterValue Class | TableAfterValue</title>
+</head>
+<body>
+<li>TableAfterValue</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#public-types">Public Types</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#background-for-this-test-content">Background for this test content</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">TableAfterValue Class</h1>
+<!-- $$$TableAfterValue-brief -->
+<p>Test that the \table command can follow a \value command. <a href="#details">More...</a></p>
+<!-- @@@TableAfterValue -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;TableAfterValue&gt;</span></td></tr>
+</table></div>
+<ul>
+<li><a href="tableaftervalue-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="public-types">Public Types</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> enum </td><td class="memItemRight bottomAlign"><b><a href="tableaftervalue.html#Reproduces-enum" translate="no">Reproduces</a></b> { Problem }</td></tr>
+</table></div>
+<!-- $$$TableAfterValue-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<h3 id="background-for-this-test-content">Background for this test content</h3>
+<p>According to QTBUG-115720, whenever a \value is followed by \table, the contents of the two commands end up being merged. This is likely surprising. The bug report suggests a workaround, which is adding \br between \value and \table.</p>
+</div>
+<p><b>See also </b><a href="tableaftervalue.html#Reproduces-enum" translate="no">TableAfterValue::Reproduces</a>.</p>
+<!-- @@@TableAfterValue -->
+<div class="types">
+<h2>Member Type Documentation</h2>
+<!-- $$$Reproduces$$$Problem -->
+<h3 class="fn" translate="no" id="Reproduces-enum">enum TableAfterValue::<span class="name">Reproduces</span></h3>
+<div class="table"><table class="valuelist"><tr><th class="tblConst">Constant</th><th class="tblVal">Value</th></tr>
+<tr><td class="topAlign"><code translate="no">TableAfterValue::Problem</code></td><td class="topAlign tblval"><code translate="no">0</code></td></tr>
+</table></div>
+<div class="table"><table class="generic">
+ <tr valign="top" class="odd"><td >This table shouldn't be mangled by the previous \value command.</td></tr>
+</table></div>
+<!-- @@@Reproduces -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/html/tableaftervalue.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/html/tableaftervalue.index
new file mode 100644
index 000000000..4f3164508
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/html/tableaftervalue.index
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="TableAfterValue Reference Documentation" version="" project="TableAfterValue">
+ <namespace name="" status="active" access="public" module="tableaftervalue">
+ <class name="TableAfterValue" href="tableaftervalue.html" status="active" access="public" location="table-after-value.h" documented="true" module="Test" brief="Test that the \table command can follow a \value command">
+ <contents name="background-for-this-test-content" title="Background for this test content" level="1"/>
+ <enum name="Reproduces" fullname="TableAfterValue::Reproduces" href="tableaftervalue.html#Reproduces-enum" status="active" access="public" location="table-after-value.h" documented="true">
+ <value name="Problem" value="0"/>
+ </enum>
+ </class>
+ <module name="Test" href="test-module.html" status="internal" seen="false" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/webxml/tableaftervalue.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/webxml/tableaftervalue.index
new file mode 100644
index 000000000..4f3164508
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/webxml/tableaftervalue.index
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="TableAfterValue Reference Documentation" version="" project="TableAfterValue">
+ <namespace name="" status="active" access="public" module="tableaftervalue">
+ <class name="TableAfterValue" href="tableaftervalue.html" status="active" access="public" location="table-after-value.h" documented="true" module="Test" brief="Test that the \table command can follow a \value command">
+ <contents name="background-for-this-test-content" title="Background for this test content" level="1"/>
+ <enum name="Reproduces" fullname="TableAfterValue::Reproduces" href="tableaftervalue.html#Reproduces-enum" status="active" access="public" location="table-after-value.h" documented="true">
+ <value name="Problem" value="0"/>
+ </enum>
+ </class>
+ <module name="Test" href="test-module.html" status="internal" seen="false" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/webxml/tableaftervalue.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/webxml/tableaftervalue.webxml
new file mode 100644
index 000000000..9f7f5e3bb
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/expected/webxml/tableaftervalue.webxml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="TableAfterValue" href="tableaftervalue.html" status="active" access="public" location="table-after-value.h" documented="true" module="Test" brief="Test that the \table command can follow a \value command">
+ <contents name="background-for-this-test-content" title="Background for this test content" level="1"/>
+ <description>
+ <brief>Test that the \table command can follow a \value command.</brief>
+ <section id="background-for-this-test-content">
+ <heading level="1">Background for this test content</heading>
+ <para>According to QTBUG-115720, whenever a \value is followed by \table, the contents of the two commands end up being merged. This is likely surprising. The bug report suggests a workaround, which is adding \br between \value and \table.</para>
+ </section>
+ <see-also>
+ <link raw="TableAfterValue::Reproduces" href="tableaftervalue.html#Reproduces-enum" type="enum" enum="TableAfterValue::Reproduces">TableAfterValue::Reproduces</link>
+ </see-also>
+ </description>
+ <enum name="Reproduces" fullname="TableAfterValue::Reproduces" href="tableaftervalue.html#Reproduces-enum" status="active" access="public" location="table-after-value.h" documented="true">
+ <value name="Problem" value="0"/>
+ <description>
+ <list type="enum">
+ <definition>
+ <term>TableAfterValue::Problem</term>Problem</definition>
+ <item/>
+ </list>
+ <table>
+ <row>
+ <item>
+ <para>This table shouldn't be mangled by the previous \value command.</para>
+ </item>
+ </row>
+ </table>
+ </description>
+ </enum>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/src/table-after-value.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/src/table-after-value.cpp
new file mode 100644
index 000000000..4ad9932d5
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/src/table-after-value.cpp
@@ -0,0 +1,28 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#include "tableaftervalue.h"
+
+/*!
+ \class TableAfterValue
+ \inmodule Test
+ \brief Test that the \\table command can follow a \\value command.
+
+ \section1 Background for this test content
+ According to QTBUG-115720, whenever a \\value is followed by \\table, the
+ contents of the two commands end up being merged. This is likely surprising.
+ The bug report suggests a workaround, which is adding \\br between \\value
+ and \\table.
+
+ \sa TableAfterValue::Reproduces
+*/
+
+/*!
+ \enum TableAfterValue::Reproduces
+
+ \value Problem
+
+ \table
+ \row
+ \li This table shouldn't be mangled by the previous \\value command.
+ \endtable
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/src/table-after-value.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/src/table-after-value.h
new file mode 100644
index 000000000..466242775
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/src/table-after-value.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+class TableAfterValue
+{
+public:
+ enum Reproduces { Problem };
+};
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/tableaftervalue.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/tableaftervalue.qdocconf
new file mode 100644
index 000000000..fb8daa132
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tableaftervalue/tableaftervalue.qdocconf
@@ -0,0 +1,29 @@
+project = TableAfterValue
+
+headerdirs = ./src
+sourcedirs = ./src
+exampledirs = ./src
+
+outputformats = WebXML HTML DocBook
+WebXML.quotinginformation = true
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
+
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+
+DocBook.nosubdirs = true
+DocBook.outputsubdir = docbok
+
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ./src/images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/docbook/templated-callables-h.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/docbook/templated-callables-h.xml
new file mode 100644
index 000000000..2a7204642
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/docbook/templated-callables-h.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>&lt;templated_callables.h&gt;</db:title>
+<db:productname>templatedcallables</db:productname>
+<db:titleabbrev>templatedcallables Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>templatedcallables Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>templated_callables.h</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:para>Containing headers for the tested free functions.</db:para>
+</db:section>
+<db:section xml:id="function-documentation">
+<db:title>Function Documentation</db:title>
+<db:section xml:id="templated_function_with_defaulted_non_type_template_parameter">
+<db:title>void templated_function_with_defaulted_non_type_template_parameter()</db:title>
+<db:para>A templated function with a defaulted non type template parameter.</db:para>
+</db:section>
+<db:section xml:id="templated_function_with_defaulted_template_template_parameter">
+<db:title>void templated_function_with_defaulted_template_template_parameter(<db:emphasis>Container&lt;T, size&gt;</db:emphasis>)</db:title>
+<db:para>A templated function with a defaulted template template parameter.</db:para>
+</db:section>
+<db:section xml:id="templated_function_with_defaulted_type_template_parameter">
+<db:title>void templated_function_with_defaulted_type_template_parameter(<db:emphasis>T</db:emphasis>)</db:title>
+<db:para>A templated function with a defaulted type template parameter.</db:para>
+</db:section>
+<db:section xml:id="templated_function_with_non_type_template_parameter">
+<db:title>void templated_function_with_non_type_template_parameter()</db:title>
+<db:para>A templated function with a non-defaulted non type template parameter.</db:para>
+</db:section>
+<db:section xml:id="templated_function_with_non_type_template_parameter_pack">
+<db:title>void templated_function_with_non_type_template_parameter_pack()</db:title>
+<db:para>A templated function with a non type template parameter pack.</db:para>
+</db:section>
+<db:section xml:id="templated_function_with_placeholder_non_type_template_parameter">
+<db:title>void templated_function_with_placeholder_non_type_template_parameter()</db:title>
+<db:para>A templated function with a placeholder non type template parameter.</db:para>
+</db:section>
+<db:section xml:id="templated_function_with_template_template_parameter">
+<db:title>void templated_function_with_template_template_parameter(<db:emphasis>K&lt;T&gt;</db:emphasis>)</db:title>
+<db:para>A templated function with a non-defaulted template template parameter.</db:para>
+</db:section>
+<db:section xml:id="templated_function_with_template_template_parameter_pack">
+<db:title>void templated_function_with_template_template_parameter_pack(<db:emphasis>Container&lt;T&gt;...</db:emphasis>)</db:title>
+<db:para>A templated function with a template template parameter pack.</db:para>
+</db:section>
+<db:section xml:id="templated_function_with_type_template_parameter">
+<db:title>void templated_function_with_type_template_parameter(<db:emphasis>T</db:emphasis>)</db:title>
+<db:para>A templated function with a non-defaulted type template parameter.</db:para>
+</db:section>
+<db:section xml:id="templated_function_with_type_template_parameter_pack">
+<db:title>void templated_function_with_type_template_parameter_pack(<db:emphasis>Ts...</db:emphasis>)</db:title>
+<db:para>A templated function with a type template parameter pack.</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/docbook/templatedclass.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/docbook/templatedclass.xml
new file mode 100644
index 000000000..427f1cab3
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/docbook/templatedclass.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>TemplatedClass Class</db:title>
+<db:subtitle>template &lt;typename T&gt; class TemplatedClass</db:subtitle>
+<db:productname>templatedcallables</db:productname>
+<db:titleabbrev>templatedcallables Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>templatedcallables Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>TemplatedClass</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:para>Containing record for the tested methods.</db:para>
+</db:section>
+<db:section xml:id="member-function-documentation">
+<db:title>Member Function Documentation</db:title>
+<db:section xml:id="templated_method_with_defaulted_non_type_template_parameter">
+<db:title>void TemplatedClass::templated_method_with_defaulted_non_type_template_parameter()</db:title>
+<db:para>A templated method under a templated class with a defaulted non type template parameter.</db:para>
+</db:section>
+<db:section xml:id="templated_method_with_defaulted_template_template_parameter">
+<db:title>void TemplatedClass::templated_method_with_defaulted_template_template_parameter(<db:emphasis>Container&lt;U, size&gt;</db:emphasis>)</db:title>
+<db:para>A templated method under a templated class with a defaulted template template parameter.</db:para>
+</db:section>
+<db:section xml:id="templated_method_with_defaulted_type_template_parameter">
+<db:title>void TemplatedClass::templated_method_with_defaulted_type_template_parameter(<db:emphasis>U</db:emphasis>)</db:title>
+<db:para>A templated method under a templated class with a defaulted type template parameter.</db:para>
+</db:section>
+<db:section xml:id="templated_method_with_non_type_template_parameter">
+<db:title>void TemplatedClass::templated_method_with_non_type_template_parameter()</db:title>
+<db:para>A templated method under a templated class with a non-defaulted non type template parameter.</db:para>
+</db:section>
+<db:section xml:id="templated_method_with_non_type_template_parameter_pack">
+<db:title>void TemplatedClass::templated_method_with_non_type_template_parameter_pack()</db:title>
+<db:para>A templated method under a templated class with a non type template parameter pack.</db:para>
+</db:section>
+<db:section xml:id="templated_method_with_placeholder_non_type_template_parameter">
+<db:title>void TemplatedClass::templated_method_with_placeholder_non_type_template_parameter()</db:title>
+<db:para>A templated method under a templated class with a placeholder non type template parameter.</db:para>
+</db:section>
+<db:section xml:id="templated_method_with_template_template_parameter">
+<db:title>void TemplatedClass::templated_method_with_template_template_parameter(<db:emphasis>X&lt;U&gt;</db:emphasis>)</db:title>
+<db:para>A templated method under a templated class with a non-defaulted template template parameter.</db:para>
+</db:section>
+<db:section xml:id="templated_method_with_template_template_parameter_pack">
+<db:title>void TemplatedClass::templated_method_with_template_template_parameter_pack(<db:emphasis>Container&lt;U&gt;...</db:emphasis>)</db:title>
+<db:para>A templated method under a templated class with a template template parameter pack.</db:para>
+</db:section>
+<db:section xml:id="templated_method_with_type_template_parameter">
+<db:title>void TemplatedClass::templated_method_with_type_template_parameter(<db:emphasis>U</db:emphasis>)</db:title>
+<db:para>A templated method under a templated class with a non-defaulted type template parameter.</db:para>
+</db:section>
+<db:section xml:id="templated_method_with_type_template_parameter_pack">
+<db:title>void TemplatedClass::templated_method_with_type_template_parameter_pack(<db:emphasis>Ts...</db:emphasis>)</db:title>
+<db:para>A templated method under a templated class with a type template parameter pack.</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templated-callables-h.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templated-callables-h.html
new file mode 100644
index 000000000..4c8bd89c8
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templated-callables-h.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- templated_callables.cpp -->
+ <title>&lt;templated_callables.h&gt; | templatedcallables</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">&lt;templated_callables.h&gt;</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;templated_callables.h&gt;</span></td></tr>
+</table></div>
+<h2 id="functions">Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templated-callables-h.html#templated_function_with_defaulted_non_type_template_parameter" translate="no">templated_function_with_defaulted_non_type_template_parameter</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templated-callables-h.html#templated_function_with_defaulted_template_template_parameter" translate="no">templated_function_with_defaulted_template_template_parameter</a></b>(Container&lt;T, size&gt;)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templated-callables-h.html#templated_function_with_defaulted_type_template_parameter" translate="no">templated_function_with_defaulted_type_template_parameter</a></b>(T)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templated-callables-h.html#templated_function_with_non_type_template_parameter" translate="no">templated_function_with_non_type_template_parameter</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templated-callables-h.html#templated_function_with_non_type_template_parameter_pack" translate="no">templated_function_with_non_type_template_parameter_pack</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templated-callables-h.html#templated_function_with_placeholder_non_type_template_parameter" translate="no">templated_function_with_placeholder_non_type_template_parameter</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templated-callables-h.html#templated_function_with_template_template_parameter" translate="no">templated_function_with_template_template_parameter</a></b>(K&lt;T&gt;)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templated-callables-h.html#templated_function_with_template_template_parameter_pack" translate="no">templated_function_with_template_template_parameter_pack</a></b>(Container&lt;T&gt;...)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templated-callables-h.html#templated_function_with_type_template_parameter" translate="no">templated_function_with_type_template_parameter</a></b>(T)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templated-callables-h.html#templated_function_with_type_template_parameter_pack" translate="no">templated_function_with_type_template_parameter_pack</a></b>(Ts...)</td></tr>
+</table></div>
+<!-- $$$<templated_callables.h>-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<p>Containing headers for the tested free functions.</p>
+</div>
+<!-- @@@<templated_callables.h> -->
+<div class="func">
+<h2>Function Documentation</h2>
+<!-- $$$templated_function_with_defaulted_non_type_template_parameter[overload1]$$$templated_function_with_defaulted_non_type_template_parameter -->
+<h3 class="fn" translate="no" id="templated_function_with_defaulted_non_type_template_parameter">template &lt;char Category = 'A'&gt; <span class="type">void</span> <span class="name">templated_function_with_defaulted_non_type_template_parameter</span>()</h3>
+<p>A templated function with a defaulted non type template parameter.</p>
+<!-- @@@templated_function_with_defaulted_non_type_template_parameter -->
+<!-- $$$templated_function_with_defaulted_template_template_parameter[overload1]$$$templated_function_with_defaulted_template_template_parameterContainer<T,size> -->
+<h3 class="fn" translate="no" id="templated_function_with_defaulted_template_template_parameter">template &lt;typename T, int size, template &lt;typename, int &gt; typename Container = std::array&gt; <span class="type">void</span> <span class="name">templated_function_with_defaulted_template_template_parameter</span>(<span class="type">Container</span>&lt;<span class="type">T</span>, <span class="type">size</span>&gt;)</h3>
+<p>A templated function with a defaulted template template parameter.</p>
+<!-- @@@templated_function_with_defaulted_template_template_parameter -->
+<!-- $$$templated_function_with_defaulted_type_template_parameter[overload1]$$$templated_function_with_defaulted_type_template_parameterT -->
+<h3 class="fn" translate="no" id="templated_function_with_defaulted_type_template_parameter">template &lt;typename T = char&gt; <span class="type">void</span> <span class="name">templated_function_with_defaulted_type_template_parameter</span>(<span class="type">T</span>)</h3>
+<p>A templated function with a defaulted type template parameter.</p>
+<!-- @@@templated_function_with_defaulted_type_template_parameter -->
+<!-- $$$templated_function_with_non_type_template_parameter[overload1]$$$templated_function_with_non_type_template_parameter -->
+<h3 class="fn" translate="no" id="templated_function_with_non_type_template_parameter">template &lt;char Category&gt; <span class="type">void</span> <span class="name">templated_function_with_non_type_template_parameter</span>()</h3>
+<p>A templated function with a non-defaulted non type template parameter.</p>
+<!-- @@@templated_function_with_non_type_template_parameter -->
+<!-- $$$templated_function_with_non_type_template_parameter_pack[overload1]$$$templated_function_with_non_type_template_parameter_pack -->
+<h3 class="fn" translate="no" id="templated_function_with_non_type_template_parameter_pack">template &lt;unsigned int... Weights&gt; <span class="type">void</span> <span class="name">templated_function_with_non_type_template_parameter_pack</span>()</h3>
+<p>A templated function with a non type template parameter pack.</p>
+<!-- @@@templated_function_with_non_type_template_parameter_pack -->
+<!-- $$$templated_function_with_placeholder_non_type_template_parameter[overload1]$$$templated_function_with_placeholder_non_type_template_parameter -->
+<h3 class="fn" translate="no" id="templated_function_with_placeholder_non_type_template_parameter">template &lt;auto Iterator&gt; <span class="type">void</span> <span class="name">templated_function_with_placeholder_non_type_template_parameter</span>()</h3>
+<p>A templated function with a placeholder non type template parameter.</p>
+<!-- @@@templated_function_with_placeholder_non_type_template_parameter -->
+<!-- $$$templated_function_with_template_template_parameter[overload1]$$$templated_function_with_template_template_parameterK<T> -->
+<h3 class="fn" translate="no" id="templated_function_with_template_template_parameter">template &lt;typename T, template &lt;typename&gt; typename K&gt; <span class="type">void</span> <span class="name">templated_function_with_template_template_parameter</span>(<span class="type">K</span>&lt;<span class="type">T</span>&gt;)</h3>
+<p>A templated function with a non-defaulted template template parameter.</p>
+<!-- @@@templated_function_with_template_template_parameter -->
+<!-- $$$templated_function_with_template_template_parameter_pack[overload1]$$$templated_function_with_template_template_parameter_packContainer<T>... -->
+<h3 class="fn" translate="no" id="templated_function_with_template_template_parameter_pack">template &lt;typename T, template &lt;typename&gt; typename... Container&gt; <span class="type">void</span> <span class="name">templated_function_with_template_template_parameter_pack</span>(<span class="type">Container</span>&lt;<span class="type">T</span>&gt;...)</h3>
+<p>A templated function with a template template parameter pack.</p>
+<!-- @@@templated_function_with_template_template_parameter_pack -->
+<!-- $$$templated_function_with_type_template_parameter[overload1]$$$templated_function_with_type_template_parameterT -->
+<h3 class="fn" translate="no" id="templated_function_with_type_template_parameter">template &lt;typename T&gt; <span class="type">void</span> <span class="name">templated_function_with_type_template_parameter</span>(<span class="type">T</span>)</h3>
+<p>A templated function with a non-defaulted type template parameter.</p>
+<!-- @@@templated_function_with_type_template_parameter -->
+<!-- $$$templated_function_with_type_template_parameter_pack[overload1]$$$templated_function_with_type_template_parameter_packTs... -->
+<h3 class="fn" translate="no" id="templated_function_with_type_template_parameter_pack">template &lt;typename... Ts&gt; <span class="type">void</span> <span class="name">templated_function_with_type_template_parameter_pack</span>(<span class="type">Ts</span>...)</h3>
+<p>A templated function with a type template parameter pack.</p>
+<!-- @@@templated_function_with_type_template_parameter_pack -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templatedcallables.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templatedcallables.index
new file mode 100644
index 000000000..91245c7f8
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templatedcallables.index
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="templatedcallables Reference Documentation" version="" project="templatedcallables">
+ <namespace name="" status="active" access="public" module="templatedcallables">
+ <function name="templated_function_with_defaulted_non_type_template_parameter" href="templated-callables-h.html#templated_function_with_defaulted_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="0" meta="plain" type="void" signature="void templated_function_with_defaulted_non_type_template_parameter()"/>
+ <function name="templated_function_with_defaulted_template_template_parameter" href="templated-callables-h.html#templated_function_with_defaulted_template_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="1" meta="plain" type="void" signature="void templated_function_with_defaulted_template_template_parameter(Container&lt;T, size&gt;)">
+ <parameter type="Container&lt;T, size&gt;" name="" default=""/>
+ </function>
+ <function name="templated_function_with_defaulted_type_template_parameter" href="templated-callables-h.html#templated_function_with_defaulted_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="2" meta="plain" type="void" signature="void templated_function_with_defaulted_type_template_parameter(T)">
+ <parameter type="T" name="" default=""/>
+ </function>
+ <function name="templated_function_with_non_type_template_parameter" href="templated-callables-h.html#templated_function_with_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="3" meta="plain" type="void" signature="void templated_function_with_non_type_template_parameter()"/>
+ <function name="templated_function_with_non_type_template_parameter_pack" href="templated-callables-h.html#templated_function_with_non_type_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" related="4" meta="plain" type="void" signature="void templated_function_with_non_type_template_parameter_pack()"/>
+ <function name="templated_function_with_placeholder_non_type_template_parameter" href="templated-callables-h.html#templated_function_with_placeholder_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="5" meta="plain" type="void" signature="void templated_function_with_placeholder_non_type_template_parameter()"/>
+ <function name="templated_function_with_template_template_parameter" href="templated-callables-h.html#templated_function_with_template_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="6" meta="plain" type="void" signature="void templated_function_with_template_template_parameter(K&lt;T&gt;)">
+ <parameter type="K&lt;T&gt;" name="" default=""/>
+ </function>
+ <function name="templated_function_with_template_template_parameter_pack" href="templated-callables-h.html#templated_function_with_template_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" related="7" meta="plain" type="void" signature="void templated_function_with_template_template_parameter_pack(Container&lt;T&gt;...)">
+ <parameter type="Container&lt;T&gt;..." name="" default=""/>
+ </function>
+ <function name="templated_function_with_type_template_parameter" href="templated-callables-h.html#templated_function_with_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="8" meta="plain" type="void" signature="void templated_function_with_type_template_parameter(T)">
+ <parameter type="T" name="" default=""/>
+ </function>
+ <function name="templated_function_with_type_template_parameter_pack" href="templated-callables-h.html#templated_function_with_type_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" related="9" meta="plain" type="void" signature="void templated_function_with_type_template_parameter_pack(Ts...)">
+ <parameter type="Ts..." name="" default=""/>
+ </function>
+ <header name="&lt;templated_callables.h&gt;" href="templated-callables-h.html" status="active" documented="true" module="TestQDoc" title="&lt;templated_callables.h&gt;" fulltitle="&lt;templated_callables.h&gt;" subtitle="">
+ <function name="templated_function_with_defaulted_non_type_template_parameter" href="templated-callables-h.html#templated_function_with_defaulted_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="0" meta="plain" type="void" signature="void templated_function_with_defaulted_non_type_template_parameter()"/>
+ <function name="templated_function_with_defaulted_template_template_parameter" href="templated-callables-h.html#templated_function_with_defaulted_template_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="1" meta="plain" type="void" signature="void templated_function_with_defaulted_template_template_parameter(Container&lt;T, size&gt;)">
+ <parameter type="Container&lt;T, size&gt;" name="" default=""/>
+ </function>
+ <function name="templated_function_with_defaulted_type_template_parameter" href="templated-callables-h.html#templated_function_with_defaulted_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="2" meta="plain" type="void" signature="void templated_function_with_defaulted_type_template_parameter(T)">
+ <parameter type="T" name="" default=""/>
+ </function>
+ <function name="templated_function_with_non_type_template_parameter" href="templated-callables-h.html#templated_function_with_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="3" meta="plain" type="void" signature="void templated_function_with_non_type_template_parameter()"/>
+ <function name="templated_function_with_non_type_template_parameter_pack" href="templated-callables-h.html#templated_function_with_non_type_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" related="4" meta="plain" type="void" signature="void templated_function_with_non_type_template_parameter_pack()"/>
+ <function name="templated_function_with_placeholder_non_type_template_parameter" href="templated-callables-h.html#templated_function_with_placeholder_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="5" meta="plain" type="void" signature="void templated_function_with_placeholder_non_type_template_parameter()"/>
+ <function name="templated_function_with_template_template_parameter" href="templated-callables-h.html#templated_function_with_template_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="6" meta="plain" type="void" signature="void templated_function_with_template_template_parameter(K&lt;T&gt;)">
+ <parameter type="K&lt;T&gt;" name="" default=""/>
+ </function>
+ <function name="templated_function_with_template_template_parameter_pack" href="templated-callables-h.html#templated_function_with_template_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" related="7" meta="plain" type="void" signature="void templated_function_with_template_template_parameter_pack(Container&lt;T&gt;...)">
+ <parameter type="Container&lt;T&gt;..." name="" default=""/>
+ </function>
+ <function name="templated_function_with_type_template_parameter" href="templated-callables-h.html#templated_function_with_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="8" meta="plain" type="void" signature="void templated_function_with_type_template_parameter(T)">
+ <parameter type="T" name="" default=""/>
+ </function>
+ <function name="templated_function_with_type_template_parameter_pack" href="templated-callables-h.html#templated_function_with_type_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" related="9" meta="plain" type="void" signature="void templated_function_with_type_template_parameter_pack(Ts...)">
+ <parameter type="Ts..." name="" default=""/>
+ </function>
+ </header>
+ <class name="TemplatedClass" href="templatedclass.html" status="active" access="public" location="templated_callables.h" documented="true" module="TestQDoc">
+ <function name="templated_method_with_defaulted_non_type_template_parameter" fullname="TemplatedClass::templated_method_with_defaulted_non_type_template_parameter" href="templatedclass.html#templated_method_with_defaulted_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_defaulted_non_type_template_parameter()"/>
+ <function name="templated_method_with_defaulted_template_template_parameter" fullname="TemplatedClass::templated_method_with_defaulted_template_template_parameter" href="templatedclass.html#templated_method_with_defaulted_template_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_defaulted_template_template_parameter(Container&lt;U, size&gt;)">
+ <parameter type="Container&lt;U, size&gt;" name="" default=""/>
+ </function>
+ <function name="templated_method_with_defaulted_type_template_parameter" fullname="TemplatedClass::templated_method_with_defaulted_type_template_parameter" href="templatedclass.html#templated_method_with_defaulted_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_defaulted_type_template_parameter(U)">
+ <parameter type="U" name="" default=""/>
+ </function>
+ <function name="templated_method_with_non_type_template_parameter" fullname="TemplatedClass::templated_method_with_non_type_template_parameter" href="templatedclass.html#templated_method_with_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_non_type_template_parameter()"/>
+ <function name="templated_method_with_non_type_template_parameter_pack" fullname="TemplatedClass::templated_method_with_non_type_template_parameter_pack" href="templatedclass.html#templated_method_with_non_type_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_non_type_template_parameter_pack()"/>
+ <function name="templated_method_with_placeholder_non_type_template_parameter" fullname="TemplatedClass::templated_method_with_placeholder_non_type_template_parameter" href="templatedclass.html#templated_method_with_placeholder_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_placeholder_non_type_template_parameter()"/>
+ <function name="templated_method_with_template_template_parameter" fullname="TemplatedClass::templated_method_with_template_template_parameter" href="templatedclass.html#templated_method_with_template_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_template_template_parameter(X&lt;U&gt;)">
+ <parameter type="X&lt;U&gt;" name="" default=""/>
+ </function>
+ <function name="templated_method_with_template_template_parameter_pack" fullname="TemplatedClass::templated_method_with_template_template_parameter_pack" href="templatedclass.html#templated_method_with_template_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_template_template_parameter_pack(Container&lt;U&gt;...)">
+ <parameter type="Container&lt;U&gt;..." name="" default=""/>
+ </function>
+ <function name="templated_method_with_type_template_parameter" fullname="TemplatedClass::templated_method_with_type_template_parameter" href="templatedclass.html#templated_method_with_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_type_template_parameter(U)">
+ <parameter type="U" name="" default=""/>
+ </function>
+ <function name="templated_method_with_type_template_parameter_pack" fullname="TemplatedClass::templated_method_with_type_template_parameter_pack" href="templatedclass.html#templated_method_with_type_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_type_template_parameter_pack(Ts...)">
+ <parameter type="Ts..." name="" default=""/>
+ </function>
+ </class>
+ <module name="TestQDoc" href="testqdoc-module.html" status="internal" seen="false" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templatedclass-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templatedclass-members.html
new file mode 100644
index 000000000..8ab0482da
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templatedclass-members.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- templated_callables.cpp -->
+ <title>List of All Members for TemplatedClass | templatedcallables</title>
+</head>
+<body>
+<li>TemplatedClass</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for TemplatedClass</h1>
+<p>This is the complete list of members for <a href="templatedclass.html">TemplatedClass</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><span class="name"><b><a href="templatedclass.html#templated_method_with_defaulted_non_type_template_parameter" translate="no">templated_method_with_defaulted_non_type_template_parameter</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="templatedclass.html#templated_method_with_defaulted_template_template_parameter" translate="no">templated_method_with_defaulted_template_template_parameter</a></b></span>(Container&lt;U, size&gt;)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="templatedclass.html#templated_method_with_defaulted_type_template_parameter" translate="no">templated_method_with_defaulted_type_template_parameter</a></b></span>(U)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="templatedclass.html#templated_method_with_non_type_template_parameter" translate="no">templated_method_with_non_type_template_parameter</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="templatedclass.html#templated_method_with_non_type_template_parameter_pack" translate="no">templated_method_with_non_type_template_parameter_pack</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="templatedclass.html#templated_method_with_placeholder_non_type_template_parameter" translate="no">templated_method_with_placeholder_non_type_template_parameter</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="templatedclass.html#templated_method_with_template_template_parameter" translate="no">templated_method_with_template_template_parameter</a></b></span>(X&lt;U&gt;)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="templatedclass.html#templated_method_with_template_template_parameter_pack" translate="no">templated_method_with_template_template_parameter_pack</a></b></span>(Container&lt;U&gt;...)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="templatedclass.html#templated_method_with_type_template_parameter" translate="no">templated_method_with_type_template_parameter</a></b></span>(U)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="templatedclass.html#templated_method_with_type_template_parameter_pack" translate="no">templated_method_with_type_template_parameter_pack</a></b></span>(Ts...)</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templatedclass.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templatedclass.html
new file mode 100644
index 000000000..21066cf52
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/html/templatedclass.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- templated_callables.cpp -->
+ <title>TemplatedClass Class | templatedcallables</title>
+</head>
+<body>
+<li>TemplatedClass</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#public-functions">Public Functions</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">TemplatedClass Class</h1>
+<span class="small-subtitle" translate="no">template &lt;typename T&gt; class TemplatedClass</span>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;TemplatedClass&gt;</span></td></tr>
+</table></div>
+<ul>
+<li><a href="templatedclass-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="public-functions">Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templatedclass.html#templated_method_with_defaulted_non_type_template_parameter" translate="no">templated_method_with_defaulted_non_type_template_parameter</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templatedclass.html#templated_method_with_defaulted_template_template_parameter" translate="no">templated_method_with_defaulted_template_template_parameter</a></b>(Container&lt;U, size&gt;)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templatedclass.html#templated_method_with_defaulted_type_template_parameter" translate="no">templated_method_with_defaulted_type_template_parameter</a></b>(U)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templatedclass.html#templated_method_with_non_type_template_parameter" translate="no">templated_method_with_non_type_template_parameter</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templatedclass.html#templated_method_with_non_type_template_parameter_pack" translate="no">templated_method_with_non_type_template_parameter_pack</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templatedclass.html#templated_method_with_placeholder_non_type_template_parameter" translate="no">templated_method_with_placeholder_non_type_template_parameter</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templatedclass.html#templated_method_with_template_template_parameter" translate="no">templated_method_with_template_template_parameter</a></b>(X&lt;U&gt;)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templatedclass.html#templated_method_with_template_template_parameter_pack" translate="no">templated_method_with_template_template_parameter_pack</a></b>(Container&lt;U&gt;...)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templatedclass.html#templated_method_with_type_template_parameter" translate="no">templated_method_with_type_template_parameter</a></b>(U)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="templatedclass.html#templated_method_with_type_template_parameter_pack" translate="no">templated_method_with_type_template_parameter_pack</a></b>(Ts...)</td></tr>
+</table></div>
+<!-- $$$TemplatedClass-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<p>Containing record for the tested methods.</p>
+</div>
+<!-- @@@TemplatedClass -->
+<div class="func">
+<h2>Member Function Documentation</h2>
+<!-- $$$templated_method_with_defaulted_non_type_template_parameter[overload1]$$$templated_method_with_defaulted_non_type_template_parameter -->
+<h3 class="fn" translate="no" id="templated_method_with_defaulted_non_type_template_parameter">template &lt;int Size = 10&gt; <span class="type">void</span> TemplatedClass::<span class="name">templated_method_with_defaulted_non_type_template_parameter</span>()</h3>
+<p>A templated method under a templated class with a defaulted non type template parameter.</p>
+<!-- @@@templated_method_with_defaulted_non_type_template_parameter -->
+<!-- $$$templated_method_with_defaulted_template_template_parameter[overload1]$$$templated_method_with_defaulted_template_template_parameterContainer<U,size> -->
+<h3 class="fn" translate="no" id="templated_method_with_defaulted_template_template_parameter">template &lt;typename U, int size, template &lt;typename, int &gt; typename Container = std::array&gt; <span class="type">void</span> TemplatedClass::<span class="name">templated_method_with_defaulted_template_template_parameter</span>(<span class="type">Container</span>&lt;<span class="type">U</span>, <span class="type">size</span>&gt;)</h3>
+<p>A templated method under a templated class with a defaulted template template parameter.</p>
+<!-- @@@templated_method_with_defaulted_template_template_parameter -->
+<!-- $$$templated_method_with_defaulted_type_template_parameter[overload1]$$$templated_method_with_defaulted_type_template_parameterU -->
+<h3 class="fn" translate="no" id="templated_method_with_defaulted_type_template_parameter">template &lt;typename U = bool&gt; <span class="type">void</span> TemplatedClass::<span class="name">templated_method_with_defaulted_type_template_parameter</span>(<span class="type">U</span>)</h3>
+<p>A templated method under a templated class with a defaulted type template parameter.</p>
+<!-- @@@templated_method_with_defaulted_type_template_parameter -->
+<!-- $$$templated_method_with_non_type_template_parameter[overload1]$$$templated_method_with_non_type_template_parameter -->
+<h3 class="fn" translate="no" id="templated_method_with_non_type_template_parameter">template &lt;int Size&gt; <span class="type">void</span> TemplatedClass::<span class="name">templated_method_with_non_type_template_parameter</span>()</h3>
+<p>A templated method under a templated class with a non-defaulted non type template parameter.</p>
+<!-- @@@templated_method_with_non_type_template_parameter -->
+<!-- $$$templated_method_with_non_type_template_parameter_pack[overload1]$$$templated_method_with_non_type_template_parameter_pack -->
+<h3 class="fn" translate="no" id="templated_method_with_non_type_template_parameter_pack">template &lt;int... Dimensions&gt; <span class="type">void</span> TemplatedClass::<span class="name">templated_method_with_non_type_template_parameter_pack</span>()</h3>
+<p>A templated method under a templated class with a non type template parameter pack.</p>
+<!-- @@@templated_method_with_non_type_template_parameter_pack -->
+<!-- $$$templated_method_with_placeholder_non_type_template_parameter[overload1]$$$templated_method_with_placeholder_non_type_template_parameter -->
+<h3 class="fn" translate="no" id="templated_method_with_placeholder_non_type_template_parameter">template &lt;auto Predicate&gt; <span class="type">void</span> TemplatedClass::<span class="name">templated_method_with_placeholder_non_type_template_parameter</span>()</h3>
+<p>A templated method under a templated class with a placeholder non type template parameter.</p>
+<!-- @@@templated_method_with_placeholder_non_type_template_parameter -->
+<!-- $$$templated_method_with_template_template_parameter[overload1]$$$templated_method_with_template_template_parameterX<U> -->
+<h3 class="fn" translate="no" id="templated_method_with_template_template_parameter">template &lt;typename U, template &lt;typename&gt; typename X&gt; <span class="type">void</span> TemplatedClass::<span class="name">templated_method_with_template_template_parameter</span>(<span class="type">X</span>&lt;<span class="type">U</span>&gt;)</h3>
+<p>A templated method under a templated class with a non-defaulted template template parameter.</p>
+<!-- @@@templated_method_with_template_template_parameter -->
+<!-- $$$templated_method_with_template_template_parameter_pack[overload1]$$$templated_method_with_template_template_parameter_packContainer<U>... -->
+<h3 class="fn" translate="no" id="templated_method_with_template_template_parameter_pack">template &lt;typename U, template &lt;typename&gt; typename... Container&gt; <span class="type">void</span> TemplatedClass::<span class="name">templated_method_with_template_template_parameter_pack</span>(<span class="type">Container</span>&lt;<span class="type">U</span>&gt;...)</h3>
+<p>A templated method under a templated class with a template template parameter pack.</p>
+<!-- @@@templated_method_with_template_template_parameter_pack -->
+<!-- $$$templated_method_with_type_template_parameter[overload1]$$$templated_method_with_type_template_parameterU -->
+<h3 class="fn" translate="no" id="templated_method_with_type_template_parameter">template &lt;typename U&gt; <span class="type">void</span> TemplatedClass::<span class="name">templated_method_with_type_template_parameter</span>(<span class="type">U</span>)</h3>
+<p>A templated method under a templated class with a non-defaulted type template parameter.</p>
+<!-- @@@templated_method_with_type_template_parameter -->
+<!-- $$$templated_method_with_type_template_parameter_pack[overload1]$$$templated_method_with_type_template_parameter_packTs... -->
+<h3 class="fn" translate="no" id="templated_method_with_type_template_parameter_pack">template &lt;typename... Ts&gt; <span class="type">void</span> TemplatedClass::<span class="name">templated_method_with_type_template_parameter_pack</span>(<span class="type">Ts</span>...)</h3>
+<p>A templated method under a templated class with a type template parameter pack.</p>
+<!-- @@@templated_method_with_type_template_parameter_pack -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/webxml/templated-callables-h.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/webxml/templated-callables-h.webxml
new file mode 100644
index 000000000..8ae7ad257
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/webxml/templated-callables-h.webxml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <header name="&lt;templated_callables.h&gt;" href="templated-callables-h.html" status="active" documented="true" module="TestQDoc" title="&lt;templated_callables.h&gt;" fulltitle="&lt;templated_callables.h&gt;" subtitle="">
+ <description>
+ <para>Containing headers for the tested free functions.</para>
+ </description>
+ <function name="templated_function_with_defaulted_non_type_template_parameter" href="templated-callables-h.html#templated_function_with_defaulted_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="0" meta="plain" type="void" signature="void templated_function_with_defaulted_non_type_template_parameter()">
+ <description>
+ <para>A templated function with a defaulted non type template parameter.</para>
+ </description>
+ </function>
+ <function name="templated_function_with_defaulted_template_template_parameter" href="templated-callables-h.html#templated_function_with_defaulted_template_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="1" meta="plain" type="void" signature="void templated_function_with_defaulted_template_template_parameter(Container&lt;T, size&gt;)">
+ <parameter type="Container&lt;T, size&gt;" name="" default=""/>
+ <description>
+ <para>A templated function with a defaulted template template parameter.</para>
+ </description>
+ </function>
+ <function name="templated_function_with_defaulted_type_template_parameter" href="templated-callables-h.html#templated_function_with_defaulted_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="2" meta="plain" type="void" signature="void templated_function_with_defaulted_type_template_parameter(T)">
+ <parameter type="T" name="" default=""/>
+ <description>
+ <para>A templated function with a defaulted type template parameter.</para>
+ </description>
+ </function>
+ <function name="templated_function_with_non_type_template_parameter" href="templated-callables-h.html#templated_function_with_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="3" meta="plain" type="void" signature="void templated_function_with_non_type_template_parameter()">
+ <description>
+ <para>A templated function with a non-defaulted non type template parameter.</para>
+ </description>
+ </function>
+ <function name="templated_function_with_non_type_template_parameter_pack" href="templated-callables-h.html#templated_function_with_non_type_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" related="4" meta="plain" type="void" signature="void templated_function_with_non_type_template_parameter_pack()">
+ <description>
+ <para>A templated function with a non type template parameter pack.</para>
+ </description>
+ </function>
+ <function name="templated_function_with_placeholder_non_type_template_parameter" href="templated-callables-h.html#templated_function_with_placeholder_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="5" meta="plain" type="void" signature="void templated_function_with_placeholder_non_type_template_parameter()">
+ <description>
+ <para>A templated function with a placeholder non type template parameter.</para>
+ </description>
+ </function>
+ <function name="templated_function_with_template_template_parameter" href="templated-callables-h.html#templated_function_with_template_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="6" meta="plain" type="void" signature="void templated_function_with_template_template_parameter(K&lt;T&gt;)">
+ <parameter type="K&lt;T&gt;" name="" default=""/>
+ <description>
+ <para>A templated function with a non-defaulted template template parameter.</para>
+ </description>
+ </function>
+ <function name="templated_function_with_template_template_parameter_pack" href="templated-callables-h.html#templated_function_with_template_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" related="7" meta="plain" type="void" signature="void templated_function_with_template_template_parameter_pack(Container&lt;T&gt;...)">
+ <parameter type="Container&lt;T&gt;..." name="" default=""/>
+ <description>
+ <para>A templated function with a template template parameter pack.</para>
+ </description>
+ </function>
+ <function name="templated_function_with_type_template_parameter" href="templated-callables-h.html#templated_function_with_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="8" meta="plain" type="void" signature="void templated_function_with_type_template_parameter(T)">
+ <parameter type="T" name="" default=""/>
+ <description>
+ <para>A templated function with a non-defaulted type template parameter.</para>
+ </description>
+ </function>
+ <function name="templated_function_with_type_template_parameter_pack" href="templated-callables-h.html#templated_function_with_type_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" related="9" meta="plain" type="void" signature="void templated_function_with_type_template_parameter_pack(Ts...)">
+ <parameter type="Ts..." name="" default=""/>
+ <description>
+ <para>A templated function with a type template parameter pack.</para>
+ </description>
+ </function>
+ </header>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/webxml/templatedcallables.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/webxml/templatedcallables.index
new file mode 100644
index 000000000..91245c7f8
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/webxml/templatedcallables.index
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="templatedcallables Reference Documentation" version="" project="templatedcallables">
+ <namespace name="" status="active" access="public" module="templatedcallables">
+ <function name="templated_function_with_defaulted_non_type_template_parameter" href="templated-callables-h.html#templated_function_with_defaulted_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="0" meta="plain" type="void" signature="void templated_function_with_defaulted_non_type_template_parameter()"/>
+ <function name="templated_function_with_defaulted_template_template_parameter" href="templated-callables-h.html#templated_function_with_defaulted_template_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="1" meta="plain" type="void" signature="void templated_function_with_defaulted_template_template_parameter(Container&lt;T, size&gt;)">
+ <parameter type="Container&lt;T, size&gt;" name="" default=""/>
+ </function>
+ <function name="templated_function_with_defaulted_type_template_parameter" href="templated-callables-h.html#templated_function_with_defaulted_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="2" meta="plain" type="void" signature="void templated_function_with_defaulted_type_template_parameter(T)">
+ <parameter type="T" name="" default=""/>
+ </function>
+ <function name="templated_function_with_non_type_template_parameter" href="templated-callables-h.html#templated_function_with_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="3" meta="plain" type="void" signature="void templated_function_with_non_type_template_parameter()"/>
+ <function name="templated_function_with_non_type_template_parameter_pack" href="templated-callables-h.html#templated_function_with_non_type_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" related="4" meta="plain" type="void" signature="void templated_function_with_non_type_template_parameter_pack()"/>
+ <function name="templated_function_with_placeholder_non_type_template_parameter" href="templated-callables-h.html#templated_function_with_placeholder_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="5" meta="plain" type="void" signature="void templated_function_with_placeholder_non_type_template_parameter()"/>
+ <function name="templated_function_with_template_template_parameter" href="templated-callables-h.html#templated_function_with_template_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="6" meta="plain" type="void" signature="void templated_function_with_template_template_parameter(K&lt;T&gt;)">
+ <parameter type="K&lt;T&gt;" name="" default=""/>
+ </function>
+ <function name="templated_function_with_template_template_parameter_pack" href="templated-callables-h.html#templated_function_with_template_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" related="7" meta="plain" type="void" signature="void templated_function_with_template_template_parameter_pack(Container&lt;T&gt;...)">
+ <parameter type="Container&lt;T&gt;..." name="" default=""/>
+ </function>
+ <function name="templated_function_with_type_template_parameter" href="templated-callables-h.html#templated_function_with_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="8" meta="plain" type="void" signature="void templated_function_with_type_template_parameter(T)">
+ <parameter type="T" name="" default=""/>
+ </function>
+ <function name="templated_function_with_type_template_parameter_pack" href="templated-callables-h.html#templated_function_with_type_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" related="9" meta="plain" type="void" signature="void templated_function_with_type_template_parameter_pack(Ts...)">
+ <parameter type="Ts..." name="" default=""/>
+ </function>
+ <header name="&lt;templated_callables.h&gt;" href="templated-callables-h.html" status="active" documented="true" module="TestQDoc" title="&lt;templated_callables.h&gt;" fulltitle="&lt;templated_callables.h&gt;" subtitle="">
+ <function name="templated_function_with_defaulted_non_type_template_parameter" href="templated-callables-h.html#templated_function_with_defaulted_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="0" meta="plain" type="void" signature="void templated_function_with_defaulted_non_type_template_parameter()"/>
+ <function name="templated_function_with_defaulted_template_template_parameter" href="templated-callables-h.html#templated_function_with_defaulted_template_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="1" meta="plain" type="void" signature="void templated_function_with_defaulted_template_template_parameter(Container&lt;T, size&gt;)">
+ <parameter type="Container&lt;T, size&gt;" name="" default=""/>
+ </function>
+ <function name="templated_function_with_defaulted_type_template_parameter" href="templated-callables-h.html#templated_function_with_defaulted_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="2" meta="plain" type="void" signature="void templated_function_with_defaulted_type_template_parameter(T)">
+ <parameter type="T" name="" default=""/>
+ </function>
+ <function name="templated_function_with_non_type_template_parameter" href="templated-callables-h.html#templated_function_with_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="3" meta="plain" type="void" signature="void templated_function_with_non_type_template_parameter()"/>
+ <function name="templated_function_with_non_type_template_parameter_pack" href="templated-callables-h.html#templated_function_with_non_type_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" related="4" meta="plain" type="void" signature="void templated_function_with_non_type_template_parameter_pack()"/>
+ <function name="templated_function_with_placeholder_non_type_template_parameter" href="templated-callables-h.html#templated_function_with_placeholder_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="5" meta="plain" type="void" signature="void templated_function_with_placeholder_non_type_template_parameter()"/>
+ <function name="templated_function_with_template_template_parameter" href="templated-callables-h.html#templated_function_with_template_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="6" meta="plain" type="void" signature="void templated_function_with_template_template_parameter(K&lt;T&gt;)">
+ <parameter type="K&lt;T&gt;" name="" default=""/>
+ </function>
+ <function name="templated_function_with_template_template_parameter_pack" href="templated-callables-h.html#templated_function_with_template_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" related="7" meta="plain" type="void" signature="void templated_function_with_template_template_parameter_pack(Container&lt;T&gt;...)">
+ <parameter type="Container&lt;T&gt;..." name="" default=""/>
+ </function>
+ <function name="templated_function_with_type_template_parameter" href="templated-callables-h.html#templated_function_with_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" related="8" meta="plain" type="void" signature="void templated_function_with_type_template_parameter(T)">
+ <parameter type="T" name="" default=""/>
+ </function>
+ <function name="templated_function_with_type_template_parameter_pack" href="templated-callables-h.html#templated_function_with_type_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" related="9" meta="plain" type="void" signature="void templated_function_with_type_template_parameter_pack(Ts...)">
+ <parameter type="Ts..." name="" default=""/>
+ </function>
+ </header>
+ <class name="TemplatedClass" href="templatedclass.html" status="active" access="public" location="templated_callables.h" documented="true" module="TestQDoc">
+ <function name="templated_method_with_defaulted_non_type_template_parameter" fullname="TemplatedClass::templated_method_with_defaulted_non_type_template_parameter" href="templatedclass.html#templated_method_with_defaulted_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_defaulted_non_type_template_parameter()"/>
+ <function name="templated_method_with_defaulted_template_template_parameter" fullname="TemplatedClass::templated_method_with_defaulted_template_template_parameter" href="templatedclass.html#templated_method_with_defaulted_template_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_defaulted_template_template_parameter(Container&lt;U, size&gt;)">
+ <parameter type="Container&lt;U, size&gt;" name="" default=""/>
+ </function>
+ <function name="templated_method_with_defaulted_type_template_parameter" fullname="TemplatedClass::templated_method_with_defaulted_type_template_parameter" href="templatedclass.html#templated_method_with_defaulted_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_defaulted_type_template_parameter(U)">
+ <parameter type="U" name="" default=""/>
+ </function>
+ <function name="templated_method_with_non_type_template_parameter" fullname="TemplatedClass::templated_method_with_non_type_template_parameter" href="templatedclass.html#templated_method_with_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_non_type_template_parameter()"/>
+ <function name="templated_method_with_non_type_template_parameter_pack" fullname="TemplatedClass::templated_method_with_non_type_template_parameter_pack" href="templatedclass.html#templated_method_with_non_type_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_non_type_template_parameter_pack()"/>
+ <function name="templated_method_with_placeholder_non_type_template_parameter" fullname="TemplatedClass::templated_method_with_placeholder_non_type_template_parameter" href="templatedclass.html#templated_method_with_placeholder_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_placeholder_non_type_template_parameter()"/>
+ <function name="templated_method_with_template_template_parameter" fullname="TemplatedClass::templated_method_with_template_template_parameter" href="templatedclass.html#templated_method_with_template_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_template_template_parameter(X&lt;U&gt;)">
+ <parameter type="X&lt;U&gt;" name="" default=""/>
+ </function>
+ <function name="templated_method_with_template_template_parameter_pack" fullname="TemplatedClass::templated_method_with_template_template_parameter_pack" href="templatedclass.html#templated_method_with_template_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_template_template_parameter_pack(Container&lt;U&gt;...)">
+ <parameter type="Container&lt;U&gt;..." name="" default=""/>
+ </function>
+ <function name="templated_method_with_type_template_parameter" fullname="TemplatedClass::templated_method_with_type_template_parameter" href="templatedclass.html#templated_method_with_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_type_template_parameter(U)">
+ <parameter type="U" name="" default=""/>
+ </function>
+ <function name="templated_method_with_type_template_parameter_pack" fullname="TemplatedClass::templated_method_with_type_template_parameter_pack" href="templatedclass.html#templated_method_with_type_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_type_template_parameter_pack(Ts...)">
+ <parameter type="Ts..." name="" default=""/>
+ </function>
+ </class>
+ <module name="TestQDoc" href="testqdoc-module.html" status="internal" seen="false" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/webxml/templatedclass.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/webxml/templatedclass.webxml
new file mode 100644
index 000000000..29c0bc661
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/expected/webxml/templatedclass.webxml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="TemplatedClass" href="templatedclass.html" status="active" access="public" location="templated_callables.h" documented="true" module="TestQDoc">
+ <description>
+ <para>Containing record for the tested methods.</para>
+ </description>
+ <function name="templated_method_with_defaulted_non_type_template_parameter" fullname="TemplatedClass::templated_method_with_defaulted_non_type_template_parameter" href="templatedclass.html#templated_method_with_defaulted_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_defaulted_non_type_template_parameter()">
+ <description>
+ <para>A templated method under a templated class with a defaulted non type template parameter.</para>
+ </description>
+ </function>
+ <function name="templated_method_with_defaulted_template_template_parameter" fullname="TemplatedClass::templated_method_with_defaulted_template_template_parameter" href="templatedclass.html#templated_method_with_defaulted_template_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_defaulted_template_template_parameter(Container&lt;U, size&gt;)">
+ <parameter type="Container&lt;U, size&gt;" name="" default=""/>
+ <description>
+ <para>A templated method under a templated class with a defaulted template template parameter.</para>
+ </description>
+ </function>
+ <function name="templated_method_with_defaulted_type_template_parameter" fullname="TemplatedClass::templated_method_with_defaulted_type_template_parameter" href="templatedclass.html#templated_method_with_defaulted_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_defaulted_type_template_parameter(U)">
+ <parameter type="U" name="" default=""/>
+ <description>
+ <para>A templated method under a templated class with a defaulted type template parameter.</para>
+ </description>
+ </function>
+ <function name="templated_method_with_non_type_template_parameter" fullname="TemplatedClass::templated_method_with_non_type_template_parameter" href="templatedclass.html#templated_method_with_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_non_type_template_parameter()">
+ <description>
+ <para>A templated method under a templated class with a non-defaulted non type template parameter.</para>
+ </description>
+ </function>
+ <function name="templated_method_with_non_type_template_parameter_pack" fullname="TemplatedClass::templated_method_with_non_type_template_parameter_pack" href="templatedclass.html#templated_method_with_non_type_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_non_type_template_parameter_pack()">
+ <description>
+ <para>A templated method under a templated class with a non type template parameter pack.</para>
+ </description>
+ </function>
+ <function name="templated_method_with_placeholder_non_type_template_parameter" fullname="TemplatedClass::templated_method_with_placeholder_non_type_template_parameter" href="templatedclass.html#templated_method_with_placeholder_non_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_placeholder_non_type_template_parameter()">
+ <description>
+ <para>A templated method under a templated class with a placeholder non type template parameter.</para>
+ </description>
+ </function>
+ <function name="templated_method_with_template_template_parameter" fullname="TemplatedClass::templated_method_with_template_template_parameter" href="templatedclass.html#templated_method_with_template_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_template_template_parameter(X&lt;U&gt;)">
+ <parameter type="X&lt;U&gt;" name="" default=""/>
+ <description>
+ <para>A templated method under a templated class with a non-defaulted template template parameter.</para>
+ </description>
+ </function>
+ <function name="templated_method_with_template_template_parameter_pack" fullname="TemplatedClass::templated_method_with_template_template_parameter_pack" href="templatedclass.html#templated_method_with_template_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_template_template_parameter_pack(Container&lt;U&gt;...)">
+ <parameter type="Container&lt;U&gt;..." name="" default=""/>
+ <description>
+ <para>A templated method under a templated class with a template template parameter pack.</para>
+ </description>
+ </function>
+ <function name="templated_method_with_type_template_parameter" fullname="TemplatedClass::templated_method_with_type_template_parameter" href="templatedclass.html#templated_method_with_type_template_parameter" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_type_template_parameter(U)">
+ <parameter type="U" name="" default=""/>
+ <description>
+ <para>A templated method under a templated class with a non-defaulted type template parameter.</para>
+ </description>
+ </function>
+ <function name="templated_method_with_type_template_parameter_pack" fullname="TemplatedClass::templated_method_with_type_template_parameter_pack" href="templatedclass.html#templated_method_with_type_template_parameter_pack" status="active" access="public" location="templated_callables.h" documented="true" meta="plain" type="void" signature="void templated_method_with_type_template_parameter_pack(Ts...)">
+ <parameter type="Ts..." name="" default=""/>
+ <description>
+ <para>A templated method under a templated class with a type template parameter pack.</para>
+ </description>
+ </function>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/src/templated_callables.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/src/templated_callables.cpp
new file mode 100644
index 000000000..9e91242e3
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/src/templated_callables.cpp
@@ -0,0 +1,149 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#pragma once
+
+#include "templated_callables.h"
+
+/*!
+ * \headerfile <templated_callables.h>
+ * \inmodule TestQDoc
+ *
+ * Containing headers for the tested free functions.
+ */
+
+/*!
+ * \class TemplatedClass
+ * \inmodule TestQDoc
+ *
+ * Containing record for the tested methods.
+ */
+
+/*!
+ * \fn template<typename T> template <typename U> void TemplatedClass<T>::templated_method_with_type_template_parameter(U)
+ *
+ * A templated method under a templated class with a non-defaulted type template parameter.
+ */
+
+/*!
+ * \fn template<typename T> void templated_function_with_type_template_parameter(T)
+ * \relates <templated_callables.h>
+ *
+ * A templated function with a non-defaulted type template parameter.
+ */
+
+/*!
+ * \fn template<typename T> template <typename U> void TemplatedClass<T>::templated_method_with_defaulted_type_template_parameter(U)
+ *
+ * A templated method under a templated class with a defaulted type template parameter.
+ */
+
+/*!
+ * \fn template<typename T> void templated_function_with_defaulted_type_template_parameter(T)
+ * \relates <templated_callables.h>
+ *
+ * A templated function with a defaulted type template parameter.
+ */
+
+/*!
+ * \fn template<typename T> template <typename... Ts> void TemplatedClass<T>::templated_method_with_type_template_parameter_pack(Ts...)
+ *
+ * A templated method under a templated class with a type template parameter pack.
+ */
+
+/*!
+ * \fn template<typename... Ts> void templated_function_with_type_template_parameter_pack(Ts...)
+ * \relates <templated_callables.h>
+ *
+ * A templated function with a type template parameter pack.
+ */
+
+/*!
+ * \fn template<typename T> template <int Size> void TemplatedClass<T>::templated_method_with_non_type_template_parameter()
+ *
+ * A templated method under a templated class with a non-defaulted non type template parameter.
+ */
+
+/*!
+ * \fn template<char Category> void templated_function_with_non_type_template_parameter()
+ * \relates <templated_callables.h>
+ *
+ * A templated function with a non-defaulted non type template parameter.
+ */
+
+/*!
+ * \fn template<typename T> template <int Size = 10> void TemplatedClass<T>::templated_method_with_defaulted_non_type_template_parameter()
+ *
+ * A templated method under a templated class with a defaulted non type template parameter.
+ */
+
+/*!
+ * \fn template<char Category = 'A'> void templated_function_with_defaulted_non_type_template_parameter()
+ * \relates <templated_callables.h>
+ *
+ * A templated function with a defaulted non type template parameter.
+ */
+
+/*!
+ * \fn template<typename T> template <int... Dimensions> void TemplatedClass<T>::templated_method_with_non_type_template_parameter_pack()
+ *
+ * A templated method under a templated class with a non type template parameter pack.
+ */
+
+/*!
+ * \fn template<unsigned... Weights> void templated_function_with_non_type_template_parameter_pack()
+ * \relates <templated_callables.h>
+ *
+ * A templated function with a non type template parameter pack.
+ */
+
+/*!
+ * \fn template<typename T> template <auto Predicate> void TemplatedClass<T>::templated_method_with_placeholder_non_type_template_parameter()
+ *
+ * A templated method under a templated class with a placeholder non type template parameter.
+ */
+
+/*!
+ * \fn template<auto Iterator> void templated_function_with_placeholder_non_type_template_parameter()
+ * \relates <templated_callables.h>
+ *
+ * A templated function with a placeholder non type template parameter.
+ */
+
+/*!
+ * \fn template<typename T> template <typename U, template <typename> typename X> void TemplatedClass<T>::templated_method_with_template_template_parameter(X<U>)
+ *
+ * A templated method under a templated class with a non-defaulted template template parameter.
+ */
+
+/*!
+ * \fn template <typename T, template <typename> typename K> void templated_function_with_template_template_parameter(K<T>)
+ * \relates <templated_callables.h>
+ *
+ * A templated function with a non-defaulted template template parameter.
+ */
+
+/*!
+ * \fn template<typename T> template <typename U, int size, template <typename, int> typename Container = std::array> void TemplatedClass<T>::templated_method_with_defaulted_template_template_parameter(Container<U, size>)
+ *
+ * A templated method under a templated class with a defaulted template template parameter.
+ */
+
+/*!
+ * \fn template <typename T, int size, template <typename, int> typename Container = std::array> void templated_function_with_defaulted_template_template_parameter(Container<T, size>)
+ * \relates <templated_callables.h>
+ *
+ * A templated function with a defaulted template template parameter.
+ */
+
+/*!
+ * \fn template<typename T> template <typename U, template <typename> typename... Container> void TemplatedClass<T>::templated_method_with_template_template_parameter_pack(Container<U>...)
+ *
+ * A templated method under a templated class with a template template parameter pack.
+ */
+
+/*!
+ * \fn template <typename T, template <typename> typename... Container> void templated_function_with_template_template_parameter_pack(Container<T>...)
+ * \relates <templated_callables.h>
+ *
+ * A templated function with a template template parameter pack.
+ */
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/src/templated_callables.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/src/templated_callables.h
new file mode 100644
index 000000000..c745ccad2
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/src/templated_callables.h
@@ -0,0 +1,73 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#pragma once
+
+// dummy declaration
+namespace std {
+ template <class T, int N> struct array;
+}
+
+template <typename T>
+class TemplatedClass
+{
+public:
+ template <typename U>
+ void templated_method_with_type_template_parameter(U);
+
+ template <typename U = bool>
+ void templated_method_with_defaulted_type_template_parameter(U);
+
+ template <typename... Ts>
+ void templated_method_with_type_template_parameter_pack(Ts...);
+
+ template <int Size>
+ void templated_method_with_non_type_template_parameter();
+
+ template <int Size = 10>
+ void templated_method_with_defaulted_non_type_template_parameter();
+
+ template <int... Dimensions>
+ void templated_method_with_non_type_template_parameter_pack();
+
+ template <auto Predicate>
+ void templated_method_with_placeholder_non_type_template_parameter();
+
+ template <typename U, template <typename> typename X>
+ void templated_method_with_template_template_parameter(X<U>);
+
+ template <typename U, int size, template <typename, int> typename Container = std::array>
+ void templated_method_with_defaulted_template_template_parameter(Container<U, size>);
+
+ template <typename U, template <typename> typename... Container>
+ void templated_method_with_template_template_parameter_pack(Container<U>...);
+};
+
+template <typename T>
+void templated_function_with_type_template_parameter(T);
+
+template <typename T = char>
+void templated_function_with_defaulted_type_template_parameter(T);
+
+template <typename... Ts>
+void templated_function_with_type_template_parameter_pack(Ts...);
+
+template <char Category>
+void templated_function_with_non_type_template_parameter();
+
+template <char Category = 'A'>
+void templated_function_with_defaulted_non_type_template_parameter();
+
+template <unsigned... Weights>
+void templated_function_with_non_type_template_parameter_pack();
+
+template <auto Iterator>
+void templated_function_with_placeholder_non_type_template_parameter();
+
+template <typename T, template <typename> typename K>
+void templated_function_with_template_template_parameter(K<T>);
+
+template <typename T, int size, template <typename, int> typename Container = std::array>
+void templated_function_with_defaulted_template_template_parameter(Container<T, size>);
+
+template <typename T, template <typename> typename... Container>
+void templated_function_with_template_template_parameter_pack(Container<T>...);
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/templatedcallables.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/templatedcallables.qdocconf
new file mode 100644
index 000000000..d7a7c46ef
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/templatedcallables/templatedcallables.qdocconf
@@ -0,0 +1,29 @@
+project = templatedcallables
+
+headerdirs = ./src
+sourcedirs = ./src
+exampledirs = ./src
+outputformats = WebXML HTML DocBook
+
+WebXML.quotinginformation = true
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
+
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+
+DocBook.nosubdirs = true
+DocBook.outputsubdir = docbook
+
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ./src/images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/autolinking.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/autolinking.xml
new file mode 100644
index 000000000..b9e9b7eb0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/autolinking.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Autolinking</db:title>
+<db:productname>TestTemplate</db:productname>
+<db:titleabbrev>TestTemplate Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>TestTemplate Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:section xml:id="testqdoc">
+<db:title>TestQDoc</db:title>
+<db:para>The string <db:link xlink:href="testqdoc.xml">TestQDoc</db:link> links to the C++ namespace unless linking explicitly, <db:link xlink:href="autolinking.xml#testqdoc">like this</db:link>, or <db:link xlink:href="testqdoc.xml">this</db:link>. Also,</db:para>
+<db:para>Autolinks:</db:para>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-testderived.xml">TestQDoc::TestDerived</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+<db:para>Explicit links:</db:para>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-testderived.xml">TestQDoc::TestDerived</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="obsolete-classes.xml#testqdoc">Obsolete Classes#TestQDoc</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+<db:section xml:id="someprop">
+<db:title>someProp</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/bar.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/bar.xml
new file mode 100644
index 000000000..9c54fe914
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/bar.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Bar Class</db:title>
+<db:subtitle>template &lt;typename T, typename D&gt; class Bar</db:subtitle>
+<db:productname>TestTemplate</db:productname>
+<db:titleabbrev>TestTemplate Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>Another class template.</db:para>
+<db:para>This class was introduced in Qt 2.0.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Bar</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 2.0</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/baz.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/baz.xml
new file mode 100644
index 000000000..cf36a35b1
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/baz.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title></db:title>
+<db:productname>TestTemplate</db:productname>
+<db:titleabbrev>TestTemplate Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>Class template template.</db:para>
+<db:para>This struct was introduced in Qt 2.0.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Baz</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 2.0</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/cpptypes.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/cpptypes.xml
new file mode 100644
index 000000000..8be7809ec
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/cpptypes.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Test C++ Types</db:title>
+<db:productname>TestTemplate</db:productname>
+<db:titleabbrev>TestTemplate Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>TestTemplate Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:anchor xml:id="details"/>
+<db:itemizedlist role="testgroup">
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml" xlink:role="class">TestQDoc::Test</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#QDOCTEST_MACRO2" xlink:role="function">TestQDoc::Test::QDOCTEST_MACRO2</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#someFunctionDefaultArg" xlink:role="function">TestQDoc::Test::someFunctionDefaultArg()</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+<db:variablelist role="members">
+<db:varlistentry>
+<db:term><db:link xlink:href="testqdoc-test.xml" xlink:role="class">TestQDoc::Test</db:link></db:term>
+<db:listitem>
+<db:para>A class in a namespace.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/crossmoduleref.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/crossmoduleref.xml
new file mode 100644
index 000000000..b86d8f01b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/crossmoduleref.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>CrossModuleRef Namespace</db:title>
+<db:productname>TestTemplate</db:productname>
+<db:titleabbrev>TestTemplate Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>Namespace that has documented functions in multiple modules.</db:para>
+<db:para>This namespace was introduced in Qt 3.0.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>CrossModuleRef</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 3.0</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="function-documentation">
+<db:title>Function Documentation</db:title>
+<db:section xml:id="documentMe">
+<db:title>void CrossModuleRef::documentMe()</db:title>
+<db:para>Document me!</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/foo.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/foo.xml
new file mode 100644
index 000000000..da6867876
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/foo.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Foo Class</db:title>
+<db:subtitle>template &lt;typename T&gt; class Foo</db:subtitle>
+<db:productname>TestTemplate</db:productname>
+<db:titleabbrev>TestTemplate Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>Class template.</db:para>
+<db:para>This class was introduced in Qt 2.0.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Foo</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 2.0</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/obsolete-classes.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/obsolete-classes.xml
new file mode 100644
index 000000000..50090db37
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/obsolete-classes.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Obsolete Classes</db:title>
+<db:productname>TestTemplate</db:productname>
+<db:titleabbrev>TestTemplate Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>TestTemplate Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:section xml:id="classes-with-obsolete-members">
+<db:title>Classes with obsolete members</db:title>
+<db:variablelist role="obsoletecppmembers">
+<db:varlistentry>
+<db:term><db:emphasis role="bold">T</db:emphasis></db:term>
+<db:listitem>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml" role="class">Test</db:link> (<db:link xlink:href="testqdoc.xml" xlink:role="namespace">TestQDoc</db:link>)</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-testderived.xml" role="class">TestDerived</db:link> (<db:link xlink:href="testqdoc.xml" xlink:role="namespace">TestQDoc</db:link>)</db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="testqdoc">
+<db:title>TestQDoc</db:title>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testcpp-module.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testcpp-module.xml
new file mode 100644
index 000000000..d7a8ea674
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testcpp-module.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>QDoc Test C++ Classes</db:title>
+<db:productname>TestTemplate</db:productname>
+<db:titleabbrev>TestTemplate Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>A test module page.</db:para>
+<db:para>This module was introduced in Qt 2.0.</db:para>
+</db:abstract>
+</db:info>
+<db:para>A test module page.</db:para>
+<db:para>This module was introduced in Qt 2.0.</db:para>
+<db:section xml:id="namespaces">
+<db:title>Namespaces</db:title>
+<db:variablelist role="namespaces">
+<db:varlistentry>
+<db:term><db:link xlink:href="crossmoduleref.xml" xlink:role="namespace">CrossModuleRef</db:link></db:term>
+<db:listitem>
+<db:para>Namespace that has documented functions in multiple modules.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="testqdoc.xml" xlink:role="namespace">TestQDoc</db:link></db:term>
+<db:listitem>
+<db:para>A namespace.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:section>
+<db:section xml:id="classes">
+<db:title>Classes</db:title>
+<db:variablelist role="classes">
+<db:varlistentry>
+<db:term><db:link xlink:href="bar.xml" xlink:role="class">Bar</db:link></db:term>
+<db:listitem>
+<db:para>Another class template.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="baz.xml" xlink:role="class">Baz</db:link></db:term>
+<db:listitem>
+<db:para>Class template template.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="foo.xml" xlink:role="class">Foo</db:link></db:term>
+<db:listitem>
+<db:para>Class template.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="testqdoc-test.xml" xlink:role="class">TestQDoc::Test</db:link></db:term>
+<db:listitem>
+<db:para>A class in a namespace.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="testqdoc-test-struct.xml" xlink:role="class">TestQDoc::Test::Struct</db:link></db:term>
+<db:listitem>
+<db:para>Templated struct.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="testqdoc-testderived.xml" xlink:role="class">TestQDoc::TestDerived</db:link></db:term>
+<db:listitem>
+<db:para>A class in a namespace, derived from Test.</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term><db:link xlink:href="testqdoc-vec.xml" xlink:role="class">TestQDoc::Vec</db:link></db:term>
+<db:listitem>
+<db:para>Type alias that has its own reference.</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+</db:section>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:note>
+<db:para>This is just a test. /* Look, Ma! {I'm made of arguments!} */</db:para>
+</db:note>
+<db:section xml:id="linking-to-function-like-things">
+<db:title>Linking to function-like things</db:title>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#someFunctionDefaultArg">someFunctionDefaultArg</db:link>()</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#QDOCTEST_MACRO2">QDOCTEST_MACRO2</db:link>()</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#QDOCTEST_MACRO2">QDOCTEST_MACRO2</db:link>(int &amp;x)</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testcpp-module.xml#section">section()</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="testcpp-module.xml#section">section() is a section title</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+<db:section xml:id="section">
+<db:title>section()</db:title>
+</db:section>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-test-struct.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-test-struct.xml
new file mode 100644
index 000000000..67962de28
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-test-struct.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title></db:title>
+<db:productname>TestTemplate</db:productname>
+<db:titleabbrev>TestTemplate Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>Templated struct.</db:para>
+<db:para>This struct was introduced in Qt 2.0.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Struct</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 2.0</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-test.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-test.xml
new file mode 100644
index 000000000..bf8c99483
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-test.xml
@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Test Class</db:title>
+<db:subtitle>TestQDoc::Test</db:subtitle>
+<db:productname>TestTemplate</db:productname>
+<db:titleabbrev>TestTemplate Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>A class in a namespace.</db:para>
+<db:para>This class was introduced in Qt 2.0.</db:para>
+<db:note>
+<db:para>All functions in this class are <db:link xlink:href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</db:link> with the following exceptions:</db:para>
+<db:para>These functions are not <db:link xlink:href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</db:link>:</db:para>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml#someFunctionDefaultArg">someFunctionDefaultArg(int i, bool b) const</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:note>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Test</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 2.0</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Inherited By</db:term>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-testderived.xml" xlink:role="class">TestQDoc::TestDerived</db:link></db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Group</db:term>
+<db:listitem>
+<db:para>Test is part of <db:simplelist><db:member>testgroup</db:member><db:member><db:link xlink:href="cpptypes.xml">Test C++ Types</db:link></db:member></db:simplelist>
+</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="member-type-documentation">
+<db:title>Member Type Documentation</db:title>
+<db:section xml:id="SomeType-typedef">
+<db:title>Test::SomeType</db:title>
+<db:para>A typedef.</db:para>
+</db:section>
+<db:section xml:id="Specialized-typedef">
+<db:title>[alias] template &lt;typename T&gt; Test::Specialized</db:title>
+</db:section>
+</db:section>
+<db:section xml:id="member-function-documentation">
+<db:title>Member Function Documentation</db:title>
+<db:section xml:id="overload">
+<db:title>[protected] void Test::overload()</db:title>
+<db:bridgehead renderas="sect2" xml:id="overload-1">[protected, since Test 1.2] void Test::overload(bool <db:emphasis>b</db:emphasis>)</db:bridgehead>
+<db:para>Overloads that share a documentation comment, optionally taking a parameter <db:code role="parameter">b</db:code>.</db:para>
+</db:section>
+<db:section xml:id="funcPtr">
+<db:title>void (*)(bool) Test::funcPtr(bool <db:emphasis>b</db:emphasis>, const char *<db:emphasis>s</db:emphasis>)</db:title>
+<db:para>Returns a pointer to a function that takes a boolean. Uses <db:code role="parameter">b</db:code> and <db:code role="parameter">s</db:code>.</db:para>
+</db:section>
+<db:section xml:id="funcTemplate">
+<db:title>[protected] void Test::funcTemplate(T1 <db:emphasis>a</db:emphasis>, T2 <db:emphasis>b</db:emphasis>)</db:title>
+<db:para>Function template with two parameters, <db:code role="parameter">a</db:code> and <db:code role="parameter">b</db:code>.</db:para>
+</db:section>
+<db:section xml:id="inlineFunction">
+<db:title>void Test::inlineFunction()</db:title>
+<db:para>An inline function, documented using the \fn QDoc command.</db:para>
+</db:section>
+<db:section xml:id="methodWithEmDashInItsDocs">
+<db:title>void Test::methodWithEmDashInItsDocs()</db:title>
+<db:para>This method has em dashes in its documentation—as you'll find represented by <db:code>---</db:code> in the sources—here and there. The important bit to note is that when passed e.g. to the \c command, the three hyphens are processed as input to the command and not replaced by an em dash.</db:para>
+<db:para>-----------------------------------------------------------------------</db:para>
+<db:para>People can still add a bunch of dashes, though, without QDoc replacing them all with a series of em dashes.</db:para>
+<db:para>—You can also start a new paragraph with an em dash, if you want to.</db:para>
+<db:section>
+<db:title>See Also</db:title>
+<db:para><db:emphasis>See also </db:emphasis>
+<db:simplelist type="vert" role="see-also">
+<db:member><db:link xlink:href="testqdoc-test.xml#methodWithEnDashInItsDocs">methodWithEnDashInItsDocs</db:link></db:member>
+</db:simplelist>
+</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="methodWithEnDashInItsDocs">
+<db:title>void Test::methodWithEnDashInItsDocs()</db:title>
+<db:para>This method has en dashes in its documentation – as you'll find represented by <db:code>--</db:code> in the sources – here and there. The important bit to note is that when passed e.g. to the \c command, the two hyphens are processed as input to the command and not replaced by an en dash. This also applies to code blocks, where otherwise, the decrement operator would get completely borked:</db:para>
+<db:programlisting language="cpp">for (int i = 42; i &amp;gt; 0; --i)
+ // Do something cool during countdown.
+</db:programlisting>
+<db:para>...as it would be silly if this would output –i instead of <db:code>--i</db:code>.</db:para>
+<db:para>-----------------------------------------------------------------------</db:para>
+<db:para>It still allows people to add a bunch of dashes, though, without replacing them all with a series of en dashes. Of course, they might want to use the \hr command instead, like this:</db:para>
+<db:para>– You can also start a new paragraph with an en dash, if you want to.</db:para>
+<db:section>
+<db:title>See Also</db:title>
+<db:para><db:emphasis>See also </db:emphasis>
+<db:simplelist type="vert" role="see-also">
+<db:member><db:link xlink:href="">methodWithEnDashInItsDocs</db:link></db:member>
+</db:simplelist>
+</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="someFunction">
+<db:title>int Test::someFunction(<db:emphasis>int</db:emphasis>, int <db:emphasis>v</db:emphasis> = 0)</db:title>
+<db:para>Function that takes a parameter <db:code role="parameter">v</db:code>. Also returns the value of <db:code role="parameter">v</db:code>.</db:para>
+</db:section>
+<db:section xml:id="someFunctionDefaultArg">
+<db:title>void Test::someFunctionDefaultArg(int <db:emphasis>i</db:emphasis>, bool <db:emphasis>b</db:emphasis> = false) const</db:title>
+<db:para>Function that takes a parameter <db:code role="parameter">i</db:code> and <db:code role="parameter">b</db:code>.</db:para>
+<db:warning>
+<db:para>This function is not <db:link xlink:href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</db:link>.</db:para>
+</db:warning></db:section>
+<db:section xml:id="virtualFun">
+<db:title>[virtual] void Test::virtualFun()</db:title>
+<db:para>Function that must be reimplemented.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="related-non-members">
+<db:title>Related Non-Members</db:title>
+<db:section xml:id="operator-eq-eq">
+<db:title>bool operator==(const TestQDoc::Test &amp;<db:emphasis>lhs</db:emphasis>, const TestQDoc::Test &amp;<db:emphasis>rhs</db:emphasis>)</db:title>
+<db:para>Returns true if <db:code role="parameter">lhs</db:code> and <db:code role="parameter">rhs</db:code> are equal.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="macro-documentation">
+<db:title>Macro Documentation</db:title>
+<db:section xml:id="QDOCTEST_MACRO2">
+<db:title>[since Test 1.1] QDOCTEST_MACRO2(int &amp;<db:emphasis>x</db:emphasis>)</db:title>
+<db:para>A macro with argument <db:code role="parameter">x</db:code>.</db:para>
+<db:para>This macro was introduced in Test 1.1.</db:para>
+</db:section>
+<db:section xml:id="Q_INVOKABLE">
+<db:title>Q_INVOKABLE</db:title>
+<db:para>This is a mock Q_INVOKABLE for the purpose of ensuring QDoc autolink to it as expected.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="obsolete">
+<db:title>Obsolete Members for Test</db:title>
+<db:para><db:emphasis role="bold">The following members of class <db:link xlink:href="testqdoc-test.xml">Test</db:link> are deprecated.</db:emphasis> We strongly advise against using them in new code.</db:para>
+<db:section xml:id="obsolete-member-function-documentation">
+<db:title>Obsolete Member Function Documentation</db:title>
+<db:section xml:id="operator-2b-2b">
+<db:title>[deprecated] TestQDoc::Test &amp;Test::operator++()</db:title>
+<db:bridgehead renderas="sect2" xml:id="operator--">[deprecated] TestQDoc::Test &amp;Test::operator--()</db:bridgehead>
+<db:para>This function is deprecated. We strongly advise against using it in new code.</db:para>
+</db:section>
+<db:section xml:id="anotherObsoleteMember">
+<db:title>[deprecated] void Test::anotherObsoleteMember()</db:title>
+<db:para>This function is deprecated. We strongly advise against using it in new code.</db:para>
+<db:para>Use <db:link xlink:href="testqdoc-test.xml#obsoleteMember">obsoleteMember</db:link>() instead.</db:para>
+</db:section>
+<db:section xml:id="deprecatedMember">
+<db:title>[deprecated in 6.0] void Test::deprecatedMember()</db:title>
+<db:para>This function is deprecated since 6.0. We strongly advise against using it in new code.</db:para>
+<db:para>Use <db:link xlink:href="testqdoc-test.xml#someFunction">someFunction</db:link>() instead.</db:para>
+</db:section>
+<db:section xml:id="obsoleteMember">
+<db:title>[deprecated] void Test::obsoleteMember()</db:title>
+<db:para>This function is deprecated. We strongly advise against using it in new code.</db:para>
+<db:para>Use <db:link xlink:href="testqdoc-test.xml#someFunction">someFunction</db:link>() instead.</db:para>
+</db:section>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-testderived.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-testderived.xml
new file mode 100644
index 000000000..602faea3b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-testderived.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>TestDerived Class</db:title>
+<db:subtitle>TestQDoc::TestDerived</db:subtitle>
+<db:productname>TestTemplate</db:productname>
+<db:titleabbrev>TestTemplate Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>A class in a namespace, derived from <db:link xlink:href="testqdoc-test.xml">Test</db:link>.</db:para>
+<db:para>This class was introduced in Qt 2.0.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>TestDerived</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 2.0</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Inherits</db:term>
+<db:listitem>
+<db:para><db:link xlink:href="testqdoc-test.xml" xlink:role="class">TestQDoc::Test</db:link></db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="member-type-documentation">
+<db:title>Member Type Documentation</db:title>
+<db:section xml:id="DerivedType-typedef">
+<db:title>[alias] TestDerived::DerivedType</db:title>
+<db:para>An aliased typedef.</db:para>
+</db:section>
+<db:section xml:id="NotTypedef-typedef">
+<db:title>[alias] TestDerived::NotTypedef</db:title>
+<db:para>I'm an alias, not a typedef.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="member-function-documentation">
+<db:title>Member Function Documentation</db:title>
+<db:section xml:id="someValue">
+<db:title>TestQDoc::TestDerived::NotTypedef TestDerived::someValue()</db:title>
+<db:para>Returns a value using an aliases type.</db:para>
+</db:section>
+<db:section xml:id="virtualFun">
+<db:title>[override virtual] void TestDerived::virtualFun()</db:title>
+<db:para>Reimplements: <db:link xlink:href="testqdoc-test.xml#virtualFun" role="function">Test::virtualFun()</db:link>.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="obsolete">
+<db:title>Obsolete Members for TestDerived</db:title>
+<db:para><db:emphasis role="bold">The following members of class <db:link xlink:href="testqdoc-testderived.xml">TestDerived</db:link> are deprecated.</db:emphasis> We strongly advise against using them in new code.</db:para>
+<db:section xml:id="obsolete-member-function-documentation">
+<db:title>Obsolete Member Function Documentation</db:title>
+<db:section xml:id="staticObsoleteMember">
+<db:title>[static, deprecated] void TestDerived::staticObsoleteMember()</db:title>
+<db:para>This function is deprecated. We strongly advise against using it in new code.</db:para>
+<db:para>Static obsolete method.</db:para>
+</db:section>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-vec.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-vec.xml
new file mode 100644
index 000000000..d276da7af
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc-vec.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Vec Class</db:title>
+<db:subtitle>template &lt;typename T&gt; class TestQDoc::Vec</db:subtitle>
+<db:productname>TestTemplate</db:productname>
+<db:titleabbrev>TestTemplate Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>Type alias that has its own reference.</db:para>
+<db:para>This class was introduced in Qt 2.0.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Vec</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 2.0</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc.xml
new file mode 100644
index 000000000..cd575b1e8
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/docbook/testqdoc.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>TestQDoc Namespace</db:title>
+<db:productname>TestTemplate</db:productname>
+<db:titleabbrev>TestTemplate Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>A namespace.</db:para>
+<db:para>This namespace was introduced in Qt 2.0.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>TestCPP</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>Since</db:term>
+<db:listitem>
+<db:para>Qt 2.0</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>CMake</db:term>
+<db:listitem>
+<db:para>find_package(Qt6 REQUIRED COMPONENTS QDocTest)</db:para>
+<db:para>target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</db:para>
+</db:listitem>
+</db:varlistentry>
+<db:varlistentry>
+<db:term>qmake</db:term>
+<db:listitem>
+<db:para>QT += testcpp</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+<db:section xml:id="usage">
+<db:title>Usage</db:title>
+<db:para>This namespace is for testing QDoc output.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="classes">
+<db:title>Classes</db:title>
+<db:section>
+<db:title>class <db:link xlink:href="testqdoc-test.xml" xlink:role="class">Test</db:link></db:title>
+<db:para>A class in a namespace.</db:para>
+</db:section>
+<db:section>
+<db:title>class <db:link xlink:href="testqdoc-testderived.xml" xlink:role="class">TestDerived</db:link></db:title>
+<db:para>A class in a namespace, derived from <db:link xlink:href="testqdoc-test.xml">Test</db:link>.</db:para>
+</db:section>
+<db:section>
+<db:title>class <db:link xlink:href="testqdoc-vec.xml" xlink:role="class">Vec</db:link></db:title>
+<db:para>Type alias that has its own reference.</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="macro-documentation">
+<db:title>Macro Documentation</db:title>
+<db:section xml:id="QDOCTEST_MACRO">
+<db:title>QDOCTEST_MACRO</db:title>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/autolinking.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/autolinking.html
new file mode 100644
index 000000000..689afdfb9
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/autolinking.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classlists.qdoc -->
+ <title>Autolinking | TestTemplate</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#testqdoc">TestQDoc</a></li>
+<li class="level1"><a href="#someprop">someProp</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Autolinking</h1>
+<!-- $$$autolinking.html-description -->
+<div class="descr" id="details">
+<h2 id="testqdoc">TestQDoc</h2>
+<p>The string <a href="testqdoc.html" translate="no">TestQDoc</a> links to the C++ namespace unless linking explicitly, <a href="autolinking.html#testqdoc">like this</a>, or <a href="testqdoc.html" translate="no">this</a>. Also,</p>
+<p>Autolinks:</p>
+<ul>
+<li><a href="testqdoc-testderived.html" translate="no">TestQDoc::TestDerived</a></li>
+</ul>
+<p>Explicit links:</p>
+<ul>
+<li><a href="testqdoc-testderived.html" translate="no">TestQDoc::TestDerived</a></li>
+<li><a href="obsolete-classes.html#testqdoc">Obsolete Classes#TestQDoc</a></li>
+</ul>
+<h2 id="someprop">someProp</h2>
+</div>
+<!-- @@@autolinking.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/bar.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/bar.html
new file mode 100644
index 000000000..3ce7052c1
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/bar.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testtemplate.cpp -->
+ <meta name="description" content="Another class template.">
+ <title>Bar Class | TestTemplate</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Bar</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Bar Class</h1>
+<span class="small-subtitle" translate="no">template &lt;typename T, typename D&gt; class Bar</span>
+<!-- $$$Bar-brief -->
+<p>Another class template. <a href="#details">More...</a></p>
+<!-- @@@Bar -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Bar&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+</table></div>
+<!-- $$$Bar-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Bar -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/baz.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/baz.html
new file mode 100644
index 000000000..03738ff89
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/baz.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testtemplate.cpp -->
+ <meta name="description" content="Class template template.">
+ <title>Baz Struct | TestTemplate</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Baz</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Baz Struct</h1>
+<span class="small-subtitle" translate="no">template &lt;template &lt;typename&gt; typename X, typename Y&gt; struct Baz</span>
+<!-- $$$Baz-brief -->
+<p>Class template template. <a href="#details">More...</a></p>
+<!-- @@@Baz -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Baz&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+</table></div>
+<!-- $$$Baz-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Baz -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/cpptypes.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/cpptypes.html
new file mode 100644
index 000000000..f33885f8f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/cpptypes.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classlists.qdoc -->
+ <title>Test C++ Types | TestTemplate</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Test C++ Types</h1>
+<!-- $$$cpptypes-description -->
+<div class="descr" id="details">
+<ul>
+<li translate="no"><a href="testqdoc-test.html">TestQDoc::Test</a></li>
+<li translate="no"><a href="testqdoc-test.html#QDOCTEST_MACRO2">TestQDoc::Test::QDOCTEST_MACRO2</a></li>
+<li translate="no"><a href="testqdoc-test.html#someFunctionDefaultArg">TestQDoc::Test::someFunctionDefaultArg()</a></li>
+</ul>
+</div>
+<!-- @@@cpptypes -->
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html">TestQDoc::Test</a></p></td><td class="tblDescr"><p>A class in a namespace</p></td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/crossmoduleref.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/crossmoduleref.html
new file mode 100644
index 000000000..551b86690
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/crossmoduleref.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="Namespace that has documented functions in multiple modules.">
+ <title>CrossModuleRef Namespace | TestTemplate</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#functions">Functions</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">CrossModuleRef Namespace</h1>
+<!-- $$$CrossModuleRef-brief -->
+<p>Namespace that has documented functions in multiple modules. <a href="#details">More...</a></p>
+<!-- @@@CrossModuleRef -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;CrossModuleRef&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 3.0</td></tr>
+</table></div>
+<h2 id="functions">Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="crossmoduleref.html#documentMe" translate="no">documentMe</a></b>()</td></tr>
+</table></div>
+<!-- $$$CrossModuleRef-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@CrossModuleRef -->
+<div class="func">
+<h2>Function Documentation</h2>
+<!-- $$$documentMe[overload1]$$$documentMe -->
+<h3 class="fn" translate="no" id="documentMe"><span class="type">void</span> CrossModuleRef::<span class="name">documentMe</span>()</h3>
+<p>Document me!</p>
+<!-- @@@documentMe -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/foo.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/foo.html
new file mode 100644
index 000000000..56e5bdd34
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/foo.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testtemplate.cpp -->
+ <meta name="description" content="Class template.">
+ <title>Foo Class | TestTemplate</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Foo</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Foo Class</h1>
+<span class="small-subtitle" translate="no">template &lt;typename T&gt; class Foo</span>
+<!-- $$$Foo-brief -->
+<p>Class template. <a href="#details">More...</a></p>
+<!-- @@@Foo -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Foo&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+</table></div>
+<!-- $$$Foo-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Foo -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/obsolete-classes.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/obsolete-classes.html
new file mode 100644
index 000000000..28e5842e7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/obsolete-classes.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- classlists.qdoc -->
+ <title>Obsolete Classes | TestTemplate</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#classes-with-obsolete-members">Classes with obsolete members</a></li>
+<li class="level2"><a href="#testqdoc">TestQDoc</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Obsolete Classes</h1>
+<!-- $$$obsolete-classes.html-description -->
+<div class="descr" id="details">
+<h2 id="classes-with-obsolete-members">Classes with obsolete members</h2>
+<div class="flowListDiv" translate="no">
+<dl class="flowList odd"><dt class="alphaChar"><b>T</b></dt>
+<dd><a href="testqdoc-test-obsolete.html">Test</a> (<a href="testqdoc.html">TestQDoc</a>)</dd>
+<dd><a href="testqdoc-testderived-obsolete.html">TestDerived</a> (<a href="testqdoc.html">TestQDoc</a>)</dd>
+</dl>
+</div>
+<h3 id="testqdoc">TestQDoc</h3>
+</div>
+<!-- @@@obsolete-classes.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testcpp-module.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testcpp-module.html
new file mode 100644
index 000000000..8734780e6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testcpp-module.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A test module page.">
+ <title>QDoc Test C++ Classes | TestTemplate</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#namespaces">Namespaces</a></li>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#linking-to-function-like-things">Linking to function-like things</a></li>
+<li class="level3"><a href="#section">section()</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">QDoc Test C++ Classes</h1>
+<!-- $$$TestCPP-brief -->
+<p>A test module page. <a href="#details">More...</a></p>
+<!-- @@@TestCPP -->
+<p>This module was introduced in Qt 2.0.</p>
+<h2 id="namespaces">Namespaces</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="crossmoduleref.html">CrossModuleRef</a></p></td><td class="tblDescr"><p>Namespace that has documented functions in multiple modules</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc.html">TestQDoc</a></p></td><td class="tblDescr"><p>A namespace</p></td></tr>
+</table></div>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="annotated">
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="bar.html">Bar</a></p></td><td class="tblDescr"><p>Another class template</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="baz.html">Baz</a></p></td><td class="tblDescr"><p>Class template template</p></td></tr>
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="foo.html">Foo</a></p></td><td class="tblDescr"><p>Class template</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test.html">TestQDoc::Test</a></p></td><td class="tblDescr"><p>A class in a namespace</p></td></tr>
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-test-struct.html">TestQDoc::Test::Struct</a></p></td><td class="tblDescr"><p>Templated struct</p></td></tr>
+<tr class="even topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-testderived.html">TestQDoc::TestDerived</a></p></td><td class="tblDescr"><p>A class in a namespace, derived from Test</p></td></tr>
+<tr class="odd topAlign"><td class="tblName" translate="no"><p><a href="testqdoc-vec.html">TestQDoc::Vec</a></p></td><td class="tblDescr"><p>Type alias that has its own reference</p></td></tr>
+</table></div>
+<!-- $$$TestCPP-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<div class="admonition note">
+<p><b>Note: </b>This is just a test. /* Look, Ma! {I'm made of arguments!} */</p>
+</div>
+<h3 id="linking-to-function-like-things">Linking to function-like things</h3>
+<ul>
+<li><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a>()</li>
+<li><a href="testqdoc-test.html#QDOCTEST_MACRO2" translate="no">QDOCTEST_MACRO2</a>()</li>
+<li><a href="testqdoc-test.html#QDOCTEST_MACRO2" translate="no">QDOCTEST_MACRO2</a>(int &amp;x)</li>
+<li><a href="testcpp-module.html#section" translate="no">section()</a></li>
+<li><a href="testcpp-module.html#section" translate="no">section() is a section title</a></li>
+</ul>
+<h4 id="section">section()</h4>
+</div>
+<!-- @@@TestCPP -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test-members.html
new file mode 100644
index 000000000..2bd243006
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test-members.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace.">
+ <title>List of All Members for Test | TestTemplate</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Test</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for Test</h1>
+<p>This is the complete list of members for <a href="testqdoc-test.html">TestQDoc::Test</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no">struct <span class="name"><b><a href="testqdoc-test-struct.html" translate="no">Struct</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#SomeType-typedef" translate="no">SomeType</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#Specialized-typedef" translate="no">Specialized</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#funcPtr" translate="no">funcPtr</a></b></span>(bool, const char *) : void (*)(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#funcTemplate" translate="no">funcTemplate</a></b></span>(T1, T2)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#inlineFunction" translate="no">inlineFunction</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEmDashInItsDocs" translate="no">methodWithEmDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload" translate="no">overload</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload-1" translate="no">overload</a></b></span>(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a></b></span>(int, int) : int</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a></b></span>(int, bool) const</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#virtualFun" translate="no">virtualFun</a></b></span>()</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test-obsolete.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test-obsolete.html
new file mode 100644
index 000000000..e70edce76
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test-obsolete.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace.">
+ <title>Obsolete Members for Test | TestTemplate</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Test</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Obsolete Members for Test</h1>
+<p><b>The following members of class <a href="testqdoc-test.html" translate="no">Test</a> are deprecated.</b> They are provided to keep old source code working. We strongly advise against using them in new code.</p>
+<h2>Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#anotherObsoleteMember" translate="no">anotherObsoleteMember</a></b>()</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated in 6.0)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#deprecatedMember" translate="no">deprecatedMember</a></b>()</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#obsoleteMember" translate="no">obsoleteMember</a></b>()</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> TestQDoc::Test &amp;</td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#operator-2b-2b" translate="no">operator++</a></b>()</td></tr>
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> TestQDoc::Test &amp;</td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-obsolete.html#operator--" translate="no">operator--</a></b>()</td></tr>
+</table></div>
+<h2>Member Function Documentation</h2>
+<!-- $$$ -->
+<div class="fngroup">
+<h3 class="fn fngroupitem" translate="no" id="operator-2b-2b"><code class="details extra" translate="no">[deprecated]</code> <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;Test::<span class="name">operator++</span>()</h3><h3 class="fn fngroupitem" translate="no" id="operator--"><code class="details extra" translate="no">[deprecated]</code> <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;Test::<span class="name">operator--</span>()</h3></div>
+<p>This function is deprecated. We strongly advise against using it in new code.</p>
+<!-- @@@ -->
+<!-- $$$anotherObsoleteMember[overload1]$$$anotherObsoleteMember -->
+<h3 class="fn" translate="no" id="anotherObsoleteMember"><code class="details extra" translate="no">[deprecated]</code> <span class="type">void</span> Test::<span class="name">anotherObsoleteMember</span>()</h3>
+<p>This function is deprecated. We strongly advise against using it in new code.</p>
+<p>Use <a href="testqdoc-test-obsolete.html#obsoleteMember" translate="no">obsoleteMember</a>() instead.</p>
+<!-- @@@anotherObsoleteMember -->
+<!-- $$$deprecatedMember[overload1]$$$deprecatedMember -->
+<h3 class="fn" translate="no" id="deprecatedMember"><code class="details extra" translate="no">[deprecated in 6.0]</code> <span class="type">void</span> Test::<span class="name">deprecatedMember</span>()</h3>
+<p>This function is deprecated since 6.0. We strongly advise against using it in new code.</p>
+<p>Use <a href="testqdoc-test.html#someFunction" translate="no">someFunction</a>() instead.</p>
+<!-- @@@deprecatedMember -->
+<!-- $$$obsoleteMember[overload1]$$$obsoleteMember -->
+<h3 class="fn" translate="no" id="obsoleteMember"><code class="details extra" translate="no">[deprecated]</code> <span class="type">void</span> Test::<span class="name">obsoleteMember</span>()</h3>
+<p>This function is deprecated. We strongly advise against using it in new code.</p>
+<p>Use <a href="testqdoc-test.html#someFunction" translate="no">someFunction</a>() instead.</p>
+<!-- @@@obsoleteMember -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test-struct.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test-struct.html
new file mode 100644
index 000000000..442f1341c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test-struct.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="Templated struct.">
+ <title>Struct Struct | TestTemplate</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Struct</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Struct Struct</h1>
+<span class="small-subtitle" translate="no">template &lt;typename D, typename T&gt; struct <a href="testqdoc.html" translate="no">TestQDoc</a>::<a href="testqdoc-test.html" translate="no">Test</a>::Struct</span>
+<!-- $$$Struct-brief -->
+<p>Templated struct. <a href="#details">More...</a></p>
+<!-- @@@Struct -->
+<p>This struct was introduced in Qt 2.0.</p>
+<!-- $$$Struct-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Struct -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test.html
new file mode 100644
index 000000000..0f25a69d4
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-test.html
@@ -0,0 +1,167 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace.">
+ <title>Test Class | TestTemplate</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Test</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#public-types">Public Types</a></li>
+<li class="level1"><a href="#public-functions">Public Functions</a></li>
+<li class="level1"><a href="#protected-functions">Protected Functions</a></li>
+<li class="level1"><a href="#related-non-members">Related Non-Members</a></li>
+<li class="level1"><a href="#macros">Macros</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Test Class</h1>
+<span class="small-subtitle" translate="no">class <a href="testqdoc.html" translate="no">TestQDoc</a>::Test</span>
+<!-- $$$Test-brief -->
+<p>A class in a namespace. <a href="#details">More...</a></p>
+<!-- @@@Test -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Test&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Inherited By:</td><td class="memItemRight bottomAlign"> <p><a href="testqdoc-testderived.html" translate="no">TestQDoc::TestDerived</a></p>
+</td></tr>
+</table></div>
+<ul>
+<li><a href="testqdoc-test-members.html">List of all members, including inherited members</a></li>
+<li><a href="testqdoc-test-obsolete.html">Deprecated members</a></li>
+<li>Test is part of <a href="cpptypes.html">Test C++ Types</a>.</li>
+</ul>
+<p><b>Note:</b> All functions in this class are <a href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</a> with the following exceptions:</p>
+<ul>
+<li><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a>(int i, bool b) const</li>
+</ul>
+<h2 id="public-types">Public Types</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since 2.0)</code> struct </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test-struct.html" translate="no">Struct</a></b></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#SomeType-typedef" translate="no">SomeType</a></b></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#Specialized-typedef" translate="no">Specialized</a></b></td></tr>
+</table></div>
+<h2 id="public-functions">Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void (*)(bool) </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#funcPtr" translate="no">funcPtr</a></b>(bool <i>b</i>, const char *<i>s</i>)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#inlineFunction" translate="no">inlineFunction</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#methodWithEmDashInItsDocs" translate="no">methodWithEmDashInItsDocs</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> int </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a></b>(int, int <i>v</i> = 0)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a></b>(int <i>i</i>, bool <i>b</i> = false) const</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> virtual void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#virtualFun" translate="no">virtualFun</a></b>()</td></tr>
+</table></div>
+<h2 id="protected-functions">Protected Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#funcTemplate" translate="no">funcTemplate</a></b>(T1 <i>a</i>, T2 <i>b</i>)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#overload" translate="no">overload</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since Test 1.2)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#overload-1" translate="no">overload</a></b>(bool <i>b</i>)</td></tr>
+</table></div>
+<h2 id="related-non-members">Related Non-Members</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> bool </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#operator-eq-eq" translate="no">operator==</a></b>(const TestQDoc::Test &amp;<i>lhs</i>, const TestQDoc::Test &amp;<i>rhs</i>)</td></tr>
+</table></div>
+<h2 id="macros">Macros</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since Test 1.1)</code> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#QDOCTEST_MACRO2" translate="no">QDOCTEST_MACRO2</a></b>(int &amp;<i>x</i>)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html#Q_INVOKABLE" translate="no">Q_INVOKABLE</a></b></td></tr>
+</table></div>
+<!-- $$$Test-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Test -->
+<div class="types">
+<h2>Member Type Documentation</h2>
+<!-- $$$SomeType -->
+<h3 class="fn" translate="no" id="SomeType-typedef">Test::<span class="name">SomeType</span></h3>
+<p>A typedef.</p>
+<!-- @@@SomeType -->
+<!-- $$$Specialized -->
+<h3 class="fn" translate="no" id="Specialized-typedef"><code class="details extra" translate="no">[alias]</code> template &lt;typename T&gt; Test::<span class="name">Specialized</span></h3>
+<!-- @@@Specialized -->
+</div>
+<div class="func">
+<h2>Member Function Documentation</h2>
+<!-- $$$ -->
+<div class="fngroup">
+<h3 class="fn fngroupitem" translate="no" id="overload"><code class="details extra" translate="no">[protected]</code> <span class="type">void</span> Test::<span class="name">overload</span>()</h3><h3 class="fn fngroupitem" translate="no" id="overload-1"><code class="details extra" translate="no">[protected, since Test 1.2]</code> <span class="type">void</span> Test::<span class="name">overload</span>(<span class="type">bool</span> <i>b</i>)</h3></div>
+<p>Overloads that share a documentation comment, optionally taking a parameter <i translate="no">b</i>.</p>
+<!-- @@@ -->
+<!-- $$$funcPtr[overload1]$$$funcPtrboolconstchar* -->
+<h3 class="fn" translate="no" id="funcPtr"><span class="type">void</span> (*)(<span class="type">bool</span>) Test::<span class="name">funcPtr</span>(<span class="type">bool</span> <i>b</i>, const <span class="type">char</span> *<i>s</i>)</h3>
+<p>Returns a pointer to a function that takes a boolean. Uses <i translate="no">b</i> and <i translate="no">s</i>.</p>
+<!-- @@@funcPtr -->
+<!-- $$$funcTemplate[overload1]$$$funcTemplateT1T2 -->
+<h3 class="fn" translate="no" id="funcTemplate"><code class="details extra" translate="no">[protected]</code> template &lt;typename T1, typename T2&gt; <span class="type">void</span> Test::<span class="name">funcTemplate</span>(<span class="type">T1</span> <i>a</i>, <span class="type">T2</span> <i>b</i>)</h3>
+<p>Function template with two parameters, <i translate="no">a</i> and <i translate="no">b</i>.</p>
+<!-- @@@funcTemplate -->
+<!-- $$$inlineFunction[overload1]$$$inlineFunction -->
+<h3 class="fn" translate="no" id="inlineFunction"><span class="type">void</span> Test::<span class="name">inlineFunction</span>()</h3>
+<p>An inline function, documented using the \fn QDoc command.</p>
+<!-- @@@inlineFunction -->
+<!-- $$$methodWithEmDashInItsDocs[overload1]$$$methodWithEmDashInItsDocs -->
+<h3 class="fn" translate="no" id="methodWithEmDashInItsDocs"><span class="type">void</span> Test::<span class="name">methodWithEmDashInItsDocs</span>()</h3>
+<p>This method has em dashes in its documentation&mdash;as you'll find represented by <code translate="no">---</code> in the sources&mdash;here and there. The important bit to note is that when passed e.g. to the \c command, the three hyphens are processed as input to the command and not replaced by an em dash.</p>
+<p>-----------------------------------------------------------------------</p>
+<p>People can still add a bunch of dashes, though, without QDoc replacing them all with a series of em dashes.</p>
+<p>&mdash;You can also start a new paragraph with an em dash, if you want to.</p>
+<p><b>See also </b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a>.</p>
+<!-- @@@methodWithEmDashInItsDocs -->
+<!-- $$$methodWithEnDashInItsDocs[overload1]$$$methodWithEnDashInItsDocs -->
+<h3 class="fn" translate="no" id="methodWithEnDashInItsDocs"><span class="type">void</span> Test::<span class="name">methodWithEnDashInItsDocs</span>()</h3>
+<p>This method has en dashes in its documentation &ndash; as you'll find represented by <code translate="no">--</code> in the sources &ndash; here and there. The important bit to note is that when passed e.g. to the \c command, the two hyphens are processed as input to the command and not replaced by an en dash. This also applies to code blocks, where otherwise, the decrement operator would get completely borked:</p>
+<pre class="cpp" translate="no"><span class="keyword">for</span> (<span class="type">int</span> i <span class="operator">=</span> <span class="number">42</span>; i <span class="operator">&gt;</span> <span class="number">0</span>; <span class="operator">-</span><span class="operator">-</span>i)
+ <span class="comment">// Do something cool during countdown.</span></pre>
+<p>...as it would be silly if this would output &ndash;i instead of <code translate="no">--i</code>.</p>
+<p>-----------------------------------------------------------------------</p>
+<p>It still allows people to add a bunch of dashes, though, without replacing them all with a series of en dashes. Of course, they might want to use the \hr command instead, like this:</p>
+<hr />
+<p>&ndash; You can also start a new paragraph with an en dash, if you want to.</p>
+<p><b>See also </b>methodWithEnDashInItsDocs.</p>
+<!-- @@@methodWithEnDashInItsDocs -->
+<!-- $$$someFunction[overload1]$$$someFunctionintint -->
+<h3 class="fn" translate="no" id="someFunction"><span class="type">int</span> Test::<span class="name">someFunction</span>(<span class="type">int</span>, <span class="type">int</span> <i>v</i> = 0)</h3>
+<p>Function that takes a parameter <i translate="no">v</i>. Also returns the value of <i translate="no">v</i>.</p>
+<!-- @@@someFunction -->
+<!-- $$$someFunctionDefaultArg[overload1]$$$someFunctionDefaultArgintbool -->
+<h3 class="fn" translate="no" id="someFunctionDefaultArg"><span class="type">void</span> Test::<span class="name">someFunctionDefaultArg</span>(<span class="type">int</span> <i>i</i>, <span class="type">bool</span> <i>b</i> = false) const</h3>
+<p>Function that takes a parameter <i translate="no">i</i> and <i translate="no">b</i>.</p>
+<p><b>Warning:</b> This function is not <a href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command">reentrant</a>.</p>
+<!-- @@@someFunctionDefaultArg -->
+<!-- $$$virtualFun[overload1]$$$virtualFun -->
+<h3 class="fn" translate="no" id="virtualFun"><code class="details extra" translate="no">[virtual]</code> <span class="type">void</span> Test::<span class="name">virtualFun</span>()</h3>
+<p>Function that must be reimplemented.</p>
+<!-- @@@virtualFun -->
+</div>
+<div class="relnonmem">
+<h2>Related Non-Members</h2>
+<!-- $$$operator==[overload1]$$$operator==constTestQDoc::Test&constTestQDoc::Test& -->
+<h3 class="fn" translate="no" id="operator-eq-eq"><span class="type">bool</span> <span class="name">operator==</span>(const <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;<i>lhs</i>, const <span class="type"><a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></span> &amp;<i>rhs</i>)</h3>
+<p>Returns true if <i translate="no">lhs</i> and <i translate="no">rhs</i> are equal.</p>
+<!-- @@@operator== -->
+</div>
+<div class="macros">
+<h2>Macro Documentation</h2>
+<!-- $$$QDOCTEST_MACRO2[overload1]$$$QDOCTEST_MACRO2int& -->
+<h3 class="fn" translate="no" id="QDOCTEST_MACRO2"><code class="details extra" translate="no">[since Test 1.1]</code> <span class="name">QDOCTEST_MACRO2</span>(<span class="type">int</span> &amp;<i>x</i>)</h3>
+<p>A macro with argument <i translate="no">x</i>.</p>
+<p>This macro was introduced in Test 1.1.</p>
+<!-- @@@QDOCTEST_MACRO2 -->
+<!-- $$$Q_INVOKABLE[overload1]$$$Q_INVOKABLE -->
+<h3 class="fn" translate="no" id="Q_INVOKABLE"><span class="name">Q_INVOKABLE</span></h3>
+<p>This is a mock Q_INVOKABLE for the purpose of ensuring QDoc autolink to it as expected.</p>
+<!-- @@@Q_INVOKABLE -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-testderived-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-testderived-members.html
new file mode 100644
index 000000000..cd251011c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-testderived-members.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace, derived from Test.">
+ <title>List of All Members for TestDerived | TestTemplate</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>TestDerived</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for TestDerived</h1>
+<p>This is the complete list of members for <a href="testqdoc-testderived.html">TestQDoc::TestDerived</a>, including inherited members.</p>
+<div class="table"><table class="propsummary" translate="no">
+<tr><td class="topAlign"><ul>
+<li class="fn" translate="no">struct <span class="name"><b><a href="testqdoc-test-struct.html" translate="no">Struct</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#DerivedType-typedef" translate="no">DerivedType</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#NotTypedef-typedef" translate="no">NotTypedef</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#SomeType-typedef" translate="no">SomeType</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#Specialized-typedef" translate="no">Specialized</a></b></span></li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#funcPtr" translate="no">funcPtr</a></b></span>(bool, const char *) : void (*)(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#funcTemplate" translate="no">funcTemplate</a></b></span>(T1, T2)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#inlineFunction" translate="no">inlineFunction</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEmDashInItsDocs" translate="no">methodWithEmDashInItsDocs</a></b></span>()</li>
+</ul></td><td class="topAlign"><ul>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#methodWithEnDashInItsDocs" translate="no">methodWithEnDashInItsDocs</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload" translate="no">overload</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#overload-1" translate="no">overload</a></b></span>(bool)</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunction" translate="no">someFunction</a></b></span>(int, int) : int</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#someFunctionDefaultArg" translate="no">someFunctionDefaultArg</a></b></span>(int, bool) const</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#someValue" translate="no">someValue</a></b></span>() : TestQDoc::TestDerived::NotTypedef</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-testderived.html#virtualFun" translate="no">virtualFun</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="testqdoc-test.html#virtualFun" translate="no">virtualFun</a></b></span>()</li>
+</ul>
+</td></tr>
+</table></div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-testderived-obsolete.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-testderived-obsolete.html
new file mode 100644
index 000000000..3eda5f832
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-testderived-obsolete.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace, derived from Test.">
+ <title>Obsolete Members for TestDerived | TestTemplate</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>TestDerived</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Obsolete Members for TestDerived</h1>
+<p><b>The following members of class <a href="testqdoc-testderived.html" translate="no">TestDerived</a> are deprecated.</b> They are provided to keep old source code working. We strongly advise against using them in new code.</p>
+<h2>Static Public Members</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft topAlign rightAlign"> <code class="summary extra" translate="no">(deprecated)</code> void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived-obsolete.html#staticObsoleteMember" translate="no">staticObsoleteMember</a></b>()</td></tr>
+</table></div>
+<h2>Member Function Documentation</h2>
+<!-- $$$staticObsoleteMember[overload1]$$$staticObsoleteMember -->
+<h3 class="fn" translate="no" id="staticObsoleteMember"><code class="details extra" translate="no">[static, deprecated]</code> <span class="type">void</span> TestDerived::<span class="name">staticObsoleteMember</span>()</h3>
+<p>This function is deprecated. We strongly advise against using it in new code.</p>
+<p>Static obsolete method.</p>
+<!-- @@@staticObsoleteMember -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-testderived.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-testderived.html
new file mode 100644
index 000000000..b96fefe51
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-testderived.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A class in a namespace, derived from Test.">
+ <title>TestDerived Class | TestTemplate</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>TestDerived</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#public-types">Public Types</a></li>
+<li class="level1"><a href="#public-functions">Public Functions</a></li>
+<li class="level1"><a href="#reimplemented-public-functions">Reimplemented Public Functions</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">TestDerived Class</h1>
+<span class="small-subtitle" translate="no">class <a href="testqdoc.html" translate="no">TestQDoc</a>::TestDerived</span>
+<!-- $$$TestDerived-brief -->
+<p>A class in a namespace, derived from <a href="testqdoc-test.html" translate="no">Test</a>. <a href="#details">More...</a></p>
+<!-- @@@TestDerived -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;TestDerived&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Inherits:</td><td class="memItemRight bottomAlign"> <a href="testqdoc-test.html" translate="no">TestQDoc::Test</a></td></tr>
+</table></div>
+<ul>
+<li><a href="testqdoc-testderived-members.html">List of all members, including inherited members</a></li>
+<li><a href="testqdoc-testderived-obsolete.html">Deprecated members</a></li>
+</ul>
+<h2 id="public-types">Public Types</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#DerivedType-typedef" translate="no">DerivedType</a></b></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#NotTypedef-typedef" translate="no">NotTypedef</a></b></td></tr>
+</table></div>
+<h2 id="public-functions">Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> TestQDoc::TestDerived::NotTypedef </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#someValue" translate="no">someValue</a></b>()</td></tr>
+</table></div>
+<h2 id="reimplemented-public-functions">Reimplemented Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> virtual void </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html#virtualFun" translate="no">virtualFun</a></b>() override</td></tr>
+</table></div>
+<!-- $$$TestDerived-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@TestDerived -->
+<div class="types">
+<h2>Member Type Documentation</h2>
+<!-- $$$DerivedType -->
+<h3 class="fn" translate="no" id="DerivedType-typedef"><code class="details extra" translate="no">[alias]</code> TestDerived::<span class="name">DerivedType</span></h3>
+<p>An aliased typedef.</p>
+<!-- @@@DerivedType -->
+<!-- $$$NotTypedef -->
+<h3 class="fn" translate="no" id="NotTypedef-typedef"><code class="details extra" translate="no">[alias]</code> TestDerived::<span class="name">NotTypedef</span></h3>
+<p>I'm an alias, not a typedef.</p>
+<!-- @@@NotTypedef -->
+</div>
+<div class="func">
+<h2>Member Function Documentation</h2>
+<!-- $$$someValue[overload1]$$$someValue -->
+<h3 class="fn" translate="no" id="someValue"><span class="type"><a href="testqdoc-testderived.html#NotTypedef-typedef" translate="no">TestQDoc::TestDerived::NotTypedef</a></span> TestDerived::<span class="name">someValue</span>()</h3>
+<p>Returns a value using an aliases type.</p>
+<!-- @@@someValue -->
+<!-- $$$virtualFun[overload1]$$$virtualFun -->
+<h3 class="fn" translate="no" id="virtualFun"><code class="details extra" translate="no">[override virtual]</code> <span class="type">void</span> TestDerived::<span class="name">virtualFun</span>()</h3>
+<p>Reimplements: <a href="testqdoc-test.html#virtualFun" translate="no">Test::virtualFun</a>().</p>
+<!-- @@@virtualFun -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-vec.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-vec.html
new file mode 100644
index 000000000..a3085f6b3
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc-vec.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="Type alias that has its own reference.">
+ <title>Vec Class | TestTemplate</title>
+</head>
+<body>
+<li><a href="testcpp-module.html" translate="no">C++ Classes</a></li>
+<li>Vec</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Vec Class</h1>
+<span class="small-subtitle" translate="no">template &lt;typename T&gt; class <a href="testqdoc.html" translate="no">TestQDoc</a>::Vec</span>
+<!-- $$$Vec-brief -->
+<p>Type alias that has its own reference. <a href="#details">More...</a></p>
+<!-- @@@Vec -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Vec&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+</table></div>
+<!-- $$$Vec-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Vec -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc.html
new file mode 100644
index 000000000..516e256da
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testqdoc.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- testcpp.cpp -->
+ <meta name="description" content="A namespace.">
+ <title>TestQDoc Namespace | TestTemplate</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#classes">Classes</a></li>
+<li class="level1"><a href="#macros">Macros</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+<li class="level2"><a href="#usage">Usage</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">TestQDoc Namespace</h1>
+<!-- $$$TestQDoc-brief -->
+<p>A namespace. <a href="#details">More...</a></p>
+<!-- @@@TestQDoc -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;TestCPP&gt;</span></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> CMake:</td><td class="memItemRight bottomAlign"> find_package(Qt6 REQUIRED COMPONENTS QDocTest) <br/>
+target_link_libraries(mytarget PRIVATE Qt6::QDocTest)</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> qmake:</td><td class="memItemRight bottomAlign"> QT += testcpp</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> Since:</td><td class="memItemRight bottomAlign"> Qt 2.0</td></tr>
+</table></div>
+<h2 id="classes">Classes</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since 2.0)</code> class </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-test.html" translate="no">Test</a></b></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since 2.0)</code> class </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-testderived.html" translate="no">TestDerived</a></b></td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> <code class="summary extra" translate="no">(since 2.0)</code> class </td><td class="memItemRight bottomAlign"><b><a href="testqdoc-vec.html" translate="no">Vec</a></b></td></tr>
+</table></div>
+<h2 id="macros">Macros</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="testqdoc.html#QDOCTEST_MACRO" translate="no">QDOCTEST_MACRO</a></b></td></tr>
+</table></div>
+<!-- $$$TestQDoc-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+<h3 id="usage">Usage</h3>
+<p>This namespace is for testing QDoc output.</p>
+</div>
+<!-- @@@TestQDoc -->
+<div class="classes">
+<h2>Classes</h2>
+<h3> class <a href="testqdoc-test.html">Test</a></h3><!-- $$$Test-brief -->
+<p>A class in a namespace. <a href="testqdoc-test.html#details">More...</a></p>
+<!-- @@@Test -->
+<h3> class <a href="testqdoc-testderived.html">TestDerived</a></h3><!-- $$$TestDerived-brief -->
+<p>A class in a namespace, derived from <a href="testqdoc-test.html" translate="no">Test</a>. <a href="testqdoc-testderived.html#details">More...</a></p>
+<!-- @@@TestDerived -->
+<h3> class <a href="testqdoc-vec.html">Vec</a></h3><!-- $$$Vec-brief -->
+<p>Type alias that has its own reference. <a href="testqdoc-vec.html#details">More...</a></p>
+<!-- @@@Vec -->
+</div>
+<div class="macros">
+<h2>Macro Documentation</h2>
+<!-- $$$QDOCTEST_MACRO[overload1]$$$QDOCTEST_MACRO -->
+<h3 class="fn" translate="no" id="QDOCTEST_MACRO"><span class="name">QDOCTEST_MACRO</span></h3>
+<!-- @@@QDOCTEST_MACRO -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testtemplate.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testtemplate.index
new file mode 100644
index 000000000..34346db56
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/html/testtemplate.index
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="TestTemplate Reference Documentation" version="" project="TestTemplate">
+ <namespace name="" status="active" access="public" module="testtemplate">
+ <function name="QDOCTEST_MACRO" href="testqdoc.html#QDOCTEST_MACRO" status="active" access="public" documented="true" related="0" meta="macrowithoutparams" signature="QDOCTEST_MACRO"/>
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ </function>
+ <function name="Q_INVOKABLE" href="testqdoc-test.html#Q_INVOKABLE" status="active" access="public" documented="true" related="2" meta="macrowithoutparams" signature="Q_INVOKABLE"/>
+ <page name="autolinking.html" href="autolinking.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Autolinking" fulltitle="Autolinking" subtitle="">
+ <contents name="testqdoc" title="TestQDoc" level="1"/>
+ <contents name="someprop" title="someProp" level="1"/>
+ </page>
+ <class name="Bar" href="bar.html" status="active" access="public" location="testtemplate.h" since="2.0" documented="true" module="TestCPP" brief="Another class template"/>
+ <struct name="Baz" href="baz.html" status="active" access="public" location="testtemplate.h" since="2.0" documented="true" module="TestCPP" brief="Class template template"/>
+ <namespace name="CrossModuleRef" href="crossmoduleref.html" status="active" access="public" location="testcpp.h" since="3.0" documented="true" module="TestCPP" brief="Namespace that has documented functions in multiple modules">
+ <function name="documentMe" fullname="CrossModuleRef::documentMe" href="crossmoduleref.html#documentMe" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void documentMe()"/>
+ </namespace>
+ <class name="DontLinkToMe" href="dontlinktome.html" status="ignored" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="Class that does not generate documentation"/>
+ <class name="Foo" href="foo.html" status="active" access="public" location="testtemplate.h" since="2.0" documented="true" module="TestCPP" brief="Class template"/>
+ <page name="obsolete-classes.html" href="obsolete-classes.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Obsolete Classes" fulltitle="Obsolete Classes" subtitle="">
+ <contents name="classes-with-obsolete-members" title="Classes with obsolete members" level="1"/>
+ <contents name="testqdoc" title="TestQDoc" level="2"/>
+ </page>
+ <namespace name="TestQDoc" href="testqdoc.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="A namespace">
+ <contents name="usage" title="Usage" level="1"/>
+ <function name="QDOCTEST_MACRO" href="testqdoc.html#QDOCTEST_MACRO" status="active" access="public" documented="true" related="0" meta="macrowithoutparams" signature="QDOCTEST_MACRO"/>
+ <class threadsafety="reentrant" name="Test" fullname="TestQDoc::Test" href="testqdoc-test.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" groups="cpptypes,testgroup" module="TestCPP" brief="A class in a namespace">
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ </function>
+ <function name="Q_INVOKABLE" href="testqdoc-test.html#Q_INVOKABLE" status="active" access="public" documented="true" related="2" meta="macrowithoutparams" signature="Q_INVOKABLE"/>
+ <function name="anotherObsoleteMember" fullname="TestQDoc::Test::anotherObsoleteMember" href="testqdoc-test-obsolete.html#anotherObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void anotherObsoleteMember()"/>
+ <function name="deprecatedMember" fullname="TestQDoc::Test::deprecatedMember" href="testqdoc-test-obsolete.html#deprecatedMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void deprecatedMember()"/>
+ <function name="funcPtr" fullname="TestQDoc::Test::funcPtr" href="testqdoc-test.html#funcPtr" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void (*)(bool)" signature="void (*)(bool) funcPtr(bool b, const char *s)">
+ <parameter type="bool" name="b" default=""/>
+ <parameter type="const char *" name="s" default=""/>
+ </function>
+ <function name="funcTemplate" fullname="TestQDoc::Test::funcTemplate" href="testqdoc-test.html#funcTemplate" status="active" access="protected" location="testcpp.h" documented="true" meta="plain" type="void" brief="Function template with two parameters, a and b" signature="void funcTemplate(T1 a, T2 b)">
+ <parameter type="T1" name="a" default=""/>
+ <parameter type="T2" name="b" default=""/>
+ </function>
+ <function name="inlineFunction" fullname="TestQDoc::Test::inlineFunction" href="testqdoc-test.html#inlineFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" brief="An inline function, documented using the \fn QDoc command" signature="void inlineFunction()"/>
+ <function name="methodWithEmDashInItsDocs" fullname="TestQDoc::Test::methodWithEmDashInItsDocs" href="testqdoc-test.html#methodWithEmDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEmDashInItsDocs()"/>
+ <function name="methodWithEnDashInItsDocs" fullname="TestQDoc::Test::methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEnDashInItsDocs()"/>
+ <function name="obsoleteMember" fullname="TestQDoc::Test::obsoleteMember" href="testqdoc-test-obsolete.html#obsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void obsoleteMember()"/>
+ <function name="operator++" fullname="TestQDoc::Test::operator++" href="testqdoc-test-obsolete.html#operator-2b-2b" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator++()"/>
+ <function name="operator--" fullname="TestQDoc::Test::operator--" href="testqdoc-test-obsolete.html#operator--" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator--()"/>
+ <function name="operator==" href="testqdoc-test.html#operator-eq-eq" status="active" access="public" location="testcpp.h" documented="true" related="3" meta="plain" type="bool" signature="bool operator==(const TestQDoc::Test &amp;lhs, const TestQDoc::Test &amp;rhs)">
+ <parameter type="const TestQDoc::Test &amp;" name="lhs" default=""/>
+ <parameter type="const TestQDoc::Test &amp;" name="rhs" default=""/>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload" status="active" access="protected" location="testcpp.h" documented="true" meta="plain" type="void" signature="void overload()"/>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload-1" status="active" access="protected" location="testcpp.h" documented="true" since="Test 1.2" meta="plain" overload="true" overload-number="1" type="void" signature="void overload(bool b)">
+ <parameter type="bool" name="b" default=""/>
+ </function>
+ <function name="someFunction" fullname="TestQDoc::Test::someFunction" href="testqdoc-test.html#someFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="int" signature="int someFunction(int, int v)">
+ <parameter type="int" name="" default=""/>
+ <parameter type="int" name="v" default="0"/>
+ </function>
+ <function name="someFunctionDefaultArg" fullname="TestQDoc::Test::someFunctionDefaultArg" href="testqdoc-test.html#someFunctionDefaultArg" threadsafety="non-reentrant" status="active" access="public" location="testcpp.h" documented="true" meta="plain" const="true" type="void" signature="void someFunctionDefaultArg(int i, bool b) const" groups="testgroup">
+ <parameter type="int" name="i" default=""/>
+ <parameter type="bool" name="b" default="false"/>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::Test::virtualFun" href="testqdoc-test.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" type="void" signature="void virtualFun()"/>
+ <struct name="Struct" fullname="TestQDoc::Test::Struct" href="testqdoc-test-struct.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="Templated struct"/>
+ <typedef name="SomeType" fullname="TestQDoc::Test::SomeType" href="testqdoc-test.html#SomeType-typedef" status="active" access="public" location="testcpp.h" documented="true"/>
+ <typedef name="Specialized" fullname="TestQDoc::Test::Specialized" href="testqdoc-test.html#Specialized-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="Struct&lt;int, T&gt;"/>
+ </class>
+ <class name="TestDerived" fullname="TestQDoc::TestDerived" href="testqdoc-testderived.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" bases="TestQDoc::Test" module="TestCPP" brief="A class in a namespace, derived from Test">
+ <function name="someValue" fullname="TestQDoc::TestDerived::someValue" href="testqdoc-testderived.html#someValue" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::TestDerived::NotTypedef" signature="TestQDoc::TestDerived::NotTypedef someValue()"/>
+ <function name="staticObsoleteMember" fullname="TestQDoc::TestDerived::staticObsoleteMember" href="testqdoc-testderived-obsolete.html#staticObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" static="true" type="void" signature="void staticObsoleteMember()"/>
+ <function name="virtualFun" fullname="TestQDoc::TestDerived::virtualFun" href="testqdoc-testderived.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" override="true" type="void" signature="void virtualFun() override"/>
+ <typedef name="DerivedType" fullname="TestQDoc::TestDerived::DerivedType" href="testqdoc-testderived.html#DerivedType-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="Test::SomeType"/>
+ <typedef name="NotTypedef" fullname="TestQDoc::TestDerived::NotTypedef" href="testqdoc-testderived.html#NotTypedef-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="int"/>
+ </class>
+ <class name="Vec" fullname="TestQDoc::Vec" href="testqdoc-vec.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="Type alias that has its own reference"/>
+ </namespace>
+ <page name="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command" href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command" status="active" location="classlists.qdoc" documented="true" subtype="externalpage" title="reentrant" fulltitle="reentrant" subtitle=""/>
+ <group name="cpptypes" href="cpptypes.html" status="active" location="classlists.qdoc" documented="true" seen="true" title="Test C++ Types"/>
+ <group name="testgroup" href="testgroup.html" status="internal" seen="false" title=""/>
+ <module name="TestCPP" href="testcpp-module.html" status="active" since="2.0" documented="true" seen="true" title="QDoc Test C++ Classes" brief="A test module page">
+ <contents name="linking-to-function-like-things" title="Linking to function-like things" level="1"/>
+ <contents name="section" title="section()" level="2"/>
+ </module>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/autolinking.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/autolinking.webxml
new file mode 100644
index 000000000..91f35d5d7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/autolinking.webxml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="autolinking.html" href="autolinking.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Autolinking" fulltitle="Autolinking" subtitle="">
+ <contents name="testqdoc" title="TestQDoc" level="1"/>
+ <contents name="someprop" title="someProp" level="1"/>
+ <description>
+ <section id="testqdoc">
+ <heading level="1">TestQDoc</heading>
+ <para>The string <link raw="TestQDoc" href="testqdoc.html" type="namespace">TestQDoc</link> links to the C++ namespace unless linking explicitly, <link raw="#TestQDoc" href="autolinking.html#testqdoc" type="page" page="Autolinking">like this</link>, or <link raw="TestQDoc" href="testqdoc.html" type="namespace">this</link>. Also,</para>
+ <para>Autolinks:</para>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="TestQDoc::TestDerived" href="testqdoc-testderived.html" type="class">TestQDoc::TestDerived</link></para>
+ </item>
+ </list>
+ <para>Explicit links:</para>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="TestQDoc::TestDerived" href="testqdoc-testderived.html" type="class">TestQDoc::TestDerived</link></para>
+ </item>
+ <item>
+ <para>
+ <link raw="Obsolete Classes#TestQDoc" href="obsolete-classes.html#testqdoc" type="page" page="Obsolete Classes">Obsolete Classes#TestQDoc</link></para>
+ </item>
+ </list>
+ </section>
+ <section id="someprop">
+ <heading level="1">someProp</heading>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/bar.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/bar.webxml
new file mode 100644
index 000000000..fd653db97
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/bar.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="Bar" href="bar.html" status="active" access="public" location="testtemplate.h" since="2.0" documented="true" module="TestCPP" brief="Another class template">
+ <description>
+ <brief>Another class template.</brief>
+ </description>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/baz.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/baz.webxml
new file mode 100644
index 000000000..8184ea659
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/baz.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <struct name="Baz" href="baz.html" status="active" access="public" location="testtemplate.h" since="2.0" documented="true" module="TestCPP" brief="Class template template">
+ <description>
+ <brief>Class template template.</brief>
+ </description>
+ </struct>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/cpptypes.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/cpptypes.webxml
new file mode 100644
index 000000000..df7cd7024
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/cpptypes.webxml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <group name="cpptypes" href="cpptypes.html" status="active" location="classlists.qdoc" documented="true" seen="true" title="Test C++ Types">
+ <description>
+ <generatedlist contents="testgroup"/>
+ <table width="100%">
+ <row>
+ <item>
+ <para>
+ <link raw="TestQDoc::Test" href="testqdoc-test.html" type="class"/>
+ </para>
+ </item>
+ <item>
+ <para>A class in a namespace.</para>
+ </item>
+ </row>
+ </table>
+ </description>
+ </group>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/crossmoduleref.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/crossmoduleref.webxml
new file mode 100644
index 000000000..682799bd8
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/crossmoduleref.webxml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <namespace name="CrossModuleRef" href="crossmoduleref.html" status="active" access="public" location="testcpp.h" since="3.0" documented="true" module="TestCPP" brief="Namespace that has documented functions in multiple modules">
+ <description>
+ <brief>Namespace that has documented functions in multiple modules.</brief>
+ </description>
+ <function name="documentMe" fullname="CrossModuleRef::documentMe" href="crossmoduleref.html#documentMe" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void documentMe()">
+ <description>
+ <para>Document me!</para>
+ </description>
+ </function>
+ </namespace>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/foo.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/foo.webxml
new file mode 100644
index 000000000..6135e5c4e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/foo.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="Foo" href="foo.html" status="active" access="public" location="testtemplate.h" since="2.0" documented="true" module="TestCPP" brief="Class template">
+ <description>
+ <brief>Class template.</brief>
+ </description>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/obsolete-classes.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/obsolete-classes.webxml
new file mode 100644
index 000000000..dda841458
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/obsolete-classes.webxml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="obsolete-classes.html" href="obsolete-classes.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Obsolete Classes" fulltitle="Obsolete Classes" subtitle="">
+ <contents name="classes-with-obsolete-members" title="Classes with obsolete members" level="1"/>
+ <contents name="testqdoc" title="TestQDoc" level="2"/>
+ <description>
+ <section id="classes-with-obsolete-members">
+ <heading level="1">Classes with obsolete members</heading>
+ <generatedlist contents="obsoletecppmembers"/>
+ </section>
+ <section id="testqdoc">
+ <heading level="2">TestQDoc</heading>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testcpp-module.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testcpp-module.webxml
new file mode 100644
index 000000000..5d24b3077
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testcpp-module.webxml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document/>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-test-struct.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-test-struct.webxml
new file mode 100644
index 000000000..f883d421e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-test-struct.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <struct name="Struct" fullname="TestQDoc::Test::Struct" href="testqdoc-test-struct.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="Templated struct">
+ <description>
+ <brief>Templated struct.</brief>
+ </description>
+ </struct>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-test.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-test.webxml
new file mode 100644
index 000000000..d6dfec6f1
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-test.webxml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class threadsafety="reentrant" name="Test" fullname="TestQDoc::Test" href="testqdoc-test.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" groups="cpptypes,testgroup" module="TestCPP" brief="A class in a namespace">
+ <description>
+ <brief>A class in a namespace.</brief>
+ </description>
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ <description>
+ <brief>A macro with argument <argument>x</argument>.</brief>
+ </description>
+ </function>
+ <function name="Q_INVOKABLE" href="testqdoc-test.html#Q_INVOKABLE" status="active" access="public" documented="true" related="2" meta="macrowithoutparams" signature="Q_INVOKABLE">
+ <description>
+ <para>This is a mock Q_INVOKABLE for the purpose of ensuring QDoc autolink to it as expected.</para>
+ </description>
+ </function>
+ <function name="anotherObsoleteMember" fullname="TestQDoc::Test::anotherObsoleteMember" href="testqdoc-test-obsolete.html#anotherObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void anotherObsoleteMember()">
+ <description>
+ <para>Use <link raw="obsoleteMember()" href="testqdoc-test.html#obsoleteMember" type="function">obsoleteMember()</link> instead.</para>
+ </description>
+ </function>
+ <function name="deprecatedMember" fullname="TestQDoc::Test::deprecatedMember" href="testqdoc-test-obsolete.html#deprecatedMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void deprecatedMember()">
+ <description>
+ <para>Use <link raw="someFunction()" href="testqdoc-test.html#someFunction" type="function">someFunction()</link> instead.</para>
+ </description>
+ </function>
+ <function name="funcPtr" fullname="TestQDoc::Test::funcPtr" href="testqdoc-test.html#funcPtr" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void (*)(bool)" signature="void (*)(bool) funcPtr(bool b, const char *s)">
+ <parameter type="bool" name="b" default=""/>
+ <parameter type="const char *" name="s" default=""/>
+ <description>
+ <para>Returns a pointer to a function that takes a boolean. Uses <argument>b</argument> and <argument>s</argument>.</para>
+ </description>
+ </function>
+ <function name="funcTemplate" fullname="TestQDoc::Test::funcTemplate" href="testqdoc-test.html#funcTemplate" status="active" access="protected" location="testcpp.h" documented="true" meta="plain" type="void" brief="Function template with two parameters, a and b" signature="void funcTemplate(T1 a, T2 b)">
+ <parameter type="T1" name="a" default=""/>
+ <parameter type="T2" name="b" default=""/>
+ <description>
+ <brief>Function template with two parameters, <argument>a</argument> and <argument>b</argument>.</brief>
+ </description>
+ </function>
+ <function name="inlineFunction" fullname="TestQDoc::Test::inlineFunction" href="testqdoc-test.html#inlineFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" brief="An inline function, documented using the \fn QDoc command" signature="void inlineFunction()">
+ <description>
+ <brief>An inline function, documented using the \fn QDoc command.</brief>
+ </description>
+ </function>
+ <function name="methodWithEmDashInItsDocs" fullname="TestQDoc::Test::methodWithEmDashInItsDocs" href="testqdoc-test.html#methodWithEmDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEmDashInItsDocs()">
+ <description>
+ <para>This method has em dashes in its documentation—as you'll find represented by <teletype type="highlighted">---</teletype> in the sources—here and there. The important bit to note is that when passed e.g. to the \c command, the three hyphens are processed as input to the command and not replaced by an em dash.</para>
+ <para>-----------------------------------------------------------------------</para>
+ <para>People can still add a bunch of dashes, though, without QDoc replacing them all with a series of em dashes.</para>
+ <para>—You can also start a new paragraph with an em dash, if you want to.</para>
+ <see-also>
+ <link raw="methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" type="function">methodWithEnDashInItsDocs</link>
+ </see-also>
+ </description>
+ </function>
+ <function name="methodWithEnDashInItsDocs" fullname="TestQDoc::Test::methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEnDashInItsDocs()">
+ <description>
+ <para>This method has en dashes in its documentation – as you'll find represented by <teletype type="highlighted">--</teletype> in the sources – here and there. The important bit to note is that when passed e.g. to the \c command, the two hyphens are processed as input to the command and not replaced by an en dash. This also applies to code blocks, where otherwise, the decrement operator would get completely borked:</para>
+ <code>for (int i = 42; i &gt; 0; --i)
+ // Do something cool during countdown.</code>
+ <para>...as it would be silly if this would output –i instead of <teletype type="highlighted">--i</teletype>.</para>
+ <para>-----------------------------------------------------------------------</para>
+ <para>It still allows people to add a bunch of dashes, though, without replacing them all with a series of en dashes. Of course, they might want to use the \hr command instead, like this:</para>
+ <para>– You can also start a new paragraph with an en dash, if you want to.</para>
+ <see-also>methodWithEnDashInItsDocs</see-also>
+ </description>
+ </function>
+ <function name="obsoleteMember" fullname="TestQDoc::Test::obsoleteMember" href="testqdoc-test-obsolete.html#obsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void obsoleteMember()">
+ <description>
+ <para>Use <link raw="someFunction()" href="testqdoc-test.html#someFunction" type="function">someFunction()</link> instead.</para>
+ </description>
+ </function>
+ <function name="operator++" fullname="TestQDoc::Test::operator++" href="testqdoc-test-obsolete.html#operator-2b-2b" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator++()">
+ <description/>
+ </function>
+ <function name="operator--" fullname="TestQDoc::Test::operator--" href="testqdoc-test-obsolete.html#operator--" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator--()">
+ <description/>
+ </function>
+ <function name="operator==" href="testqdoc-test.html#operator-eq-eq" status="active" access="public" location="testcpp.h" documented="true" related="3" meta="plain" type="bool" signature="bool operator==(const TestQDoc::Test &amp;lhs, const TestQDoc::Test &amp;rhs)">
+ <parameter type="const TestQDoc::Test &amp;" name="lhs" default=""/>
+ <parameter type="const TestQDoc::Test &amp;" name="rhs" default=""/>
+ <description>
+ <para>Returns true if <argument>lhs</argument> and <argument>rhs</argument> are equal.</para>
+ </description>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload" status="active" access="protected" location="testcpp.h" documented="true" meta="plain" type="void" signature="void overload()">
+ <description/>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload-1" status="active" access="protected" location="testcpp.h" documented="true" since="Test 1.2" meta="plain" overload="true" overload-number="1" type="void" signature="void overload(bool b)">
+ <parameter type="bool" name="b" default=""/>
+ <description/>
+ </function>
+ <function name="someFunction" fullname="TestQDoc::Test::someFunction" href="testqdoc-test.html#someFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="int" signature="int someFunction(int, int v)">
+ <parameter type="int" name="" default=""/>
+ <parameter type="int" name="v" default="0"/>
+ <description>
+ <para>Function that takes a parameter <argument>v</argument>. Also returns the value of <argument>v</argument>.</para>
+ </description>
+ </function>
+ <function name="someFunctionDefaultArg" fullname="TestQDoc::Test::someFunctionDefaultArg" href="testqdoc-test.html#someFunctionDefaultArg" threadsafety="non-reentrant" status="active" access="public" location="testcpp.h" documented="true" meta="plain" const="true" type="void" signature="void someFunctionDefaultArg(int i, bool b) const" groups="testgroup">
+ <parameter type="int" name="i" default=""/>
+ <parameter type="bool" name="b" default="false"/>
+ <description>
+ <para>Function that takes a parameter <argument>i</argument> and <argument>b</argument>.</para>
+ </description>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::Test::virtualFun" href="testqdoc-test.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" type="void" signature="void virtualFun()">
+ <description>
+ <para>Function that must be reimplemented.</para>
+ </description>
+ </function>
+ <struct name="Struct" fullname="TestQDoc::Test::Struct" href="testqdoc-test-struct.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="Templated struct">
+ <description>
+ <brief>Templated struct.</brief>
+ </description>
+ </struct>
+ <typedef name="SomeType" fullname="TestQDoc::Test::SomeType" href="testqdoc-test.html#SomeType-typedef" status="active" access="public" location="testcpp.h" documented="true">
+ <description>
+ <brief>A typedef.</brief>
+ </description>
+ </typedef>
+ <typedef name="Specialized" fullname="TestQDoc::Test::Specialized" href="testqdoc-test.html#Specialized-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="Struct&lt;int, T&gt;">
+ <description/>
+ </typedef>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-testderived.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-testderived.webxml
new file mode 100644
index 000000000..3752e9950
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-testderived.webxml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="TestDerived" fullname="TestQDoc::TestDerived" href="testqdoc-testderived.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" bases="TestQDoc::Test" module="TestCPP" brief="A class in a namespace, derived from Test">
+ <description>
+ <brief>A class in a namespace, derived from <link raw="Test" href="testqdoc-test.html" type="class">Test</link>.</brief>
+ </description>
+ <function name="someValue" fullname="TestQDoc::TestDerived::someValue" href="testqdoc-testderived.html#someValue" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::TestDerived::NotTypedef" signature="TestQDoc::TestDerived::NotTypedef someValue()">
+ <description>
+ <para>Returns a value using an aliases type.</para>
+ </description>
+ </function>
+ <function name="staticObsoleteMember" fullname="TestQDoc::TestDerived::staticObsoleteMember" href="testqdoc-testderived-obsolete.html#staticObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" static="true" type="void" signature="void staticObsoleteMember()">
+ <description>
+ <para>Static obsolete method.</para>
+ </description>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::TestDerived::virtualFun" href="testqdoc-testderived.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" override="true" type="void" signature="void virtualFun() override">
+ <description/>
+ </function>
+ <typedef name="DerivedType" fullname="TestQDoc::TestDerived::DerivedType" href="testqdoc-testderived.html#DerivedType-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="Test::SomeType">
+ <description>
+ <para>An aliased typedef.</para>
+ </description>
+ </typedef>
+ <typedef name="NotTypedef" fullname="TestQDoc::TestDerived::NotTypedef" href="testqdoc-testderived.html#NotTypedef-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="int">
+ <description>
+ <para>I'm an alias, not a typedef.</para>
+ </description>
+ </typedef>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-vec.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-vec.webxml
new file mode 100644
index 000000000..9e3104404
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc-vec.webxml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <class name="Vec" fullname="TestQDoc::Vec" href="testqdoc-vec.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="Type alias that has its own reference">
+ <description>
+ <brief>Type alias that has its own reference.</brief>
+ </description>
+ </class>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc.webxml
new file mode 100644
index 000000000..de87a3e86
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testqdoc.webxml
@@ -0,0 +1,176 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <namespace name="TestQDoc" href="testqdoc.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="A namespace">
+ <contents name="usage" title="Usage" level="1"/>
+ <description>
+ <brief>A namespace.</brief>
+ <section id="usage">
+ <heading level="1">Usage</heading>
+ <para>This namespace is for testing QDoc output.</para>
+ </section>
+ </description>
+ <function name="QDOCTEST_MACRO" href="testqdoc.html#QDOCTEST_MACRO" status="active" access="public" documented="true" related="0" meta="macrowithoutparams" signature="QDOCTEST_MACRO">
+ <description/>
+ </function>
+ <class threadsafety="reentrant" name="Test" fullname="TestQDoc::Test" href="testqdoc-test.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" groups="cpptypes,testgroup" module="TestCPP" brief="A class in a namespace">
+ <description>
+ <brief>A class in a namespace.</brief>
+ </description>
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ <description>
+ <brief>A macro with argument <argument>x</argument>.</brief>
+ </description>
+ </function>
+ <function name="Q_INVOKABLE" href="testqdoc-test.html#Q_INVOKABLE" status="active" access="public" documented="true" related="2" meta="macrowithoutparams" signature="Q_INVOKABLE">
+ <description>
+ <para>This is a mock Q_INVOKABLE for the purpose of ensuring QDoc autolink to it as expected.</para>
+ </description>
+ </function>
+ <function name="anotherObsoleteMember" fullname="TestQDoc::Test::anotherObsoleteMember" href="testqdoc-test-obsolete.html#anotherObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void anotherObsoleteMember()">
+ <description>
+ <para>Use <link raw="obsoleteMember()" href="testqdoc-test.html#obsoleteMember" type="function">obsoleteMember()</link> instead.</para>
+ </description>
+ </function>
+ <function name="deprecatedMember" fullname="TestQDoc::Test::deprecatedMember" href="testqdoc-test-obsolete.html#deprecatedMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void deprecatedMember()">
+ <description>
+ <para>Use <link raw="someFunction()" href="testqdoc-test.html#someFunction" type="function">someFunction()</link> instead.</para>
+ </description>
+ </function>
+ <function name="funcPtr" fullname="TestQDoc::Test::funcPtr" href="testqdoc-test.html#funcPtr" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void (*)(bool)" signature="void (*)(bool) funcPtr(bool b, const char *s)">
+ <parameter type="bool" name="b" default=""/>
+ <parameter type="const char *" name="s" default=""/>
+ <description>
+ <para>Returns a pointer to a function that takes a boolean. Uses <argument>b</argument> and <argument>s</argument>.</para>
+ </description>
+ </function>
+ <function name="funcTemplate" fullname="TestQDoc::Test::funcTemplate" href="testqdoc-test.html#funcTemplate" status="active" access="protected" location="testcpp.h" documented="true" meta="plain" type="void" brief="Function template with two parameters, a and b" signature="void funcTemplate(T1 a, T2 b)">
+ <parameter type="T1" name="a" default=""/>
+ <parameter type="T2" name="b" default=""/>
+ <description>
+ <brief>Function template with two parameters, <argument>a</argument> and <argument>b</argument>.</brief>
+ </description>
+ </function>
+ <function name="inlineFunction" fullname="TestQDoc::Test::inlineFunction" href="testqdoc-test.html#inlineFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" brief="An inline function, documented using the \fn QDoc command" signature="void inlineFunction()">
+ <description>
+ <brief>An inline function, documented using the \fn QDoc command.</brief>
+ </description>
+ </function>
+ <function name="methodWithEmDashInItsDocs" fullname="TestQDoc::Test::methodWithEmDashInItsDocs" href="testqdoc-test.html#methodWithEmDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEmDashInItsDocs()">
+ <description>
+ <para>This method has em dashes in its documentation—as you'll find represented by <teletype type="highlighted">---</teletype> in the sources—here and there. The important bit to note is that when passed e.g. to the \c command, the three hyphens are processed as input to the command and not replaced by an em dash.</para>
+ <para>-----------------------------------------------------------------------</para>
+ <para>People can still add a bunch of dashes, though, without QDoc replacing them all with a series of em dashes.</para>
+ <para>—You can also start a new paragraph with an em dash, if you want to.</para>
+ <see-also>
+ <link raw="methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" type="function">methodWithEnDashInItsDocs</link>
+ </see-also>
+ </description>
+ </function>
+ <function name="methodWithEnDashInItsDocs" fullname="TestQDoc::Test::methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEnDashInItsDocs()">
+ <description>
+ <para>This method has en dashes in its documentation – as you'll find represented by <teletype type="highlighted">--</teletype> in the sources – here and there. The important bit to note is that when passed e.g. to the \c command, the two hyphens are processed as input to the command and not replaced by an en dash. This also applies to code blocks, where otherwise, the decrement operator would get completely borked:</para>
+ <code>for (int i = 42; i &gt; 0; --i)
+ // Do something cool during countdown.</code>
+ <para>...as it would be silly if this would output –i instead of <teletype type="highlighted">--i</teletype>.</para>
+ <para>-----------------------------------------------------------------------</para>
+ <para>It still allows people to add a bunch of dashes, though, without replacing them all with a series of en dashes. Of course, they might want to use the \hr command instead, like this:</para>
+ <para>– You can also start a new paragraph with an en dash, if you want to.</para>
+ <see-also>methodWithEnDashInItsDocs</see-also>
+ </description>
+ </function>
+ <function name="obsoleteMember" fullname="TestQDoc::Test::obsoleteMember" href="testqdoc-test-obsolete.html#obsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void obsoleteMember()">
+ <description>
+ <para>Use <link raw="someFunction()" href="testqdoc-test.html#someFunction" type="function">someFunction()</link> instead.</para>
+ </description>
+ </function>
+ <function name="operator++" fullname="TestQDoc::Test::operator++" href="testqdoc-test-obsolete.html#operator-2b-2b" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator++()">
+ <description/>
+ </function>
+ <function name="operator--" fullname="TestQDoc::Test::operator--" href="testqdoc-test-obsolete.html#operator--" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator--()">
+ <description/>
+ </function>
+ <function name="operator==" href="testqdoc-test.html#operator-eq-eq" status="active" access="public" location="testcpp.h" documented="true" related="3" meta="plain" type="bool" signature="bool operator==(const TestQDoc::Test &amp;lhs, const TestQDoc::Test &amp;rhs)">
+ <parameter type="const TestQDoc::Test &amp;" name="lhs" default=""/>
+ <parameter type="const TestQDoc::Test &amp;" name="rhs" default=""/>
+ <description>
+ <para>Returns true if <argument>lhs</argument> and <argument>rhs</argument> are equal.</para>
+ </description>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload" status="active" access="protected" location="testcpp.h" documented="true" meta="plain" type="void" signature="void overload()">
+ <description/>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload-1" status="active" access="protected" location="testcpp.h" documented="true" since="Test 1.2" meta="plain" overload="true" overload-number="1" type="void" signature="void overload(bool b)">
+ <parameter type="bool" name="b" default=""/>
+ <description/>
+ </function>
+ <function name="someFunction" fullname="TestQDoc::Test::someFunction" href="testqdoc-test.html#someFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="int" signature="int someFunction(int, int v)">
+ <parameter type="int" name="" default=""/>
+ <parameter type="int" name="v" default="0"/>
+ <description>
+ <para>Function that takes a parameter <argument>v</argument>. Also returns the value of <argument>v</argument>.</para>
+ </description>
+ </function>
+ <function name="someFunctionDefaultArg" fullname="TestQDoc::Test::someFunctionDefaultArg" href="testqdoc-test.html#someFunctionDefaultArg" threadsafety="non-reentrant" status="active" access="public" location="testcpp.h" documented="true" meta="plain" const="true" type="void" signature="void someFunctionDefaultArg(int i, bool b) const" groups="testgroup">
+ <parameter type="int" name="i" default=""/>
+ <parameter type="bool" name="b" default="false"/>
+ <description>
+ <para>Function that takes a parameter <argument>i</argument> and <argument>b</argument>.</para>
+ </description>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::Test::virtualFun" href="testqdoc-test.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" type="void" signature="void virtualFun()">
+ <description>
+ <para>Function that must be reimplemented.</para>
+ </description>
+ </function>
+ <struct name="Struct" fullname="TestQDoc::Test::Struct" href="testqdoc-test-struct.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="Templated struct">
+ <description>
+ <brief>Templated struct.</brief>
+ </description>
+ </struct>
+ <typedef name="SomeType" fullname="TestQDoc::Test::SomeType" href="testqdoc-test.html#SomeType-typedef" status="active" access="public" location="testcpp.h" documented="true">
+ <description>
+ <brief>A typedef.</brief>
+ </description>
+ </typedef>
+ <typedef name="Specialized" fullname="TestQDoc::Test::Specialized" href="testqdoc-test.html#Specialized-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="Struct&lt;int, T&gt;">
+ <description/>
+ </typedef>
+ </class>
+ <class name="TestDerived" fullname="TestQDoc::TestDerived" href="testqdoc-testderived.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" bases="TestQDoc::Test" module="TestCPP" brief="A class in a namespace, derived from Test">
+ <description>
+ <brief>A class in a namespace, derived from <link raw="Test" href="testqdoc-test.html" type="class">Test</link>.</brief>
+ </description>
+ <function name="someValue" fullname="TestQDoc::TestDerived::someValue" href="testqdoc-testderived.html#someValue" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::TestDerived::NotTypedef" signature="TestQDoc::TestDerived::NotTypedef someValue()">
+ <description>
+ <para>Returns a value using an aliases type.</para>
+ </description>
+ </function>
+ <function name="staticObsoleteMember" fullname="TestQDoc::TestDerived::staticObsoleteMember" href="testqdoc-testderived-obsolete.html#staticObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" static="true" type="void" signature="void staticObsoleteMember()">
+ <description>
+ <para>Static obsolete method.</para>
+ </description>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::TestDerived::virtualFun" href="testqdoc-testderived.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" override="true" type="void" signature="void virtualFun() override">
+ <description/>
+ </function>
+ <typedef name="DerivedType" fullname="TestQDoc::TestDerived::DerivedType" href="testqdoc-testderived.html#DerivedType-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="Test::SomeType">
+ <description>
+ <para>An aliased typedef.</para>
+ </description>
+ </typedef>
+ <typedef name="NotTypedef" fullname="TestQDoc::TestDerived::NotTypedef" href="testqdoc-testderived.html#NotTypedef-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="int">
+ <description>
+ <para>I'm an alias, not a typedef.</para>
+ </description>
+ </typedef>
+ </class>
+ <class name="Vec" fullname="TestQDoc::Vec" href="testqdoc-vec.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="Type alias that has its own reference">
+ <description>
+ <brief>Type alias that has its own reference.</brief>
+ </description>
+ </class>
+ </namespace>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testtemplate.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testtemplate.index
new file mode 100644
index 000000000..34346db56
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/expected/webxml/testtemplate.index
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="TestTemplate Reference Documentation" version="" project="TestTemplate">
+ <namespace name="" status="active" access="public" module="testtemplate">
+ <function name="QDOCTEST_MACRO" href="testqdoc.html#QDOCTEST_MACRO" status="active" access="public" documented="true" related="0" meta="macrowithoutparams" signature="QDOCTEST_MACRO"/>
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ </function>
+ <function name="Q_INVOKABLE" href="testqdoc-test.html#Q_INVOKABLE" status="active" access="public" documented="true" related="2" meta="macrowithoutparams" signature="Q_INVOKABLE"/>
+ <page name="autolinking.html" href="autolinking.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Autolinking" fulltitle="Autolinking" subtitle="">
+ <contents name="testqdoc" title="TestQDoc" level="1"/>
+ <contents name="someprop" title="someProp" level="1"/>
+ </page>
+ <class name="Bar" href="bar.html" status="active" access="public" location="testtemplate.h" since="2.0" documented="true" module="TestCPP" brief="Another class template"/>
+ <struct name="Baz" href="baz.html" status="active" access="public" location="testtemplate.h" since="2.0" documented="true" module="TestCPP" brief="Class template template"/>
+ <namespace name="CrossModuleRef" href="crossmoduleref.html" status="active" access="public" location="testcpp.h" since="3.0" documented="true" module="TestCPP" brief="Namespace that has documented functions in multiple modules">
+ <function name="documentMe" fullname="CrossModuleRef::documentMe" href="crossmoduleref.html#documentMe" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void documentMe()"/>
+ </namespace>
+ <class name="DontLinkToMe" href="dontlinktome.html" status="ignored" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="Class that does not generate documentation"/>
+ <class name="Foo" href="foo.html" status="active" access="public" location="testtemplate.h" since="2.0" documented="true" module="TestCPP" brief="Class template"/>
+ <page name="obsolete-classes.html" href="obsolete-classes.html" status="active" location="classlists.qdoc" documented="true" subtype="page" title="Obsolete Classes" fulltitle="Obsolete Classes" subtitle="">
+ <contents name="classes-with-obsolete-members" title="Classes with obsolete members" level="1"/>
+ <contents name="testqdoc" title="TestQDoc" level="2"/>
+ </page>
+ <namespace name="TestQDoc" href="testqdoc.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="A namespace">
+ <contents name="usage" title="Usage" level="1"/>
+ <function name="QDOCTEST_MACRO" href="testqdoc.html#QDOCTEST_MACRO" status="active" access="public" documented="true" related="0" meta="macrowithoutparams" signature="QDOCTEST_MACRO"/>
+ <class threadsafety="reentrant" name="Test" fullname="TestQDoc::Test" href="testqdoc-test.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" groups="cpptypes,testgroup" module="TestCPP" brief="A class in a namespace">
+ <function name="QDOCTEST_MACRO2" href="testqdoc-test.html#QDOCTEST_MACRO2" status="active" access="public" documented="true" related="1" since="Test 1.1" meta="macrowithparams" brief="A macro with argument x" signature="QDOCTEST_MACRO2(int &amp;x)" groups="testgroup">
+ <parameter type="int &amp;" name="x" default=""/>
+ </function>
+ <function name="Q_INVOKABLE" href="testqdoc-test.html#Q_INVOKABLE" status="active" access="public" documented="true" related="2" meta="macrowithoutparams" signature="Q_INVOKABLE"/>
+ <function name="anotherObsoleteMember" fullname="TestQDoc::Test::anotherObsoleteMember" href="testqdoc-test-obsolete.html#anotherObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void anotherObsoleteMember()"/>
+ <function name="deprecatedMember" fullname="TestQDoc::Test::deprecatedMember" href="testqdoc-test-obsolete.html#deprecatedMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void deprecatedMember()"/>
+ <function name="funcPtr" fullname="TestQDoc::Test::funcPtr" href="testqdoc-test.html#funcPtr" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void (*)(bool)" signature="void (*)(bool) funcPtr(bool b, const char *s)">
+ <parameter type="bool" name="b" default=""/>
+ <parameter type="const char *" name="s" default=""/>
+ </function>
+ <function name="funcTemplate" fullname="TestQDoc::Test::funcTemplate" href="testqdoc-test.html#funcTemplate" status="active" access="protected" location="testcpp.h" documented="true" meta="plain" type="void" brief="Function template with two parameters, a and b" signature="void funcTemplate(T1 a, T2 b)">
+ <parameter type="T1" name="a" default=""/>
+ <parameter type="T2" name="b" default=""/>
+ </function>
+ <function name="inlineFunction" fullname="TestQDoc::Test::inlineFunction" href="testqdoc-test.html#inlineFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" brief="An inline function, documented using the \fn QDoc command" signature="void inlineFunction()"/>
+ <function name="methodWithEmDashInItsDocs" fullname="TestQDoc::Test::methodWithEmDashInItsDocs" href="testqdoc-test.html#methodWithEmDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEmDashInItsDocs()"/>
+ <function name="methodWithEnDashInItsDocs" fullname="TestQDoc::Test::methodWithEnDashInItsDocs" href="testqdoc-test.html#methodWithEnDashInItsDocs" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void methodWithEnDashInItsDocs()"/>
+ <function name="obsoleteMember" fullname="TestQDoc::Test::obsoleteMember" href="testqdoc-test-obsolete.html#obsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="void" signature="void obsoleteMember()"/>
+ <function name="operator++" fullname="TestQDoc::Test::operator++" href="testqdoc-test-obsolete.html#operator-2b-2b" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator++()"/>
+ <function name="operator--" fullname="TestQDoc::Test::operator--" href="testqdoc-test-obsolete.html#operator--" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::Test &amp;" signature="TestQDoc::Test &amp; operator--()"/>
+ <function name="operator==" href="testqdoc-test.html#operator-eq-eq" status="active" access="public" location="testcpp.h" documented="true" related="3" meta="plain" type="bool" signature="bool operator==(const TestQDoc::Test &amp;lhs, const TestQDoc::Test &amp;rhs)">
+ <parameter type="const TestQDoc::Test &amp;" name="lhs" default=""/>
+ <parameter type="const TestQDoc::Test &amp;" name="rhs" default=""/>
+ </function>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload" status="active" access="protected" location="testcpp.h" documented="true" meta="plain" type="void" signature="void overload()"/>
+ <function name="overload" fullname="TestQDoc::Test::overload" href="testqdoc-test.html#overload-1" status="active" access="protected" location="testcpp.h" documented="true" since="Test 1.2" meta="plain" overload="true" overload-number="1" type="void" signature="void overload(bool b)">
+ <parameter type="bool" name="b" default=""/>
+ </function>
+ <function name="someFunction" fullname="TestQDoc::Test::someFunction" href="testqdoc-test.html#someFunction" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="int" signature="int someFunction(int, int v)">
+ <parameter type="int" name="" default=""/>
+ <parameter type="int" name="v" default="0"/>
+ </function>
+ <function name="someFunctionDefaultArg" fullname="TestQDoc::Test::someFunctionDefaultArg" href="testqdoc-test.html#someFunctionDefaultArg" threadsafety="non-reentrant" status="active" access="public" location="testcpp.h" documented="true" meta="plain" const="true" type="void" signature="void someFunctionDefaultArg(int i, bool b) const" groups="testgroup">
+ <parameter type="int" name="i" default=""/>
+ <parameter type="bool" name="b" default="false"/>
+ </function>
+ <function name="virtualFun" fullname="TestQDoc::Test::virtualFun" href="testqdoc-test.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" type="void" signature="void virtualFun()"/>
+ <struct name="Struct" fullname="TestQDoc::Test::Struct" href="testqdoc-test-struct.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="Templated struct"/>
+ <typedef name="SomeType" fullname="TestQDoc::Test::SomeType" href="testqdoc-test.html#SomeType-typedef" status="active" access="public" location="testcpp.h" documented="true"/>
+ <typedef name="Specialized" fullname="TestQDoc::Test::Specialized" href="testqdoc-test.html#Specialized-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="Struct&lt;int, T&gt;"/>
+ </class>
+ <class name="TestDerived" fullname="TestQDoc::TestDerived" href="testqdoc-testderived.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" bases="TestQDoc::Test" module="TestCPP" brief="A class in a namespace, derived from Test">
+ <function name="someValue" fullname="TestQDoc::TestDerived::someValue" href="testqdoc-testderived.html#someValue" status="active" access="public" location="testcpp.h" documented="true" meta="plain" type="TestQDoc::TestDerived::NotTypedef" signature="TestQDoc::TestDerived::NotTypedef someValue()"/>
+ <function name="staticObsoleteMember" fullname="TestQDoc::TestDerived::staticObsoleteMember" href="testqdoc-testderived-obsolete.html#staticObsoleteMember" status="deprecated" access="public" location="testcpp.h" documented="true" meta="plain" static="true" type="void" signature="void staticObsoleteMember()"/>
+ <function name="virtualFun" fullname="TestQDoc::TestDerived::virtualFun" href="testqdoc-testderived.html#virtualFun" status="active" access="public" location="testcpp.h" documented="true" meta="plain" virtual="virtual" override="true" type="void" signature="void virtualFun() override"/>
+ <typedef name="DerivedType" fullname="TestQDoc::TestDerived::DerivedType" href="testqdoc-testderived.html#DerivedType-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="Test::SomeType"/>
+ <typedef name="NotTypedef" fullname="TestQDoc::TestDerived::NotTypedef" href="testqdoc-testderived.html#NotTypedef-typedef" status="active" access="public" location="testcpp.h" documented="true" aliasedtype="int"/>
+ </class>
+ <class name="Vec" fullname="TestQDoc::Vec" href="testqdoc-vec.html" status="active" access="public" location="testcpp.h" since="2.0" documented="true" module="TestCPP" brief="Type alias that has its own reference"/>
+ </namespace>
+ <page name="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command" href="https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command" status="active" location="classlists.qdoc" documented="true" subtype="externalpage" title="reentrant" fulltitle="reentrant" subtitle=""/>
+ <group name="cpptypes" href="cpptypes.html" status="active" location="classlists.qdoc" documented="true" seen="true" title="Test C++ Types"/>
+ <group name="testgroup" href="testgroup.html" status="internal" seen="false" title=""/>
+ <module name="TestCPP" href="testcpp-module.html" status="active" since="2.0" documented="true" seen="true" title="QDoc Test C++ Classes" brief="A test module page">
+ <contents name="linking-to-function-like-things" title="Linking to function-like things" level="1"/>
+ <contents name="section" title="section()" level="2"/>
+ </module>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/classlists.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/classlists.qdoc
new file mode 100644
index 000000000..2954e5beb
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/classlists.qdoc
@@ -0,0 +1,51 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*!
+ \page obsolete-classes.html
+ \title Obsolete Classes
+
+ \section1 Classes with obsolete members
+ \generatelist obsoletecppmembers
+
+ \section2 TestQDoc
+*/
+
+/*!
+ \page autolinking.html
+ \title Autolinking
+
+ //! a section title that qualifies for autolinking
+ \section1 TestQDoc
+
+ The string TestQDoc links to the C++ namespace unless linking explicitly,
+ \l {#TestQDoc}{like this}, or \l {TestQDoc}{this}. Also,
+
+ Autolinks:
+
+ \list
+ \li TestQDoc::TestDerived
+ \endlist
+
+ Explicit links:
+
+ \list
+ \li \l [CPP] {TestQDoc::TestDerived}
+ \li \l {Obsolete Classes#TestQDoc}
+ \endlist
+
+ //! a section title shadowing a known property name
+ \section1 someProp
+*/
+
+/*!
+ \group cpptypes
+ \title Test C++ Types
+
+ \generatelist testgroup
+*/
+
+/*!
+ \externalpage https://doc.qt.io/qt/17-qdoc-commands-thread.html#reentrant-command
+ \title reentrant
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/snippets/snippet_testcpp.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/snippets/snippet_testcpp.cpp
new file mode 100644
index 000000000..1660fbc2b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/snippets/snippet_testcpp.cpp
@@ -0,0 +1,3 @@
+//! [random tag]
+You're not supposed to see this.
+//! [random tag]
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testcpp.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testcpp.cpp
new file mode 100644
index 000000000..25dc960ff
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testcpp.cpp
@@ -0,0 +1,402 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include "testcpp.h"
+
+namespace TestQDoc {
+
+/*
+//! [random tag]
+\note This is just a test.
+//! [random tag]
+
+//! [args]
+\1\2 \3 \2\1
+//! [args]
+*/
+
+/*!
+ \module TestCPP
+ \qtvariable testcpp
+ \qtcmakepackage QDocTest
+ \title QDoc Test C++ Classes
+ \brief A test module page.
+ \since 2.0
+
+ \testnoautolist
+
+ \include testcpp.cpp random tag
+ \include testcpp.cpp {args} {/} {*} {Look, Ma! {I'm made of arguments!}}
+
+\if defined(test_nestedmacro)
+ \versionnote {module} {\ver 5.15.0}
+ \ver 1.0.0
+\endif
+
+ \section1 Linking to function-like things
+
+ \list
+ \li \l {TestQDoc::Test::someFunctionDefaultArg}
+ {someFunctionDefaultArg()}
+ \li QDOCTEST_MACRO2()
+ \li \l {TestQDoc::Test::}{QDOCTEST_MACRO2(int &x)}
+ \li \l {section()}
+ \li \l {section()} {section() is a section title}
+ \endlist
+
+ \section2 section()
+*/
+
+/*!
+ \namespace TestQDoc
+ \inheaderfile TestCPP
+ \inmodule TestCPP
+ \brief A namespace.
+
+ \section1 Usage
+ This namespace is for testing QDoc output.
+*/
+
+/*!
+ \class TestQDoc::Test
+ \inmodule TestCPP
+ \brief A class in a namespace.
+
+\if defined(test_ignoresince)
+ //! omitted by ignoresince
+ \since 1.1
+\endif
+ \ingroup testgroup
+ \ingroup cpptypes
+ \reentrant
+*/
+
+/*!
+ \class TestQDoc::TestDerived
+ \inmodule TestCPP
+ \brief A class in a namespace, derived from \l [CPP] Test.
+*/
+
+/*!
+ \macro QDOCTEST_MACRO
+ \relates TestQDoc
+\if defined(test_ignoresince)
+ //! omitted by ignoresince.Test
+ \since Test 0.9
+\endif
+*/
+
+/*!
+ \macro QDOCTEST_MACRO2(int &x)
+ \relates TestQDoc::Test
+ \since Test 1.1
+ \brief A macro with argument \a x.
+ \ingroup testgroup
+*/
+
+/*!
+\if defined(test_properties)
+ \property Test::id
+\else
+ \nothing
+\endif
+*/
+
+/*!
+ \deprecated [6.0] Use someFunction() instead.
+*/
+void Test::deprecatedMember()
+{
+ return;
+}
+
+/*!
+ \obsolete
+
+ Use someFunction() instead.
+*/
+void Test::obsoleteMember()
+{
+ return;
+}
+
+/*!
+ \obsolete Use obsoleteMember() instead.
+*/
+void Test::anotherObsoleteMember()
+{
+ return;
+}
+
+/*!
+ \nonreentrant
+ Function that takes a parameter \a i and \a b.
+\if defined(test_ignoresince)
+ \since 2.0
+\endif
+ \ingroup testgroup
+*/
+void Test::someFunctionDefaultArg(int i, bool b = false) const
+{
+ return;
+}
+
+/*!
+ \fn void Test::func(bool)
+ \internal
+*/
+
+/*!
+ \fn [funcPtr] void (*funcPtr(bool b, const char *s))(bool)
+
+ Returns a pointer to a function that takes a boolean. Uses \a b and \a s.
+*/
+
+/*!
+ \fn [op-inc] Test::operator++()
+ \fn [op-dec] Test::operator--()
+ \deprecated
+*/
+
+/*!
+ This method has en dashes in its documentation -- as you'll find
+ represented by \c{--} in the sources -- here and there. The important bit
+ to note is that when passed e.g. to the \\c command, the two hyphens are
+ processed as input to the command and not replaced by an en dash. This also
+ applies to code blocks, where otherwise, the decrement operator would get
+ completely borked:
+
+ \code
+ for (int i = 42; i > 0; --i)
+ // Do something cool during countdown.
+ \endcode
+
+ ...as it would be silly if this would output --i instead of \c {--i}.
+
+ -----------------------------------------------------------------------
+
+ It still allows people to add a bunch of dashes, though, without replacing
+ them all with a series of en dashes. Of course, they might want to use the
+ \\hr command instead, like this:
+ \hr
+
+ -- You can also start a new paragraph with an en dash, if you want to.
+
+ //! Self-referencing \sa-command for tests.
+ \sa methodWithEnDashInItsDocs
+*/
+void Test::methodWithEnDashInItsDocs()
+{
+ // Nothing to see here.
+}
+
+/*!
+ This method has em dashes in its documentation---as you'll find
+ represented by \c{---} in the sources---here and there. The important bit
+ to note is that when passed e.g. to the \\c command, the three hyphens are
+ processed as input to the command and not replaced by an em dash.
+
+ -----------------------------------------------------------------------
+
+ People can still add a bunch of dashes, though, without QDoc replacing
+ them all with a series of em dashes.
+
+ ---You can also start a new paragraph with an em dash, if you want to.
+
+ \sa methodWithEnDashInItsDocs
+
+*/
+void Test::methodWithEmDashInItsDocs()
+{
+ // Woah! Look at that!
+}
+
+// Documented below with an \fn command. Unnecessary but we support it, and it's used.
+int Test::someFunction(int, int v)
+{
+ return v;
+}
+
+/*!
+ \fn void TestQDoc::Test::inlineFunction()
+
+ \brief An inline function, documented using the \CMDFN QDoc command.
+*/
+
+/*!
+ \fn int Test::someFunction(int, int v = 0)
+
+ Function that takes a parameter \a v.
+ Also returns the value of \a v.
+\if defined(test_ignoresince)
+ \since Test 1.0
+\endif
+*/
+
+/*!
+ Function that must be reimplemented.
+*/
+void Test::virtualFun()
+{
+ return;
+}
+
+/*!
+ \fn bool Test::operator==(const Test &lhs, const Test &rhs)
+
+ Returns true if \a lhs and \a rhs are equal.
+*/
+
+/*!
+ \typedef TestQDoc::Test::SomeType
+ \brief A typedef.
+*/
+
+/*!
+ \reimp
+*/
+void TestDerived::virtualFun()
+{
+ return;
+}
+
+/*!
+ \fn TestQDoc::Test::overload()
+ \fn Test::overload(bool b)
+ //! The second overload should match even without the fully qualified path
+
+ Overloads that share a documentation comment, optionally taking
+ a parameter \a b.
+*/
+
+/*!
+ \fn Test::overload(bool b)
+ \since Test 1.2
+*/
+
+/*!
+ \typealias TestQDoc::TestDerived::DerivedType
+ An aliased typedef.
+*/
+
+/*!
+ \typedef TestQDoc::TestDerived::NotTypedef
+ I'm an alias, not a typedef.
+*/
+
+/*!
+ \obsolete
+
+ Static obsolete method.
+*/
+void TestDerived::staticObsoleteMember()
+{
+ return;
+}
+
+/*!
+\if defined(test_properties)
+ \fn void TestDerived::emitSomething()
+ Emitted when things happen.
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_properties)
+ \reimp
+\else
+ \nothing
+\endif
+*/
+int TestDerived::id()
+{
+ return 1;
+}
+
+/*!
+ Returns a value using an aliases type.
+*/
+TestDerived::NotTypedef TestDerived::someValue()
+{
+ return 0;
+}
+
+/*!
+\if defined(test_template)
+ \fn template <typename T1, typename T2> void TestQDoc::Test::funcTemplate(T1 a, T2 b)
+ \brief Function template with two parameters, \a a and \a b.
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_template)
+ \struct TestQDoc::Test::Struct
+ \inmodule TestCPP
+ \brief Templated struct.
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_template)
+ \typealias TestQDoc::Test::Specialized
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_template)
+ \class TestQDoc::Vec
+ \inmodule TestCPP
+ \brief Type alias that has its own reference.
+\else
+ \nothing
+\endif
+*/
+
+/*!
+\if defined(test_template)
+ \macro Q_INVOKABLE
+ \relates TestQDoc::Test
+
+ This is a mock Q_INVOKABLE for the purpose of ensuring QDoc autolink to it
+ as expected.
+\else
+ \nothing
+\endif
+*/
+
+} // namespace TestQDoc
+
+
+/*!
+ \namespace CrossModuleRef
+ \inmodule TestCPP
+ \brief Namespace that has documented functions in multiple modules.
+ \since 3.0
+*/
+namespace CrossModuleRef {
+
+/*!
+ Document me!
+*/
+void documentMe()
+{
+}
+
+} // namespace CrossModuleRef
+
+/*!
+ \class DontLinkToMe
+ \inmodule TestCPP
+ \brief Class that does not generate documentation.
+*/
+
+/*!
+ \dontdocument (DontLinkToMe)
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testcpp.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testcpp.h
new file mode 100644
index 000000000..06126494a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testcpp.h
@@ -0,0 +1,137 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#pragma once
+
+#ifdef test_properties
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qproperty.h>
+#include <QtCore/qstring.h>
+#endif
+
+#define QDOCTEST_MACRO test
+#define QDOCTEST_MACRO2(x) (x) < 0 ? 0 : (x))
+
+namespace TestQDoc {
+
+class Test {
+#ifdef test_properties
+ Q_OBJECT
+ Q_PROPERTY(int id READ id)
+#endif
+public:
+
+#ifdef test_template
+template<typename D, typename T> struct Struct {};
+template<typename T>
+using Specialized = Struct<int, T>;
+#endif
+
+#ifdef test_template
+# define Q_INVOKABLE void foo() {};
+#endif
+
+#ifdef test_scopedenum
+ enum ClassicEnum { Yee, Haw, Howdy, Partner };
+
+ enum class ScopedEnum : unsigned char {
+ This = 0x01,
+ That = 0x02,
+ All = This | That,
+ OmittedValue = 99,
+ UselessValue,
+ VeryLastValue
+ };
+#endif
+ typedef struct {
+ int data;
+ } SomeType;
+ int someFunction(int, int v = 0);
+ void someFunctionDefaultArg(int i, bool b) const;
+ void obsoleteMember();
+ void anotherObsoleteMember();
+ void deprecatedMember();
+ void methodWithEnDashInItsDocs();
+ void methodWithEmDashInItsDocs();
+ void func(bool) {};
+ //! [funcPtr]
+ void (*funcPtr(bool b, const char *s))(bool) {
+ return func;
+ }
+ //! [op-inc]
+ Test &operator++() { return *this; }
+ //! [op-dec]
+ Test &operator--() { return *this; }
+
+ void anotherFunc() {};
+ inline void inlineFunction() {};
+ virtual void virtualFun();
+
+ friend bool operator==(const Test &lhs, const Test &rhs) { return false; }
+
+protected:
+ void overload() {}
+ void overload(bool b) { if (!b) return; }
+#ifdef test_template
+ template <typename T1, typename T2> void funcTemplate(T1 a, T2 b) {
+ a = b;
+ }
+#endif
+#ifdef test_properties
+ virtual int id() { return 0; }
+#endif
+};
+
+class TestDerived : public Test {
+#ifdef test_properties
+ Q_OBJECT
+
+ Q_PROPERTY(QString bindableProp READ bindableProp WRITE setBindableProp NOTIFY bindablePropChanged BINDABLE bindableProp)
+ Q_PROPERTY(QString someProp READ someProp BINDABLE somBindableProp)
+ Q_PROPERTY(int *intProp READ getInt STORED false CONSTANT FINAL)
+ Q_PROPERTY(const QString *name READ name)
+ QDOC_PROPERTY(bool boolProp READ boolProp WRITE setBoolProp NOTIFY boolPropChanged RESET resetBoolProp REVISION 1)
+#endif
+
+public:
+ using DerivedType = Test::SomeType;
+ using NotTypedef = int;
+ void virtualFun() override;
+ static void staticObsoleteMember();
+ NotTypedef someValue();
+#ifdef test_properties
+ QBindable<QString> bindableProp();
+ QBindable<QString> someBindableProp();
+ const QString &someProp();
+ int *getInt();
+ bool boolProp();
+ const QString *name() const;
+
+ Q_INVOKABLE void invokeMe() const {}
+ int id() override;
+
+Q_SIGNALS:
+ void emitSomething(QPrivateSignal);
+ void bindablePropChanged();
+ Q_REVISION(1) void boolPropChanged();
+
+public Q_SLOTS:
+ void setBindableProp(const QString &s);
+ void setBoolProp(bool b);
+ void resetBoolProp();
+#endif
+};
+
+#ifdef test_template
+template <typename T>
+struct BaseVec {};
+template <typename T>
+using Vec = BaseVec<T>;
+#endif
+
+} // namespace TestQDoc
+
+namespace CrossModuleRef {
+ void documentMe();
+}
+
+class DontLinkToMe {};
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testtemplate.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testtemplate.cpp
new file mode 100644
index 000000000..04be329ea
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testtemplate.cpp
@@ -0,0 +1,23 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "testtemplate.h"
+
+/*!
+ \class Foo
+ \inmodule TestCPP
+ \brief Class template.
+*/
+
+/*!
+ \class Bar
+ \inmodule TestCPP
+ \brief Another class template.
+*/
+
+/*!
+ //! Baz is a struct, QDoc auto-converts this to the correct type
+ \class Baz
+ \inmodule TestCPP
+ \brief Class template template.
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testtemplate.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testtemplate.h
new file mode 100644
index 000000000..68c164e25
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/src/testtemplate.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+template <typename T>
+class Foo {
+public:
+ Foo() {}
+private:
+ T t;
+};
+
+template <typename T, typename D>
+class Bar {
+public:
+ Bar() {}
+private:
+ T t;
+ D d;
+};
+
+template<template<typename> class X, typename Y>
+struct Baz
+{
+ X<Y> z;
+ Baz() : z() {}
+};
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/testtemplate.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/testtemplate.qdocconf
new file mode 100644
index 000000000..188c268bd
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/testtemplate/testtemplate.qdocconf
@@ -0,0 +1,38 @@
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ./src/images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+project = TestTemplate
+includepaths += -I./src
+
+headers = ./src/testcpp.h \
+ ./src/testtemplate.h
+sources = ./src/testcpp.cpp \
+ ./src/testtemplate.cpp \
+ ./src/classlists.qdoc
+exampledirs = ./src/snippets
+
+macro.CMDFN = \\\\fn
+macro.nothing = \\dontdocument ()
+macro.testnoautolist = \\if defined(test_noautolist)\n\\noautolist\n\\endif
+
+navigation.cppclassespage = "QDoc Test C++ Classes"
+
+defines += test_template
+
+outputformats = HTML DocBook WebXML
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+DocBook.nosubdirs = true
+DocBook.outputsubdir = docbook
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/crash.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/crash.xml
new file mode 100644
index 000000000..542b9ce59
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/crash.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title></db:title>
+<db:productname>OutputFromQDocFiles</db:productname>
+<db:edition>OutputFromQDocFiles - A test project for QDoc build artifacts</db:edition>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:extendedlink xlink:type="extended"><db:link xlink:to="qdoctests-qdocfileoutput-linking.xml" xlink:type="arc" xlink:arcrole="prev" xlink:title="QDoc Linking Test"/></db:extendedlink>
+<db:extendedlink xlink:type="extended"><db:link xlink:to="toc.xml" xlink:type="arc" xlink:arcrole="next" xlink:title="Table of Contents"/></db:extendedlink>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:para><db:link xlink:href=""></db:link></db:para>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/images/01.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/images/01.png
new file mode 100644
index 000000000..d73ab969b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/images/01.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/images/leonardo-da-vinci.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/images/leonardo-da-vinci.png
new file mode 100644
index 000000000..854acb4ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/images/leonardo-da-vinci.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocfileoutput-exhaustive.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocfileoutput-exhaustive.xml
new file mode 100644
index 000000000..f7aff1e27
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocfileoutput-exhaustive.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Exhaustive testing of QDoc commands</db:title>
+<db:productname>OutputFromQDocFiles</db:productname>
+<db:edition>OutputFromQDocFiles - A test project for QDoc build artifacts</db:edition>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:extendedlink xlink:type="extended"><db:link xlink:to="qdoctests-qdocfileoutput.xml" xlink:type="arc" xlink:arcrole="prev" xlink:title="QDoc Testing"/></db:extendedlink>
+<db:extendedlink xlink:type="extended"><db:link xlink:to="qdoctests-qdocfileoutput-linking.xml" xlink:type="arc" xlink:arcrole="next" xlink:title="QDoc Linking Test"/></db:extendedlink>
+<db:abstract>
+<db:para>This page is a dumping ground for QDoc commands under test.</db:para>
+</db:abstract>
+</db:info>
+<db:section xml:id="this-is-a-section1">
+<db:title>This is a section1</db:title>
+<db:section xml:id="this-is-a-section2">
+<db:title>This is a section2</db:title>
+<db:section xml:id="this-is-a-section3">
+<db:title>This is a section3</db:title>
+<db:section xml:id="this-is-a-section4">
+<db:title>This is a section4</db:title>
+<db:programlisting language="cpp" role="bad">This is bad code
+</db:programlisting>
+<db:para>This text should have a line break riiiiight noooow.</db:para>
+<db:para><db:emphasis role="bold">All your text belong to bold</db:emphasis> ...And this is an examble of only <db:emphasis role="bold">bold</db:emphasis> being, well, bold.</db:para>
+<db:programlisting language="cpp"> ...
+</db:programlisting>
+<db:title>This a caption</db:title>
+<db:para>Lorem legal ipsum</db:para>
+<db:blockquote><db:para>This is a quotation.</db:para>
+</db:blockquote>
+<db:sidebar><db:para>Look, ma! I made a sidebar!</db:para>
+</db:sidebar>
+<db:informaltable style="generic">
+<db:tr valign="top">
+<db:td>
+<db:para>Table item in a table row</db:para>
+</db:td>
+</db:tr>
+<db:tr valign="top">
+<db:td>
+<db:para>Another item in a different row</db:para>
+</db:td>
+</db:tr>
+</db:informaltable>
+<db:important>
+<db:para>This is really important.</db:para>
+</db:important>
+<db:note>
+<db:para>The code above doesn't compile</db:para>
+</db:note>
+</db:section>
+</db:section>
+</db:section>
+</db:section>
+<db:section xml:id="images">
+<db:title>Images</db:title>
+<db:para>An image without any text:</db:para>
+<db:mediaobject>
+<db:imageobject>
+<db:imagedata fileref="images/leonardo-da-vinci.png"/>
+</db:imageobject>
+</db:mediaobject>
+<db:para>An image with just an alternative text:</db:para>
+<db:mediaobject>
+<db:alt>Image alt</db:alt>
+<db:imageobject>
+<db:imagedata fileref="images/leonardo-da-vinci.png"/>
+</db:imageobject>
+</db:mediaobject>
+<db:para>An image with alternative text and 1-atom caption:</db:para>
+<db:figure>
+<db:title>Image caption</db:title>
+<db:mediaobject>
+<db:alt>Image alt</db:alt>
+<db:imageobject>
+<db:imagedata fileref="images/leonardo-da-vinci.png"/>
+</db:imageobject>
+</db:mediaobject>
+</db:figure>
+<db:para>An image with alternative text and 2-atom caption:</db:para>
+<db:figure>
+<db:title>Image caption with <db:emphasis role="bold">bold</db:emphasis> text</db:title>
+<db:mediaobject>
+<db:alt>Image alt</db:alt>
+<db:imageobject>
+<db:imagedata fileref="images/leonardo-da-vinci.png"/>
+</db:imageobject>
+</db:mediaobject>
+</db:figure>
+<db:para>A bordered image:</db:para>
+<db:mediaobject>
+<db:imageobject>
+<db:imagedata fileref="images/leonardo-da-vinci.png"/>
+</db:imageobject>
+</db:mediaobject>
+<db:para>A bordered image with a caption:</db:para>
+<db:figure>
+<db:title>Screenshot of the System Tray Icon</db:title>
+<db:mediaobject>
+<db:imageobject>
+<db:imagedata fileref="images/leonardo-da-vinci.png"/>
+</db:imageobject>
+</db:mediaobject>
+</db:figure>
+<db:para>An inline image:</db:para>
+<db:para>The is a paragraph containing an <db:inlinemediaobject>
+<db:imageobject>
+<db:imagedata fileref="images/01.png"/>
+</db:imageobject>
+</db:inlinemediaobject> inline image to test if qdoc handles them properly, without considering rest of the line as alt text for the image.</db:para>
+<db:para>An inline image with alt text:</db:para>
+<db:para>Here is another example of <db:inlinemediaobject>
+<db:alt>No. 1</db:alt>
+<db:imageobject>
+<db:imagedata fileref="images/01.png"/>
+</db:imageobject>
+</db:inlinemediaobject> inline image with alternative text, which should be added as an attribute to the inline image.</db:para>
+<db:para>File quoting:</db:para>
+<db:programlisting language="cpp"> if (false) {
+ return 1;
+ }
+</db:programlisting>
+</db:section>
+<db:section xml:id="commands-not-yet-tested">
+<db:title>Commands not yet tested</db:title>
+<db:warning>
+<db:para>The following commands have yet to be tested: footnote link sincelist header index topicref // or just don’t care, remove it inlineimage printline printto quotefile skipline skipuntil span snippet codeline overload sub sup tableofcontents tt uicontrol endmapref endomit underline unicode</db:para>
+</db:warning>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocfileoutput-linking.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocfileoutput-linking.xml
new file mode 100644
index 000000000..f7a8c6f59
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocfileoutput-linking.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Testing QDoc's link command</db:title>
+<db:productname>OutputFromQDocFiles</db:productname>
+<db:edition>OutputFromQDocFiles - A test project for QDoc build artifacts</db:edition>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:extendedlink xlink:type="extended"><db:link xlink:to="qdoctests-qdocfileoutput-exhaustive.xml" xlink:type="arc" xlink:arcrole="prev" xlink:title="Exhaustive testing of QDoc commands"/></db:extendedlink>
+<db:extendedlink xlink:type="extended"><db:link xlink:to="" xlink:type="arc" xlink:arcrole="next" xlink:title="Random page"/></db:extendedlink>
+<db:abstract>
+<db:para>This is a page for testing QDoc's link command.</db:para>
+</db:abstract>
+</db:info>
+<db:anchor xml:id="link-test-target"/>
+<db:section xml:id="link-targets">
+<db:title>Link targets</db:title>
+<db:para>Valid parameters for the link command (<db:code>\l</db:code>) are page and section titles, targets defined with \target or \keyword commands, and API reference keywords (types, methods, namespaces, and so on).</db:para>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocfileoutput.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocfileoutput.xml
new file mode 100644
index 000000000..81b07bf82
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocfileoutput.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Testing QDoc output from .qdoc files</db:title>
+<db:productname>OutputFromQDocFiles</db:productname>
+<db:edition>OutputFromQDocFiles - A test project for QDoc build artifacts</db:edition>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:extendedlink xlink:type="extended"><db:link xlink:to="qdoctests-qdocfileoutput-exhaustive.xml" xlink:type="arc" xlink:arcrole="next" xlink:title="Exhaustive testing of QDoc commands"/></db:extendedlink>
+<db:abstract>
+<db:para>This is a simple page for testing purposes only.</db:para>
+</db:abstract>
+</db:info>
+<db:para>QDoc generates documentation for software projects. It does this by extracting <db:emphasis>QDoc comments</db:emphasis> from project source files. QDoc comments are signified by a C-style-like comment tag followed by an exclamation point, like this: <db:code>/*!</db:code> <db:code>This text is contained within QDoc comment tags.</db:code> <db:code>*/</db:code>.</db:para>
+<db:section xml:id="supported-file-types">
+<db:title>Supported file types</db:title>
+<db:para>QDoc parses <db:code>.cpp</db:code> and <db:code>.qdoc</db:code> files. It does extract comments from header (<db:code>.h</db:code>) files.</db:para>
+</db:section>
+<db:section xml:id="further-information">
+<db:title>Further information</db:title>
+<db:para>This test document is written with the purpose of testing the output QDoc generates when parsing <db:code>.qdoc</db:code> files. It is fairly simple and makes use of a limited subset of QDoc's command. Those commands are:</db:para>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:code>\page</db:code></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:code>\title</db:code></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:code>\brief</db:code></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:code>\e</db:code> (for emphasizing &quot;QDoc comments&quot;)</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:code>\c</db:code> (for multiple monospace-formatted entries)</db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:code>\section1</db:code></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:code>\list</db:code></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:code>\li</db:code></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:code>\endlist</db:code></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+<db:section xml:id="linking">
+<db:title>Linking</db:title>
+<db:para>There are multiple ways to create hyperlinks to other topics:</db:para>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="qdoctests-qdocfileoutput-linking.xml">Linking to a page title</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="qdoctests-qdocfileoutput-linking.xml#link-targets">Linking to a section title</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="qdoctests-qdocfileoutput-linking.xml#link-test-target">Linking using a \target string</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="qdoctests-qdocfileoutput-linking.xml">Linking using a \keyword string</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:section>
+<db:section xml:id="qdoc-linking-test">
+<db:title>QDoc Linking Test</db:title>
+<db:para>This section title is overridden by another target which takes precedence.</db:para>
+</db:section>
+<db:section xml:id="linking-to-something-in-a-section-title">
+<db:title>Linking to <db:link xlink:href="qdoctests-qdocfileoutput.xml#further-information">something</db:link> in a section title</db:title>
+<db:para>This is allowed but a questionable practice.</db:para>
+<db:note>
+<db:para>You're looking at detailed information.</db:para>
+</db:note>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocmanuallikefileoutput.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocmanuallikefileoutput.xml
new file mode 100644
index 000000000..38e3887ef
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/qdoctests-qdocmanuallikefileoutput.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Document Navigation</db:title>
+<db:productname>OutputFromQDocFiles</db:productname>
+<db:edition>OutputFromQDocFiles - A test project for QDoc build artifacts</db:edition>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:para>The navigation commands...</db:para>
+<db:blockquote/>
+<db:programlisting language="cpp">&amp;lt;head&amp;gt;
+ ...
+ &amp;lt;link rel=&amp;quot;start&amp;quot; href=&amp;quot;basicqt.html&amp;quot; /&amp;gt;
+ ...
+&amp;lt;/head&amp;gt;
+</db:programlisting>
+<db:section xml:id="commands">
+<db:title>Commands</db:title>
+<db:anchor xml:id="previouspage-command"/>
+<db:section xml:id="previouspage">
+<db:title>\previouspage</db:title>
+<db:para>The \previouspage command...</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/toc-test.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/toc-test.xml
new file mode 100644
index 000000000..6230326b7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/toc-test.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>TOC</db:title>
+<db:productname>OutputFromQDocFiles</db:productname>
+<db:edition>OutputFromQDocFiles - A test project for QDoc build artifacts</db:edition>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="qdoctests-qdocfileoutput.xml">QDoc Testing</db:link></db:para>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="qdoctests-qdocfileoutput-exhaustive.xml">Exhaustive testing of QDoc commands</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:listitem>
+<db:listitem>
+<db:para>Linking</db:para>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="qdoctests-qdocfileoutput-linking.xml">QDoc Linking Test</db:link></db:para>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="qdoctests-qdocfileoutput-linking.xml#link-targets">Link targets</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="crash.xml">Random page</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="toc.xml">Table of Contents</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:listitem>
+</db:itemizedlist>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/toc.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/toc.xml
new file mode 100644
index 000000000..693673259
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/docbook/toc.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Table of Contents</db:title>
+<db:productname>OutputFromQDocFiles</db:productname>
+<db:edition>OutputFromQDocFiles - A test project for QDoc build artifacts</db:edition>
+<db:titleabbrev>A test project for QDoc build artifacts</db:titleabbrev>
+<db:extendedlink xlink:type="extended"><db:link xlink:to="" xlink:type="arc" xlink:arcrole="prev" xlink:title="Random page"/></db:extendedlink>
+<db:abstract>
+<db:para>A test project for QDoc build artifacts.</db:para></db:abstract>
+</db:info>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="qdoctests-qdocfileoutput.xml">QDoc Testing</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="qdoctests-qdocfileoutput-linking.xml">QDoc Linking Test</db:link></db:para>
+</db:listitem>
+<db:listitem>
+<db:para><db:link xlink:href="toc.xml">Table of Contents</db:link></db:para>
+</db:listitem>
+</db:itemizedlist>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/crash.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/crash.html
new file mode 100644
index 000000000..3c502d3fc
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/crash.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- qdoctests-outputfromqdocfiles.qdoc -->
+ <title>OutputFromQDocFiles</title>
+</head>
+<body>
+ <link rel="prev" href="qdoctests-qdocfileoutput-linking.html" />
+ <link rel="next" href="toc.html" />
+<p class="naviNextPrevious headerNavi">
+<a class="prevPage" href="qdoctests-qdocfileoutput-linking.html">QDoc Linking Test</a>
+<a class="nextPage" href="toc.html">Table of Contents</a>
+</p>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<!-- $$$crash.html-description -->
+<div class="descr" id="details">
+<p></p>
+</div>
+<!-- @@@crash.html -->
+<p class="naviNextPrevious footerNavi">
+<a class="prevPage" href="qdoctests-qdocfileoutput-linking.html">QDoc Linking Test</a>
+<a class="nextPage" href="toc.html">Table of Contents</a>
+</p>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/images/01.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/images/01.png
new file mode 100644
index 000000000..d73ab969b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/images/01.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/images/leonardo-da-vinci.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/images/leonardo-da-vinci.png
new file mode 100644
index 000000000..854acb4ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/images/leonardo-da-vinci.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/outputfromqdocfiles.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/outputfromqdocfiles.index
new file mode 100644
index 000000000..c158f9fac
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/outputfromqdocfiles.index
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="A test project for QDoc build artifacts" version="" project="OutputFromQDocFiles">
+ <namespace name="" status="active" access="public" module="outputfromqdocfiles">
+ <page name="qdoctests-qdocmanuallikefileoutput.html" href="qdoctests-qdocmanuallikefileoutput.html" status="active" location="qdoctests-outputfromqdocmanuallikefiles.qdoc" documented="true" subtype="page" title="Document Navigation" fulltitle="Document Navigation" subtitle="">
+ <target name="previouspage-command"/>
+ <contents name="commands" title="Commands" level="1"/>
+ <contents name="previouspage" title="\previouspage" level="2"/>
+ </page>
+ <page name="qdoctests-qdocfileoutput-exhaustive.html" href="qdoctests-qdocfileoutput-exhaustive.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Exhaustive testing of QDoc commands" fulltitle="Exhaustive testing of QDoc commands" subtitle="" brief="This page is a dumping ground for QDoc commands under test">
+ <contents name="this-is-a-section1" title="This is a section1" level="1"/>
+ <contents name="this-is-a-section2" title="This is a section2" level="2"/>
+ <contents name="this-is-a-section3" title="This is a section3" level="3"/>
+ <contents name="this-is-a-section4" title="This is a section4" level="4"/>
+ <contents name="images" title="Images" level="1"/>
+ <contents name="commands-not-yet-tested" title="Commands not yet tested" level="1"/>
+ </page>
+ <page name="toc-test.html" href="toc-test.html" status="active" location="toc.qdoc" documented="true" subtype="page" title="TOC" fulltitle="TOC" subtitle=""/>
+ <page name="toc.html" href="toc.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Table of Contents" fulltitle="Table of Contents" subtitle=""/>
+ <page name="qdoctests-qdocfileoutput.html" href="qdoctests-qdocfileoutput.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Testing QDoc output from .qdoc files" fulltitle="Testing QDoc output from .qdoc files" subtitle="" brief="This is a simple page for testing purposes only">
+ <contents name="supported-file-types" title="Supported file types" level="1"/>
+ <contents name="further-information" title="Further information" level="1"/>
+ <contents name="linking" title="Linking" level="1"/>
+ <contents name="qdoc-linking-test" title="QDoc Linking Test" level="1"/>
+ <contents name="linking-to-something-in-a-section-title" title="Linking to something in a section title" level="1"/>
+ </page>
+ <page name="qdoctests-qdocfileoutput-linking.html" href="qdoctests-qdocfileoutput-linking.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Testing QDoc's link command" fulltitle="Testing QDoc's link command" subtitle="" brief="This is a page for testing QDoc's link command">
+ <target name="link-test-target"/>
+ <keyword name="qdoc-linking-test" title="QDoc Linking Test"/>
+ <contents name="link-targets" title="Link targets" level="1"/>
+ </page>
+ <page name="crash.html" href="crash.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocfileoutput-exhaustive.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocfileoutput-exhaustive.html
new file mode 100644
index 000000000..4c707b186
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocfileoutput-exhaustive.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- qdoctests-outputfromqdocfiles.qdoc -->
+ <meta name="description" content="This page is a dumping ground for QDoc commands under test.">
+ <title>Exhaustive testing of QDoc commands | OutputFromQDocFiles</title>
+</head>
+<body>
+<li><a href="toc-test.html">TOC</a></li>
+<li><a href="qdoctests-qdocfileoutput.html">Testing QDoc output from .qdoc files</a></li>
+<li>Exhaustive testing of QDoc commands</li>
+<li id="buildversion">OutputFromQDocFiles - A test project for QDoc build artifacts</li>
+ <link rel="prev" href="qdoctests-qdocfileoutput.html" />
+ <link rel="next" href="qdoctests-qdocfileoutput-linking.html" />
+<p class="naviNextPrevious headerNavi">
+<a class="prevPage" href="qdoctests-qdocfileoutput.html">QDoc Testing</a>
+<a class="nextPage" href="qdoctests-qdocfileoutput-linking.html">QDoc Linking Test</a>
+</p>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#this-is-a-section1">This is a section1</a></li>
+<li class="level2"><a href="#this-is-a-section2">This is a section2</a></li>
+<li class="level3"><a href="#this-is-a-section3">This is a section3</a></li>
+<li class="level4"><a href="#this-is-a-section4">This is a section4</a></li>
+<li class="level1"><a href="#images">Images</a></li>
+<li class="level1"><a href="#commands-not-yet-tested">Commands not yet tested</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Exhaustive testing of QDoc commands</h1>
+<!-- $$$qdoctests-qdocfileoutput-exhaustive.html-description -->
+<div class="descr" id="details">
+<h2 id="this-is-a-section1">This is a section1</h2>
+<h3 id="this-is-a-section2">This is a section2</h3>
+<h4 id="this-is-a-section3">This is a section3</h4>
+<h5 id="this-is-a-section4">This is a section4</h5>
+<pre class="cpp plain" translate="no">This is bad code</pre>
+<p>This text should have a line break riiiiight <br />
+ noooow.</p>
+<p><b>All your text belong to bold</b> ...And this is an examble of only <b>bold</b> being, well, bold.</p>
+<pre class="cpp" translate="no"> ...</pre>
+<p class="figCaption">This a caption</p>
+<div class="LegaleseLeft"><p>Lorem legal ipsum</p>
+</div><blockquote><p>This is a quotation.</p>
+</blockquote>
+ <html><body>This is <b>raw</b>. Like the <h1>Eddie Murphy</h1> movie. Just not as funny.</body></html>
+ <p>Look, ma! I made a sidebar!</p>
+<div class="table"><table class="generic">
+ <tr valign="top" class="odd"><td >Table item in a table row</td></tr>
+<tr valign="top" class="even"><td >Another item in a different row</td></tr>
+</table></div>
+<div class="admonition important">
+<p><b>Important: </b>This is really important.</p>
+</div>
+<div class="admonition note">
+<p><b>Note: </b>The code above doesn't compile</p>
+</div>
+<hr />
+<h2 id="images">Images</h2>
+<p>An image without any text:</p>
+<p class="centerAlign"><img src="images/leonardo-da-vinci.png" alt="" /></p><p>An image with just an alternative text:</p>
+<p class="centerAlign"><img src="images/leonardo-da-vinci.png" alt="Image alt" /></p><p>An image with alternative text and 1-atom caption:</p>
+<p class="centerAlign"><img src="images/leonardo-da-vinci.png" alt="Image alt" /></p><p class="figCaption">Image caption</p>
+<p>An image with alternative text and 2-atom caption:</p>
+<p class="centerAlign"><img src="images/leonardo-da-vinci.png" alt="Image alt" /></p><p class="figCaption">Image caption with <b>bold</b> text</p>
+<p>A bordered image:</p>
+<div class="border"><p class="centerAlign"><img src="images/leonardo-da-vinci.png" alt="" /></p></div><p>A bordered image with a caption:</p>
+<div class="border"><p class="centerAlign"><img src="images/leonardo-da-vinci.png" alt="" /></p></div><p class="figCaption">Screenshot of the System Tray Icon</p>
+<p>An inline image:</p>
+<p>The is a paragraph containing an <img src="images/01.png" alt="" /> inline image to test if qdoc handles them properly, without considering rest of the line as alt text for the image.</p>
+<p>An inline image with alt text:</p>
+<p>Here is another example of <img src="images/01.png" alt="No. 1" /> inline image with alternative text, which should be added as an attribute to the inline image.</p>
+<p>File quoting:</p>
+<pre class="cpp" translate="no"> <span class="keyword">if</span> (<span class="keyword">false</span>) {
+ <span class="keyword">return</span> <span class="number">1</span>;
+ }</pre>
+<h2 id="commands-not-yet-tested">Commands not yet tested</h2>
+<div class="admonition warning">
+<p><b>Warning: </b>The following commands have yet to be tested: footnote link sincelist header index topicref // or just don’t care, remove it inlineimage printline printto quotefile skipline skipuntil span snippet codeline overload sub sup tableofcontents tt uicontrol endmapref endomit underline unicode</p>
+</div>
+</div>
+<!-- @@@qdoctests-qdocfileoutput-exhaustive.html -->
+<p class="naviNextPrevious footerNavi">
+<a class="prevPage" href="qdoctests-qdocfileoutput.html">QDoc Testing</a>
+<a class="nextPage" href="qdoctests-qdocfileoutput-linking.html">QDoc Linking Test</a>
+</p>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocfileoutput-linking.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocfileoutput-linking.html
new file mode 100644
index 000000000..137707017
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocfileoutput-linking.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- qdoctests-outputfromqdocfiles.qdoc -->
+ <meta name="description" content="This is a page for testing QDoc's link command.">
+ <title>Testing QDoc's link command | OutputFromQDocFiles</title>
+</head>
+<body>
+<li><a href="toc-test.html">TOC</a></li>
+<li>Testing QDoc's link command</li>
+<li id="buildversion">OutputFromQDocFiles - A test project for QDoc build artifacts</li>
+ <link rel="prev" href="qdoctests-qdocfileoutput-exhaustive.html" />
+ <link rel="next" href="" />
+<p class="naviNextPrevious headerNavi">
+<a class="prevPage" href="qdoctests-qdocfileoutput-exhaustive.html">Exhaustive testing of QDoc commands</a>
+<a class="nextPage" href="">Random page</a>
+</p>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#link-targets">Link targets</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Testing QDoc's link command</h1>
+<!-- $$$qdoctests-qdocfileoutput-linking.html-description -->
+<div class="descr" id="details">
+<span id="link-test-target"></span><h2 id="link-targets">Link targets</h2>
+<p>Valid parameters for the link command (<code translate="no">\l</code>) are page and section titles, targets defined with \target or \keyword commands, and API reference keywords (types, methods, namespaces, and so on).</p>
+</div>
+<!-- @@@qdoctests-qdocfileoutput-linking.html -->
+<p class="naviNextPrevious footerNavi">
+<a class="prevPage" href="qdoctests-qdocfileoutput-exhaustive.html">Exhaustive testing of QDoc commands</a>
+<a class="nextPage" href="">Random page</a>
+</p>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocfileoutput.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocfileoutput.html
new file mode 100644
index 000000000..9b7578fc3
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocfileoutput.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- qdoctests-outputfromqdocfiles.qdoc -->
+ <meta name="description" content="This is a simple page for testing purposes only.">
+ <title>Testing QDoc output from .qdoc files | OutputFromQDocFiles</title>
+</head>
+<body>
+<li><a href="toc-test.html">TOC</a></li>
+<li>Testing QDoc output from .qdoc files</li>
+<li id="buildversion">OutputFromQDocFiles - A test project for QDoc build artifacts</li>
+ <link rel="next" href="qdoctests-qdocfileoutput-exhaustive.html" />
+<p class="naviNextPrevious headerNavi">
+<a class="nextPage" href="qdoctests-qdocfileoutput-exhaustive.html">Exhaustive testing of QDoc commands</a>
+</p>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#supported-file-types">Supported file types</a></li>
+<li class="level1"><a href="#further-information">Further information</a></li>
+<li class="level1"><a href="#linking">Linking</a></li>
+<li class="level1"><a href="#qdoc-linking-test">QDoc Linking Test</a></li>
+<li class="level1"><a href="#linking-to-something-in-a-section-title">Linking to something in a section title</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Testing QDoc output from .qdoc files</h1>
+<!-- $$$qdoctests-qdocfileoutput.html-description -->
+<div class="descr" id="details">
+<p>QDoc generates documentation for software projects. It does this by extracting <i>QDoc comments</i> from project source files. QDoc comments are signified by a C-style-like comment tag followed by an exclamation point, like this: <code translate="no">/*!</code> <code translate="no">This text is contained within QDoc comment tags.</code> <code translate="no">*/</code>.</p>
+<h2 id="supported-file-types">Supported file types</h2>
+<p>QDoc parses <code translate="no">.cpp</code> and <code translate="no">.qdoc</code> files. It does extract comments from header (<code translate="no">.h</code>) files.</p>
+<h2 id="further-information">Further information</h2>
+<p>This test document is written with the purpose of testing the output QDoc generates when parsing <code translate="no">.qdoc</code> files. It is fairly simple and makes use of a limited subset of QDoc's command. Those commands are:</p>
+<ul>
+<li><code translate="no">\page</code></li>
+<li><code translate="no">\title</code></li>
+<li><code translate="no">\brief</code></li>
+<li><code translate="no">\e</code> (for emphasizing &quot;QDoc comments&quot;)</li>
+<li><code translate="no">\c</code> (for multiple monospace-formatted entries)</li>
+<li><code translate="no">\section1</code></li>
+<li><code translate="no">\list</code></li>
+<li><code translate="no">\li</code></li>
+<li><code translate="no">\endlist</code></li>
+</ul>
+<h2 id="linking">Linking</h2>
+<p>There are multiple ways to create hyperlinks to other topics:</p>
+<ul>
+<li><a href="qdoctests-qdocfileoutput-linking.html">Linking to a page title</a></li>
+<li><a href="qdoctests-qdocfileoutput-linking.html#link-targets">Linking to a section title</a></li>
+<li><a href="qdoctests-qdocfileoutput-linking.html#link-test-target">Linking using a \target string</a></li>
+<li><a href="qdoctests-qdocfileoutput-linking.html">Linking using a \keyword string</a></li>
+</ul>
+<h2 id="qdoc-linking-test">QDoc Linking Test</h2>
+<p>This section title is overridden by another target which takes precedence.</p>
+<h2 id="linking-to-something-in-a-section-title">Linking to <a href="qdoctests-qdocfileoutput.html#further-information">something</a> in a section title</h2>
+<p>This is allowed but a questionable practice.</p>
+<details>
+<summary>QDoc details</summary>
+<div class="admonition note">
+<p><b>Note: </b>You're looking at detailed information.</p>
+</div>
+</details>
+</div>
+<!-- @@@qdoctests-qdocfileoutput.html -->
+<p class="naviNextPrevious footerNavi">
+<a class="nextPage" href="qdoctests-qdocfileoutput-exhaustive.html">Exhaustive testing of QDoc commands</a>
+</p>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocmanuallikefileoutput.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocmanuallikefileoutput.html
new file mode 100644
index 000000000..e4b120bd0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/qdoctests-qdocmanuallikefileoutput.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- qdoctests-outputfromqdocmanuallikefiles.qdoc -->
+ <title>Document Navigation | OutputFromQDocFiles</title>
+</head>
+<body>
+<li id="buildversion">OutputFromQDocFiles - A test project for QDoc build artifacts</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#commands">Commands</a></li>
+<li class="level2"><a href="#previouspage">\previouspage</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Document Navigation</h1>
+<!-- $$$qdoctests-qdocmanuallikefileoutput.html-description -->
+<div class="descr" id="details">
+<p>The navigation commands...</p>
+<blockquote> <table border="0" cellpadding="0" cellspacing="5" width="100%">
+
+ <tr>
+ <p>
+ [Previous: <a href="15-qdoc-commands-navigation.html#deadlink">
+ Basic Qt</a>]
+ [<a href="15-qdoc-commands-navigation.html#deadlink">Contents</a>]
+ [Next: <a href="15-qdoc-commands-navigation.html#deadlink">
+ Creating Dialogs</a>]
+ </p>
+
+ <h1 align="center">Getting Started<br /></h1>
+
+ <p>
+ This chapter shows how to combine basic C++ with the
+ functionality provided by Qt to create a few small graphical
+ interface (GUI) applications.
+ </p>
+
+ <p>
+ [Previous: <a href="15-qdoc-commands-navigation.html#deadlink">
+ Basic Qt</a>]
+ [<a href="15-qdoc-commands-navigation.html#deadlink">Contents</a>]
+ [Next: <a href="15-qdoc-commands-navigation.html#deadlink">
+ Creating Dialogs</a>]
+ </p>
+
+ </table>
+ </blockquote>
+<pre class="cpp" translate="no"><span class="operator">&lt;</span>head<span class="operator">&gt;</span>
+ <span class="operator">.</span><span class="operator">.</span><span class="operator">.</span>
+ <span class="operator">&lt;</span>link rel<span class="operator">=</span><span class="string">&quot;start&quot;</span> href<span class="operator">=</span><span class="string">&quot;basicqt.html&quot;</span> <span class="operator">/</span><span class="operator">&gt;</span>
+ <span class="operator">.</span><span class="operator">.</span><span class="operator">.</span>
+<span class="operator">&lt;</span><span class="operator">/</span>head<span class="operator">&gt;</span></pre>
+<h2 id="commands">Commands</h2>
+<span id="previouspage-command"></span><h3 id="previouspage">\previouspage</h3>
+<p>The \previouspage command...</p>
+</div>
+<!-- @@@qdoctests-qdocmanuallikefileoutput.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/toc-test.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/toc-test.html
new file mode 100644
index 000000000..08ec52c10
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/toc-test.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- toc.qdoc -->
+ <title>TOC | OutputFromQDocFiles</title>
+</head>
+<body>
+<li id="buildversion">OutputFromQDocFiles - A test project for QDoc build artifacts</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">TOC</h1>
+<!-- $$$toc-test.html-description -->
+<div class="descr" id="details">
+<ul>
+<li><a href="qdoctests-qdocfileoutput.html">QDoc Testing</a><ul>
+<li><a href="qdoctests-qdocfileoutput-exhaustive.html">Exhaustive testing of QDoc commands</a></li>
+</ul>
+</li>
+<li>Linking<ul>
+<li><a href="qdoctests-qdocfileoutput-linking.html">QDoc Linking Test</a><ul>
+<li><a href="qdoctests-qdocfileoutput-linking.html#link-targets">Link targets</a></li>
+</ul>
+</li>
+<li><a href="crash.html">Random page</a></li>
+<li><a href="toc.html">Table of Contents</a></li>
+</ul>
+</li>
+</ul>
+</div>
+<!-- @@@toc-test.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/toc.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/toc.html
new file mode 100644
index 000000000..d7220d013
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/html/toc.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- qdoctests-outputfromqdocfiles.qdoc -->
+ <title>Table of Contents | OutputFromQDocFiles</title>
+</head>
+<body>
+<li><a href="toc-test.html">TOC</a></li>
+<li>Table of Contents</li>
+<li id="buildversion">OutputFromQDocFiles - A test project for QDoc build artifacts</li>
+ <link rel="prev" href="" />
+<p class="naviNextPrevious headerNavi">
+<a class="prevPage" href="">Random page</a>
+</p>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Table of Contents</h1>
+<!-- $$$toc.html-description -->
+<div class="descr" id="details">
+<ul>
+<li><a href="qdoctests-qdocfileoutput.html">QDoc Testing</a></li>
+<li><a href="qdoctests-qdocfileoutput-linking.html">QDoc Linking Test</a></li>
+<li><a href="toc.html">Table of Contents</a></li>
+</ul>
+</div>
+<!-- @@@toc.html -->
+<p class="naviNextPrevious footerNavi">
+<a class="prevPage" href="">Random page</a>
+</p>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/crash.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/crash.webxml
new file mode 100644
index 000000000..6ffdbcba5
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/crash.webxml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="crash.html" href="crash.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle="">
+ <description>
+ <relation href="toc.html" type="page" meta="next" description="Table of Contents"/>
+ <relation href="qdoctests-qdocfileoutput-linking.html" type="page" meta="previous" description="Testing QDoc's link command"/>
+ <para></para>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/images/01.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/images/01.png
new file mode 100644
index 000000000..d73ab969b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/images/01.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/images/leonardo-da-vinci.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/images/leonardo-da-vinci.png
new file mode 100644
index 000000000..854acb4ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/images/leonardo-da-vinci.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/outputfromqdocfiles.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/outputfromqdocfiles.index
new file mode 100644
index 000000000..c158f9fac
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/outputfromqdocfiles.index
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="A test project for QDoc build artifacts" version="" project="OutputFromQDocFiles">
+ <namespace name="" status="active" access="public" module="outputfromqdocfiles">
+ <page name="qdoctests-qdocmanuallikefileoutput.html" href="qdoctests-qdocmanuallikefileoutput.html" status="active" location="qdoctests-outputfromqdocmanuallikefiles.qdoc" documented="true" subtype="page" title="Document Navigation" fulltitle="Document Navigation" subtitle="">
+ <target name="previouspage-command"/>
+ <contents name="commands" title="Commands" level="1"/>
+ <contents name="previouspage" title="\previouspage" level="2"/>
+ </page>
+ <page name="qdoctests-qdocfileoutput-exhaustive.html" href="qdoctests-qdocfileoutput-exhaustive.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Exhaustive testing of QDoc commands" fulltitle="Exhaustive testing of QDoc commands" subtitle="" brief="This page is a dumping ground for QDoc commands under test">
+ <contents name="this-is-a-section1" title="This is a section1" level="1"/>
+ <contents name="this-is-a-section2" title="This is a section2" level="2"/>
+ <contents name="this-is-a-section3" title="This is a section3" level="3"/>
+ <contents name="this-is-a-section4" title="This is a section4" level="4"/>
+ <contents name="images" title="Images" level="1"/>
+ <contents name="commands-not-yet-tested" title="Commands not yet tested" level="1"/>
+ </page>
+ <page name="toc-test.html" href="toc-test.html" status="active" location="toc.qdoc" documented="true" subtype="page" title="TOC" fulltitle="TOC" subtitle=""/>
+ <page name="toc.html" href="toc.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Table of Contents" fulltitle="Table of Contents" subtitle=""/>
+ <page name="qdoctests-qdocfileoutput.html" href="qdoctests-qdocfileoutput.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Testing QDoc output from .qdoc files" fulltitle="Testing QDoc output from .qdoc files" subtitle="" brief="This is a simple page for testing purposes only">
+ <contents name="supported-file-types" title="Supported file types" level="1"/>
+ <contents name="further-information" title="Further information" level="1"/>
+ <contents name="linking" title="Linking" level="1"/>
+ <contents name="qdoc-linking-test" title="QDoc Linking Test" level="1"/>
+ <contents name="linking-to-something-in-a-section-title" title="Linking to something in a section title" level="1"/>
+ </page>
+ <page name="qdoctests-qdocfileoutput-linking.html" href="qdoctests-qdocfileoutput-linking.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Testing QDoc's link command" fulltitle="Testing QDoc's link command" subtitle="" brief="This is a page for testing QDoc's link command">
+ <target name="link-test-target"/>
+ <keyword name="qdoc-linking-test" title="QDoc Linking Test"/>
+ <contents name="link-targets" title="Link targets" level="1"/>
+ </page>
+ <page name="crash.html" href="crash.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="" fulltitle="" subtitle=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocfileoutput-exhaustive.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocfileoutput-exhaustive.webxml
new file mode 100644
index 000000000..25e3dcc9e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocfileoutput-exhaustive.webxml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="qdoctests-qdocfileoutput-exhaustive.html" href="qdoctests-qdocfileoutput-exhaustive.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Exhaustive testing of QDoc commands" fulltitle="Exhaustive testing of QDoc commands" subtitle="" brief="This page is a dumping ground for QDoc commands under test">
+ <contents name="this-is-a-section1" title="This is a section1" level="1"/>
+ <contents name="this-is-a-section2" title="This is a section2" level="2"/>
+ <contents name="this-is-a-section3" title="This is a section3" level="3"/>
+ <contents name="this-is-a-section4" title="This is a section4" level="4"/>
+ <contents name="images" title="Images" level="1"/>
+ <contents name="commands-not-yet-tested" title="Commands not yet tested" level="1"/>
+ <description>
+ <relation href="qdoctests-qdocfileoutput-linking.html" type="page" meta="next" description="Testing QDoc's link command"/>
+ <relation href="qdoctests-qdocfileoutput.html" type="page" meta="previous" description="Testing QDoc output from .qdoc files"/>
+ <brief>This page is a dumping ground for QDoc commands under test.</brief>
+ <section id="this-is-a-section1">
+ <heading level="1">This is a section1</heading>
+ </section>
+ <section id="this-is-a-section2">
+ <heading level="2">This is a section2</heading>
+ </section>
+ <section id="this-is-a-section3">
+ <heading level="3">This is a section3</heading>
+ </section>
+ <section id="this-is-a-section4">
+ <heading level="4">This is a section4</heading>
+ </section>
+ </description>
+ </page>
+ </document>
+ <badcode>This is bad code</badcode>
+ <para>This text should have a line break riiiiight noooow.</para>
+ <para>
+ <bold>All your text belong to bold</bold> ...And this is an examble of only <bold>bold</bold> being, well, bold.</para>
+ <dots indent="4">...</dots>
+ <para>This a caption</para>
+ <legalese>
+ <para>Lorem legal ipsum</para>
+ </legalese>
+ <quote>
+ <para>This is a quotation.</para>
+ </quote>
+ <raw format="HTML"> &lt;html&gt;&lt;body&gt;This is &lt;b&gt;raw&lt;/b&gt;. Like the &lt;h1&gt;Eddie Murphy&lt;/h1&gt; movie. Just not as funny.&lt;/body&gt;&lt;/html&gt;
+ </raw>
+ <para>Look, ma! I made a sidebar!</para>
+ <table>
+ <row>
+ <item>
+ <para>Table item in a table row</para>
+ </item>
+ </row>
+ <row>
+ <item>
+ <para>Another item in a different row</para>
+ </item>
+ </row>
+ </table>
+ <para>
+ <bold>Important:</bold> This is really important.</para>
+ <para>
+ <bold>Note:</bold> The code above doesn't compile</para>
+ <section id="images">
+ <heading level="1">Images</heading>
+ <para>An image without any text:</para>
+ <image href="images/leonardo-da-vinci.png"/>
+ <para>An image with just an alternative text:</para>
+ <image href="images/leonardo-da-vinci.png"/>
+ <para>An image with alternative text and 1-atom caption:</para>
+ <image href="images/leonardo-da-vinci.png"/>
+ <para>Image caption</para>
+ <para>An image with alternative text and 2-atom caption:</para>
+ <image href="images/leonardo-da-vinci.png"/>
+ <para>Image caption with <bold>bold</bold> text</para>
+ <para>A bordered image:</para>
+ <image href="images/leonardo-da-vinci.png"/>
+ <para>A bordered image with a caption:</para>
+ <image href="images/leonardo-da-vinci.png"/>
+ <para>Screenshot of the System Tray Icon</para>
+ <para>An inline image:</para>
+ <para>The is a paragraph containing an <inlineimage href="images/01.png"/> inline image to test if qdoc handles them properly, without considering rest of the line as alt text for the image.</para>
+ <para>An inline image with alt text:</para>
+ <para>Here is another example of <inlineimage href="images/01.png"/> inline image with alternative text, which should be added as an attribute to the inline image.</para>
+ <para>File quoting:</para>
+ <quotefromfile>main.cpp</quotefromfile>
+ <skipto>/if \(/</skipto>
+ <printuntil>/^ \}/</printuntil>
+ </section>
+ <section id="commands-not-yet-tested">
+ <heading level="1">Commands not yet tested</heading>
+ <para>
+ <bold>Warning:</bold> The following commands have yet to be tested: footnote link sincelist header index topicref // or just don’t care, remove it inlineimage printline printto quotefile skipline skipuntil span snippet codeline overload sub sup tableofcontents tt uicontrol endmapref endomit underline unicode</para>
+ </section>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocfileoutput-linking.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocfileoutput-linking.webxml
new file mode 100644
index 000000000..d2c53d965
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocfileoutput-linking.webxml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="qdoctests-qdocfileoutput-linking.html" href="qdoctests-qdocfileoutput-linking.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Testing QDoc's link command" fulltitle="Testing QDoc's link command" subtitle="" brief="This is a page for testing QDoc's link command">
+ <target name="link-test-target"/>
+ <keyword name="qdoc-linking-test" title="QDoc Linking Test"/>
+ <contents name="link-targets" title="Link targets" level="1"/>
+ <description>
+ <relation href="" type="page" meta="next" description="Random page"/>
+ <relation href="qdoctests-qdocfileoutput-exhaustive.html" type="page" meta="previous" description="Exhaustive testing of QDoc commands"/>
+ <brief>This is a page for testing QDoc's link command.</brief>
+ <target name="link-test-target"/>
+ <section id="link-targets">
+ <heading level="1">Link targets</heading>
+ <para>Valid parameters for the link command (<teletype type="highlighted">\l</teletype>) are page and section titles, targets defined with \target or \keyword commands, and API reference keywords (types, methods, namespaces, and so on).</para>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocfileoutput.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocfileoutput.webxml
new file mode 100644
index 000000000..b6b8dc6cb
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocfileoutput.webxml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="qdoctests-qdocfileoutput.html" href="qdoctests-qdocfileoutput.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Testing QDoc output from .qdoc files" fulltitle="Testing QDoc output from .qdoc files" subtitle="" brief="This is a simple page for testing purposes only">
+ <contents name="supported-file-types" title="Supported file types" level="1"/>
+ <contents name="further-information" title="Further information" level="1"/>
+ <contents name="linking" title="Linking" level="1"/>
+ <contents name="qdoc-linking-test" title="QDoc Linking Test" level="1"/>
+ <contents name="linking-to-something-in-a-section-title" title="Linking to something in a section title" level="1"/>
+ <description>
+ <relation href="qdoctests-qdocfileoutput-exhaustive.html" type="page" meta="next" description="Exhaustive testing of QDoc commands"/>
+ <brief>This is a simple page for testing purposes only.</brief>
+ <para>QDoc generates documentation for software projects. It does this by extracting <italic>QDoc comments</italic> from project source files. QDoc comments are signified by a C-style-like comment tag followed by an exclamation point, like this: <teletype type="highlighted">/*!</teletype> <teletype type="highlighted">This text is contained within QDoc comment tags.</teletype> <teletype type="highlighted">*/</teletype>.</para>
+ <section id="supported-file-types">
+ <heading level="1">Supported file types</heading>
+ <para>QDoc parses <teletype type="highlighted">.cpp</teletype> and <teletype type="highlighted">.qdoc</teletype> files. It does extract comments from header (<teletype type="highlighted">.h</teletype>) files.</para>
+ </section>
+ <section id="further-information">
+ <heading level="1">Further information</heading>
+ <para>This test document is written with the purpose of testing the output QDoc generates when parsing <teletype type="highlighted">.qdoc</teletype> files. It is fairly simple and makes use of a limited subset of QDoc's command. Those commands are:</para>
+ <list type="bullet">
+ <item>
+ <para>
+ <teletype type="highlighted">\page</teletype></para>
+ </item>
+ <item>
+ <para>
+ <teletype type="highlighted">\title</teletype></para>
+ </item>
+ <item>
+ <para>
+ <teletype type="highlighted">\brief</teletype></para>
+ </item>
+ <item>
+ <para>
+ <teletype type="highlighted">\e</teletype> (for emphasizing &quot;QDoc comments&quot;)</para>
+ </item>
+ <item>
+ <para>
+ <teletype type="highlighted">\c</teletype> (for multiple monospace-formatted entries)</para>
+ </item>
+ <item>
+ <para>
+ <teletype type="highlighted">\section1</teletype></para>
+ </item>
+ <item>
+ <para>
+ <teletype type="highlighted">\list</teletype></para>
+ </item>
+ <item>
+ <para>
+ <teletype type="highlighted">\li</teletype></para>
+ </item>
+ <item>
+ <para>
+ <teletype type="highlighted">\endlist</teletype></para>
+ </item>
+ </list>
+ </section>
+ <section id="linking">
+ <heading level="1">Linking</heading>
+ <para>There are multiple ways to create hyperlinks to other topics:</para>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="Testing QDoc's link command" href="qdoctests-qdocfileoutput-linking.html" type="page" page="Testing QDoc's link command">Linking to a page title</link></para>
+ </item>
+ <item>
+ <para>
+ <link raw="Link targets" href="qdoctests-qdocfileoutput-linking.html#link-targets" type="page" page="Testing QDoc's link command">Linking to a section title</link></para>
+ </item>
+ <item>
+ <para>
+ <link raw="link-test-target" href="qdoctests-qdocfileoutput-linking.html#link-test-target" type="page" page="Testing QDoc's link command">Linking using a \target string</link></para>
+ </item>
+ <item>
+ <para>
+ <link raw="QDoc Linking Test" href="qdoctests-qdocfileoutput-linking.html" type="page" page="Testing QDoc's link command">Linking using a \keyword string</link></para>
+ </item>
+ </list>
+ </section>
+ <section id="qdoc-linking-test">
+ <heading level="1">QDoc Linking Test</heading>
+ <para>This section title is overridden by another target which takes precedence.</para>
+ </section>
+ <section id="linking-to-something-in-a-section-title">
+ <heading level="1">Linking to <link raw="Further information" href="qdoctests-qdocfileoutput.html#further-information" type="page" page="Testing QDoc output from .qdoc files">something</link> in a section title</heading>
+ <para>This is allowed but a questionable practice.</para>
+ <para>
+ <bold>Note:</bold> You're looking at detailed information.</para>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocmanuallikefileoutput.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocmanuallikefileoutput.webxml
new file mode 100644
index 000000000..4502dcf7f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/qdoctests-qdocmanuallikefileoutput.webxml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="qdoctests-qdocmanuallikefileoutput.html" href="qdoctests-qdocmanuallikefileoutput.html" status="active" location="qdoctests-outputfromqdocmanuallikefiles.qdoc" documented="true" subtype="page" title="Document Navigation" fulltitle="Document Navigation" subtitle="">
+ <target name="previouspage-command"/>
+ <contents name="commands" title="Commands" level="1"/>
+ <contents name="previouspage" title="\previouspage" level="2"/>
+ <description>
+ <para>The navigation commands...</para>
+ <quote>
+ <raw format="HTML"> &lt;table border=&quot;0&quot; cellpadding=&quot;0&quot; cellspacing=&quot;5&quot; width=&quot;100%&quot;&gt;
+
+ &lt;tr&gt;
+ &lt;p&gt;
+ [Previous: &lt;a href=&quot;15-qdoc-commands-navigation.html#deadlink&quot;&gt;
+ Basic Qt&lt;/a&gt;]
+ [&lt;a href=&quot;15-qdoc-commands-navigation.html#deadlink&quot;&gt;Contents&lt;/a&gt;]
+ [Next: &lt;a href=&quot;15-qdoc-commands-navigation.html#deadlink&quot;&gt;
+ Creating Dialogs&lt;/a&gt;]
+ &lt;/p&gt;
+
+ &lt;h1 align=&quot;center&quot;&gt;Getting Started&lt;br /&gt;&lt;/h1&gt;
+
+ &lt;p&gt;
+ This chapter shows how to combine basic C++ with the
+ functionality provided by Qt to create a few small graphical
+ interface (GUI) applications.
+ &lt;/p&gt;
+
+ &lt;p&gt;
+ [Previous: &lt;a href=&quot;15-qdoc-commands-navigation.html#deadlink&quot;&gt;
+ Basic Qt&lt;/a&gt;]
+ [&lt;a href=&quot;15-qdoc-commands-navigation.html#deadlink&quot;&gt;Contents&lt;/a&gt;]
+ [Next: &lt;a href=&quot;15-qdoc-commands-navigation.html#deadlink&quot;&gt;
+ Creating Dialogs&lt;/a&gt;]
+ &lt;/p&gt;
+
+ &lt;/table&gt;
+ </raw>
+ </quote>
+ <code>&lt;head&gt;
+ ...
+ &lt;link rel=&quot;start&quot; href=&quot;basicqt.html&quot; /&gt;
+ ...
+&lt;/head&gt;</code>
+ <section id="commands">
+ <heading level="1">Commands</heading>
+ <target name="previouspage-command"/>
+ </section>
+ <section id="previouspage">
+ <heading level="2">\previouspage</heading>
+ <para>The \previouspage command...</para>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/toc-test.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/toc-test.webxml
new file mode 100644
index 000000000..767973fc6
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/toc-test.webxml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="toc-test.html" href="toc-test.html" status="active" location="toc.qdoc" documented="true" subtype="page" title="TOC" fulltitle="TOC" subtitle="">
+ <description>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="Testing QDoc output from .qdoc files" href="qdoctests-qdocfileoutput.html" type="page" page="Testing QDoc output from .qdoc files">QDoc Testing</link></para>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="Exhaustive testing of QDoc commands" href="qdoctests-qdocfileoutput-exhaustive.html" type="page" page="Exhaustive testing of QDoc commands">Exhaustive testing of QDoc commands</link></para>
+ </item>
+ </list>
+ </item>
+ <item>
+ <para>Linking</para>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="QDoc Linking Test" href="qdoctests-qdocfileoutput-linking.html" type="page" page="Testing QDoc's link command">QDoc Linking Test</link></para>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="Link targets" href="qdoctests-qdocfileoutput-linking.html#link-targets" type="page" page="Testing QDoc's link command">Link targets</link></para>
+ </item>
+ </list>
+ </item>
+ <item>
+ <para>
+ <link raw="crash.html" href="crash.html" type="page" page="crash.html">Random page</link></para>
+ </item>
+ <item>
+ <para>
+ <link raw="Table of Contents" href="toc.html" type="page" page="Table of Contents">Table of Contents</link></para>
+ </item>
+ </list>
+ </item>
+ </list>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/toc.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/toc.webxml
new file mode 100644
index 000000000..9718191e0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/expected/webxml/toc.webxml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="toc.html" href="toc.html" status="active" location="qdoctests-outputfromqdocfiles.qdoc" documented="true" subtype="page" title="Table of Contents" fulltitle="Table of Contents" subtitle="">
+ <description>
+ <relation href="" type="page" meta="previous" description="Random page"/>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="Testing QDoc output from .qdoc files" href="qdoctests-qdocfileoutput.html" type="page" page="Testing QDoc output from .qdoc files">QDoc Testing</link></para>
+ </item>
+ <item>
+ <para>
+ <link raw="QDoc Linking Test" href="qdoctests-qdocfileoutput-linking.html" type="page" page="Testing QDoc's link command">QDoc Linking Test</link></para>
+ </item>
+ <item>
+ <para>
+ <link raw="Table of Contents" href="toc.html" type="page" page="Table of Contents">Table of Contents</link></para>
+ </item>
+ </list>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/images/01.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/images/01.png
new file mode 100644
index 000000000..d73ab969b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/images/01.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/images/leonardo-da-vinci.png b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/images/leonardo-da-vinci.png
new file mode 100644
index 000000000..854acb4ca
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/images/leonardo-da-vinci.png
Binary files differ
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/qdoctests-outputfromqdocfiles.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/qdoctests-outputfromqdocfiles.qdoc
new file mode 100644
index 000000000..b19905b7e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/qdoctests-outputfromqdocfiles.qdoc
@@ -0,0 +1,241 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+\if defined(test_navigation)
+ \nextpage {qdoctests-qdocfileoutput-linking.html}{QDoc Linking Test}
+\endif
+
+ \page qdoctests-qdocfileoutput.html
+ \title Testing \PROD output from .qdoc files
+ \brief This is a simple page for testing purposes only.
+
+ QDoc generates documentation for software projects. It does this by
+ extracting \e {QDoc comments} from project source files. QDoc comments are
+ signified by a C-style-like comment tag followed by an exclamation point,
+ like this:
+ \beginqdoc \c {This text is contained within QDoc comment tags.} \endqdoc.
+
+ \section1 Supported file types
+ QDoc parses \c .cpp and \c .qdoc files. It does extract comments from
+ header (\c {.h}) files.
+
+ \section1 Further information
+ This test document is written with the purpose of testing the output QDoc
+ generates when parsing \c .qdoc files. It is fairly simple and makes use of
+ a limited subset of QDoc's command. Those commands are:
+ \list
+ \li \c {\page}
+ \li \c {\title}
+ \li \c {\brief}
+ \li \c {\e} (for emphasizing "QDoc comments")
+ \li \c {\c} (for multiple monospace-formatted entries)
+ \li \c {\section1}
+ \li \c {\list}
+ \li \c {\li}
+ \li \c {\endlist}
+ \endlist
+
+ \section1 Linking
+
+ There are multiple ways to create hyperlinks to other topics:
+
+ \list
+ \li \l {Testing QDoc's link command}{Linking to a page title}
+ \li \l {Link targets}{Linking to a section title}
+ \li \l {link-test-target}{Linking using a \\target string}
+ \li \l {QDoc Linking Test}{Linking using a \\keyword string}
+ \endlist
+
+ \section1 QDoc Linking Test
+
+ This section title is overridden by another target which takes
+ precedence.
+
+ \section1 Linking to \l {Further information}{something} in a section title
+
+ This is allowed but a questionable practice.
+
+ \details {\PROD details}
+ \note You're looking at detailed information.
+ \enddetails
+*/
+
+/*!
+\if defined(test_navigation)
+ \previouspage qdoctests-qdocfileoutput.html \PROD Testing
+ \nextpage Table of Contents
+\endif
+
+ \keyword QDoc Linking Test
+ \page qdoctests-qdocfileoutput-linking.html
+ \title Testing QDoc's link command
+ \brief This is a page for testing QDoc's link command.
+
+ \target link-test-target
+ \section1 Link targets
+
+ Valid parameters for the link command (\c {\l}) are page and section
+ titles, targets defined with \\target or \\keyword commands, and API
+ reference keywords (types, methods, namespaces, and so on).
+*/
+
+/*!
+\if defined(test_navigation)
+ \previouspage {Testing QDoc's link command}{QDoc Linking Test}
+\endif
+
+ \page toc.html
+ \title Table of Contents
+
+ \list
+ \li \l {Testing \PROD output from .qdoc files}{\PROD Testing}
+ \li \l {QDoc Linking Test}
+ \li \l {Table of Contents}
+ \endlist
+*/
+
+/*!
+ \page qdoctests-qdocfileoutput-exhaustive.html
+ \title Exhaustive testing of QDoc commands
+ \brief This page is a dumping ground for QDoc commands under test.
+
+ \section1 This is a section1
+ \section2 This is a section2
+ \section3 This is a section3
+ \section4 This is a section4
+ \endsection4
+ \endsection3
+ \endsection2
+ \endsection1
+
+ \badcode
+ This is bad code
+ \endcode
+
+ This text should have a line break riiiiight \br noooow.
+
+ \b{All your text belong to bold}
+ ...And this is an examble of only \b bold being, well, bold.
+
+ \dots
+
+ \caption This a caption
+
+ \legalese
+ Lorem legal ipsum
+ \endlegalese
+
+ \quotation
+ This is a quotation.
+ \endquotation
+
+ \raw HTML
+ <html><body>This is <b>raw</b>. Like the <h1>Eddie Murphy</h1> movie. Just not as funny.</body></html>
+ \endraw
+
+ \sidebar
+ Look, ma! I made a sidebar!
+ \endsidebar
+
+ \table
+ \row \li Table item in a table row
+ \row \li Another item in a different row
+ \endtable
+
+ \important This is really important.
+
+ \note The code above doesn't compile
+
+ \hr
+
+ \section1 Images
+
+ An image without any text:
+
+ \image leonardo-da-vinci.png
+
+ An image with just an alternative text:
+
+ \image leonardo-da-vinci.png Image alt
+
+ An image with alternative text and 1-atom caption:
+
+ \image leonardo-da-vinci.png Image alt
+ \caption Image caption
+
+ An image with alternative text and 2-atom caption:
+
+ \image leonardo-da-vinci.png Image alt
+ \caption Image caption with \b {bold} text
+
+ A bordered image:
+
+ \borderedimage leonardo-da-vinci.png
+
+ //! A bordered image with alternative text:
+ //!
+ //! \borderedimage leonardo-da-vinci.png Screenshot of the Drill Down Example
+ //! It looks like this macro is not written to handle alternative text (no \2)
+
+ A bordered image with a caption:
+
+ \borderedimage leonardo-da-vinci.png
+ \caption Screenshot of the System Tray Icon
+
+ An inline image:
+
+ The is a paragraph containing an \inlineimage 01.png inline image to test
+ if qdoc handles them properly, without considering rest of the line as
+ alt text for the image.
+
+ An inline image with alt text:
+
+ Here is another example of \inlineimage 01.png {No. 1} inline image with
+ alternative text, which should be added as an attribute to the inline
+ image.
+
+ File quoting:
+
+ \quotefromfile main.cpp
+ \skipto /if \(/
+ \printuntil /^ \}/
+
+ \section1 Commands not yet tested
+
+ \warning The following commands have yet to be tested:
+ footnote
+ link
+ //! Check why above two (when used in this order) cause missing linefeeds on Windows/webxml
+ sincelist
+ header
+ index
+ topicref // or just don’t care, remove it
+ inlineimage
+ printline
+ printto
+ quotefile
+ skipline
+ skipuntil
+ span
+ snippet
+ codeline
+ overload
+ sub
+ sup
+ tableofcontents
+ tt
+ uicontrol
+ endmapref
+ endomit
+ underline
+ unicode
+
+*/
+
+// Empty link target that was known to assert
+/*!
+ \page crash.html
+
+ \l {}
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/qdoctests-outputfromqdocmanuallikefiles.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/qdoctests-outputfromqdocmanuallikefiles.qdoc
new file mode 100644
index 000000000..23f229745
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/qdoctests-outputfromqdocmanuallikefiles.qdoc
@@ -0,0 +1,59 @@
+// Copyright (C) 2022 Thibaut Cuvelier
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+// Excerpts from src/qdoc/doc/qdoc-guide.qdoc
+
+/*!
+ \page qdoctests-qdocmanuallikefileoutput.html
+
+ \title Document Navigation
+
+ The navigation commands...
+
+ \quotation
+ \raw HTML
+ <table border="0" cellpadding="0" cellspacing="5" width="100%">
+
+ <tr>
+ <p>
+ [Previous: <a href="15-qdoc-commands-navigation.html#deadlink">
+ Basic Qt</a>]
+ [<a href="15-qdoc-commands-navigation.html#deadlink">Contents</a>]
+ [Next: <a href="15-qdoc-commands-navigation.html#deadlink">
+ Creating Dialogs</a>]
+ </p>
+
+ <h1 align="center">Getting Started<br /></h1>
+
+ <p>
+ This chapter shows how to combine basic C++ with the
+ functionality provided by Qt to create a few small graphical
+ interface (GUI) applications.
+ </p>
+
+ <p>
+ [Previous: <a href="15-qdoc-commands-navigation.html#deadlink">
+ Basic Qt</a>]
+ [<a href="15-qdoc-commands-navigation.html#deadlink">Contents</a>]
+ [Next: <a href="15-qdoc-commands-navigation.html#deadlink">
+ Creating Dialogs</a>]
+ </p>
+
+ </table>
+ \endraw
+ \endquotation
+
+ \code
+ <head>
+ ...
+ <link rel="start" href="basicqt.html" />
+ ...
+ </head>
+ \endcode
+
+ \section1 Commands
+
+ \target previouspage-command
+ \section2 \\previouspage
+
+ The \\previouspage command...
+*/ \ No newline at end of file
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/snippets/main.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/snippets/main.cpp
new file mode 100644
index 000000000..6993849e4
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/snippets/main.cpp
@@ -0,0 +1,10 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+int main()
+{
+ if (false) {
+ return 1;
+ }
+ return 0;
+}
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/toc.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/toc.qdoc
new file mode 100644
index 000000000..6ca61de64
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/src/toc.qdoc
@@ -0,0 +1,23 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+/*!
+ \page toc-test.html
+ \title TOC
+
+ \list
+ \li \l {Testing \PROD output from .qdoc files}{\PROD Testing}
+ \list
+ \li \l {Exhaustive testing of QDoc commands}
+ \endlist
+ \li Linking
+ \list
+ \li \l {QDoc Linking Test}
+ \list
+ \li \l {Link targets}
+ \endlist
+ \li \l {crash.html}{Random page}
+ \li \l {Table of Contents}
+ \endlist
+ \endlist
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/tocnavigation.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/tocnavigation.qdocconf
new file mode 100644
index 000000000..b78d91169
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/tocnavigation/tocnavigation.qdocconf
@@ -0,0 +1,48 @@
+project = OutputFromQDocFiles
+description = "A test project for QDoc build artifacts"
+buildversion = "$project - $description"
+moduleheader =
+
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# zero warning policy -- here; allow one which is (qdoc) warning: Can't link to ''
+warninglimit = 1
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+outputformats = HTML WebXML DocBook
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+WebXML.quotinginformation = true
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
+DocBook.nosubdirs = true
+DocBook.outputsubdir = docbook
+DocBook.usedocbookextensions = true
+
+# images
+imagedirs = ./src/images
+
+sources = ./src/qdoctests-outputfromqdocfiles.qdoc \
+ ./src/qdoctests-outputfromqdocmanuallikefiles.qdoc \
+ ./src/toc.qdoc
+
+exampledirs = ./src/snippets
+
+macro.beginqdoc = "\\c {/*!}"
+macro.endqdoc = "\\c */"
+macro.PROD = QDoc
+
+# Macro from qtbase/doc/global/macros.qdocconf
+# The file cannot be included directly, because it requires many
+# variables to be set, like QT_VER
+macro.borderedimage = "\\div {class=\"border\"} \\image \1\n\\enddiv"
+
+defines =
+
+navigation.toctitles = TOC
+navigation.toctitles.inclusive = true
+
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/docbook/trademark-test.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/docbook/trademark-test.xml
new file mode 100644
index 000000000..4f80aa29a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/docbook/trademark-test.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Testing the \tm command</db:title>
+<db:productname>TrademarkCommand</db:productname>
+<db:titleabbrev>TrademarkCommand Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>TrademarkCommand Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:para><db:trademark>Acme</db:trademark> is a trademark.</db:para>
+<db:section xml:id="acme-corp">
+<db:title> Acme corp</db:title>
+<db:para><db:phrase>Acme</db:phrase>, the <db:trademark>Acme Anvil</db:trademark>, and <db:trademark>`It Rings Like a Bell!`</db:trademark> are trademarks of the <db:emphasis>Acme corp</db:emphasis>.</db:para>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/docbook/trademarks.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/docbook/trademarks.xml
new file mode 100644
index 000000000..c6a5a5953
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/docbook/trademarks.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Trademarks</db:title>
+<db:productname>TrademarkCommand</db:productname>
+<db:titleabbrev>TrademarkCommand Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>A page that documents trademarks.</db:para>
+</db:abstract>
+</db:info>
+<db:itemizedlist>
+<db:listitem>
+<db:para><db:link xlink:href="trademark-test.xml#acme-corp">Acme corp</db:link>.</db:para>
+</db:listitem>
+</db:itemizedlist>
+<db:para><db:trademark>Foo</db:trademark> should have the trademark symbol but no link as we're already on the correct page.</db:para>
+<db:para>For repeated keywords, the symbol is added only to the first instance:</db:para>
+<db:para><db:trademark>Foobar</db:trademark> <db:phrase>Foobar</db:phrase> is a trademark.</db:para>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/html/trademark-test.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/html/trademark-test.html
new file mode 100644
index 000000000..ae890142c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/html/trademark-test.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- test.qdoc -->
+ <title>Testing the \tm command | TrademarkCommand</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#acme-corp"> Acme corp</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Testing the \tm command</h1>
+<!-- $$$trademark-test.html-description -->
+<div class="descr" id="details">
+<p><span translate="no">Acme<a href="trademarks.html">&#8482;</a></span> is a trademark.</p>
+<h2 id="acme-corp"> Acme corp</h2>
+<p><span translate="no">Acme</span>, the <span translate="no">Acme Anvil<a href="trademarks.html">&#8482;</a></span>, and <span translate="no">`It Rings Like a Bell!`<a href="trademarks.html">&#8482;</a></span> are trademarks of the <i>Acme corp</i>.</p>
+</div>
+<!-- @@@trademark-test.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/html/trademarkcommand.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/html/trademarkcommand.index
new file mode 100644
index 000000000..9b0fe807a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/html/trademarkcommand.index
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="TrademarkCommand Reference Documentation" version="" project="TrademarkCommand">
+ <namespace name="" status="active" access="public" module="trademarkcommand">
+ <page name="trademark-test.html" href="trademark-test.html" status="active" location="test.qdoc" documented="true" subtype="page" title="Testing the \tm command" fulltitle="Testing the \tm command" subtitle="">
+ <contents name="acme-corp" title=" Acme corp" level="1"/>
+ </page>
+ <page name="trademarks.html" href="trademarks.html" status="active" location="test.qdoc" documented="true" subtype="page" title="Trademarks" fulltitle="Trademarks" subtitle="" brief="A page that documents trademarks">
+ <keyword name="trademark"/>
+ </page>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/html/trademarks.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/html/trademarks.html
new file mode 100644
index 000000000..46aacb969
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/html/trademarks.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- test.qdoc -->
+ <meta name="description" content="A page that documents trademarks.">
+ <title>Trademarks | TrademarkCommand</title>
+</head>
+<body>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title">Trademarks</h1>
+<!-- $$$trademarks.html-description -->
+<div class="descr" id="details">
+<ul>
+<li><a href="trademark-test.html#acme-corp">Acme corp</a>.</li>
+</ul>
+<p><span translate="no">Foo&#8482;</span> should have the trademark symbol but no link as we're already on the correct page.</p>
+<p>For repeated keywords, the symbol is added only to the first instance:</p>
+<p><span translate="no">Foobar&#8482;</span> <span translate="no">Foobar</span> is a trademark.</p>
+</div>
+<!-- @@@trademarks.html -->
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/webxml/trademark-test.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/webxml/trademark-test.webxml
new file mode 100644
index 000000000..09f1c5a94
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/webxml/trademark-test.webxml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="trademark-test.html" href="trademark-test.html" status="active" location="test.qdoc" documented="true" subtype="page" title="Testing the \tm command" fulltitle="Testing the \tm command" subtitle="">
+ <contents name="acme-corp" title=" Acme corp" level="1"/>
+ <description>
+ <para>Acme™ is a trademark.</para>
+ <section id="acme-corp">
+ <heading level="1"> Acme corp</heading>
+ <para>Acme, the Acme Anvil™, and `It Rings Like a Bell!`™ are trademarks of the <italic>Acme corp</italic>.</para>
+ </section>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/webxml/trademarkcommand.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/webxml/trademarkcommand.index
new file mode 100644
index 000000000..9b0fe807a
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/webxml/trademarkcommand.index
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="TrademarkCommand Reference Documentation" version="" project="TrademarkCommand">
+ <namespace name="" status="active" access="public" module="trademarkcommand">
+ <page name="trademark-test.html" href="trademark-test.html" status="active" location="test.qdoc" documented="true" subtype="page" title="Testing the \tm command" fulltitle="Testing the \tm command" subtitle="">
+ <contents name="acme-corp" title=" Acme corp" level="1"/>
+ </page>
+ <page name="trademarks.html" href="trademarks.html" status="active" location="test.qdoc" documented="true" subtype="page" title="Trademarks" fulltitle="Trademarks" subtitle="" brief="A page that documents trademarks">
+ <keyword name="trademark"/>
+ </page>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/webxml/trademarks.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/webxml/trademarks.webxml
new file mode 100644
index 000000000..1d9350777
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/expected/webxml/trademarks.webxml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <page name="trademarks.html" href="trademarks.html" status="active" location="test.qdoc" documented="true" subtype="page" title="Trademarks" fulltitle="Trademarks" subtitle="" brief="A page that documents trademarks">
+ <keyword name="trademark"/>
+ <description>
+ <brief>A page that documents trademarks.</brief>
+ <list type="bullet">
+ <item>
+ <para>
+ <link raw="Acme corp" href="trademark-test.html#acme-corp" type="page" page="Testing the \tm command">Acme corp</link>.</para>
+ </item>
+ </list>
+ <para>Foo™ should have the trademark symbol but no link as we're already on the correct page.</para>
+ <para>For repeated keywords, the symbol is added only to the first instance:</para>
+ <para>Foobar™ Foobar is a trademark.</para>
+ </description>
+ </page>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/src/test.qdoc b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/src/test.qdoc
new file mode 100644
index 000000000..ca7265253
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/src/test.qdoc
@@ -0,0 +1,32 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+ \page trademark-test.html
+ \title Testing the \\tm command
+
+ \tm {Acme} is a trademark.
+
+ \section1 \tm Acme corp
+
+ \tm Acme, the \tm {Acme Anvil}, and \tm {`It Rings Like a Bell!`}
+ are trademarks of the \e {Acme corp}.
+*/
+
+/*!
+ \page trademarks.html
+ \title Trademarks
+ \keyword trademark
+ \brief A page that documents trademarks.
+
+ \list
+ \li \l {Acme corp}.
+ \endlist
+
+ \tm Foo should have the trademark symbol but no link as we're
+ already on the correct page.
+
+ For repeated keywords, the symbol is added only to the first
+ instance:
+ \trademark {\Foobar \Foobar}
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/trademark_command.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/trademark_command.qdocconf
new file mode 100644
index 000000000..150f9873f
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trademark_command/trademark_command.qdocconf
@@ -0,0 +1,20 @@
+project = TrademarkCommand
+
+{sourcedirs,headerdirs} = ./src
+
+navigation.trademarkspage = Trademarks
+
+macro.trademark = "\n\n\1 is a trademark.\n\n"
+macro.Foobar = "\\tm {Foobar}"
+
+locationinfo = false
+warninglimit = 0
+warninglimit.enabled = true
+
+outputformats = WebXML HTML DocBook
+{WebXML.nosubdirs,HTML.nosubdirs,DocBook.nosubdirs} = true
+WebXML.quotinginformation = true
+
+WebXML.outputsubdir = webxml
+HTML.outputsubdir = html
+DocBook.outputsubdir = docbook
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/docbook/struct.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/docbook/struct.xml
new file mode 100644
index 000000000..48efdadb7
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/docbook/struct.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title></db:title>
+<db:productname>TrailingBackslashes</db:productname>
+<db:titleabbrev>TrailingBackslashes Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>TrailingBackslashes Reference Documentation.</db:para></db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Struct</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="member-function-documentation">
+<db:title>Member Function Documentation</db:title>
+<db:section xml:id="MultipleTrailingSlashes">
+<db:title>void Struct::MultipleTrailingSlashes()</db:title>
+<db:section>
+<db:title>See Also</db:title>
+<db:para><db:emphasis>See also </db:emphasis>
+<db:simplelist type="vert" role="see-also">
+<db:member><db:link xlink:href="struct.xml#oneTrailingSlash">one slash</db:link></db:member>
+</db:simplelist>
+</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="multipleTrailingSlashesAndRandomWhitespace">
+<db:title>void Struct::multipleTrailingSlashesAndRandomWhitespace()</db:title>
+<db:section>
+<db:title>See Also</db:title>
+<db:para><db:emphasis>See also </db:emphasis>
+<db:simplelist type="vert" role="see-also">
+<db:member><db:link xlink:href="struct.xml#MultipleTrailingSlashes">two slashes here</db:link></db:member>
+</db:simplelist>
+</db:para>
+</db:section>
+</db:section>
+<db:section xml:id="oneTrailingSlash">
+<db:title>void Struct::oneTrailingSlash()</db:title>
+<db:section>
+<db:title>See Also</db:title>
+<db:para><db:emphasis>See also </db:emphasis>
+<db:simplelist type="vert" role="see-also">
+<db:member><db:link xlink:href="struct.xml#multipleTrailingSlashesAndRandomWhitespace">two slashes again</db:link></db:member>
+</db:simplelist>
+</db:para>
+</db:section>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/html/struct-members.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/html/struct-members.html
new file mode 100644
index 000000000..7fe474e80
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/html/struct-members.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- trailing_backslashes.cpp -->
+ <title>List of All Members for Struct | TrailingBackslashes</title>
+</head>
+<body>
+<li>Struct</li>
+<div class="sidebar"><div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">List of All Members for Struct</h1>
+<p>This is the complete list of members for <a href="struct.html">Struct</a>, including inherited members.</p>
+<ul>
+<li class="fn" translate="no"><span class="name"><b><a href="struct.html#MultipleTrailingSlashes" translate="no">MultipleTrailingSlashes</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="struct.html#multipleTrailingSlashesAndRandomWhitespace" translate="no">multipleTrailingSlashesAndRandomWhitespace</a></b></span>()</li>
+<li class="fn" translate="no"><span class="name"><b><a href="struct.html#oneTrailingSlash" translate="no">oneTrailingSlash</a></b></span>()</li>
+</ul>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/html/struct.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/html/struct.html
new file mode 100644
index 000000000..0e5a0b45d
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/html/struct.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- trailing_backslashes.cpp -->
+ <title>Struct Struct | TrailingBackslashes</title>
+</head>
+<body>
+<li>Struct</li>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#public-functions">Public Functions</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Struct Struct</h1>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Struct&gt;</span></td></tr>
+</table></div>
+<ul>
+<li><a href="struct-members.html">List of all members, including inherited members</a></li>
+</ul>
+<h2 id="public-functions">Public Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="struct.html#MultipleTrailingSlashes" translate="no">MultipleTrailingSlashes</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="struct.html#multipleTrailingSlashesAndRandomWhitespace" translate="no">multipleTrailingSlashesAndRandomWhitespace</a></b>()</td></tr>
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="struct.html#oneTrailingSlash" translate="no">oneTrailingSlash</a></b>()</td></tr>
+</table></div>
+<!-- $$$Struct-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Struct -->
+<div class="func">
+<h2>Member Function Documentation</h2>
+<!-- $$$MultipleTrailingSlashes[overload1]$$$MultipleTrailingSlashes -->
+<h3 class="fn" translate="no" id="MultipleTrailingSlashes"><span class="type">void</span> Struct::<span class="name">MultipleTrailingSlashes</span>()</h3>
+<p><b>See also </b><a href="struct.html#oneTrailingSlash" translate="no">one slash</a>.</p>
+<!-- @@@MultipleTrailingSlashes -->
+<!-- $$$multipleTrailingSlashesAndRandomWhitespace[overload1]$$$multipleTrailingSlashesAndRandomWhitespace -->
+<h3 class="fn" translate="no" id="multipleTrailingSlashesAndRandomWhitespace"><span class="type">void</span> Struct::<span class="name">multipleTrailingSlashesAndRandomWhitespace</span>()</h3>
+<p><b>See also </b><a href="struct.html#MultipleTrailingSlashes" translate="no">two slashes here</a>.</p>
+<!-- @@@multipleTrailingSlashesAndRandomWhitespace -->
+<!-- $$$oneTrailingSlash[overload1]$$$oneTrailingSlash -->
+<h3 class="fn" translate="no" id="oneTrailingSlash"><span class="type">void</span> Struct::<span class="name">oneTrailingSlash</span>()</h3>
+<p><b>See also </b><a href="struct.html#multipleTrailingSlashesAndRandomWhitespace" translate="no">two slashes again</a>.</p>
+<!-- @@@oneTrailingSlash -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/html/trailingbackslashes.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/html/trailingbackslashes.index
new file mode 100644
index 000000000..be2edeaf0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/html/trailingbackslashes.index
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="TrailingBackslashes Reference Documentation" version="" project="TrailingBackslashes">
+ <namespace name="" status="active" access="public" module="trailingbackslashes">
+ <struct name="Struct" href="struct.html" status="active" access="public" location="trailing_backslashes.h" documented="true" module="TrailingBackslashes">
+ <function name="MultipleTrailingSlashes" fullname="Struct::MultipleTrailingSlashes" href="struct.html#MultipleTrailingSlashes" status="active" access="public" location="trailing_backslashes.h" documented="true" meta="plain" type="void" signature="void MultipleTrailingSlashes()">
+ <keyword name="two-slashes-here" title="two slashes here"/>
+ </function>
+ <function name="multipleTrailingSlashesAndRandomWhitespace" fullname="Struct::multipleTrailingSlashesAndRandomWhitespace" href="struct.html#multipleTrailingSlashesAndRandomWhitespace" status="active" access="public" location="trailing_backslashes.h" documented="true" meta="plain" type="void" signature="void multipleTrailingSlashesAndRandomWhitespace()">
+ <keyword name="two-slashes-again" title="two slashes again"/>
+ </function>
+ <function name="oneTrailingSlash" fullname="Struct::oneTrailingSlash" href="struct.html#oneTrailingSlash" status="active" access="public" location="trailing_backslashes.h" documented="true" meta="plain" type="void" signature="void oneTrailingSlash()">
+ <keyword name="one-slash" title="one slash"/>
+ </function>
+ </struct>
+ <module name="TrailingBackslashes" href="trailingbackslashes-module.html" status="internal" seen="false" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/webxml/struct.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/webxml/struct.webxml
new file mode 100644
index 000000000..85a7a1f28
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/webxml/struct.webxml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <struct name="Struct" href="struct.html" status="active" access="public" location="trailing_backslashes.h" documented="true" module="TrailingBackslashes">
+ <description/>
+ <function name="MultipleTrailingSlashes" fullname="Struct::MultipleTrailingSlashes" href="struct.html#MultipleTrailingSlashes" status="active" access="public" location="trailing_backslashes.h" documented="true" meta="plain" type="void" signature="void MultipleTrailingSlashes()">
+ <keyword name="two-slashes-here" title="two slashes here"/>
+ <description>
+ <see-also>
+ <link raw="one slash" href="struct.html#oneTrailingSlash" type="function">one slash</link>
+ </see-also>
+ </description>
+ </function>
+ <function name="multipleTrailingSlashesAndRandomWhitespace" fullname="Struct::multipleTrailingSlashesAndRandomWhitespace" href="struct.html#multipleTrailingSlashesAndRandomWhitespace" status="active" access="public" location="trailing_backslashes.h" documented="true" meta="plain" type="void" signature="void multipleTrailingSlashesAndRandomWhitespace()">
+ <keyword name="two-slashes-again" title="two slashes again"/>
+ <description>
+ <see-also>
+ <link raw="two slashes here" href="struct.html#MultipleTrailingSlashes" type="function">two slashes here</link>
+ </see-also>
+ </description>
+ </function>
+ <function name="oneTrailingSlash" fullname="Struct::oneTrailingSlash" href="struct.html#oneTrailingSlash" status="active" access="public" location="trailing_backslashes.h" documented="true" meta="plain" type="void" signature="void oneTrailingSlash()">
+ <keyword name="one-slash" title="one slash"/>
+ <description>
+ <see-also>
+ <link raw="two slashes again" href="struct.html#multipleTrailingSlashesAndRandomWhitespace" type="function">two slashes again</link>
+ </see-also>
+ </description>
+ </function>
+ </struct>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/webxml/trailingbackslashes.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/webxml/trailingbackslashes.index
new file mode 100644
index 000000000..be2edeaf0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/expected/webxml/trailingbackslashes.index
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="TrailingBackslashes Reference Documentation" version="" project="TrailingBackslashes">
+ <namespace name="" status="active" access="public" module="trailingbackslashes">
+ <struct name="Struct" href="struct.html" status="active" access="public" location="trailing_backslashes.h" documented="true" module="TrailingBackslashes">
+ <function name="MultipleTrailingSlashes" fullname="Struct::MultipleTrailingSlashes" href="struct.html#MultipleTrailingSlashes" status="active" access="public" location="trailing_backslashes.h" documented="true" meta="plain" type="void" signature="void MultipleTrailingSlashes()">
+ <keyword name="two-slashes-here" title="two slashes here"/>
+ </function>
+ <function name="multipleTrailingSlashesAndRandomWhitespace" fullname="Struct::multipleTrailingSlashesAndRandomWhitespace" href="struct.html#multipleTrailingSlashesAndRandomWhitespace" status="active" access="public" location="trailing_backslashes.h" documented="true" meta="plain" type="void" signature="void multipleTrailingSlashesAndRandomWhitespace()">
+ <keyword name="two-slashes-again" title="two slashes again"/>
+ </function>
+ <function name="oneTrailingSlash" fullname="Struct::oneTrailingSlash" href="struct.html#oneTrailingSlash" status="active" access="public" location="trailing_backslashes.h" documented="true" meta="plain" type="void" signature="void oneTrailingSlash()">
+ <keyword name="one-slash" title="one slash"/>
+ </function>
+ </struct>
+ <module name="TrailingBackslashes" href="trailingbackslashes-module.html" status="internal" seen="false" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/src/trailing_backslashes.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/src/trailing_backslashes.cpp
new file mode 100644
index 000000000..878425613
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/src/trailing_backslashes.cpp
@@ -0,0 +1,32 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+/*!
+ \struct Struct
+*/
+
+/*!
+ \fn void Struct::oneTrailingSlash()
+ \keyword one \
+ slash
+
+ \sa {two slashes again}
+*/
+
+/*!
+ \fn void Struct::MultipleTrailingSlashes()
+ \keyword two \
+ slashes \
+ here
+
+ \sa {one slash}
+*/
+
+/*!
+ \fn void Struct::multipleTrailingSlashesAndRandomWhitespace()
+ \keyword two\
+ slashes \
+ again
+
+ \sa {two slashes here}
+*/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/src/trailing_backslashes.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/src/trailing_backslashes.h
new file mode 100644
index 000000000..0fb059b97
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/src/trailing_backslashes.h
@@ -0,0 +1,14 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef TRAILING_BACKSLASHES_H
+#define TRAILING_BACKSLASHES_H
+
+struct Struct
+{
+ void oneTrailingSlash(){};
+ void MultipleTrailingSlashes(){};
+ void multipleTrailingSlashesAndRandomWhitespace(){};
+};
+
+#endif // TRAILING_BACKSLASHES_H
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/trailing_backslashes.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/trailing_backslashes.qdocconf
new file mode 100644
index 000000000..3f8f4d011
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/trailing_backslashes/trailing_backslashes.qdocconf
@@ -0,0 +1,29 @@
+project = TrailingBackslashes
+
+headerdirs = ./src/
+sourcedirs = ./src/
+exampledirs = ./src/
+
+outputformats = WebXML HTML DocBook
+WebXML.quotinginformation = true
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
+
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+
+DocBook.nosubdirs = true
+DocBook.outputsubdir = docbook
+
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ./src/images
+
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+warninglimit = 1
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/docbook/space.xml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/docbook/space.xml
new file mode 100644
index 000000000..174b64092
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/docbook/space.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<db:article xmlns:db="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.2" xml:lang="en">
+<db:info>
+<db:title>Space Namespace</db:title>
+<db:productname>UsingDirective</db:productname>
+<db:titleabbrev>UsingDirective Reference Documentation</db:titleabbrev>
+<db:abstract>
+<db:para>A namespace...in space.</db:para>
+</db:abstract>
+</db:info>
+<db:variablelist>
+<db:varlistentry>
+<db:term>Header</db:term>
+<db:listitem>
+<db:para>Space</db:para>
+</db:listitem>
+</db:varlistentry>
+</db:variablelist>
+<db:section xml:id="details">
+<db:title>Detailed Description</db:title>
+</db:section>
+<db:section xml:id="function-documentation">
+<db:title>Function Documentation</db:title>
+<db:section xml:id="spaceFun">
+<db:title>void spaceFun(Space::spacename <db:emphasis>space</db:emphasis>)</db:title>
+<db:para>A <db:code role="parameter">space</db:code> function.</db:para>
+</db:section>
+</db:section>
+</db:article>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/html/space.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/html/space.html
new file mode 100644
index 000000000..60e389382
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/html/space.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+<!-- space.cpp -->
+ <meta name="description" content="A namespace...in space.">
+ <title>Space Namespace | UsingDirective</title>
+</head>
+<body>
+<div class="sidebar">
+<div class="toc">
+<h3 id="toc">Contents</h3>
+<ul>
+<li class="level1"><a href="#functions">Functions</a></li>
+<li class="level1"><a href="#details">Detailed Description</a></li>
+</ul>
+</div>
+<div class="sidebar-content" id="sidebar-content"></div></div>
+<h1 class="title" translate="no">Space Namespace</h1>
+<!-- $$$Space-brief -->
+<p>A namespace...in space. <a href="#details">More...</a></p>
+<!-- @@@Space -->
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include &lt;Space&gt;</span></td></tr>
+</table></div>
+<h2 id="functions">Functions</h2>
+<div class="table"><table class="alignedsummary" translate="no">
+<tr><td class="memItemLeft rightAlign topAlign"> void </td><td class="memItemRight bottomAlign"><b><a href="space.html#spaceFun" translate="no">spaceFun</a></b>(Space::spacename <i>space</i>)</td></tr>
+</table></div>
+<!-- $$$Space-description -->
+<div class="descr">
+<h2 id="details">Detailed Description</h2>
+</div>
+<!-- @@@Space -->
+<div class="func">
+<h2>Function Documentation</h2>
+<!-- $$$spaceFun[overload1]$$$spaceFunSpace::spacename -->
+<h3 class="fn" translate="no" id="spaceFun"><span class="type">void</span> <span class="name">spaceFun</span>(<span class="type">Space::spacename</span> <i>space</i>)</h3>
+<p>A <i translate="no">space</i> function.</p>
+<!-- @@@spaceFun -->
+</div>
+</body>
+</html>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/html/usingdirective.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/html/usingdirective.index
new file mode 100644
index 000000000..252512890
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/html/usingdirective.index
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="UsingDirective Reference Documentation" version="" project="UsingDirective">
+ <namespace name="" status="active" access="public" module="usingdirective">
+ <function name="spaceFun" href="space.html#spaceFun" status="active" access="public" location="space.h" documented="true" related="0" meta="plain" type="void" signature="void spaceFun(Space::spacename space)">
+ <parameter type="Space::spacename" name="space" default=""/>
+ </function>
+ <namespace name="Space" href="space.html" status="active" access="public" location="space.h" documented="true" module="UsingDirective" brief="A namespace...in space">
+ <function name="spaceFun" href="space.html#spaceFun" status="active" access="public" location="space.h" documented="true" related="0" meta="plain" type="void" signature="void spaceFun(Space::spacename space)">
+ <parameter type="Space::spacename" name="space" default=""/>
+ </function>
+ </namespace>
+ <module name="UsingDirective" href="usingdirective-module.html" status="internal" seen="false" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/webxml/space.webxml b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/webxml/space.webxml
new file mode 100644
index 000000000..15102ba9e
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/webxml/space.webxml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<WebXML>
+ <document>
+ <namespace name="Space" href="space.html" status="active" access="public" location="space.h" documented="true" module="UsingDirective" brief="A namespace...in space">
+ <description>
+ <brief>A namespace...in space.</brief>
+ </description>
+ <function name="spaceFun" href="space.html#spaceFun" status="active" access="public" location="space.h" documented="true" related="0" meta="plain" type="void" signature="void spaceFun(Space::spacename space)">
+ <parameter type="Space::spacename" name="space" default=""/>
+ <description>
+ <para>A <argument>space</argument> function.</para>
+ </description>
+ </function>
+ </namespace>
+ </document>
+</WebXML>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/webxml/usingdirective.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/webxml/usingdirective.index
new file mode 100644
index 000000000..252512890
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/expected/webxml/usingdirective.index
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QDOCINDEX>
+<INDEX url="" title="UsingDirective Reference Documentation" version="" project="UsingDirective">
+ <namespace name="" status="active" access="public" module="usingdirective">
+ <function name="spaceFun" href="space.html#spaceFun" status="active" access="public" location="space.h" documented="true" related="0" meta="plain" type="void" signature="void spaceFun(Space::spacename space)">
+ <parameter type="Space::spacename" name="space" default=""/>
+ </function>
+ <namespace name="Space" href="space.html" status="active" access="public" location="space.h" documented="true" module="UsingDirective" brief="A namespace...in space">
+ <function name="spaceFun" href="space.html#spaceFun" status="active" access="public" location="space.h" documented="true" related="0" meta="plain" type="void" signature="void spaceFun(Space::spacename space)">
+ <parameter type="Space::spacename" name="space" default=""/>
+ </function>
+ </namespace>
+ <module name="UsingDirective" href="usingdirective-module.html" status="internal" seen="false" title=""/>
+ </namespace>
+</INDEX>
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/UsingDirective b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/UsingDirective
new file mode 100644
index 000000000..422d01e91
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/UsingDirective
@@ -0,0 +1,2 @@
+#include "alias.h"
+#include "space.h"
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/alias.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/alias.h
new file mode 100644
index 000000000..5691035da
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/alias.h
@@ -0,0 +1,10 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "space.h"
+
+namespace Alias {
+ using spacename = Space::spacename;
+}
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/space.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/space.cpp
new file mode 100644
index 000000000..d84156da0
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/space.cpp
@@ -0,0 +1,21 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "space.h"
+
+/*!
+ \namespace Space
+ \inmodule UsingDirective
+ \brief A namespace...in space.
+*/
+
+using namespace Alias;
+using namespace Space;
+
+/*!
+ \relates Space
+ A \a space function.
+*/
+void spaceFun(spacename space)
+{
+}
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/space.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/space.h
new file mode 100644
index 000000000..09472535c
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/src/space.h
@@ -0,0 +1,10 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace Space {
+ typedef int spacename;
+}
+
+void spaceFun(Space::spacename space);
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/usingdirective.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/usingdirective.qdocconf
new file mode 100644
index 000000000..01456b049
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/usingdirective/usingdirective.qdocconf
@@ -0,0 +1,27 @@
+sources.fileextensions = "*.qml *.cpp *.qdoc"
+headers.fileextensions = "*.h"
+
+# images
+imagedirs = ./src/images
+
+# zero warning policy
+warninglimit = 0
+warninglimit.enabled = true
+
+# don't write host system-specific paths to index files
+locationinfo = false
+
+# by default, use -outputdir directly, no subdir.
+# outputsubdir '.' matches the root of expected_output/
+outputformats = HTML WebXML DocBook
+HTML.nosubdirs = true
+HTML.outputsubdir = html
+
+DocBook.nosubdirs = true
+DocBook.outputsubdir = docbook
+
+WebXML.nosubdirs = true
+WebXML.outputsubdir = webxml
+
+project = UsingDirective
+{includepaths,headerdirs,sourcedirs} = ./src/
diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/tst_validateqdocoutputfiles.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/tst_validateqdocoutputfiles.cpp
new file mode 100644
index 000000000..a3c08ad7b
--- /dev/null
+++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/tst_validateqdocoutputfiles.cpp
@@ -0,0 +1,182 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <utility>
+
+class tst_validateQdocOutputFiles : public QObject
+{
+ Q_OBJECT
+private:
+ void runQDocProcess(const QStringList &arguments);
+ std::optional<QByteArray> gitDiffDirectories(const QString &actualPath,
+ const QString &expectedPath);
+
+private slots:
+ void initTestCase();
+ void init();
+ void qdocProjects_data();
+ void qdocProjects();
+
+private:
+ const QString m_testDataDirectory = QFINDTESTDATA("testdata");
+ QString m_qdocBinary{};
+ QString m_extraParams{};
+ QScopedPointer<QTemporaryDir> m_outputDir{};
+};
+
+static constexpr QLatin1StringView ASAN_OPTIONS_ENVVAR{"ASAN_OPTIONS"};
+static inline bool regenerate{false};
+
+//! Update `README.md` if you change the name of this environment variable!
+static constexpr QLatin1StringView REGENERATE_ENVVAR{"QDOC_REGENERATE_TESTDATA"};
+static QProcessEnvironment s_environment {QProcessEnvironment::systemEnvironment()};
+
+void tst_validateQdocOutputFiles::initTestCase()
+{
+ if (s_environment.contains(REGENERATE_ENVVAR)) {
+ qInfo() << "Regenerating expected output for all tests.";
+ regenerate = true;
+ qInfo("Removing %s environment variable.", REGENERATE_ENVVAR.constData());
+ s_environment.remove(REGENERATE_ENVVAR);
+ }
+
+ // We must disable the use of sigaltstack for ASan to work properly with QDoc when
+ // linked against libclang, to avoid a crash in ASan. This is a known issue and workaround,
+ // see e.g. https://github.com/google/sanitizers/issues/849 and
+ // https://github.com/KDE/kdevelop/commit/e306f3e39aba37b606dadba195fa5b7b73816f8f.
+ // We do this for the process environment of the QDoc process only to avoid affecting
+ // other processes that might be started by the test runner in COIN.
+ const QString optionString = s_environment.contains(ASAN_OPTIONS_ENVVAR) ? ",use_sigaltstack=0" : "use_sigaltstack=0";
+ s_environment.insert(ASAN_OPTIONS_ENVVAR, s_environment.value(ASAN_OPTIONS_ENVVAR) + optionString);
+ qInfo() << "Disabling ASan's alternate signal stack by setting `ASAN_OPTIONS=use_sigaltstack=0`.";
+
+ // Build the path to the QDoc binary the same way moc tests do for moc.
+ const auto binpath = QLibraryInfo::path(QLibraryInfo::BinariesPath);
+ const auto extension = QSysInfo::productType() == "windows" ? ".exe" : "";
+ m_qdocBinary = binpath + QLatin1String("/qdoc") + extension;
+ QVERIFY(QFile::exists(m_qdocBinary));
+
+ // Resolve the path to the file containing extra parameters
+ m_extraParams = QFileInfo(QTest::currentAppName()).dir().filePath(DOCINCPATH);
+ if (!QFileInfo::exists(m_extraParams)) {
+ qWarning("Cannot locate %s", m_extraParams.toLocal8Bit().constData());
+ m_extraParams.clear();
+ } else {
+ m_extraParams.insert(0, '@');
+ }
+}
+
+void tst_validateQdocOutputFiles::init()
+{
+ m_outputDir.reset(new QTemporaryDir());
+ if (!m_outputDir->isValid()) {
+ const QString errorMessage =
+ "Couldn't create temporary directory: " + m_outputDir->errorString();
+ QFAIL(qPrintable(errorMessage));
+ }
+}
+
+void tst_validateQdocOutputFiles::runQDocProcess(const QStringList &arguments)
+{
+ QProcess qdocProcess;
+ qdocProcess.setProcessEnvironment(s_environment);
+ qdocProcess.setProgram(m_qdocBinary);
+ qdocProcess.setArguments(arguments);
+
+ auto failQDoc = [&](QProcess::ProcessError) {
+ qFatal("Running qdoc failed with exit code %i: %s",
+ qdocProcess.exitCode(), qUtf8Printable(qdocProcess.errorString()));
+ };
+ QObject::connect(&qdocProcess, &QProcess::errorOccurred, this, failQDoc);
+
+ qdocProcess.start();
+ qdocProcess.waitForFinished();
+ if (qdocProcess.exitCode() == 0)
+ return;
+
+ QString errors = qdocProcess.readAllStandardError();
+ if (!errors.isEmpty())
+ qInfo().nospace() << "Received errors:\n" << qUtf8Printable(errors);
+ if (!QTest::currentTestFailed())
+ failQDoc(QProcess::UnknownError);
+}
+
+std::optional<QByteArray>
+tst_validateQdocOutputFiles::gitDiffDirectories(const QString &actualPath, const QString &expectedPath)
+{
+ QProcess gitProcess;
+ gitProcess.setProgram("git");
+
+ const QStringList arguments{"diff", "--", actualPath, expectedPath};
+ gitProcess.setArguments(arguments);
+
+ auto failGit = [&](QProcess::ProcessError) {
+ qFatal("Running git failed with exit code %i: %s",
+ gitProcess.exitCode(), gitProcess.errorString().toLocal8Bit().constData());
+ };
+ QObject::connect(&gitProcess, &QProcess::errorOccurred, this, failGit);
+
+ gitProcess.start();
+ gitProcess.waitForFinished();
+
+ if (gitProcess.exitCode() == 0)
+ return {};
+
+ return gitProcess.readAllStandardOutput();
+}
+
+void tst_validateQdocOutputFiles::qdocProjects_data()
+{
+ using namespace Qt::StringLiterals;
+ QTest::addColumn<QString>("qdocconf");
+ QTest::addColumn<QString>("expectedPath");
+
+ QDirIterator qdocconfit(m_testDataDirectory, QStringList { u"*.qdocconf"_s },
+ QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
+ while (qdocconfit.hasNext()) {
+ const QFileInfo configFile = qdocconfit.nextFileInfo();
+ if (configFile.baseName() != configFile.dir().dirName())
+ continue;
+
+ const QString testName =
+ configFile.dir().dirName() + u'/' + configFile.fileName();
+
+ QTest::newRow(testName.toUtf8().constData())
+ << configFile.absoluteFilePath() << configFile.dir().absolutePath() + "/expected/";
+ }
+}
+
+void tst_validateQdocOutputFiles::qdocProjects()
+{
+ QFETCH(const QString, qdocconf);
+ QFETCH(const QString, expectedPath);
+
+ QString actualPath{m_outputDir->path()};
+ if (regenerate) {
+ actualPath = expectedPath;
+ QDir pathToRemove{expectedPath};
+ if (!pathToRemove.removeRecursively())
+ qCritical("Cannot remove expected output directory, aborting!");
+ }
+
+ const QStringList arguments{ "-outputdir", actualPath, m_extraParams, qdocconf };
+
+ runQDocProcess(arguments);
+
+ if (regenerate) {
+ const QString message = "Regenerated expected output files for" + qdocconf;
+ QSKIP(message.toLocal8Bit().constData());
+ }
+
+ std::optional<QByteArray> gitDiff = gitDiffDirectories(actualPath, expectedPath);
+ if (gitDiff.has_value()) {
+ qInfo() << qUtf8Printable(gitDiff.value());
+ QFAIL("Inspect the output for details.");
+ }
+ QVERIFY(true);
+}
+
+QTEST_MAIN(tst_validateQdocOutputFiles)
+#include "tst_validateqdocoutputfiles.moc"