fix: Word разбивает маркеры циклов/переменные на run'ы + адрес ИС
- _normalize_loop_markers: склейка текста параграфа в один run перед
поиском <!-- loop:... --> и {{item.*}}, иначе циклы не разворачивались
(шаблон '6. Акт определения уровня защищенности ПДн.docx')
- variables.py: поле address в цикле information_systems
This commit is contained in:
@@ -68,6 +68,59 @@ OLD_VAR_MAP = {
|
|||||||
# 2. ОБРАБОТКА ЦИКЛОВ НА УРОВНЕ ZIP/XML
|
# 2. ОБРАБОТКА ЦИКЛОВ НА УРОВНЕ ZIP/XML
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
|
W_NS = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_loop_markers(doc_xml: str) -> str:
|
||||||
|
"""Word разбивает маркеры <!-- loop:... --> и переменные {{item.x}}
|
||||||
|
на несколько run'ов (например '<!-- ' в одном <w:t>, 'loop:info' в другом,
|
||||||
|
'rmation -->' в третьем). Склеиваем текст каждого параграфа в один run,
|
||||||
|
чтобы маркеры и переменные циклов снова стали едиными."""
|
||||||
|
try:
|
||||||
|
root = etree.fromstring(doc_xml.encode('utf-8'))
|
||||||
|
except Exception:
|
||||||
|
return doc_xml
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
for p in root.iter(f'{{{W_NS}}}p'):
|
||||||
|
# Собираем весь текст параграфа (по всем run'ам)
|
||||||
|
texts = []
|
||||||
|
for t in p.iter(f'{{{W_NS}}}t'):
|
||||||
|
texts.append(t.text or '')
|
||||||
|
full = ''.join(texts)
|
||||||
|
|
||||||
|
# Интересуют только параграфы с маркерами циклов ИЛИ переменными цикла
|
||||||
|
if 'loop:' not in full and 'loop_end' not in full and '{{item.' not in full:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Сохраняем форматирование первого run
|
||||||
|
first_r = p.find(f'{{{W_NS}}}r')
|
||||||
|
rpr = None
|
||||||
|
if first_r is not None:
|
||||||
|
rpr_el = first_r.find(f'{{{W_NS}}}rPr')
|
||||||
|
if rpr_el is not None:
|
||||||
|
rpr = etree.fromstring(etree.tostring(rpr_el))
|
||||||
|
|
||||||
|
# Оставляем только pPr, удаляем все run'ы
|
||||||
|
for child in list(p):
|
||||||
|
if child.tag != f'{{{W_NS}}}pPr':
|
||||||
|
p.remove(child)
|
||||||
|
|
||||||
|
# Создаём один run с полным текстом параграфа
|
||||||
|
new_r = etree.SubElement(p, f'{{{W_NS}}}r')
|
||||||
|
if rpr is not None:
|
||||||
|
new_r.append(rpr)
|
||||||
|
new_t = etree.SubElement(new_r, f'{{{W_NS}}}t')
|
||||||
|
new_t.text = full
|
||||||
|
new_t.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve')
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if not changed:
|
||||||
|
return doc_xml
|
||||||
|
|
||||||
|
return etree.tostring(root, xml_declaration=True, encoding='UTF-8', standalone=True).decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
def expand_loops_in_zip(docx_path: str, loops: Dict[str, List[Dict]]) -> str:
|
def expand_loops_in_zip(docx_path: str, loops: Dict[str, List[Dict]]) -> str:
|
||||||
"""Обрабатывает циклы напрямую в ZIP/DOCX на уровне XML.
|
"""Обрабатывает циклы напрямую в ZIP/DOCX на уровне XML.
|
||||||
Работает до загрузки python-docx — гарантирует корректную вложенность.
|
Работает до загрузки python-docx — гарантирует корректную вложенность.
|
||||||
@@ -102,6 +155,9 @@ def expand_loops_in_zip(docx_path: str, loops: Dict[str, List[Dict]]) -> str:
|
|||||||
if not doc_xml:
|
if not doc_xml:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Word мог разбить маркеры циклов на несколько run'ов — склеиваем
|
||||||
|
doc_xml = _normalize_loop_markers(doc_xml)
|
||||||
|
|
||||||
# Находим ВСЕ маркеры циклов
|
# Находим ВСЕ маркеры циклов
|
||||||
start_markers = list(LOOP_START.finditer(doc_xml))
|
start_markers = list(LOOP_START.finditer(doc_xml))
|
||||||
end_markers = list(LOOP_END.finditer(doc_xml))
|
end_markers = list(LOOP_END.finditer(doc_xml))
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ def _build_loop_data(company: Company, is_list: List) -> Dict[str, List[Dict]]:
|
|||||||
items.append({
|
items.append({
|
||||||
'name': isys.name or '—',
|
'name': isys.name or '—',
|
||||||
'description': isys.description or '—',
|
'description': isys.description or '—',
|
||||||
|
'address': getattr(isys, 'address', '') or '—',
|
||||||
'is_has_lan': 'Да' if isys.is_local_network else 'Нет',
|
'is_has_lan': 'Да' if isys.is_local_network else 'Нет',
|
||||||
'has_internet': 'да' if isys.is_internet else 'нет',
|
'has_internet': 'да' if isys.is_internet else 'нет',
|
||||||
'defence_level': str(isys.defence_level or '—'),
|
'defence_level': str(isys.defence_level or '—'),
|
||||||
|
|||||||
Reference in New Issue
Block a user