diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 13f6fbb..a316b03 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,7 +5,12 @@ - + + + + + + - + + + + - - - + - + - @@ -199,9 +205,14 @@ - file://$PROJECT_DIR$/manage/NovelManage.py - 4 - + + file://$PROJECT_DIR$/frame/CompareViews.py + 164 + diff --git a/frame/CompareViews.py b/frame/CompareViews.py new file mode 100644 index 0000000..9006bf6 --- /dev/null +++ b/frame/CompareViews.py @@ -0,0 +1,195 @@ +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QTableView, QSplitter, QApplication, QMainWindow +from PyQt5.QtGui import QStandardItemModel, QStandardItem +from typing import List, Dict, Tuple +from manage.MileStone import base_store_path, current_store_path +from parse.StorylineCmp import ModifyReason, CmpTool +from parse.StoryMap import FragmentSlice, StoryMap, XAST_ParseTool, storyline_list2map +import sys +from PyQt5.QtCore import pyqtSignal, pyqtSlot, QModelIndex +from frame.MergeView import LinesMergeView,MergeDestination + + +class ResultList(QTableView): + def __init__(self, parent): + QTableView.__init__(self, parent) + self.model = QStandardItemModel(self) + self.model.setHorizontalHeaderLabels(["故事名称", "情节名称", "状态"]) + self.setModel(self.model) + self.setSelectionMode(QTableView.SelectionMode.SingleSelection) + self.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows) + pass + + def present_affected_fragments(self,graph_new: Dict[str, StoryMap], cmp_result: Dict[ModifyReason, List[FragmentSlice]]): + """ + 展示影响节点 + :return: + """ + old_remove = cmp_result[ModifyReason.Removed] + affected_list = [] + for node in old_remove: + affected_list = affected_list + self.__find_remove_affected_slice_within_new(graph_new, node) + pass + + fragm_items = self.__filter_and_related_defines_seek(affected_list) + self.__append_fragments_list(fragm_items, "REMOVE") + + curr_append = cmp_result[ModifyReason.Append] + affected_list = self.__find_append_and_changed_affected_slice(curr_append) + fragm_items = self.__filter_and_related_defines_seek(affected_list) + self.__append_fragments_list(fragm_items, "APPEND") + + curr_changed = cmp_result[ModifyReason.Changed] + affected_list = self.__find_append_and_changed_affected_slice(curr_changed) + fragm_items = self.__filter_and_related_defines_seek(affected_list) + self.__append_fragments_list(fragm_items, "UPDATE") + + pass + + def __find_remove_affected_slice_within_new(self, graph_new: Dict[str, StoryMap], peers_old: FragmentSlice) -> List[FragmentSlice]: + """ + 获取受到移除节点直接影响的节点 + :param graph_new: 新图 + :param peers_old: 旧图节点 + :return: + """ + retvs = [] + story_name = peers_old.parent_story + + # 移除了情节定义 + if peers_old.is_define_node[0]: + refer_list = peers_old.refers_slice + + for refer_slice_prev in refer_list.values(): + retvs = retvs + self.__find_remove_affect_within_new(graph_new, refer_slice_prev) + pass + + story_curr = graph_new[story_name] + # 新图内故事线存在,非最后一个节点,计算影响 + if story_curr is not None and peers_old.next_node is not None: + o_frag_def = peers_old.next_node + frag_curr_def = story_curr.get_fragment_defined(o_frag_def.is_define_node[1]) + if frag_curr_def is not None: + retvs.append(frag_curr_def) + pass + pass + else: + refer_story_new = graph_new[story_name] + slice_affected = peers_old.next_node + + # 非最后一个,需要计算影响,新图中的故事线存在,计算影响 + if slice_affected is not None and refer_story_new is not None: + v_s_name = slice_affected.story_refer + v_f_name = slice_affected.fragm_refer + + # 获取收到影响的情节片段 + slice_refer_now = CmpTool.__locate_fragment_refer__(refer_story_new.slice_list[0], v_s_name, v_f_name) + if slice_refer_now is not None: + retvs.append(slice_refer_now) + pass + pass + + return retvs + + def __find_append_and_changed_affected_slice(self, peers_new: List[FragmentSlice]) -> List[FragmentSlice]: + retvs = [] + + for curr_node in peers_new: + next_slice = curr_node.next_node + if next_slice is not None: + retvs.append(next_slice) + + return retvs + + def __filter_and_related_defines_seek(self, affect_directed: List[FragmentSlice])-> List[Tuple[str, str]]: + story_fragment_names: Dict[str, Tuple[str, str]] = {} + for node in affect_directed: + # 情节定义节点 + if node.is_define_node[0]: + fragm_key = f"{node.parent_story}#{node.is_define_node[1]}" + story_fragment_names[fragm_key] = (node.parent_story, node.is_define_node[1]) + + # 情节引用节点 + else: + fragm_key = f"{node.story_refer}#{node.fragm_refer}" + story_fragment_names[fragm_key] = (node.story_refer, node.fragm_refer) + pass + pass + + return list(story_fragment_names.values()) + + def __append_fragments_list(self, fragment_keys: List[Tuple[str, str]], type: str) -> None: + for key_def in fragment_keys: + row = [] + row.append(QStandardItem(key_def[0])) + row.append(QStandardItem(key_def[1])) + row.append(QStandardItem(type)) + for cell in row: cell.setEditable(False) + self.model.appendRow(row) + pass + pass + + pass + + +class CompareWindow(QWidget): + def __init__(self, graph_new: Dict[str, StoryMap], cmp_result: Dict[ModifyReason, List[FragmentSlice]], parent): + QWidget.__init__(self, parent) + + self.graph_new_refer = graph_new + + layout = QVBoxLayout(self) + self.splitter = QSplitter(self) + layout.addWidget(self.splitter) + + self.result_list = ResultList(self) + self.splitter.addWidget(self.result_list) + self.splitter.addWidget(QWidget(self)) + + self.result_list.present_affected_fragments(graph_new, cmp_result) + + self.result_list.clicked.connect(self.present_fragment_merge_content) + pass + + def present_fragment_merge_content(self, index: QModelIndex): + if not index.isValid(): + return + + story_name = self.result_list.model.item(index.row(), 0).text() + fragm_name = self.result_list.model.item(index.row(), 1).text() + story_node = self.graph_new_refer[story_name] + fragm_node = story_node.get_fragment_defined(fragm_name) + + main_point = MergeDestination(story_name, fragm_node.prev_node, fragm_node) + elist = [] + for refstory in fragm_node.refers_slice.keys(): + refslice = fragm_node.refers_slice[refstory] + elist.append(MergeDestination(refstory, refslice.prev_node, refslice)) + + new_view = LinesMergeView(main_point, elist, self) + new_view.load_from_mem() + self.splitter.replaceWidget(1, new_view) + pass + + pass + + +if __name__ == "__main__": + app = QApplication(sys.argv) + win = QMainWindow() + + ast_old = XAST_ParseTool(base_store_path) + ast_new = XAST_ParseTool(current_store_path) + map_old = storyline_list2map(ast_old.story_list) + map_new = storyline_list2map(ast_new.story_list) + + ast_old.storylines_plait(map_old) + ast_new.storylines_plait(map_new) + + cmp_tool = CmpTool() + all_changed = cmp_tool.graph_compare(map_new, map_old) + + view = CompareWindow(map_new, all_changed, win) + win.setCentralWidget(view) + win.show() + + app.exec() \ No newline at end of file diff --git a/frame/CompareWindow.py b/frame/CompareWindow.py deleted file mode 100644 index 8b3f761..0000000 --- a/frame/CompareWindow.py +++ /dev/null @@ -1,56 +0,0 @@ -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/MergeView.py b/frame/MergeView.py index bd2f648..0b0613f 100644 --- a/frame/MergeView.py +++ b/frame/MergeView.py @@ -21,6 +21,7 @@ class LinesMergeView(QWidget, MemorySkin): self.refers_line = refdefs layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) splitter = QSplitter(Qt.Orientation.Vertical, self) layout.addWidget(splitter) diff --git a/frame/__pycache__/CompareWindow.cpython-312.pyc b/frame/__pycache__/CompareWindow.cpython-312.pyc deleted file mode 100644 index 8d10171..0000000 Binary files a/frame/__pycache__/CompareWindow.cpython-312.pyc and /dev/null differ diff --git a/frame/__pycache__/MergeView.cpython-312.pyc b/frame/__pycache__/MergeView.cpython-312.pyc new file mode 100644 index 0000000..fd48e7e Binary files /dev/null and b/frame/__pycache__/MergeView.cpython-312.pyc differ diff --git a/manage/__pycache__/MileStone.cpython-312.pyc b/manage/__pycache__/MileStone.cpython-312.pyc new file mode 100644 index 0000000..98b4483 Binary files /dev/null and b/manage/__pycache__/MileStone.cpython-312.pyc differ diff --git a/manage/__pycache__/__init__.cpython-312.pyc b/manage/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..f36462b Binary files /dev/null and b/manage/__pycache__/__init__.cpython-312.pyc differ diff --git a/parse/StoryMap.py b/parse/StoryMap.py index 72d4beb..1acfcf0 100644 --- a/parse/StoryMap.py +++ b/parse/StoryMap.py @@ -7,6 +7,7 @@ from frame.ReferView import EmptyNode, MemoryNode class FragmentSlice(EmptyNode): def __init__(self, def_mark: bool = False, name: str= ""): self.is_define_node = (def_mark, name) + self.parent_story = "" self.story_refer = "" self.fragm_refer = "" self.fragm_sort_i = 0 @@ -19,11 +20,11 @@ class FragmentSlice(EmptyNode): pass def get_from_memory(self) -> str: - return r"\n".join(self.text_sections) + return "\n".join(self.text_sections) pass def set_to_memory(self, content: str): - self.text_sections = content.split(r"\n") + self.text_sections = content.split("\n") pass @@ -37,6 +38,7 @@ class StoryMap: def append_fragment_slice(self, node: FragmentSlice): self.slice_list[-1].next_node = node + node.parent_story = self.story_name node.prev_node = self.slice_list[-1] node.fragm_sort_i = len(self.slice_list) self.slice_list.append(node) diff --git a/parse/StorylineCmp.py b/parse/StorylineCmp.py index b1360d0..a368b3e 100644 --- a/parse/StorylineCmp.py +++ b/parse/StorylineCmp.py @@ -14,20 +14,20 @@ class CmpTool: def __init__(self): pass - def graph_compare(self, graph_new: Dict[str, StoryMap], graph_old: Dict[str, StoryMap]) -> Dict[ModifyReason, FragmentSlice]: + def graph_compare(self, graph_new: Dict[str, StoryMap], graph_old: Dict[str, StoryMap]) -> Dict[ModifyReason, List[FragmentSlice]]: fragments_has_removed = [] for story in graph_old.values(): - fragments_has_removed = fragments_has_removed + self.__event_remove_check(graph_new, story) + fragments_has_removed = fragments_has_removed + self.__slice_remove_check(graph_new, story) pass fragments_has_appended = [] for story in graph_new.values(): - fragments_has_appended = fragments_has_appended + self.__event_append_check(graph_old, story) + fragments_has_appended = fragments_has_appended + self.__slice_append_check(graph_old, story) pass fragments_has_changed = [] for story in graph_new.values(): - fragments_has_changed = fragments_has_changed + self.__event_changed_check(graph_old, story) + fragments_has_changed = fragments_has_changed + self.__slice_changed_check(graph_old, story) pass return { @@ -36,35 +36,78 @@ class CmpTool: ModifyReason.Changed: fragments_has_changed, } - def __event_remove_check(self, graph_new: Dict[str, StoryMap], story_old: StoryMap) -> List[FragmentSlice]: + def __slice_remove_check(self, graph_new: Dict[str, StoryMap], story_old: StoryMap) -> List[FragmentSlice]: list_retv = [] + s_name = story_old.story_name + story_curr = graph_new[s_name] + for slice in story_old.slice_list[1:]: + if story_curr is None: + list_retv.append(slice) + continue + + # 确定情节定义节点 if slice.is_define_node[0]: - s_name = story_old.story_name f_name = slice.is_define_node[1] - story_findout = graph_new[s_name] - if story_findout is None or story_findout.get_fragment_defined(f_name) is None: + if story_curr.get_fragment_defined(f_name) is None: + list_retv.append(slice) + pass + pass + # 确定情节引用节点 + else: + s_refer_name = slice.story_refer + f_refer_name = slice.fragm_refer + refer_curr = CmpTool.__locate_fragment_refer__(story_curr.slice_list[0], s_refer_name, f_refer_name) + if refer_curr is None: list_retv.append(slice) pass pass pass return list_retv - def __event_append_check(self, graph_old: Dict[str, StoryMap], story_new: StoryMap) -> List[FragmentSlice]: + def __slice_append_check(self, graph_old: Dict[str, StoryMap], story_new: StoryMap) -> List[FragmentSlice]: list_retv = [] + s_name = story_new.story_name + story_prev = graph_old[s_name] + for slice in story_new.slice_list[1:]: + if story_prev is None: + list_retv.append(slice) + continue + + # 确定情节定义节点 if slice.is_define_node[0]: - s_name = story_new.story_name f_name = slice.is_define_node[1] - story_findout = graph_old[s_name] - if story_findout is None or story_findout.get_fragment_defined(f_name) is None: + if story_prev.get_fragment_defined(f_name) is None: + list_retv.append(slice) + pass + pass + # 确定情节引用节点 + else: + s_refer_name = slice.story_refer + f_refer_name = slice.fragm_refer + refer_prev = CmpTool.__locate_fragment_refer__(story_prev.slice_list[0], s_refer_name, f_refer_name) + if refer_prev is None: list_retv.append(slice) pass pass pass + return list_retv - def __event_changed_check(self, graph_old: Dict[str, StoryMap], story_new: StoryMap) -> List[FragmentSlice]: + @classmethod + def __locate_fragment_refer__(cls, ref_story_head: FragmentSlice, story_self: str, fragm_self: str) -> FragmentSlice: + if ref_story_head is None: + return None + + # 确定属于引用节点 + if not ref_story_head.is_define_node[0]: + if ref_story_head.story_refer == story_self and ref_story_head.fragm_refer == fragm_self: + return ref_story_head + + return cls.__locate_fragment_refer__(ref_story_head.next_node, story_self, fragm_self) + + def __slice_changed_check(self, graph_old: Dict[str, StoryMap], story_new: StoryMap) -> List[FragmentSlice]: story_old = graph_old[story_new.story_name] if story_old is not None: return self.__event_changed_chack_for(story_old, story_new) @@ -73,19 +116,34 @@ class CmpTool: def __event_changed_chack_for(self, story_old: StoryMap, story_new: StoryMap) -> List[FragmentSlice]: list_retv = [] for fragm_curr in story_new.slice_list[1:]: + # 情节定义只比较定义节点本身 if fragm_curr.is_define_node[0]: fragm_prev = story_old.get_fragment_defined(fragm_curr.is_define_node[1]) + # 确定新旧图都定义了该情节 if fragm_prev is not None: if fragm_curr.fragm_sort_i != fragm_prev.fragm_sort_i: list_retv.append(fragm_curr) - pass - - if len(fragm_curr.text_sections) != len(fragm_prev.text_sections): + elif len(fragm_curr.text_sections) != len(fragm_prev.text_sections): list_retv.append(fragm_curr) elif fragm_curr.text_sections != fragm_prev.text_sections: list_retv.append(fragm_curr) pass pass pass + else: + refer_curr = fragm_curr + refer_prev = CmpTool.__locate_fragment_refer__(story_old.slice_list[0], refer_curr.story_refer, refer_curr.fragm_refer) + # 确定新旧图都定义了该引用节点 + if refer_prev is not None: + if refer_prev.fragm_sort_i != refer_curr.fragm_sort_i: + list_retv.append(refer_curr) + elif len(refer_prev.text_sections) != len(refer_curr.text_sections): + list_retv.append(refer_curr) + elif refer_prev.text_sections != refer_curr.text_sections: + list_retv.append(refer_curr) + pass + pass + pass pass + return list_retv \ No newline at end of file diff --git a/parse/__pycache__/StoryMap.cpython-312.pyc b/parse/__pycache__/StoryMap.cpython-312.pyc index 376af54..c7824f1 100644 Binary files a/parse/__pycache__/StoryMap.cpython-312.pyc and b/parse/__pycache__/StoryMap.cpython-312.pyc differ diff --git a/parse/__pycache__/StorylineCmp.cpython-312.pyc b/parse/__pycache__/StorylineCmp.cpython-312.pyc new file mode 100644 index 0000000..ffeea86 Binary files /dev/null and b/parse/__pycache__/StorylineCmp.cpython-312.pyc differ