vnote/src/vplantumlhelper.cpp
2019-06-22 20:05:33 +08:00

1134 lines
31 KiB
C++

#include "vplantumlhelper.h"
#include <QDebug>
#include <QThread>
#include "vconfigmanager.h"
#include "utils/vprocessutils.h"
extern VConfigManager *g_config;
#define TaskIdProperty "PlantUMLTaskId"
#define TaskFormatProperty "PlantUMLTaskFormat"
#define TaskTimeStampProperty "PlantUMLTaskTimeStamp"
VPlantUMLHelper::VPlantUMLHelper(QObject *p_parent)
: QObject(p_parent)
{
m_customCmd = g_config->getPlantUMLCmd();
if (m_customCmd.isEmpty()) {
prepareCommand(m_program, m_args);
}
}
VPlantUMLHelper::VPlantUMLHelper(const QString &p_jar, QObject *p_parent)
: QObject(p_parent)
{
m_customCmd = g_config->getPlantUMLCmd();
if (m_customCmd.isEmpty()) {
prepareCommand(m_program, m_args, p_jar);
}
}
void VPlantUMLHelper::processAsync(int p_id,
TimeStamp p_timeStamp,
const QString &p_format,
const QString &p_text)
{
QProcess *process = new QProcess(this);
process->setProperty(TaskIdProperty, p_id);
process->setProperty(TaskTimeStampProperty, p_timeStamp);
process->setProperty(TaskFormatProperty, p_format);
connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
this, SLOT(handleProcessFinished(int, QProcess::ExitStatus)));
if (m_customCmd.isEmpty()) {
QStringList args(m_args);
args << ("-t" + p_format);
qDebug() << m_program << args;
process->start(m_program, refineArgsForUse(args));
} else {
QString cmd(m_customCmd);
cmd.replace("%0", p_format);
qDebug() << cmd;
process->start(cmd);
}
if (process->write(p_text.toUtf8()) == -1) {
qWarning() << "fail to write to QProcess:" << process->errorString();
}
process->closeWriteChannel();
}
void VPlantUMLHelper::prepareCommand(QString &p_program,
QStringList &p_args,
const QString &p_jar) const
{
#if defined(Q_OS_WIN)
p_program = "cmd.exe";
p_args << "/c";
#else
p_program = "/bin/sh";
p_args << "-c";
#endif
p_args << "java";
p_args << "-jar" << (p_jar.isEmpty() ? g_config->getPlantUMLJar() : p_jar);
p_args << "-charset" << "UTF-8";
/*
int nbthread = QThread::idealThreadCount();
p_args << "-nbthread" << QString::number(nbthread > 0 ? nbthread : 1);
*/
const QString &dot = g_config->getGraphvizDot();
if (!dot.isEmpty()) {
p_args << "-graphvizdot";
p_args << dot;
}
p_args << "-pipe";
p_args << g_config->getPlantUMLArgs();
}
void VPlantUMLHelper::handleProcessFinished(int p_exitCode, QProcess::ExitStatus p_exitStatus)
{
QProcess *process = static_cast<QProcess *>(sender());
int id = process->property(TaskIdProperty).toInt();
QString format = process->property(TaskFormatProperty).toString();
TimeStamp timeStamp = process->property(TaskTimeStampProperty).toULongLong();
qDebug() << QString("PlantUML finished: id %1 timestamp %2 format %3 exitcode %4 exitstatus %5")
.arg(id)
.arg(timeStamp)
.arg(format)
.arg(p_exitCode)
.arg(p_exitStatus);
bool failed = true;
if (p_exitStatus == QProcess::NormalExit) {
if (p_exitCode < 0) {
qWarning() << "PlantUML fail" << p_exitCode;
} else {
failed = false;
QByteArray outBa = process->readAllStandardOutput();
if (format == "svg") {
emit resultReady(id, timeStamp, format, QString::fromLocal8Bit(outBa));
} else {
emit resultReady(id, timeStamp, format, QString::fromLocal8Bit(outBa.toBase64()));
}
}
} else {
qWarning() << "fail to start PlantUML process" << p_exitCode << p_exitStatus;
}
QByteArray errBa = process->readAllStandardError();
if (!errBa.isEmpty()) {
QString errStr(QString::fromLocal8Bit(errBa));
if (failed) {
qWarning() << "PlantUML stderr:" << errStr;
} else {
qDebug() << "PlantUML stderr:" << errStr;
}
}
if (failed) {
emit resultReady(id, timeStamp, format, "");
}
process->deleteLater();
}
bool VPlantUMLHelper::testPlantUMLJar(const QString &p_jar, QString &p_msg)
{
VPlantUMLHelper inst(p_jar);
QStringList args(inst.m_args);
args << "-tsvg";
args = refineArgsForUse(args);
QString testGraph("VNote->Markdown : hello");
int exitCode = -1;
QByteArray out, err;
int ret = VProcessUtils::startProcess(inst.m_program,
args,
testGraph.toUtf8(),
exitCode,
out,
err);
p_msg = QString("Command: %1 %2\nExitCode: %3\nOutput: %4\nError: %5")
.arg(inst.m_program)
.arg(args.join(' '))
.arg(exitCode)
.arg(QString::fromLocal8Bit(out))
.arg(QString::fromLocal8Bit(err));
return ret == 0 && exitCode == 0;
}
QByteArray VPlantUMLHelper::process(const QString &p_format, const QString &p_text)
{
VPlantUMLHelper inst;
int exitCode = -1;
QByteArray out, err;
int ret = -1;
if (inst.m_customCmd.isEmpty()) {
QStringList args(inst.m_args);
args << ("-t" + p_format);
args = refineArgsForUse(args);
ret = VProcessUtils::startProcess(inst.m_program,
args,
p_text.toUtf8(),
exitCode,
out,
err);
} else {
QString cmd(inst.m_customCmd);
cmd.replace("%0", p_format);
ret = VProcessUtils::startProcess(cmd,
p_text.toUtf8(),
exitCode,
out,
err);
}
if (ret != 0 || exitCode < 0) {
qWarning() << "PlantUML fail" << ret << exitCode << QString::fromLocal8Bit(err);
}
return out;
}
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|"
"--|\\|\\||(?: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;
}
static bool tryClassDiagram(QString &p_keyword, QString &p_hints, bool &p_isRegex, bool &p_needCreole)
{
Q_UNUSED(p_isRegex);
// class ABC #Pink
// 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);
p_hints = "id";
return true;
}
// class "ABC DEF" as AD #Pink
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()) {
p_keyword = classDef2.cap(1).trimmed();
} else {
p_keyword = classDef2.cap(2);
}
p_hints = "id";
return true;
}
// class01 "1" *-- "many" class02 : contains 4 >
static QRegExp relation("^\\s*(?:(\\w+)|\"([^\"]+)\")\\s*"
"(?:\"[^\"]+\"\\s*)?"
"(?:<\\||[*o<#x}+^])?" "(?:-+|\\.+)" "(?:\\|>|[*o>#x{+^])?\\s*"
"(?:\"[^\"]+\"\\s*)?"
"(?:(\\w+)|\"([^\"]+)\")\\s*"
"(?::(.+))?");
if (relation.indexIn(p_keyword) >= 0) {
if (relation.cap(5).isEmpty()) {
QString class2 = relation.cap(3);
if (class2.isEmpty()) {
class2 = relation.cap(4).trimmed();
}
p_keyword = class2;
p_hints = "id";
} else {
p_needCreole = true;
p_keyword = relation.cap(5).trimmed();
}
return true;
}
// {static} field : String
bool containsModifier = false;
static QRegExp modifier("\\{(?:static|abstract|classifier)\\}");
if (modifier.indexIn(p_keyword) >= 0) {
containsModifier = true;
p_keyword.remove(modifier);
}
// + field
static QRegExp member("^\\s*[-#~+]\\s*(.*)");
if (member.indexIn(p_keyword) >= 0) {
p_keyword = member.cap(1).trimmed();
return true;
} else if (containsModifier) {
p_keyword = p_keyword.trimmed();
return true;
}
// note left on link #Pink : message
// note left on link
// node on link: message
// MUST before next rule "note".
static QRegExp note4("^\\s*note\\s+(?:(?:left|top|right|bottom)\\s+)?"
"on\\s+link"
"[^:]*"
"(?::(.*))?");
if (note4.indexIn(p_keyword) >= 0) {
p_needCreole = true;
p_keyword = note4.cap(1).trimmed();
return true;
}
// note top of Object #Pink : message
// note top of Object
// 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+)|"
"\\(([^\\)]+)\\)|"
"\\[([^\\]]+)\\]))?"
"[^:]*"
"(?::(.*))?");
if (note.indexIn(p_keyword) >= 0) {
p_keyword = note.cap(4).trimmed();
if (p_keyword.isEmpty()) {
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;
}
return true;
}
// note "a floating note" as N1 #Pink
// note as N1
static QRegExp note2("^\\s*note\\s+(?:\"([^\"]*)\"\\s+)?as\\s+\\w+");
if (note2.indexIn(p_keyword) >= 0) {
p_keyword = note2.cap(1).trimmed();
return true;
}
return false;
}
static bool tryCommonElements(QString &p_keyword, QString &p_hints, bool &p_isRegex, bool &p_needCreole)
{
Q_UNUSED(p_isRegex);
Q_UNUSED(p_hints);
Q_UNUSED(p_needCreole);
// Words in quotes.
// cmf("abc")
static QRegExp quote("^[^\"]*\"([^\"]+)\"[^\"]*$");
if (quote.indexIn(p_keyword) >= 0) {
p_keyword = quote.cap(1).trimmed();
return true;
}
return false;
}
static bool tryActivityDiagram(QString &p_keyword, QString &p_hints, bool &p_isRegex, bool &p_needCreole)
{
Q_UNUSED(p_isRegex);
Q_UNUSED(p_hints);
// Activity. (Do not support color.)
// :Hello world;
// :Across multiple lines
static QRegExp activity1("^\\s*:([^:]+)\\s*$");
if (activity1.indexIn(p_keyword) >= 0) {
p_keyword = activity1.cap(1).trimmed();
if (!p_keyword.isEmpty()) {
QChar ch = p_keyword[p_keyword.size() - 1];
if (ch == ';' || ch == '|' || ch == '<' || ch == '>'
|| ch == '/' || ch == ']' || ch == '}') {
p_keyword = p_keyword.left(p_keyword.size() - 1);
}
}
p_needCreole = true;
return true;
}
// Activity.
// multiple lines;
static QRegExp activity2("^\\s*([^\\[]+)([;|<>/\\]}])\\s*$");
if (activity2.indexIn(p_keyword) >= 0) {
QString word = activity2.cap(1);
QChar end = activity2.cap(2)[0];
if (end != ';' && !word.isEmpty()) {
// || << >> // ]] }} are not legal.
if (word[word.size() - 1] == end) {
return false;
}
}
// It may conflict with note.
p_needCreole = true;
p_keyword = word.trimmed();
return true;
}
// Conditionals.
// if (Graphviz) then (yes)
// else if (Graphviz) then (yes)
static QRegExp conIf("^\\s*(?:else)?if\\s+\\(([^\\)]+)\\)\\s+then(?:\\s+\\([^\\)]+\\))?\\s*$");
if (conIf.indexIn(p_keyword) >= 0) {
p_keyword = conIf.cap(1).trimmed();
return true;
}
// else (no)
static QRegExp conElse("^\\s*else(?:\\s+\\(([^\\)]+)\\))?\\s*$");
if (conElse.indexIn(p_keyword) >= 0) {
p_keyword = conElse.cap(1).trimmed();
return true;
}
// Repeat loop.
// repeat while (more data?)
static QRegExp repeat("^\\s*repeat\\s+while\\s+\\(([^\\)]+)\\)\\s*$");
if (repeat.indexIn(p_keyword) >= 0) {
p_keyword = repeat.cap(1).trimmed();
return true;
}
// while (check?) is (not empty)
static QRegExp whileLoop("^\\s*while\\s+\\(([^\\)]+)\\)(?:\\s+is\\s+\\([^\\)]+\\))?\\s*$");
if (whileLoop.indexIn(p_keyword) >= 0) {
p_keyword = whileLoop.cap(1).trimmed();
return true;
}
// endwhile (empty)
static QRegExp endWhile("^\\s*endwhile(?:\\s+\\(([^\\)]+)\\))?\\s*$");
if (endWhile.indexIn(p_keyword) >= 0) {
p_keyword = endWhile.cap(1).trimmed();
return true;
}
// partition Running {
static QRegExp partition("^\\s*partition\\s+(\\w+)\\s+\\{\\s*$");
if (partition.indexIn(p_keyword) >= 0) {
p_keyword = partition.cap(1).trimmed();
return true;
}
// |Swimlane1|
// |#Pink|Swimlane1|
static QRegExp swimline("^\\s*(?:\\|[^\\|]+)?\\|([^|]+)\\|\\s*$");
if (swimline.indexIn(p_keyword) >= 0) {
p_keyword = swimline.cap(1).trimmed();
return true;
}
return false;
}
static bool trySequenceDiagram(QString &p_keyword, QString &p_hints, bool &p_isRegex, bool &p_needCreole)
{
Q_UNUSED(p_isRegex);
Q_UNUSED(p_hints);
// participant ABC #Pink
// participant "ABC DEF" as AD #Pink
static QRegExp participant1("^\\s*(?:participant|actor|boundary|control|entity|database)\\s+"
"(?:(\\w+)|\"([^\"]+)\"\\s*(?:\\bas\\s+\\w+)?)");
if (participant1.indexIn(p_keyword) >= 0) {
p_keyword = participant1.cap(1);
if (p_keyword.isEmpty()) {
p_keyword = participant1.cap(2).trimmed();
}
return true;
}
// "abc" ->> "def" : Authentication
static QRegExp message("^\\s*(?:\\w+|\"[^\"]+\")\\s+"
"[-<>x\\\\/o]+\\s+"
"(?:\\w+|\"[^\"]+\")\\s*"
":\\s*(.+)");
if (message.indexIn(p_keyword) >= 0) {
p_needCreole = true;
p_keyword = message.cap(1).trimmed();
return true;
}
// autonumber
static QRegExp autonum("^\\s*autonumber\\s+");
if (autonum.indexIn(p_keyword) >= 0) {
p_keyword.clear();
return true;
}
// newpage
static QRegExp newpage("^\\s*newpage\\s+(.+)");
if (newpage.indexIn(p_keyword) >= 0) {
p_keyword = newpage.cap(1).trimmed();
return true;
}
// alt, else, group, loop ABCDEFG
static QRegExp group1("^\\s*(?:alt|else|group|loop)\\s+(.*)");
if (group1.indexIn(p_keyword) >= 0) {
p_keyword = group1.cap(1).trimmed();
return true;
}
// note over bob, alice #Pink:
// ref over bob, alice : init
static QRegExp noteon("^\\s*(?:[hr]?note|ref)\\s+over\\s+"
"(\\w+)[^:]*"
"(?::(.+))?");
if (noteon.indexIn(p_keyword) >= 0) {
p_keyword = noteon.cap(2).trimmed();
if (p_keyword.isEmpty()) {
p_keyword = noteon.cap(1);
} else {
p_needCreole = true;
}
return true;
}
// Divider.
// == Initialization ==
static QRegExp divider("^\\s*==\\s*([^=]*)==\\s*$");
if (divider.indexIn(p_keyword) >= 0) {
p_needCreole = true;
p_keyword = divider.cap(1).trimmed();
return true;
}
// Delay.
// ... 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;
}
// activate A
static QRegExp activate("^\\s*(?:(?:de)?activate|destroy)\\s+"
"(?:(\\w+)|\"([^\"]+)\")");
if (activate.indexIn(p_keyword) >= 0) {
p_keyword = activate.cap(1);
if (p_keyword.isEmpty()) {
p_keyword = activate.cap(2).trimmed();
}
return true;
}
// create control ABC
static QRegExp create("^\\s*create\\s+(?:\\w+\\s+)?"
"(?:(\\w+)|\"([^\"]+)\")");
if (create.indexIn(p_keyword) >= 0) {
p_keyword = create.cap(1);
if (p_keyword.isEmpty()) {
p_keyword = create.cap(2).trimmed();
}
return true;
}
// Incoming and outgoing message.
static QRegExp incoming("^\\s*\\[[-<>ox]+\\s*"
"(?:\\w+|\"[^\"]+\")\\s*"
":\\s*(.+)");
if (incoming.indexIn(p_keyword) >= 0) {
p_needCreole = true;
p_keyword = incoming.cap(1).trimmed();
return true;
}
static QRegExp outgoing("^\\s*(?:\\w+|\"[^\"]+\")\\s*"
"[-<>ox]+\\]\\s*"
":\\s*(.+)");
if (outgoing.indexIn(p_keyword) >= 0) {
p_needCreole = true;
p_keyword = outgoing.cap(1).trimmed();
return true;
}
// box "Internal Service" #Pink
static QRegExp box("^\\s*box(?:\\s+\"([^\"]+)\")?\\s*");
if (box.indexIn(p_keyword) >= 0) {
p_keyword = box.cap(1).trimmed();
return true;
}
return false;
}
static bool tryUseCaseDiagram(QString &p_keyword, QString &p_hints, bool &p_isRegex, bool &p_needCreole)
{
Q_UNUSED(p_isRegex);
Q_UNUSED(p_hints);
// User -> (Start)
// User --> (Use the application) : A small label
// :Main Admin: --> (Use the application) : This is another label
// (chekckout) -- (payment) : include
static QRegExp rel("^\\s*(?:(\\w+)|:([^:]+):|\\(([^\\)]+)\\))\\s*"
"[-.<>]+\\s*"
"(?:\\(([^\\)]+)\\)|(\\w+))\\s*"
"(?::(.+))?");
if (rel.indexIn(p_keyword) >= 0) {
QString msg(rel.cap(6).trimmed());
if (msg.isEmpty()) {
QString ent2(rel.cap(4).trimmed());
if (ent2.isEmpty()) {
ent2 = rel.cap(5).trimmed();
}
if (ent2.isEmpty()) {
QString ent1(rel.cap(3).trimmed());
if (ent1.isEmpty()) {
ent1 = rel.cap(2).trimmed();
if (ent1.isEmpty()) {
ent1 = rel.cap(1).trimmed();
}
}
p_keyword = ent1;
} else {
p_keyword = ent2;
}
} else {
p_keyword = msg;
}
p_needCreole = true;
return true;
}
// Usecase.
// (First usecase) as (UC2)
// usecase UC3
// usecase (Last usecase) as UC4
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();
p_needCreole = true;
} else {
p_keyword = usecase1.cap(1);
}
return true;
}
// This will eat almost anything starting with ().
static QRegExp usecase2("^\\s*\\(([^\\)]+)\\)");
if (usecase2.indexIn(p_keyword) >= 0) {
p_keyword = usecase2.cap(1).trimmed();
p_needCreole = true;
return true;
}
// Actor.
// :Another\nactor: as men2
// actor :Last actor: as men4
static QRegExp actor1("^\\s*(?:actor\\s+)?:([^:]+):(?:\\s+as\\s+\\w+)?[^:]*$");
if (actor1.indexIn(p_keyword) >= 0) {
p_keyword = actor1.cap(1).trimmed();
p_needCreole = true;
return true;
}
// actor men1
static QRegExp actor2("^\\s*actor\\s+(\\w+)");
if (actor2.indexIn(p_keyword) >= 0) {
p_keyword = actor2.cap(1).trimmed();
p_needCreole = true;
return true;
}
// rectangle checkout {
static QRegExp rect("^\\s*rectangle\\s+(?:(\\w+)|\"([^\"]+)\")");
if (rect.indexIn(p_keyword) >= 0) {
p_keyword = rect.cap(1).trimmed();
if (p_keyword.isEmpty()) {
p_keyword = rect.cap(2).trimmed();
}
p_needCreole = 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;
}
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();
ret = true;
}
// 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 &p_hints,
bool &p_isRegex)
{
QString kw = p_text.trimmed();
if (kw.isEmpty()) {
return kw;
}
p_isRegex = false;
bool needCreole = false;
qDebug() << "tryKeywords" << kw;
if (tryKeywords(kw, needCreole)) {
if (needCreole) {
goto creole;
}
return kw;
}
qDebug() << "tryClassDiagram" << kw;
if (tryClassDiagram(kw, p_hints, p_isRegex, needCreole)) {
if (needCreole) {
goto creole;
}
return kw;
}
qDebug() << "tryActivityDiagram" << kw;
if (tryActivityDiagram(kw, p_hints, p_isRegex, needCreole)) {
if (needCreole) {
goto creole;
}
return kw;
}
qDebug() << "trySequenceDiagram" << kw;
if (trySequenceDiagram(kw, p_hints, p_isRegex, needCreole)) {
if (needCreole) {
goto creole;
}
return kw;
}
qDebug() << "tryUseCaseDiagram" << kw;
if (tryUseCaseDiagram(kw, p_hints, p_isRegex, needCreole)) {
if (needCreole) {
goto creole;
}
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)) {
if (needCreole) {
goto creole;
}
return kw;
}
creole:
tryCreole(kw);
return kw;
}
QStringList VPlantUMLHelper::refineArgsForUse(const QStringList &p_args)
{
if (p_args.isEmpty()) {
return QStringList();
}
if (p_args[0] == "-c") {
QStringList args;
args << p_args[0];
QString subCmd;
for (int i = 1; i < p_args.size(); ++i) {
subCmd += " " + p_args[i];
}
args << subCmd;
return args;
} else {
return p_args;
}
}