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 @@
-
+
+
+
+
+
+
@@ -35,6 +40,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -94,28 +122,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -161,19 +167,19 @@
+
-
+
-
@@ -199,9 +205,14 @@
- file://$PROJECT_DIR$/manage/NovelManage.py
- 4
-
+ file://$PROJECT_DIR$/frame/CompareViews.py
+ 167
+
+
+
+ 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