from lxml import etree from typing import List, Dict global_ast_path = r"E:\storyline.xast" def items_filter(items, proc): values = [] for it in items: if proc(it): values.append(it) pass pass return values def items_map(items: List['StoryLine']) -> Dict[str, 'StoryLine']: map: Dict[str, 'StoryLine'] = {} for inst in items: map[inst.signature] = inst pass return map class FragmentSlice(): """ 故事情节片段 """ def __init__(self, signature: str, file_path: str): self.signature = signature self.file_path = file_path self.text_sections = [] self.refer_items: 'FragmentSlice' = [] pass def append_text_section(self, section: str): """ 添加描述文本段落 :param section: 文本段落 :return: None """ self.text_sections.append(section) pass def append_refer_slice(self, refer:'FragmentSlice'): """ 添加引用描述段落内容 :param refer: 引用段落 :return: None """ self.refer_items.append(refer) pass def content_equal(self, other: 'FragmentSlice') -> bool: """ 内容比较 :param other: 其他片段 :return: 比较结果 """ if self.signature != other.signature: return False if len(self.text_sections) != len(other.text_sections): return False for line0 in self.text_sections: if not line0 in other.text_sections: return False pass if len(self.refer_items) != len(other.refer_items): return False for slice in self.refer_items: find: bool = False for slice_1 in other.refer_items: if slice.content_equal(slice_1): find = True pass pass if not find: return False pass return True class StoryLine(): """ 故事线结果 """ def __init__(self, signature: str, file_path: str): self.signature = signature self.file_path = file_path self.text_sections = [] self.fragment_slices = [] pass def append_text_section(self, section: str): """ 添加文本描述 :param section: 文本段落 :return: None """ self.text_sections.append(section) pass def append_fragment_slice(self, slice: FragmentSlice): """ 添加情节 :param slice: 情节实例 :return: None """ self.fragment_slices.append(slice) pass def content_equal(self, other: "StoryLine") -> bool: """ 内容比较 :param other: 其他实例 :return: 比较结果 """ if self.signature != other.signature: return False if len(self.text_sections) != len(other.text_sections): return False for line in self.text_sections: if not line in other.text_sections: return False pass if len(self.fragment_slices) != len(other.fragment_slices): return False for slice0 in self.fragment_slices: find: bool = False for slice1 in other.fragment_slices: if slice0.content_equal(slice1): find = True pass pass if not find: return False pass return True class AstParse(): """ Ast解析器 """ def __init__(self, ast_path: str): self.ast_path = ast_path ast_file = open(ast_path, "rb") ast_text = ast_file.read() self.ast_inst = etree.XML(ast_text) pass def generate_time(self) -> str: """ 获取生成时间 :return: 生成时间字符串 """ attr_time = self.ast_inst.xpath("/ast[1]/@time")[0] return str(attr_time) def peak_storylines(self) -> List[StoryLine]: """ 提取所有故事线 :return: 故事线列表 """ story_list = self.ast_inst.xpath("/ast/story") # 获取所有故事节点 story_items = [] fragm_map: Dict[str, FragmentSlice] = {} for story in story_list: story_name = story.xpath("@name")[0] story_file = story.xpath("@file-path")[0] story_slice = StoryLine("story:" + story_name, story_file) story_items.append(story_slice) story_text_lines = story.xpath("text-section/@text") #获取所有描述 for line in story_text_lines: story_slice.append_text_section(line) pass fragment_items = story.xpath("fragment") #获取所有情节定义 for fragm in fragment_items: fragment_name = fragm.xpath("@name")[0] fragm_slice = FragmentSlice(f"fragment:{story_name}#{fragment_name}", story_file) story_slice.append_fragment_slice(fragm_slice) fragm_map[fragm_slice.signature] = fragm_slice fragm_text_lines = fragm.xpath("text-section/@text") for line in fragm_text_lines: fragm_slice.append_text_section(line) pass pass pass fragm_refer_items = self.ast_inst.xpath("//refer") for refer_item in fragm_refer_items: story_name_refer = refer_item.xpath("@story")[0] fragm_name_refer = refer_item.xpath("@fragment")[0] fragm_file = refer_item.xpath("@file-path")[0] fragm_signature = f"fragment:{story_name_refer}#{fragm_name_refer}" fragment_target = fragm_map[fragm_signature] fragm_pnode = refer_item.xpath("..")[0] if fragm_pnode.xpath("name()") == "story": this_story_name = fragm_pnode.xpath("@name")[0] refer_slice = FragmentSlice(f"refer:<{this_story_name}>{story_name_refer}#{fragm_name_refer}", fragm_file) fragment_target.append_refer_slice(refer_slice) refer_lines = refer_item.xpath("text-section/@text") for line in refer_lines: refer_slice.append_text_section(line) pass pass if fragm_pnode.xpath("name()") == "article": this_article_name = fragm_pnode.xpath("@name")[0] this_volume_node = fragm_pnode.xpath("..")[0] this_volume_name = this_volume_node.xpath("@name")[0] refer_slice = FragmentSlice(f"refer-article<{this_volume_name}#{this_article_name}>{story_name_refer}#{fragm_name_refer}", fragm_file) fragment_target.append_refer_slice(refer_slice) refer_lines = refer_item.xpath("text-section/@text") for line in refer_lines: refer_slice.append_text_section(line) pass pass return story_items def storylines_compare(self, base_list: List[StoryLine], other_list: List[StoryLine]) -> List[str]: changed_list: List[str] = [] base_map = items_map(base_list) for other_item in other_list: if not other_item.signature in base_map: changed_list.append(other_item.signature) continue base_item = base_map[other_item.signature] if not other_item.content_equal(base_item): changed_list.append(other_item.signature) pass base_map.pop(other_item.signature) pass for addit in base_map.values(): changed_list.append(addit.signature) pass return changed_list