diff --git a/linker.py b/linker.py new file mode 100644 index 0000000..1503c40 --- /dev/null +++ b/linker.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 + +import re +import shutil +import tempfile +import xml.etree.ElementTree as ET + +from pathlib import Path +from zipfile import ZipFile + + +FIRST_NEW_SOURCE = 8 + +NS = { + "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main" +} + + +def normalize_url(url): + url = url.strip() + + if "#:~:text=" in url: + url = url.split("#:~:text=", 1)[0] + + url = url.rstrip("/") + + return url + + +def build_url_mapping(root): + """ + Первый проход. + + Собираем все уникальные URL и + присваиваем им номера. + """ + + url_to_index = {} + next_index = FIRST_NEW_SOURCE + + for node in root.findall(".//w:instrText", NS): + + text = "".join(node.itertext()) + + match = re.search( + r'HYPERLINK\s+"([^"]+)"', + text + ) + + if not match: + continue + + url = normalize_url(match.group(1)) + + if url in url_to_index: + continue + + url_to_index[url] = next_index + next_index += 1 + + return url_to_index + + +def patch_document(root, url_to_index): + """ + Второй проход. + + Находим: + HYPERLINK "..." + + Затем ближайший следующий + [старый номер] + + И меняем его на новый. + """ + + current_url = None + waiting_for_visible_text = False + + for elem in root.iter(): + + tag = elem.tag + + if tag.endswith("instrText"): + + text = "".join(elem.itertext()) + + match = re.search( + r'HYPERLINK\s+"([^"]+)"', + text + ) + + if match: + current_url = normalize_url( + match.group(1) + ) + + waiting_for_visible_text = True + + continue + + if ( + waiting_for_visible_text + and tag.endswith("t") + and elem.text + and re.match(r"\[\d+\]", elem.text.strip()) + ): + index = url_to_index[current_url] + + old = elem.text + elem.text = f"[{index}]" + + print( + f"{old} -> {elem.text} : {current_url}" + ) + + waiting_for_visible_text = False + current_url = None + + +def main(): + input_docx = Path("Отчет.docx") + + output_docx = Path( + input_docx.stem + "_fixed.docx" + ) + + with tempfile.TemporaryDirectory() as tmp: + + tmp = Path(tmp) + + with ZipFile(input_docx) as archive: + archive.extractall(tmp) + + document_xml = ( + tmp / + "word" / + "document.xml" + ) + + tree = ET.parse(document_xml) + + root = tree.getroot() + + url_to_index = build_url_mapping(root) + + print("\nURL -> INDEX\n") + + for url, index in url_to_index.items(): + print( + f"[{index}] {url}" + ) + + print( + f"\nFound {len(url_to_index)} unique sources\n" + ) + + patch_document( + root, + url_to_index + ) + + tree.write( + document_xml, + encoding="utf-8", + xml_declaration=True + ) + + with ZipFile( + output_docx, + "w" + ) as out_zip: + + for file in tmp.rglob("*"): + + if file.is_dir(): + continue + + out_zip.write( + file, + file.relative_to(tmp) + ) + + print( + f"\nSaved: {output_docx}" + ) + + +if __name__ == "__main__": + main() + + diff --git a/Отчет.docx b/Отчет.docx index 53230d4..00b5f0d 100644 Binary files a/Отчет.docx and b/Отчет.docx differ