• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dkrajzew / db2qthelp / 17177825513

23 Aug 2025 04:45PM UTC coverage: 93.103%. First build
17177825513

push

github

dkrajzew
moving to utf-8 encoding; patching tetss

1 of 3 new or added lines in 1 file covered. (33.33%)

459 of 493 relevant lines covered (93.1%)

9.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.56
/db2qthelp/db2qthelp.py
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
"""db2qthelp - a DocBook book to QtHelp project converter"""
4
# ===========================================================================
5
__author__     = "Daniel Krajzewicz"
10✔
6
__copyright__  = "Copyright 2022-2025, Daniel Krajzewicz"
10✔
7
__credits__    = ["Daniel Krajzewicz"]
10✔
8
__license__    = "GPLv3"
10✔
9
__version__    = "0.2.0"
10✔
10
__maintainer__ = "Daniel Krajzewicz"
10✔
11
__email__      = "daniel@krajzewicz.de"
10✔
12
__status__     = "Development"
10✔
13
# ===========================================================================
14
# - https://github.com/dkrajzew/db2qthelp
15
# - http://www.krajzewicz.de/docs/db2qthelp/index.html
16
# - http://www.krajzewicz.de
17
# ===========================================================================
18

19

20
# --- imports ---------------------------------------------------------------
21
import os
10✔
22
import sys
10✔
23
import shutil
10✔
24
import glob
10✔
25
import re
10✔
26
import argparse
10✔
27
import configparser
10✔
28
import subprocess
10✔
29
from typing import List, Set, Tuple
10✔
30

31

32
# --- variables and constants -----------------------------------------------
33
CSS_DEFINITION = """body {
10✔
34
 margin: 0;
35
 padding: 0 0 0 0;
36
 background-color: rgba(255, 255, 255, 1);
37
 font-size: 12pt;
38
}
39
ul { margin: -.8em 0em 1em 0em; }
40
li { margin: 0em 0em 0em 0em; }
41
p { margin: .2em 0em .4em 0em; }
42
h4 { font-size: 14pt; }
43
h3 { font-size: 16pt; }
44
h2 { font-size: 18pt; }
45
h1 { font-size: 20pt; }
46
pre { background-color: rgba(224, 224, 224, 1); }
47
.guimenu, .guimenuitem, .guibutton { font-weight: bold; }
48
table, th, td { border: 1px solid black; border-collapse: collapse; }
49
th, td { padding: 4px; }
50
th { background-color: rgba(204, 212, 255, 1); }
51
div.informalequation { text-align: center; font-style: italic; }
52
.note p { background-color: #e0f0ff; margin: 8px 8px 8px 8px; }
53
.tip p { background-color: #c0ffc0; margin: 8px 8px 8px 8px; }
54
.warning p { background-color: #ffff80; margin: 8px 8px 8px 8px; }
55
"""
56

57
QHP_TEMPLATE = """<?xml version="1.0" encoding="UTF-8"?>
10✔
58
<QtHelpProject version="1.0">
59
    <namespace>%appname%</namespace>
60
    <virtualFolder>doc</virtualFolder>
61
    <filterSection>
62
        <filterAttribute>%appname%</filterAttribute>
63
        <toc>
64
%toc%
65
        </toc>
66
        <keywords>
67
%keywords%
68
        </keywords>
69
        <files>
70
            <file>*.html</file>
71
            <file>*.png</file>
72
            <file>*.gif</file>
73
        </files>
74
    </filterSection>
75
</QtHelpProject>
76
"""
77

78
QCHP = """<?xml version="1.0" encoding="UTF-8"?>
10✔
79
<QHelpCollectionProject version="1.0">
80
    <docFiles>
81
        <generate>
82
            <file>
83
                <input>%appname%.qhp</input>
84
                <output>%appname%.qch</output>
85
            </file>
86
        </generate>
87
        <register>
88
            <file>%appname%.qch</file>
89
        </register>
90
    </docFiles>
91
</QHelpCollectionProject>
92
"""
93

