.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Разделяне на произволен текст на секции
|
||||
|
||||
Този документ описва **реалното** поведение на RIP Help System при разделяне на help-файл на секции. Източникът е `help_processor.py` след последните корекции (вкл. `merge_short_sections` / `merge_preamble_sections`). Не е маркетингово резюме.
|
||||
Този документ описва **реалното** поведение на RIP Help System при разделяне на help-файл на секции. Източникът е `help_processor.py` след последните корекции (вкл. `merge_short_sections` / `merge_preamble_sections` / номерирани глави). Не е маркетингово резюме.
|
||||
|
||||
Генерирането на ключови думи е **извън обхвата**, освен един ред в края.
|
||||
|
||||
@@ -29,22 +29,40 @@ ZIP не е формат за разделяне: разопакова се и
|
||||
|
||||
## 2. Какво е граница на секция
|
||||
|
||||
**Граница = открито заглавие ниво 1** (H1 / Heading 1 / Заглавие 1) в HTML и DOCX. Текущата секция се затваря (`flush`) и започва нова. Текстът на заглавието става `title` и **не** се копира в тялото на секцията.
|
||||
В HTML и DOCX граница е едно от:
|
||||
|
||||
1. **H1 / Heading 1 / Заглавие 1** (класическа граница); **или**
|
||||
2. **Номерирана top-level глава** от вида `1. Заглавие` / `2) Заглавие` — дори ако е H2, bold или Normal — когато редът изглежда като заглавие на глава (виж §2.1).
|
||||
|
||||
Текстът на границата става `title` и **не** се копира в тялото на секцията.
|
||||
|
||||
В HTML/DOCX **не** отварят нова секция:
|
||||
|
||||
- Word **Title** / **Наименование** / CSS `Title` / `MsoTitle` — корица; става заглавие на преамбюла (или ред в тялото, ако вече има съдържание);
|
||||
- „**Съдържание**“ / Contents / TOC и еквиваленти — остават в тялото;
|
||||
- H2–H6, Subtitle / Подзаглавие, изцяло bold параграфи — влизат като редове в **текущата** секция.
|
||||
- „**Съдържание**“ / Contents / TOC и еквиваленти — остават в тялото и включват TOC фаза (виж §2.1);
|
||||
- H2–H6, Subtitle / Подзаглавие, изцяло bold параграфи — **освен** ако текстът е номерирана глава (`N. Title`);
|
||||
- редове от таблици (`a | b | c`), „Фигура N: …“, дълги изречения с номерация.
|
||||
|
||||
TXT и PDF запазват своите правила (номерирани редове / по-голям шрифт) — там ниво 2 е нормална граница.
|
||||
|
||||
Всичко, което не е граница — параграфи, списъци, таблици, картинки — се добавя към **текущата** секция.
|
||||
|
||||
Ако преди първото H1 има съдържание (корица, TOC), то образува преамбюл секция (с title от корицата, ако има).
|
||||
Ако преди първата глава има корица + съдържание, те образуват **преамбюл** (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)
|
||||
@@ -59,12 +77,12 @@ TXT и PDF запазват своите правила (номерирани р
|
||||
|
||||
| Токен (примери) | Ниво / роля |
|
||||
|---|---|
|
||||
| Heading 1 / heading1 | 1 — **граница** |
|
||||
| Heading 2 / 3 | 2 / 3 — в тялото, не граница |
|
||||
| Heading 1 / heading1 | 1 — **граница** (освен ако е само корица преди TOC; виж §2) |
|
||||
| Heading 2 / 3 | 2 / 3 — в тялото, **освен** номерирана глава `N. Title` |
|
||||
| **Title** / MsoTitle / **Наименование** | корица — не граница |
|
||||
| Subtitle / Подзаглавие | 2 — в тялото |
|
||||
| **Заглавие** / **Заглавие 1** | 1 — **граница** |
|
||||
| Заглавие 2 / 3 | 2 / 3 — в тялото |
|
||||
| Заглавие 2 / 3 | 2 / 3 — в тялото (освен номерирана глава) |
|
||||
| Überschrift / Überschrift 1 | 1 — **граница** |
|
||||
| MsoHeading1 / 2 / 3 | 1 / 2 / 3 |
|
||||
|
||||
@@ -72,16 +90,11 @@ TXT и PDF запазват своите правила (номерирани р
|
||||
|
||||
Ако стилът не е heading, има fallback: Word `outlineLvl` 0–2 **и** текст под 120 символа → ниво = `outlineLvl + 1`.
|
||||
|
||||
### 3.2. Bold като подзаглавие (не граница)
|
||||
### 3.2. Bold / H2 като подзаглавие
|
||||
|
||||
Ако няма стилово ниво 1, параграфът се приема за **текст в тялото** (бивш „heading“ ниво 2), когато:
|
||||
Изцяло bold кратък параграф или H2 **без** `N. Title` остава в тялото.
|
||||
|
||||
- видимият текст е непразен и ≤ 120 символа;
|
||||
- всички run-ове с текст са **bold**;
|
||||
- стилът **не** започва с `list`;
|
||||
- в параграфа **няма** картинки.
|
||||
|
||||
Такъв ред **не** отваря нова секция — остава под текущото H1.
|
||||
Ако текстът е номерирана глава (`1. Преглед…`), реже секция дори при Heading 2 / bold / Normal (с TOC правилата от §2.1).
|
||||
|
||||
### 3.3. Таблици и картинки
|
||||
|
||||
@@ -111,10 +124,11 @@ TXT и PDF запазват своите правила (номерирани р
|
||||
|
||||
### 5.1. Заглавие
|
||||
|
||||
1. Тагове: `h1`→ граница (ниво 1); `h2`–`h6`→ в тялото (ниво 2/3).
|
||||
1. Тагове: `h1`→ граница (ниво 1); `h2`–`h6`→ в тялото **освен** номерирана глава.
|
||||
2. CSS class с `heading` / `заглавие` / `msoheading` / `überschrift` (+ опционална цифра). `Title` / `MsoTitle` / `наименование` са **корица**, не граница. `subtitle` / `подзаглавие` → в тялото.
|
||||
3. `p` или `div` с текст < 120 символа, изцяло обвит в `<b>` / `<strong>` → в тялото (не граница).
|
||||
4. Текст „Съдържание“ / Contents / TOC (дори като `h1`) → в тялото на преамбюла.
|
||||
3. `p` или `div` с текст < 120 символа, изцяло обвит в `<b>` / `<strong>` → в тялото, **освен** номерирана глава.
|
||||
4. Текст „Съдържание“ / Contents / TOC → в тялото + TOC фаза (§2.1).
|
||||
5. Номерирана глава `N. Title` на `p` / `h2` / bold → граница според TOC правилата.
|
||||
|
||||
### 5.2. Съдържание
|
||||
|
||||
@@ -167,9 +181,10 @@ TXT и PDF запазват своите правила (номерирани р
|
||||
|
||||
Следствия:
|
||||
|
||||
- Корица + съдържание + първа глава (H1) → **две** секции (не четири от Title / TOC / H1 / H2).
|
||||
- Корица + съдържание + първа глава → **две** секции (не четири от Title / TOC / H2-подзаглавия).
|
||||
- Пълен документ с номерирани глави 1…N (дори като H2) → преамбюл + N секции.
|
||||
- Последователни H1 без тяло („Глава 1“ веднага следвано от друг H1) остават **две** секции.
|
||||
- H2 / bold подзаглавия („Какво е …?“) не режат секция — остават в тялото на текущото H1.
|
||||
- H2 / bold подзаглавия без номерация („Какво е …?“) не режат секция.
|
||||
- Untitled текст в началото на файла остава отделна секция (докато не е под прага *и* няма към какво да се слее — първият елемент не се слива).
|
||||
|
||||
При запис в `process_file()`:
|
||||
@@ -185,8 +200,8 @@ TXT и PDF запазват своите правила (номерирани р
|
||||
|
||||
Типични случаи:
|
||||
|
||||
- Увод преди първия Heading 1.
|
||||
- HTML/DOCX без нито едно разпознато заглавие → целият файл е една untitled секция.
|
||||
- Увод преди първата граница.
|
||||
- HTML/DOCX без нито едно разпознато заглавие/номерирана глава → целият файл е една untitled секция.
|
||||
- TXT без markdown/номерация.
|
||||
- Къс untitled остатък след заглавие се слива с предишната секция (§8).
|
||||
|
||||
|
||||
Binary file not shown.
@@ -605,6 +605,76 @@ def _is_toc_heading(text: str) -> bool:
|
||||
return bool(_TOC_HEADING_RE.match((text or "").strip()))
|
||||
|
||||
|
||||
# Top-level chapter: "1. Title" / "2) Title" — short line, not body/table/figure.
|
||||
_NUMBERED_CHAPTER_RE = re.compile(
|
||||
r"^(\d{1,2})[\.\)]\s+(\S.{0,100})$"
|
||||
)
|
||||
_FIGURE_CAPTION_RE = re.compile(
|
||||
r"^(фигура|figure|abb\.?|рис\.?)\s*\d+",
|
||||
re.I,
|
||||
)
|
||||
|
||||
|
||||
def _normalize_chapter_key(text: str) -> str:
|
||||
return re.sub(r"\s+", " ", (text or "").strip().lower())
|
||||
|
||||
|
||||
def _is_numbered_chapter_heading(text: str) -> bool:
|
||||
"""True for top-level numbered chapter titles (not TOC prose, figures, tables)."""
|
||||
t = (text or "").strip()
|
||||
if not t or len(t) >= 120:
|
||||
return False
|
||||
if "|" in t:
|
||||
return False
|
||||
if _is_toc_heading(t):
|
||||
return False
|
||||
if _FIGURE_CAPTION_RE.match(t):
|
||||
return False
|
||||
m = _NUMBERED_CHAPTER_RE.match(t)
|
||||
if not m:
|
||||
return False
|
||||
rest = m.group(2).strip()
|
||||
words = rest.split()
|
||||
if not words or len(words) > 12:
|
||||
return False
|
||||
# Body-like numbered sentences: "1. Copy the files into the folder."
|
||||
if rest.endswith((".", "!", "?")) and len(words) > 4:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class _TocSplitState:
|
||||
"""Tracks Съдържание / TOC so first '1. Title' stays in preamble, second starts a section."""
|
||||
|
||||
__slots__ = ("phase", "had_toc", "titles")
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.phase = False
|
||||
self.had_toc = False
|
||||
self.titles: set[str] = set()
|
||||
|
||||
def note_toc_heading(self) -> None:
|
||||
self.phase = True
|
||||
self.had_toc = True
|
||||
|
||||
def leave_phase(self) -> None:
|
||||
self.phase = False
|
||||
|
||||
def handle_numbered(self, text: str) -> str:
|
||||
"""Return 'toc' (keep in body), 'split' (new section), or 'no' (not numbered chapter)."""
|
||||
if not _is_numbered_chapter_heading(text):
|
||||
return "no"
|
||||
key = _normalize_chapter_key(text)
|
||||
if self.phase:
|
||||
if key in self.titles:
|
||||
self.phase = False
|
||||
return "split"
|
||||
self.titles.add(key)
|
||||
return "toc"
|
||||
# After TOC list/block, or docs without TOC: numbered chapter opens a section.
|
||||
return "split"
|
||||
|
||||
|
||||
def _heading_level_from_token(token: str) -> Optional[int]:
|
||||
t = _compact_style_token(token)
|
||||
if not t:
|
||||
@@ -779,6 +849,7 @@ def parse_html(path: Path) -> list[Section]:
|
||||
sec_html: list[str] = []
|
||||
sec_images: list[ImageRef] = []
|
||||
img_counter = [0]
|
||||
toc_state = _TocSplitState()
|
||||
|
||||
def flush():
|
||||
if current_title or sec_text or sec_html or sec_images:
|
||||
@@ -798,6 +869,13 @@ def parse_html(path: Path) -> list[Section]:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def start_section(title: str, level: int = 1):
|
||||
nonlocal current_title, current_level, sec_text, sec_html, sec_images
|
||||
flush()
|
||||
current_title = title
|
||||
current_level = level
|
||||
sec_text, sec_html, sec_images = [], [], []
|
||||
|
||||
for el in blocks:
|
||||
txt = el.get_text(" ", strip=True)
|
||||
is_cover = _html_is_cover_title(el)
|
||||
@@ -809,23 +887,42 @@ def parse_html(path: Path) -> list[Section]:
|
||||
current_title = txt
|
||||
current_level = 1
|
||||
else:
|
||||
if toc_state.phase:
|
||||
toc_state.leave_phase()
|
||||
append_block_as_body(el)
|
||||
continue
|
||||
|
||||
if txt and _is_toc_heading(txt):
|
||||
toc_state.note_toc_heading()
|
||||
append_block_as_body(el)
|
||||
continue
|
||||
|
||||
# Номерирана глава (H1/H2/bold/plain) — с TOC: първото срещане в съдържанието остава в преамбюла
|
||||
numbered_action = toc_state.handle_numbered(txt) if txt else "no"
|
||||
if numbered_action == "toc":
|
||||
append_block_as_body(el)
|
||||
continue
|
||||
if numbered_action == "split":
|
||||
start_section(txt, heading_lvl or 1)
|
||||
continue
|
||||
|
||||
if heading_lvl:
|
||||
if not txt:
|
||||
continue
|
||||
# TOC и H2+ остават в тялото — граница само при H1 / Заглавие 1
|
||||
if _is_toc_heading(txt) or heading_lvl >= 2:
|
||||
# H2+ без номерация остават в тялото; H1 / Заглавие 1 режат
|
||||
if heading_lvl >= 2:
|
||||
if toc_state.phase:
|
||||
toc_state.leave_phase()
|
||||
append_block_as_body(el)
|
||||
continue
|
||||
flush()
|
||||
current_title = txt
|
||||
current_level = heading_lvl
|
||||
sec_text, sec_html, sec_images = [], [], []
|
||||
if toc_state.phase:
|
||||
toc_state.leave_phase()
|
||||
start_section(txt, heading_lvl)
|
||||
continue
|
||||
|
||||
if el.name == "img":
|
||||
if toc_state.phase:
|
||||
toc_state.leave_phase()
|
||||
# самостоятелен <img> (не вътре в блок)
|
||||
_swap_imgs_in_block(el.parent if el.parent and el.parent.name else el,
|
||||
base_dir, sec_images, img_counter)
|
||||
@@ -836,6 +933,11 @@ def parse_html(path: Path) -> list[Section]:
|
||||
sec_html.append(f"<p>{img_txt}</p>")
|
||||
continue
|
||||
|
||||
if toc_state.phase and (el.name or "").lower() in _HTML_PLAIN_NL_TAGS:
|
||||
# <ol>/<ul> TOC списък — остава в преамбюла, приключва TOC фазата
|
||||
toc_state.leave_phase()
|
||||
elif toc_state.phase and txt and not _is_numbered_chapter_heading(txt):
|
||||
toc_state.leave_phase()
|
||||
append_block_as_body(el)
|
||||
|
||||
flush()
|
||||
@@ -911,6 +1013,7 @@ def parse_docx(path: Path) -> list[Section]:
|
||||
buf: list[str] = []
|
||||
sec_images: list[ImageRef] = []
|
||||
img_counter = [0]
|
||||
toc_state = _TocSplitState()
|
||||
|
||||
def flush():
|
||||
if current_title or buf or sec_images:
|
||||
@@ -918,8 +1021,26 @@ def parse_docx(path: Path) -> list[Section]:
|
||||
sec.images = list(sec_images)
|
||||
sections.append(sec)
|
||||
|
||||
def append_para(text: str, para_imgs: list[ImageRef]):
|
||||
if text:
|
||||
buf.append(text)
|
||||
for im in para_imgs:
|
||||
img_counter[0] += 1
|
||||
im.placeholder = f"img_{img_counter[0]:02d}"
|
||||
sec_images.append(im)
|
||||
buf.append(f"[IMG: {im.placeholder}]")
|
||||
|
||||
def start_section(title: str, level: int = 1):
|
||||
nonlocal current_title, current_level, buf, sec_images
|
||||
flush()
|
||||
buf, sec_images = [], []
|
||||
current_title = title
|
||||
current_level = level
|
||||
|
||||
for kind, block in _iter_docx_blocks(doc):
|
||||
if kind == "tbl":
|
||||
if toc_state.phase:
|
||||
toc_state.leave_phase()
|
||||
for line in _table_lines(block):
|
||||
buf.append(line)
|
||||
continue
|
||||
@@ -948,48 +1069,40 @@ def parse_docx(path: Path) -> list[Section]:
|
||||
current_title = text
|
||||
current_level = 1
|
||||
else:
|
||||
buf.append(text)
|
||||
for im in para_imgs:
|
||||
img_counter[0] += 1
|
||||
im.placeholder = f"img_{img_counter[0]:02d}"
|
||||
sec_images.append(im)
|
||||
buf.append(f"[IMG: {im.placeholder}]")
|
||||
if toc_state.phase:
|
||||
toc_state.leave_phase()
|
||||
append_para(text, para_imgs)
|
||||
continue
|
||||
|
||||
# TOC и H2+/bold остават в тялото — граница само при Heading 1 / Заглавие 1
|
||||
if text and _is_toc_heading(text):
|
||||
buf.append(text)
|
||||
for im in para_imgs:
|
||||
img_counter[0] += 1
|
||||
im.placeholder = f"img_{img_counter[0]:02d}"
|
||||
sec_images.append(im)
|
||||
buf.append(f"[IMG: {im.placeholder}]")
|
||||
toc_state.note_toc_heading()
|
||||
append_para(text, para_imgs)
|
||||
continue
|
||||
|
||||
numbered_action = toc_state.handle_numbered(text) if text else "no"
|
||||
if numbered_action == "toc":
|
||||
append_para(text, para_imgs)
|
||||
continue
|
||||
if numbered_action == "split":
|
||||
start_section(text, level or 1)
|
||||
continue
|
||||
|
||||
# H2+/bold без номерация на глава — в тялото
|
||||
if (level or 0) >= 2 or is_bold_heading:
|
||||
if text:
|
||||
buf.append(text)
|
||||
for im in para_imgs:
|
||||
img_counter[0] += 1
|
||||
im.placeholder = f"img_{img_counter[0]:02d}"
|
||||
sec_images.append(im)
|
||||
buf.append(f"[IMG: {im.placeholder}]")
|
||||
if toc_state.phase:
|
||||
toc_state.leave_phase()
|
||||
append_para(text, para_imgs)
|
||||
continue
|
||||
|
||||
if level == 1:
|
||||
flush()
|
||||
buf, sec_images = [], []
|
||||
current_title = text
|
||||
current_level = 1
|
||||
if toc_state.phase:
|
||||
toc_state.leave_phase()
|
||||
start_section(text, 1)
|
||||
continue
|
||||
|
||||
if text:
|
||||
buf.append(text)
|
||||
for im in para_imgs:
|
||||
img_counter[0] += 1
|
||||
im.placeholder = f"img_{img_counter[0]:02d}"
|
||||
sec_images.append(im)
|
||||
buf.append(f"[IMG: {im.placeholder}]")
|
||||
if toc_state.phase and text and not _is_numbered_chapter_heading(text):
|
||||
toc_state.leave_phase()
|
||||
append_para(text, para_imgs)
|
||||
|
||||
flush()
|
||||
|
||||
|
||||
25
tests/fixtures/nespertcam_launcher_chapters.html
vendored
Normal file
25
tests/fixtures/nespertcam_launcher_chapters.html
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><meta charset="utf-8"></head>
|
||||
<body>
|
||||
<!-- Като atra-manual.docx: единственото H1 е заглавието; главите са H2 с номерация. -->
|
||||
<h1>NESPERTCAM Launcher - Пълно ръководство на потребителя</h1>
|
||||
<h3>Съдържание</h3>
|
||||
<p>1. Преглед на приложението</p>
|
||||
<p>2. Инсталация и настройка</p>
|
||||
<p>3. Конфигурация и настройки</p>
|
||||
<h2>1. Преглед на приложението</h2>
|
||||
<h3>Какво е NESPERTCAM Launcher?</h3>
|
||||
<p>NESPERTCAM Launcher е специализирано Windows приложение за стартиране на NESPERTCAM64.exe.</p>
|
||||
<p><b>Основни предимства</b></p>
|
||||
<p>Преносимост и пълен екран интерфейс.</p>
|
||||
<p>Фигура 1: Главен интерфейс на NESPERTCAM Launcher с пълен екран режим</p>
|
||||
<h2>2. Инсталация и настройка</h2>
|
||||
<h3>Системни изисквания</h3>
|
||||
<table>
|
||||
<tr><td>Компонент</td><td>Минимални изисквания</td><td>Препоръчителни</td></tr>
|
||||
<tr><td>Операционна система</td><td>Windows 10</td><td>Windows 11</td></tr>
|
||||
</table>
|
||||
<p>Стъпки за инсталация следват тук с достатъчно текст за тяло на секцията.</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Корица + Съдържание + глава 1 → 2 секции (не 4)."""
|
||||
"""Корица + Съдържание + глави: преамбюл + по една секция на номерирана глава."""
|
||||
from pathlib import Path
|
||||
|
||||
from help_processor import (
|
||||
@@ -8,7 +8,9 @@ from help_processor import (
|
||||
Section,
|
||||
)
|
||||
|
||||
FIXTURE = Path(__file__).parent / "fixtures" / "nespertcam_launcher_preamble.html"
|
||||
FIXTURES = Path(__file__).parent / "fixtures"
|
||||
PREAMBLE = FIXTURES / "nespertcam_launcher_preamble.html"
|
||||
CHAPTERS = FIXTURES / "nespertcam_launcher_chapters.html"
|
||||
|
||||
|
||||
def _titles(sections):
|
||||
@@ -16,7 +18,7 @@ def _titles(sections):
|
||||
|
||||
|
||||
def test_nespertcam_preamble_yields_two_sections():
|
||||
sections = merge_preamble_sections(merge_short_sections(parse_html(FIXTURE)))
|
||||
sections = merge_preamble_sections(merge_short_sections(parse_html(PREAMBLE)))
|
||||
assert len(sections) == 2
|
||||
assert sections[0].title == "NESPERTCAM Launcher - Пълно ръководство на потребителя"
|
||||
assert sections[1].title == "1. Преглед на приложението"
|
||||
@@ -26,8 +28,27 @@ def test_nespertcam_preamble_yields_two_sections():
|
||||
assert "NESPERTCAM64.exe" in sections[1].text
|
||||
|
||||
|
||||
def test_nespertcam_h2_chapters_split_not_one_section():
|
||||
"""Като atra-manual.docx: H1=корица, TOC като Normal, глави като H2 → ≥3 секции."""
|
||||
sections = merge_preamble_sections(merge_short_sections(parse_html(CHAPTERS)))
|
||||
assert len(sections) >= 3
|
||||
assert sections[0].title == "NESPERTCAM Launcher - Пълно ръководство на потребителя"
|
||||
assert "Съдържание" in sections[0].text
|
||||
assert "1. Преглед на приложението" in sections[0].text
|
||||
assert sections[1].title == "1. Преглед на приложението"
|
||||
assert sections[2].title == "2. Инсталация и настройка"
|
||||
assert "Какво е NESPERTCAM Launcher?" in sections[1].text
|
||||
assert "Основни предимства" in sections[1].text
|
||||
assert "Фигура 1:" in sections[1].text
|
||||
assert "Системни изисквания" in sections[2].text
|
||||
assert "Windows 10" in sections[2].text
|
||||
# H3/bold/фигура не отварят собствени секции
|
||||
assert "Какво е NESPERTCAM Launcher?" not in _titles(sections)
|
||||
assert "Основни предимства" not in _titles(sections)
|
||||
|
||||
|
||||
def test_h2_and_bold_do_not_split_inside_chapter():
|
||||
html = Path(__file__).parent / "fixtures" / "_tmp_h2.html"
|
||||
html = FIXTURES / "_tmp_h2.html"
|
||||
html.write_text(
|
||||
"""<!DOCTYPE html><html><body>
|
||||
<h1>Глава А</h1>
|
||||
@@ -64,7 +85,7 @@ def test_toc_heading_as_h1_merges_into_cover():
|
||||
|
||||
|
||||
def test_heading1_chapters_still_split():
|
||||
html = Path(__file__).parent / "fixtures" / "_tmp_chapters.html"
|
||||
html = FIXTURES / "_tmp_chapters.html"
|
||||
html.write_text(
|
||||
"""<!DOCTYPE html><html><body>
|
||||
<h1>1. Първа</h1><p>Аа аа аа.</p>
|
||||
|
||||
Reference in New Issue
Block a user