LivePreview: support component/state/object diagram

This commit is contained in:
Le Tan 2018-09-05 20:15:48 +08:00
parent 774eec32d7
commit 2338002b1f

View File

@ -189,23 +189,55 @@ QByteArray VPlantUMLHelper::process(const QString &p_format, const QString &p_te
return out; return out;
} }
static bool tryKeywords(QString &p_keyword) static bool tryKeywords(QString &p_keyword, bool &p_needCreole)
{ {
// start, stop, end, endif, repeat, fork, fork again, end fork, }, // start, stop, end, endif, repeat, fork, fork again, end fork, },
// detach, end note, end box, endrnote, endhnote, // detach, end note, end box, endrnote, endhnote,
// top to bottom direction, left to right direction, // top to bottom direction, left to right direction,
// @startuml, @enduml // @startuml, @enduml
// ||, --, title, end title, end legend
static QRegExp keywords("^\\s*(?:start|stop|end|endif|repeat|" static QRegExp keywords("^\\s*(?:start|stop|end|endif|repeat|"
"fork(?:\\s+again)?|end\\s+fork|\\}|detach|" "fork(?:\\s+again)?|end\\s+fork|\\}|detach|"
"end ?(?:note|box)|endrnote|endhnote|" "end ?(?:note|box)|endrnote|endhnote|"
"top\\s+to\\s+bottom\\s+direction|" "top\\s+to\\s+bottom\\s+direction|"
"left\\s+to\\s+right\\s+direction|" "left\\s+to\\s+right\\s+direction|"
"@startuml|@enduml)\\s*$"); "@startuml|@enduml|"
"--|\\|\\||(?:end\\s+)?title|end\\s+legend)\\s*$");
if (keywords.indexIn(p_keyword) >= 0) { if (keywords.indexIn(p_keyword) >= 0) {
p_keyword.clear(); p_keyword.clear();
return true; return true;
} }
// Comments.
static QRegExp comment("^\\s*'");
if (comment.indexIn(p_keyword) >= 0) {
p_keyword.clear();
return true;
}
// scale 1.5
static QRegExp scale("^\\s*scale\\s+\\w+");
if (scale.indexIn(p_keyword) >= 0) {
p_keyword.clear();
return true;
}
// title
// caption
static QRegExp title("^\\s*(?:title|caption)\\s+(.+)");
if (title.indexIn(p_keyword) >= 0) {
p_keyword = title.cap(1).trimmed();
p_needCreole = true;
return true;
}
// legend right
static QRegExp legend("^\\s*legend(?:\\s+(?:left|right|center))?");
if (legend.indexIn(p_keyword) >= 0) {
p_keyword.clear();
return true;
}
return false; return false;
} }
@ -214,7 +246,8 @@ static bool tryClassDiagram(QString &p_keyword, QString &p_hints, bool &p_isRege
Q_UNUSED(p_isRegex); Q_UNUSED(p_isRegex);
// class ABC #Pink // class ABC #Pink
static QRegExp classDef1("^\\s*(?:class|(?:abstract(?:\\s+class)?)|interface|annotation|enum)\\s+" // interface conflicts with component diagram, so it is removed from here.
static QRegExp classDef1("^\\s*(?:class|(?:abstract(?:\\s+class)?)|annotation|enum)\\s+"
"(?!class)(\\w+)"); "(?!class)(\\w+)");
if (classDef1.indexIn(p_keyword) >= 0) { if (classDef1.indexIn(p_keyword) >= 0) {
p_keyword = classDef1.cap(1); p_keyword = classDef1.cap(1);
@ -223,7 +256,7 @@ static bool tryClassDiagram(QString &p_keyword, QString &p_hints, bool &p_isRege
} }
// class "ABC DEF" as AD #Pink // class "ABC DEF" as AD #Pink
static QRegExp classDef2("^\\s*(?:class|(?:abstract(?:\\s+class)?)|interface|annotation|enum)\\s+" static QRegExp classDef2("^\\s*(?:class|(?:abstract(?:\\s+class)?)|annotation|enum)\\s+"
"\"([^\"]+)\"\\s*(?:\\bas\\s+(\\w+))?"); "\"([^\"]+)\"\\s*(?:\\bas\\s+(\\w+))?");
if (classDef2.indexIn(p_keyword) >= 0) { if (classDef2.indexIn(p_keyword) >= 0) {
if (classDef2.cap(2).isEmpty()) { if (classDef2.cap(2).isEmpty()) {
@ -296,19 +329,28 @@ static bool tryClassDiagram(QString &p_keyword, QString &p_hints, bool &p_isRege
// note top: message // note top: message
// hnote and rnote for sequence diagram. // hnote and rnote for sequence diagram.
// note right of (use case) // note right of (use case)
// note right of [component]
static QRegExp note("^\\s*[hr]?note\\s+(?:left|top|right|bottom)" static QRegExp note("^\\s*[hr]?note\\s+(?:left|top|right|bottom)"
"(?:\\s+of\\s+(?:(\\w+)|\\(([^\\)]+)\\)))?" "(?:\\s+of\\s+(?:(\\w+)|"
"\\(([^\\)]+)\\)|"
"\\[([^\\]]+)\\]))?"
"[^:]*" "[^:]*"
"(?::(.*))?"); "(?::(.*))?");
if (note.indexIn(p_keyword) >= 0) { if (note.indexIn(p_keyword) >= 0) {
p_keyword = note.cap(3).trimmed(); p_keyword = note.cap(4).trimmed();
if (p_keyword.isEmpty()) { if (p_keyword.isEmpty()) {
p_keyword = note.cap(2).trimmed(); QString ent = note.cap(1);
if (p_keyword.isEmpty()) { if (ent.isEmpty()) {
p_keyword = note.cap(1); ent = note.cap(2).trimmed();
p_hints = "id"; if (ent.isEmpty()) {
} else { ent = note.cap(3).trimmed();
}
p_keyword = ent;
p_needCreole = true; p_needCreole = true;
} else {
p_keyword = ent;
p_hints = "id";
} }
} else { } else {
p_needCreole = true; p_needCreole = true;
@ -370,7 +412,7 @@ static bool tryActivityDiagram(QString &p_keyword, QString &p_hints, bool &p_isR
// Activity. // Activity.
// multiple lines; // multiple lines;
static QRegExp activity2("^\\s*(.+)([;|<>/\\]}])\\s*$"); static QRegExp activity2("^\\s*([^\\[]+)([;|<>/\\]}])\\s*$");
if (activity2.indexIn(p_keyword) >= 0) { if (activity2.indexIn(p_keyword) >= 0) {
QString word = activity2.cap(1); QString word = activity2.cap(1);
QChar end = activity2.cap(2)[0]; QChar end = activity2.cap(2)[0];
@ -463,7 +505,7 @@ static bool trySequenceDiagram(QString &p_keyword, QString &p_hints, bool &p_isR
// "abc" ->> "def" : Authentication // "abc" ->> "def" : Authentication
static QRegExp message("^\\s*(?:\\w+|\"[^\"]+\")\\s+" static QRegExp message("^\\s*(?:\\w+|\"[^\"]+\")\\s+"
"[-<>x\\\\/o]{2,}\\s+" "[-<>x\\\\/o]+\\s+"
"(?:\\w+|\"[^\"]+\")\\s*" "(?:\\w+|\"[^\"]+\")\\s*"
":\\s*(.+)"); ":\\s*(.+)");
if (message.indexIn(p_keyword) >= 0) { if (message.indexIn(p_keyword) >= 0) {
@ -513,6 +555,7 @@ static bool trySequenceDiagram(QString &p_keyword, QString &p_hints, bool &p_isR
// == Initialization == // == Initialization ==
static QRegExp divider("^\\s*==\\s*([^=]*)==\\s*$"); static QRegExp divider("^\\s*==\\s*([^=]*)==\\s*$");
if (divider.indexIn(p_keyword) >= 0) { if (divider.indexIn(p_keyword) >= 0) {
p_needCreole = true;
p_keyword = divider.cap(1).trimmed(); p_keyword = divider.cap(1).trimmed();
return true; return true;
} }
@ -521,6 +564,7 @@ static bool trySequenceDiagram(QString &p_keyword, QString &p_hints, bool &p_isR
// ... 5 minutes latter ... // ... 5 minutes latter ...
static QRegExp delay("^\\s*\\.\\.\\.(?:(.+)\\.\\.\\.)?\\s*$"); static QRegExp delay("^\\s*\\.\\.\\.(?:(.+)\\.\\.\\.)?\\s*$");
if (delay.indexIn(p_keyword) >= 0) { if (delay.indexIn(p_keyword) >= 0) {
p_needCreole = true;
p_keyword = delay.cap(1).trimmed(); p_keyword = delay.cap(1).trimmed();
return true; return true;
} }
@ -588,7 +632,7 @@ static bool tryUseCaseDiagram(QString &p_keyword, QString &p_hints, bool &p_isRe
// :Main Admin: --> (Use the application) : This is another label // :Main Admin: --> (Use the application) : This is another label
// (chekckout) -- (payment) : include // (chekckout) -- (payment) : include
static QRegExp rel("^\\s*(?:(\\w+)|:([^:]+):|\\(([^\\)]+)\\))\\s*" static QRegExp rel("^\\s*(?:(\\w+)|:([^:]+):|\\(([^\\)]+)\\))\\s*"
"[-.<>]{2,}\\s*" "[-.<>]+\\s*"
"(?:\\(([^\\)]+)\\)|(\\w+))\\s*" "(?:\\(([^\\)]+)\\)|(\\w+))\\s*"
"(?::(.+))?"); "(?::(.+))?");
if (rel.indexIn(p_keyword) >= 0) { if (rel.indexIn(p_keyword) >= 0) {
@ -624,7 +668,7 @@ static bool tryUseCaseDiagram(QString &p_keyword, QString &p_hints, bool &p_isRe
// (First usecase) as (UC2) // (First usecase) as (UC2)
// usecase UC3 // usecase UC3
// usecase (Last usecase) as UC4 // usecase (Last usecase) as UC4
static QRegExp usecase1("^\\s*usecase\\s+(?:(\\w+)|\\(([^\\)]+)\\)\\s+as\\s+\\w+)"); static QRegExp usecase1("^\\s*usecase\\s+""(?:(\\w+)|\\(([^\\)]+)\\)\\s+as\\s+\\w+)");
if (usecase1.indexIn(p_keyword) >= 0) { if (usecase1.indexIn(p_keyword) >= 0) {
if (usecase1.cap(1).isEmpty()) { if (usecase1.cap(1).isEmpty()) {
p_keyword = usecase1.cap(2).trimmed(); p_keyword = usecase1.cap(2).trimmed();
@ -674,25 +718,280 @@ static bool tryUseCaseDiagram(QString &p_keyword, QString &p_hints, bool &p_isRe
return true; return true;
} }
// Grouping.
// package "ABC DEF" {
static QRegExp group("^\\s*(?:package|node|folder|frame|cloud|database)\\s+"
"(?:(\\w+)|\"([^\"]+)\")");
if (group.indexIn(p_keyword) >= 0) {
if (group.cap(1).isEmpty()) {
p_keyword = group.cap(2).trimmed();
p_needCreole = true;
} else {
p_keyword = group.cap(1);
}
return true;
}
return false; return false;
} }
static bool tryComponentDiagram(QString &p_keyword, QString &p_hints, bool &p_isRegex, bool &p_needCreole)
{
Q_UNUSED(p_isRegex);
Q_UNUSED(p_hints);
// DataAccess - [First Component]
// [First Component] ..> HTTP : use
static QRegExp rel("^\\s*(?:(\\w+)|\\[([^\\]]+)\\])\\s*"
"[-.<>]+\\s*"
"(?:(\\w+)|\\[([^\\]]+)\\])\\s*"
"(?::(.+))?");
if (rel.indexIn(p_keyword) >= 0) {
QString msg(rel.cap(5).trimmed());
if (msg.isEmpty()) {
QString ent1(rel.cap(3));
if (ent1.isEmpty()) {
ent1 = rel.cap(4).trimmed();
if (ent1 == "*") {
// State diagram.
ent1 = rel.cap(1);
if (ent1.isEmpty()) {
ent1 = rel.cap(2).trimmed();
p_needCreole = true;
}
} else {
p_needCreole = true;
}
}
p_keyword = ent1;
} else {
p_needCreole = true;
p_keyword = msg;
}
return true;
}
// Components.
// [First component]
// [Another component] as Comp2
// component comp3
// component [last\ncomponent] as Comp4
static QRegExp comp1("^\\s*component\\s+(?:(\\w+)|\\[([^\\]]+)\\]\\s+as\\s+\\w+)");
if (comp1.indexIn(p_keyword) >= 0) {
if (comp1.cap(1).isEmpty()) {
p_keyword = comp1.cap(2).trimmed();
p_needCreole = true;
} else {
p_keyword = comp1.cap(1);
}
return true;
}
// This will eat almost anything starting with [].
static QRegExp comp2("^\\s*\\[([^\\]]+)\\]");
if (comp2.indexIn(p_keyword) >= 0) {
p_keyword = comp2.cap(1).trimmed();
p_needCreole = true;
return true;
}
// Interface.
// interface Int1
// interface "last interface" as Int2
static QRegExp int1("^\\s*interface\\s+(?:(\\w+)|\"([^\"]+)\"\\s+as\\s+\\w+)");
if (int1.indexIn(p_keyword) >= 0) {
if (int1.cap(1).isEmpty()) {
p_keyword = int1.cap(2).trimmed();
p_needCreole = true;
} else {
p_keyword = int1.cap(1);
}
return true;
}
// () "First Interface" as Inter2
static QRegExp int2("^\\s*\\(\\)\\s+\"([^\"]+)\"");
if (int2.indexIn(p_keyword) >= 0) {
p_keyword = int2.cap(1).trimmed();
p_needCreole = true;
return true;
}
return false;
}
static bool tryStateDiagram(QString &p_keyword, QString &p_hints, bool &p_isRegex, bool &p_needCreole)
{
Q_UNUSED(p_isRegex);
Q_UNUSED(p_hints);
// state State3 {
static QRegExp state("^\\s*state\\s+"
"(?:(\\w+)|\"([^\"]+)\")");
if (state.indexIn(p_keyword) >= 0) {
p_keyword = state.cap(1);
if (p_keyword.isEmpty()) {
p_keyword = state.cap(2).trimmed();
p_needCreole = true;
}
return true;
}
// state1 : this is a string
static QRegExp state2("^\\s*\\w+\\s*:(.+)");
if (state2.indexIn(p_keyword) >= 0) {
p_keyword = state2.cap(1).trimmed();
p_needCreole = true;
return true;
}
return false;
}
static bool tryObjectDiagram(QString &p_keyword, QString &p_hints, bool &p_isRegex, bool &p_needCreole)
{
Q_UNUSED(p_isRegex);
Q_UNUSED(p_hints);
// object obj {
static QRegExp object("^\\s*object\\s+"
"(?:(\\w+)|\"([^\"]+)\")");
if (object.indexIn(p_keyword) >= 0) {
p_keyword = object.cap(1);
if (p_keyword.isEmpty()) {
p_keyword = object.cap(2).trimmed();
p_needCreole = true;
}
return true;
}
return false;
}
static bool tryMultipleLineText(QString &p_keyword)
{
static QRegExp mulline("\\\\n");
QString maxPart;
bool found = false;
int pos = 0;
while (pos < p_keyword.size()) {
int idx = mulline.indexIn(p_keyword, pos);
if (idx == -1) {
if (found) {
if (p_keyword.size() - pos > maxPart.size()) {
maxPart = p_keyword.mid(pos);
}
}
break;
}
found = true;
// [pos, idx) is part of the plain text.
if (idx - pos > maxPart.size()) {
maxPart = p_keyword.mid(pos, idx - pos);
}
pos = idx + mulline.matchedLength();
}
if (found) {
p_keyword = maxPart.trimmed();
return true;
} else {
return false;
}
}
static bool tryEmphasizedText(QString &p_keyword)
{
static QRegExp emph("(--|\\*\\*|//|\"\"|__|~~)"
"([^-*/\"_~]+)"
"\\1");
QString maxPart;
bool found = false;
int pos = 0;
while (pos < p_keyword.size()) {
int idx = emph.indexIn(p_keyword, pos);
if (idx == -1) {
if (found) {
if (p_keyword.size() - pos > maxPart.size()) {
maxPart = p_keyword.mid(pos);
}
}
break;
}
found = true;
// [pos, idx) is part of the plain text.
if (idx - pos > maxPart.size()) {
maxPart = p_keyword.mid(pos, idx - pos);
}
if (emph.cap(2).size() > maxPart.size()) {
maxPart = emph.cap(2);
}
pos = idx + emph.matchedLength();
}
if (found) {
p_keyword = maxPart.trimmed();
return true;
} else {
return false;
}
}
static bool tryCreole(QString &p_keyword) static bool tryCreole(QString &p_keyword)
{ {
if (p_keyword.isEmpty()) { if (p_keyword.isEmpty()) {
return false; return false;
} }
bool ret = false;
// List. // List.
// ** list // ** list
// # list // # list
static QRegExp listMark("^\\s*(?:\\*+|#+)\\s+(.+)$"); static QRegExp listMark("^\\s*(?:\\*+|#+)\\s+(.+)$");
if (listMark.indexIn(p_keyword) >= 0) { if (listMark.indexIn(p_keyword) >= 0) {
p_keyword = listMark.cap(1).trimmed(); p_keyword = listMark.cap(1).trimmed();
return true; ret = true;
} }
return false; // Heading.
// ### head
static QRegExp headMark("^\\s*=+\\s+(.+)");
if (headMark.indexIn(p_keyword) >= 0) {
p_keyword = headMark.cap(1).trimmed();
ret = true;
}
// \n
if (tryMultipleLineText(p_keyword)) {
ret = true;
}
// Emphasized text.
if (tryEmphasizedText(p_keyword)) {
ret = true;
}
return ret;
} }
QString VPlantUMLHelper::keywordForSmartLivePreview(const QString &p_text, QString VPlantUMLHelper::keywordForSmartLivePreview(const QString &p_text,
@ -709,7 +1008,11 @@ QString VPlantUMLHelper::keywordForSmartLivePreview(const QString &p_text,
qDebug() << "tryKeywords" << kw; qDebug() << "tryKeywords" << kw;
if (tryKeywords(kw)) { if (tryKeywords(kw, needCreole)) {
if (needCreole) {
goto creole;
}
return kw; return kw;
} }
@ -753,6 +1056,36 @@ QString VPlantUMLHelper::keywordForSmartLivePreview(const QString &p_text,
return kw; return kw;
} }
qDebug() << "tryComponentDiagram" << kw;
if (tryComponentDiagram(kw, p_hints, p_isRegex, needCreole)) {
if (needCreole) {
goto creole;
}
return kw;
}
qDebug() << "tryStateDiagram" << kw;
if (tryStateDiagram(kw, p_hints, p_isRegex, needCreole)) {
if (needCreole) {
goto creole;
}
return kw;
}
qDebug() << "tryObjectDiagram" << kw;
if (tryObjectDiagram(kw, p_hints, p_isRegex, needCreole)) {
if (needCreole) {
goto creole;
}
return kw;
}
qDebug() << "tryCommonElements" << kw; qDebug() << "tryCommonElements" << kw;
if (tryCommonElements(kw, p_hints, p_isRegex, needCreole)) { if (tryCommonElements(kw, p_hints, p_isRegex, needCreole)) {