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