Срез 2a: парсер ОСВ из XLS/XLSX/XLSB — динамическое обнаружение структуры (§1), контракт v1.1, golden на 66 реальных файлах #6

Merged
aep merged 45 commits from feat/5-slice-2a-osv-parser into main 2026-10-05 19:18:07 +03:00
Showing only changes of commit 471ae70873 - Show all commits

View File

@ -198,8 +198,8 @@ groups:
Любой реальный файл разбирается быстрее 5 с (измерено: максимум ≈ 0,6 с).
Скан объединённых ячеек xlsx (ruling R-25) повторяет то, что разложит openpyxl, с запасом в сторону отказа:
сканируются все записи zip `*.xml` и записи, начало которых похоже на XML (openpyxl идёт к листу по цели связи в
`workbook.xml.rels`, каталог и расширение части не проверяет); считается элемент с локальным именем `mergeCell` в любом
сканируются все записи zip `*.xml` и записи, первые 64 байта которых (после BOM и пробелов) начинаются с `<` (openpyxl идёт к
листу по цели связи в `workbook.xml.rels`, расширение части не проверяет); считается элемент с локальным именем `mergeCell` в любом
пространстве имён (openpyxl берёт дочерние `mergeCell` по локальному имени) и только атрибут `ref` без префикса;
ссылки на символы (`&#50;`) раскрывает парсер, `>` в кавычках тег не обрывает; площадь — по строке `ref` (`CellRange`
openpyxl), ячейки не перебираются; отказ — сразу при превышении. Битый XML части `*.xml` и `ref`, который openpyxl не
@ -215,8 +215,18 @@ openpyxl 0,58 с; синтетический лист в 2 млн ячеек (63
- XML-бомбы entity expansion: `defusedxml` не подключён; от экспоненты «billion laughs» защищает только сам expat ≥ 2.4
(и в скане, и в openpyxl, который разбирает части тем же expat).
Жёсткий лимит памяти контейнера, `defusedxml` или разбор в подпроцессе с `RLIMIT_AS` — решения платформы. Книга в
пределах лимитов читается библиотекой целиком.
- **известный обход предсканирования (R-26):** предскан — эшелон защиты, а не граница безопасности. Намеренно собранный
файл, у которого лист лежит в части без расширения `.xml`, начинается с ≥ 64 байт пробелов (или BOM UTF-16 и пробелов) и
не имеет XML-декларации, пропускает предскан: файл в 1,8 КБ читается `read_book` 27 с и занимает 1,4 ГБ. Первый
follow-up: убрать «sniff» и лениво отдавать expat каждую некаталожную запись архива (+ тест);
- **память и CPU самого предскана** не ограничены размером чанка: «мусорная» часть `xl/junk.xml` с одним атрибутом в
400 МБ даёт 1,3 ГБ за 1,2 с, 400 МБ тегов `<a/>` — 8,5 с CPU (в пределах лимита распакованного zip, 500 МиБ);
- **xls с очень многим числом крупных merged-диапазонов** (каждый площадью > 10 000, например 100 000 штук): индекс
`Sheet` просматривает их линейно (≈ 4,4 мс на обращение), останавливает только `PARSE_TIMEOUT_SECONDS` (`TIMEOUT`).
Follow-up: потолок на число таких диапазонов или интервальный индекс.
Жёсткий лимит памяти контейнера, `defusedxml` или разбор в подпроцессе с `RLIMIT_AS` — решения платформы (они же —
настоящая граница безопасности чтения). Книга в пределах лимитов читается библиотекой целиком.
## Тесты на реальных данных и golden-снимки