94
# --- functions -------------------------------------------------------------
95
class Db2QtHelp:
10✔
96
    def __init__(self, qt_path : str, xsltproc_path : str, css_definition : str, qhp_template : str):
10✔
97
        """Contructor
98

99
        Args:
100
            qt_path (str): Path to the Qt binaries
101
            xsltproc_path (str): Path to the xsltproc binary
102
            css_definition (str): CSS definition to use
103
            qhp_template (str): Template for the .qhp file
104
        """
105
        self._qt_path = qt_path
10✔
106
        self._xsltproc_path = xsltproc_path
10✔
107
        self._css_definition = css_definition if css_definition is not None else CSS_DEFINITION
10✔
108
        self._css_definition = "\n<style>\n" + self._css_definition + "</style>\n"
10✔
109
        self._qhp_template = qhp_template if qhp_template is not None else QHP_TEMPLATE
10✔
110

111

112
    def _get_id(self, html : str) -> str:
10✔
113
        """Return the docbook ID of the current section.
114

115
        The value of the first a-element's name attribute is assumed to be the docbook ID.
116

117
        Args:
118
            html (str): The HTML snippet to get the next docbook ID from
119

120
        Returns:
121
            (str): The next ID found in the snippet
122
        """
123
        db_id = html[html.find("<a name=\"")+9:]
10✔
124
        db_id = db_id[:db_id.find("\"")]
10✔
125
        return db_id
10✔
126

127

128
    def _get_name(self, html : str) -> str:
10✔
129
        """Return the name of the current section.
130

131
        Args:
132
            html (str): The HTML snippet to get the next name from
133

134
        Returns:
135
            (str): The next name found in the snippet
136
        """
137
        name = html[html.find("</a>")+4:]
10✔
138
        name = name[:name.find("</h")]
10✔
139
        name = name.replace("\"", "'")
10✔
140
        name = name.strip()
10✔
141
        return name
10✔
142

143

144
    def _get_title(self, html : str) -> str:
10✔
145
        """Return the name of the current section.
146

147
        Args:
148
            html (str): The HTML snippet to get the next name from
149

150
        Returns:
151
            (str): The next name found in the snippet
152
        """
153
        name = html[html.find("<title>")+7:]
10✔
154
        name = name[:name.find("</title>")]
10✔
155
        name = name.replace("\"", "'")
10✔
156
        name = name.strip()
10✔
157
        return name
10✔
158

159

160
    def patch_links(self, doc : str, app_name : str, files : Set[str]) -> str:
10✔
161
        """Extracts references to images; patches links to point to main document folder
162

163
        Args:
164
            doc (str): The HTML document to process
165
            app_name (str): The application name
166
            files (Set[str]): The container to store links into
167

168
        Returns:
169
            (str): The changed document
170
        """
171
        srcs = re.findall(r'src\s*=\s*"(.+?)"', doc)
10✔
172
        seen = set()
10✔
173
        for src in srcs:
10✔
174
            filename = os.path.split(src)[1]
×
175
            if filename in seen:
×
176
                continue
×
177
            seen.add(filename)
×
178
            nsrc = f"qthelp://{app_name}/doc/{filename}"
×
179
            doc = doc.replace(src, nsrc)
×
180
            files.add(src)
×
181
        return doc
10✔
182

183

184
    def _write_sections_recursive(self, html : str, dst_folder : str, pages : List[Tuple[str, str]], level : int) -> None:
10✔
185
        """Writes the given section and it's sub-sections recursively.
186

187
        The id and the name of the section are retrieved, first.
188

189
        Then, the toc HTML file is extended and the reference to this section is
190
        appended to the returned toc. Keywords are extended by the section's name.
191

192
        The section is then split along the
193
        '&lt;div class="sect&lt;INDENT&gt;"&gt;' elements which are processed
194
        recursively.
195

196
        The (recursively) collected keywords and toc are returned.
197

198
        Args:
199
            html (str): The (string) content of the DocBook book section or appendix
200
            dst_folder (str): The folder to write the section into
201
            pages (List[Tuple[str, str]]): The list of HTML sections to fill
202
            level (int): intendation level
203
        """
