From 2338002b1f95372f94672d21b79576fd8a3adc47 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Wed, 5 Sep 2018 20:15:48 +0800 Subject: [PATCH] LivePreview: support component/state/object diagram --- src/vplantumlhelper.cpp | 369 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 351 insertions(+), 18 deletions(-) diff --git a/src/vplantumlhelper.cpp b/src/vplantumlhelper.cpp index b9dfae96..56309c33 100644 --- a/src/vplantumlhelper.cpp +++ b/src/vplantumlhelper.cpp @@ -189,23 +189,55 @@ QByteArray VPlantUMLHelper::process(const QString &p_format, const QString &p_te 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, }, // detach, end note, end box, endrnote, endhnote, // top to bottom direction, left to right direction, // @startuml, @enduml + // ||, --, title, end title, end legend static QRegExp keywords("^\\s*(?:start|stop|end|endif|repeat|" "fork(?:\\s+again)?|end\\s+fork|\\}|detach|" "end ?(?:note|box)|endrnote|endhnote|" "top\\s+to\\s+bottom\\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) { p_keyword.clear(); 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; } @@ -214,7 +246,8 @@ static bool tryClassDiagram(QString &p_keyword, QString &p_hints, bool &p_isRege Q_UNUSED(p_isRegex); // 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+)"); if (classDef1.indexIn(p_keyword) >= 0) { 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 - 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+))?"); if (classDef2.indexIn(p_keyword) >= 0) { if (classDef2.cap(2).isEmpty()) { @@ -296,19 +329,28 @@ static bool tryClassDiagram(QString &p_keyword, QString &p_hints, bool &p_isRege // note top: message // hnote and rnote for sequence diagram. // note right of (use case) + // note right of [component] 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) { - p_keyword = note.cap(3).trimmed(); + p_keyword = note.cap(4).trimmed(); if (p_keyword.isEmpty()) { - p_keyword = note.cap(2).trimmed(); - if (p_keyword.isEmpty()) { - p_keyword = note.cap(1); - p_hints = "id"; - } else { + QString ent = note.cap(1); + if (ent.isEmpty()) { + ent = note.cap(2).trimmed(); + if (ent.isEmpty()) { + ent = note.cap(3).trimmed(); + } + + p_keyword = ent; p_needCreole = true; + } else { + p_keyword = ent; + p_hints = "id"; } } else { p_needCreole = true; @@ -370,7 +412,7 @@ static bool tryActivityDiagram(QString &p_keyword, QString &p_hints, bool &p_isR // Activity. // multiple lines; - static QRegExp activity2("^\\s*(.+)([;|<>/\\]}])\\s*$"); + static QRegExp activity2("^\\s*([^\\[]+)([;|<>/\\]}])\\s*$"); if (activity2.indexIn(p_keyword) >= 0) { QString word = activity2.cap(1); 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 static QRegExp message("^\\s*(?:\\w+|\"[^\"]+\")\\s+" - "[-<>x\\\\/o]{2,}\\s+" + "[-<>x\\\\/o]+\\s+" "(?:\\w+|\"[^\"]+\")\\s*" ":\\s*(.+)"); if (message.indexIn(p_keyword) >= 0) { @@ -513,6 +555,7 @@ static bool trySequenceDiagram(QString &p_keyword, QString &p_hints, bool &p_isR // == Initialization == static QRegExp divider("^\\s*==\\s*([^=]*)==\\s*$"); if (divider.indexIn(p_keyword) >= 0) { + p_needCreole = true; p_keyword = divider.cap(1).trimmed(); return true; } @@ -521,6 +564,7 @@ static bool trySequenceDiagram(QString &p_keyword, QString &p_hints, bool &p_isR // ... 5 minutes latter ... static QRegExp delay("^\\s*\\.\\.\\.(?:(.+)\\.\\.\\.)?\\s*$"); if (delay.indexIn(p_keyword) >= 0) { + p_needCreole = true; p_keyword = delay.cap(1).trimmed(); 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 // (chekckout) -- (payment) : include static QRegExp rel("^\\s*(?:(\\w+)|:([^:]+):|\\(([^\\)]+)\\))\\s*" - "[-.<>]{2,}\\s*" + "[-.<>]+\\s*" "(?:\\(([^\\)]+)\\)|(\\w+))\\s*" "(?::(.+))?"); 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) // usecase UC3 // 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.cap(1).isEmpty()) { p_keyword = usecase1.cap(2).trimmed(); @@ -674,25 +718,280 @@ static bool tryUseCaseDiagram(QString &p_keyword, QString &p_hints, bool &p_isRe 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; } +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) { if (p_keyword.isEmpty()) { return false; } + bool ret = false; + // List. // ** list // # list static QRegExp listMark("^\\s*(?:\\*+|#+)\\s+(.+)$"); if (listMark.indexIn(p_keyword) >= 0) { 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, @@ -709,7 +1008,11 @@ QString VPlantUMLHelper::keywordForSmartLivePreview(const QString &p_text, qDebug() << "tryKeywords" << kw; - if (tryKeywords(kw)) { + if (tryKeywords(kw, needCreole)) { + if (needCreole) { + goto creole; + } + return kw; } @@ -753,6 +1056,36 @@ QString VPlantUMLHelper::keywordForSmartLivePreview(const QString &p_text, 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; if (tryCommonElements(kw, p_hints, p_isRegex, needCreole)) {