Введение
Писать справку напрямую в HTML утомительно для авторов контента, не являющихся разработчиками — теги, экранирование символов, ручная синхронизация навигации отвлекают от содержания. Markdown — гораздо более дружелюбный формат для авторов, и существующие инструменты (pandoc, MkDocs) умеют конвертировать его в HTML, который затем можно обернуть в .qhp и скомпилировать в .qch, как мы делали в начале цикла. В статье построим пайплайн от исходников в Markdown до готового .qch.
Концепция
Markdown-файлы хранят содержательную часть документации в простом текстовом формате с минимальной разметкой (заголовки через #, списки через -, ссылки через [текст](url)). Pandoc конвертирует каждый .md-файл в самостоятельный HTML-файл с предсказуемой структурой заголовков, которую затем можно использовать для автоматической генерации TOC — заголовки #/## естественно превращаются в иерархию разделов .qhp. Генерация .qhp-файла на основе структуры Markdown-документов автоматизируется отдельным скриптом, который сканирует директорию с исходниками, конвертирует каждый файл и собирает итоговый .qhp на основе их заголовков.
Пример кода: Markdown → Qt Help
<!-- docs/markdown/getting_started.md -->
# Начало работы
## Системные требования
Приложение работает на Windows 10+, macOS 12+ и большинстве дистрибутивов Linux.
## Установка
Скачайте установщик с официального сайта и запустите его.
## Первый запуск
При первом запуске будет предложено настроить базовые параметры.
#!/usr/bin/env bash
# build_help_from_markdown.sh — конвертация Markdown в HTML и сборка .qch
set -e
SOURCE_DIR="docs/markdown"
OUTPUT_DIR="docs/html"
mkdir -p "$OUTPUT_DIR"
echo "Конвертация Markdown в HTML через pandoc..."
for mdfile in "$SOURCE_DIR"/*.md; do
basename=$(basename "$mdfile" .md)
pandoc "$mdfile" \
--standalone \
--css="../style.css" \
--metadata title="$basename" \
-o "$OUTPUT_DIR/$basename.html"
done
echo "Генерация .qhp на основе сконвертированных файлов..."
python3 generate_qhp.py "$OUTPUT_DIR" > docs/myapp.qhp
echo "Компиляция .qch..."
qhelpgenerator docs/myapp.qhp -o docs/myapp.qch
echo "Готово: docs/myapp.qch"
# generate_qhp.py — упрощённый генератор .qhp на основе HTML-заголовков
import sys
import os
from html.parser import HTMLParser
class HeadingExtractor(HTMLParser):
def __init__(self):
super().__init__()
self.headings = []
self.current_tag = None
def handle_starttag(self, tag, attrs):
if tag in ("h1", "h2", "h3"):
self.current_tag = tag
attr_dict = dict(attrs)
self.headings.append({"tag": tag, "id": attr_dict.get("id", ""), "text": ""})
def handle_data(self, data):
if self.current_tag and self.headings:
self.headings[-1]["text"] += data
def handle_endtag(self, tag):
self.current_tag = None
def main(html_dir):
print('<?xml version="1.0" encoding="UTF-8"?>')
print('<QtHelpProject version="1.0">')
print(' <namespace>com.mycompany.myapp.1.0</namespace>')
print(' <virtualFolder>doc</virtualFolder>')
print(' <filterSection>')
print(' <toc>')
files = []
for filename in sorted(os.listdir(html_dir)):
if not filename.endswith(".html"):
continue
files.append(filename)
with open(os.path.join(html_dir, filename), encoding="utf-8") as f:
parser = HeadingExtractor()
parser.feed(f.read())
for heading in parser.headings:
ref = f"html/{filename}"
if heading["id"]:
ref += f"#{heading['id']}"
indent = " " * (int(heading["tag"][1]))
print(f'{indent}<section title="{heading["text"]}" ref="{ref}" />')
print(' </toc>')
print(' <files>')
for filename in files:
print(f' <file>html/{filename}</file>')
print(' </files>')
print(' </filterSection>')
print('</QtHelpProject>')
if __name__ == "__main__":
main(sys.argv[1])
Пояснения к коду
Скрипт build_help_from_markdown.sh сначала конвертирует каждый Markdown-файл в самостоятельный HTML-документ через pandoc --standalone, подключая общий CSS-файл для единообразного оформления всех страниц — флаг --standalone важен, поскольку без него pandoc вывел бы только фрагмент <body> без обёртки <html>/<head>, которая нужна для самостоятельного HTML-файла, готового к включению в .qch.
generate_qhp.py — упрощённый, но рабочий генератор: он разбирает HTML-заголовки (h1/h2/h3) каждого сконвертированного файла через стандартный html.parser и строит TOC, отражающий вложенность заголовков через отступ — реальный продакшен-инструмент обычно сложнее (поддержка нескольких уровней вложенности директорий, явных метаданных порядка страниц), но базовый принцип — автоматическое построение навигации из структуры контента — продемонстрирован полностью.
Подводные камни
- Pandoc не добавляет
idк заголовкам автоматически без явной настройки. Без атрибутовidу<h2>/<h3>ссылки на конкретные разделы внутри страницы (а не только на страницу целиком) не сработают — нужно использовать расширение pandoc--id-prefixили явно прописывать идентификаторы в исходном Markdown через синтаксис## Заголовок {#my-id}(поддерживается с расширениемheader_attributes). - Порядок страниц в TOC, определяемый алфавитной сортировкой имён файлов, редко совпадает с желаемым логическим порядком изложения материала (например, «Введение» должно идти раньше «Установки», но алфавитно может оказаться после) — реальный пайплайн должен либо использовать явный список порядка страниц в конфигурационном файле, либо префиксы с номерами в именах файлов (
01_intro.md,02_install.md). - Markdown-расширения (таблицы, сноски, синтаксис кода с подсветкой), специфичные для конкретного диалекта Markdown, могут конвертироваться pandoc по-разному в зависимости от указанного формата исходного текста (
--from gfmдля GitHub-стиля,--from markdownдля стандартного Pandoc Markdown) — несогласованность между диалектом, в котором фактически пишут авторы, и диалектом, который ожидает команда конвертации, приводит к неожиданным артефактам в итоговом HTML. - Отсутствие проверки на ошибки конвертации pandoc для отдельных файлов в цикле bash-скрипта. Если один Markdown-файл содержит синтаксическую проблему, вызывающую сбой pandoc для этого конкретного файла, скрипт (без
set -e, реагирующего только на общий код возврата, но не гарантирующего диагностику конкретного файла) может продолжить выполнение с пропущенным файлом, что обнаружится только при последующей проверке итоговой справки на полноту контента.