diff --git a/src/backend/schedule/parsing.py b/src/backend/schedule/parsing.py
index ef68a22..1e2888e 100644
--- a/src/backend/schedule/parsing.py
+++ b/src/backend/schedule/parsing.py
@@ -83,7 +83,9 @@ def extract_week_ranges(sheets: dict[str, Worksheet]) -> list[dict]:
return []
ranges: list[dict] = []
seen: set[tuple[str, date]] = set()
- for row in sheet.rows[1:]:
+ for row_idx, row in enumerate(sheet.rows[1:], start=1):
+ if row_idx in sheet.hidden_rows:
+ continue # скрытые строки не парсим
if not any(row[i] for i in range(4)):
continue
for parity, start_col, end_col in (("ч", 0, 1), ("з", 2, 3)):
@@ -148,7 +150,9 @@ def parse_schedule(sheets: dict[str, Worksheet]) -> dict:
parity: str | None = None
rows_in_day = 0
- for row in rows[1:]:
+ for row_idx, row in enumerate(rows[1:], start=1):
+ if row_idx in sheet.hidden_rows:
+ continue # скрытые строки не парсим
if not any(row[i] for i in range(4)):
continue
day = _normalize_header(row[_COL_DAY]).lower()
@@ -485,7 +489,7 @@ def parse_rooms_info(sheets: dict[str, Worksheet]) -> dict:
return
label = f"ауд. {m.group(1)}"
if label in info:
- return # видимый столбец важнее скрытого дубля с тем же номером
+ return # дубль с тем же номером: первый столбец важнее
text = _strip_marks(
" ".join(ln.strip() for ln in str(desc[col]).split("\n") if ln.strip())
)
@@ -500,12 +504,8 @@ def parse_rooms_info(sheets: dict[str, Worksheet]) -> dict:
"available_days": room_weekdays(text),
}
- columns = range(_FIRST_GROUP_COL, max(len(header), len(desc)))
- for col in columns: # сначала видимые
- if col not in sheet.hidden_cols:
- add(col)
- for col in columns: # потом скрытые: аудитория без видимого столбца тоже нужна
- if col in sheet.hidden_cols:
+ for col in range(_FIRST_GROUP_COL, max(len(header), len(desc))):
+ if col not in sheet.hidden_cols: # скрытое не парсим
add(col)
return info
@@ -541,6 +541,8 @@ def parse_teachers_full(sheets: dict[str, Worksheet]) -> dict:
header = sheet.rows[1]
result: dict = {}
for col in range(_FIRST_GROUP_COL, len(header)):
+ if col in sheet.hidden_cols: # скрытое не парсим
+ continue
value = _strip_marks(str(header[col]))
parts = [p.strip() for p in re.split(r"[\s\n]+", value) if p.strip()]
if not parts:
diff --git a/src/backend/schedule/xlsx.py b/src/backend/schedule/xlsx.py
index c960cfa..69740b2 100644
--- a/src/backend/schedule/xlsx.py
+++ b/src/backend/schedule/xlsx.py
@@ -14,7 +14,7 @@ import io
import re
import zipfile
import xml.etree.ElementTree as ET
-from dataclasses import dataclass
+from dataclasses import dataclass, field
from datetime import date, datetime, timedelta
from .errors import ScheduleError
@@ -32,6 +32,7 @@ _REF_RE = re.compile(r"^([A-Z]+)(\d+)$")
class Worksheet:
rows: list[list[str]]
hidden_cols: set[int]
+ hidden_rows: set[int] = field(default_factory=set)
def cell(self, row_idx: int, col_idx: int) -> str:
"""Значение ячейки, "" вне границ матрицы."""
@@ -135,11 +136,14 @@ def _parse_sheet(xml_bytes: bytes, shared: list[str]) -> Worksheet:
if col.get("hidden") == "1":
hidden_cols.update(range(c_min - 1, c_max))
+ hidden_rows: set[int] = set()
for row in root.iter(f"{{{XLSX_NS}}}row"):
row_num = row.get("r")
if not row_num:
continue
r = int(row_num) - 1
+ if row.get("hidden") == "1":
+ hidden_rows.add(r)
for cell in row.iter(f"{{{XLSX_NS}}}c"):
ref = cell.get("r")
if not ref:
@@ -194,7 +198,7 @@ def _parse_sheet(xml_bytes: bytes, shared: list[str]) -> Worksheet:
[cells.get((r, c), "") for c in range(max_col + 1)]
for r in range(max_row + 1)
]
- return Worksheet(rows=rows, hidden_cols=hidden_cols)
+ return Worksheet(rows=rows, hidden_cols=hidden_cols, hidden_rows=hidden_rows)
def _iter_text(node: ET.Element):
diff --git a/tests/test_parsing.py b/tests/test_parsing.py
index 9ceca59..e8b846e 100644
--- a/tests/test_parsing.py
+++ b/tests/test_parsing.py
@@ -302,7 +302,7 @@ def test_lowercase_except_abbreviations():
def test_room_visible_column_wins_over_hidden_duplicate():
- """У 223 в таблице два столбца: видимый (с панелью) и скрытый (без). Берём видимый."""
+ """Скрытый столбец-дубль (у 223) не парсится вовсе — берём видимый."""
from xlsx_builder import SheetSpec
spec = SheetSpec(
@@ -322,8 +322,8 @@ def test_room_visible_column_wins_over_hidden_duplicate():
assert room["type"] == "комп.класс linux/windows"
-def test_room_only_in_hidden_column_still_parsed():
- """Аудитория, у которой есть только скрытый столбец, не пропадает."""
+def test_room_only_in_hidden_column_not_parsed():
+ """Скрытое не парсим: аудитория со скрытым столбцом описания не получает."""
from xlsx_builder import SheetSpec
spec = SheetSpec(
@@ -334,5 +334,24 @@ def test_room_only_in_hidden_column_still_parsed():
hidden_cols=[(6, 7)],
)
info = parsing.parse_rooms_info(make_sheets({parsing.ROOMS_SHEET: spec}))
- assert info["ауд. 402"]["available_days"] == ["четверг", "пятница"]
- assert info["ауд. 410"]["capacity"] == 30
+ assert info == {}
+
+
+def test_hidden_rows_not_parsed(monkeypatch):
+ """Скрытые строки листа в расписание не попадают."""
+ from fixtures import fixture_sheets
+ from xlsx_builder import SheetSpec, build_workbook
+
+ sheets = fixture_sheets()
+ main = sheets[parsing.MAIN_SHEET]
+ # строки: 0 заголовок, 1 с/по, 2..5 понедельник, 6..7 вторник — скроем строку с первым вторником
+ spec = SheetSpec(
+ rows=main.rows,
+ merges=main.merges,
+ hidden_cols=main.hidden_cols,
+ hidden_rows=[6],
+ )
+ data = parsing.parse_schedule(make_sheets({parsing.MAIN_SHEET: spec}))
+ tuesday = data["ИВТ-11БО"]["days"].get("вторник", {})
+ assert not tuesday, "строка вторника скрыта — занятий быть не должно"
+ assert data["ИВТ-11БО"]["days"]["понедельник"][1]["ч"] # понедельник на месте
diff --git a/tests/xlsx_builder.py b/tests/xlsx_builder.py
index e93b0f9..bbd6a7a 100644
--- a/tests/xlsx_builder.py
+++ b/tests/xlsx_builder.py
@@ -28,6 +28,7 @@ class SheetSpec:
rows: list[list]
merges: list[str] = field(default_factory=list)
hidden_cols: list[tuple[int, int]] = field(default_factory=list) # 0-based, инклязивно
+ hidden_rows: list[int] = field(default_factory=list) # 0-based
def build_workbook(sheets: dict[str, SheetSpec]) -> bytes:
@@ -83,7 +84,8 @@ def build_workbook(sheets: dict[str, SheetSpec]) -> bytes:
t = f' t="{t_attr}"' if t_attr else ""
cells.append(f'{escape(str(v))}')
if cells:
- row_xml.append(f'{"".join(cells)}
')
+ hidden = ' hidden="1"' if (r - 1) in spec.hidden_rows else ""
+ row_xml.append(f'{"".join(cells)}
')
merges_xml = ""
if spec.merges: