Browse Source

LivePreview: support component/state/object diagram

Le Tan 7 years ago
parent
commit
2338002b1f
1 changed files with 351 additions and 18 deletions
  1. 351 18
      src/vplantumlhelper.cpp

+ 351 - 18
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)) {