204
        db_id = self._get_id(html)
10✔
205
        name = self._get_name(html)
10✔
206
        pages.append([f"{db_id}.html", name])
10✔
207
        subs = html.split(f"<div class=\"sect{level}\">")
10✔
208
        if subs[0].rfind("</div>")>=len(subs[0])-6:
10✔
209
            subs[0] = subs[0][:subs[0].rfind("</div>")]
×
210
        # patch links (convert links to anchors, to the proper links to split pages)
211
        subs[0] = re.sub(r'<a href="#([^"]*)">([^<]*)</a>', r'<a href="\1.html">\2</a>', subs[0])
10✔
212
        subs[0] = re.sub(r'<a class="ulink" href="#([^"]*)">([^<]*)</a>', r'<a class="ulink" href="\1.html">\2</a>', subs[0])
10✔
213
        # write the document part as document
214
        subs[0] = "<html><head>" + self._css_definition + "</head><body>" + subs[0] + "</body></html>"
10✔
215
        with open(dst_folder + f"/{db_id}.html", "w", encoding="utf-8") as fdo:
10✔
216
            fdo.write(subs[0])
10✔
217
        if len(subs)>1:
10✔
218
            for i,sub in enumerate(subs):
10✔
219
                if i==0:
10✔
220
                    continue
10✔
221
                sub = sub[:sub.rfind("</div>")]
10✔
222
                self._write_sections_recursive(sub, dst_folder, pages, level+1)
10✔
223

224

225
    def _process_single(self, source : str, dst_folder : str, pages : List[Tuple[str, str]], files : Set[str], app_name : str) -> None:
10✔
226
        """Processes a single (not chunked) HTML document generated by docbook
227

228
        Args:
229
            source (str): The HTML document to process
230
            dst_folder (str): The folder to write the section into
231
            pages (List[Tuple[str, str]]): The list of HTML sections to fill
232
            files (Set[str]): The set of referenced files (images) to fill
233
            app_name (str): The application name
234
        """
235
        # read doc
236
        with open(source, encoding="utf-8") as fdi:
10✔
237
            doc = fdi.read()
10✔
238
        doc = self.patch_links(doc, app_name, files)
10✔
239
        # process document
240
        chapters = doc.split("<div class=\"chapter\">")
10✔
241
        appendices = chapters[-1].split('<div class="appendix">')
10✔
242
        chapters = chapters[:-1]
10✔
243
        chapters.extend(appendices)
10✔
244
        for c in chapters:
10✔
245
            self._write_sections_recursive(c, dst_folder, pages, 1)
10✔
246

247

248
    def _generate_html(self, source : str, folder : str) -> int:
10✔
249
        """Generates a chunked HTML document from the source docbook document
250

251
        Args:
252
            source (str): The XML DocBook document to process
253
            folder (str): A (temporary) folder to store the xsltproc output to
254
        """
255
        shutil.rmtree(folder, ignore_errors=True)
10✔
256
        chunk_xsl_path = os.path.join(os.path.split(__file__)[0], "data", "chunk_html.xsl")
10✔
257
        try:
10✔
258
            result = subprocess.run([os.path.join(self._xsltproc_path, 'xsltproc'),
10✔
259
                "--stringparam", "base.dir", folder,
260
                chunk_xsl_path, source], check = True)
261
        except subprocess.CalledProcessError:
10✔
262
            raise RuntimeError("could not invoke xsltproc...")
×
263
        except FileNotFoundError:
10✔
264
            raise RuntimeError("could not invoke xsltproc...")
10✔
265
        if isinstance(result, subprocess.CompletedProcess):
×
266
            ret = result.returncode
×
267
        else:
268
            ret = 3
×
269
        return ret
×
270

271

