insert_manual_images.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. # -*- coding: utf-8 -*-
  2. """Insert 8.28 screenshots back into 9.19 markdown manuals."""
  3. import json, re
  4. from pathlib import Path
  5. ROOT = Path(r"D:\产线\大洋\大洋操作手册汇总9.19")
  6. IMG = ROOT / "images"
  7. IMG_RE = re.compile(r"^\s*!\[.*?\]\(images/.*?\)\s*$")
  8. CAP_RE = re.compile(r"^\s*<p class=\"imgcap\">.*$</s>$")
  9. FASCICLES = [
  10. "1、系统管理操作手册.md",
  11. "2、设备管理操作手册.md",
  12. "3、物料管理操作手册.md",
  13. "4、质量管理操作手册.md",
  14. "5、生产管理操作手册.md",
  15. "6、ERP对接操作手册.md",
  16. "7、生产线管理操作手册.md",
  17. "8、PDA操作说明操作手册.md",
  18. "9、MES系统客户端员工扫码操作手册.md",
  19. ]
  20. def norm(s):
  21. s = (s or "").replace(" ", "").replace(" ", "")
  22. s = s.replace("→", "").replace("—", "").replace("-", "")
  23. s = re.sub(r"^\d+(\.\d+)*\.?", "", s)
  24. return s
  25. def parse_sections(lines):
  26. """Return list of {level,title,start,end} covering body after heading."""
  27. secs = []
  28. for i, line in enumerate(lines):
  29. m = re.match(r"^(#{2,6})\s+(.*)$", line)
  30. if m:
  31. secs.append({"level": len(m.group(1)), "title": m.group(2).strip(), "start": i})
  32. for i, sec in enumerate(secs):
  33. sec["end"] = secs[i + 1]["start"] if i + 1 < len(secs) else len(lines)
  34. return secs
  35. def strip_old_images(text):
  36. lines = text.splitlines()
  37. out = []
  38. i = 0
  39. while i < len(lines):
  40. line = lines[i]
  41. if IMG_RE.match(line) or line.startswith('<p class="imgcap">') or line.strip() == "*(界面截图取自 8.28 手册,现场如有改动以实际为准。)*":
  42. i += 1
  43. # skip following blank
  44. if i < len(lines) and not lines[i].strip():
  45. i += 1
  46. continue
  47. if line.strip() == "## 原手册界面截图(8.28)":
  48. # drop this section until next heading
  49. i += 1
  50. while i < len(lines) and not re.match(r"^## ", lines[i]):
  51. i += 1
  52. continue
  53. out.append(line)
  54. i += 1
  55. return "\n".join(out).rstrip() + "\n"
  56. def score(sec, cap, heading, body=""):
  57. title = sec["title"]
  58. tn, cn, hn = norm(title), norm(cap), norm(heading)
  59. blob = tn + norm(body)
  60. s = 0
  61. if cn and (cn in tn or tn in cn):
  62. s += 60
  63. if hn and (hn in tn or tn in hn):
  64. s += 45
  65. hay = (title + " " + body).upper()
  66. for code in set(re.findall(r"OP\d+", (cap + heading).upper())):
  67. if code and code in hay:
  68. s += 80
  69. keys = re.findall(r"[\u4e00-\u9fff]{2,}", cap + " " + heading)
  70. for k in keys:
  71. if k in title or k in body[:800]:
  72. s += 12
  73. return s
  74. def image_md(item):
  75. cap = item.get("caption") or "界面截图"
  76. rel = item["file"]
  77. if not rel.startswith("images/"):
  78. rel = "images/" + rel
  79. return "\n![%s](%s)\n\n*(界面截图取自 8.28 手册,现场如有改动以实际为准。)*\n" % (cap, rel)
  80. def insert_into(md_name, items):
  81. path = ROOT / md_name
  82. text = strip_old_images(path.read_text(encoding="utf-8"))
  83. lines = text.splitlines()
  84. secs = parse_sections(lines)
  85. if not secs:
  86. path.write_text(text + "\n## 原手册界面截图(8.28)\n" + "".join(image_md(x) for x in items), encoding="utf-8")
  87. return len(items), 0
  88. buckets = {id(sec): [] for sec in secs}
  89. unmatched = []
  90. for item in items:
  91. best, best_s = None, 0
  92. for sec in secs:
  93. body = "\n".join(lines[sec["start"]:sec["end"]])
  94. sc = score(sec, item.get("caption", ""), item.get("heading", ""), body)
  95. if sc > best_s or (best is not None and sc == best_s and sec["level"] > best["level"]):
  96. best, best_s = sec, sc
  97. # special: login / home screens
  98. cap = item.get("caption") or ""
  99. if "登录" in cap:
  100. for sec in secs:
  101. if "登录" in sec["title"] or "安装" in sec["title"]:
  102. best, best_s = sec, max(best_s, 80)
  103. if "主功能" in cap or "我的界面" in cap:
  104. for sec in secs:
  105. if "界面" in sec["title"]:
  106. best, best_s = sec, max(best_s, 80)
  107. if best is None or best_s < 12:
  108. unmatched.append(item)
  109. else:
  110. buckets[id(best)].append(item)
  111. # insert from bottom so indexes stay valid
  112. for sec in reversed(secs):
  113. imgs = buckets[id(sec)]
  114. if not imgs:
  115. continue
  116. # insert before sec['end'] (start of next heading), after trailing blanks of this section
  117. pos = sec["end"]
  118. while pos > sec["start"] + 1 and pos <= len(lines) and (pos == len(lines) or not lines[pos - 1].strip()):
  119. pos -= 1
  120. block = "".join(image_md(x) for x in imgs).splitlines()
  121. lines[pos:pos] = [""] + block + [""]
  122. text = "\n".join(lines).rstrip() + "\n"
  123. if unmatched:
  124. extra = ["", "## 原手册界面截图(8.28)", "", "以下截图未能自动对上新目录,仍予保留。", ""]
  125. for x in unmatched:
  126. extra.extend(image_md(x).splitlines())
  127. text = text.rstrip() + "\n" + "\n".join(extra) + "\n"
  128. path.write_text(text, encoding="utf-8")
  129. return len(items) - len(unmatched), len(unmatched)
  130. def main():
  131. total_m, total_u = 0, 0
  132. for md in FASCICLES:
  133. stem = md[:-3]
  134. man = IMG / stem / "manifest.json"
  135. if not man.exists():
  136. print("no manifest", stem)
  137. continue
  138. items = json.loads(man.read_text(encoding="utf-8"))
  139. # keep only files that exist
  140. ok = []
  141. for it in items:
  142. p = ROOT / it["file"]
  143. if p.exists():
  144. ok.append(it)
  145. m, u = insert_into(md, ok)
  146. total_m += m
  147. total_u += u
  148. print(stem, "matched", m, "appendix", u, "total", len(ok))
  149. print("ALL matched", total_m, "appendix", total_u)
  150. if __name__ == "__main__":
  151. main()