185 lines
4.9 KiB
Python
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))
|