272
    def _process_chunked(self, folder : str, pages : List[Tuple[str, str]], files : Set[str], app_name : str, dst_folder) -> None:
10✔
273
        """Processes a the set of HTML documents generated by chunking docbook
274

275
        Args:
276
            folder (str): A (temporary) folder to store the xsltproc output to
277
            pages (List[Tuple[str, str]]): The list of HTML sections to fill
278
            files (Set[str]): The set of referenced files (images) to fill
279
            app_name (str): The application name
280
        """
281
        # collect entries
282
        for file in glob.glob(os.path.join(folder, "*.html")):
10✔
283
            _, filename = os.path.split(file)
10✔
284
            if filename=="index.html":
10✔
285
                continue
10✔
286
            with open(file, encoding="utf-8") as fd:
10✔
287
                html = fd.read()
10✔
288
            html = self.patch_links(html, app_name, files)
10✔
289
            title_end = html.find("</title>") + 8
10✔
290
            html = html[:title_end] + self._css_definition + html[title_end:]
10✔
291
            title = self._get_title(html)
10✔
292
            pages.append([filename, title])
10✔
293
            _, filename = os.path.split(file)
10✔
294
            with open(os.path.join(dst_folder, filename), "w", encoding="utf-8") as fd:
10✔
295
                fd.write(html)
10✔
296

297

298
    def _copy_files(self, files : Set[str], source : str, dst_folder : str) -> None:
10✔
299
        """Copies referenced files into the destination folder
300

301
        Args:
302
            files (Set[str]): The files to compy
303
            source (str): The origin folder
304
            dst_folder (str): The destination folder
305
        """
306
        base_path = source if os.path.isdir(source) else os.path.split(source)[0]
10✔
307
        for file in files:
10✔
308
            _, filename = os.path.split(file)
×
309
            shutil.copy(os.path.join(base_path, file), f"{dst_folder}/{filename}")
×
310

311

312
    def build_toc_sections(self, pages : List[Tuple[str, str, List[int]]]) -> str:
10✔
313
        """Generates a hierarchical list of pages to be embedded in the toc-section
314
        of the qhp-file.
315

316
        Args:
317
            pages (List[Tuple[str, str, List[int]]]): The sorted list of pages
318

319
        Returns:
320
            (str): The pages formatted as toc-sections
321
        """
322
        toc = ""
10✔
323
        level = 1
10✔
324
        for ie,e in enumerate(pages):
10✔
325
            filename = e[0]
10✔
326
            title = e[1]
10✔
327
            nlevel = len(e[2])
10✔
328
            while ie!=0 and nlevel<=level:
10✔
329
                indent = " "*(level*4+8)
10✔
330
                toc += indent + "</section>\n"
10✔
331
                level -= 1
10✔
332
            level = nlevel
10✔
333
            indent = " "*(level*4+8)
10✔
334
            toc += indent + f"<section title=\"{title}\" ref=\"{filename}\">\n"
10✔
335
        while level>0:
10✔
336
            indent = " "*(level*4+8)
10✔
337
            toc += indent + "</section>\n"
10✔
338
            level -= 1
10✔
339
        return toc
10✔
340

341

342
    def process(self, source : str, dst_folder : str, app_name : str) -> None:
10✔
343
        """Performs the conversion
344

345
        Args:
346
            source (str): The input file or folder
347
            dst_folder (str): The destination folder (where the documentation is built)
348
            app_name (str): The name of the application
349
        """
350
        # clear output folder
351
        shutil.rmtree(dst_folder, ignore_errors=True)
10✔
352
        os.makedirs(dst_folder, exist_ok=True)
10✔
353
        # process
354
        pages = []
10✔
355
        files = set()
10✔
356
        if os.path.isdir(source):
10✔
357
            print(f"Processing chunked HTML output from '{source}'")
10✔
358
            self._process_chunked(source, pages, files, app_name, dst_folder)
10✔
359
        elif os.path.isfile(source):
10✔
360
            if source.endswith(".html"):
