Files
obsidian-vault/personal/tech/bilety-oxota-interaktiv.md
T

86 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
created: 2026-08-24
tags: [html, quiz, tilda, static-page, python, eagle-dash]
updated: 2026-08-24
---
# Билеты охотника — статичный интерактив (a1-tir.ru/bilety → bilety.html)
## Итог (2026-08-24)
Скачал `https://a1-tir.ru/bilety` (билеты для экзамена на гражданское оружие/охотничий минимум) и превратил в **статичную страницу с кнопками-ответами, вшитыми в HTML** (БЕЗ JS-скрипта-генератора). Задеплоено в Eagle Dashboard Pages: `/Library/WebServer/Documents/bilety.html` (≈902 КБ, 902285 байт), URL `/local-pages/bilety.html`, вкладка Pages.
- **181 карточка-вопрос, 543 кнопки-ответа** — **все вопросы** покрыты (1.1→1.93, 2.1→2.42, 58→103), каждая карточка ровно 3 кнопки. Нет дублей и пропусков.
- Каждый ответ — `<button class="whale-a" data-correct="N" data-n="M" onclick="...">`.
- Номер правильного ответа **скрыт** — живёт только в `data-correct`, не рендерится на экран.
- Клик (инлайн `onclick` в каждой кнопке): выбрал неверно → кнопка красная `wrong` + правильная подсвечивается зелёной `correct`; верно → зелёная.
- CSS (`.whale-answers`, `.whale-a`, `.correct`, `.wrong`) вшит в `<head>` страницы.
### Восстановление потерянных вопросов (2026-08-24, исправление)
Изначально генератор давал **176 карточек** (терял 5 вопросов). Причины потери и фикс:
1. **Номер правильного в нестандартной обёртке** — парсер ждал только простой `<em>N</em>`, а у части вопросов был `<strong><em data-redactor-tag="em">N</em></strong>` (вопросы 81, 83, 94) или `<p style="text-align:center;"><em>N</em></p>` (1.93, последний в разделе). Вопрос без распознанного `correct` отбрасывался (`flush()` требует `cur["c"] is not None`).
2. **Вопрос без `<strong>`-обёртки** — вопрос 2.16 («Отдачей оружия называется:») в оригинале записан голым текстом «`2.16. …`» без `<strong>`. Парсер не распознал его как новый вопрос и **склеил его ответы с карточкой 2.15** (у 2.15 вышло **7 кнопок** вместо 3, а 2.16 вообще пропал как отдельный вопрос).
**Как исправлено** (точечные скрипты в `~/Downloads/`, без переписывания gen2):
- `gen_fix.py` — вставляет 4 карточки (1.93, 81, 83, 94) в правильные позиции **после своих предшественников** (по поиску границ соседних карточек `<div class="whale-q">`, сортировка вставок по убыванию позиции, чтобы не сдвигать индексы).
- Дальше обнаружен и 2.16 → `fix216.py` заменяет сегмент карточки 2.15 (по абсолютным индексам от находки `2.15.` до начала `2.17.`) на корректные 2.15 (3 кнопки) + 2.16 (3 кнопки, correct=2).
**Pitfall при точечной вставке**: первый вариант regex-патча (`.*?</div>...`) оказался слишком жадным и удалил соседние карточки (181→155). **Надёжно — по границам**: карточки в файле идут подряд без разделителя, конец одной = начало следующей `<div class="whale-q">`. Вставлять по позициям, а не regex-далящим патчем.
**Как проверял полноту** (главный урок): не полагаться на «~180», а сверять множества номеров оригинала vs сгенерированного:
```python
orig_nums = set(...) # номера из <strong>N. и голых N. в оригинале
gen_nums = set(re.findall(r'whale-q-text">(\d+(?:\.\d+)?\.)\s', gen))
missing = orig_nums - gen_nums
```
Номера «1.», «2.», «3.» в результатах — это **ложные вхождения** (номера ответов, не вопросов), их отфильтровывать по наличию точки-разделителя (`'.' in n`).
## Универсальная структура исходника (Tilda text-блок)
Одна строка-контейнер на раздел, `div field="text" class="t-text t-text_md ">` (4 таких блока на странице). Внутри:
```
<p style="text-align:center;"><strong>Заголовок раздела</strong></p> (не всегда)
<strong>1.1. Текст вопроса</strong>
<br/>1. Вариант1
<br/>2. Вариант2
<br/>3. Вариант3
<br/><em>2</em> ← номер правильного (скрывать)
<br/><strong>&nbsp;</strong><br/>
<strong>1.2. ...</strong>
...
```
- Вопрос может занимать несколько подряд идущих `<strong>` (фрагмент без номера — продолжение).
- Ответы — текстовые узлы после `</strong>` между `<br/>`, каждый начинается с `N. `.
- `<em>N</em>` — чистый номер правильного. Исключение: `<em><u>Примечание</u>: …</em>` — это примечание, не номер.
- Мусор: `<strong>&nbsp;</strong>`, `<em>&nbsp;</em>`, пустые `<em>`/`<strong data-redactor-tag="strong">`.
## Генератор: `~/Downloads/gen2.py`
Python (без внешних зависимостей), превращает `origen_raw.html``bilety.html`.
```bash
python3 gen2.py # вывод: containers=4 cards=176 buttons=532 bytes=816979
```
Ключевые моменты:
- `find_end(html, start)` — баланс `<div>`/`</div>` от индекса открывающего `<div field="text">` до его закрытия.
- `transform_container`**сначала вырезает `<p[^>]*>.*?</p>`** (заголовки разделов). Это критично: без этого блоки с `<p>`-заголовком (раздел «Правовая подготовка» и «Огневая подготовка») давали 0 карточек, вопросы терялись целиком. Убирание заголовков заставляет обрабатывать каждый `<strong>` единообразно (как в блоках без заголовка).
- `_scan(inner)` — токенизация текста/тегов; каждый `N. `-ответ → `cur["a"]`; `<em>`-цифра → `cur["c"]` (правильный); strong без номера при пустых ответах → мержится в текущий вопрос.
- `_btn(num, text, correct)` — инлайн `onclick`, красящий красный/зелёный.
## Pitfalls
1. **Блоки с `<p>`-заголовком**: если не вырезать `<p>.../p>` первым шагом, такие блоки дают 0 карточек (вопросы теряются). Симптом: в собранном файле `cards` вдвое меньше (~86 вместо ~180).
2. **Скрытие номера**: не выводить `<em>` в разметку — только `data-correct` на кнопке.
3. **`grep -c` по однострочному HTML** вернёт 1 (файл — одна гигантская строка), даже если совпадений сотни. Считать через `python` (`s.count(...)`), не через grep.
4. **Файл после генерации большой** (~894 КБ): на каждую кнопку инлайн-`onclick` — приемлемо для статики.
5. Node парсеры/`node --max-old-space-size` тут НЕ нужны — на больших минифицированных Tilda-файлах node легко падает в OOM (heap limit) при самопальных токенизаторах. Python + `str.find`/`regex` надёжнее и не жрёт память.
## Деплой в Eagle Dashboard Pages
- Механика + права — в `personal/projects/personal-os/eagle-dashboard.md` (секция «Как добавить страницу в Pages»).
- Скопировать `bilety.html` в `/Library/WebServer/Documents/` (root-owned, нужен sudo) → страница подхватывается живьём, рестарт дашборда не нужен.