diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 6094081..b7fe87f 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,7 +5,12 @@
+
+
+
+
+
@@ -35,15 +40,61 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -69,13 +120,15 @@
+
+
-
+
diff --git a/entry.py b/entry.py
index 2b0f420..e6055c3 100644
--- a/entry.py
+++ b/entry.py
@@ -1,17 +1,9 @@
-from PyQt5.QtWidgets import QWidget, QApplication
-from frame.CompareWindow import CompareWin
-from sys import argv
-from parse.ast_load import global_ast_path, AstParse
+from parse.StoryMap import XAST_ParseTool, storyline_list2map
+from parse.ast_load import global_ast_path
-tool = AstParse(global_ast_path)
-print(tool.generate_time())
-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
+astx = XAST_ParseTool(global_ast_path)
+storys = astx.story_list
+storys_map = storyline_list2map(storys)
+astx.storylines_plait(storys_map)
+print(storys_map)
\ No newline at end of file
diff --git a/frame/MergeView.py b/frame/MergeView.py
new file mode 100644
index 0000000..bd2f648
--- /dev/null
+++ b/frame/MergeView.py
@@ -0,0 +1,71 @@
+from PyQt5.QtWidgets import QWidget, QTextEdit, QTabWidget, QSplitter, QApplication, QVBoxLayout
+import sys
+from frame.ReferView import MemoryNode, RelateReferView, EmptyNode, MemorySkin
+from typing import List
+from PyQt5.Qt import Qt
+
+
+class MergeDestination:
+ def __init__(self, story_name:str, prev: MemoryNode, curr: MemoryNode):
+ self.prev_node = prev
+ self.curr_node = curr
+ self.storyline_name = story_name
+ pass
+
+
+class LinesMergeView(QWidget, MemorySkin):
+ def __init__(self, fdefs: MergeDestination, refdefs: List[MergeDestination], parent):
+ QWidget.__init__(self, parent)
+
+ self.define_line = fdefs
+ self.refers_line = refdefs
+
+ layout = QVBoxLayout(self)
+ splitter = QSplitter(Qt.Orientation.Vertical, self)
+ layout.addWidget(splitter)
+
+ self.tabs_con: QTabWidget = QTabWidget(self)
+ splitter.addWidget(self.tabs_con)
+
+ self.define_prev: QTextEdit = QTextEdit(self)
+ self.define_prev.setReadOnly(True)
+ self.tabs_con.addTab(self.define_prev, fdefs.storyline_name)
+ self.refers_views: List[RelateReferView] = []
+ for node in refdefs:
+ refer_view = RelateReferView(node.prev_node, node.curr_node, self)
+ self.tabs_con.addTab(refer_view, node.storyline_name)
+ self.refers_views.append(refer_view)
+ pass
+
+ self.mut_enter = QTextEdit(self)
+ splitter.addWidget(self.mut_enter)
+
+ pass
+
+ def load_from_mem(self) -> None:
+ self.define_prev.setText(self.define_line.prev_node.get_from_memory())
+ self.mut_enter.setText(self.define_line.curr_node.get_from_memory())
+
+ for v in self.refers_views:
+ v.load_from_mem()
+ pass
+
+ pass
+
+ def save_to_mem(self):
+ self.define_line.curr_node.set_to_memory(self.mut_enter.toPlainText())
+
+ for v in self.refers_views:
+ v.save_to_mem()
+ pass
+
+ pass
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ defx = MergeDestination("page0", EmptyNode(), EmptyNode())
+ refx0 = MergeDestination("pagex1", EmptyNode(), EmptyNode())
+ view = LinesMergeView(defx, [refx0], None)
+ view.show()
+ app.exec()
\ No newline at end of file
diff --git a/frame/ReferView.py b/frame/ReferView.py
new file mode 100644
index 0000000..0262cd7
--- /dev/null
+++ b/frame/ReferView.py
@@ -0,0 +1,82 @@
+from PyQt5.QtWidgets import QTextEdit, QWidget, QSplitter, QVBoxLayout, QApplication
+from PyQt5.Qt import Qt
+from abc import abstractmethod
+from typing import List
+import sys
+
+
+class MemoryNode:
+ @abstractmethod
+ def get_from_memory(self) -> str:
+ raise NotImplementedError("get_from_memory")
+
+ @abstractmethod
+ def set_to_memory(self, content: str):
+ raise NotImplementedError("set_to_memmory")
+
+
+class MemorySkin:
+ @abstractmethod
+ def load_from_mem(self) -> None:
+ raise NotImplementedError("load_from_mem")
+
+ @abstractmethod
+ def save_to_mem(self):
+ raise NotImplementedError("save_to_mem")
+
+
+class RelateReferView(QWidget, MemorySkin):
+ def __init__(self, prev_frefs: MemoryNode, curr_frefs: MemoryNode, parent):
+ """
+ 构建引用关系视图
+
+ :param prev_frefs: 上游节点
+ :param curr_frefs: 当前引用节点
+ """
+ QWidget.__init__(self, parent)
+ self.prev_fragment = prev_frefs
+ self.curr_fragment = curr_frefs
+ layout = QVBoxLayout(self)
+ layout.setContentsMargins(0, 0, 0, 0)
+
+ splitter_container = QSplitter(Qt.Orientation.Vertical, self)
+ layout.addWidget(splitter_container)
+
+ self.immut_view: QTextEdit = QTextEdit(self)
+ self.immut_view.setReadOnly(True)
+ self.mut_refers: QTextEdit = QTextEdit(self)
+ splitter_container.addWidget(self.immut_view)
+ splitter_container.addWidget(self.mut_refers)
+
+ pass
+
+ def load_from_mem(self):
+ """
+ 载入内存内容到视图
+ """
+ self.immut_view.setText(self.prev_fragment.get_from_memory())
+ self.mut_refers.setText(self.curr_fragment.get_from_memory())
+ pass
+
+ def save_to_mem(self):
+ """
+ 保存视图内容到内存
+ """
+ self.prev_fragment.set_to_memory(self.immut_view.toPlainText())
+ self.curr_fragment.set_to_memory(self.mut_refers.toPlainText())
+ pass
+
+
+class EmptyNode(MemoryNode):
+ def get_from_memory(self) -> str:
+ return "站位视图内容"
+
+ def set_to_memory(self, content: str):
+ pass
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ view = RelateReferView(EmptyNode(), EmptyNode(), None)
+ view.show()
+ app.exec()
diff --git a/frame/__pycache__/CompareWindow.cpython-312.pyc b/frame/__pycache__/CompareWindow.cpython-312.pyc
index 9f93294..8d10171 100644
Binary files a/frame/__pycache__/CompareWindow.cpython-312.pyc and b/frame/__pycache__/CompareWindow.cpython-312.pyc differ
diff --git a/frame/__pycache__/ReferView.cpython-312.pyc b/frame/__pycache__/ReferView.cpython-312.pyc
new file mode 100644
index 0000000..48187f7
Binary files /dev/null and b/frame/__pycache__/ReferView.cpython-312.pyc differ
diff --git a/frame/__pycache__/__init__.cpython-312.pyc b/frame/__pycache__/__init__.cpython-312.pyc
index 9cabe46..7a98443 100644
Binary files a/frame/__pycache__/__init__.cpython-312.pyc and b/frame/__pycache__/__init__.cpython-312.pyc differ
diff --git a/parse/StoryMap.py b/parse/StoryMap.py
new file mode 100644
index 0000000..7441793
--- /dev/null
+++ b/parse/StoryMap.py
@@ -0,0 +1,149 @@
+import xml.dom.minidom as mdom
+from typing import List, Tuple, Dict
+from enum import Enum
+from frame.ReferView import EmptyNode, MemoryNode
+
+
+class ModifyReason(Enum):
+ Removed = 0,
+ Append = 1,
+ Changed = 2,
+ Nothing = 4,
+
+
+class FragmentSlice(EmptyNode):
+ def __init__(self, def_mark: bool = False, name: str= ""):
+ self.is_define_node = (def_mark, name)
+ self.story_refer = ""
+ self.fragm_refer = ""
+
+ self.prev_node: 'FragmentSlice' = None # 上游节点
+ self.next_node: 'FragmentSlice' = None # 下游节点
+ self.refers_slice: Dict[str, 'FragmentSlice'] = {} # 引用切面
+
+ self.changed_prev_nodes: List[Tuple[ModifyReason, 'FragmentSlice']] = [] # 变更的上游节点
+ self.text_sections: List[str] = [] # 文本段落
+ pass
+
+ def get_from_memory(self) -> str:
+ return r"\n".join(self.text_sections)
+ pass
+
+ def set_to_memory(self, content: str):
+ self.text_sections = content.split(r"\n")
+ pass
+
+
+class StoryMap:
+ def __init__(self, name: str):
+ self.story_name = name
+ self.sort_index = 0
+ self.empty_head = FragmentSlice()
+ self.slice_list: List[FragmentSlice] = [self.empty_head]
+ pass
+
+ def append_fragment_slice(self, node: FragmentSlice):
+ self.slice_list[-1].next_node = node
+ node.prev_node = self.slice_list[-1]
+ self.slice_list.append(node)
+ pass
+
+ def get_fragment_defined(self, name: str) -> FragmentSlice:
+ for fit in self.slice_list:
+ if fit.is_define_node[0]:
+ if fit.is_define_node[1] == name:
+ return fit
+ pass
+ pass
+ return None
+
+
+class XAST_ParseTool:
+ def __init__(self, ast_path: str):
+ self.dom_tree = mdom.parse(ast_path)
+ self.story_list: List[StoryMap] = []
+ names = self.story_names()
+ for nm in names:
+ vinst = self.__extract_empty_storymap(nm)
+ self.__storynode_initfrags(vinst[0], vinst[1])
+ self.story_list.append(vinst[0])
+ pass
+
+ def story_names(self) -> List[str]:
+ """
+ 获取所有故事名称
+ :return: List[故事名]
+ """
+ storys = self.dom_tree.getElementsByTagName("story")
+ values = []
+ for n in storys:
+ values.append(n.getAttribute("name"))
+ pass
+ return values
+
+ def __extract_empty_storymap(self, name: str) -> Tuple[StoryMap, mdom.Element]:
+ storys = self.dom_tree.getElementsByTagName("story")
+ for node in storys:
+ if node.getAttribute("name") == name:
+ mem_node = StoryMap(name)
+ mem_node.sort_index = int(node.getAttribute("sort"))
+ return (mem_node, node)
+ pass
+ return None
+
+ def __storynode_initfrags(self, node: StoryMap, e: mdom.Element):
+ first_elem: mdom.Element = e.firstChild
+ while first_elem != None:
+ if hasattr(first_elem, "tagName"):
+ dom_tag = first_elem.tagName
+ if first_elem.tagName == "fragment":
+ fnode = FragmentSlice(True, first_elem.getAttribute("name"))
+ self.__fragmnode_init_desc(fnode, first_elem)
+ node.append_fragment_slice(fnode)
+ elif first_elem.tagName == "refer":
+ fnode = FragmentSlice()
+ fnode.story_refer = first_elem.getAttribute("story")
+ fnode.fragm_refer = first_elem.getAttribute("fragment")
+ self.__fragmnode_init_desc(fnode, first_elem)
+ node.append_fragment_slice(fnode)
+ pass
+ pass
+ first_elem = first_elem.nextSibling
+ pass
+
+ def __fragmnode_init_desc(self, node: FragmentSlice, e: mdom.Element):
+ node_elem: mdom.Element = e.firstChild
+ while node_elem != None:
+ if hasattr(node_elem, "tagName") and node_elem.tagName == "text-section":
+ line = node_elem.getAttribute("text")
+ node.text_sections.append(line)
+ pass
+ node_elem = node_elem.nextSibling
+ pass
+ pass
+
+ def storylines_plait(self, storys: Dict[str, StoryMap]):
+ for snode in storys.values():
+ self.__storyline_plait_within(snode, storys)
+ pass
+
+ def __storyline_plait_within(self, story: StoryMap, storys_map: Dict[str, StoryMap]):
+ for fnode in story.slice_list[1:]:
+ if not fnode.is_define_node[0]:
+ sname = fnode.story_refer
+ fname = fnode.fragm_refer
+ target_story = storys_map[sname]
+ target_fragment = target_story.get_fragment_defined(fname)
+ target_fragment.refers_slice[story.story_name] = fnode
+ pass
+ pass
+ pass
+ pass
+
+
+def storyline_list2map(storylines: List[StoryMap]) -> Dict[str, StoryMap]:
+ retv = {}
+ for linst in storylines:
+ retv[linst.story_name] = linst
+ pass
+ return retv
diff --git a/parse/StorylineCmp.py b/parse/StorylineCmp.py
new file mode 100644
index 0000000..5c6e2ab
--- /dev/null
+++ b/parse/StorylineCmp.py
@@ -0,0 +1,53 @@
+from parse.StoryMap import StoryMap, ModifyReason, FragmentSlice
+from typing import Dict, List
+
+
+class CmpTool:
+ def __init__(self):
+ pass
+
+ def graph_compare(self, graph_new: Dict[str, StoryMap], graph_old: Dict[str, StoryMap]):
+ fragments_has_removed = []
+ for story in graph_old.values():
+ fragments_has_removed = fragments_has_removed + self.__event_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)
+ pass
+
+
+
+
+ pass
+
+ def __event_remove_check(self, graph_new: Dict[str, StoryMap], story_old: StoryMap) -> List[FragmentSlice]:
+ list_retv = []
+ for slice in story_old.slice_list[1:]:
+ 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:
+ 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]:
+ list_retv = []
+ for slice in story_new.slice_list[1:]:
+ 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:
+ list_retv.append(slice)
+ pass
+ pass
+ pass
+ return list_retv
+
+ def __event_changed_check(self,):
\ No newline at end of file
diff --git a/parse/__pycache__/StoryMap.cpython-312.pyc b/parse/__pycache__/StoryMap.cpython-312.pyc
new file mode 100644
index 0000000..fa22917
Binary files /dev/null and b/parse/__pycache__/StoryMap.cpython-312.pyc differ
diff --git a/parse/__pycache__/__init__.cpython-312.pyc b/parse/__pycache__/__init__.cpython-312.pyc
index 08e7bfe..fd33a09 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 ba6adab..d3627ca 100644
Binary files a/parse/__pycache__/ast_load.cpython-312.pyc and b/parse/__pycache__/ast_load.cpython-312.pyc differ