#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
convert_sdk_to_md.py — Экспорт справки KOMPAS SDK (Help & Manual WebHelp) в
отдельные Markdown-файлы.
Каждая HTML-страница из docs/KOMPAS_SDK_ru-RU/ конвертируется в отдельный
.md файл в docs/KOMPAS_SDK_ru-RU/md/. Информативные изображения сохраняются
как base64 data-URI прямо в Markdown (inline).
Изображения, которые НЕ являются информативными (UI, навигация и т.п.), пропускаются.
"""
import os
import sys
import re
import json
import base64
import html as hmod
from html.parser import HTMLParser
from pathlib import Path
# ─── Настройки ───────────────────────────────────────────────────────────────
for _s in (sys.stdout, sys.stderr):
try:
_s.reconfigure(encoding="utf-8")
except Exception:
pass
HERE = Path(__file__).resolve().parent.parent
DOC_DIR = HERE / "docs" / "KOMPAS_SDK_ru-RU"
INDEX_PATH = HERE / "docs" / "kompas_sdk_index.tsv"
OUTPUT_DIR = DOC_DIR / "md" # docs/KOMPAS_SDK_ru-RU/md/
# Изображения, которые НЕ являются информативными (UI-элементы навигации и т.п.)
UI_IMAGE_PREFIXES = [
"leftright", "toc", "mob_", "TextPlus", "TextMinus", "TextNormal",
"ZoomIn", "ZoomClose", "spacer.gif", "bg.png", "page-bg.png",
"blackpaisley.jpeg", "banner_company", "kompas_logo_circle",
"kompas-logo", "internet.svg", "icon-new-window", "icon-open-file",
"warn", "note", "closehelp", "cicon_loadindex_ani", "kw-page",
"social_mail", "_a", "_praezession", "kbd.png", "page-icon",
"logo-image-desk", "logo-image-mobile", "close_data", "open_data",
]
# ─── Проверка: информативное ли изображение ──────────────────────────────────
def is_informative_image(src: str) -> bool:
basename = os.path.basename(src).lower()
for prefix in UI_IMAGE_PREFIXES:
if prefix.lower() in basename:
return False
img_path = DOC_DIR / src
if img_path.exists() and img_path.stat().st_size < 500:
return False
return True
# ─── Парсер тела топика ─────────────────────────────────────────────────────
class TopicExtractor(HTMLParser):
BLOCK_TAGS = {"p", "div", "br", "tr", "li", "h1", "h2", "h3", "h4",
"table", "pre", "td", "th"}
def __init__(self):
super().__init__(convert_charrefs=True)
self.depth = 0
self.capturing = False
self.skip = 0
self.out = []
def handle_starttag(self, tag, attrs):
attrd = dict(attrs)
if not self.capturing and attrd.get("id") == "topicbody":
self.capturing = True
self.depth = 1
return
if not self.capturing:
return
if tag in ("script", "style"):
self.skip += 1
if tag in self.BLOCK_TAGS:
self.out.append("\n")
if tag != "br":
self.depth += 1
def handle_endtag(self, tag):
if not self.capturing:
return
if tag in ("script", "style") and self.skip:
self.skip -= 1
if tag != "br":
self.depth -= 1
if self.depth <= 0:
self.capturing = False
def handle_data(self, data):
if self.capturing and not self.skip:
self.out.append(data)
# ─── Шум и подвал ────────────────────────────────────────────────────────────
NOISE_WORDS = {
"please enable javascript to view this site.",
"scroll", "предыдущий", "вверх", "следующий",
"содержание", "содержане", "указатель", "поиск",
"подразделы:", "(отсутствуют)", "поделиться",
}
FOOTER_RE = re.compile(r"©?\s*ООО\s*«?АСКОН")
def clean_text(raw: str) -> list[str]:
cleaned = []
for line in raw.splitlines():
s = re.sub(r"[ \t]+", " ", line).strip()
if not s:
continue
if s.lower() in NOISE_WORDS:
continue
if FOOTER_RE.search(s):
break
cleaned.append(s)
return cleaned
# ─── Извлечение изображений как base64 data-URI ──────────────────────────────
def extract_images(html_content: str) -> list[tuple[str, str]]:
images = []
for m in re.finditer(r']+>', html_content):
tag = m.group()
src_m = re.search(r'''src=["']([^"']+)["']''', tag)
alt_m = re.search(r'''alt=["']([^"']*)["']''', tag)
if not src_m:
continue
src = src_m.group(1)
alt = alt_m.group(1) if alt_m else ""
if not is_informative_image(src):
continue
img_path = DOC_DIR / src
if not img_path.exists():
continue
ext = os.path.splitext(src)[1].lower().lstrip(".")
mime_map = {
"jpg": "image/jpeg", "jpeg": "image/jpeg",
"png": "image/png", "gif": "image/gif",
"svg": "image/svg+xml", "webp": "image/webp",
"bmp": "image/bmp",
}
mime = mime_map.get(ext, f"image/{ext}")
data = base64.b64encode(img_path.read_bytes()).decode("ascii")
data_uri = f"data:{mime};base64,{data}"
images.append((data_uri, alt))
return images
# ─── Заголовок и хлебные крошки ──────────────────────────────────────────────
def extract_title_and_breadcrumbs(html_content: str) -> tuple[str, list[tuple[str, str]]]:
title = ""
for m in re.finditer(r'
]*>(.*?)
', html_content, re.DOTALL): title = re.sub(r'<[^>]+>', '', m.group(1)).strip() title = hmod.unescape(title) break breadcrumbs = [] bc_match = re.search( r']*>(.*?)
', html_content, re.DOTALL, ) if bc_match: bc_html = bc_match.group(1) for a_m in re.finditer(r']+href="([^"]*)"[^>]*>(.*?)', bc_html, re.DOTALL): href = a_m.group(1) text = re.sub(r'<[^>]+>', '', a_m.group(2)).strip() text = hmod.unescape(text) if text: breadcrumbs.append((text, os.path.basename(href))) return title, breadcrumbs # ─── Раскрытие HTML-сущностей ──────────────────────────────────────────────── def unescape_html(s: str) -> str: s = s.replace(" ", " ") s = s.replace(" ", " ") s = s.replace("", "") s = s.replace("—", "—") s = s.replace("–", "–") s = s.replace("«", "«") s = s.replace("»", "»") s = s.replace(""", '"') s = s.replace("'", "'") s = s.replace("'", "'") s = s.replace("<", "<") s = s.replace(">", ">") return hmod.unescape(s) # ─── Извлечение ссылок из HTML-тела топика ────────────────────────────────── def extract_links_from_html(html_content: str) -> list[tuple[str, str]]: """ Находит все в topicbody и возвращает список [(anchor_text, href), ...] для последующей подстановки. anchor_text — текст ссылки без HTML-тегов. """ # Ищем всё содержимое после id="topicbody" до