10✔
361
                print(f"Processing single HTML output from '{source}'")
10✔
362
                self._process_single(source, dst_folder, pages, files, app_name)
10✔
363
            elif source.endswith(".xml"):
10✔
364
                print(f"Processing docboook '{source}'")
10✔
365
                tmp_dir = "_tmp_db2qthelp_dir"
10✔
366
                os.makedirs(tmp_dir, exist_ok=True)
10✔
367
                print("... generating chunked HTML")
10✔
368
                ret = self._generate_html(source, tmp_dir)
10✔
369
                if ret!=0:
×
370
                    raise ValueError(f"xsltproc failed with ret={ret}")
×
371
                print("... processing chunked HTML")
×
372
                self._process_chunked(tmp_dir, pages, files, app_name, dst_folder)
×
373
                shutil.rmtree(tmp_dir, ignore_errors=True)
×
374
            else:
375
                raise ValueError(f"unsupported file extension of '{source}'")
×
376
        else:
377
            raise ValueError(f"unknown file '{source}'")
×
378
        # copy images etc.
379
        self._copy_files(files, source, dst_folder)
10✔
380
        # sort pages
381
        # https://stackoverflow.com/questions/14861843/sorting-chapters-numbers-like-1-2-1-or-1-4-2-4
382
        def expand_chapter(ch, depth):
10✔
383
            ch = ch + [0,] * (depth - len(ch))
10✔
384
            return ch
10✔
385
        max_depth = 0
10✔
386
        for page in pages:
10✔
387
            if page[1][0]=='A':
10✔
388
                chapter = [ord(page[1].split()[1][0]) + 1000]
×
389
            else:
390
                chapter = [int(x) for x in page[1].split()[0].split(".")[:-1]]
10✔
391
            if len(chapter)==0:
10✔
392
                chapter = [0]
10✔
393
            page.append(chapter)
10✔
394
            max_depth = max(len(chapter), max_depth)
10✔
395
        pages.sort(key = lambda x: expand_chapter(x[2], max_depth))
10✔
396
        #
397
        toc = self.build_toc_sections(pages)
10✔
398
        keywords = "\n".join(" "*12 + f"<keyword name=\"{page[1]}\" ref=\"./{page[0]}\"/>" for page in pages)
10✔
399
        # read template, write extended by collected data
400
        path = f"{dst_folder}/{app_name}"
10✔
401
        with open(path + ".qhp", "w", encoding="utf-8") as fdo:
10✔
402
            fdo.write(self._qhp_template.replace("%toc%", toc).replace("%keywords%", keywords).replace("%appname%", app_name))
10✔
403
        # generate qhcp
404
        with open(path + ".qhcp", "w", encoding="utf-8") as fdo:
10✔
405
            fdo.write(QCHP.replace("%appname%", app_name))
10✔
406
        # generate QtHelp
407
        os.system(f"{os.path.join(self._qt_path, 'qhelpgenerator')} {path}.qhp -o {path}.qch")
10✔
408
        os.system(f"{os.path.join(self._qt_path, 'qhelpgenerator')} {path}.qhcp -o {path}.qhc")
10✔
409

410

