gwdoc/resetlevel.py

185 lines
4.9 KiB
Python

import copy
import json
import re
import sys
from typing import Any
CN_NUM = "\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343\u4e07\u3007\u96f6\u4e24"
CHAPTER_PATTERN = re.compile(rf"^\s*\u7b2c\s*([{CN_NUM}\d]+)\s*\u7ae0")
CN_LEVEL1_PATTERN = re.compile(rf"^\s*([{CN_NUM}]+)(?=\s|[\u3001\u3002\uff0e.])")
CASE_LIKE_PATTERN = re.compile(r"^\s*\d+(?:\.|\uff0e)(?!\d|\s|$)")
ARABIC_PATTERN = re.compile(
r"^\s*(\d+(?:\.\d+)*)(?=\s|[\u4e00-\u9fff\u3001\u3002\uff0e]|$|\.(?:\s|$))"
)
def has_valid_text_level(item: dict) -> bool:
return "text_level" in item and item.get("text_level") not in (None, "")
def parse_heading(text: str):
text = text.strip()
if not text:
return None, None
if CASE_LIKE_PATTERN.match(text):
return "case_like", None
if CHAPTER_PATTERN.match(text):
return "level1", None
if CN_LEVEL1_PATTERN.match(text):
return "level1", None
match = ARABIC_PATTERN.match(text)
if match:
return "arabic", match.group(1)
return None, None
def arabic_depth(number: str) -> int:
return number.count(".") + 1
def parent_number(number: str) -> str | None:
parts = number.split(".")
if len(parts) <= 1:
return None
return ".".join(parts[:-1])
def collect_headings(data: list) -> list:
headings = []
for item in data:
if not isinstance(item, dict):
continue
if item.get("type") != "text":
continue
if not has_valid_text_level(item):
continue
kind, number = parse_heading(item.get("text", ""))
if kind in {"level1", "arabic", "case_like"}:
headings.append({"kind": kind, "number": number})
return headings
def is_valid_arabic_hierarchy(numbers: list[str], allow_missing_depth1_parent: bool) -> bool:
seen = set()
has_depth2 = False
has_depth3 = False
for number in numbers:
depth = arabic_depth(number)
if depth >= 2:
has_depth2 = True
if depth >= 3:
has_depth3 = True
parent = parent_number(number)
if parent is not None:
parent_depth = arabic_depth(parent)
parent_is_missing_depth1 = allow_missing_depth1_parent and parent_depth == 1
if parent not in seen and not parent_is_missing_depth1:
return False
seen.add(number)
return has_depth2 and has_depth3
def detect_valid_mode(headings: list):
if not headings:
return None
if any(heading["kind"] == "case_like" for heading in headings):
return None
first = headings[0]
if first["kind"] == "arabic":
numbers = [heading["number"] for heading in headings if heading["kind"] == "arabic"]
if numbers and arabic_depth(numbers[0]) == 1:
if is_valid_arabic_hierarchy(numbers, allow_missing_depth1_parent=False):
return "A"
return None
if first["kind"] == "level1":
numbers = [heading["number"] for heading in headings[1:] if heading["kind"] == "arabic"]
if not numbers:
return None
first_depth = arabic_depth(numbers[0])
if first_depth == 2 and is_valid_arabic_hierarchy(numbers, allow_missing_depth1_parent=True):
return "B"
if first_depth == 1 and is_valid_arabic_hierarchy(numbers, allow_missing_depth1_parent=False):
return "C"
return None
JsonContent = Any
def reset_textlevel(json_content: JsonContent) -> JsonContent:
"""
输入 JSON 内容,返回更新 text_level 后的新 JSON 内容。
json_content 可以是已经 json.load/json.loads 后的 Python 对象,
也可以是 JSON 字符串。函数不会修改原始入参。
"""
if isinstance(json_content, str):
data = json.loads(json_content)
else:
data = copy.deepcopy(json_content)
if not isinstance(data, list):
return data
headings = collect_headings(data)
mode = detect_valid_mode(headings)
if mode is None:
return data
for item in data:
if not isinstance(item, dict):
continue
if item.get("type") != "text":
continue
if not has_valid_text_level(item):
continue
kind, number = parse_heading(item.get("text", ""))
if kind == "level1":
item["text_level"] = 1
continue
if kind != "arabic":
continue
depth = arabic_depth(number)
item["text_level"] = depth if mode in {"A", "B"} else depth + 1
return data
if __name__ == "__main__":
json_file_path = r'E:\ZKYNLP\Hjunproject\project0506\kgrag\163-06A0014-B01001_发动机-维修手册_content_list.json'
with open(json_file_path, "r", encoding="utf-8") as f:
json_content = json.load(f)
result= reset_textlevel(json_content)
for ins in result[:30]:
print(ins)
# print(json.dumps(result, ensure_ascii=False, indent=2))