#!/usr/bin/env python3 import re import tempfile import xml.etree.ElementTree as ET from pathlib import Path from zipfile import ZipFile from urllib.parse import urlsplit, urlunsplit import requests from bs4 import BeautifulSoup INPUT_DOCX = "Отчет.docx" OUTPUT_DOCX = "Отчет_fixed.docx" FIRST_NEW_SOURCE = 8 ACCESS_DATE = "30.05.2026" 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] parts = urlsplit(url) return urlunsplit(( parts.scheme.lower(), parts.netloc.lower(), parts.path.rstrip("/"), "", "" )) def get_title(url): try: response = requests.get( url, timeout=15, headers={ "User-Agent": "Mozilla/5.0" } ) response.raise_for_status() soup = BeautifulSoup(response.text, "html.parser") og = soup.find("meta", property="og:title") if og and og.get("content"): return " ".join(og["content"].split()) meta = soup.find("meta", attrs={"name": "title"}) if meta and meta.get("content"): return " ".join(meta["content"].split()) if soup.title and soup.title.string: return " ".join(soup.title.string.split()) except Exception as e: print(f"WARNING: {url}: {e}") return url def main(): with tempfile.TemporaryDirectory() as tmpdir: tmpdir = Path(tmpdir) with ZipFile(INPUT_DOCX) as archive: archive.extractall(tmpdir) document_xml = tmpdir / "word" / "document.xml" tree = ET.parse(document_xml) root = tree.getroot() url_to_index = {} index_to_url = {} next_index = FIRST_NEW_SOURCE # # Первый проход: # собираем все URL и выдаем номера # 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 index_to_url[next_index] = url next_index += 1 print("\n=== URL -> INDEX ===\n") for index in sorted(index_to_url): print(f"[{index}] {index_to_url[index]}") print() # # Второй проход: # меняем номера ссылок # current_url = None waiting_for_text = False replaced_count = 0 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_text = True continue if not waiting_for_text: continue if not tag.endswith("t"): continue if elem.text is None: continue if not re.match(r"\[\d+\]", elem.text.strip()): continue new_index = url_to_index[current_url] old_text = elem.text elem.text = f"[{new_index}]" print( f"{old_text} -> {elem.text} : {current_url}" ) replaced_count += 1 waiting_for_text = False current_url = None print() print(f"Replaced links: {replaced_count}") print() tree.write( document_xml, encoding="utf-8", xml_declaration=True ) with ZipFile(OUTPUT_DOCX, "w") as out_zip: for file in tmpdir.rglob("*"): if file.is_dir(): continue out_zip.write( file, file.relative_to(tmpdir) ) print(f"Saved: {OUTPUT_DOCX}") # # Формируем список литературы # print("\n=== SOURCES ===\n") for index in sorted(index_to_url): url = index_to_url[index] title = get_title(url) print( f"{index}. " f"{title} " f"(дата обращения: {ACCESS_DATE}). " f"Режим доступа - {url}" ) if __name__ == "__main__": main()