16 KiB
Разделяне на произволен текст на секции
Този документ описва реалното поведение на RIP Help System при разделяне на help-файл на секции. Източникът е help_processor.py след последните корекции (вкл. merge_short_sections / merge_preamble_sections / номерирани глави). Не е маркетингово резюме.
Генерирането на ключови думи е извън обхвата, освен един ред в края.
За да прегенерираш PDF-а:
python docs/md_to_pdf.py
1. Място в обработката
За всеки файл process_file() прави:
- Избира парсер по разширение (
.html/.htm,.docx,.doc,.pdf,.txt). - Парсерът връща списък от
Section(title, text, level, images, html_text). - Върху списъка се вика
merge_short_sections(), послеmerge_preamble_sections(). - Празните блокове се филтрират при запис; заглавие без тяло се запазва (виж §8).
ZIP не е формат за разделяне: разопакова се и всеки вътрешен файл минава по същия път.
Нива: 1 = H1, 2 = H2, 3 = H3 (по-дълбоки HTML/Word заглавия се режат до 3), 0 = без заглавие.
2. Какво е граница на секция
В HTML и DOCX граница е едно от:
- H1 / Heading 1 / Заглавие 1 (класическа граница); или
- Номерирана top-level глава от вида
1. Заглавие/2) Заглавие— дори ако е H2, bold или Normal — когато редът изглежда като заглавие на глава (виж §2.1).
Текстът на границата става title и не се копира в тялото на секцията.
В HTML/DOCX не отварят нова секция:
- Word Title / Наименование / CSS
Title/MsoTitle— корица; става заглавие на преамбюла (или ред в тялото, ако вече има съдържание); - „Съдържание“ / Contents / TOC и еквиваленти — остават в тялото и включват TOC фаза (виж §2.1);
- H2–H6, Subtitle / Подзаглавие, изцяло bold параграфи — освен ако текстът е номерирана глава (
N. Title); - редове от таблици (
a | b | c), „Фигура N: …“, дълги изречения с номерация.
TXT и PDF запазват своите правила (номерирани редове / по-голям шрифт) — там ниво 2 е нормална граница.
Ако преди първата глава има корица + съдържание, те образуват преамбюл (title от корицата/първото H1).
Ако парсерът не открие нито една секция, файлът става една секция без заглавие, с целия извлечен текст.
2.1. Номерирани глави и TOC
Шаблон: ^\d{1,2}[\.\)]\s+\S , дължина < 120, ≤ 12 думи след номера, без |, без „Фигура N“.
След заглавие „Съдържание“/TOC парсерът влиза в TOC фаза:
- първите срещания на
1. …,2. …(Normal / списък) остават в преамбюла; - повторно срещане на същия текст (реалната глава) затваря TOC фазата и отваря нова секция;
- ако TOC е в
<ol>/<ul>или след TOC има не-номериран блок, TOC фазата приключва и следващата номерирана глава пак реже.
Без блок „Съдържание“ всяка номерирана глава реже директно.
Пример (atra-manual.docx / NESPERTCAM): единственото Heading 1 е заглавието на документа; главите 1–9 са Heading 2. Очакване: 10 секции — преамбюл (title + Съдържание + TOC) + по една за 1. … 9.. Не една мега-секция.
3. Word (.docx)
Обхождат се децата на w:body в document-order: параграфи и таблици (_iter_docx_blocks). Стандартният Document.paragraphs пропуска таблиците, затова не се ползва за сегментиране.
3.1. Стилово заглавие
_docx_heading_level() гледа style_id и style.name, после base_style нагоре по веригата. Токенът се нормализира (без интервали, _, -, без значение на регистъра).
Разпознават се поне:
| Токен (примери) | Ниво / роля |
|---|---|
| Heading 1 / heading1 | 1 — граница (освен ако е само корица преди TOC; виж §2) |
| Heading 2 / 3 | 2 / 3 — в тялото, освен номерирана глава N. Title |
| Title / MsoTitle / Наименование | корица — не граница |
| Subtitle / Подзаглавие | 2 — в тялото |
| Заглавие / Заглавие 1 | 1 — граница |
| Заглавие 2 / 3 | 2 / 3 — в тялото (освен номерирана глава) |
| Überschrift / Überschrift 1 | 1 — граница |
| MsoHeading1 / 2 / 3 | 1 / 2 / 3 |
Число над 3 се ограничава до 3. Subtitle / Подзаглавие без цифра → ниво 2; останалите именувани heading-стилове без цифра → ниво 1. Title / Наименование не връщат ниво за сегментиране.
Ако стилът не е heading, има fallback: Word outlineLvl 0–2 и текст под 120 символа → ниво = outlineLvl + 1.
3.2. Bold / H2 като подзаглавие
Изцяло bold кратък параграф или H2 без N. Title остава в тялото.
Ако текстът е номерирана глава (1. Преглед…), реже секция дори при Heading 2 / bold / Normal (с TOC правилата от §2.1).
3.3. Таблици и картинки
Таблица: всеки ред се сплесква до клетка | клетка | клетка в plain text; в html_text — прост <table>.
Картинки в параграф: записват се като [IMG: img_NN] в plain text и в html_text (viewer ги заменя с <img>). Поддържат се r:embed (вградени) и r:link (външни file:/// / UNC). При успешен UNC/file прочит се опакова копие в <име>_media/ до .docx; при недостъпен линк се търси sidecar (123.png, <stem>_media/, media/, images/). Липсващи пътища се логват като warning.
Rich HTML (DOCX): за всеки параграф в тялото се строи html_text от Word runs — <b> / <i> / <u> (изричен run или наследен от paragraph/character style), <span style="color:#RRGGBB"> при изричен RGB цвят, спец. символи (•, →, …) чрез HTML escape. Runs вътре в w:hyperlink (TOC редове) също влизат в html_text; ако runs липсват, има fallback към escaped para.text. Plain text остава без markup (за split / Claude). Theme/auto цветове без RGB не се записват. Viewer показва html_text когато е наличен — затова TOC/bold/картинки трябва да са в него, не само в plain text.
Празен параграф без картинка се пропуска.
3.4. Fallback
Ако след обхождането няма секции: една секция от всички непразни параграфи; ако и те са празни — от таблиците.
4. Стар .doc
Конвертира се към .docx с LibreOffice (soffice) или, на Windows, MS Word през COM. После се вика същият parse_docx. Ако и двете конверсии пропаднат, файлът се чете като обикновен текст (parse_txt).
5. HTML / HTM (вкл. Word „Запиши като уеб“)
Премахват се script, style, nav, footer, header, noscript.
Събират се top-level блокове: h1–h6, p, ul, ol, table, dl, pre, blockquote, figure, hr, самостоятелни img, и div само ако изглежда като заглавие. Вложен блок не се обработва втори път.
5.1. Заглавие
- Тагове:
h1→ граница (ниво 1);h2–h6→ в тялото освен номерирана глава. - CSS class с
heading/заглавие/msoheading/überschrift(+ опционална цифра).Title/MsoTitle/наименованиеса корица, не граница.subtitle/подзаглавие→ в тялото. pилиdivс текст < 120 символа, изцяло обвит в<b>/<strong>→ в тялото, освен номерирана глава.- Текст „Съдържание“ / Contents / TOC → в тялото + TOC фаза (§2.1).
- Номерирана глава
N. Titleнаp/h2/ bold → граница според TOC правилата.
5.2. Съдържание
Списъци и таблици влизат в текущата секция. В plain text редовете им са с нов ред (не сплескани в един ред). Декоративни атрибути (class, id, on*, data-* …) се махат; от style се пазят color, font-weight (bold) и font-style (italic). Тагове <b>/<strong>/<i>/<em> и <font color> остават.
Картинки: локални и data: URI; HTTP(S) се пропускат. Дребни иконки под 50×50 px се изхвърлят.
Ако няма нито една секция: цялото body като една секция без заглавие.
6. Обикновен текст (.txt)
Кодирането се детектира с chardet.
Ред е граница, ако след strip():
- съвпада с Markdown заглавие
#{1,3} текст— нивото е броят#, заглавието е текстът след тях; или - съвпада с номерирано заглавие:
1. …,2) …,I. …,А. …(1–2 цифри / римски / една главна буква, после.или)), дължина на реда < 120.
Всички останали редове отиват в тялото (десният край се реже, водещите интервали се пазят).
Ако няма такива редове: една секция с целия файл.
7. PDF
Няма стилове. Текстът се събира като визуални редове по Y (не по PDF stream), буквите от drop-cap се слепват, сплескани списъци от вида 1 Foo 2 Bar се разбиват на редове.
Граница: група редове с почти еднакъв размер на шрифта, ако размерът е по-голям от предишния с повече от 1 pt и текстът е под 150 символа → нова секция, ниво 2.
Това е по-ненадеждно от Word/HTML. Картинките се кропват от страницата и се редят в текущата секция по вертикална позиция.
8. Сливане на кратки секции (merge_short_sections / merge_preamble_sections)
Константа: MIN_SECTION_TOKENS = 60 (думи в тялото, str.split()).
merge_short_sections:
- Секция с непразно заглавие не се слива, дори тялото да е късо или празно.
- Секция без заглавие и с под 60 думи се залепва към предишната (текст, картинки, HTML).
- Няма предишна секция → късият untitled блок остава сам.
merge_preamble_sections (след горното):
- Ако първата секция е корица с късо/празно тяло, а втората е озаглавена „Съдържание“/TOC → сливат се в един преамбюл (title = корицата; TOC заглавието влиза в тялото).
Следствия:
- Корица + съдържание + първа глава → две секции (не четири от Title / TOC / H2-подзаглавия).
- Пълен документ с номерирани глави 1…N (дори като H2) → преамбюл + N секции.
- Последователни H1 без тяло („Глава 1“ веднага следвано от друг H1) остават две секции.
- H2 / bold подзаглавия без номерация („Какво е …?“) не режат секция.
- Untitled текст в началото на файла остава отделна секция (докато не е под прага и няма към какво да се слее — първият елемент не се слива).
При запис в process_file():
- Ако няма текст, картинки и HTML, но има заглавие → тялото става самото заглавие. Затова „голо“ заглавие оцелява като секция.
- Ако няма нито текст, нито заглавие, нито картинки → секцията се пропуска.
clean_text() срива поредици от интервали/табове, но пази нови редове (списъци и абзаци). Маха опасни C0 контроли (NUL и др.); Unicode символи (•, →) остават; NBSP става обикновен интервал.
9. Текст без заглавие
Типични случаи:
- Увод преди първата граница.
- HTML/DOCX без нито едно разпознато заглавие/номерирана глава → целият файл е една untitled секция.
- TXT без markdown/номерация.
- Къс untitled остатък след заглавие се слива с предишната секция (§8).
Празното title по-късно се попълва от класификатора (извън този документ).
10. Таблици — обобщение
| Формат | Поведение |
|---|---|
| DOCX | Редове a | b | c в текущата секция; не са граница |
| HTML | Целият <table> е блок в текущата секция |
| TXT / PDF | Няма отделен table parser; таблицата е просто текст/редове |
11. Ключови думи (един ред)
След като секциите са вече разделени, всяка получава заглавие и ключови думи чрез Claude или fallback; ръчните думи от таб „Сканиране“ се записват първи във всяка секция.