diff --git a/.idea/workspace.xml b/.idea/workspace.xml index de3eb34..8001eec 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,6 +5,8 @@ + + @@ -24,9 +26,9 @@ - + { + "associatedIndex": 1 +} @@ -39,8 +41,10 @@ "keyToString": { "Python.ast_load.executor": "Debug", "Python.entry.executor": "Debug", + "RunOnceActivity.OpenProjectViewOnStart": "true", "RunOnceActivity.ShowReadmeOnStart": "true", - "git-widget-placeholder": "master" + "git-widget-placeholder": "master", + "last_opened_file_path": "D:/Projects/Python/StoryCheckTools" } }]]> @@ -75,7 +79,7 @@ - diff --git a/entry.py b/entry.py index a8fd386..2b0f420 100644 --- a/entry.py +++ b/entry.py @@ -1,8 +1,17 @@ from PyQt5.QtWidgets import QWidget, QApplication +from frame.CompareWindow import CompareWin from sys import argv from parse.ast_load import global_ast_path, AstParse tool = AstParse(global_ast_path) print(tool.generate_time()) -tool.peak_storylines() \ No newline at end of file +storys = tool.peak_storylines() +storys_2 = tool.peak_storylines() +defferent_list = tool.storylines_compare(storys, storys_2) + +app = QApplication(argv) +win = CompareWin() +win.load_differents(defferent_list, storys, storys_2) +win.show() +app.exec() \ No newline at end of file diff --git a/frame/CompareWindow.py b/frame/CompareWindow.py new file mode 100644 index 0000000..8b3f761 --- /dev/null +++ b/frame/CompareWindow.py @@ -0,0 +1,56 @@ +from PyQt5.QtWidgets import QWidget, QTreeView, QVBoxLayout +from PyQt5.QtGui import QStandardItemModel, QStandardItem +from typing import List +from parse.ast_load import StoryLine, FragmentSlice, items_map + + +class CompareWin(QWidget): + def __init__(self): + QWidget.__init__(self) + self.setWindowTitle("故事线比较") + + self.__compare_model = QStandardItemModel(self) + self.__compare_view = QTreeView(self) + self.__compare_model.setHorizontalHeaderLabels(["节点名称", "比对参数"]) + self.__compare_view.setModel(self.__compare_model) + + layout = QVBoxLayout(self) + layout.addWidget(self.__compare_view) + pass + + def load_differents(self, changed: List[str], base_list: List[StoryLine], other_list: List[StoryLine]): + base_map = items_map(base_list) + other_map = items_map(other_list) + + for changed_item in changed: + item = QStandardItem(changed_item) + item2 = QStandardItem() + item.setEditable(False) + item2.setEditable(False) + self.__compare_model.appendRow([item, item2]) + + if changed_item in other_map: + story_origin = other_map[changed_item] + item_o = QStandardItem(story_origin.signature) + if not changed_item in base_map: + item2.setText("append") + else: + item2.setText("modify") + item_o2 = QStandardItem(story_origin.file_path) + item_o.setEditable(False) + item_o2.setEditable(False) + item.appendRow([item_o, item_o2]) + pass + else: + if changed_item in base_map: + story_modif = base_map[changed_item] + item_ext = QStandardItem(story_modif.signature) + item2.setText("remove") + item_ext2 = QStandardItem(story_modif.file_path) + item_ext.setEditable(False) + item_ext2.setEditable(False) + item.appendRow([item_ext, item_ext2]) + pass + pass + pass + pass diff --git a/frame/__init__.py b/frame/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/frame/__pycache__/CompareWindow.cpython-312.pyc b/frame/__pycache__/CompareWindow.cpython-312.pyc new file mode 100644 index 0000000..9f93294 Binary files /dev/null and b/frame/__pycache__/CompareWindow.cpython-312.pyc differ diff --git a/frame/__pycache__/__init__.cpython-312.pyc b/frame/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..9cabe46 Binary files /dev/null and b/frame/__pycache__/__init__.cpython-312.pyc differ diff --git a/parse/__pycache__/__init__.cpython-312.pyc b/parse/__pycache__/__init__.cpython-312.pyc index fd33a09..08e7bfe 100644 Binary files a/parse/__pycache__/__init__.cpython-312.pyc and b/parse/__pycache__/__init__.cpython-312.pyc differ diff --git a/parse/__pycache__/ast_load.cpython-312.pyc b/parse/__pycache__/ast_load.cpython-312.pyc index ff05651..ba6adab 100644 Binary files a/parse/__pycache__/ast_load.cpython-312.pyc and b/parse/__pycache__/ast_load.cpython-312.pyc differ diff --git a/parse/ast_load.py b/parse/ast_load.py index b1dac6b..865b699 100644 --- a/parse/ast_load.py +++ b/parse/ast_load.py @@ -1,5 +1,5 @@ from lxml import etree -from typing import List +from typing import List, Dict global_ast_path = r"E:\storyline.xast" @@ -15,35 +15,151 @@ def items_filter(items, proc): 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): + """ + 故事情节片段 + """ + 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'): + """ + 添加引用描述段落内容 -class StorySlice(): - def __init__(self, signature: str): + :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") @@ -52,27 +168,42 @@ class AstParse(): 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[StorySlice]: + 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_slice = StorySlice("story:"+story_name) + 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") # 获取所有描述 + 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") # 获取所有情节定义 + fragment_items = story.xpath("fragment") #获取所有情节定义 for fragm in fragment_items: - fragment_name = fragm.xpath("@name") - fragm_slice = FragmentSlice("fragment:"+story_name+"#"+fragment_name) + 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) @@ -80,14 +211,62 @@ class AstParse(): pass pass - fragm_refers = self.ast_inst.xpath("//refer") - for refer in fragm_refers: - story_name = refer.xpath("@story") - fragm_name = refer.xpath("@fragment") - story_refers = items_filter(story_items, lambda x:x.signature == f"story:{story_name}") - if(len(story_refers) > 0): - story_refer = story_refers[0] - + 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