Doxygen
Loading...
Searching...
No Matches
plantuml.cpp
Go to the documentation of this file.
1/******************************************************************************
2 *
3 * Copyright (C) 1997-2015 by Dimitri van Heesch.
4 *
5 * Permission to use, copy, modify, and distribute this software and its
6 * documentation under the terms of the GNU General Public License is hereby
7 * granted. No representations are made about the suitability of this software
8 * for any purpose. It is provided "as is" without express or implied warranty.
9 * See the GNU General Public License for more details.
10 *
11 * Documents produced by Doxygen are derivative works derived from the
12 * input used in their production; they are not affected by this license.
13 *
14 */
15
16// own header
17#include "plantuml.h"
18
19// standard includes
20#include <mutex>
21#include <iostream>
22
23// other includes
24#include "config.h"
25#include "debug.h"
26#include "dir.h"
27#include "doxygen.h"
28#include "fileinfo.h"
29#include "indexlist.h"
30#include "message.h"
31#include "portable.h"
32#include "stringutil.h"
33#include "util.h"
34
35static std::mutex g_PlantUmlMutex;
36
38 const DString &content,OutputFormat format, const DString &engine,
39 const DString &srcFile,int srcLine,bool inlineCode)
40{
41 StringVector baseNameVector;
42 DString baseName;
43 DString puName;
44 DString imgName;
45 DString outDir(outDirArg);
46
47 Debug::print(Debug::Plantuml,0,"*** writePlantUMLSource fileName: {}\n",fileName);
48 Debug::print(Debug::Plantuml,0,"*** writePlantUMLSource outDir: {}\n",outDir);
49
50 // strip any trailing slashes and backslashes
51 size_t l = 0;
52 while ((l=outDir.length())>0 && (outDir.at(l-1)=='/' || outDir.at(l-1)=='\\'))
53 {
54 outDir = outDir.left(l-1);
55 }
56
57 generatePlantUmlFileNames(fileName,format,outDir,baseName,puName,imgName);
58
59 Debug::print(Debug::Plantuml,0,"*** writePlantUMLSourcebaseName: {}\n",baseName);
60 Debug::print(Debug::Plantuml,0,"*** writePlantUMLSourcebaseName puName: {}\n",puName);
61 Debug::print(Debug::Plantuml,0,"*** writePlantUMLSourcebaseName imgName: {}\n",imgName);
62
63 DString text;
64 if (inlineCode) text = "@start"+engine+" "+imgName+"\n";
65 text.reserve(text.length()+content.length()+100); // add room for image name and end marker
66 const char *p = content.data();
67 if (p)
68 {
69 char c = 0;
70 bool insideComment = false;
71 DString locEngine;
72 while ((c=*p++))
73 {
74 text+=c;
75 switch (c)
76 {
77 case '\'': insideComment=true; break;
78 case '\n': insideComment=false; break;
79 case '\t': break;
80 case ' ': break;
81 case '@':
82 if (!insideComment && literal_at(p,"start")) // @start...
83 {
84 locEngine.clear();
85 p+=5;
86 text += "start";
87 while ((c=*p++) && isId(c))
88 {
89 locEngine += c;
90 text+=c;
91 }
92 DString inpName;
93 DString rest;
94
95 // skip leading whitespace
96 if (*p && (c==' ' || c=='\t'))
97 {
98 while ((c=*p++) && (c==' ' || c=='\t')) {}
99 }
100 // get everything till end or endOfLine, and split into inpName (without extension) and rest
101 enum State { InName, InExt, InRest };
102 State state = InName;
103 while (*p && c!='\n')
104 {
105 switch (state)
106 {
107 case InName: // looking for the name part
108 if (isId(c) || c=='-') { inpName+=c; }
109 else if (c=='.') { state=InExt; }
110 else { rest+=c; state=InRest; }
111 break;
112 case InExt: // skipping over extension part
113 if (!isId(c) && c!='-') { rest+=c; state=InRest; }
114 break;
115 case InRest: // gather rest until new line
116 rest+=c;
117 break;
118 }
119 c = *p++;
120 }
121 //printf("inpName='%s' rest='%s'\n",qPrint(inpName),qPrint(rest));
122 generatePlantUmlFileNames(inpName,format,outDir,baseName,puName,imgName);
123
124 // insert the image name
125 text+=' ';
126 text+=imgName;
127
128 if (!rest.empty())
129 {
130 text += '\n';
131 text += rest;
132 }
133 if (c) text+=c;
134 }
135 else if (!insideComment && strncmp(p,("end"+locEngine).data(), 3+strlen(engine.data()))==0) // @end...
136 {
137 text += "end"+locEngine+"\n";
138 p+=3+locEngine.length();
139 if (!inlineCode)
140 {
141 DString qcOutDir(substitute(outDir,"\\","/"));
142 size_t pos = qcOutDir.rfind('/');
143 DString generateType(pos!=DString::npos ? qcOutDir.mid(pos+1) : qcOutDir);
144 Debug::print(Debug::Plantuml,0,"*** writePlantUMLSource generateType: {}\n",generateType);
145 PlantumlManager::instance().insert(generateType.str(),puName.str(),outDir,format,text,srcFile,srcLine);
146 Debug::print(Debug::Plantuml,0,"*** writePlantUMLSource generateType: {}\n",generateType);
147 baseNameVector.push_back(baseName.str());
148 text.clear();
149 }
150 }
151 break;
152 default:
153 break;
154 }
155 }
156 text+='\n';
157 }
158 if (inlineCode)
159 {
160 text +="@end"+engine+"\n";
161 //printf("content\n====\n%s\n=====\n->\n-----\n%s\n------\n",qPrint(content),qPrint(text));
162 DString qcOutDir(substitute(outDir,"\\","/"));
163 size_t pos = qcOutDir.rfind("/");
164 DString generateType(pos!=DString::npos ? qcOutDir.mid(pos+1) : qcOutDir);
165 Debug::print(Debug::Plantuml,0,"*** writePlantUMLSource generateType: {}\n",generateType);
166 PlantumlManager::instance().insert(generateType.str(),puName.str(),outDir,format,text,srcFile,srcLine);
167 Debug::print(Debug::Plantuml,0,"*** writePlantUMLSource generateType: {}\n",generateType);
168 baseNameVector.push_back(baseName.str());
169 }
170
171 return baseNameVector;
172}
173
175 DString &baseName,DString &puName,DString &imgName)
176{
177 static int umlindex=1;
178
179 if (fileName.empty()) // generate name
180 {
181 std::lock_guard<std::mutex> lock(g_PlantUmlMutex);
182 puName = "inline_umlgraph_"+DString().setNum(umlindex);
183 baseName = outDir+"/inline_umlgraph_"+DString().setNum(umlindex++);
184 }
185 else // user specified name
186 {
187 baseName = fileName;
188 if (size_t i=baseName.rfind('.'); i!=DString::npos) baseName = baseName.left(i);
189 puName = baseName;
190 baseName.prepend(outDir+"/");
191 }
192
193 switch (format)
194 {
195 case PUML_BITMAP:
196 imgName =puName+".png";
197 break;
198 case PUML_EPS:
199 imgName =puName+".eps";
200 break;
201 case PUML_SVG:
202 imgName =puName+".svg";
203 break;
204 }
205}
206
207void PlantumlManager::generatePlantUMLOutput(const DString &baseName,const DString &/* outDir */,OutputFormat format,bool toIndex)
208{
209 if (!toIndex) return;
210 DString imgName = baseName;
211 // The basename contains path, we need to strip the path from the filename in order
212 // to create the image file name which should be included in the index.qhp (Qt help index file).
213 if (size_t i = imgName.rfind('/'); i!=DString::npos) // strip path
214 {
215 imgName=imgName.mid(i+1);
216 }
217 switch (format)
218 {
219 case PUML_BITMAP:
220 imgName+=".png";
221 break;
222 case PUML_EPS:
223 imgName+=".eps";
224 break;
225 case PUML_SVG:
226 imgName+=".svg";
227 break;
228 }
229
231}
232
233//--------------------------------------------------------------------
234
235
237{
238 static PlantumlManager theInstance;
239 return theInstance;
240}
241
245
247{
248 return !Config_getString(PLANTUML_JAR_PATH).empty() ||
249 !Config_getString(PLANTUML_TOOL).empty();
250}
251
252/** Returns the java executable used to run the PlantUML jar file. */
254{
255 DString javaPath = Config_getString(PLANTUML_JAVA_PATH);
256 if (!javaPath.empty()) return javaPath;
257 DString javaHome = Portable::getenv("JAVA_HOME");
258 if (!javaHome.empty())
259 {
260 DString javaHomeExe = javaHome+"/bin/java"+Portable::commandExtension();
261 FileInfo fi(javaHomeExe.str());
262 if (fi.exists() && fi.isFile()) return javaHomeExe;
263 }
264 return "java";
265}
266
267static void runPlantumlContent(const PlantumlManager::FilesMap &plantumlFiles,
268 const PlantumlManager::ContentMap &plantumlContent,
270{
271 /* example : running: java -Djava.awt.headless=true
272 -jar "/usr/local/bin/plantuml.jar"
273 -o "test_doxygen/DOXYGEN_OUTPUT/html"
274 -tpng
275 "test_doxygen/DOXYGEN_OUTPUT/html/A.pu"
276 -charset UTF-8
277 outDir:test_doxygen/DOXYGEN_OUTPUT/html
278 test_doxygen/DOXYGEN_OUTPUT/html/A
279 */
280 int exitCode = 0;
281 DString plantumlJarPath = Config_getString(PLANTUML_JAR_PATH);
282 DString plantumlConfigFile = Config_getString(PLANTUML_CFG_FILE);
283 DString plantumlTool = Config_getString(PLANTUML_TOOL);
284 bool useJar = plantumlTool.empty(); // no PlantUML executable given, so run the jar file via java
285
286 DString pumlExe = useJar ? javaExecutable() : plantumlTool;
287 DString pumlArgs = "";
288 DString pumlType = "";
289 DString pumlOutDir = "";
290
291 StringVector pumlIncludePathList = Config_getList(PLANTUML_INCLUDE_PATH);
292 if (useJar)
293 {
294 auto it = pumlIncludePathList.begin();
295 if (it!=pumlIncludePathList.end())
296 {
297 pumlArgs += "-Dplantuml.include.path=\"";
298 pumlArgs += it->c_str();
299 ++it;
300 }
301 while (it!=pumlIncludePathList.end())
302 {
303 pumlArgs += Portable::pathListSeparator();
304 pumlArgs += it->c_str();
305 ++it;
306 }
307 if (!pumlIncludePathList.empty()) pumlArgs += "\" ";
308 pumlArgs += "-Djava.awt.headless=true -jar \""+plantumlJarPath+"\" ";
309 }
310 else if (!pumlIncludePathList.empty())
311 {
312 // a PlantUML executable does not take java system properties, but PlantUML also
313 // accepts the include path via the environment.
314 DString includePath;
315 bool first = true;
316 for (const auto &path : pumlIncludePathList)
317 {
318 if (!first) includePath += Portable::pathListSeparator();
319 includePath += path.c_str();
320 first = false;
321 }
322 Portable::setenv("PLANTUML_INCLUDE_PATH",includePath);
323 }
324 if (!plantumlConfigFile.empty())
325 {
326 pumlArgs += "-config \"";
327 pumlArgs += plantumlConfigFile;
328 pumlArgs += "\" ";
329 }
330 // the -graphvizdot option expects a relative or absolute path to the dot executable, so
331 // we need to use the unverified DOT_PATH option and check if it points to an existing file.
332 DString dotPath = Config_getString(DOT_PATH);
333 FileInfo dp(dotPath.str());
334 if (Config_getBool(HAVE_DOT) && dp.exists() && dp.isFile())
335 {
336 pumlArgs += "-graphvizdot \"";
337 pumlArgs += dotPath;
338 pumlArgs += "\" ";
339 }
340 switch (format)
341 {
343 pumlType="png";
344 break;
346 pumlType="eps";
347 break;
349 pumlType="svg";
350 break;
351 }
352
353 {
354 for (const auto &[name,nb] : plantumlContent)
355 {
356 if (nb.content.empty()) continue;
357
358 DString pumlArguments = pumlArgs;
359 msg("Generating PlantUML {} Files in {}\n",pumlType,name);
360 pumlArguments+="-o \"";
361 pumlArguments+=nb.outDir;
362 pumlArguments+="\" ";
363 pumlArguments+="-charset UTF-8 -t";
364 pumlArguments+=pumlType;
365 pumlArguments+=" ";
366
367 DString puFileName("");
368 puFileName+=nb.outDir;
369 puFileName+="/";
370 pumlOutDir=puFileName;
371 puFileName+="inline_umlgraph_";
372 puFileName+=pumlType;
373 puFileName+=name.c_str();
374 puFileName+=".pu";
375
376 pumlArguments+="\"";
377 pumlArguments+=puFileName;
378 pumlArguments+="\" ";
379
380
381 DString cachedContent;
382 FileInfo fi(puFileName.str());
383 if (fi.exists())
384 {
385 cachedContent = fileToString(puFileName);
386 }
387
388 std::ofstream file = Portable::openOutputStream(puFileName);
389 if (!file.is_open())
390 {
391 err_full(nb.srcFile,nb.srcLine,"Could not open file {} for writing",puFileName);
392 }
393 file.write( nb.content.data(), nb.content.length() );
394 file.close();
395 Debug::print(Debug::Plantuml,0,"*** PlantumlManager::runPlantumlContent Running Plantuml arguments:{}\n",pumlArguments);
396
397 if (cachedContent == nb.content) continue;
398
399 if ((exitCode=Portable::system(pumlExe.data(),pumlArguments.data(),true))!=0)
400 {
401 err_full(nb.srcFile,nb.srcLine,"Problems running PlantUML. Verify that the command '{} -h' works from the command line. Exit code: {}.",
402 useJar ? pumlExe+" -jar \""+plantumlJarPath+"\"" : pumlExe,exitCode);
403 }
404
405 if ( (format==PlantumlManager::PUML_EPS) && (Config_getBool(USE_PDFLATEX)) )
406 {
407 Debug::print(Debug::Plantuml,0,"*** PlantumlManager::runPlantumlContent Running epstopdf\n");
408 auto files_kv = plantumlFiles.find(name);
409 if (files_kv!=plantumlFiles.end())
410 {
411 for (const auto &str : files_kv->second)
412 {
413 const int maxCmdLine = 40960;
414 DString epstopdfArgs(maxCmdLine, DString::ExplicitSize);
415 epstopdfArgs.sprintf("\"%s%s.eps\" --outfile=\"%s%s.pdf\"",
416 pumlOutDir.data(),str.c_str(), pumlOutDir.data(),str.c_str());
417 if ((exitCode=Portable::system("epstopdf",epstopdfArgs.data()))!=0)
418 {
419 err_full(nb.srcFile,nb.srcLine,"Problems running epstopdf. Check your TeX installation! Exit code: {}.",exitCode);
420 }
421 else
422 {
423 Dir().remove(pumlOutDir.str()+str+".eps");
424 }
425 }
426 }
427 }
428 }
429 }
430}
431
439
440static void print(const PlantumlManager::FilesMap &plantumlFiles)
441{
443 {
444 for (const auto &[key,list] : plantumlFiles)
445 {
446 Debug::print(Debug::Plantuml,0,"*** PlantumlManager::print Files PlantumlFiles key:{} size:{}\n",key,list.size());
447 for (const auto &s : list)
448 {
449 Debug::print(Debug::Plantuml,0,"*** PlantumlManager::print list:{}\n",s);
450 }
451 }
452 }
453}
454
455static void print(const PlantumlManager::ContentMap &plantumlContent)
456{
458 {
459 for (const auto &[key,content] : plantumlContent)
460 {
461 Debug::print(Debug::Plantuml,0,"*** PlantumlManager::print Content PlantumlContent key: {}\n",key);
462 Debug::print(Debug::Plantuml,0,"*** PlantumlManager::print Content:\n{}\n",content.content);
463 }
464 }
465}
466
468 const std::string &key, const std::string &value)
469{
470 auto kv = plantumlFiles.find(key);
471 if (kv==plantumlFiles.end())
472 {
473 kv = plantumlFiles.emplace(key,StringVector()).first;
474 }
475 kv->second.push_back(value);
476}
477
479 const std::string &key, const DString &outDir, const DString &puContent,
480 const DString &srcFile,int srcLine)
481{
482 auto kv = plantumlContent.find(key);
483 if (kv==plantumlContent.end())
484 {
485 kv = plantumlContent.emplace(key,PlantumlContent("",outDir,srcFile,srcLine)).first;
486 }
487 kv->second.content+=puContent;
488}
489
490void PlantumlManager::insert(const std::string &key, const std::string &value,
491 const DString &outDir,OutputFormat format,const DString &puContent,
492 const DString &srcFile,int srcLine)
493{
494 Debug::print(Debug::Plantuml,0,"*** PlantumlManager::insert key:{} ,value:{}\n",key,value);
495
496 switch (format)
497 {
498 case PUML_BITMAP:
501 addPlantumlContent(m_pngPlantumlContent,key,outDir,puContent,srcFile,srcLine);
503 break;
504 case PUML_EPS:
507 addPlantumlContent(m_epsPlantumlContent,key,outDir,puContent,srcFile,srcLine);
509 break;
510 case PUML_SVG:
513 addPlantumlContent(m_svgPlantumlContent,key,outDir,puContent,srcFile,srcLine);
515 break;
516 }
517}
518
519//--------------------------------------------------------------------
A String class for use with Doxygen wrapping std::string and adding some additional functionality off...
Definition dstring.h:84
void clear()
Definition dstring.h:214
DString & setNum(short n)
Definition dstring.h:552
size_t rfind(char c, size_t pos=npos) const
Definition dstring.h:244
DString mid(size_t index, size_t len=npos) const
Definition dstring.h:318
bool empty() const
Returns true iff the string is empty (std::string compatible alias for isEmpty()).
Definition dstring.h:148
const char * c_str() const
Returns a pointer to the contents of the string in the form of a 0-terminated C string.
Definition dstring.h:160
static constexpr size_t npos
value used to indicate 'not found' or 'to the end of the string', matching std::string::npos
Definition dstring.h:178
char & at(size_t i)
Returns a reference to the character at index i.
Definition dstring.h:686
DString & prepend(const char *s)
Definition dstring.h:515
DString & sprintf(const char *format,...)
Definition dstring.cpp:34
void reserve(size_t size)
Reserve space for size bytes without changing the string contents.
Definition dstring.h:217
@ ExplicitSize
Definition dstring.h:131
DString left(size_t len) const
Definition dstring.h:306
const std::string & str() const
Definition dstring.h:645
const char * data() const
Returns a pointer to the contents of the string in the form of a 0-terminated C string.
Definition dstring.h:157
size_t length() const
Returns the length of the string, not counting the 0-terminator.
Definition dstring.h:151
@ Plantuml
Definition debug.h:40
static bool isFlagSet(const DebugMask mask)
Definition debug.cpp:132
static void print(DebugMask mask, int prio, fmt::format_string< Args... > fmt, Args &&... args)
Definition debug.h:78
Class representing a directory in the file system.
Definition dir.h:73
bool remove(const std::string &path, bool acceptsAbsPath=true) const
Definition dir.cpp:320
static IndexList * indexList
Definition doxygen.h:125
Minimal replacement for QFileInfo.
Definition fileinfo.h:26
bool exists() const
Definition fileinfo.cpp:34
bool isFile() const
Definition fileinfo.cpp:67
void addImageFile(const DString &name)
Definition indexlist.h:124
ContentMap m_pngPlantumlContent
Definition plantuml.h:104
void generatePlantUMLOutput(const DString &baseName, const DString &outDir, OutputFormat format, bool toIndex)
Convert a PlantUML file to an image.
Definition plantuml.cpp:207
std::map< std::string, PlantumlContent > ContentMap
Definition plantuml.h:88
void insert(const std::string &key, const std::string &value, const DString &outDir, OutputFormat format, const DString &puContent, const DString &srcFile, int srcLine)
Definition plantuml.cpp:490
OutputFormat
Plant UML output image formats.
Definition plantuml.h:43
ContentMap m_epsPlantumlContent
Definition plantuml.h:106
std::map< std::string, StringVector > FilesMap
Definition plantuml.h:87
StringVector writePlantUMLSource(const DString &outDirArg, const DString &fileName, const DString &content, OutputFormat format, const DString &engine, const DString &srcFile, int srcLine, bool inlineCode)
Write a PlantUML compatible file.
Definition plantuml.cpp:37
void generatePlantUmlFileNames(const DString &fileName, OutputFormat format, const DString &outDir, DString &baseName, DString &puName, DString &imgName)
Definition plantuml.cpp:174
static bool isEnabled()
Returns true if doxygen has been configured to run PlantUML, i.e.
Definition plantuml.cpp:246
ContentMap m_svgPlantumlContent
Definition plantuml.h:105
FilesMap m_pngPlantumlFiles
Definition plantuml.h:101
static PlantumlManager & instance()
Definition plantuml.cpp:236
FilesMap m_svgPlantumlFiles
Definition plantuml.h:102
void run()
Run plant UML tool for all images.
Definition plantuml.cpp:432
FilesMap m_epsPlantumlFiles
Definition plantuml.h:103
#define Config_getList(name)
Definition config.h:38
#define Config_getBool(name)
Definition config.h:33
#define Config_getString(name)
Definition config.h:32
std::vector< std::string > StringVector
Definition containers.h:33
DString substitute(const DString &s, const DString &src, const DString &dst)
substitute all occurrences of src in s by dst
Definition dstring.cpp:485
bool isId(int c)
Returns true if c is a valid character for an identifier.
Definition dstring.h:895
#define msg(fmt,...)
Definition message.h:94
#define err_full(file, line, fmt,...)
Definition message.h:132
void setenv(const DString &variable, const DString &value)
Definition portable.cpp:302
int system(const DString &command, const DString &args, bool commandHasConsole=true)
Definition portable.cpp:121
DString getenv(const DString &variable)
Definition portable.cpp:337
DString pathListSeparator()
Definition portable.cpp:399
std::ofstream openOutputStream(const DString &name, bool append=false)
Definition portable.cpp:681
const char * commandExtension()
Definition portable.cpp:477
static void runPlantumlContent(const PlantumlManager::FilesMap &plantumlFiles, const PlantumlManager::ContentMap &plantumlContent, PlantumlManager::OutputFormat format)
Definition plantuml.cpp:267
static DString javaExecutable()
Returns the java executable used to run the PlantUML jar file.
Definition plantuml.cpp:253
static std::mutex g_PlantUmlMutex
Definition plantuml.cpp:35
static void addPlantumlContent(PlantumlManager::ContentMap &plantumlContent, const std::string &key, const DString &outDir, const DString &puContent, const DString &srcFile, int srcLine)
Definition plantuml.cpp:478
static void print(const PlantumlManager::FilesMap &plantumlFiles)
Definition plantuml.cpp:440
static void addPlantumlFiles(PlantumlManager::FilesMap &plantumlFiles, const std::string &key, const std::string &value)
Definition plantuml.cpp:467
Portable versions of functions that are platform dependent.
Some helper functions for std::string.
bool literal_at(const char *data, const char(&str)[N])
returns true iff data points to a substring that matches string literal str
Definition stringutil.h:101
DString fileToString(const DString &name, bool filter, bool isSourceCode)
Definition util.cpp:1053
A bunch of utility functions.