прогнал через скрипт список ссылок и источников, часть уже оформил правильно
This commit is contained in:
@@ -1,15 +1,22 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import shutil
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from zipfile import ZipFile
|
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
|
FIRST_NEW_SOURCE = 8
|
||||||
|
ACCESS_DATE = "30.05.2026"
|
||||||
|
|
||||||
NS = {
|
NS = {
|
||||||
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||||
@@ -22,143 +29,153 @@ def normalize_url(url):
|
|||||||
if "#:~:text=" in url:
|
if "#:~:text=" in url:
|
||||||
url = url.split("#:~:text=", 1)[0]
|
url = url.split("#:~:text=", 1)[0]
|
||||||
|
|
||||||
url = url.rstrip("/")
|
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
|
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 "..."
|
|
||||||
|
|
||||||
Затем ближайший следующий
|
|
||||||
<w:t>[старый номер]</w:t>
|
|
||||||
|
|
||||||
И меняем его на новый.
|
|
||||||
"""
|
|
||||||
|
|
||||||
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():
|
def main():
|
||||||
input_docx = Path("Отчет.docx")
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
|
||||||
output_docx = Path(
|
tmpdir = Path(tmpdir)
|
||||||
input_docx.stem + "_fixed.docx"
|
|
||||||
)
|
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
with ZipFile(INPUT_DOCX) as archive:
|
||||||
|
archive.extractall(tmpdir)
|
||||||
|
|
||||||
tmp = Path(tmp)
|
document_xml = tmpdir / "word" / "document.xml"
|
||||||
|
|
||||||
with ZipFile(input_docx) as archive:
|
|
||||||
archive.extractall(tmp)
|
|
||||||
|
|
||||||
document_xml = (
|
|
||||||
tmp /
|
|
||||||
"word" /
|
|
||||||
"document.xml"
|
|
||||||
)
|
|
||||||
|
|
||||||
tree = ET.parse(document_xml)
|
tree = ET.parse(document_xml)
|
||||||
|
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
|
|
||||||
url_to_index = build_url_mapping(root)
|
url_to_index = {}
|
||||||
|
index_to_url = {}
|
||||||
|
|
||||||
print("\nURL -> INDEX\n")
|
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}]"
|
||||||
|
|
||||||
for url, index in url_to_index.items():
|
|
||||||
print(
|
print(
|
||||||
f"[{index}] {url}"
|
f"{old_text} -> {elem.text} : {current_url}"
|
||||||
)
|
)
|
||||||
|
|
||||||
print(
|
replaced_count += 1
|
||||||
f"\nFound {len(url_to_index)} unique sources\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
patch_document(
|
waiting_for_text = False
|
||||||
root,
|
current_url = None
|
||||||
url_to_index
|
|
||||||
)
|
print()
|
||||||
|
print(f"Replaced links: {replaced_count}")
|
||||||
|
print()
|
||||||
|
|
||||||
tree.write(
|
tree.write(
|
||||||
document_xml,
|
document_xml,
|
||||||
@@ -166,27 +183,41 @@ def main():
|
|||||||
xml_declaration=True
|
xml_declaration=True
|
||||||
)
|
)
|
||||||
|
|
||||||
with ZipFile(
|
with ZipFile(OUTPUT_DOCX, "w") as out_zip:
|
||||||
output_docx,
|
|
||||||
"w"
|
|
||||||
) as out_zip:
|
|
||||||
|
|
||||||
for file in tmp.rglob("*"):
|
for file in tmpdir.rglob("*"):
|
||||||
|
|
||||||
if file.is_dir():
|
if file.is_dir():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
out_zip.write(
|
out_zip.write(
|
||||||
file,
|
file,
|
||||||
file.relative_to(tmp)
|
file.relative_to(tmpdir)
|
||||||
)
|
)
|
||||||
|
|
||||||
print(
|
print(f"Saved: {OUTPUT_DOCX}")
|
||||||
f"\nSaved: {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__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
BIN
Binary file not shown.
Reference in New Issue
Block a user