diff --git a/ArgsParser/argsparser.cpp b/ArgsParser/argsparser.cpp index 5d0102a..8c3d99b 100644 --- a/ArgsParser/argsparser.cpp +++ b/ArgsParser/argsparser.cpp @@ -1,63 +1,72 @@ #include "argsparser.h" using namespace args_parse; +using namespace std; -ArgvPackImpl::ArgvPackImpl(const QString& means, ParamType t) : means_store(means), type_store(t) {} +ArgvPackImpl::ArgvPackImpl(const QString& means, ParamType t) : means_store(means), type_store(t) { } // 通过 ArgvPack 继承 -ParamType ArgvPackImpl::paramType() const { return type_store; } +ParamType ArgvPackImpl::paramType() const { + return type_store; +} -QString ArgvPackImpl::means() const { return means_store; } +QString ArgvPackImpl::means() const { + return means_store; +} void ArgvPackImpl::setValue(const QString& v) { this->value_store = v; } -QString ArgvPackImpl::value() const { return value_store; } +QString ArgvPackImpl::value() const { + return value_store; +} FloatArgvPack::FloatArgvPack(const QString& key, const QString& means, bool optional) : - FloatArgvImpl(key, means, optional) {} + FloatArgvImpl(key, means, optional) { } -QString FloatArgvImpl::bindKey() const { return key_name; } +QString FloatArgvImpl::bindKey() const { + return key_name; +} bool FloatArgvImpl::optional() const { return optional_value; } -int FloatArgvPack::matchLenth() const -{ +int FloatArgvPack::matchLenth() const { return 2; } -bool FloatArgvPack::parse(const QList args, int start) -{ +bool FloatArgvPack::parse(const QList args, int start) { auto args_t = args[start]; auto args_v = args[start + 1]; - if(args_t == bindKey()) + if (args_t == bindKey()) setValue(args_v); return args_t == bindKey(); } -IndexParam::IndexParam(const QString& means) : ArgvPackImpl(means, ParamType::IndexParam) {} +IndexParam::IndexParam(const QString& means) : ArgvPackImpl(means, ParamType::IndexParam) { } -int args_parse::IndexParam::matchLenth() const -{ +int IndexParam::matchLenth() const { return 1; } -bool args_parse::IndexParam::parse(const QList args, int start) -{ +bool IndexParam::parse(const QList args, int start) { setValue(args[start]); return true; } FloatArgvImpl::FloatArgvImpl(const QString& key, const QString& means, bool optional) - : ArgvPackImpl(means, ParamType::FloatParam), key_name(key), optional_value(optional) {} + : ArgvPackImpl(means, ParamType::FloatParam), key_name(key), optional_value(optional) { } -FloatOption::FloatOption(const QString &key, const QString &means, bool opt) - : FloatArgvImpl(key, means, opt) { setValue(u8"0"); } +FloatOption::FloatOption(const QString& key, const QString& means, bool opt) + : FloatArgvImpl(key, means, opt) { + setValue(u8"0"); +} -int FloatOption::matchLenth() const { return 1; } +int FloatOption::matchLenth() const { + return 1; +} bool FloatOption::parse(const QList args, int start) { auto args_t = args[start]; @@ -68,68 +77,88 @@ bool FloatOption::parse(const QList args, int start) { namespace args_parse { class MatchMode { private: - QList> args_mode; + QList> args_mode; int code_store; public: - explicit MatchMode(const QList> mode, int mode_code) :args_mode(mode), code_store(mode_code) {} + explicit MatchMode(const QList> mode, int mode_code) + :args_mode(mode), code_store(mode_code) { } - int modeCode()const {return code_store;} - QList> result() const {return args_mode;} - bool parse(const QList& args, int argv_start, int parse_index) { - if(argv_start >= args.size()) + /** + * @brief 获取模式代码 + * @return 模式代码 + */ + int modeCode()const { + return code_store; + } + /** + * @brief 获取模式匹配结果 + * @return 模式匹配结果 + */ + QList> result() const { + return args_mode; + } + /** + * @brief 解析匹配参数 + */ + bool parse(const QList& args, int argv_index, int parse_index) { + if (argv_index >= args.size()) return true; + // 获取模式匹配单元 auto parse_unit = args_mode[parse_index]; - switch (parse_unit->paramType()) { - case ParamType::IndexParam: { - parse_unit->parse(args, argv_start); - return parse(args, argv_start + parse_unit->matchLenth(), parse_index + 1); - }break; - case ParamType::FloatParam: { - QList> float_parsers; + case ParamType::IndexParam:// 固定位置索引匹配 + { + parse_unit->parse(args, argv_index); + // 继续匹配下一个为止 + return parse(args, argv_index + parse_unit->matchLenth(), parse_index + 1); + }break; + case ParamType::FloatParam:// 浮动参数匹配 + { + QList> float_parsers; - for (auto& unit : args_mode) { - if (unit->paramType() == ParamType::FloatParam) - float_parsers.append(std::dynamic_pointer_cast(unit)); - } + for (auto& unit : args_mode) { + if (unit->paramType() == ParamType::FloatParam) + float_parsers << dynamic_pointer_cast(unit); + } - for (auto& unit : float_parsers) { - if(unit->matchLenth() + argv_start > args.size()) - continue; + for (auto& unit : float_parsers) { + if (unit->matchLenth() + argv_index > args.size()) + continue; - auto result = unit->parse(args, argv_start); - if (result) - if (parse(args, argv_start + unit->matchLenth(), parse_index + 1)) - return true; - else if (!result && unit->optional()) - if (parse(args, argv_start, parse_index + 1)) - return true; - } - }break; + auto result = unit->parse(args, argv_index); + if (result) + if (parse(args, argv_index + unit->matchLenth(), parse_index + 1)) + return true; + else if (!result && unit->optional()) + if (parse(args, argv_index, parse_index + 1)) + return true; + } + }break; } return false; } }; } -void args_parse::ArgsParser::loadMode(int mode_code, const QList>& args_model) -{ - this->match_modes.append(std::make_shared(args_model, mode_code)); +void ArgsParser::loadMode(int mode_code, const QList>& args_model) { + this->match_modes.append(make_shared(args_model, mode_code)); } -std::tuple>> args_parse::ArgsParser::parse(int argc, char* argv[]){ +tuple>> ArgsParser::parse(int argc, char* argv[]) { + // 聚合参数数组 QList args_list; for (int idx = 0; idx < argc; idx++) { auto argv_str = QString::fromLocal8Bit(argv[idx]); args_list.append(argv_str); } + // 枚举模式匹配 for (auto& minst : this->match_modes) { if (minst->parse(args_list, 0, 0)) - return std::tuple>>(minst->modeCode(), minst->result()); + return tuple>>(minst->modeCode(), minst->result()); } - return std::tuple>>(0, QList>()); + return tuple>>(0, QList>()); } diff --git a/StoryPresent/.editorconfig b/StoryPresent/.editorconfig new file mode 100644 index 0000000..2f712d8 --- /dev/null +++ b/StoryPresent/.editorconfig @@ -0,0 +1,82 @@ +# Visual Studio 鐢熸垚浜嗗叿鏈 C++ 璁剧疆鐨 .editorconfig 鏂囦欢銆 +root = true + +[*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}] + +# Visual C++ 浠g爜鏍峰紡璁剧疆 + +cpp_generate_documentation_comments = xml + +# Visual C++ 鏍煎紡璁剧疆 + +cpp_indent_braces = false +cpp_indent_multi_line_relative_to = statement_begin +cpp_indent_within_parentheses = indent +cpp_indent_preserve_within_parentheses = true +cpp_indent_case_contents = true +cpp_indent_case_labels = false +cpp_indent_case_contents_when_block = true +cpp_indent_lambda_braces_when_parameter = false +cpp_indent_goto_labels = one_left +cpp_indent_preprocessor = leftmost_column +cpp_indent_access_specifiers = false +cpp_indent_namespace_contents = true +cpp_indent_preserve_comments = false +cpp_new_line_before_open_brace_namespace = same_line +cpp_new_line_before_open_brace_type = same_line +cpp_new_line_before_open_brace_function = ignore +cpp_new_line_before_open_brace_block = same_line +cpp_new_line_before_open_brace_lambda = ignore +cpp_new_line_scope_braces_on_separate_lines = true +cpp_new_line_close_brace_same_line_empty_type = true +cpp_new_line_close_brace_same_line_empty_function = true +cpp_new_line_before_catch = true +cpp_new_line_before_else = true +cpp_new_line_before_while_in_do_while = false +cpp_space_before_function_open_parenthesis = remove +cpp_space_within_parameter_list_parentheses = false +cpp_space_between_empty_parameter_list_parentheses = false +cpp_space_after_keywords_in_control_flow_statements = true +cpp_space_within_control_flow_statement_parentheses = false +cpp_space_before_lambda_open_parenthesis = false +cpp_space_within_cast_parentheses = false +cpp_space_after_cast_close_parenthesis = true +cpp_space_within_expression_parentheses = false +cpp_space_before_block_open_brace = true +cpp_space_between_empty_braces = true +cpp_space_before_initializer_list_open_brace = false +cpp_space_within_initializer_list_braces = true +cpp_space_preserve_in_initializer_list = true +cpp_space_before_open_square_bracket = false +cpp_space_within_square_brackets = false +cpp_space_before_empty_square_brackets = false +cpp_space_between_empty_square_brackets = false +cpp_space_group_square_brackets = true +cpp_space_within_lambda_brackets = false +cpp_space_between_empty_lambda_brackets = false +cpp_space_before_comma = false +cpp_space_after_comma = true +cpp_space_remove_around_member_operators = true +cpp_space_before_inheritance_colon = true +cpp_space_before_constructor_colon = true +cpp_space_remove_before_semicolon = true +cpp_space_after_semicolon = true +cpp_space_remove_around_unary_operator = true +cpp_space_around_binary_operator = insert +cpp_space_around_assignment_operator = insert +cpp_space_pointer_reference_alignment = left +cpp_space_around_ternary_operator = insert +cpp_use_unreal_engine_macro_formatting = true +cpp_wrap_preserve_blocks = never + +# Visual C++ 鍖呭惈娓呯悊璁剧疆 + +cpp_include_cleanup_add_missing_error_tag_type = suggestion +cpp_include_cleanup_remove_unused_error_tag_type = dimmed +cpp_include_cleanup_optimize_unused_error_tag_type = suggestion +cpp_include_cleanup_sort_after_edits = false +cpp_sort_includes_error_tag_type = none +cpp_sort_includes_priority_case_sensitive = false +cpp_sort_includes_priority_style = quoted +cpp_includes_style = default +cpp_includes_use_forward_slash = true diff --git a/StoryPresent/StoryPresent.vcxproj b/StoryPresent/StoryPresent.vcxproj new file mode 100644 index 0000000..98187e6 --- /dev/null +++ b/StoryPresent/StoryPresent.vcxproj @@ -0,0 +1,122 @@ +锘 + + + + Debug + x64 + + + Release + x64 + + + + {48DA8516-26EA-4D59-8913-7EF28E3F87C3} + QtVS_v304 + 10.0 + 10.0 + $(MSBuildProjectDirectory)\QtMsBuild + + + + Application + v143 + true + Unicode + + + Application + v143 + false + true + Unicode + + + + + + + 5.12.11_msvc2017_64 + core;xml;gui;widgets + debug + + + 5.12.11_msvc2017_64 + core;xml;gui;widgets + release + + + + + + + + + + + + + + + + + storym + + + StoryManage + + + + true + Level3 + true + true + + + Windows + true + + + + + true + Level3 + true + true + true + true + + + Windows + false + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/StoryPresent/StoryPresent.vcxproj.filters b/StoryPresent/StoryPresent.vcxproj.filters new file mode 100644 index 0000000..9464d77 --- /dev/null +++ b/StoryPresent/StoryPresent.vcxproj.filters @@ -0,0 +1,81 @@ +锘 + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + qml;cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + qrc;rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {99349809-55BA-4b9d-BF79-8FDBB0286EB3} + ui + + + {639EADAA-A684-42e4-A9AD-28FC9BCB8F7C} + ts + + + + + Resource Files + + + Header Files + + + Source Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Header Files + + + \ No newline at end of file diff --git a/StoryPresent/StoryPresent.vcxproj.user b/StoryPresent/StoryPresent.vcxproj.user new file mode 100644 index 0000000..8d0546a --- /dev/null +++ b/StoryPresent/StoryPresent.vcxproj.user @@ -0,0 +1,15 @@ +锘 + + + --graph --path D:\CustomNovel + WindowsLocalDebugger + + + + + + + + + + \ No newline at end of file diff --git a/StoryPresent/dag_layout.cpp b/StoryPresent/dag_layout.cpp new file mode 100644 index 0000000..c9b36c2 --- /dev/null +++ b/StoryPresent/dag_layout.cpp @@ -0,0 +1,301 @@ +#include "dag_layout.h" +using namespace dags; +using namespace graph_data; + +DAGLayerHelper::DAGLayerHelper(const Node& bind) + : bind_node(bind) { +} + +Node DAGLayerHelper::bindPoint() const { + return this->bind_node; +} + +int& dags::DAGLayerHelper::inputCount() { + return this->input_count; +} + +int dags::DAGLayerHelper::layerValue() const { + return this->layer_v; +} + +void dags::DAGLayerHelper::setLayerValue(int v) { + this->layer_v = v; +} + +void DAGLayerHelper::nextAppend(std::shared_ptr inst) { + if (this->next_points.contains(inst)) + return; + this->next_points.append(inst); + inst->input_count++; +} + +QList> DAGLayerHelper::nextNodes() const { + return this->next_points; +} + +DAGOrderHelper::DAGOrderHelper(std::shared_ptr bind) : layer_bind(bind) { + this->layer_number = bind->layerValue(); +} + +DAGOrderHelper::DAGOrderHelper(std::shared_ptr f, std::shared_ptr t) + : relate_bind(f), towards_to(t) { +} + +bool DAGOrderHelper::isFakeNode() const { + return !this->layer_bind; +} + +std::shared_ptr dags::DAGOrderHelper::layerNode() const { + return this->layer_bind; +} + +std::shared_ptr dags::DAGOrderHelper::relateNode() const { + return this->relate_bind; +} + +std::shared_ptr dags::DAGOrderHelper::towardsNode() const { + return this->towards_to; +} + +int dags::DAGOrderHelper::layerNumber() const { + return this->layer_number; +} + +void dags::DAGOrderHelper::setLayerNumber(int v) { + this->layer_number = v; +} + +double dags::DAGOrderHelper::sortNumber() const { + return this->sort_number; +} + +void dags::DAGOrderHelper::setSortNumber(double v) { + this->sort_number = v; +} + +QList> DAGOrderHelper::getUpstreamNodes() const { + return this->__prev_layer_nodes; +} + +void DAGOrderHelper::appendUpstreamNode(std::shared_ptr inst) { + this->__prev_layer_nodes.append(inst); +} + +void DAGGraph::rebuildFromEdges(const QList& arrow_list) { + for (auto& arr : arrow_list) { + auto start = arr.startPoint(); + std::shared_ptr start_helper; + if (this->graph_inst.contains(start.name())) { + start_helper = this->graph_inst[start.name()]; + } + else { + start_helper = std::make_shared(start); + this->graph_inst[start.name()] = start_helper; + } + + auto end = arr.endPoint(); + std::shared_ptr< DAGLayerHelper> end_helper; + if (this->graph_inst.contains(end.name())) { + end_helper = this->graph_inst[end.name()]; + } + else { + end_helper = std::make_shared(end); + this->graph_inst[end.name()] = end_helper; + } + + start_helper->nextAppend(end_helper); + } +} + +QList> dags::DAGGraph::nodeWithLayout() const { + return this->node_with_layout; +} + +int dags::DAGGraph::maxLayerCount() const { + return this->max_layer_count; +} + +std::shared_ptr DAGGraph::spawns_peak(QList>& ref_set) { + for (auto inst : ref_set) { + if (!inst->inputCount()) { + for (auto it_nxt : inst->nextNodes()) { + it_nxt->inputCount()--; + if (!ref_set.contains(it_nxt)) + ref_set << it_nxt; + } + + ref_set.removeAll(inst); + this->graph_inst.remove(inst->bindPoint().name()); + return inst; + } + } + + for (auto inst : this->graph_inst.values()) { + if (!inst->inputCount()) { + if (ref_set.contains(inst)) + ref_set.removeAll(inst); + + for (auto it_nxt : inst->nextNodes()) { + it_nxt->inputCount()--; + if (!ref_set.contains(it_nxt)) + ref_set << it_nxt; + } + + this->graph_inst.remove(inst->bindPoint().name()); + return inst; + } + } + + if (this->graph_inst.size()) { + throw "在有向无环图中发现环形回路。"; + } + + return std::shared_ptr(); +} + +void DAGGraph::graph_recovery(QList> sort_seqs) { + for (auto it : sort_seqs) { + it->inputCount() = 0; + } + + for (auto it : sort_seqs) { + for (auto nxt : it->nextNodes()) { + nxt->inputCount()++; + } + } + + this->graph_inst.clear(); + for (auto it : sort_seqs) { + this->graph_inst[it->bindPoint().name()] = it; + } +} + +int DAGGraph::node_layering(std::shared_ptr inst, int layer_current) { + auto max_remains = layer_current; + if (!layer_current || inst->layerValue() < layer_current) { + inst->setLayerValue(layer_current); + + for (auto fork : inst->nextNodes()) { + max_remains = std::max(this->node_layering(fork, inst->layerValue() + 1), max_remains); + } + } + + return max_remains + 1; +} + +int DAGGraph::node_layering_adj(std::shared_ptr inst) { + if (inst->inputCount() > 1) + return inst->layerValue() - 1; + + if (!inst->nextNodes().size()) + return inst->layerValue() - 1; + + auto layer_number = INT_MAX; + for (auto cinst : inst->nextNodes()) { + layer_number = std::min(layer_number, this->node_layering_adj(cinst)); + } + + inst->setLayerValue(layer_number); + return inst->layerValue() - 1; +} + +QList> DAGGraph::tidy_graph_nodes() { + QHash> nodes_temp; + for (auto node : this->graph_inst.values()) { + nodes_temp[node->bindPoint().name()] = std::make_shared(node); + } + + QList> temp_array; + temp_array.append(nodes_temp.values()); + + for (auto node : this->graph_inst.values()) { + for (auto next : node->nextNodes()) { + auto node_links = QList>{ + nodes_temp[node->bindPoint().name()] + }; + for (auto layer_index = node->layerValue() + 1; layer_index < next->layerValue(); ++layer_index) { + node_links.append(std::make_shared(node, next)); + node_links.last()->setLayerNumber(layer_index); + } + node_links.append(nodes_temp[next->bindPoint().name()]); + for (auto idx = 1; idx < node_links.size(); ++idx) { + auto start_point = node_links[idx - 1]; + auto end_point = node_links[idx]; + end_point->appendUpstreamNode(start_point); + } + temp_array.append(node_links.mid(1, node_links.size() - 2)); + } + } + return temp_array; +} + +void DAGGraph::graph_layer_nodes_sort(int layer_index, QList> nodes) { + QList> target_nodes_within_layer; + for (auto n : nodes) + if (n->layerNumber() == layer_index) { + target_nodes_within_layer.append(n); + } + + if (target_nodes_within_layer.size()) { + if (!layer_index) { + for (auto idx = 0; idx < target_nodes_within_layer.size(); ++idx) { + target_nodes_within_layer[idx]->setSortNumber(idx + 1); + } + } + else if (layer_index > 0) { + for (auto target_node : target_nodes_within_layer) { + QList prev_sorts; + auto upstream_list = target_node->getUpstreamNodes(); + std::transform(upstream_list.begin(), upstream_list.end(), + std::back_inserter(prev_sorts), [](std::shared_ptr inst) { + return inst->sortNumber(); + }); + + if (prev_sorts.size()) { + target_node->setSortNumber(std::accumulate(prev_sorts.begin(), prev_sorts.end(), 0) / prev_sorts.size()); + } + } + + std::sort(target_nodes_within_layer.begin(), target_nodes_within_layer.end(), + [](std::shared_ptr a, std::shared_ptr b) { + return a->sortNumber() < b->sortNumber(); + }); + + for (auto idx = 0; idx < target_nodes_within_layer.size(); ++idx) { + auto target_item = target_nodes_within_layer[idx]; + target_item->setSortNumber(idx + 1); + } + } + + this->graph_layer_nodes_sort(layer_index + 1, nodes); + } +} + +void dags::DAGGraph::graphLayout() { + QList> sort_seqs; + QList> refs; + while (1) { + auto peaks = this->spawns_peak(refs); + if (!peaks) + break; + + sort_seqs.append(peaks); + } + this->graph_recovery(sort_seqs); + for (auto item : sort_seqs) { + if (!item->inputCount()) { + this->max_layer_count = std::max(this->max_layer_count, this->node_layering(item, 0)); + } + } + for (auto item : sort_seqs) { + if (!item->inputCount()) { + this->node_layering_adj(item); + } + } + + auto tidy_nodes = this->tidy_graph_nodes(); + this->graph_layer_nodes_sort(0, tidy_nodes); + this->node_with_layout = tidy_nodes; +} + + diff --git a/StoryPresent/dag_layout.h b/StoryPresent/dag_layout.h new file mode 100644 index 0000000..cba2f24 --- /dev/null +++ b/StoryPresent/dag_layout.h @@ -0,0 +1,92 @@ +#pragma once +#include "data_type.h" +#include +#include +#include + +namespace dags { + /// + /// 节点分层辅助数据 + /// + class DAGLayerHelper { + private: + graph_data::Node bind_node; + int input_count = 0; + QList> next_points; + int layer_v = 0; + + public: + explicit DAGLayerHelper(const graph_data::Node& bind); + + graph_data::Node bindPoint() const; + int& inputCount(); + int layerValue() const; + void setLayerValue(int v); + void nextAppend(std::shared_ptr inst); + QList> nextNodes() const; + }; + + /// + /// 节点排序辅助节点 + /// + class DAGOrderHelper { + private: + std::shared_ptr layer_bind = nullptr; + std::shared_ptr relate_bind = nullptr; + std::shared_ptr towards_to = nullptr; + int layer_number = 0; + double sort_number = 0; + QList> __prev_layer_nodes; + + public: + /// + /// 构建一个可视图形节点 + /// + /// 图形节点 + DAGOrderHelper(std::shared_ptr bind); + /// + /// 构建一个辅助的不可视图形节点 + /// + /// 起始节点 + /// 终止节点 + DAGOrderHelper(std::shared_ptr f, std::shared_ptr t); + + bool isFakeNode() const; + + std::shared_ptr layerNode() const; + std::shared_ptr relateNode() const; + std::shared_ptr towardsNode() const; + + int layerNumber() const; + void setLayerNumber(int v); + + double sortNumber() const; + void setSortNumber(double v); + + QList> getUpstreamNodes() const; + + void appendUpstreamNode(std::shared_ptr inst); + }; + + class DAGGraph { + private: + QHash> graph_inst; + QList> node_with_layout; + int max_layer_count = 0; + + public: + void rebuildFromEdges(const QList& arrow_list); + QList> nodeWithLayout() const; + int maxLayerCount() const; + void graphLayout(); + + private: + std::shared_ptr spawns_peak(QList>& ref_set); + void graph_recovery(QList> sort_seqs); + int node_layering(std::shared_ptr inst, int layer_current); + int node_layering_adj(std::shared_ptr inst); + QList> tidy_graph_nodes(); + void graph_layer_nodes_sort(int layer_index, QList> nodes); + + }; +} diff --git a/StoryPresent/dag_present.cpp b/StoryPresent/dag_present.cpp new file mode 100644 index 0000000..dbe22f4 --- /dev/null +++ b/StoryPresent/dag_present.cpp @@ -0,0 +1,344 @@ +#include "dag_present.h" + +using namespace dags; + +ActivePresentNode::ActivePresentNode(const QString name, PrsnType type, QFont font) + : __GraphNodeBase(GraphNodeType::ActivePresentNode), + node_type(type), node_name(name), measure_base(font) {} + +QString ActivePresentNode::nodeName() const { + return this->node_name; +} + +QRectF ActivePresentNode::boundingRect() const { + auto rect = this->measure_base.boundingRect(this->node_name); + return rect += QMarginsF(0, 0, 10, 10); +} + +void ActivePresentNode::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { + auto outline = this->boundingRect(); + painter->save(); + if (this->node_type == PrsnType::NormalNode) + painter->fillRect(outline, Qt::gray); + else + painter->fillRect(outline, Qt::green); + + if (this->isHighlighted()) { + painter->setPen(Qt::red); + } + + painter->drawRect(outline); + painter->drawText(outline - QMarginsF(5, 5, 5, 5), this->node_name); + painter->restore(); +} + +PenetrateNode::PenetrateNode(double width, const QString& towards, const QString& to) + : __GraphNodeBase(GraphNodeType::PenetrateNode), + width_store(width), from_name(towards), to_name(to) { +} + +void PenetrateNode::resizeWidth(double width) { + this->width_store = width; + this->update(); +} + +QString PenetrateNode::nodeFrom() const { + return from_name; +} + +QString PenetrateNode::nodeTo() const { + return to_name; +} + +QRectF PenetrateNode::boundingRect() const { + return QRectF(0, 0, this->width_store, 8); +} + +void PenetrateNode::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { + auto outline = this->boundingRect(); + + painter->save(); + + if (this->isHighlighted()) + painter->fillRect(QRectF(0, -2, outline.width(), 4), Qt::red); + else + painter->fillRect(QRectF(0, -2, outline.width(), 4), Qt::black); + + painter->restore(); +} + +TransitionCurve::TransitionCurve(IGraphNode* start, IGraphNode* end, double prev_layer_width) + : __GraphNodeBase(GraphNodeType::TransitionCurve), + start_node(start), end_node(end), prev_layer_w(prev_layer_width) {} + +void TransitionCurve::layoutRefresh() { + auto orect = this->start_node->boundingRect(); + auto erect = this->end_node->boundingRect(); + + auto xpos = this->start_node->pos().x() + this->start_node->boundingRect().width(); + auto width_value = this->end_node->pos().x() - this->start_node->pos().x() - orect.width(); + + auto ypos = std::min(this->start_node->pos().y(), this->end_node->pos().y()); + auto bottom_y = std::max(this->start_node->pos().y() + orect.height(), this->end_node->pos().y() + erect.height()); + + this->outline = QRectF(0, 0, width_value, bottom_y - ypos); + this->setPos(xpos, ypos); +} + +QString TransitionCurve::nodeFrom() const { + if (start_node->nodeType() == GraphNodeType::ActivePresentNode) + return static_cast(start_node)->nodeName(); + else + return static_cast(start_node)->nodeFrom(); +} + +QString TransitionCurve::nodeTo() const { + if (end_node->nodeType() == GraphNodeType::ActivePresentNode) + return static_cast(end_node)->nodeName(); + else + return static_cast(end_node)->nodeTo(); +} + +QRectF TransitionCurve::boundingRect() const { + return this->outline; +} + +void TransitionCurve::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { + auto outline = this->boundingRect(); + + auto start_rect = this->start_node->boundingRect(); + auto end_rect = this->end_node->boundingRect(); + auto aj_start_pos = this->start_node->pos() + QPointF(start_rect.width(), 0); + auto aj_end_pos = this->end_node->pos(); + auto line_span = this->prev_layer_w - start_rect.width(); + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + + auto start_pos = aj_start_pos - QGraphicsItem::pos(); + auto end_pos = aj_end_pos - QGraphicsItem::pos(); + + auto line_epos = start_pos + QPointF(line_span, 0); + auto control_pos0 = line_epos + QPointF((outline.width() - line_span) / 3, 0); + auto control_pos1 = end_pos - QPointF((outline.width() - line_span) / 3, 0); + + auto npen = QPen(Qt::black); + if (this->isHighlighted()) + npen = QPen(Qt::red); + + npen.setWidthF(4); + painter->setPen(npen); + painter->drawLine(start_pos, line_epos); + + auto path0 = QPainterPath(); + path0.moveTo(line_epos); + path0.cubicTo(control_pos0, control_pos1, end_pos); + painter->drawPath(path0); + + painter->restore(); +} + +DAGActiveView::DAGActiveView(QWidget* parent) + : QGraphicsView(parent) { + this->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); + this->setScene(&this->scene_bind); + + auto f = this->font(); + f.setPixelSize(25); + this->setFont(f); +} + +QList DAGActiveView::layer_nodes_construction( + const QHash>& prev_layer_helper, + const QList>& total_datas, int layer_idx, double prev_layer_end) { + // 挑选当前层次节点 + QList> current_layer_datas; + std::copy_if(total_datas.begin(), total_datas.end(), std::back_inserter(current_layer_datas), + [&](std::shared_ptr ins) { return ins->layerNumber() == layer_idx; }); + std::sort(current_layer_datas.begin(), current_layer_datas.end(), + [](std::shared_ptra, std::shared_ptr b) { + return a->sortNumber() < b->sortNumber(); + }); + + QHash> current_nodes_helper; + // 当前层次无节点 + if (!current_layer_datas.size()) + return current_nodes_helper.keys(); + + // 构建当前层次图形节点 + double ypos_acc = 0; + for (auto& data_node : current_layer_datas) { + if (data_node->isFakeNode()) { + auto from = data_node->relateNode()->bindPoint().name(); + auto to = data_node->towardsNode()->bindPoint().name(); + auto curr_gnode = new PenetrateNode(20, from, to); + curr_gnode->setPos(prev_layer_end, ypos_acc); + ypos_acc += curr_gnode->boundingRect().height(); + + current_nodes_helper[curr_gnode] = data_node; + } + else { + auto node_type_vx = PrsnType::NormalNode; + if (!data_node->layerNode()->inputCount()) { + node_type_vx = PrsnType::StartNode; + } + auto curr_gnode = new ActivePresentNode(data_node->layerNode()->bindPoint().name(), node_type_vx, this->font()); + curr_gnode->setPos(prev_layer_end, ypos_acc); + ypos_acc += curr_gnode->boundingRect().height(); + + current_nodes_helper[curr_gnode] = data_node; + } + ypos_acc += this->node_span; + } + + // 对其当前层次节点宽度 + double curr_layer_width = 0; + for (auto n : current_nodes_helper.keys()) { + curr_layer_width = std::max(curr_layer_width, n->boundingRect().width()); + } + for (auto n : current_nodes_helper.keys()) { + if (n->nodeType() == GraphNodeType::PenetrateNode) { + static_cast(n)->resizeWidth(curr_layer_width); + } + } + + auto next_nodes = this->layer_nodes_construction(current_nodes_helper, + total_datas, layer_idx + 1, prev_layer_end + curr_layer_width + this->layer_span); + + // 构建层次连线 + if (prev_layer_helper.size()) { + double prev_layer_width = 0; + for (auto n : prev_layer_helper.keys()) { + prev_layer_width = std::max(prev_layer_width, n->boundingRect().width()); + } + + for (auto curr_gnode : current_nodes_helper.keys()) { + auto curr_data = current_nodes_helper[curr_gnode]; + auto upstream_nodes = curr_data->getUpstreamNodes(); + + for (auto prev_gnode : prev_layer_helper.keys()) { + auto prev_data = prev_layer_helper[prev_gnode]; + if (upstream_nodes.contains(prev_data)) { + auto line_cmbn = new TransitionCurve(prev_gnode, curr_gnode, prev_layer_width); + line_cmbn->layoutRefresh(); + next_nodes << line_cmbn; + } + } + } + } + + next_nodes.append(current_nodes_helper.keys()); + return next_nodes; +} + +void DAGActiveView::updateWithEdges(QList arrows) { + DAGGraph tools; + tools.rebuildFromEdges(arrows); + tools.graphLayout(); + + auto total_nodes = tools.nodeWithLayout(); + + auto gnodelist = this->layer_nodes_construction(QHash>(), total_nodes); + for (auto& gnode : gnodelist) { + this->scene_bind.addItem(dynamic_cast(gnode)); + total_graph_nodes << gnode; + } +} + +void DAGActiveView::highlightGraphLink(const QList arrows) { + for (auto& n : this->highlight_nodelist) + n->highlight(false); + this->highlight_nodelist.clear(); + + QList _color_path; + for (auto& a : arrows) { + _color_path << a.endPoint().name(); + _color_path << a.startPoint().name(); + } + auto set = _color_path.toSet(); + + //高亮关键路径点 + for (auto& node : this->total_graph_nodes) { + if (node->nodeType() == GraphNodeType::ActivePresentNode) { + auto x_node = static_cast(node); + if (set.contains(x_node->nodeName())) { + x_node->highlight(true); + this->highlight_nodelist << x_node; + } + } + } + + for (auto idx = 0; idx < arrows.size(); ++idx) { + auto start_name = arrows.at(idx).startPoint().name(); + auto end_name = arrows.at(idx).endPoint().name(); + + for (auto gnode : this->total_graph_nodes) { + switch (gnode->nodeType()) { + // 高亮占位节点 + case GraphNodeType::PenetrateNode: + { + auto pinst = dynamic_cast(gnode); + if (start_name == pinst->nodeFrom() && end_name == pinst->nodeTo()) { + gnode->highlight(true); + this->highlight_nodelist << gnode; + } + } break; + case GraphNodeType::TransitionCurve: + { + auto pinst = dynamic_cast(gnode); + if (start_name == pinst->nodeFrom() && end_name == pinst->nodeTo()) { + gnode->highlight(true); + this->highlight_nodelist << gnode; + } + } break; + default: break; + } + } + } + + this->scene_bind.update(); + this->update(); +} + +void DAGActiveView::mousePressEvent(QMouseEvent* ev) { + QGraphicsView::mousePressEvent(ev); + + if (ev->buttons().testFlag(Qt::LeftButton)) { + auto gitems = this->items(ev->pos()); + if (!gitems.size()) + return; + + for (auto& gitem : gitems) { + auto type_item = dynamic_cast(gitem); + if (type_item->nodeType() == GraphNodeType::ActivePresentNode) { + auto node = static_cast(type_item); + emit this->nodeClicked(ev->pos(), QList() << node->nodeName()); + break; + } + } + } +} + +__GraphNodeBase::__GraphNodeBase(GraphNodeType t) + :_node_type(t), _highlight_mark(false) {} + +GraphNodeType __GraphNodeBase::nodeType() const { + return _node_type; +} + +void __GraphNodeBase::highlight(bool mark) { + _highlight_mark = mark; + this->update(); +} + +bool __GraphNodeBase::isHighlighted() const { + return _highlight_mark; +} + +QPointF __GraphNodeBase::pos() const { + return QGraphicsItem::pos(); +} + +void __GraphNodeBase::setPos(qreal x, qreal y) { + QGraphicsItem::setPos(x, y); +} diff --git a/StoryPresent/dag_present.h b/StoryPresent/dag_present.h new file mode 100644 index 0000000..974cbcd --- /dev/null +++ b/StoryPresent/dag_present.h @@ -0,0 +1,123 @@ +#pragma once +#include +#include +#include +#include +#include +#include "data_type.h" +#include "dag_layout.h" + + +namespace dags { + enum class GraphNodeType { + ActivePresentNode, + PenetrateNode, + TransitionCurve, + }; + + class IGraphNode { + public: + virtual GraphNodeType nodeType() const = 0; + virtual void highlight(bool mark) = 0; + virtual bool isHighlighted() const = 0; + + virtual QRectF boundingRect() const = 0; + virtual QPointF pos() const = 0; + virtual void setPos(qreal x, qreal y) = 0; + }; + + class __GraphNodeBase : public QGraphicsItem, public IGraphNode { + private: + GraphNodeType _node_type; + bool _highlight_mark = false; + + public: + __GraphNodeBase(GraphNodeType t); + + // Inherited via IGraphNode + GraphNodeType nodeType() const override; + void highlight(bool mark) override; + bool isHighlighted() const override; + QPointF pos() const override; + void setPos(qreal x, qreal y) override; + }; + + enum class PrsnType { + StartNode, + NormalNode, + }; + class ActivePresentNode : public __GraphNodeBase { + private: + PrsnType node_type = PrsnType::NormalNode; + QString node_name; + QFontMetricsF measure_base; + + public: + ActivePresentNode(const QString name, PrsnType type, QFont font); + QString nodeName() const; + + // 通过 QGraphicsItem 继承 + QRectF boundingRect() const override; + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + }; + class PenetrateNode : public __GraphNodeBase { + private: + double width_store = 20; + QString from_name, to_name; + + public: + PenetrateNode(double width, const QString &from, const QString &to); + + void resizeWidth(double width); + QString nodeFrom() const; + QString nodeTo() const; + + // 通过 QGraphicsItem 继承 + QRectF boundingRect() const override; + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + }; + class TransitionCurve : public __GraphNodeBase { + private: + IGraphNode* start_node, * end_node; + double prev_layer_w = 0; + QRectF outline; + + public: + TransitionCurve(IGraphNode* start, IGraphNode* end, double prev_layer_width); + + void layoutRefresh(); + QString nodeFrom() const; + QString nodeTo() const; + + // 通过 QGraphicsItem 继承 + QRectF boundingRect() const override; + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + }; + + class DAGActiveView : public QGraphicsView { + Q_OBJECT + private: + double layer_span = 200; + double node_span = 10; + QGraphicsScene scene_bind; + QList highlight_nodelist; + QList total_graph_nodes; + + signals: + void nodeClicked(const QPointF &pos, const QList &node_name); + + public: + DAGActiveView(QWidget* parent = nullptr); + + QList layer_nodes_construction( + const QHash>& prev_layer, + const QList>& total_datas, + int layer_idx = 0, double prev_layer_end = 0); + + void updateWithEdges(QList arrows); + void highlightGraphLink(const QList color_path); + + virtual void mousePressEvent(QMouseEvent *ev) override; + }; +}; + diff --git a/StoryPresent/data_type.cpp b/StoryPresent/data_type.cpp new file mode 100644 index 0000000..7616cd8 --- /dev/null +++ b/StoryPresent/data_type.cpp @@ -0,0 +1,30 @@ +#include "data_type.h" + +using namespace graph_data; + +Node::Node(const QString& name) : _point_name(name) { } + +QString Node::name() const { + return _point_name; +} + +Line::Line(const Node& _p0, const Node& _p1) :_p0(_p0), _p1(_p1) {} + +Line::Line(const QString& n0, const QString& n1) : + Line(Node(n0), Node(n1)) {} + +QList Line::points() const { + return QList{this->_p0, this->_p1}; +} + +Arrow::Arrow(const Node& s, const Node& e) : Line(s, e) {} + +Arrow::Arrow(const QString& s, const QString& e) : Line(s, e) {} + +Node Arrow::startPoint() const { + return this->points()[0]; +} + +Node Arrow::endPoint() const { + return this->points()[1]; +} diff --git a/StoryPresent/data_type.h b/StoryPresent/data_type.h new file mode 100644 index 0000000..0b5be49 --- /dev/null +++ b/StoryPresent/data_type.h @@ -0,0 +1,44 @@ +#pragma once +#include +#include + + +namespace graph_data { + /// + /// 图形节点 + /// + class Node { + private: + QString _point_name; + + public: + explicit Node(const QString& name); + QString name() const; + }; + + /// + /// 图形边 + /// + class Line { + private: + Node _p0, _p1; + + public: + Line(const Node& _p0, const Node& _p1); + Line(const QString& n0, const QString& n1); + + QList points() const; + }; + + /// + /// 带方向的边 + /// + class Arrow : public Line { + public: + Arrow(const Node& s, const Node& e); + Arrow(const QString& s, const QString& e); + + Node startPoint() const; + Node endPoint() const; + }; +} \ No newline at end of file diff --git a/StoryPresent/main.cpp b/StoryPresent/main.cpp new file mode 100644 index 0000000..f9f6009 --- /dev/null +++ b/StoryPresent/main.cpp @@ -0,0 +1,17 @@ +#include "storypresent.h" +#include "dag_layout.h" +#include "xast_parse.h" +#include "dag_present.h" +#include "view_present.h" +#include +#include + +int main(int argc, char* argv[]) { + QApplication a(argc, argv); + + for (auto idx = 0; idx < argc; ++idx) { + qDebug() << argv[idx]; + } + + return a.exec(); +} diff --git a/StoryPresent/storyline_compare.cpp b/StoryPresent/storyline_compare.cpp new file mode 100644 index 0000000..5a752d0 --- /dev/null +++ b/StoryPresent/storyline_compare.cpp @@ -0,0 +1,200 @@ +#include "storyline_compare.h" + +using namespace compare; +using namespace xast_parse; +using namespace std; + + +Compare::Compare(const QHash>& graph) + : _graph_base(graph) {} + +const QHash>& compare::Compare::graphBind() const +{ + return _graph_base; +} + +QList> +Compare::changeCompare(Type _type, const QHash>& g_old) const { + QList> values; + + for (auto& type : _graph_base.keys()) { + if (g_old.contains(type)) { + auto story_new = _graph_base[type]; + auto story_old = g_old[type]; + + auto story_new_temp = story_new->firstChild(); + QList fragmets_define; + while (story_new_temp) { + if (story_new_temp->type() == SliceType::FragmentDefines) { + auto fragm = std::dynamic_pointer_cast(story_new_temp); + fragmets_define << fragm->name(); + } + story_new_temp = story_new_temp->nextSlice(); + } + + for (auto fragm : fragmets_define) { + auto fragm_this = std::dynamic_pointer_cast(story_new->getFragment(fragm)); + auto fragm_peer = std::dynamic_pointer_cast(story_old->getFragment(fragm)); + if (fragm_peer) { + if (_type == Type::FragmentAlter) { + if (fragment_check(fragm_this, fragm_peer)) + values << fragm_this; + } + else { + if (upstream_check(fragm_this, fragm_peer)) + values << fragm_this; + } + } + } + } + } + + return values; +} + +bool Compare::fragment_check(std::shared_ptr a, std::shared_ptr b) const +{ + auto texts_a = a->getTextNode(); + auto texts_b = b->getTextNode(); + + if (texts_a->getLines() != texts_b->getLines()) + return true; + + auto slices_a = a->referSlices(); + auto slices_b = b->referSlices(); + if (slices_a.size() != slices_b.size()) + return true; + + auto get_label = [](std::shared_ptr v) -> QString { + switch (v->type()) { + case SliceType::StoryDefines: + { + auto cast_it = std::dynamic_pointer_cast(v); + return "s:" + cast_it->name(); + }break; + default: + { + auto cast_it = std::dynamic_pointer_cast(v); + return "a:" + cast_it->name(); + }break; + } + }; + std::sort(slices_a.begin(), slices_a.end(), [&](std::shared_ptra, std::shared_ptrb) { + auto parent_a = a->parentSlice(); + auto parent_b = b->parentSlice(); + + auto label_a = get_label(parent_a.lock()); + auto label_b = get_label(parent_b.lock()); + + return label_a < label_b; + }); + std::sort(slices_b.begin(), slices_b.end(), [&](std::shared_ptra, std::shared_ptrb) { + auto parent_a = a->parentSlice(); + auto parent_b = b->parentSlice(); + + auto label_a = get_label(parent_a.lock()); + auto label_b = get_label(parent_b.lock()); + + return label_a < label_b; + }); + + for (auto idx = 0; idx < slices_a.size(); ++idx) { + auto refer_a = std::dynamic_pointer_cast(slices_a.at(idx)); + auto refer_b = std::dynamic_pointer_cast(slices_b.at(idx)); + + auto desc_a = refer_a->getTextNode()->getLines(); + auto desc_b = refer_b->getTextNode()->getLines(); + if (desc_a != desc_b) + return true; + } + + return false; +} + +bool Compare::upstream_check(std::shared_ptr a, std::shared_ptr b) const +{ + auto prev_slice_a = a->prevSlice().lock(); + auto prev_slice_b = b->prevSlice().lock(); + + if (!prev_slice_a && prev_slice_b) + return true; + if (prev_slice_a && !prev_slice_b) + return true; + if (prev_slice_a && prev_slice_b) { + if (prev_slice_a->type() != prev_slice_b->type()) + return true; + + QString desc_a, desc_b; + switch (prev_slice_a->type()) { + case SliceType::TextPragraph: + { + desc_a = std::dynamic_pointer_cast(prev_slice_a)->getLines(); + desc_b = std::dynamic_pointer_cast(prev_slice_b)->getLines(); + }break; + case SliceType::FragmentRefers: + { + desc_a = std::dynamic_pointer_cast(prev_slice_a)->getTextNode()->getLines(); + desc_b = std::dynamic_pointer_cast(prev_slice_b)->getTextNode()->getLines(); + }break; + default: + { + desc_a = std::dynamic_pointer_cast(prev_slice_a)->getTextNode()->getLines(); + desc_b = std::dynamic_pointer_cast(prev_slice_b)->getTextNode()->getLines(); + } + break; + } + + if (desc_a != desc_b) + return true; + } + + if (a->type() == SliceType::FragmentDefines) { + auto slices_a = std::dynamic_pointer_cast(a)->referSlices(); + auto slices_b = std::dynamic_pointer_cast(b)->referSlices(); + + if(slices_a.size() != slices_b.size()) + return true; + + auto get_label = [](std::shared_ptr v) -> QString { + switch (v->type()) { + case SliceType::StoryDefines: + { + auto cast_it = std::dynamic_pointer_cast(v); + return "s:" + cast_it->name(); + }break; + default: + { + auto cast_it = std::dynamic_pointer_cast(v); + return "a:" + cast_it->name(); + }break; + } + }; + std::sort(slices_a.begin(), slices_a.end(), [&](std::shared_ptra, std::shared_ptrb) { + auto parent_a = a->parentSlice(); + auto parent_b = b->parentSlice(); + + auto label_a = get_label(parent_a.lock()); + auto label_b = get_label(parent_b.lock()); + + return label_a < label_b; + }); + std::sort(slices_b.begin(), slices_b.end(), [&](std::shared_ptra, std::shared_ptrb) { + auto parent_a = a->parentSlice(); + auto parent_b = b->parentSlice(); + + auto label_a = get_label(parent_a.lock()); + auto label_b = get_label(parent_b.lock()); + + return label_a < label_b; + }); + + for (auto idx = 0; idx < slices_a.size(); ++idx) { + auto refer_a = std::dynamic_pointer_cast(slices_a.at(idx)); + auto refer_b = std::dynamic_pointer_cast(slices_b.at(idx)); + if(upstream_check(refer_a, refer_b)) + return true; + } + } + + return false; +} diff --git a/StoryPresent/storyline_compare.h b/StoryPresent/storyline_compare.h new file mode 100644 index 0000000..598111c --- /dev/null +++ b/StoryPresent/storyline_compare.h @@ -0,0 +1,41 @@ +#pragma once +#include "xast_parse.h" +#include + +namespace compare { + enum class Type { + UpstreamAlter, + FragmentAlter + }; + + class Compare { + private: + QHash> _graph_base; + + public: + explicit Compare(const QHash>& graph); + + const QHash> &graphBind() const; + + QList> changeCompare(Type type, + const QHash>& g_old) const; + + private: + /* + * @brief 情节相关内容比较 + * @param a 本情节 + * @param b 旧情节 + * @return true-有变更 + */ + bool fragment_check(std::shared_ptr a, std::shared_ptr b) const; + /* + * @brief 情节上游内容比较 + * @param a 本情节 + * @param b 旧情节 + * @return true-有变更 + */ + bool upstream_check(std::shared_ptr a, std::shared_ptr b) const; + }; + +} + diff --git a/StoryPresent/storypresent.cpp b/StoryPresent/storypresent.cpp new file mode 100644 index 0000000..81bfc64 --- /dev/null +++ b/StoryPresent/storypresent.cpp @@ -0,0 +1,9 @@ +#include "storypresent.h" + +StoryPresent::StoryPresent(QWidget *parent) + : QMainWindow(parent) +{ +} + +StoryPresent::~StoryPresent() +{} diff --git a/StoryPresent/storypresent.h b/StoryPresent/storypresent.h new file mode 100644 index 0000000..09967e4 --- /dev/null +++ b/StoryPresent/storypresent.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +class StoryPresent : public QMainWindow { + Q_OBJECT + +public: + StoryPresent(QWidget* parent = nullptr); + ~StoryPresent(); + +}; diff --git a/StoryPresent/storypresent.qrc b/StoryPresent/storypresent.qrc new file mode 100644 index 0000000..e9b70e0 --- /dev/null +++ b/StoryPresent/storypresent.qrc @@ -0,0 +1,4 @@ + + + + diff --git a/StoryPresent/view_present.cpp b/StoryPresent/view_present.cpp new file mode 100644 index 0000000..18821dd --- /dev/null +++ b/StoryPresent/view_present.cpp @@ -0,0 +1,323 @@ +#include "view_present.h" +#include +#include +#include +#include + +using namespace views; +using namespace xast_parse; +using namespace std; +using namespace compare; + + +StoryChangePresent::StoryChangePresent( + const QHash>& g_base, + const QHash>& g_old, + QWidget* parent) : + QWidget(parent), + base_compare(Compare(g_base)), g_old(g_old), + _type_appoint(new QToolBox(this)), + _fragment_summary(new QTableView(this)), + _upstream_summary(new QTableView(this)), + _change_model(new QStandardItemModel(this)), + _story_appoint(new QComboBox(this)), + _edit_splitter(new QSplitter(this)), + _define_view(new QListView(this)), + _refer_view(new QListView(this)), + _defn_fragment(new StorylineModel), + _curr_refslice(new StorylineModel) { + _type_appoint->layout()->setMargin(1); + _type_appoint->layout()->setSpacing(1); + + auto _base_layout = new QVBoxLayout(this); + _base_layout->setMargin(0); + _base_layout->setSpacing(0); + + // 功能区域左右分割 + auto area_splitter = new QSplitter(this); + _base_layout->addWidget(area_splitter); + area_splitter->addWidget(_type_appoint); + + // 编辑区域 + auto edit_area = new QWidget(this); + area_splitter->addWidget(edit_area); + auto edit_layout = new QVBoxLayout(edit_area); + edit_layout->setMargin(0); + edit_layout->addWidget(_story_appoint); + edit_layout->addWidget(_edit_splitter, 1); + + _edit_splitter->addWidget(_refer_view); + _edit_splitter->addWidget(_define_view); + + // ============================ + _type_appoint->addItem(_upstream_summary, u8"上游修改"); + _type_appoint->addItem(_fragment_summary, u8"情节修改"); + + auto font_default = this->font(); + font_default.setPointSize(16); + this->setFont(font_default); + _define_view->setModel(_defn_fragment); + _refer_view->setModel(_curr_refslice); + _fragment_summary->setModel(_change_model); + _upstream_summary->setModel(_change_model); + + // =========================== + connect(_type_appoint, &QToolBox::currentChanged, this, &StoryChangePresent::type_change); + type_change(0); + _define_view->setWordWrap(true); + _define_view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + _define_view->setItemDelegate(new StorylineDelegate(_define_view)); + + _refer_view->setWordWrap(true); + _refer_view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + _refer_view->setItemDelegate(new StorylineDelegate(_refer_view)); +} + +Compare& StoryChangePresent::compareTool() { + return this->base_compare; +} + +void StoryChangePresent::type_change(int idx) { + disconnect(_story_appoint, QOverload::of(&QComboBox::currentIndexChanged), nullptr, nullptr); + disconnect(_upstream_summary, &QTableView::pressed, nullptr, nullptr); + disconnect(_fragment_summary, &QTableView::pressed, nullptr, nullptr); + + _story_appoint->clear(); + _story_appoint->setEnabled(false); + _defn_fragment->loadCurrentSlice(nullptr); + _curr_refslice->loadCurrentSlice(nullptr); + _change_model->clear(); + + QList> fragms; + if (!idx) { + fragms = base_compare.changeCompare(Type::UpstreamAlter, g_old); + } + else { + fragms = base_compare.changeCompare(Type::FragmentAlter, g_old); + } + + _change_model->setHorizontalHeaderLabels(QStringList() << u8"故事名称" << u8"情节名称"); + for (auto fragm : fragms) { + auto story = dynamic_pointer_cast(fragm->parentSlice().lock()); + QList row; + row << new QStandardItem(story->name()); + row << new QStandardItem(fragm->name()); + _change_model->appendRow(row); + } + _upstream_summary->resizeColumnsToContents(); + _fragment_summary->resizeColumnsToContents(); + connect(_upstream_summary, &QTableView::pressed, this, &StoryChangePresent::fragment_selected); + connect(_fragment_summary, &QTableView::pressed, this, &StoryChangePresent::fragment_selected); +} + +pair combine_with(const QList& sections) { + if (sections.isEmpty()) + return pair(); + + auto head_rst = combine_with(sections.mid(0, sections.size() - 1)); + auto target_str = sections.last(); + auto final_rst = make_pair( + head_rst.first + "/" + target_str, + head_rst.second << 8 + target_str.size() + ); + return final_rst; +} + +void StoryChangePresent::fragment_selected(const QModelIndex& item) { + if (!item.isValid()) + return; + + disconnect(_story_appoint, QOverload::of(&QComboBox::currentIndexChanged), nullptr, nullptr); + _story_appoint->clear(); + _story_appoint->setEnabled(true); + + auto story_index = item.sibling(item.row(), 0); + auto fragm_index = item.sibling(item.row(), 1); + auto story_item = this->_change_model->itemFromIndex(story_index); + auto fragm_item = this->_change_model->itemFromIndex(fragm_index); + + auto story_inst = this->base_compare.graphBind()[story_item->text()]; + auto fragm_slice = story_inst->getFragment(fragm_item->text()); + auto fragm_def = dynamic_pointer_cast(fragm_slice); + _defn_fragment->loadCurrentSlice(fragm_def); + + connect(_story_appoint, QOverload::of(&QComboBox::currentIndexChanged), + this, &StoryChangePresent::storyline_selected); + + for (auto refer : fragm_def->referSlices()) { + auto def_v = combine_with(refer->nameSet()); + _story_appoint->addItem(def_v.first, def_v.second); + } +} + +void StoryChangePresent::storyline_selected(const QString& line) { + auto def_fragm = dynamic_pointer_cast(_defn_fragment->currentSlice()); + + auto flag = _story_appoint->currentData(); + for (auto slice : def_fragm->referSlices()) { + auto defv = combine_with(slice->nameSet()); + if (flag == defv.second && line == defv.first) { + _curr_refslice->loadCurrentSlice(slice); + break; + } + } +} + +void StorylineModel::loadCurrentSlice(shared_ptr target_node) { + this->beginResetModel(); + this->_current_slice = target_node; + _nodes_temp.clear(); + + if (this->_current_slice) { + auto pslice = this->_current_slice->parentSlice().lock(); + auto conn = dynamic_pointer_cast(pslice); + + auto temp_node = conn->firstChild(); + while (temp_node) { + _nodes_temp << temp_node; + temp_node = temp_node->nextSlice(); + } + } + this->endResetModel(); +} + +shared_ptr StorylineModel::currentSlice() { + return this->_current_slice; +} + +QModelIndex StorylineModel::index(int row, int column, const QModelIndex& parent) const { + return createIndex(row, column); +} + +QModelIndex StorylineModel::parent(const QModelIndex& child) const { + return QModelIndex(); +} + +int StorylineModel::rowCount(const QModelIndex& parent) const { + return _nodes_temp.size(); +} + +int StorylineModel::columnCount(const QModelIndex& parent) const { + return 1; +} + +QVariant StorylineModel::data(const QModelIndex& index, int role) const { + switch (role) { + case Qt::EditRole: + case Qt::DisplayRole: + { + auto curr_node = _nodes_temp.at(index.row()); + switch (curr_node->type()) { + case SliceType::TextPragraph: + { + auto node = dynamic_pointer_cast(curr_node); + return node->getLines(); + } + case SliceType::FragmentDefines: + { + auto node = dynamic_pointer_cast(curr_node); + return node->getTextNode()->getLines(); + } + case SliceType::FragmentRefers: + { + auto node = dynamic_pointer_cast(curr_node); + return node->getTextNode()->getLines(); + } + } + } + case Qt::BackgroundRole: + { + auto curr_node = _nodes_temp.at(index.row()); + if (curr_node == _current_slice) { + return QColor(Qt::lightGray); + } + return QColor(Qt::white); + } + default: + return QVariant(); + } +} + +Qt::ItemFlags StorylineModel::flags(const QModelIndex& idx) const { + auto flags = QAbstractItemModel::flags(idx); + auto curr_node = _nodes_temp.at(idx.row()); + if (curr_node == _current_slice) + return flags | Qt::ItemIsEditable; + return flags; +} + +bool StorylineModel::setData(const QModelIndex& index, const QVariant& value, int role) { + if (role == Qt::EditRole) { + auto curr_node = _nodes_temp.at(index.row()); + switch (curr_node->type()) { + case SliceType::TextPragraph: + { + auto tnode = dynamic_pointer_cast(curr_node); + tnode->setLines(value.toString()); + }break; + case SliceType::FragmentDefines: + { + auto defnode = dynamic_pointer_cast(curr_node); + defnode->getTextNode()->setLines(value.toString()); + }break; + case SliceType::FragmentRefers: + { + auto refnode = dynamic_pointer_cast(curr_node); + refnode->getTextNode()->setLines(value.toString()); + }break; + default: + return false; + } + + this->dataChanged(index, index, QVector() << Qt::EditRole << Qt::DisplayRole); + return true; + } + else { + return QAbstractItemModel::setData(index, value, role); + } +} + +#include +StorylineDelegate::StorylineDelegate(QAbstractItemView* pwidget) + :QStyledItemDelegate(pwidget), bind_view(pwidget) { } +QWidget* StorylineDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { + return new QTextEdit(bind_view); +} + +void StorylineDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const { + static_cast(editor)->setText(index.data(Qt::EditRole).toString()); +} + +void StorylineDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { + auto ned = static_cast(editor); + model->setData(index, ned->toPlainText(), Qt::EditRole); +} + +void StorylineDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const { + editor->setGeometry(option.rect); +} + +QSize StorylineDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { + return QStyledItemDelegate::sizeHint(option, index) + QSize(0, 20); +} + +#include +#include +#include +CompareWindow::CompareWindow(QFileInfo filebase, QFileInfo fileold) + :_graph_base(XAST_Parser(filebase.canonicalFilePath())), + _graph_old(XAST_Parser(fileold.canonicalFilePath())), + _cmp_widget(new StoryChangePresent(_graph_base.storyGraph(), _graph_old.storyGraph())) { + + auto mbar = menuBar(); + auto file = mbar->addMenu(u8"文件"); + file->addAction(u8"另存为", [=]() { + auto target = QFileDialog::getExistingDirectory(this, "获取目标文件夹", QDir::homePath()); + if (target == u8"") + return; + + this->_graph_base.output(QDir(target)); + }); + + this->setCentralWidget(_cmp_widget); +} diff --git a/StoryPresent/view_present.h b/StoryPresent/view_present.h new file mode 100644 index 0000000..607710b --- /dev/null +++ b/StoryPresent/view_present.h @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xast_parse.h" +#include "storyline_compare.h" + + +namespace views { + class StorylineModel : public QAbstractItemModel { + private: + QList> _nodes_temp; + std::shared_ptr _current_slice = nullptr; + + public: + void loadCurrentSlice(std::shared_ptr target_slice); + std::shared_ptr currentSlice(); + + // Inherited via QAbstractItemModel + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const override; + virtual QModelIndex parent(const QModelIndex& child) const override; + virtual int rowCount(const QModelIndex& parent) const override; + virtual int columnCount(const QModelIndex& parent) const override; + virtual QVariant data(const QModelIndex& index, int role) const override; + + virtual Qt::ItemFlags flags(const QModelIndex& idx) const override; + virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); + }; + + class StorylineDelegate : public QStyledItemDelegate { + private: + QWidget* const bind_view; + public: + explicit StorylineDelegate(QAbstractItemView* pwidget); + + virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + virtual void setEditorData(QWidget* editor, const QModelIndex& index) const override; + virtual void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; + virtual void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + + virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; + }; + + class StoryChangePresent : public QWidget { + public: + StoryChangePresent( + const QHash>& g_base, + const QHash>& g_old, + QWidget* parent = nullptr); + + compare::Compare& compareTool(); + + private: + compare::Compare base_compare; + QHash> g_old; + + QToolBox* const _type_appoint; + QTableView* const _fragment_summary; // 本节点修改汇总 + QTableView* const _upstream_summary; // 上游节点修改汇总 + QStandardItemModel* const _change_model; + + QComboBox* const _story_appoint; + + QSplitter* const _edit_splitter; + QListView* const _define_view; + QListView* const _refer_view; + + StorylineModel* const _defn_fragment; + StorylineModel* const _curr_refslice; + + void type_change(int idx); + void fragment_selected(const QModelIndex& item); + void storyline_selected(const QString& line); + }; + + class CompareWindow : public QMainWindow { + private: + xast_parse::XAST_Parser _graph_base, _graph_old; + StoryChangePresent *const _cmp_widget; + + public: + CompareWindow(QFileInfo file_base, QFileInfo file_old); + }; +} + diff --git a/StoryPresent/xast_parse.cpp b/StoryPresent/xast_parse.cpp new file mode 100644 index 0000000..2908936 --- /dev/null +++ b/StoryPresent/xast_parse.cpp @@ -0,0 +1,605 @@ +#include "xast_parse.h" + +using namespace xast_parse; +using namespace std; + +__SiblingImpl::__SiblingImpl(SliceType t, shared_ptr pnode) + : t_store(t), parent_store(pnode) { } + +SliceType __SiblingImpl::type() const { + return t_store; +} + +weak_ptr __SiblingImpl::parentSlice() const { + return this->parent_store; +} + +void __SiblingImpl::setPrev(shared_ptr inst) { + this->prev_store = inst; +} + +weak_ptr __SiblingImpl::prevSlice() const { + return this->prev_store; +} + +void __SiblingImpl::setNext(shared_ptr next) { + if (this->next_store) { + this->next_store->setPrev(next); + next->setNext(this->next_store); + } + + next->setPrev(this->shared_from_this()); + this->next_store = next; +} + +shared_ptr __SiblingImpl::nextSlice() const { + return this->next_store; +} + +uint __SiblingImpl::index() const { + uint v_index = 0; + auto temp_node = this->prevSlice(); + while (temp_node.lock()) { + v_index++; + temp_node = temp_node.lock()->prevSlice(); + } + return v_index; +} + +TextParagraph::TextParagraph(shared_ptr parent) + : __SiblingImpl(SliceType::TextPragraph, parent) { } + +void TextParagraph::setLines(const QString& contents) { + this->lines = contents.split('\n'); +} + +QString TextParagraph::getLines() const { + return lines.join('\n'); +} + +void TextParagraph::addLine(const QString& line) { + this->lines << line; +} + +QList TextParagraph::nameSet() const { + return parentSlice().lock()->nameSet() << "$$"; +} + +StoryDefine::StoryDefine(const QString& name, uint sort, const QString& path) + :__CollectionElement(SliceType::StoryDefines, nullptr), + name_store(name), sort_store(sort), file_path(path) { } + +QString StoryDefine::name() const { + return this->name_store; +} + +QString xast_parse::StoryDefine::bindPath() const +{ + return file_path; +} + +shared_ptr StoryDefine::getFragment(const QString& name) { + auto temp_node = this->firstChild(); + while (temp_node) { + if (temp_node->type() == SliceType::FragmentDefines) { + if (dynamic_pointer_cast(temp_node)->name() == name) + return temp_node; + } + temp_node = temp_node->nextSlice(); + } + return nullptr; +} + +QList StoryDefine::nameSet() const { + return QList() << name(); +} + +uint StoryDefine::index() const { + return this->sort_store; +} + +FragmentDefine::FragmentDefine(const QString& name, shared_ptr pnode) + : __DesElement(SliceType::FragmentDefines, static_pointer_cast(pnode)), name_store(name) { } + +QString FragmentDefine::name() const { + return name_store; +} + +QList> FragmentDefine::referSlices() const { + return this->refs_list; +} + +void FragmentDefine::appendRefer(shared_ptr slice) { + if (this->refs_list.contains(slice)) + return; + this->refs_list << slice; +} + +QList FragmentDefine::nameSet() const { + return parentSlice().lock()->nameSet() << name(); +} + +__CollectionElement::__CollectionElement(SliceType t, shared_ptr pnode) :__SiblingImpl(t, pnode) { } + +shared_ptr __CollectionElement::firstChild() const { + return this->first_head; +} + +void __CollectionElement::setFirstChild(shared_ptr inst) { + this->first_head = inst; +} + +__DesElement::__DesElement(SliceType t, shared_ptr pnode) + : __SiblingImpl(t, pnode) { } + +shared_ptr __DesElement::getTextNode() const { + if (!text_store) + const_cast<__DesElement*>(this)->text_store = make_shared( + const_pointer_cast<__SiblingImpl>(this->shared_from_this())); + + return text_store; +} + +FragmentRefer::FragmentRefer(const QString& story, const QString& fragm, shared_ptr pnode) + : __DesElement(SliceType::FragmentRefers, pnode), story_refers(story), fragment_refers(fragm) { } + +QString FragmentRefer::storyRefer() const { + return this->story_refers; +} + +QString FragmentRefer::fragmentRefer() const { + return this->fragment_refers; +} + +weak_ptr FragmentRefer::referTarget() const { + return this->refer_targets; +} + +void FragmentRefer::setReferTowards(shared_ptr target) { + this->refer_targets = target; +} + +QList FragmentRefer::nameSet() const { + return parentSlice().lock()->nameSet() << "@" + storyRefer() << fragmentRefer(); +} + +ArticleDefine::ArticleDefine(const QString& name, shared_ptr pnode) + : __CollectionElement(SliceType::ArticleDefines, pnode), name_store(name) { } + +QString ArticleDefine::name() const { + return name_store; +} + +QList ArticleDefine::nameSet() const { + return parentSlice().lock()->nameSet() << name(); +} + +VolumeDefine::VolumeDefine(const QString& name, const QString& path) + : __CollectionElement(SliceType::VolumeDefines, nullptr), name_store(name), file_path(path) { } + +QString VolumeDefine::name() const { + return name_store; +} + +QString VolumeDefine::bindPath() const { + return file_path; +} + +QList VolumeDefine::getArticleNames() const { + QList names; + auto first_elm = this->firstChild(); + while (first_elm) { + if (first_elm->type() == SliceType::ArticleDefines) { + names << static_pointer_cast(first_elm)->name(); + } + first_elm = first_elm->nextSlice(); + } + return names; +} + +shared_ptr VolumeDefine::getArticleWith(const QString& name) const { + auto temp_elm = this->firstChild(); + while (temp_elm) { + if (temp_elm->type() == SliceType::ArticleDefines) { + auto conv_elm = static_pointer_cast(temp_elm); + if (conv_elm->name() == name) { + return conv_elm; + } + } + temp_elm = temp_elm->nextSlice(); + } + return nullptr; +} + +QList VolumeDefine::nameSet() const { + return QList() << name(); +} + +XAST_Parser::XAST_Parser(const QString& ast_path) { + QFile ast_file(ast_path); + if (!ast_file.open(QIODevice::Text | QIODevice::ReadOnly)) + throw new exception("无法打开指定文件:" + ast_path.toLocal8Bit()); + + this->dom_tree.setContent(&ast_file); + auto ranks = this->dom_tree.elementsByTagName(u8"rank"); + for (auto idx = 0; idx < ranks.size(); ++idx) { + auto re = ranks.at(idx).toElement(); + auto path = re.attribute(u8"doc-path"); + auto rankv = re.attribute(u8"rank"); + auto inst = std::make_shared(path, rankv); + this->rank_coll.append(inst); + } + + auto nodes = this->dom_tree.elementsByTagName(u8"story"); + for (auto idx = 0; idx < nodes.size(); ++idx) { + auto nd = nodes.at(idx); + auto sinst = this->init_story_define(nd.toElement()); + story_dict[sinst->name()] = sinst; + } + + nodes = this->dom_tree.elementsByTagName(u8"volume"); + for (auto idx = 0; idx < nodes.size(); ++idx) { + auto nd = nodes.at(idx); + auto vinst = this->init_volume_define(nd.toElement()); + volume_dict[vinst->name()] = vinst; + } + + for (auto sit : story_dict.values()) { + this->fragments_plait(story_dict, sit); + } + for (auto vit : volume_dict.values()) { + this->fragments_plait(story_dict, vit); + } + + ast_file.close(); +} + +QList> XAST_Parser::rankList() const { + return rank_coll; +} + +QHash> XAST_Parser::storyGraph() const { + return story_dict; +} + +QHash> XAST_Parser::volumeGraph() const { + return volume_dict; +} + +void xast_parse::XAST_Parser::output(const QDir& dir) { + for (auto& r : this->rankList()) { + this->write_rank(r, dir); + } + for (auto& s : this->storyGraph()) { + this->write_story(s, dir); + } + for (auto& v : this->volumeGraph()) { + this->write_volume(v, dir); + } +} + +shared_ptr XAST_Parser::init_story_define(const QDomElement& story_e) { + auto s_name = story_e.attribute(u8"name"); + auto s_sort = story_e.attribute(u8"sort").toUInt(); + auto s_path = story_e.attribute(u8"file-path"); + auto inst = make_shared(s_name, s_sort, s_path); + + QList elms_list; + auto temp_elm = story_e.firstChildElement(); + while (!temp_elm.isNull()) { + if (temp_elm.tagName() != u8"tokens") + elms_list << temp_elm; + temp_elm = temp_elm.nextSiblingElement(); + } + + shared_ptr temporary_ptr = nullptr; + while (elms_list.size()) { + shared_ptr current_ptr = nullptr; + auto text_count = this->text_sections_count(elms_list); + if (text_count) { + current_ptr = this->text_paragraph_build(inst, elms_list, text_count); + } + else if (elms_list.at(0).tagName() == u8"fragment") { + current_ptr = this->fragment_define_build(inst, elms_list.at(0)); + elms_list.takeFirst(); + } + else if (elms_list.at(0).tagName() == u8"refer") { + current_ptr = this->fragment_refer_build(inst, elms_list.at(0)); + elms_list.takeFirst(); + } + + if (!temporary_ptr) { + inst->setFirstChild(current_ptr); + } + else { + temporary_ptr->setNext(current_ptr); + } + temporary_ptr = current_ptr; + } + + return inst; +} + +uint XAST_Parser::text_sections_count(const QList& elms) { + uint count = 0; + for (auto enode : elms) { + if (enode.tagName() != u8"text-section") + break; + + count++; + } + return count; +} + +shared_ptr XAST_Parser::text_paragraph_build(shared_ptr<__CollectionElement> pnode, QList& elms, uint count) { + auto inst = make_shared(pnode); + for (uint idx = 0; idx < count; ++idx) { + auto telm = elms.takeAt(0); + auto line = telm.attribute(u8"text"); + inst->addLine(line); + } + return inst; +} + +shared_ptr XAST_Parser::fragment_define_build(shared_ptr pnode, const QDomElement& e) { + auto fname = e.attribute(u8"name"); + auto fragm = make_shared(fname, pnode); + + auto temp_elm = e.firstChildElement(); + while (!temp_elm.isNull()) { + if (temp_elm.tagName() == u8"text-section") { + auto line = temp_elm.attribute(u8"text"); + fragm->getTextNode()->addLine(line); + } + temp_elm = temp_elm.nextSiblingElement(); + } + + return fragm; +} + +shared_ptr XAST_Parser::fragment_refer_build(shared_ptr<__CollectionElement> pnode, const QDomElement& e) { + auto story_ref = e.attribute(u8"story"); + auto fragm_ref = e.attribute(u8"fragment"); + auto fragmemt_refi = make_shared(story_ref, fragm_ref, pnode); + + auto temp_node = e.firstChildElement(); + while (!temp_node.isNull()) { + if (temp_node.tagName() == u8"text-section") { + auto line = temp_node.attribute(u8"text"); + fragmemt_refi->getTextNode()->addLine(line); + } + temp_node = temp_node.nextSiblingElement(); + } + return fragmemt_refi; +} + +void XAST_Parser::fragments_plait(QHash> story_map, shared_ptr reflist) { + auto slice = reflist->firstChild(); + while (slice) { + if (slice->type() == SliceType::FragmentRefers) { + auto refer_inst = static_pointer_cast(slice); + auto story_target = story_map[refer_inst->storyRefer()]; + auto fragm_target = story_target->getFragment(refer_inst->fragmentRefer()); + auto fragm_cast = static_pointer_cast(fragm_target); + + fragm_cast->appendRefer(refer_inst); + refer_inst->setReferTowards(fragm_cast); + } + switch (slice->type()) { + case SliceType::StoryDefines: + case SliceType::VolumeDefines: + case SliceType::ArticleDefines: + this->fragments_plait(story_map, static_pointer_cast<__CollectionElement>(slice)); + break; + + default: + break; + } + slice = slice->nextSlice(); + } +} + +shared_ptr XAST_Parser::init_volume_define(const QDomElement& volume_e) { + auto v_name = volume_e.attribute(u8"name"); + auto v_path = volume_e.attribute(u8"file-path"); + auto inst = make_shared(v_name, v_path); + + QList node_list; + auto temp_node = volume_e.firstChildElement(); + while (!temp_node.isNull()) { + if (temp_node.tagName() != u8"tokens") + node_list << temp_node; + temp_node = temp_node.nextSiblingElement(); + } + + shared_ptr temporary_ptr = nullptr; + while (node_list.size()) { + shared_ptr current_ptr = nullptr; + auto text_count = this->text_sections_count(node_list); + if (text_count) { + current_ptr = this->text_paragraph_build(inst, node_list, text_count); + } + else if (node_list.at(0).tagName() == u8"article") { + current_ptr = this->init_article_define(inst, node_list.at(0)); + node_list.takeFirst(); + } + + if (!temporary_ptr) { + inst->setFirstChild(current_ptr); + } + else { + temporary_ptr->setNext(current_ptr); + } + temporary_ptr = current_ptr; + } + return inst; +} + +shared_ptr XAST_Parser::init_article_define(shared_ptr pnode, const QDomElement& article_e) { + auto a_name = article_e.attribute(u8"name"); + auto inst = make_shared(a_name, pnode); + + QList node_list; + auto temp_node = article_e.firstChildElement(); + while (!temp_node.isNull()) { + if (temp_node.tagName() != u8"tokens") + node_list << temp_node; + temp_node = temp_node.nextSiblingElement(); + } + + shared_ptr temporary_ptr = nullptr; + while (node_list.size()) { + shared_ptr current_ptr = nullptr; + auto text_count = this->text_sections_count(node_list); + if (text_count) { + current_ptr = this->text_paragraph_build(inst, node_list, text_count); + } + else if (node_list.at(0).tagName() == u8"refer") { + current_ptr = this->fragment_refer_build(inst, node_list.at(0)); + node_list.takeFirst(); + } + + if (!temporary_ptr) { + inst->setFirstChild(current_ptr); + } + else { + temporary_ptr->setNext(current_ptr); + } + temporary_ptr = current_ptr; + } + + return inst; +} + +#include +#include +#include +void xast_parse::XAST_Parser::write_rank(std::shared_ptr inst, const QDir& odir) { + auto fname = inst->bindPath(); + QFile target_file(odir.filePath(fname)); + if (!target_file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(nullptr, "文件操作", odir.filePath(fname) + "无法打开!"); + QCoreApplication::exit(0); + return; + } + QTextStream o(&target_file); + o.setCodec("UTF-8"); + o << QString(u8"#排序 ") << inst->rank() << endl; +} + +void xast_parse::XAST_Parser::write_story(std::shared_ptr inst, const QDir& odir) +{ + auto fname = inst->bindPath(); + QFile target_file(odir.filePath(fname)); + if (!target_file.open(QIODevice::Append | QIODevice::Text)) { + QMessageBox::critical(nullptr, "文件操作", odir.filePath(fname) + "无法打开!"); + QCoreApplication::exit(0); + return; + } + QTextStream o(&target_file); + o.setCodec("UTF-8"); + o << QString(u8"{故事 ") << inst->name() << "\n"; + + auto temp = inst->firstChild(); + while (temp) { + switch (temp->type()) { + case SliceType::TextPragraph: + this->write_text(1, std::dynamic_pointer_cast(temp), o); + break; + case SliceType::FragmentDefines: + this->write_fragmdef(1, std::dynamic_pointer_cast(temp), o); + break; + case SliceType::FragmentRefers: + this->write_fragmref(1, std::dynamic_pointer_cast(temp), o); + break; + default: + break; + } + + temp = temp->nextSlice(); + } + + o << QString(u8"}\n"); +} + +void xast_parse::XAST_Parser::write_text(int dep, std::shared_ptr inst, QTextStream& out) +{ + auto text = inst->getLines(); + text.replace("\n", "\n" + QString(dep * 2, ' ')); + out << QString(dep * 2, ' ') << text << "\n"; +} + +void xast_parse::XAST_Parser::write_fragmdef(int dep, std::shared_ptr inst, QTextStream& out) +{ + out << QString(dep * 2, ' ') << QString(u8"{情节 %1\n").arg(inst->name()); + this->write_text(dep + 1, inst->getTextNode(), out); + out << QString(dep * 2, ' ') << QString(u8"}\n"); +} + +void xast_parse::XAST_Parser::write_fragmref(int dep, std::shared_ptr inst, QTextStream& out) +{ + out << QString(dep * 2, ' ') << QString(u8"{@情节 %1&%2\n").arg(inst->fragmentRefer(), inst->storyRefer()); + this->write_text(dep + 1, inst->getTextNode(), out); + out << QString(dep * 2, ' ') << QString(u8"}\n"); +} + +void xast_parse::XAST_Parser::write_volume(std::shared_ptr inst, const QDir& odir) +{ + auto fname = inst->bindPath(); + QFile target_file(odir.filePath(fname)); + if (!target_file.open(QIODevice::Append | QIODevice::Text)) { + QMessageBox::critical(nullptr, "文件操作", odir.filePath(fname) + "无法打开!"); + QCoreApplication::exit(0); + return; + } + QTextStream o(&target_file); + o.setCodec("UTF-8"); + o << QString(u8"{分卷 %1\n").arg(inst->name()); + + auto temp = inst->firstChild(); + while (temp) { + switch (temp->type()) { + case SliceType::TextPragraph: + this->write_text(1, std::dynamic_pointer_cast(temp), o); + break; + case SliceType::ArticleDefines: + this->write_article(1, std::dynamic_pointer_cast(temp), o); + break; + default: + break; + } + temp = temp->nextSlice(); + } + o << QString(u8"}\n"); +} + +void xast_parse::XAST_Parser::write_article(int dep, std::shared_ptr inst, QTextStream& out) +{ + out << QString(u8"%1{章节 %2\n").arg(QString(dep * 2, ' '), inst->name()); + auto temp = inst->firstChild(); + while (temp) { + switch (temp->type()) { + case SliceType::TextPragraph: + this->write_text(dep + 1, std::dynamic_pointer_cast(temp), out); + break; + case SliceType::FragmentRefers: + this->write_fragmref(dep + 1, std::dynamic_pointer_cast(temp), out); + break; + default: + break; + } + temp = temp->nextSlice(); + } + out << QString(u8"%1}\n").arg(QString(dep * 2, ' ')); +} + +RankDecs::RankDecs(const QString& path, const QString& rank) + :fpath(path), rank_v(rank) { } + +QString RankDecs::bindPath() const { + return fpath; +} + +QString RankDecs::rank() const { + return rank_v; +} diff --git a/StoryPresent/xast_parse.h b/StoryPresent/xast_parse.h new file mode 100644 index 0000000..2e18a02 --- /dev/null +++ b/StoryPresent/xast_parse.h @@ -0,0 +1,256 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace xast_parse { + enum class SliceType { + StoryDefines, // 故事线定义 + TextPragraph, // 文字段落 + FragmentDefines, // 情节定义 + FragmentRefers, // 情节引用 + VolumeDefines, // 卷宗定义 + ArticleDefines, // 章节定义 + }; + + /// + /// 故事节点切片 + /// + class IElementSlice { + public: + virtual SliceType type() const = 0; + + virtual std::weak_ptr parentSlice() const = 0; + virtual QList nameSet() const = 0; + + virtual void setPrev(std::shared_ptr inst) = 0; + virtual std::weak_ptr prevSlice() const = 0; + + virtual void setNext(std::shared_ptr next) = 0; + virtual std::shared_ptr nextSlice() const = 0; + + virtual uint index() const = 0; + }; + /// + /// 可以包含子节点的故事节点 + /// + class ICollection { + public: + virtual std::shared_ptr firstChild() const = 0; + virtual void setFirstChild(std::shared_ptr inst) = 0; + }; + + class __SiblingImpl : public IElementSlice, public std::enable_shared_from_this<__SiblingImpl> { + private: + SliceType t_store; + std::weak_ptr parent_store; + std::weak_ptr prev_store; + std::shared_ptr next_store; + + public: + __SiblingImpl(SliceType t, std::shared_ptr pnode); + + virtual SliceType type() const; + + virtual std::weak_ptr parentSlice() const; + + virtual void setPrev(std::shared_ptr inst); + virtual std::weak_ptr prevSlice() const; + + virtual void setNext(std::shared_ptr next); + virtual std::shared_ptr nextSlice() const; + + virtual uint index() const; + }; + + class __CollectionElement : public __SiblingImpl, public ICollection { + private: + std::shared_ptr first_head; + + public: + __CollectionElement(SliceType t, std::shared_ptr pnode); + + // 通过 ICollection 继承 + std::shared_ptr firstChild() const override; + void setFirstChild(std::shared_ptr inst) override; + }; + + + /// + /// 连续文本段落 + /// + class TextParagraph : public __SiblingImpl { + private: + QStringList lines; + + public: + TextParagraph(std::shared_ptr parent); + virtual ~TextParagraph() = default; + + virtual void setLines(const QString& contents); + virtual QString getLines() const; + virtual void addLine(const QString& line); + + // IElementSlice + virtual QList nameSet() const override; + }; + + class __DesElement : public __SiblingImpl { + private: + std::shared_ptr text_store; + + public: + __DesElement(SliceType t, std::shared_ptr pnode); + virtual std::shared_ptr getTextNode() const; + }; + + /// + /// 故事定义节点 + /// + class StoryDefine : public __CollectionElement { + private: + QString name_store, file_path; + uint sort_store; + + public: + StoryDefine(const QString& name, uint sort, const QString &path); + virtual ~StoryDefine() = default; + + virtual QString name() const; + virtual QString bindPath() const; + virtual uint index() const; + virtual std::shared_ptr getFragment(const QString& name); + + // IElementSlice + virtual QList nameSet() const override; + }; + /// + /// 情节定义节点 + /// + class FragmentDefine : public __DesElement { + private: + QString name_store; + QList> refs_list; + + public: + FragmentDefine(const QString& name, std::shared_ptr pnode); + virtual ~FragmentDefine() = default; + + virtual QString name() const; + + virtual QList> referSlices() const; + virtual void appendRefer(std::shared_ptr slice); + + // IElementSlice + virtual QList nameSet() const override; + }; + /// + /// 情节引用节点 + /// + class FragmentRefer : public __DesElement { + private: + QString story_refers, fragment_refers; + std::weak_ptr refer_targets; + + public: + FragmentRefer(const QString& story, const QString& fragm, std::shared_ptr pnode); + virtual ~FragmentRefer() = default; + + virtual QString storyRefer() const; + virtual QString fragmentRefer() const; + + virtual std::weak_ptr referTarget() const; + virtual void setReferTowards(std::shared_ptr target); + + // IElementSlice + virtual QList nameSet() const override; + }; + /// + /// 章节定义节点 + /// + class ArticleDefine : public __CollectionElement { + private: + QString name_store; + + public: + ArticleDefine(const QString& name, std::shared_ptr pnode); + virtual ~ArticleDefine() = default; + + virtual QString name() const; + // IElementSlice + virtual QList nameSet() const override; + }; + /// + /// 卷宗定义节点 + /// + class VolumeDefine : public __CollectionElement { + private: + QString name_store, file_path; + + public: + VolumeDefine(const QString &name, const QString &path); + virtual ~VolumeDefine() = default; + + virtual QString name() const; + virtual QString bindPath() const; + virtual QList getArticleNames() const; + virtual std::shared_ptr getArticleWith(const QString& name) const; + // IElementSlice + virtual QList nameSet() const override; + }; + + class RankDecs { + private: + QString fpath, rank_v; + + public: + RankDecs(const QString &path, const QString &rank); + + QString bindPath() const; + QString rank() const; + }; + + class XAST_Parser { + private: + QDomDocument dom_tree; + QList> rank_coll; + QHash> story_dict; + QHash> volume_dict; + + public: + XAST_Parser(const QString& ast_path); + + QList> rankList() const; + QHash> storyGraph() const; + QHash> volumeGraph() const; + + void output(const QDir &dir); + + private: + std::shared_ptr init_story_define(const QDomElement& story_e); + + uint text_sections_count(const QList& elms); + std::shared_ptr text_paragraph_build(std::shared_ptr<__CollectionElement> pnode, QList& elms, uint count); + std::shared_ptr fragment_define_build(std::shared_ptr pnode, const QDomElement& e); + std::shared_ptr fragment_refer_build(std::shared_ptr<__CollectionElement> pnode, const QDomElement& e); + void fragments_plait(QHash> story_map, std::shared_ptr reflist); + + std::shared_ptr init_volume_define(const QDomElement& volume_e); + std::shared_ptr init_article_define(std::shared_ptr pnode, const QDomElement& article_e); + + void write_rank(std::shared_ptr inst, const QDir &odir); + void write_story(std::shared_ptr inst, const QDir &odir); + void write_text(int dep, std::shared_ptr inst, QTextStream &out); + void write_fragmdef(int dep, std::shared_ptr inst, QTextStream& out); + void write_fragmref(int dep, std::shared_ptr inst, QTextStream& out); + + void write_volume(std::shared_ptr inst, const QDir &odir); + void write_article(int dep, std::shared_ptr inst, QTextStream &out); + }; +} diff --git a/WsNovelParser.sln b/WsNovelParser.sln index d744a57..df2ff76 100644 --- a/WsNovelParser.sln +++ b/WsNovelParser.sln @@ -31,6 +31,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "瑙e喅鏂规椤", "瑙e喅 EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ArgsParser", "ArgsParser\ArgsParser.vcxproj", "{1FF80476-26C9-42FB-BFF6-D587C4941964}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "StoryPresent", "StoryPresent\StoryPresent.vcxproj", "{48DA8516-26EA-4D59-8913-7EF28E3F87C3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -57,6 +59,10 @@ Global {1FF80476-26C9-42FB-BFF6-D587C4941964}.Debug|x64.Build.0 = Debug|x64 {1FF80476-26C9-42FB-BFF6-D587C4941964}.Release|x64.ActiveCfg = Release|x64 {1FF80476-26C9-42FB-BFF6-D587C4941964}.Release|x64.Build.0 = Release|x64 + {48DA8516-26EA-4D59-8913-7EF28E3F87C3}.Debug|x64.ActiveCfg = Debug|x64 + {48DA8516-26EA-4D59-8913-7EF28E3F87C3}.Debug|x64.Build.0 = Debug|x64 + {48DA8516-26EA-4D59-8913-7EF28E3F87C3}.Release|x64.ActiveCfg = Release|x64 + {48DA8516-26EA-4D59-8913-7EF28E3F87C3}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE