fix: nested loops work - preserve inner loop_end markers
This commit is contained in:
+37
-18
@@ -82,20 +82,25 @@ def expand_loops_in_zip(docx_path: str, loops: Dict[str, List[Dict]]) -> str:
|
||||
if not loops:
|
||||
return docx_path
|
||||
|
||||
# Временная копия
|
||||
# Временный файл
|
||||
tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.docx')
|
||||
tmp.close()
|
||||
shutil.copy2(docx_path, tmp.name)
|
||||
|
||||
max_passes = 30
|
||||
for _ in range(max_passes):
|
||||
# Читаем document.xml
|
||||
# Читаем ВСЕ файлы из исходного ZIP
|
||||
all_files = {}
|
||||
try:
|
||||
with zipfile.ZipFile(tmp.name, 'r') as z:
|
||||
doc_xml = z.read('word/document.xml').decode('utf-8')
|
||||
with zipfile.ZipFile(docx_path if _ == 0 else tmp.name, 'r') as z:
|
||||
for name in z.namelist():
|
||||
all_files[name] = z.read(name)
|
||||
doc_xml = all_files.get('word/document.xml', b'').decode('utf-8')
|
||||
except Exception:
|
||||
break
|
||||
|
||||
if not doc_xml:
|
||||
break
|
||||
|
||||
# Находим ВСЕ маркеры циклов
|
||||
start_markers = list(LOOP_START.finditer(doc_xml))
|
||||
end_markers = list(LOOP_END.finditer(doc_xml))
|
||||
@@ -107,12 +112,26 @@ def expand_loops_in_zip(docx_path: str, loops: Dict[str, List[Dict]]) -> str:
|
||||
outer = start_markers[0]
|
||||
outer_key = outer.group(1)
|
||||
|
||||
# Соответствующий end = последний end после outer
|
||||
# Соответствующий end = первый end после outer (или последний, если вложенные)
|
||||
# Считаем глубину: каждый start +1, каждый end -1
|
||||
depth = 1
|
||||
outer_end = None
|
||||
for m in reversed(end_markers):
|
||||
if m.start() > outer.start():
|
||||
outer_end = m
|
||||
break
|
||||
all_markers = sorted(
|
||||
[('start', m) for m in start_markers] +
|
||||
[('end', m) for m in end_markers],
|
||||
key=lambda x: x[1].start()
|
||||
)
|
||||
found_start = False
|
||||
for kind, m in all_markers:
|
||||
if m.start() <= outer.start():
|
||||
continue
|
||||
if kind == 'start':
|
||||
depth += 1
|
||||
elif kind == 'end':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
outer_end = m
|
||||
break
|
||||
|
||||
if outer_end is None:
|
||||
break
|
||||
@@ -121,19 +140,17 @@ def expand_loops_in_zip(docx_path: str, loops: Dict[str, List[Dict]]) -> str:
|
||||
if not items:
|
||||
# Удаляем блок цикла полностью
|
||||
doc_xml = doc_xml[:outer.start()] + doc_xml[outer_end.end():]
|
||||
all_files['word/document.xml'] = doc_xml.encode('utf-8')
|
||||
with zipfile.ZipFile(tmp.name, 'w', zipfile.ZIP_DEFLATED) as z:
|
||||
z.writestr('word/document.xml', doc_xml.encode('utf-8'))
|
||||
for name, data in all_files.items():
|
||||
z.writestr(name, data)
|
||||
continue
|
||||
|
||||
# Извлекаем шаблон (всё между start и end маркерами)
|
||||
template = doc_xml[outer.end():outer_end.start()]
|
||||
|
||||
# Удаляем только последний loop_end (принадлежит внешнему циклу)
|
||||
# НЕ удаляем loop_end из шаблона — они принадлежат вложенным циклам!
|
||||
template_clean = template
|
||||
last_end_markers = list(LOOP_END.finditer(template_clean))
|
||||
if last_end_markers:
|
||||
last_end = last_end_markers[-1]
|
||||
template_clean = template_clean[:last_end.start()] + template_clean[last_end.end():]
|
||||
|
||||
# Размножаем шаблон для каждого элемента данных
|
||||
expanded_parts = []
|
||||
@@ -148,9 +165,11 @@ def expand_loops_in_zip(docx_path: str, loops: Dict[str, List[Dict]]) -> str:
|
||||
# Собираем новый XML
|
||||
new_xml = doc_xml[:outer.start()] + ''.join(expanded_parts) + doc_xml[outer_end.end():]
|
||||
|
||||
# Сохраняем
|
||||
# Сохраняем ВСЕ файлы ZIP, обновляя только document.xml
|
||||
all_files['word/document.xml'] = new_xml.encode('utf-8')
|
||||
with zipfile.ZipFile(tmp.name, 'w', zipfile.ZIP_DEFLATED) as z:
|
||||
z.writestr('word/document.xml', new_xml.encode('utf-8'))
|
||||
for name, data in all_files.items():
|
||||
z.writestr(name, data)
|
||||
|
||||
return tmp.name
|
||||
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from dokogen.models import Company, CommissionMember, InformationSystem
|
||||
from dokogen.variables import build_replacements
|
||||
from dokogen.generator import process_template, _expand_loops_in_zip
|
||||
from dokogen.generator import process_template, expand_loops_in_zip
|
||||
|
||||
def create_test_template():
|
||||
"""Creates a test .docx with nested loops for verification."""
|
||||
@@ -108,7 +108,7 @@ def test_zip_loop_expansion():
|
||||
|
||||
# Process with ZIP-level loop expansion
|
||||
print('\nProcessing loops via ZIP...')
|
||||
result_path = _expand_loops_in_zip(template_path, loops)
|
||||
result_path = expand_loops_in_zip(template_path, loops)
|
||||
|
||||
# Verify result
|
||||
with zipfile.ZipFile(result_path, 'r') as z:
|
||||
|
||||
Reference in New Issue
Block a user