_word_toc_one.py 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. # -*- coding: utf-8 -*-
  2. """Refresh TOC fields in one docx via a fresh Word process."""
  3. from __future__ import annotations
  4. import hashlib
  5. import shutil
  6. import sys
  7. import tempfile
  8. import time
  9. from pathlib import Path
  10. WD_COLOR_BLACK = 0
  11. WD_STAT_PAGES = 2
  12. def retry(fn, times=8, wait=0.8):
  13. last = None
  14. for i in range(times):
  15. try:
  16. return fn()
  17. except Exception as exc:
  18. last = exc
  19. time.sleep(wait * (i + 1))
  20. raise last
  21. def main() -> int:
  22. if len(sys.argv) < 2:
  23. return 2
  24. src = Path(sys.argv[1])
  25. tmp_dir = Path(tempfile.gettempdir()) / "aidop_toc_word"
  26. tmp_dir.mkdir(exist_ok=True)
  27. tmp = tmp_dir / f"{hashlib.md5(str(src).encode('utf-8')).hexdigest()}_{int(time.time())}.docx"
  28. shutil.copy2(src, tmp)
  29. import pythoncom
  30. import win32com.client as win32
  31. pythoncom.CoInitialize()
  32. word = None
  33. doc = None
  34. try:
  35. word = retry(lambda: win32.DispatchEx("Word.Application"))
  36. word.Visible = False
  37. try:
  38. word.DisplayAlerts = 0
  39. except Exception:
  40. pass
  41. try:
  42. word.AutomationSecurity = 3
  43. except Exception:
  44. pass
  45. time.sleep(1)
  46. doc = retry(
  47. lambda: word.Documents.Open(
  48. str(tmp),
  49. ConfirmConversions=False,
  50. ReadOnly=False,
  51. AddToRecentFiles=False,
  52. )
  53. )
  54. time.sleep(1)
  55. retry(lambda: doc.Fields.Update())
  56. def update_tocs():
  57. for i in range(1, doc.TablesOfContents.Count + 1):
  58. doc.TablesOfContents(i).Update()
  59. retry(update_tocs)
  60. try:
  61. doc.Repaginate()
  62. except Exception:
  63. pass
  64. retry(update_tocs)
  65. for name in ("TOC 1", "TOC 2", "TOC 3", "TOC 4", "Hyperlink"):
  66. try:
  67. doc.Styles(name).Font.Color = WD_COLOR_BLACK
  68. except Exception:
  69. pass
  70. pages = retry(lambda: doc.ComputeStatistics(WD_STAT_PAGES))
  71. lines = 0
  72. if doc.TablesOfContents.Count:
  73. lines = len([x for x in doc.TablesOfContents(1).Range.Text.split("\r") if x.strip()])
  74. retry(doc.Save)
  75. retry(lambda: doc.Close(False))
  76. doc = None
  77. retry(word.Quit)
  78. word = None
  79. time.sleep(0.5)
  80. shutil.copy2(tmp, src)
  81. print(f"pages={pages} toc={lines}", flush=True)
  82. return 0
  83. finally:
  84. try:
  85. if doc is not None:
  86. doc.Close(False)
  87. except Exception:
  88. pass
  89. try:
  90. if word is not None:
  91. word.Quit()
  92. except Exception:
  93. pass
  94. try:
  95. pythoncom.CoUninitialize()
  96. except Exception:
  97. pass
  98. try:
  99. tmp.unlink(missing_ok=True)
  100. except Exception:
  101. pass
  102. if __name__ == "__main__":
  103. raise SystemExit(main())