86 lines
9.0 KiB
Markdown
86 lines
9.0 KiB
Markdown
---
|
||
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> </strong><br/>
|
||
<strong>1.2. ...</strong>
|
||
...
|
||
```
|
||
|
||
- Вопрос может занимать несколько подряд идущих `<strong>` (фрагмент без номера — продолжение).
|
||
- Ответы — текстовые узлы после `</strong>` между `<br/>`, каждый начинается с `N. `.
|
||
- `<em>N</em>` — чистый номер правильного. Исключение: `<em><u>Примечание</u>: …</em>` — это примечание, не номер.
|
||
- Мусор: `<strong> </strong>`, `<em> </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) → страница подхватывается живьём, рестарт дашборда не нужен.
|