diff options
author | Dimitri van Heesch <dimitri@stack.nl> | 2014-08-10 18:09:33 (GMT) |
---|---|---|
committer | Dimitri van Heesch <dimitri@stack.nl> | 2014-08-10 18:09:33 (GMT) |
commit | 7506404e646f1fcc5a26ca6fca91a7f65154f05a (patch) | |
tree | f97964759aa6238f02bd5b7f853306c19965914d | |
parent | 97d12d058a7831adcc8c6f2cfe8c20ddd2ae5bc2 (diff) | |
download | Doxygen-7506404e646f1fcc5a26ca6fca91a7f65154f05a.zip Doxygen-7506404e646f1fcc5a26ca6fca91a7f65154f05a.tar.gz Doxygen-7506404e646f1fcc5a26ca6fca91a7f65154f05a.tar.bz2 |
Bug 731947 - Support for PlantUML
-rw-r--r-- | Doxyfile | 48 | ||||
-rw-r--r-- | doc/commands.doc | 61 | ||||
-rw-r--r-- | src/cmdmapper.cpp | 2 | ||||
-rw-r--r-- | src/cmdmapper.h | 4 | ||||
-rw-r--r-- | src/commentcnv.l | 6 | ||||
-rw-r--r-- | src/commentscan.l | 16 | ||||
-rw-r--r-- | src/compound.xsd | 2 | ||||
-rw-r--r-- | src/config.l | 31 | ||||
-rw-r--r-- | src/config.xml | 10 | ||||
-rw-r--r-- | src/docbookvisitor.cpp | 94 | ||||
-rw-r--r-- | src/docbookvisitor.h | 1 | ||||
-rw-r--r-- | src/docparser.cpp | 18 | ||||
-rw-r--r-- | src/docparser.h | 2 | ||||
-rw-r--r-- | src/doctokenizer.h | 1 | ||||
-rw-r--r-- | src/doctokenizer.l | 32 | ||||
-rw-r--r-- | src/htmldocvisitor.cpp | 48 | ||||
-rw-r--r-- | src/htmldocvisitor.h | 1 | ||||
-rw-r--r-- | src/htmlentity.cpp | 2 | ||||
-rw-r--r-- | src/latexdocvisitor.cpp | 29 | ||||
-rw-r--r-- | src/latexdocvisitor.h | 1 | ||||
-rw-r--r-- | src/libdoxygen.pro.in | 2 | ||||
-rw-r--r-- | src/mandocvisitor.cpp | 1 | ||||
-rw-r--r-- | src/markdown.cpp | 4 | ||||
-rw-r--r-- | src/perlmodgen.cpp | 3 | ||||
-rw-r--r-- | src/plantuml.cpp | 97 | ||||
-rw-r--r-- | src/plantuml.h | 40 | ||||
-rw-r--r-- | src/printdocvisitor.h | 2 | ||||
-rw-r--r-- | src/rtfdocvisitor.cpp | 34 | ||||
-rw-r--r-- | src/rtfdocvisitor.h | 1 | ||||
-rw-r--r-- | src/xmldocvisitor.cpp | 5 | ||||
-rw-r--r-- | winbuild/Doxygen.vcproj | 8 |
31 files changed, 544 insertions, 62 deletions
@@ -1,4 +1,4 @@ -# Doxyfile 1.8.3.1 +# Doxyfile 1.8.7 #--------------------------------------------------------------------------- # Project related configuration options @@ -10,6 +10,7 @@ PROJECT_BRIEF = PROJECT_LOGO = OUTPUT_DIRECTORY = doxygen_docs CREATE_SUBDIRS = YES +ALLOW_UNICODE_NAMES = NO OUTPUT_LANGUAGE = English BRIEF_MEMBER_DESC = YES REPEAT_BRIEF = YES @@ -63,6 +64,7 @@ INTERNAL_DOCS = NO CASE_SENSE_NAMES = NO HIDE_SCOPE_NAMES = NO SHOW_INCLUDE_FILES = YES +SHOW_GROUPED_MEMB_INC = NO FORCE_LOCAL_INCLUDES = NO INLINE_INFO = YES SORT_MEMBER_DOCS = YES @@ -84,7 +86,7 @@ FILE_VERSION_FILTER = LAYOUT_FILE = CITE_BIB_FILES = #--------------------------------------------------------------------------- -# configuration options related to warning and progress messages +# Configuration options related to warning and progress messages #--------------------------------------------------------------------------- QUIET = NO WARNINGS = YES @@ -94,9 +96,10 @@ WARN_NO_PARAMDOC = NO WARN_FORMAT = "$file:$line: $text " WARN_LOGFILE = #--------------------------------------------------------------------------- -# configuration options related to the input files +# Configuration options related to the input files #--------------------------------------------------------------------------- -INPUT = src +INPUT = src \ + vhdlparser INPUT_ENCODING = UTF-8 FILE_PATTERNS = *.h \ *.cpp \ @@ -105,6 +108,7 @@ RECURSIVE = NO EXCLUDE = src/logos.cpp \ src/lodepng.cpp EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = EXCLUDE_SYMBOLS = EXAMPLE_PATH = EXAMPLE_PATTERNS = @@ -116,7 +120,7 @@ FILTER_SOURCE_FILES = NO FILTER_SOURCE_PATTERNS = USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- -# configuration options related to source browsing +# Configuration options related to source browsing #--------------------------------------------------------------------------- SOURCE_BROWSER = YES INLINE_SOURCES = NO @@ -124,18 +128,17 @@ STRIP_CODE_COMMENTS = YES REFERENCED_BY_RELATION = YES REFERENCES_RELATION = YES REFERENCES_LINK_SOURCE = YES +SOURCE_TOOLTIPS = YES USE_HTAGS = NO VERBATIM_HEADERS = YES -CLANG_ASSISTED_PARSING = YES -CLANG_OPTIONS = #--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index +# Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- ALPHABETICAL_INDEX = YES COLS_IN_ALPHA_INDEX = 5 IGNORE_PREFIX = #--------------------------------------------------------------------------- -# configuration options related to the HTML output +# Configuration options related to the HTML output #--------------------------------------------------------------------------- GENERATE_HTML = YES HTML_OUTPUT = @@ -184,6 +187,7 @@ USE_MATHJAX = NO MATHJAX_FORMAT = HTML-CSS MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest MATHJAX_EXTENSIONS = +MATHJAX_CODEFILE = SEARCHENGINE = YES SERVER_BASED_SEARCH = YES EXTERNAL_SEARCH = NO @@ -192,7 +196,7 @@ SEARCHDATA_FILE = searchdata.xml EXTERNAL_SEARCH_ID = EXTRA_SEARCH_MAPPINGS = #--------------------------------------------------------------------------- -# configuration options related to the LaTeX output +# Configuration options related to the LaTeX output #--------------------------------------------------------------------------- GENERATE_LATEX = NO LATEX_OUTPUT = @@ -211,7 +215,7 @@ LATEX_HIDE_INDICES = NO LATEX_SOURCE_CODE = NO LATEX_BIB_STYLE = plain #--------------------------------------------------------------------------- -# configuration options related to the RTF output +# Configuration options related to the RTF output #--------------------------------------------------------------------------- GENERATE_RTF = NO RTF_OUTPUT = @@ -220,31 +224,31 @@ RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- -# configuration options related to the man page output +# Configuration options related to the man page output #--------------------------------------------------------------------------- GENERATE_MAN = NO MAN_OUTPUT = MAN_EXTENSION = .3 +MAN_SUBDIR = MAN_LINKS = NO #--------------------------------------------------------------------------- -# configuration options related to the XML output +# Configuration options related to the XML output #--------------------------------------------------------------------------- GENERATE_XML = NO XML_OUTPUT = xml -XML_SCHEMA = -XML_DTD = XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- -# configuration options related to the DOCBOOK output +# Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook +DOCBOOK_PROGRAMLISTING = NO #--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output +# Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- -# configuration options related to the Perl module output +# Configuration options related to the Perl module output #--------------------------------------------------------------------------- GENERATE_PERLMOD = NO PERLMOD_LATEX = NO @@ -257,13 +261,14 @@ ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES SEARCH_INCLUDES = YES -INCLUDE_PATH = qtools libmd5 +INCLUDE_PATH = qtools \ + libmd5 INCLUDE_FILE_PATTERNS = PREDEFINED = EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- -# Configuration::additions related to external references +# Configuration options related to external references #--------------------------------------------------------------------------- TAGFILES = qtools_docs/qtools.tag=../../qtools_docs/html GENERATE_TAGFILE = doxygen.tag @@ -276,6 +281,7 @@ PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- CLASS_DIAGRAMS = NO MSCGEN_PATH = +DIA_PATH = HIDE_UNDOC_RELATIONS = YES HAVE_DOT = YES DOT_NUM_THREADS = 0 @@ -299,6 +305,8 @@ INTERACTIVE_SVG = YES DOT_PATH = DOTFILE_DIRS = MSCFILE_DIRS = +DIAFILE_DIRS = +PLANTUML_JAR_PATH = DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 0 DOT_TRANSPARENT = NO diff --git a/doc/commands.doc b/doc/commands.doc index 9175856..73eb842 100644 --- a/doc/commands.doc +++ b/doc/commands.doc @@ -90,6 +90,7 @@ documentation: \refitem cmdendrtfonly \\endrtfonly \refitem cmdendsecreflist \\endsecreflist \refitem cmdendverbatim \\endverbatim +\refitem cmdenduml \\enduml \refitem cmdendxmlonly \\endxmlonly \refitem cmdenum \\enum \refitem cmdexample \\example @@ -174,6 +175,7 @@ documentation: \refitem cmdskip \\skip \refitem cmdskipline \\skipline \refitem cmdsnippet \\snippet +\refitem cmdstartuml \\startuml \refitem cmdstruct \\struct \refitem cmdsubpage \\subpage \refitem cmdsubsection \\subsection @@ -2531,6 +2533,59 @@ class Receiver \sa section \ref cmdmscfile "\\mscfile". <hr> +\section cmdstartuml \\startuml + + \addindex \\startuml + Starts a text fragment which should contain a valid description of a + PlantUML diagram. See http://plantuml.sourceforge.net/ for examples. + The text fragment ends with \ref cmdenduml "\\enduml". + \note You need to install Java and the PlantUML's jar file, + if you want to use this command. The location of the jar file should be specified + using \ref cfg_plantuml_jar_path "PLANTUML_JAR_PATH". + +Here is an example of the use of the \c \\startuml command. +\code +/** Sender class. Can be used to send a command to the server. + * The receiver will acknowledge the command by calling Ack(). + * \startuml + * Sender->Receiver : Command() + * Sender<--Receiver : Ack() + * \enduml + */ +class Sender +{ + public: + /** Acknowledgment from server */ + void Ack(bool ok); +}; + +/** Receiver class. Can be used to receive and execute commands. + * After execution of a command, the receiver will send an acknowledgment + * \startuml + * Receiver<-Sender : Command() + * Receiver-->Sender : Ack() + * \enduml + */ +class Receiver +{ + public: + /** Executable a command on the server */ + void Command(int commandId); +}; +\endcode + +\note For compatibility with running PlantUML as a preprocessing step before +running doxygen, you can also add the name of the image file after \c \\startuml +and inside curly brackets, i.e. +\verbatim +@startuml{myimage.png} +Alice -> Bob : Hello +@enduml +\endverbatim +When the name of the image is specified, doxygen will generate an image with that name. +Without the name doxygen will choose a name automatically. + +<hr> \section cmddotfile \\dotfile <file> ["caption"] \addindex \\dotfile @@ -2651,6 +2706,12 @@ class Receiver Ends a block that was started with \ref cmdmsc "\\msc". <hr> +\section cmdenduml \\enduml + + \addindex \\enduml + Ends a block that was started with \ref cmdstartuml "\\startuml". + +<hr> \section cmdendhtmlonly \\endhtmlonly \addindex \\endhtmlonly diff --git a/src/cmdmapper.cpp b/src/cmdmapper.cpp index 45469f2..b0ca85f 100644 --- a/src/cmdmapper.cpp +++ b/src/cmdmapper.cpp @@ -120,8 +120,10 @@ CommandMap cmdMap[] = { "_internalref", CMD_INTERNALREF }, { "dot", CMD_DOT }, { "msc", CMD_MSC }, + { "startuml", CMD_STARTUML }, { "enddot", CMD_ENDDOT }, { "endmsc", CMD_ENDMSC }, + { "enduml", CMD_ENDUML }, { "manonly", CMD_MANONLY }, { "endmanonly", CMD_ENDMANONLY }, { "includelineno", CMD_INCWITHLINES }, diff --git a/src/cmdmapper.h b/src/cmdmapper.h index d89e368..75fd8ec 100644 --- a/src/cmdmapper.h +++ b/src/cmdmapper.h @@ -127,7 +127,9 @@ enum CommandType CMD_DIAFILE = 97, CMD_LATEXINCLUDE = 98, CMD_NDASH = 99, - CMD_MDASH = 100 + CMD_MDASH = 100, + CMD_STARTUML = 101, + CMD_ENDUML = 102 }; enum HtmlTagType diff --git a/src/commentcnv.l b/src/commentcnv.l index 4cc9da9..ec56b90 100644 --- a/src/commentcnv.l +++ b/src/commentcnv.l @@ -446,7 +446,7 @@ void replaceComment(int offset); g_blockName=&yytext[1]; BEGIN(VerbatimCode); } -<CComment,ReadLine>[\\@]("dot"|"code"|"msc")/[^a-z_A-Z0-9] { /* start of a verbatim block */ +<CComment,ReadLine>[\\@]("dot"|"code"|"msc"|"startuml")/[^a-z_A-Z0-9] { /* start of a verbatim block */ copyToOutput(yytext,(int)yyleng); g_lastCommentContext = YY_START; g_javaBlock=0; @@ -517,7 +517,7 @@ void replaceComment(int offset); } } } -<VerbatimCode>[\\@]("enddot"|"endcode"|"endmsc") { /* end of verbatim block */ +<VerbatimCode>[\\@]("enddot"|"endcode"|"endmsc"|"enduml") { /* end of verbatim block */ copyToOutput(yytext,(int)yyleng); if (&yytext[4]==g_blockName) { @@ -554,7 +554,7 @@ void replaceComment(int offset); copyToOutput(yytext,(int)yyleng); } <Verbatim>^[ \t]*"///" { - if (g_blockName=="dot" || g_blockName=="msc" || g_blockName.at(0)=='f') + if (g_blockName=="dot" || g_blockName=="msc" || g_blockName=="startuml" || g_blockName.at(0)=='f') { // see bug 487871, strip /// from dot images and formulas. int l=0; diff --git a/src/commentscan.l b/src/commentscan.l index a42f6ce..369349b 100644 --- a/src/commentscan.l +++ b/src/commentscan.l @@ -191,6 +191,7 @@ static DocCmdMap docCmdMap[] = { "manonly", &handleFormatBlock, FALSE }, { "dot", &handleFormatBlock, TRUE }, { "msc", &handleFormatBlock, TRUE }, + { "startuml", &handleFormatBlock, TRUE }, { "code", &handleFormatBlock, TRUE }, { "addindex", &handleAddIndex, FALSE }, { "if", &handleIf, FALSE }, @@ -877,7 +878,7 @@ static int yyread(char *buf,int max_size) /* start command character */ CMD ("\\"|"@") DCMD1 ("arg"|"attention"|"author"|"cite"|"code") -DCMD2 ("date"|"dot"|"msc"|"dotfile"|"example") +DCMD2 ("date"|"dot"|"msc"|"dotfile"|"example"|"startuml") DCMD3 ("htmlinclude"|"htmlonly"|"image"|"include") DCMD4 ("includelineno"|"internal"|"invariant") DCMD5 ("latexinclude"|"latexonly"|"li"|"line"|"manonly"|"name") @@ -1785,6 +1786,13 @@ RCSTAG "$"{ID}":"[^\n$]+"$" BEGIN(Comment); } } +<FormatBlock>{CMD}"enduml" { + addOutput(yytext); + if (blockName=="startuml") // found end of the block + { + BEGIN(Comment); + } + } <FormatBlock>[^ \@\*\/\\\n]* { // some word addOutput(yytext); } @@ -1809,9 +1817,11 @@ RCSTAG "$"{ID}":"[^\n$]+"$" addOutput(*yytext); } <FormatBlock><<EOF>> { + QCString endTag = "@end"+blockName; + if (blockName=="startuml") endTag="enduml"; warn(yyFileName,yyLineNr, - "reached end of comment while inside a @%s block; check for missing @end%s tag!", - blockName.data(),blockName.data() + "reached end of comment while inside a @%s block; check for missing @%s tag!", + blockName.data(),endTag.data() ); yyterminate(); } diff --git a/src/compound.xsd b/src/compound.xsd index dec596b..be897c3 100644 --- a/src/compound.xsd +++ b/src/compound.xsd @@ -386,7 +386,7 @@ <xsd:element name="rtfonly" type="xsd:string" /> <xsd:element name="latexonly" type="xsd:string" /> <xsd:element name="dot" type="xsd:string" /> - <xsd:element name="msc" type="xsd:string" /> + <xsd:element name="plantuml" type="xsd:string" /> <xsd:element name="anchor" type="docAnchorType" /> <xsd:element name="formula" type="docFormulaType" /> <xsd:element name="ref" type="docRefTextType" /> diff --git a/src/config.l b/src/config.l index 133bc11..a237faf 100644 --- a/src/config.l +++ b/src/config.l @@ -1329,6 +1329,37 @@ void Config::check() mscgenPath=""; } + // check plantuml path + QCString &plantumlJarPath = Config_getString("PLANTUML_JAR_PATH"); + if (!plantumlJarPath.isEmpty()) + { + QFileInfo pu(plantumlJarPath); + if (pu.exists() && pu.isDir()) // PLANTUML_JAR_PATH is directory + { + QFileInfo jar(plantumlJarPath+portable_pathSeparator()+"plantuml.jar"); + if (jar.exists() && jar.isFile()) + { + plantumlJarPath = jar.dirPath(TRUE).utf8()+portable_pathSeparator(); + } + else + { + config_err("Jar file plantuml.jar not found at location " + "specified via PLANTUML_JAR_PATH: '%s'\n",plantumlJarPath.data()); + plantumlJarPath=""; + } + } + else if (pu.exists() && pu.isFile() && plantumlJarPath.right(4)==".jar") // PLANTUML_JAR_PATH is file + { + plantumlJarPath = pu.dirPath(TRUE).utf8()+portable_pathSeparator(); + } + else + { + config_err("path specified via PLANTUML_JAR_PATH does not exist or not a directory: %s\n", + plantumlJarPath.data()); + plantumlJarPath=""; + } + } + // check dia path QCString &diaPath = Config_getString("DIA_PATH"); if (!diaPath.isEmpty()) diff --git a/src/config.xml b/src/config.xml index 5a0af59..9020d30 100644 --- a/src/config.xml +++ b/src/config.xml @@ -3295,6 +3295,16 @@ to be found in the default search path. ]]> </docs> </option> + <option type='string' id='PLANTUML_JAR_PATH' format='dir' defval='' depends='HAVE_DOT'> + <docs> +<![CDATA[ + When using plantuml, the \c PLANTUML_JAR_PATH tag should be used to specify the path where + java can find the \c plantuml.jar file. If left blank, it is assumed PlantUML is not used or + called during a preprocessing step. Doxygen will generate a warning when it encounters a + \ref cmdstartuml "\\startuml" command in this case and will not generate output for the diagram. +]]> + </docs> + </option> <option type='int' id='DOT_GRAPH_MAX_NODES' minval='0' maxval='10000' defval='50' depends='HAVE_DOT'> <docs> <![CDATA[ diff --git a/src/docbookvisitor.cpp b/src/docbookvisitor.cpp index 5d7aafd..9f2dbe3 100644 --- a/src/docbookvisitor.cpp +++ b/src/docbookvisitor.cpp @@ -34,6 +34,7 @@ #include "msc.h" #include "dia.h" #include "htmlentity.h" +#include "plantuml.h" DocbookDocVisitor::DocbookDocVisitor(FTextStream &t,CodeOutputInterface &ci) : DocVisitor(DocVisitor_Docbook), m_t(t), m_ci(ci), m_insidePre(FALSE), m_hide(FALSE) @@ -216,35 +217,58 @@ void DocbookDocVisitor::visit(DocVerbatim *s) } break; case DocVerbatim::Msc: - static int mscindex = 1; - QCString baseName(4096); - QCString name; - QCString stext = s->text(); - m_t << "<para>" << endl; - name.sprintf("%s%d", "msc_inline_mscgraph_", mscindex); - baseName.sprintf("%s%d", - (Config_getString("DOCBOOK_OUTPUT")+"/inline_mscgraph_").data(), - mscindex++ - ); - QFile file(baseName+".msc"); - if (!file.open(IO_WriteOnly)) { - err("Could not open file %s.msc for writing\n",baseName.data()); + static int mscindex = 1; + QCString baseName(4096); + QCString name; + QCString stext = s->text(); + m_t << "<para>" << endl; + name.sprintf("%s%d", "msc_inline_mscgraph_", mscindex); + baseName.sprintf("%s%d", + (Config_getString("DOCBOOK_OUTPUT")+"/inline_mscgraph_").data(), + mscindex++ + ); + QFile file(baseName+".msc"); + if (!file.open(IO_WriteOnly)) + { + err("Could not open file %s.msc for writing\n",baseName.data()); + } + QCString text = "msc {"; + text+=stext; + text+="}"; + file.writeBlock( text, text.length() ); + file.close(); + m_t << " <figure>" << endl; + m_t << " <title>" << name << "</title>" << endl; + m_t << " <mediaobject>" << endl; + m_t << " <imageobject>" << endl; + writeMscFile(baseName); + m_t << " </imageobject>" << endl; + m_t << " </mediaobject>" << endl; + m_t << " </figure>" << endl; + m_t << "</para>" << endl; + } + break; + case DocVerbatim::PlantUML: + { + static QCString docbookOutput = Config_getString("DOCBOOK_OUTPUT"); + QCString baseName = writePlantUMLSource(docbookOutput,s->exampleFile(),s->text()); + QCString shortName = baseName; + int i; + if ((i=shortName.findRev('/'))!=-1) + { + shortName=shortName.right(shortName.length()-i-1); + } + m_t << " <figure>" << endl; + m_t << " <title>" << shortName << "</title>" << endl; + m_t << " <mediaobject>" << endl; + m_t << " <imageobject>" << endl; + writePlantUMLFile(baseName); + m_t << " </imageobject>" << endl; + m_t << " </mediaobject>" << endl; + m_t << " </figure>" << endl; + m_t << "</para>" << endl; } - QCString text = "msc {"; - text+=stext; - text+="}"; - file.writeBlock( text, text.length() ); - file.close(); - m_t << " <figure>" << endl; - m_t << " <title>" << name << "</title>" << endl; - m_t << " <mediaobject>" << endl; - m_t << " <imageobject>" << endl; - writeMscFile(baseName); - m_t << " </imageobject>" << endl; - m_t << " </mediaobject>" << endl; - m_t << " </figure>" << endl; - m_t << "</para>" << endl; break; } } @@ -1195,6 +1219,22 @@ void DocbookDocVisitor::writeMscFile(const QCString &baseName) m_t << "</imagedata>" << endl; } +void DocbookDocVisitor::writePlantUMLFile(const QCString &baseName) +{ + QCString shortName = baseName; + int i; + if ((i=shortName.findRev('/'))!=-1) + { + shortName=shortName.right(shortName.length()-i-1); + } + QCString outDir = Config_getString("DOCBOOK_OUTPUT"); + generatePlantUMLOutput(baseName,outDir,PUML_BITMAP); + m_t << " <imagedata"; + m_t << " width=\"50%\""; + m_t << " align=\"center\" valign=\"middle\" scalefit=\"1\" fileref=\"" << shortName << ".png" << "\">"; + m_t << "</imagedata>" << endl; +} + void DocbookDocVisitor::startMscFile(const QCString &fileName, const QCString &width, const QCString &height, diff --git a/src/docbookvisitor.h b/src/docbookvisitor.h index 20d424e..dd67aba 100644 --- a/src/docbookvisitor.h +++ b/src/docbookvisitor.h @@ -156,6 +156,7 @@ class DocbookDocVisitor : public DocVisitor const QCString &height, bool hasCaption); void endDotFile(bool hasCaption); void writeDotFile(const QCString &fileName); + void writePlantUMLFile(const QCString &fileName); //-------------------------------------- // state variables //-------------------------------------- diff --git a/src/docparser.cpp b/src/docparser.cpp index 6d788fd..bab0c75 100644 --- a/src/docparser.cpp +++ b/src/docparser.cpp @@ -5569,6 +5569,23 @@ int DocPara::handleCommand(const QCString &cmdName) doctokenizerYYsetStatePara(); } break; + case CMD_STARTUML: + { + static QCString jarPath = Config_getString("PLANTUML_JAR_PATH"); + doctokenizerYYsetStatePlantUML(); + retval = doctokenizerYYlex(); + if (jarPath.isEmpty()) + { + warn_doc_error(g_fileName,doctokenizerYYlineno,"ignoring startuml command because PLANTUML_JAR_PATH is not set"); + } + else + { + m_children.append(new DocVerbatim(this,g_context,g_token->verb,DocVerbatim::PlantUML,FALSE,g_token->sectionId)); + } + if (retval==0) warn_doc_error(g_fileName,doctokenizerYYlineno,"startuml section ended without end marker"); + doctokenizerYYsetStatePara(); + } + break; case CMD_ENDPARBLOCK: retval=RetVal_EndParBlock; break; @@ -5583,6 +5600,7 @@ int DocPara::handleCommand(const QCString &cmdName) case CMD_ENDVERBATIM: case CMD_ENDDOT: case CMD_ENDMSC: + case CMD_ENDUML: warn_doc_error(g_fileName,doctokenizerYYlineno,"unexpected command %s",qPrint(g_token->name)); break; case CMD_PARAM: diff --git a/src/docparser.h b/src/docparser.h index 17f296b..3dc3d84 100644 --- a/src/docparser.h +++ b/src/docparser.h @@ -433,7 +433,7 @@ class DocWhiteSpace : public DocNode class DocVerbatim : public DocNode { public: - enum Type { Code, HtmlOnly, ManOnly, LatexOnly, RtfOnly, XmlOnly, Verbatim, Dot, Msc, DocbookOnly }; + enum Type { Code, HtmlOnly, ManOnly, LatexOnly, RtfOnly, XmlOnly, Verbatim, Dot, Msc, DocbookOnly, PlantUML }; DocVerbatim(DocNode *parent,const QCString &context, const QCString &text, Type t,bool isExample, const QCString &exampleFile,bool isBlock=FALSE,const QCString &lang=QCString()); diff --git a/src/doctokenizer.h b/src/doctokenizer.h index 1db764e..c95230b 100644 --- a/src/doctokenizer.h +++ b/src/doctokenizer.h @@ -160,5 +160,6 @@ void doctokenizerYYpushBackHtmlTag(const char *tag); void doctokenizerYYsetStateSnippet(); void doctokenizerYYstartAutoList(); void doctokenizerYYendAutoList(); +void doctokenizerYYsetStatePlantUML(); #endif diff --git a/src/doctokenizer.l b/src/doctokenizer.l index 3bd2058..ea4a1a4 100644 --- a/src/doctokenizer.l +++ b/src/doctokenizer.l @@ -417,6 +417,8 @@ REFWORD {LABELID}|{REFWORD2}|{REFWORD3}|{LNKWORD2} %x St_Verbatim %x St_Dot %x St_Msc +%x St_PlantUMLOpt +%x St_PlantUML %x St_Param %x St_XRefItem %x St_XRefItem2 @@ -843,7 +845,7 @@ REFWORD {LABELID}|{REFWORD2}|{REFWORD3}|{LNKWORD2} <St_Dot>. { /* dot text */ g_token->verb+=yytext; } -<St_Msc>{CMD}("endmsc"|"endvhdlflow") { +<St_Msc>{CMD}("endmsc") { return RetVal_OK; } <St_Msc>[^\\@\n]+ | @@ -851,6 +853,23 @@ REFWORD {LABELID}|{REFWORD2}|{REFWORD3}|{LNKWORD2} <St_Msc>. { /* msc text */ g_token->verb+=yytext; } +<St_PlantUMLOpt>\n { + g_token->sectionId=g_token->sectionId.stripWhiteSpace(); + BEGIN(St_PlantUML); + } +<St_PlantUMLOpt>["{}] { // skip curly brackets or quotes around the optional image name + } +<St_PlantUMLOpt>. { + g_token->sectionId += yytext; + } +<St_PlantUML>{CMD}"enduml" { + return RetVal_OK; + } +<St_PlantUML>[^\\@\n]+ | +<St_PlantUML>\n | +<St_PlantUML>. { /* plantuml text */ + g_token->verb+=yytext; + } <St_Title>"\"" { // quoted title BEGIN(St_TitleQ); } @@ -1121,6 +1140,10 @@ REFWORD {LABELID}|{REFWORD2}|{REFWORD3}|{LNKWORD2} g_endMarker="endmsc"; BEGIN(St_SecSkip); } +<St_Sections>{CMD}"startuml"/[^a-z_A-Z0-9] { + g_endMarker="enduml"; + BEGIN(St_SecSkip); + } <St_Sections>{CMD}"htmlonly"/[^a-z_A-Z0-9] { g_endMarker="endhtmlonly"; BEGIN(St_SecSkip); @@ -1318,6 +1341,13 @@ void doctokenizerYYsetStateMsc() BEGIN(St_Msc); } +void doctokenizerYYsetStatePlantUML() +{ + g_token->verb=""; + g_token->sectionId=""; + BEGIN(St_PlantUMLOpt); +} + void doctokenizerYYsetStateParam() { BEGIN(St_Param); diff --git a/src/htmldocvisitor.cpp b/src/htmldocvisitor.cpp index bd94232..e319a1f 100644 --- a/src/htmldocvisitor.cpp +++ b/src/htmldocvisitor.cpp @@ -34,6 +34,7 @@ #include "filedef.h" #include "memberdef.h" #include "htmlentity.h" +#include "plantuml.h" static const int NUM_HTML_LIST_TYPES = 4; static const char types[][NUM_HTML_LIST_TYPES] = {"1", "a", "i", "A"}; @@ -429,9 +430,21 @@ void HtmlDocVisitor::visit(DocVerbatim *s) m_t << "<div align=\"center\">" << endl; writeMscFile(baseName+".msc",s->relPath(),s->context()); if (Config_getBool("DOT_CLEANUP")) file.remove(); + m_t << "</div>" << endl; + forceStartParagraph(s); + } + break; + case DocVerbatim::PlantUML: + { + forceEndParagraph(s); + + static QCString htmlOutput = Config_getString("HTML_OUTPUT"); + QCString baseName = writePlantUMLSource(htmlOutput,s->exampleFile(),s->text()); + m_t << "<div align=\"center\">" << endl; + writePlantUMLFile(baseName,s->relPath(),s->context()); + m_t << "</div>" << endl; + forceStartParagraph(s); } - m_t << "</div>" << endl; - forceStartParagraph(s); break; } } @@ -1976,6 +1989,37 @@ void HtmlDocVisitor::writeDiaFile(const QCString &fileName, m_t << "<img src=\"" << relPath << baseName << ".png" << "\" />" << endl; } +void HtmlDocVisitor::writePlantUMLFile(const QCString &fileName, + const QCString &relPath, + const QCString &) +{ + QCString baseName=fileName; + int i; + if ((i=baseName.findRev('/'))!=-1) // strip path + { + baseName=baseName.right(baseName.length()-i-1); + } + if ((i=baseName.findRev('.'))!=-1) // strip extension + { + baseName=baseName.left(i); + } + static QCString outDir = Config_getString("HTML_OUTPUT"); + static QCString imgExt = Config_getEnum("DOT_IMAGE_FORMAT"); + if (imgExt=="svg") + { + generatePlantUMLOutput(fileName,outDir,PUML_SVG); + //m_t << "<iframe scrolling=\"no\" frameborder=\"0\" src=\"" << relPath << baseName << ".svg" << "\" />" << endl; + //m_t << "<p><b>This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.</b></p>"; + //m_t << "</iframe>" << endl; + m_t << "<object type=\"image/svg+xml\" data=\"" << relPath << baseName << ".svg\"></object>" << endl; + } + else + { + generatePlantUMLOutput(fileName,outDir,PUML_BITMAP); + m_t << "<img src=\"" << relPath << baseName << ".png" << "\" />" << endl; + } +} + /** Used for items found inside a paragraph, which due to XHTML restrictions * have to be outside of the paragraph. This method will forcefully end * the current paragraph and forceStartParagraph() will restart it. diff --git a/src/htmldocvisitor.h b/src/htmldocvisitor.h index ddefecd..00ae09e 100644 --- a/src/htmldocvisitor.h +++ b/src/htmldocvisitor.h @@ -153,6 +153,7 @@ class HtmlDocVisitor : public DocVisitor void writeDotFile(const QCString &fileName,const QCString &relPath,const QCString &context); void writeMscFile(const QCString &fileName,const QCString &relPath,const QCString &context); void writeDiaFile(const QCString &fileName,const QCString &relPath,const QCString &context); + void writePlantUMLFile(const QCString &fileName,const QCString &relPath,const QCString &context); void pushEnabled(); void popEnabled(); diff --git a/src/htmlentity.cpp b/src/htmlentity.cpp index c49491e..216b65c 100644 --- a/src/htmlentity.cpp +++ b/src/htmlentity.cpp @@ -462,7 +462,7 @@ const DocSymbol::PerlSymb *HtmlEntityMapper::perl(DocSymbol::SymType symb) const /*! * @brief Give code of the requested HTML entity name - * @param symName HTML entity name without \c & and \c; + * @param symName HTML entity name without \c & and \c ; * @return the code for the requested HTML entity name, * in case the requested HTML item does not exist `DocSymbol::Sym_unknown` is returned. */ diff --git a/src/latexdocvisitor.cpp b/src/latexdocvisitor.cpp index dd23d60..af1a5fd 100644 --- a/src/latexdocvisitor.cpp +++ b/src/latexdocvisitor.cpp @@ -32,6 +32,7 @@ #include "filedef.h" #include "config.h" #include "htmlentity.h" +#include "plantuml.h" static QCString escapeLabelName(const char *s) { @@ -321,6 +322,16 @@ void LatexDocVisitor::visit(DocVerbatim *s) if (Config_getBool("DOT_CLEANUP")) file.remove(); } break; + case DocVerbatim::PlantUML: + { + QCString latexOutput = Config_getString("LATEX_OUTPUT"); + QCString baseName = writePlantUMLSource(latexOutput,s->exampleFile(),s->text()); + + m_t << "\\begin{center}\n"; + writePlantUMLFile(baseName); + m_t << "\\end{center}\n"; + } + break; } } @@ -1743,6 +1754,7 @@ void LatexDocVisitor::writeMscFile(const QCString &baseName) m_t << "\\end{DoxyImageNoCaption}\n"; } + void LatexDocVisitor::startDiaFile(const QCString &fileName, const QCString &width, const QCString &height, @@ -1834,3 +1846,20 @@ void LatexDocVisitor::writeDiaFile(const QCString &baseName) m_t << "\\end{DoxyImageNoCaption}\n"; } +void LatexDocVisitor::writePlantUMLFile(const QCString &baseName) +{ + QCString shortName = baseName; + int i; + if ((i=shortName.findRev('/'))!=-1) + { + shortName=shortName.right(shortName.length()-i-1); + } + QCString outDir = Config_getString("LATEX_OUTPUT"); + generatePlantUMLOutput(baseName,outDir,PUML_EPS); + m_t << "\n\\begin{DoxyImageNoCaption}" + " \\mbox{\\includegraphics"; + m_t << "{" << shortName << "}"; + m_t << "}\n"; // end mbox + m_t << "\\end{DoxyImageNoCaption}\n"; +} + diff --git a/src/latexdocvisitor.h b/src/latexdocvisitor.h index d386569..64560b9 100644 --- a/src/latexdocvisitor.h +++ b/src/latexdocvisitor.h @@ -176,6 +176,7 @@ class LatexDocVisitor : public DocVisitor const QCString &height, bool hasCaption); void endDiaFile(bool hasCaption); void writeDiaFile(const QCString &fileName); + void writePlantUMLFile(const QCString &fileName); void pushEnabled(); void popEnabled(); diff --git a/src/libdoxygen.pro.in b/src/libdoxygen.pro.in index f50f987..703b885 100644 --- a/src/libdoxygen.pro.in +++ b/src/libdoxygen.pro.in @@ -87,6 +87,7 @@ HEADERS = arguments.h \ pagedef.h \ perlmodgen.h \ lodepng.h \ + plantuml.h \ pre.h \ printdocvisitor.h \ pycode.h \ @@ -165,6 +166,7 @@ SOURCES = arguments.cpp \ layout.cpp \ lodepng.cpp \ logos.cpp \ + plantuml.cpp \ mandocvisitor.cpp \ mangen.cpp \ sqlite3gen.cpp \ diff --git a/src/mandocvisitor.cpp b/src/mandocvisitor.cpp index 5403324..1fe5409 100644 --- a/src/mandocvisitor.cpp +++ b/src/mandocvisitor.cpp @@ -208,6 +208,7 @@ void ManDocVisitor::visit(DocVerbatim *s) case DocVerbatim::DocbookOnly: case DocVerbatim::Dot: case DocVerbatim::Msc: + case DocVerbatim::PlantUML: /* nothing */ break; } diff --git a/src/markdown.cpp b/src/markdown.cpp index 291e1dc..11b01ea 100644 --- a/src/markdown.cpp +++ b/src/markdown.cpp @@ -208,6 +208,10 @@ static QCString isBlockCommand(const char *data,int offset,int size) { return "end"+blockName; } + else if (blockName=="startuml") + { + return "enduml"; + } else if (blockName=="f" && end<size) { if (data[end]=='$') diff --git a/src/perlmodgen.cpp b/src/perlmodgen.cpp index 948836b..c636cdf 100644 --- a/src/perlmodgen.cpp +++ b/src/perlmodgen.cpp @@ -662,8 +662,9 @@ void PerlModDocVisitor::visit(DocVerbatim *s) case DocVerbatim::LatexOnly: type = "latexonly"; break; case DocVerbatim::XmlOnly: type = "xmlonly"; break; case DocVerbatim::DocbookOnly: type = "docbookonly"; break; - case DocVerbatim::Dot: type = "dot"; break; + case DocVerbatim::Dot: type = "dot"; break; case DocVerbatim::Msc: type = "msc"; break; + case DocVerbatim::PlantUML: type = "plantuml"; break; } openItem(type); m_output.addFieldQuotedString("content", s->text()); diff --git a/src/plantuml.cpp b/src/plantuml.cpp new file mode 100644 index 0000000..18f028b --- /dev/null +++ b/src/plantuml.cpp @@ -0,0 +1,97 @@ +/****************************************************************************** + * + * Copyright (C) 1997-2014 by Dimitri van Heesch. + * + * Permission to use, copy, modify, and distribute this software and its + * documentation under the terms of the GNU General Public License is hereby + * granted. No representations are made about the suitability of this software + * for any purpose. It is provided "as is" without express or implied warranty. + * See the GNU General Public License for more details. + * + * Documents produced by Doxygen are derivative works derived from the + * input used in their production; they are not affected by this license. + * + */ + +#include "plantuml.h" +#include "portable.h" +#include "config.h" +#include "message.h" + +#include <qdir.h> + +//static const int maxCmdLine = 40960; + +QCString writePlantUMLSource(const QCString &outDir,const QCString &fileName,const QCString &content) +{ + QCString baseName(4096); + static int umlindex=1; + + if (fileName.isEmpty()) // generate name + { + baseName = outDir+"/inline_umlgraph_"+QCString().setNum(umlindex++); + } + else // user specified name + { + baseName = fileName; + int i=baseName.findRev('.'); + if (i!=-1) baseName = baseName.left(i); + baseName.prepend(outDir+"/"); + } + QFile file(baseName+".pu"); + if (!file.open(IO_WriteOnly)) + { + err("Could not open file %s for writing\n",baseName.data()); + } + QCString text = "@startuml\n"; + text+=content; + text+="@enduml\n"; + file.writeBlock( text, text.length() ); + file.close(); + return baseName; +} + +void generatePlantUMLOutput(const char *baseName,const char *outDir,PlantUMLOutputFormat format) +{ + static QCString plantumlJarPath = Config_getString("PLANTUML_JAR_PATH"); + + QCString pumlExe = "java"; + QCString pumlArgs = "-Djava.awt.headless=true -jar \""+plantumlJarPath+"plantuml.jar\" "; + pumlArgs+="-o \""; + pumlArgs+=outDir; + pumlArgs+="\" "; + QCString extension; + switch (format) + { + case PUML_BITMAP: + pumlArgs+="-tpng"; + extension=".png"; + break; + case PUML_EPS: + pumlArgs+="-teps"; + extension=".eps"; + break; + case PUML_SVG: + pumlArgs+="-tsvg"; + extension=".svg"; + break; + } + pumlArgs+=" \""; + pumlArgs+=baseName; + pumlArgs+=".pu\" "; + int exitCode; + //printf("*** running: %s %s outDir:%s %s\n",pumlExe.data(),pumlArgs.data(),outDir,outFile); + msg("Running PlantUML on generated file %s.pu\n",baseName); + portable_sysTimerStart(); + if ((exitCode=portable_system(pumlExe,pumlArgs,FALSE))!=0) + { + err("Problems running PlantUML. Verify that the command 'java -jar \"%splantuml.jar\" -h' works from the command line\n", + plantumlJarPath.data()); + } + else if (Config_getBool("DOT_CLEANUP")) + { + QFile(QCString(baseName)+".pu").remove(); + } + portable_sysTimerStop(); +} + diff --git a/src/plantuml.h b/src/plantuml.h new file mode 100644 index 0000000..27626d1 --- /dev/null +++ b/src/plantuml.h @@ -0,0 +1,40 @@ +/****************************************************************************** + * + * Copyright (C) 1997-2014 by Dimitri van Heesch. + * + * Permission to use, copy, modify, and distribute this software and its + * documentation under the terms of the GNU General Public License is hereby + * granted. No representations are made about the suitability of this software + * for any purpose. It is provided "as is" without express or implied warranty. + * See the GNU General Public License for more details. + * + * Documents produced by Doxygen are derivative works derived from the + * input used in their production; they are not affected by this license. + * + */ + +#ifndef PLANTUML_H +#define PLANTUML_H + +class QCString; + +/** Plant UML output image formats */ +enum PlantUMLOutputFormat { PUML_BITMAP, PUML_EPS, PUML_SVG }; + +/** Write a PlantUML compatible file. + * @param[in] outDir the output directory to write the file to. + * @param[in] fileName the name of the file. If empty a name will be chosen automatically. + * @param[in] content the contents of the PlantUML file. + * @returns The name of the generated file. + */ +QCString writePlantUMLSource(const QCString &outDir,const QCString &fileName,const QCString &content); + +/** Convert a PlantUML file to an image. + * @param[in] baseName the name of the generated file (as returned by writePlantUMLSource()) + * @param[in] outDir the directory to write the resulting image into. + * @param[in] format the image format to generate. + */ +void generatePlantUMLOutput(const char *baseName,const char *outDir,PlantUMLOutputFormat format); + +#endif + diff --git a/src/printdocvisitor.h b/src/printdocvisitor.h index ee2ae3f..1d384b1 100644 --- a/src/printdocvisitor.h +++ b/src/printdocvisitor.h @@ -134,6 +134,7 @@ class PrintDocVisitor : public DocVisitor case DocVerbatim::DocbookOnly: printf("<docbookonly>"); break; case DocVerbatim::Dot: printf("<dot>"); break; case DocVerbatim::Msc: printf("<msc>"); break; + case DocVerbatim::PlantUML: printf("<plantuml>"); break; } printf("%s",s->text().data()); switch(s->type()) @@ -148,6 +149,7 @@ class PrintDocVisitor : public DocVisitor case DocVerbatim::DocbookOnly: printf("</docbookonly>"); break; case DocVerbatim::Dot: printf("</dot>"); break; case DocVerbatim::Msc: printf("</msc>"); break; + case DocVerbatim::PlantUML: printf("</plantuml>"); break; } } void visit(DocAnchor *a) diff --git a/src/rtfdocvisitor.cpp b/src/rtfdocvisitor.cpp index 0d17208..5e27137 100644 --- a/src/rtfdocvisitor.cpp +++ b/src/rtfdocvisitor.cpp @@ -16,6 +16,8 @@ * */ +#include <qfileinfo.h> + #include "rtfdocvisitor.h" #include "docparser.h" #include "language.h" @@ -26,13 +28,13 @@ #include "util.h" #include "rtfstyle.h" #include "message.h" -#include <qfileinfo.h> #include "parserintf.h" #include "msc.h" #include "dia.h" #include "filedef.h" #include "config.h" #include "htmlentity.h" +#include "plantuml.h" //#define DBG_RTF(x) m_t << x #define DBG_RTF(x) do {} while(0) @@ -317,6 +319,16 @@ void RTFDocVisitor::visit(DocVerbatim *s) if (Config_getBool("DOT_CLEANUP")) file.remove(); } break; + case DocVerbatim::PlantUML: + { + static QCString rtfOutput = Config_getString("RTF_OUTPUT"); + QCString baseName = writePlantUMLSource(rtfOutput,s->exampleFile(),s->text()); + + m_t << "\\par{\\qc "; // center picture + writePlantUMLFile(baseName); + m_t << "} "; + } + break; } m_lastIsPara=FALSE; } @@ -1704,3 +1716,23 @@ void RTFDocVisitor::writeDiaFile(const QCString &fileName) m_lastIsPara=TRUE; } +void RTFDocVisitor::writePlantUMLFile(const QCString &fileName) +{ + QCString baseName=fileName; + int i; + if ((i=baseName.findRev('/'))!=-1) + { + baseName=baseName.right(baseName.length()-i-1); + } + QCString outDir = Config_getString("RTF_OUTPUT"); + generatePlantUMLOutput(fileName,outDir,PUML_BITMAP); + if (!m_lastIsPara) m_t << "\\par" << endl; + m_t << "{" << endl; + m_t << rtf_Style_Reset; + m_t << "\\pard \\qc {\\field\\flddirty {\\*\\fldinst INCLUDEPICTURE \""; + m_t << baseName << ".png"; + m_t << "\" \\\\d \\\\*MERGEFORMAT}{\\fldrslt IMAGE}}\\par" << endl; + m_t << "}" << endl; + m_lastIsPara=TRUE; +} + diff --git a/src/rtfdocvisitor.h b/src/rtfdocvisitor.h index efc9d21..c50802d 100644 --- a/src/rtfdocvisitor.h +++ b/src/rtfdocvisitor.h @@ -155,6 +155,7 @@ class RTFDocVisitor : public DocVisitor void writeDotFile(const QCString &fileName); void writeMscFile(const QCString &fileName); void writeDiaFile(const QCString &fileName); + void writePlantUMLFile(const QCString &fileName); //-------------------------------------- // state variables diff --git a/src/xmldocvisitor.cpp b/src/xmldocvisitor.cpp index 343926c..f2da28c 100644 --- a/src/xmldocvisitor.cpp +++ b/src/xmldocvisitor.cpp @@ -209,6 +209,11 @@ void XmlDocVisitor::visit(DocVerbatim *s) filter(s->text()); m_t << "</msc>"; break; + case DocVerbatim::PlantUML: + m_t << "<plantuml>"; + filter(s->text()); + m_t << "</plantuml>"; + break; } } diff --git a/winbuild/Doxygen.vcproj b/winbuild/Doxygen.vcproj index f539511..882e514 100644 --- a/winbuild/Doxygen.vcproj +++ b/winbuild/Doxygen.vcproj @@ -843,6 +843,10 @@ >
</File>
<File
+ RelativePath="..\src\plantuml.cpp"
+ >
+ </File>
+ <File
RelativePath="..\src\portable.cpp"
>
</File>
@@ -2615,6 +2619,10 @@ >
</File>
<File
+ RelativePath="..\src\plantuml.h"
+ >
+ </File>
+ <File
RelativePath="..\src\portable.h"
>
</File>
|