2023-08-13 10:02:21 +00:00
|
|
|
|
#include "parsebridge.h"
|
|
|
|
|
|
|
|
|
|
#include <QFileInfo>
|
|
|
|
|
#include <QList>
|
|
|
|
|
#include <libConfig.h>
|
|
|
|
|
|
|
|
|
|
using namespace bridge;
|
|
|
|
|
|
|
|
|
|
class Impl_NovelParseException : public Config::ParseException {
|
|
|
|
|
private:
|
|
|
|
|
QString title_s, reason_s;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
Impl_NovelParseException(const QString &t, const QString &r) {
|
|
|
|
|
this->title_s = t;
|
|
|
|
|
this->reason_s = r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ParseException interface
|
|
|
|
|
public:
|
|
|
|
|
virtual QString reason() const override { return reason_s; }
|
|
|
|
|
virtual QString title() const override { return reason_s; }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ParseBridge::ParseBridge() : chains_model(new QStandardItemModel), volumes_model(new QStandardItemModel), errors_model(new QStandardItemModel) {}
|
|
|
|
|
|
|
|
|
|
ParseBridge::~ParseBridge() {
|
|
|
|
|
delete chains_model;
|
|
|
|
|
delete volumes_model;
|
|
|
|
|
delete errors_model;
|
|
|
|
|
|
|
|
|
|
for (auto it : docs_store)
|
|
|
|
|
delete it;
|
|
|
|
|
docs_store.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString ParseBridge::novelName() const { return name_store; }
|
|
|
|
|
|
|
|
|
|
QStandardItemModel *ParseBridge::volumesPresentModel() const { return volumes_model; }
|
|
|
|
|
|
|
|
|
|
void bridge::ParseBridge::load(const QString &data_path) {
|
|
|
|
|
// clear cache
|
|
|
|
|
this->doc_ins.clear();
|
|
|
|
|
chains_model->clear();
|
|
|
|
|
volumes_model->clear();
|
|
|
|
|
errors_model->clear();
|
|
|
|
|
|
|
|
|
|
for (auto it : docs_store)
|
|
|
|
|
delete it;
|
|
|
|
|
docs_store.clear();
|
|
|
|
|
|
|
|
|
|
// 重新载入小说编译结果
|
|
|
|
|
QFileInfo target_xml(data_path);
|
|
|
|
|
if (!target_xml.exists())
|
|
|
|
|
throw new Impl_NovelParseException("小说解析过程错", "指定路径数据文件不存在:" + data_path);
|
|
|
|
|
|
|
|
|
|
QFile data_in(target_xml.canonicalFilePath());
|
|
|
|
|
if (!data_in.open(QIODevice::ReadOnly | QIODevice::Text))
|
|
|
|
|
throw new Impl_NovelParseException("小说解析过程错", "指定数据文件无法打开:" + target_xml.canonicalFilePath());
|
|
|
|
|
|
|
|
|
|
QString err_msg;
|
|
|
|
|
int col, row;
|
|
|
|
|
if (!this->doc_ins.setContent(&data_in, &err_msg, &row, &col)) {
|
|
|
|
|
throw new Impl_NovelParseException("小说解析过程我", QString("数据文件格式错:<row:%1, col:%2>%3").arg(row).arg(col).arg(err_msg));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 载入语法节点
|
|
|
|
|
auto root = doc_ins.documentElement();
|
|
|
|
|
name_store = root.attribute("name", "空白小说名称");
|
|
|
|
|
auto childnodes = root.childNodes();
|
|
|
|
|
for (auto idx = 0; idx < childnodes.size(); ++idx) {
|
|
|
|
|
auto child = childnodes.at(idx);
|
|
|
|
|
if (child.isElement()) {
|
|
|
|
|
auto elm_inst = child.toElement();
|
|
|
|
|
if (elm_inst.tagName() == "doc") {
|
|
|
|
|
load_document(elm_inst, this->docs_store);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ParseBridge::load_document(const QDomElement &pdoc, std::vector<DocumentInst *> node_sets) {
|
|
|
|
|
auto doc_name = pdoc.attribute("name", "未命名文档名");
|
|
|
|
|
auto file_path = pdoc.attribute("file");
|
|
|
|
|
auto doc_ins = new DocumentInst(doc_name, file_path);
|
|
|
|
|
node_sets.push_back(doc_ins);
|
|
|
|
|
|
|
|
|
|
// 载入解析错误
|
|
|
|
|
auto errors = pdoc.elementsByTagName("error");
|
|
|
|
|
for (auto idx = 0; idx < errors.size(); ++idx) {
|
|
|
|
|
auto e_elm = errors.at(idx).toElement();
|
|
|
|
|
load_errors(errorsPresentModel(), e_elm, doc_ins);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto children = pdoc.childNodes();
|
|
|
|
|
for (auto idx = 0; idx < children.size(); ++idx) {
|
|
|
|
|
auto node = children.at(idx);
|
|
|
|
|
if (node.isElement()) {
|
|
|
|
|
load_element(node.toElement(), doc_ins);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ParseBridge::load_element(const QDomElement &telm, DocumentInst *pinst, QStandardItem *pitem) {
|
|
|
|
|
if (telm.tagName() == "story")
|
|
|
|
|
load_story(chainsPresentModel(), telm, pinst);
|
|
|
|
|
else if (telm.tagName() == "text")
|
|
|
|
|
load_desc(telm, static_cast<ContentNode *>(pitem));
|
|
|
|
|
else if (telm.tagName() == "fragment")
|
|
|
|
|
load_fragment(telm, pitem);
|
|
|
|
|
else if (telm.tagName() == "frag-refer")
|
|
|
|
|
load_frag_refer(telm, pitem);
|
|
|
|
|
else if (telm.tagName() == "volume")
|
|
|
|
|
load_volumes(volumesPresentModel(), telm, pinst);
|
|
|
|
|
else if (telm.tagName() == "article")
|
|
|
|
|
load_article(telm, pitem);
|
|
|
|
|
else
|
|
|
|
|
throw new Impl_NovelParseException("小说解析结果载入错", "位置tagName:" + telm.tagName());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ParseBridge::load_errors(QStandardItemModel *anchor, const QDomElement &error_elm, DocumentInst *pinst) {
|
|
|
|
|
auto pos_value = error_elm.attribute("pos");
|
|
|
|
|
auto message = error_elm.attribute("message");
|
|
|
|
|
|
|
|
|
|
QRegExp exp0("(\\d+)");
|
|
|
|
|
auto index = exp0.indexIn(pos_value);
|
|
|
|
|
auto row = exp0.cap(1).toInt();
|
|
|
|
|
exp0.indexIn(pos_value, index + exp0.cap(1).length());
|
|
|
|
|
auto col = exp0.cap(1).toInt();
|
|
|
|
|
|
|
|
|
|
auto perror = new ParseException(pinst);
|
|
|
|
|
perror->load(message, row, col);
|
|
|
|
|
|
|
|
|
|
QList<QStandardItem *> inst_row;
|
|
|
|
|
inst_row << perror;
|
|
|
|
|
inst_row << new SupplyItem<ParseException, 1>(perror);
|
|
|
|
|
inst_row << new SupplyItem<ParseException, 2>(perror);
|
|
|
|
|
inst_row << new SupplyItem<ParseException, 3>(perror);
|
|
|
|
|
anchor->appendRow(inst_row);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define PEAK_POSITION(e) \
|
|
|
|
|
auto start_pos = (e).attribute("start-pos"); \
|
|
|
|
|
auto end_pos = (e).attribute("end-pos"); \
|
|
|
|
|
QRegExp exp("(\\d+)"); \
|
|
|
|
|
auto xidx = exp.indexIn(start_pos); \
|
|
|
|
|
auto start_row = exp.cap(1).toInt(); \
|
|
|
|
|
exp.indexIn(start_pos, xidx + exp.cap(1).length()); \
|
|
|
|
|
auto start_col = exp.cap(1).toInt(); \
|
|
|
|
|
xidx = exp.indexIn(end_pos); \
|
|
|
|
|
auto end_row = exp.cap(1).toInt(); \
|
|
|
|
|
exp.indexIn(start_pos, xidx + exp.cap(1).length()); \
|
|
|
|
|
auto end_col = exp.cap(1).toInt();
|
|
|
|
|
|
|
|
|
|
void ParseBridge::load_story(QStandardItemModel *model, const QDomElement &chain_elm, DocumentInst *pinst) {
|
|
|
|
|
auto name = chain_elm.attribute("name");
|
|
|
|
|
auto index = chain_elm.attribute("sort");
|
|
|
|
|
auto story = new StorychainInst(pinst, name);
|
|
|
|
|
model->appendRow(story);
|
|
|
|
|
story->indexSet(index.toInt());
|
|
|
|
|
|
|
|
|
|
PEAK_POSITION(chain_elm);
|
|
|
|
|
|
|
|
|
|
story->loadPos(start_row, start_col, end_row, end_col);
|
|
|
|
|
|
|
|
|
|
auto children = chain_elm.childNodes();
|
|
|
|
|
for (auto idx = 0; idx < children.size(); ++idx) {
|
|
|
|
|
auto node = children.at(idx);
|
|
|
|
|
if (node.isElement()) {
|
|
|
|
|
load_element(node.toElement(), nullptr, story);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ParseBridge::load_desc(const QDomElement &desc_elm, ContentNode *pitem) {
|
|
|
|
|
auto content = desc_elm.attribute("content");
|
|
|
|
|
pitem->append(content);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ParseBridge::load_fragment(const QDomElement &frag_elm, QStandardItem *pinst) {
|
|
|
|
|
auto name = frag_elm.attribute("name");
|
|
|
|
|
auto logic_index = frag_elm.attribute("logic-index");
|
|
|
|
|
PEAK_POSITION(frag_elm);
|
|
|
|
|
|
|
|
|
|
auto frag_inst = new StoryfragmentInst(name);
|
|
|
|
|
pinst->appendRow(frag_inst);
|
|
|
|
|
|
|
|
|
|
frag_inst->setLogicIndex(logic_index.toInt());
|
|
|
|
|
frag_inst->loadPos(start_row, start_col, end_row, end_col);
|
|
|
|
|
|
|
|
|
|
auto children = frag_elm.childNodes();
|
|
|
|
|
for (auto idx = 0; idx < children.size(); ++idx) {
|
|
|
|
|
auto node = children.at(idx);
|
|
|
|
|
if (node.isElement()) {
|
|
|
|
|
load_element(node.toElement(), nullptr, frag_inst);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ParseBridge::load_frag_refer(const QDomElement &refer_elm, QStandardItem *pitem) {
|
|
|
|
|
auto story = refer_elm.attribute("story-ref");
|
|
|
|
|
auto frag = refer_elm.attribute("fragment-ref");
|
|
|
|
|
PEAK_POSITION(refer_elm);
|
|
|
|
|
|
|
|
|
|
auto item = locate_node(chainsPresentModel(), QStringList() << story << frag);
|
|
|
|
|
auto refer_inst = new StoryfragmentRefer(static_cast<StoryfragmentInst *>(item));
|
|
|
|
|
pitem->appendRow(refer_inst);
|
|
|
|
|
refer_inst->appointStory(story);
|
|
|
|
|
refer_inst->appointFragment(frag);
|
|
|
|
|
refer_inst->loadPos(start_row, start_col, end_row, end_col);
|
|
|
|
|
|
|
|
|
|
auto children = refer_elm.childNodes();
|
|
|
|
|
for (auto idx = 0; idx < children.size(); ++idx) {
|
|
|
|
|
auto node = children.at(idx);
|
|
|
|
|
if (node.isElement()) {
|
|
|
|
|
load_element(node.toElement(), nullptr, refer_inst);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ParseBridge::load_volumes(QStandardItemModel *model, const QDomElement &volume_elm, DocumentInst *pinst) {
|
|
|
|
|
auto name = volume_elm.attribute("content");
|
|
|
|
|
PEAK_POSITION(volume_elm);
|
|
|
|
|
|
|
|
|
|
auto volume = new StoryvolumeInst(pinst, name);
|
|
|
|
|
model->appendRow(volume);
|
|
|
|
|
volume->loadPos(start_row, start_col, end_row, end_col);
|
|
|
|
|
|
|
|
|
|
auto children = volume_elm.childNodes();
|
|
|
|
|
for (auto idx = 0; idx < children.size(); ++idx) {
|
|
|
|
|
auto node = children.at(idx);
|
|
|
|
|
if (node.isElement()) {
|
|
|
|
|
load_element(node.toElement(), nullptr, volume);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ParseBridge::load_article(const QDomElement &article_elm, QStandardItem *pitem) {
|
|
|
|
|
auto name = article_elm.attribute("content");
|
|
|
|
|
PEAK_POSITION(article_elm);
|
|
|
|
|
|
|
|
|
|
auto article = new StoryarticleInst(name);
|
|
|
|
|
pitem->appendRow(article);
|
|
|
|
|
article->loadPos(start_row, start_col, end_row, end_col);
|
|
|
|
|
|
|
|
|
|
auto children = article_elm.childNodes();
|
|
|
|
|
for (auto idx = 0; idx < children.size(); ++idx) {
|
|
|
|
|
auto node = children.at(idx);
|
|
|
|
|
if (node.isElement()) {
|
|
|
|
|
load_element(node.toElement(), nullptr, article);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QStandardItem *ParseBridge::locate_node(QStandardItemModel *model, const QStringList path) {
|
|
|
|
|
QStandardItem *current_item = nullptr;
|
2023-08-15 14:14:00 +00:00
|
|
|
|
for (auto &n : path) {
|
|
|
|
|
if (!current_item) {
|
|
|
|
|
for (auto idx = 0; idx < model->rowCount(); ++idx) {
|
|
|
|
|
if (model->item(idx)->text() == n)
|
|
|
|
|
current_item = model->item(idx);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2023-08-13 10:02:21 +00:00
|
|
|
|
for (auto idx = 0; idx < current_item->rowCount(); ++idx) {
|
|
|
|
|
if (current_item->child(idx)->text() == n) {
|
|
|
|
|
current_item = current_item->child(idx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return current_item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ParseException::ParseException(DocumentInst *pinst) : key_store(std::make_tuple(pinst, "尚未载入异常信息", 0, 0)) { setText(std::get<1>(key_store)); }
|
|
|
|
|
|
|
|
|
|
void ParseException::load(const QString &message, int row, int col) {
|
|
|
|
|
this->key_store = std::make_tuple(std::get<0>(key_store), message, row, col);
|
|
|
|
|
setText(std::get<1>(key_store));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const DocumentInst *ParseException::doc() const { return std::get<0>(key_store); }
|
|
|
|
|
|
|
|
|
|
QString ParseException::message() const { return std::get<1>(key_store); }
|
|
|
|
|
|
|
|
|
|
int ParseException::row() const { return std::get<2>(key_store); }
|
|
|
|
|
|
|
|
|
|
int ParseException::col() const { return std::get<3>(key_store); }
|
|
|
|
|
|
|
|
|
|
QString ParseException::operator[](int index) {
|
|
|
|
|
switch (index) {
|
|
|
|
|
case 0:
|
|
|
|
|
return message();
|
|
|
|
|
case 1:
|
|
|
|
|
return QString("%1").arg(row());
|
|
|
|
|
case 2:
|
|
|
|
|
return QString("%1").arg(col());
|
|
|
|
|
case 3:
|
|
|
|
|
return QString("%1").arg(doc()->name());
|
|
|
|
|
default:
|
|
|
|
|
return "超出索引范围";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DocumentInst::DocumentInst(const QString &name, const QString &path) : n(name), p(path) {}
|
|
|
|
|
|
|
|
|
|
QString DocumentInst::name() const { return n; }
|
|
|
|
|
|
|
|
|
|
QString DocumentInst::path() const { return p; }
|
|
|
|
|
|
|
|
|
|
StorychainInst::StorychainInst(DocumentInst *pinst, const QString &name) : ContentNode(name), pinst_store(pinst) {}
|
|
|
|
|
|
|
|
|
|
void StorychainInst::indexSet(int sort) { this->sort_store = sort; }
|
|
|
|
|
|
|
|
|
|
int StorychainInst::sortIndex() const { return sort_store; }
|
|
|
|
|
|
|
|
|
|
ContentNode::ContentNode(const QString &name) {
|
|
|
|
|
setText(name);
|
|
|
|
|
name_store = name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::tuple<int, int> SyntaxNode::startPos() const { return this->start; }
|
|
|
|
|
|
|
|
|
|
std::tuple<int, int> SyntaxNode::endPos() const { return this->end; }
|
|
|
|
|
|
|
|
|
|
void SyntaxNode::loadPos(int start_r, int start_c, int end_r, int end_c) {
|
|
|
|
|
this->start = std::make_tuple(start_r, start_c);
|
|
|
|
|
this->end = std::make_tuple(end_r, end_c);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ContentNode::append(const QString §ion) { this->desc_sections += section + "\n"; }
|
|
|
|
|
|
|
|
|
|
QString ContentNode::desc() const { return desc_sections; }
|
|
|
|
|
|
|
|
|
|
void ContentNode::clear() { desc_sections = ""; }
|
|
|
|
|
|
|
|
|
|
DocumentInst *StorychainInst::doc() const { return pinst_store; }
|
|
|
|
|
|
|
|
|
|
QString StorychainInst::operator[](int index) {
|
|
|
|
|
switch (index) {
|
|
|
|
|
case 0:
|
|
|
|
|
return text();
|
|
|
|
|
case 1:
|
|
|
|
|
return QString("%1").arg(sortIndex());
|
|
|
|
|
case 2:
|
|
|
|
|
return desc();
|
|
|
|
|
default:
|
|
|
|
|
return "超出索引范围";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StoryfragmentInst::StoryfragmentInst(const QString &name) : ContentNode(name) {}
|
|
|
|
|
|
|
|
|
|
int StoryfragmentInst::logicIndex() const { return sort_store; }
|
|
|
|
|
|
|
|
|
|
void StoryfragmentInst::setLogicIndex(int index) { this->sort_store = index; }
|
|
|
|
|
|
|
|
|
|
QString StoryfragmentInst::operator[](int index) {
|
|
|
|
|
switch (index) {
|
|
|
|
|
case 0:
|
|
|
|
|
return text();
|
|
|
|
|
case 1:
|
|
|
|
|
return QString("%1").arg(logicIndex());
|
|
|
|
|
case 2:
|
|
|
|
|
return desc();
|
|
|
|
|
default:
|
|
|
|
|
return "超出有效索引范围";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString StoryfragmentRefer::storyRef() const { return this->story; }
|
|
|
|
|
|
|
|
|
|
void StoryfragmentRefer::appointStory(const QString &ref) { this->story = ref; }
|
|
|
|
|
|
|
|
|
|
QString StoryfragmentRefer::fragmentRef() const { return fragment; }
|
|
|
|
|
|
|
|
|
|
void StoryfragmentRefer::appointFragment(const QString &ref) { fragment = ref; }
|
|
|
|
|
|
|
|
|
|
QString StoryfragmentRefer::operator[](int index) {
|
|
|
|
|
switch (index) {
|
|
|
|
|
case 0:
|
|
|
|
|
return storyRef();
|
|
|
|
|
case 1:
|
|
|
|
|
return fragmentRef();
|
|
|
|
|
case 2:
|
|
|
|
|
return desc();
|
|
|
|
|
default:
|
|
|
|
|
return "超出有效索引范围";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StoryvolumeInst::StoryvolumeInst(DocumentInst *inst, const QString &name) : ContentNode(name), doc_store(inst) {}
|
|
|
|
|
|
|
|
|
|
DocumentInst *StoryvolumeInst::doc() const { return doc_store; }
|
|
|
|
|
|
|
|
|
|
QString StoryvolumeInst::operator[](int index) {
|
|
|
|
|
switch (index) {
|
|
|
|
|
case 0:
|
|
|
|
|
return text();
|
|
|
|
|
case 1:
|
|
|
|
|
return desc();
|
|
|
|
|
default:
|
|
|
|
|
return "超出有效索引范围";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StoryarticleInst::StoryarticleInst(const QString &name) : ContentNode(name) {}
|
|
|
|
|
|
|
|
|
|
QString StoryarticleInst::operator[](int index) {
|
|
|
|
|
switch (index) {
|
|
|
|
|
case 0:
|
|
|
|
|
return text();
|
|
|
|
|
case 1:
|
|
|
|
|
return desc();
|
|
|
|
|
default:
|
|
|
|
|
return "超出有效索引范围";
|
|
|
|
|
}
|
|
|
|
|
}
|