411
def main(arguments : List[str] = None) -> int:
10✔
412
    """The main method using parameter from the command line.
413

414
    The application deletes previously collected and build .html, .png, and
415
    .gif-files within the folder defined by --destination. Then, it copies
416
    .png and .gif-files from the folders defined using --files into the
417
    destination folder.
418

419
    It then reads the HTML-file generated from DocBook defined using --input
420
    and processes it. Processing means here that the file is split at sections
421
    and appendices. Each subpart is written into the destination folder defined
422
    using --destination and included in the table of contents (toc). The paths
423
    to references images as defined using --files are replaced by the
424
    destination path defined using --destination. In addition, the headers are
425
    included in the list of keywords.
426

427
    Then, the qhp-templated defined using --template is loaded and the
428
    placeholders (see above) are replaced by the given / collected data.
429

430
    db2qthelp generates a qhcp file afterwards as
431
    "&lt;DESTINATION_FOLDER>/&lt;APPLICATION_NAME&gt;.qhcp".
432

433
    Finally, the script calls two QtHelp processing applications which must be
434
    located in the folder defined using --path:
435

436
    &lt;QT_PATH&gt;/qhelpgenerator &lt;APPLICATION_NAME&gt;.qhp -o &lt;APPLICATION_NAME&gt;.qch
437
    &lt;QT_PATH&gt;/qcollectiongenerator &lt;APPLICATION_NAME&gt;.qhcp -o &lt;APPLICATION_NAME&gt;.qhc
438

439
    Args:
440
        arguments (List[str]): The command line arguments, parsed as options using OptionParser.
441

442
    Returns:
443
        (int): The return code
444

445
    Options
446
    -------
447

448
    The following options must be set:
449

450
    --input / -i &lt;DOCBOOK_HTML&gt;:
451
        Defines the DocBook HTML document to parse
452

453
    --appname / -a &lt;APPLICATION_NAME&gt;:
454
        Sets the name of the application
455

456
    --source / -s &lt;ADDITIONAL_FILES_FOLDER&gt;:
457
        Sets the documentation source url
458

459

460
    The following options are optional:
461

462
    --files / -f &lt;ADDITIONAL_FILES_FOLDER&gt;[,&lt;ADDITIONAL_FILES_FOLDER&gt;]*:
463
        Sets the folder(s) to collect files from
464

465
    --destination / -d &lt;DESTINATION_FOLDER&gt;:
466
        Sets the output folder
467

468
    --template / -t &lt;TEMPLATE_FILE&gt;:
469
        Defines the QtHelp project template to use; default: 'template.qhp'
470

471
    --generate / -g:
472
        If set, the template is written to the file as defined by --template;
473
        The application quits afterwards
474

475
    --path / -p &lt;QT_PATH&gt;:
476
        Sets the path to the Qt binaries to use
477

478
    --help:
479
        Prints the help screen
480
    """
481
    # parse options
482
    # https://stackoverflow.com/questions/3609852/which-is-the-best-way-to-allow-configuration-options-be-overridden-at-the-comman
483
    defaults = {}
10✔
484
    conf_parser = argparse.ArgumentParser(prog='db2qthelp', add_help=False)
10✔
485
    conf_parser.add_argument("-c", "--config", metavar="FILE", help="Reads the named configuration file")
10✔
486
    args, remaining_argv = conf_parser.parse_known_args(arguments)
10✔
487
    if args.config is not None:
10✔
488
        if not os.path.exists(args.config):
×
489
            print (f"db2qthelp: error: configuration file '{str(args.config)}' does not exist", file=sys.stderr)
×
490
            raise SystemExit(2)
×
491
        config = configparser.ConfigParser()
×
492
        config.read([args.config])
×
493
        defaults.update(dict(config.items("db2qthelp")))
×
494
    parser = argparse.ArgumentParser(prog='db2qthelp', parents=[conf_parser],
10✔
495
        description="a DocBook book to QtHelp project converter",
496
        epilog='(c) Daniel Krajzewicz 2022-2025')
497
    parser.add_argument("-i", "--input", dest="input", default=None, help="Defines the DocBook HTML document to parse")
10✔
498
    parser.add_argument("-d", "--destination", dest="destination", default="qtdoc", help="Sets the output folder")
10✔
499
    parser.add_argument("-a", "--appname", dest="appname", default="na", help="Sets the name of the application")
10✔
500
    parser.add_argument("--css-definition", dest="css_definition", default=None, help="Defines the CSS definition file to use")
10✔
501
    parser.add_argument("--generate-css-definition", dest="generate_css_definition", action="store_true", default=False, help="If set, a CSS definition file is generated")
10✔
502
    parser.add_argument("--qhp-template", dest="qhp_template", default=None, help="Defines the QtHelp project (.qhp) template to use")
10✔
503
    parser.add_argument("--generate-qhp-template", dest="generate_qhp_template", action="store_true", default=False, help="If set, a QtHelp project (.qhp) template is generated")
10✔
504
    parser.add_argument("-Q", "--qt-path", dest="qt_path", default="", help="Sets the path to the Qt binaries")
10✔
505
    parser.add_argument("-X", "--xslt-path", dest="xslt_path", default="", help="Sets the path to xsltproc")
10✔
506
    parser.add_argument('--version', action='version', version='%(prog)s 0.2.0')
10✔
507
    parser.set_defaults(**defaults)
10✔
508
    args = parser.parse_args(remaining_argv)
10✔
509
    # - generate the css template and quit, if wished
510
    if args.generate_css_definition:
10✔
511
        template_name = args.css_definition if args.css_definition is not None else "template.css"
10✔
512
        with open(template_name, "w", encoding="utf-8") as fdo:
10✔
513
            fdo.write(CSS_DEFINITION)
10✔
514
        print (f"Written css definition to '{template_name}'")
10✔
515
        sys.exit(0)
10✔
516
    # - generate the qhp template and quit, if wished
517
    if args.generate_qhp_template:
10✔
518
        template_name = args.qhp_template if args.qhp_template is not None else "template.qhp"
10✔
519
        with open(template_name, "w", encoding="utf-8") as fdo:
10✔
520
            fdo.write(QHP_TEMPLATE)
10✔
521
        print (f"Written qhp template to '{template_name}'")
10✔
522
        sys.exit(0)
10✔
523
    # check
524
    errors = []
10✔
525
    if args.input is None:
10✔
526
        errors.append("no input file given (use -i <HTML_DOCBOOK>)...")
10✔
527
    elif os.path.isfile(args.input) and (not args.input.endswith(".html") and not args.input.endswith(".xml")):
10✔
528
        errors.append("unrecognized input extension '{os.path.splitext(args.input)[1]}'")
×
529
    elif not os.path.exists(args.input):
10✔
530
        errors.append(f"did not find input '{args.input}'")
10✔
531
    if args.qhp_template is not None and not os.path.exists(args.qhp_template):
10✔
532
        errors.append(f"did not find QtHelp project (.qhp) template file '{args.qhp_template}'; you may generate one using the option --generate-qhp-template")
10✔
533
    if args.css_definition is not None and not os.path.exists(args.css_definition):
10✔
534
        errors.append(f"did not find CSS definition file '{args.css_definition}'; you may generate one using the option --generate-css-definition")
10✔
535
    if len(errors)!=0:
10✔
536
        for e in errors:
10✔
537
            print(f"db2qthelp: error: {e}", file=sys.stderr)
10✔
538
        raise SystemExit(2)
10✔
539
    # get settings
540
    qhp_template = None
10✔
541
    if args.qhp_template is not None:
10✔
NEW
542
        with open(args.qhp_template, encoding="utf-8") as fdi:
×
543
            qhp_template = fdi.read()
×
544
    css_definition = None
10✔
545
    if args.css_definition is not None:
10✔
NEW
546
        with open(args.css_definition, encoding="utf-8") as fdi:
×
547
            css_definition = fdi.read()
×
548
    # process
549
    ret = 0
10✔
550
    db2qthelp = Db2QtHelp(args.qt_path, args.xslt_path, css_definition, qhp_template)
10✔
551
    try:
10✔
552
        db2qthelp.process(args.input, args.destination, args.appname)
10✔
553
    except Exception as e:
10✔
554
        print(f"db2qthelp: error: {str(e)}", file=sys.stderr)
10✔
555
        ret = 2
10✔
556
    return ret
10✔
557

558

559
def script_run() -> int:
10✔
560
    """Execute from command line."""
561
    sys.exit(main(sys.argv[1:])) # pragma: no cover
562

563

564
# -- main check
565
if __name__ == '__main__':
10✔
566
    main(sys.argv[1:]) # pragma: no cover
567

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc