vplantumlhelper.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769
  1. #include "vplantumlhelper.h"
  2. #include <QDebug>
  3. #include <QThread>
  4. #include "vconfigmanager.h"
  5. #include "utils/vprocessutils.h"
  6. extern VConfigManager *g_config;
  7. #define TaskIdProperty "PlantUMLTaskId"
  8. #define TaskFormatProperty "PlantUMLTaskFormat"
  9. #define TaskTimeStampProperty "PlantUMLTaskTimeStamp"
  10. VPlantUMLHelper::VPlantUMLHelper(QObject *p_parent)
  11. : QObject(p_parent)
  12. {
  13. m_customCmd = g_config->getPlantUMLCmd();
  14. if (m_customCmd.isEmpty()) {
  15. prepareCommand(m_program, m_args);
  16. }
  17. }
  18. VPlantUMLHelper::VPlantUMLHelper(const QString &p_jar, QObject *p_parent)
  19. : QObject(p_parent)
  20. {
  21. m_customCmd = g_config->getPlantUMLCmd();
  22. if (m_customCmd.isEmpty()) {
  23. prepareCommand(m_program, m_args, p_jar);
  24. }
  25. }
  26. void VPlantUMLHelper::processAsync(int p_id,
  27. TimeStamp p_timeStamp,
  28. const QString &p_format,
  29. const QString &p_text)
  30. {
  31. QProcess *process = new QProcess(this);
  32. process->setProperty(TaskIdProperty, p_id);
  33. process->setProperty(TaskTimeStampProperty, p_timeStamp);
  34. process->setProperty(TaskFormatProperty, p_format);
  35. connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
  36. this, SLOT(handleProcessFinished(int, QProcess::ExitStatus)));
  37. if (m_customCmd.isEmpty()) {
  38. QStringList args(m_args);
  39. args << ("-t" + p_format);
  40. qDebug() << m_program << args;
  41. process->start(m_program, args);
  42. } else {
  43. QString cmd(m_customCmd);
  44. cmd.replace("%0", p_format);
  45. qDebug() << cmd;
  46. process->start(cmd);
  47. }
  48. if (process->write(p_text.toUtf8()) == -1) {
  49. qWarning() << "fail to write to QProcess:" << process->errorString();
  50. }
  51. process->closeWriteChannel();
  52. }
  53. void VPlantUMLHelper::prepareCommand(QString &p_program,
  54. QStringList &p_args,
  55. const QString &p_jar) const
  56. {
  57. p_program = "java";
  58. p_args << "-jar" << (p_jar.isEmpty() ? g_config->getPlantUMLJar() : p_jar);
  59. p_args << "-charset" << "UTF-8";
  60. int nbthread = QThread::idealThreadCount();
  61. p_args << "-nbthread" << QString::number(nbthread > 0 ? nbthread : 1);
  62. const QString &dot = g_config->getGraphvizDot();
  63. if (!dot.isEmpty()) {
  64. p_args << "-graphvizdot";
  65. p_args << dot;
  66. }
  67. p_args << "-pipe";
  68. p_args << g_config->getPlantUMLArgs();
  69. }
  70. void VPlantUMLHelper::handleProcessFinished(int p_exitCode, QProcess::ExitStatus p_exitStatus)
  71. {
  72. QProcess *process = static_cast<QProcess *>(sender());
  73. int id = process->property(TaskIdProperty).toInt();
  74. QString format = process->property(TaskFormatProperty).toString();
  75. TimeStamp timeStamp = process->property(TaskTimeStampProperty).toULongLong();
  76. qDebug() << QString("PlantUML finished: id %1 timestamp %2 format %3 exitcode %4 exitstatus %5")
  77. .arg(id)
  78. .arg(timeStamp)
  79. .arg(format)
  80. .arg(p_exitCode)
  81. .arg(p_exitStatus);
  82. bool failed = true;
  83. if (p_exitStatus == QProcess::NormalExit) {
  84. if (p_exitCode < 0) {
  85. qWarning() << "PlantUML fail" << p_exitCode;
  86. } else {
  87. failed = false;
  88. QByteArray outBa = process->readAllStandardOutput();
  89. if (format == "svg") {
  90. emit resultReady(id, timeStamp, format, QString::fromLocal8Bit(outBa));
  91. } else {
  92. emit resultReady(id, timeStamp, format, QString::fromLocal8Bit(outBa.toBase64()));
  93. }
  94. }
  95. } else {
  96. qWarning() << "fail to start PlantUML process" << p_exitCode << p_exitStatus;
  97. }
  98. QByteArray errBa = process->readAllStandardError();
  99. if (!errBa.isEmpty()) {
  100. QString errStr(QString::fromLocal8Bit(errBa));
  101. if (failed) {
  102. qWarning() << "PlantUML stderr:" << errStr;
  103. } else {
  104. qDebug() << "PlantUML stderr:" << errStr;
  105. }
  106. }
  107. if (failed) {
  108. emit resultReady(id, timeStamp, format, "");
  109. }
  110. process->deleteLater();
  111. }
  112. bool VPlantUMLHelper::testPlantUMLJar(const QString &p_jar, QString &p_msg)
  113. {
  114. VPlantUMLHelper inst(p_jar);
  115. QStringList args(inst.m_args);
  116. args << "-tsvg";
  117. QString testGraph("VNote->Markdown : hello");
  118. int exitCode = -1;
  119. QByteArray out, err;
  120. int ret = VProcessUtils::startProcess(inst.m_program,
  121. args,
  122. testGraph.toUtf8(),
  123. exitCode,
  124. out,
  125. err);
  126. p_msg = QString("Command: %1 %2\nExitCode: %3\nOutput: %4\nError: %5")
  127. .arg(inst.m_program)
  128. .arg(args.join(' '))
  129. .arg(exitCode)
  130. .arg(QString::fromLocal8Bit(out))
  131. .arg(QString::fromLocal8Bit(err));
  132. return ret == 0 && exitCode == 0;
  133. }
  134. QByteArray VPlantUMLHelper::process(const QString &p_format, const QString &p_text)
  135. {
  136. VPlantUMLHelper inst;
  137. int exitCode = -1;
  138. QByteArray out, err;
  139. int ret = -1;
  140. if (inst.m_customCmd.isEmpty()) {
  141. QStringList args(inst.m_args);
  142. args << ("-t" + p_format);
  143. ret = VProcessUtils::startProcess(inst.m_program,
  144. args,
  145. p_text.toUtf8(),
  146. exitCode,
  147. out,
  148. err);
  149. } else {
  150. QString cmd(inst.m_customCmd);
  151. cmd.replace("%0", p_format);
  152. ret = VProcessUtils::startProcess(cmd,
  153. p_text.toUtf8(),
  154. exitCode,
  155. out,
  156. err);
  157. }
  158. if (ret != 0 || exitCode < 0) {
  159. qWarning() << "PlantUML fail" << ret << exitCode << QString::fromLocal8Bit(err);
  160. }
  161. return out;
  162. }
  163. static bool tryKeywords(QString &p_keyword)
  164. {
  165. // start, stop, end, endif, repeat, fork, fork again, end fork, },
  166. // detach, end note, end box, endrnote, endhnote,
  167. // top to bottom direction, left to right direction,
  168. // @startuml, @enduml
  169. static QRegExp keywords("^\\s*(?:start|stop|end|endif|repeat|"
  170. "fork(?:\\s+again)?|end\\s+fork|\\}|detach|"
  171. "end ?(?:note|box)|endrnote|endhnote|"
  172. "top\\s+to\\s+bottom\\s+direction|"
  173. "left\\s+to\\s+right\\s+direction|"
  174. "@startuml|@enduml)\\s*$");
  175. if (keywords.indexIn(p_keyword) >= 0) {
  176. p_keyword.clear();
  177. return true;
  178. }
  179. return false;
  180. }
  181. static bool tryClassDiagram(QString &p_keyword, QString &p_hints, bool &p_isRegex, bool &p_needCreole)
  182. {
  183. Q_UNUSED(p_isRegex);
  184. // class ABC #Pink
  185. static QRegExp classDef1("^\\s*(?:class|(?:abstract(?:\\s+class)?)|interface|annotation|enum)\\s+"
  186. "(?!class)(\\w+)");
  187. if (classDef1.indexIn(p_keyword) >= 0) {
  188. p_keyword = classDef1.cap(1);
  189. p_hints = "id";
  190. return true;
  191. }
  192. // class "ABC DEF" as AD #Pink
  193. static QRegExp classDef2("^\\s*(?:class|(?:abstract(?:\\s+class)?)|interface|annotation|enum)\\s+"
  194. "\"([^\"]+)\"\\s*(?:\\bas\\s+(\\w+))?");
  195. if (classDef2.indexIn(p_keyword) >= 0) {
  196. if (classDef2.cap(2).isEmpty()) {
  197. p_keyword = classDef2.cap(1).trimmed();
  198. } else {
  199. p_keyword = classDef2.cap(2);
  200. }
  201. p_hints = "id";
  202. return true;
  203. }
  204. // class01 "1" *-- "many" class02 : contains 4 >
  205. static QRegExp relation("^\\s*(?:(\\w+)|\"([^\"]+)\")\\s*"
  206. "(?:\"[^\"]+\"\\s*)?"
  207. "(?:<\\||[*o<#x}+^])?" "(?:-+|\\.+)" "(?:\\|>|[*o>#x{+^])?\\s*"
  208. "(?:\"[^\"]+\"\\s*)?"
  209. "(?:(\\w+)|\"([^\"]+)\")\\s*"
  210. "(?::(.+))?");
  211. if (relation.indexIn(p_keyword) >= 0) {
  212. if (relation.cap(5).isEmpty()) {
  213. QString class2 = relation.cap(3);
  214. if (class2.isEmpty()) {
  215. class2 = relation.cap(4).trimmed();
  216. }
  217. p_keyword = class2;
  218. p_hints = "id";
  219. } else {
  220. p_needCreole = true;
  221. p_keyword = relation.cap(5).trimmed();
  222. }
  223. return true;
  224. }
  225. // {static} field : String
  226. bool containsModifier = false;
  227. static QRegExp modifier("\\{(?:static|abstract|classifier)\\}");
  228. if (modifier.indexIn(p_keyword) >= 0) {
  229. containsModifier = true;
  230. p_keyword.remove(modifier);
  231. }
  232. // + field
  233. static QRegExp member("^\\s*[-#~+]\\s*(.*)");
  234. if (member.indexIn(p_keyword) >= 0) {
  235. p_keyword = member.cap(1).trimmed();
  236. return true;
  237. } else if (containsModifier) {
  238. p_keyword = p_keyword.trimmed();
  239. return true;
  240. }
  241. // note left on link #Pink : message
  242. // note left on link
  243. // node on link: message
  244. // MUST before next rule "note".
  245. static QRegExp note4("^\\s*note\\s+(?:(?:left|top|right|bottom)\\s+)?"
  246. "on\\s+link"
  247. "[^:]*"
  248. "(?::(.*))?");
  249. if (note4.indexIn(p_keyword) >= 0) {
  250. p_needCreole = true;
  251. p_keyword = note4.cap(1).trimmed();
  252. return true;
  253. }
  254. // note top of Object #Pink : message
  255. // note top of Object
  256. // note top: message
  257. // hnote and rnote for sequence diagram.
  258. // note right of (use case)
  259. static QRegExp note("^\\s*[hr]?note\\s+(?:left|top|right|bottom)"
  260. "(?:\\s+of\\s+(?:(\\w+)|\\(([^\\)]+)\\)))?"
  261. "[^:]*"
  262. "(?::(.*))?");
  263. if (note.indexIn(p_keyword) >= 0) {
  264. p_keyword = note.cap(3).trimmed();
  265. if (p_keyword.isEmpty()) {
  266. p_keyword = note.cap(2).trimmed();
  267. if (p_keyword.isEmpty()) {
  268. p_keyword = note.cap(1);
  269. p_hints = "id";
  270. } else {
  271. p_needCreole = true;
  272. }
  273. } else {
  274. p_needCreole = true;
  275. }
  276. return true;
  277. }
  278. // note "a floating note" as N1 #Pink
  279. // note as N1
  280. static QRegExp note2("^\\s*note\\s+(?:\"([^\"]*)\"\\s+)?as\\s+\\w+");
  281. if (note2.indexIn(p_keyword) >= 0) {
  282. p_keyword = note2.cap(1).trimmed();
  283. return true;
  284. }
  285. return false;
  286. }
  287. static bool tryCommonElements(QString &p_keyword, QString &p_hints, bool &p_isRegex, bool &p_needCreole)
  288. {
  289. Q_UNUSED(p_isRegex);
  290. Q_UNUSED(p_hints);
  291. Q_UNUSED(p_needCreole);
  292. // Words in quotes.
  293. // cmf("abc")
  294. static QRegExp quote("^[^\"]*\"([^\"]+)\"[^\"]*$");
  295. if (quote.indexIn(p_keyword) >= 0) {
  296. p_keyword = quote.cap(1).trimmed();
  297. return true;
  298. }
  299. return false;
  300. }
  301. static bool tryActivityDiagram(QString &p_keyword, QString &p_hints, bool &p_isRegex, bool &p_needCreole)
  302. {
  303. Q_UNUSED(p_isRegex);
  304. Q_UNUSED(p_hints);
  305. // Activity. (Do not support color.)
  306. // :Hello world;
  307. // :Across multiple lines
  308. static QRegExp activity1("^\\s*:([^:]+)\\s*$");
  309. if (activity1.indexIn(p_keyword) >= 0) {
  310. p_keyword = activity1.cap(1).trimmed();
  311. if (!p_keyword.isEmpty()) {
  312. QChar ch = p_keyword[p_keyword.size() - 1];
  313. if (ch == ';' || ch == '|' || ch == '<' || ch == '>'
  314. || ch == '/' || ch == ']' || ch == '}') {
  315. p_keyword = p_keyword.left(p_keyword.size() - 1);
  316. }
  317. }
  318. p_needCreole = true;
  319. return true;
  320. }
  321. // Activity.
  322. // multiple lines;
  323. static QRegExp activity2("^\\s*(.+)([;|<>/\\]}])\\s*$");
  324. if (activity2.indexIn(p_keyword) >= 0) {
  325. QString word = activity2.cap(1);
  326. QChar end = activity2.cap(2)[0];
  327. if (end != ';' && !word.isEmpty()) {
  328. // || << >> // ]] }} are not legal.
  329. if (word[word.size() - 1] == end) {
  330. return false;
  331. }
  332. }
  333. // It may conflict with note.
  334. p_needCreole = true;
  335. p_keyword = word.trimmed();
  336. return true;
  337. }
  338. // Conditionals.
  339. // if (Graphviz) then (yes)
  340. // else if (Graphviz) then (yes)
  341. static QRegExp conIf("^\\s*(?:else)?if\\s+\\(([^\\)]+)\\)\\s+then(?:\\s+\\([^\\)]+\\))?\\s*$");
  342. if (conIf.indexIn(p_keyword) >= 0) {
  343. p_keyword = conIf.cap(1).trimmed();
  344. return true;
  345. }
  346. // else (no)
  347. static QRegExp conElse("^\\s*else(?:\\s+\\(([^\\)]+)\\))?\\s*$");
  348. if (conElse.indexIn(p_keyword) >= 0) {
  349. p_keyword = conElse.cap(1).trimmed();
  350. return true;
  351. }
  352. // Repeat loop.
  353. // repeat while (more data?)
  354. static QRegExp repeat("^\\s*repeat\\s+while\\s+\\(([^\\)]+)\\)\\s*$");
  355. if (repeat.indexIn(p_keyword) >= 0) {
  356. p_keyword = repeat.cap(1).trimmed();
  357. return true;
  358. }
  359. // while (check?) is (not empty)
  360. static QRegExp whileLoop("^\\s*while\\s+\\(([^\\)]+)\\)(?:\\s+is\\s+\\([^\\)]+\\))?\\s*$");
  361. if (whileLoop.indexIn(p_keyword) >= 0) {
  362. p_keyword = whileLoop.cap(1).trimmed();
  363. return true;
  364. }
  365. // endwhile (empty)
  366. static QRegExp endWhile("^\\s*endwhile(?:\\s+\\(([^\\)]+)\\))?\\s*$");
  367. if (endWhile.indexIn(p_keyword) >= 0) {
  368. p_keyword = endWhile.cap(1).trimmed();
  369. return true;
  370. }
  371. // partition Running {
  372. static QRegExp partition("^\\s*partition\\s+(\\w+)\\s+\\{\\s*$");
  373. if (partition.indexIn(p_keyword) >= 0) {
  374. p_keyword = partition.cap(1).trimmed();
  375. return true;
  376. }
  377. // |Swimlane1|
  378. // |#Pink|Swimlane1|
  379. static QRegExp swimline("^\\s*(?:\\|[^\\|]+)?\\|([^|]+)\\|\\s*$");
  380. if (swimline.indexIn(p_keyword) >= 0) {
  381. p_keyword = swimline.cap(1).trimmed();
  382. return true;
  383. }
  384. return false;
  385. }
  386. static bool trySequenceDiagram(QString &p_keyword, QString &p_hints, bool &p_isRegex, bool &p_needCreole)
  387. {
  388. Q_UNUSED(p_isRegex);
  389. Q_UNUSED(p_hints);
  390. // participant ABC #Pink
  391. // participant "ABC DEF" as AD #Pink
  392. static QRegExp participant1("^\\s*(?:participant|actor|boundary|control|entity|database)\\s+"
  393. "(?:(\\w+)|\"([^\"]+)\"\\s*(?:\\bas\\s+\\w+)?)");
  394. if (participant1.indexIn(p_keyword) >= 0) {
  395. p_keyword = participant1.cap(1);
  396. if (p_keyword.isEmpty()) {
  397. p_keyword = participant1.cap(2).trimmed();
  398. }
  399. return true;
  400. }
  401. // "abc" ->> "def" : Authentication
  402. static QRegExp message("^\\s*(?:\\w+|\"[^\"]+\")\\s+"
  403. "[-<>x\\\\/o]{2,}\\s+"
  404. "(?:\\w+|\"[^\"]+\")\\s*"
  405. ":\\s*(.+)");
  406. if (message.indexIn(p_keyword) >= 0) {
  407. p_needCreole = true;
  408. p_keyword = message.cap(1).trimmed();
  409. return true;
  410. }
  411. // autonumber
  412. static QRegExp autonum("^\\s*autonumber\\s+");
  413. if (autonum.indexIn(p_keyword) >= 0) {
  414. p_keyword.clear();
  415. return true;
  416. }
  417. // newpage
  418. static QRegExp newpage("^\\s*newpage\\s+(.+)");
  419. if (newpage.indexIn(p_keyword) >= 0) {
  420. p_keyword = newpage.cap(1).trimmed();
  421. return true;
  422. }
  423. // alt, else, group, loop ABCDEFG
  424. static QRegExp group1("^\\s*(?:alt|else|group|loop)\\s+(.*)");
  425. if (group1.indexIn(p_keyword) >= 0) {
  426. p_keyword = group1.cap(1).trimmed();
  427. return true;
  428. }
  429. // note over bob, alice #Pink:
  430. // ref over bob, alice : init
  431. static QRegExp noteon("^\\s*(?:[hr]?note|ref)\\s+over\\s+"
  432. "(\\w+)[^:]*"
  433. "(?::(.+))?");
  434. if (noteon.indexIn(p_keyword) >= 0) {
  435. p_keyword = noteon.cap(2).trimmed();
  436. if (p_keyword.isEmpty()) {
  437. p_keyword = noteon.cap(1);
  438. } else {
  439. p_needCreole = true;
  440. }
  441. return true;
  442. }
  443. // Divider.
  444. // == Initialization ==
  445. static QRegExp divider("^\\s*==\\s*([^=]*)==\\s*$");
  446. if (divider.indexIn(p_keyword) >= 0) {
  447. p_keyword = divider.cap(1).trimmed();
  448. return true;
  449. }
  450. // Delay.
  451. // ... 5 minutes latter ...
  452. static QRegExp delay("^\\s*\\.\\.\\.(?:(.+)\\.\\.\\.)?\\s*$");
  453. if (delay.indexIn(p_keyword) >= 0) {
  454. p_keyword = delay.cap(1).trimmed();
  455. return true;
  456. }
  457. // activate A
  458. static QRegExp activate("^\\s*(?:(?:de)?activate|destroy)\\s+"
  459. "(?:(\\w+)|\"([^\"]+)\")");
  460. if (activate.indexIn(p_keyword) >= 0) {
  461. p_keyword = activate.cap(1);
  462. if (p_keyword.isEmpty()) {
  463. p_keyword = activate.cap(2).trimmed();
  464. }
  465. return true;
  466. }
  467. // create control ABC
  468. static QRegExp create("^\\s*create\\s+(?:\\w+\\s+)?"
  469. "(?:(\\w+)|\"([^\"]+)\")");
  470. if (create.indexIn(p_keyword) >= 0) {
  471. p_keyword = create.cap(1);
  472. if (p_keyword.isEmpty()) {
  473. p_keyword = create.cap(2).trimmed();
  474. }
  475. return true;
  476. }
  477. // Incoming and outgoing message.
  478. static QRegExp incoming("^\\s*\\[[-<>ox]+\\s*"
  479. "(?:\\w+|\"[^\"]+\")\\s*"
  480. ":\\s*(.+)");
  481. if (incoming.indexIn(p_keyword) >= 0) {
  482. p_needCreole = true;
  483. p_keyword = incoming.cap(1).trimmed();
  484. return true;
  485. }
  486. static QRegExp outgoing("^\\s*(?:\\w+|\"[^\"]+\")\\s*"
  487. "[-<>ox]+\\]\\s*"
  488. ":\\s*(.+)");
  489. if (outgoing.indexIn(p_keyword) >= 0) {
  490. p_needCreole = true;
  491. p_keyword = outgoing.cap(1).trimmed();
  492. return true;
  493. }
  494. // box "Internal Service" #Pink
  495. static QRegExp box("^\\s*box(?:\\s+\"([^\"]+)\")?\\s*");
  496. if (box.indexIn(p_keyword) >= 0) {
  497. p_keyword = box.cap(1).trimmed();
  498. return true;
  499. }
  500. return false;
  501. }
  502. static bool tryUseCaseDiagram(QString &p_keyword, QString &p_hints, bool &p_isRegex, bool &p_needCreole)
  503. {
  504. Q_UNUSED(p_isRegex);
  505. Q_UNUSED(p_hints);
  506. // User -> (Start)
  507. // User --> (Use the application) : A small label
  508. // :Main Admin: --> (Use the application) : This is another label
  509. // (chekckout) -- (payment) : include
  510. static QRegExp rel("^\\s*(?:(\\w+)|:([^:]+):|\\(([^\\)]+)\\))\\s*"
  511. "[-.<>]{2,}\\s*"
  512. "(?:\\(([^\\)]+)\\)|(\\w+))\\s*"
  513. "(?::(.+))?");
  514. if (rel.indexIn(p_keyword) >= 0) {
  515. QString msg(rel.cap(6).trimmed());
  516. if (msg.isEmpty()) {
  517. QString ent2(rel.cap(4).trimmed());
  518. if (ent2.isEmpty()) {
  519. ent2 = rel.cap(5).trimmed();
  520. }
  521. if (ent2.isEmpty()) {
  522. QString ent1(rel.cap(3).trimmed());
  523. if (ent1.isEmpty()) {
  524. ent1 = rel.cap(2).trimmed();
  525. if (ent1.isEmpty()) {
  526. ent1 = rel.cap(1).trimmed();
  527. }
  528. }
  529. p_keyword = ent1;
  530. } else {
  531. p_keyword = ent2;
  532. }
  533. } else {
  534. p_keyword = msg;
  535. }
  536. p_needCreole = true;
  537. return true;
  538. }
  539. // Usecase.
  540. // (First usecase) as (UC2)
  541. // usecase UC3
  542. // usecase (Last usecase) as UC4
  543. static QRegExp usecase1("^\\s*usecase\\s+(?:(\\w+)|\\(([^\\)]+)\\)\\s+as\\s+\\w+)");
  544. if (usecase1.indexIn(p_keyword) >= 0) {
  545. if (usecase1.cap(1).isEmpty()) {
  546. p_keyword = usecase1.cap(2).trimmed();
  547. p_needCreole = true;
  548. } else {
  549. p_keyword = usecase1.cap(1);
  550. }
  551. return true;
  552. }
  553. // This will eat almost anything starting with ().
  554. static QRegExp usecase2("^\\s*\\(([^\\)]+)\\)");
  555. if (usecase2.indexIn(p_keyword) >= 0) {
  556. p_keyword = usecase2.cap(1).trimmed();
  557. p_needCreole = true;
  558. return true;
  559. }
  560. // Actor.
  561. // :Another\nactor: as men2
  562. // actor :Last actor: as men4
  563. static QRegExp actor1("^\\s*(?:actor\\s+)?:([^:]+):(?:\\s+as\\s+\\w+)?[^:]*$");
  564. if (actor1.indexIn(p_keyword) >= 0) {
  565. p_keyword = actor1.cap(1).trimmed();
  566. p_needCreole = true;
  567. return true;
  568. }
  569. // actor men1
  570. static QRegExp actor2("^\\s*actor\\s+(\\w+)");
  571. if (actor2.indexIn(p_keyword) >= 0) {
  572. p_keyword = actor2.cap(1).trimmed();
  573. p_needCreole = true;
  574. return true;
  575. }
  576. // rectangle checkout {
  577. static QRegExp rect("^\\s*rectangle\\s+(?:(\\w+)|\"([^\"]+)\")");
  578. if (rect.indexIn(p_keyword) >= 0) {
  579. p_keyword = rect.cap(1).trimmed();
  580. if (p_keyword.isEmpty()) {
  581. p_keyword = rect.cap(2).trimmed();
  582. }
  583. p_needCreole = true;
  584. return true;
  585. }
  586. return false;
  587. }
  588. static bool tryCreole(QString &p_keyword)
  589. {
  590. if (p_keyword.isEmpty()) {
  591. return false;
  592. }
  593. // List.
  594. // ** list
  595. // # list
  596. static QRegExp listMark("^\\s*(?:\\*+|#+)\\s+(.+)$");
  597. if (listMark.indexIn(p_keyword) >= 0) {
  598. p_keyword = listMark.cap(1).trimmed();
  599. return true;
  600. }
  601. return false;
  602. }
  603. QString VPlantUMLHelper::keywordForSmartLivePreview(const QString &p_text,
  604. QString &p_hints,
  605. bool &p_isRegex)
  606. {
  607. QString kw = p_text.trimmed();
  608. if (kw.isEmpty()) {
  609. return kw;
  610. }
  611. p_isRegex = false;
  612. bool needCreole = false;
  613. qDebug() << "tryKeywords" << kw;
  614. if (tryKeywords(kw)) {
  615. return kw;
  616. }
  617. qDebug() << "tryClassDiagram" << kw;
  618. if (tryClassDiagram(kw, p_hints, p_isRegex, needCreole)) {
  619. if (needCreole) {
  620. goto creole;
  621. }
  622. return kw;
  623. }
  624. qDebug() << "tryActivityDiagram" << kw;
  625. if (tryActivityDiagram(kw, p_hints, p_isRegex, needCreole)) {
  626. if (needCreole) {
  627. goto creole;
  628. }
  629. return kw;
  630. }
  631. qDebug() << "trySequenceDiagram" << kw;
  632. if (trySequenceDiagram(kw, p_hints, p_isRegex, needCreole)) {
  633. if (needCreole) {
  634. goto creole;
  635. }
  636. return kw;
  637. }
  638. qDebug() << "tryUseCaseDiagram" << kw;
  639. if (tryUseCaseDiagram(kw, p_hints, p_isRegex, needCreole)) {
  640. if (needCreole) {
  641. goto creole;
  642. }
  643. return kw;
  644. }
  645. qDebug() << "tryCommonElements" << kw;
  646. if (tryCommonElements(kw, p_hints, p_isRegex, needCreole)) {
  647. if (needCreole) {
  648. goto creole;
  649. }
  650. return kw;
  651. }
  652. creole:
  653. tryCreole(kw);
  654. return kw;
  655. }