*~
-woc/woc
-woc/prewoc
*.exe
*.o
*.obj
vinfo
.ctmp
.ptmp
+*.tag
+object_script*
+.qmake.stash
qtbase/Makefile*
+woc/woc
+woc/prewoc
woc/Makefile*
+woc/pattern.qrc
doc/*base
doc/woc
-*.tag
-object_script*
cgi2scgi/cgi2scgi
cgi2scgi/cgi2scgi.exe
examples/clock/html/
examples/clock/qts/clockserv
examples/clock/qtc/clock.exe
examples/clock/qts/clockserv.exe
-.qmake.stash
+woc/qrcgen/Makefile
+woc/qrcgen/qrcgen
+woc/test/woctest_*
+woc/test/Makefile*
+woc/test/*/Makefile*
+woc/test/generic/testfiles.qrc
.PHONY: prewoc woc qtbase doc clean install install-woc install-qtbase install-phpbase install-doc
-vinfo/staticVersion.h prewoc:
+qrcgen:
+ cd woc/qrcgen && $(QMAKE) && $(MAKE)
+
+vinfo/staticVersion.h prewoc: qrcgen
cd woc && $(QMAKE) -o Makefile.prewoc CONFIG+=prewoc
$(MAKE) -C woc -f Makefile.prewoc
cd woc && $(abspath woc/prewoc) :/version.wolf
-woc: vinfo/staticVersion.h
+woc: vinfo/staticVersion.h qrcgen
cd woc && $(QMAKE) CONFIG+=woc
$(MAKE) -C woc
=================
PACK = Persistence And Communication Kit
-version 0.7
+version 1.1
-(c) Konrad Rosenbaum, 2009-2013
+(c) Konrad Rosenbaum, 2009-2018
Directory woc:
The C++-source files of woc (Web Object Compiler) are licensed under GPLv3 or
The SOAP target of woc uses some schema files that are under different
copyright, see those files (woc/soap/*.xsd) for details.
-Directories qtbase, phpbase:
-The C++-source files of the Qt base library are licensed under LGPLv3 or at
+Directories *base:
+The C++-source files of the base libraries are licensed under LGPLv3 or at
your option any newer version of the license. See COPYING.LGPL for details.
AGPL option:
Since you are free to link this code with code under the AGPL, I encourage its
-use for server side code. See COPYING.AGPL for details.
+use for server side code, but I do not require it. See COPYING.AGPL for details.
Directory examples:
The example code is in the public domain - you are free to use it however you
<td><b>Server</b></td>
</tr>
-<tr><td><b>C++/Qt 4.x</b></td>
+<tr><td><b>C++/Qt 5.x</b></td>
<td>yes</td>
<td>partial <i>(no tables)</i></td>
</tr>
<h2>Building PACK</h2>
-For compiling PACK you need a C++ compiler (eg. GCC) and Qt4.x.
-The meta-compiler WOC and the Qt binding require at least Qt 4.7 or 5.x.
+For compiling PACK you need a C++ compiler (eg. GCC) and Qt5.x.
+The meta-compiler WOC and the Qt binding require at least Qt 5.6.
For compiling and running generated code it all depends on your target:<br>
<table frame="1" border="1">
-<tr><td>C++/Qt<td>A C++ compiler and at least Qt 4.7 or 5.x, modules: Core, Network, XML, optionally XMLpatterns</tr>
-<tr><td>PHP<td>PHP, at least version 5.2</tr>
+<tr><td>C++/Qt<td>A C++-11 compiler and at least Qt 5.6 LTS(*), modules: Core, Network, XML, optionally XMLpatterns</tr>
+<tr><td>PHP<td>PHP, at least version 5.6(*)</tr>
<tr><td>SOAP<td>any compatible toolkit that can handle WSDL files</tr>
</table><p>
+(*) earlier versions may work, but are untested<p>
Compiling the meta-compiler is relatively easy: just enter the woc directory and call qmake and make (or open the woc.pro file in QtCreator and compile from there). The same approach works for PACK's Qt base library. The PHP library of PACK does not need to be compiled, just include it in your project.
<ul>
<li><a href="woc.html">Building and Using WOC</a><ul>
- <li><a href="woc/index.html">Source Docu</li>
+ <li><a href="woc/index.html">Source Docu</a></li>
+ <li><a href="woc-pattern.html">Developing new WOC patterns</a></li>
</ul></li>
<li><a href="wolf.html">Web Object Language Files</a><ul>
<li><a href="wolf-db.html">WOLF DataBase Layer</a></li>
<li><a href="phpbase/index.html">Source Docu</li>
</ul></li>
<li>Qt Side:<ul>
- <li><a href="qtcli.html">Qt 4 Client Binding</a></li>
- <li><a href="qtsrv.html">Qt 4 Server Binding</a></li>
+ <li><a href="qtcli.html">Qt 5 Client Binding</a></li>
+ <li><a href="qtsrv.html">Qt 5 Server Binding</a></li>
<li><a href="qtbase/index.html">Source Docu</a></li>
</ul></li>
</ul>
-</html>
\ No newline at end of file
+</html>
--- /dev/null
+<html>
+<head>
+<style>
+tt {background-color: #ddd; }
+pre {background-color: #ddd; }
+table {border: 2px solid black; border-collapse: collapse; }
+th, td {border: 1px solid #444; border-collapse: collapse; }
+th {background-color: #0000c0 ; color: white; border: 1px solid #ddd; }
+.header {background-color: #e0e0ff; }
+.subheader {background-color: #e0ffe0; }
+.nowrap {white-space: nowrap;}
+</style>
+<title>Web Object Compiler - Patterns</title>
+</head>
+<body>
+<h1>Extending WOC with New Patterns</h1>
+
+The Web Object Compiler (<a href="woc.html">woc</a>) can be extended with new patterns for new languages and targets.<p>
+
+Each pattern description contains a meta.xml file that contains the main defintion of that pattern, it describes basic properties of the generated language and references pattern files that are used to build specific file types.<p>
+
+Normally patterns are built-in, but they can be extended or overwritten by supplying one or multiple <tt>-pattern=/directory/to/patterns</tt> arguments to the WOC command line.
+WOC will then search all subdirectories of the given directory for meta.xml files that contain pattern definitions.<p>
+
+<h2>Pattern Meta Data: meta.xml</h2>
+
+This is an example definition for a fictitious programming language "MyLang":
+<pre>
+<?xml version="1.0" encoding="utf-8" ?>
+<Meta lang="mylang/client" tag="MyLangClientOutput">
+
+ <Include file="../meta-mylang-common.xml" />
+
+ <Syntax comment="##" section-start="#--" variable-marker="${ }$" />
+
+ <Feature name="allClassPrefix" type="string" default="C" />
+
+ <File type="interface" tag="" name="src{interface.classname}.h" pattern="interface.my"/>
+ <File type="class" tag="definition" name="{class.classname}.my" pattern="class-definition.my"/>
+ <File type="project" tag="includeAll" name="includeAll.my" pattern="includeAll.my"/>
+
+ <Type config="string" map="MyString"/>
+ <Type config="astring" map="MyString"/>
+ <Type config="string:*" map="MyString"/>
+ <Type config="text" map="MyString"/>
+ <Type config="List:*" map="List&lt;*>"/>
+ <Type config="int" map="int"/>
+ <Type config="int32" map="int"/>
+ <Type config="int64" map="long"/>
+
+ <Precalc trigger="interface.new" deleteOn="interface.close">
+ <Variable name="interface.classname" value="{allClassPrefix}Interface"/>
+ </Precalc>
+ <Precalc trigger="class.new" deleteOn="class.close">
+ <Variable name="class.classname" if="{class.abstract}" valueFalse="{allClassPrefix}{class.name}" valueTrue="{allClassPrefix}{class.name}Abstract"/>
+ </Precalc>
+
+</Meta>
+</pre>
+
+This pattern could be instantiated in a WOLF file with this line:
+<pre>
+ <MyLangClientOutput sourceDir="mysource" subDir="wob" allClassPrefix="MyPrf" clean="yes" />
+</pre>
+
+<table>
+<tr><th><b>Tag/@Attribute</b></th><th><b>Description</b></th></tt>
+
+<tr class="header"><td>Meta/...</td><td>is the main element of a pattern definition; only files that have this document element are considered valid definitions</td></tr>
+<tr><td>Meta/@lang</td><td>defines the target language and feature separated by a slash; features are usually "client" or "server", but may be any token; this is matched against the lang attribute in WOLF files; this does not need to be unique among patterns</td></tr>
+<tr><td>Meta/@tag</td><td>the XML tag used in WOLF files to instantiate and configure this pattern; if two or more pattern definitions contain the same tag name then the one loaded last overrides the earlier ones</td></tr>
+
+<tr class="subheader"><td>Meta/Include</td><td>includes another XML file as part of this meta file; it must follow the same syntax; since included files are executed before any other definition in the originating file, definitions in the including file override included definitions; the attributes of the Meta tag are completely ignored in included files</td></tr>
+<tr><td>Meta/Include/@file</td><td>specifies the file name of the included file relative to this file's location</td></tr>
+
+<tr class="subheader"><td>Meta/Syntax/...</td><td>defines the syntax elements used in pattern files</td></tr>
+<tr><td>Meta/Syntax/@comment</td><td>Comments that are discarded entirely from pattern files - lines started with this string do not end up in generated files, while comments following the target language syntax are generated. The default is "#WOC:". This could be an extension of the normal line comment marker or something different.</td></tr>
+<tr><td>Meta/Syntax/@section-start</td><td>Lines starting with this string mark the start of a new section in a pattern file. The default is "#SECTION:". This could be an extension of the normal line comment marker or something different.</td></tr>
+<tr><td>Meta/Syntax/@variable-marker</td><td>Space separated markers for the beginning and end of a variable reference, the default is { and }. In this example variables additionally are surrounded by $-signs as in <tt>${variable}$</tt>. The strings should be chosen so that it is unlikely they occur naturally anywhere in the target language.</td></tr>
+
+<tr class="subheader"><td>Meta/Feature/...</td><td>Features are configuration options that can be overridden in WOLF files as attributes to the instantiation tag.</td></tr>
+<tr><td>Meta/Feature/@name</td><td>The name of the feature - this is also used as attribute name for the WOLF file and as variable name to reflect the actual value.</td></tr>
+<tr><td>Meta/Feature/@type</td><td>Either "string" or "boolean".</td></tr>
+<tr><td>Meta/Feature/@default</td><td>The default value for the feature if the attribute is not used.</td></tr>
+
+<tr class="subheader"><td>Meta/File/...</td><td>Defines a file pattern: what kind of file to generate, when to do it and which file describes the content.</td></tr>
+<tr><td>Meta/File/@type</td><td>Defines when a file is created and closed. E.g. for type "class" the file is created on class.new and closed on class.close; valid types are any pairs of "*.new" and "*.close" triggers (e.g. class, interface, transaction.input).</td></tr>
+<tr><td>Meta/File/@tag</td><td>A distinguishing tag for the file definition. The combination of type and tag must be unique. Empty tags are allowed.</td></tr>
+<tr><td>Meta/File/@name</td><td>An expression to calculate the final file name of the generated file(s). Per default the name will be identical to that of the pattern file. If the trigger ("type".new) can occur multiple times then this pattern should contain variables to make the file names unique, otherwise later triggered files overwrite earlier files.</td></tr>
+<tr><td>Meta/File/@pattern</td><td>The pattern file describing the file content. The pattern file name is relative to the current XML file.</td></tr>
+
+<tr class="subheader"><td>Meta/Type/...</td><td>Maps data types from the generic WOLF types to language specific types.</td></tr>
+<tr><td>Meta/Type/@config</td><td>The type used in a WOLF file for transactions or for database types.</td></tr>
+<tr><td>Meta/Type/@map</td><td>The language specific data type</td></tr>
+
+<tr class="subheader"><td>Meta/Precalc/...</td><td>Describes a set of pre-calculated variables. Pre-calculation can happen on any trigger.</td></tr>
+<tr><td>Meta/Precalc/@trigger</td><td>The trigger that starts the pre-calculation. The calculation happens before any patterns are evaluated.</td></tr>
+<tr><td>Meta/Precalc/@deleteOn</td><td>Optional. If given: the variables are deleted again after this trigger.</td></tr>
+<tr><td>Meta/Precalc/Variable/@name</td><td>The variable to be calculated.</td></tr>
+<tr><td>Meta/Precalc/Variable/@value</td><td>The content of the variable, it may contain variable values/expressions itself. (Ignored if there is an "if" attribute.)</td></tr>
+<tr><td>Meta/Precalc/Variable/@if</td><td>If given: makes the value dependent on this boolean expression.</td></tr>
+<tr><td>Meta/Precalc/Variable/@valueTrue</td><td>Content of the variable if the expression in "if" evaluates to true. This may be an expression.</td></tr>
+<tr><td>Meta/Precalc/Variable/@valueFalse</td><td>Content of the variable if the expression in "if" evaluates to false. This may be an expression.</td></tr>
+</table>
+
+<h3>Value Expressions</h3>
+
+Whereever values are assigned (file names, feature defaults, value assignments to variables, boolean expressions or inside pattern files) those assignments may contain quoted variables and transformations on them. Inside the meta.xml file variables are always enclosed in { and }, which the configured syntax patterns are used in pattern files.<p>
+
+For example if the variable "v1" contains the value "world" then the expression <tt>hello {v1}!</tt> evaluates to <tt>hello world!</tt>.<p>
+
+A variable value can be transformed with any of the transformations listed in the reference below by simply using a "pipe expression".
+Transformations can be concatenated to execute several of them consecutively.
+<br/>E.g. <tt>hello {v1|toUpper|toCString}!</tt> evaluates to <tt>hello "WORLD"!</tt>.
+
+<h3>Boolean Expressions</h3>
+
+A single boolean value can be expressed as the following case insensitive values:<ul>
+ <li>values for true: "y", "yes", "true", "t", "on", "1" (or any other positive integer)</li>
+ <li>values for false: "n", "no", "false", "f", "off", "0"</li>
+ <li>any other value will be interpreted as false, but also produce a warning.</li></ul><p>
+
+Variables can be used to obtain boolean values simply by using the appropriate markers (<tt>{variable}</tt> in meta.xml, the configured marker in pattern files).
+Complex transformations can be used on <p>
+
+A value can be negated by prefixing it with an exclamation mark, e.g.: <tt>!{negatedVariable}</tt><p>
+
+Multiple boolean values can be strung together with <tt>&&</tt> (boolean and) or <tt>||</tt> (boolean or) to form expressions.
+Those expressions are always evaluated from left to right with no operator precedence.
+Spaces are ignored in expressions.
+<br/>E.g.: <tt>true && {variable} || {othervariable}</tt><p>
+
+Grouping sub-expressions with parentheses is currently not supported.
+
+<h3>Type Mappings</h3>
+
+The following types need to be mapped for each supported language:
+
+<table>
+<tr><th><b>WOLF Type<br/>config="..."</b></th><th><b>DB / Class</b></th><th>Example (Qt)<br/>map="..."</th><th>Description</th></tr>
+<tr class="header"><td colspan="4"><font size="2" color="#444"><i><b> simple class and transaction types</b></i></td></tr>
+<tr><td>string</td><td class="nowrap"> DB / Class </td><td>QString</td><td> Strings stored in XML elements or medium long strings in DB: needs a type that can store several kB of character data. </td></tr>
+<tr><td>astring</td><td> Class </td><td>QString</td><td> Strings stored in XML attributes: needs a type that can store about 1kB of character data. </td></tr>
+<tr><td>text</td><td> DB / Class </td><td>QString</td><td> Very long text: needs a type that can store large amounts of character data. </td></tr>
+<tr><td>int</td><td> Class </td><td>qint64</td><td> Generic integer: should be at least 32bits wide. </td></tr>
+<tr><td>int32</td><td> DB / Class </td><td>qint32</td><td> 32bit integer </td></tr>
+<tr><td>int64</td><td> DB / Class </td><td>qint64</td><td> 64bit integer </td></tr>
+<tr><td>bool</td><td> DB / Class </td><td>bool</td><td> Boolean value, needs a type that understands true/false. </td></tr>
+<tr class="header"><td colspan="4"><font size="2" color="#444"><i><b> DB types</b></i></td></tr>
+<tr><td>seq</td><td> DB </td><td>qint64</td><td> DB sequence, needs an integer type that is at least 32bits wide. </td></tr>
+<tr><td>seq32</td><td> DB </td><td>qint32</td><td> DB sequence, needs an integer type that is at least 32bits wide. </td></tr>
+<tr><td>seq64</td><td> DB </td><td>qint64</td><td> DB sequence, needs an integer type that is at least 64bits wide. </td></tr>
+<tr><td>enum</td><td> DB </td><td>qint64</td><td> Enum value that is stored as integer, needs an integer type that is at least 32bits wide. </td></tr>
+<tr><td>enum32</td><td> DB </td><td>qint32</td><td> Enum value that is stored as integer, needs an integer type that is at least 32bits wide. </td></tr>
+<tr><td>enum64</td><td> DB </td><td>qint64</td><td> Enum value that is stored as integer, needs an integer type that is at least 64bits wide. </td></tr>
+<tr><td>blob</td><td> DB </td><td>QByteArray</td><td> Large binary object, needs a type that can store large amounts of opaque bytes. </td></tr>
+<tr><td>string:*</td><td> DB </td><td>QString</td><td> This is a pattern matching string with a defined length (varchar(*)), needs a type that can store strings of at least the defined length. You can use a "*" character in the mapped type to insert the length (e.g. "MyLimitedString<*>"). </td></tr>
+<tr class="header"><td colspan="4"><font size="2" color="#444"><i><b> special class/transaction types</b></i></td></tr>
+<tr><td>List:*</td><td> Class </td><td>QList<*></td><td> Matches any list type. The mapping function is called recursively for the element type of the list - the result of that second call is injected into the mapped type to replace any "*" character. In this example "List:string" would be translated to "QList<QString>". </td></tr>
+<tr><td>Object</td><td> Class </td><td class="nowrap">{allClassPrefix}{classPrefix}*</td><td> Matches all already defined Class/Object types. Executes the mapped type as an expression, so that variables may be used (this is called in the class and transaction contexts, so only variables common to both can be used), afterwards any "*" characters are replaced by the original type name. </td></tr>
+<tr><td>Enum</td><td> Class </td><td>*</td><td> Matches all enum types defined inside a class. Executes the mapped type as an expression, so that variables may be used (this is called only in the class context, so only variables valid in this context can be used). </td></tr>
+</table>
+
+
+<h2>Pattern Files</h2>
+
+Pattern files are used to describe how a specific kind of file is constructed. The exact markers used in a pattern file depend on the attributes of the <tt><Syntax ...></tt> tag in the meta.xml file.<p>
+
+Pattern files are always presumed to be in UTF-8 encoding with Unix line endings (\n only).<p>
+
+In the following examples configuration of above is assumed:<ul>
+<li><tt>##</tt> ... is a comment of the pattern file itself and is never reproduced into a target file.</li>
+<li><tt>##--</tt> ... is a section definition.</li>
+<li><tt>${</tt> ... <tt>}$</tt> is used to mark variables and value expressions.</li>
+<li>A simple <tt>#</tt>... is assumed to be the line comment character for the target programming language. With <tt>#!</tt>... being used for special documentation comments.</li></ul>
+
+The following is an example pattern of an interface definition for the MyLang language:
+<pre>
+## interface.my
+## this is an example of a pattern file...
+#-- * interface.new
+define class ${interface.classname}$ inherit MyInterface
+ ${#TRANSCALL}$
+#-- * interface.close
+end class
+##
+##
+#-- TRANSCALLARG transaction.input.new clear
+#-- TRANSCALLARG transaction.input.value
+ ${transaction.input.name}$
+##
+#-- TRANSCALL transaction.close
+ #! convenience call to query ${transaction.classname}$ synchronously
+ function query${transaction.name}$ ( ${#TRANSCALLARG|oneLine(,)}$ )
+ {
+ return ${transaction.classname}$.query( ${#TRANSCALLARG|oneLine(,)}$ );
+ }
+#-- END ofFile
+</pre><p>
+
+Each section above has the following syntax:<br/>
+<tt>#-- SECTION trigger options...<br/>
+content...</tt><p>
+
+The <tt>SECTION</tt> is a keyword used to identify a file local variable into which the generated content is stored by appending it to the already existing content. Those variables are only valid in relation to this specific pattern file and are accessible by prepending a <tt>#</tt> sign to the section name (e.g. <tt>${#SECTION}$</tt>).<p>
+
+<b>Warning:</b> make sure you do not recursively include section variables in sections that are already included in those variables - this can easily lead to memory overflows through exponential growth of those sections.<p>
+
+The special section <tt>*</tt> writes directly into the file and is not available as variable. There must be at least one section definition with this target, otherwise the file will remain empty.<p>
+
+The special section <tt>*ERROR</tt> writes the content as an error to the console and immediately stops woc. The special section <tt>*WARNING</tt> writes the content as a warning message to the console and continues with processing. These can be used in connection with conditionals to signal fatal or unexpected situations.<p>
+
+Any trigger can be used in a section definition. However, the pattern will only evaluate triggers while the file is open, non-existent triggers and those that happen while the file is not open are ignored.<p>
+
+If you need to make sure that the last section ends with a defined number of newline characters it is advisable to add a dummy section definition at the end with a non-existent trigger. Per convention it should be a variation of <tt>#-- END ofFile</tt>.<p>
+
+Options for section definitions can be:<ul>
+<li><tt>clear</tt> - the target variable is emptied when this trigger happens and before any new content is added to the variable. This has no effect on the special <tt>*</tt> section.</li>
+<li><tt>if(...)</tt> - makes the section conditional and executes the trigger if the expression is also true (see below).</li>
+<li><tt>else</tt> - if the previous section contained a conditional then this section is executed if the previous section was not executed.</li>
+<li><tt>else if(...)</tt> - combines <tt>else</tt> and <tt>if</tt> by evaluating the condition only if the previous section was not executed. (Technically <tt>else</tt> can also follow the <tt>if</tt> option, but they will still be interpreted as <tt>else if</tt>.)</li></ul><p>
+
+Sections can be conditional by appending <tt>if(...)</tt> boolean expressions. The section is only executed if the boolean expression inside the parentheses evaluates to true:
+<pre>
+## version.my
+#-- * version.new if(${version.system|contains[svn]}$)
+VersionControlSystemName="Subversion";
+#-- * version.new else if(${version.system|contains[git]}$)
+VersionControlSystemName="Git";
+#-- * version.new else
+VersionControlSystemName="Unknown VCS!";
+##end of if-else-chain
+##
+#-- * version.close if(!${version.modified|contains[un]}$ || !${version.author|contains[Belzebub]}$)
+VersionControlSystemWARNING="Sources are untrustworthy!";
+</pre>
+If parentheses are ambiguous in those expressions they can be replaced by brackets (<tt>if[...]</tt>), curly braces (<tt>if{...}</tt>) or angle brackets (<tt>if<...></tt>). Since the parser used is rather simplistic (in order to allow for braces as parameters) you may need up to three different kinds of braces in an if-expression.<p>
+
+Some notes on <tt>if</tt> - <tt>elseif</tt> - <tt>else</tt> chains:<ul>
+<li>All elements of a chain must have the same trigger, but may have different targets and options.</li>
+<li><tt>else if</tt> and <tt>else</tt> must follow immediately after another <tt>if</tt> or <tt>else if</tt> section, otherwise the chain is broken.</li>
+<li>The chain is terminated by:<ul>
+ <li>A section with only the <tt>else</tt> option.</li>
+ <li>A section with none of those options.</li>
+ <li>A section for a different trigger.</li>
+ <li>The end of the file.</li></ul>
+</ul><p>
+
+During each trigger received by an open pattern/file all section definitions are checked from top to bottom. Each definition that matches (trigger match and optional if(...) matches) is executed before the next section definition is checked. Multiple section definitions for the same trigger may be executed if they match.
+
+<h2>Trigger and Variable Reference</h2>
+
+<h3>Common File Types</h3>
+
+Those are the most common types used for file definitions, but any other prefix that has a new and close trigger may be used as well:
+<table>
+<tr><th><b>File Type</b></th><th><b>Description</b></th></tr>
+<tr><td>project</td><td>started before all others, ends after all others, gets all triggers</td></tr>
+<tr><td>version</td><td>generated at the start (before interface) for version information, short lived</td></tr>
+<tr><td>interface</td><td>started after project, ends before project, gets all triggers except project.* and version.*</td></tr>
+<tr><td>class</td><td>generated for each class, short lived, gets only class.* triggers</td></tr>
+<tr><td>transaction</td><td>generated for each transaction, short lived, gets only transaction.* triggers</td></tr>
+<tr><td>table</td><td>generated for each table, short lived, gets only table.* triggers</td></tr>
+</table>
+<p>
+
+<h3>Triggers</h3>
+
+<table>
+<tr><th><b>Trigger</b></th><th><b>Description</b></th></tr>
+
+<tr class="header"><td colspan="2"><font size="2" color="#444"><i><b> Project</b></i></td></tr>
+<tr><td>project.new</td><td>triggered once before all other triggers, only received by project files</td></tr>
+<tr><td>project.close</td><td>triggered once after all other triggers, only received by project files</td></tr>
+
+<tr class="header"><td colspan="2"><font size="2" color="#444"><i><b> Version</b></i></td></tr>
+<tr><td>version.new</td><td>triggered once before all other triggers to generate project version information, received by project and version files</td></tr>
+<tr><td>version.close</td><td>triggered immediately after version.new, received by project and version files</td></tr>
+
+<tr class="header"><td colspan="2"><font size="2" color="#444"><i><b> Interface</b></i></td></tr>
+<tr><td>interface.new</td><td>triggered once to start interface files; received by project and interface files</td></tr>
+<tr><td>interface.close</td><td>triggered once to close interface files; received by project and interface files</td></tr>
+
+<tr class="header"><td colspan="2"><font size="2" color="#444"><i><b> Class</b></i></td></tr>
+<tr><td>class.new</td><td>triggered to start files for a new class; received by project, interface and class files</td></tr>
+<tr><td>class.close</td><td>triggered to close files for a class; received by project, interface and class files</td></tr>
+<tr><td>class.property</td><td>triggered to insert a property into a class; received by project, interface and class files</td></tr>
+<tr><td>class.enum.new</td><td>triggered to insert a new enum into a class; received by project, interface and class files</td></tr>
+<tr><td>class.enum.close</td><td>triggered to close an enum of a class; received by project, interface and class files</td></tr>
+<tr><td>class.enum.value</td><td>triggered to insert a new enum value into a class; received by project, interface and class files</td></tr>
+<tr><td>class.map.new</td><td>triggered to insert a new table mapping into a class; received by project, interface and class files</td></tr>
+<tr><td>class.map.close</td><td>triggered to close an table mapping of a class; received by project, interface and class files</td></tr>
+<tr><td>class.map.value</td><td>triggered to insert a new table mapping value (single property-column mapping) into a class; received by project, interface and class files</td></tr>
+
+<tr class="header"><td colspan="2"><font size="2" color="#444"><i><b> Transaction</b></i></td></tr>
+<tr><td>transaction.new</td><td>triggered to start files for a new transaction; received by project, interface and transaction files</td></tr>
+<tr><td>transaction.close</td><td>triggered to close files for a transaction; received by project, interface and transaction files</td></tr>
+<tr><td>transaction.input.new</td><td>triggered to start the input section for a new transaction; received by project, interface and transaction files</td></tr>
+<tr><td>transaction.input.close</td><td>triggered to close the input section for a transaction; received by project, interface and transaction files</td></tr>
+<tr><td>transaction.input.value</td><td>triggered to create an input parameter for a transaction; received by project, interface and transaction files</td></tr>
+<tr><td>transaction.output.new</td><td>triggered to start the output section for a new transaction; received by project, interface and transaction files</td></tr>
+<tr><td>transaction.output.close</td><td>triggered to close the output section for a transaction; received by project, interface and transaction files</td></tr>
+<tr><td>transaction.output.value</td><td>triggered to create an output parameter for a transaction; received by project, interface and transaction files</td></tr>
+<tr><td>transaction.call</td><td>triggered to create the call code for a transaction; received by project, interface and transaction files</td></tr>
+<tr><td>transaction.privilege</td><td>triggered to create a privilege definition for a transaction; received by project, interface and transaction files</td></tr>
+
+<tr class="header"><td colspan="2"><font size="2" color="#444"><i><b> DB Schema</b></i></td></tr>
+<tr><td>db.new</td><td>triggered to start files for the DB layer before any table is started; received by project, interface and db files</td></tr>
+<tr><td>db.close</td><td>triggered to close files for the DB layer after all tables are complete; received by project, interface and db files</td></tr>
+
+<tr class="header"><td colspan="2"><font size="2" color="#444"><i><b> Table</b></i></td></tr>
+<tr><td>table.new</td><td>triggered to start files for a new table; received by project, interface, db and table files</td></tr>
+<tr><td>table.close</td><td>triggered to close files for a table; received by project, interface, db and table files</td></tr>
+<tr><td>table.column.new</td><td>triggered to start a new column for a table; received by project, interface, db and table files</td></tr>
+<tr><td>table.column.close</td><td>triggered to close a column for a table; received by project, interface, db and table files</td></tr>
+<tr><td>table.column.call</td><td>triggered to add an initializer call for a column for a table; received by project, interface, db and table files</td></tr>
+<tr><td>table.column.enum.new</td><td>triggered to start the enum definition of a column for a table; received by project, interface, db and table files</td></tr>
+<tr><td>table.column.enum.close</td><td>triggered to close the enum definition of a column for a table; received by project, interface, db and table files</td></tr>
+<tr><td>table.column.enum.value</td><td>triggered to add a value to the enum definition of a column for a table; received by project, interface, db and table files</td></tr>
+<tr><td>table.preset.new</td><td>triggered to start the preset section for a table, does not trigger if there are no presets; received by project, interface, db and table files</td></tr>
+<tr><td>table.preset.close</td><td>triggered to close the preset section for a table; received by project, interface, db and table files</td></tr>
+<tr><td>table.preset.row.new</td><td>triggered to start a row in the preset section for a table; received by project, interface, db and table files</td></tr>
+<tr><td>table.preset.row.close</td><td>triggered to close a row in the preset section for a table; received by project, interface, db and table files</td></tr>
+<tr><td>table.preset.row.value</td><td>triggered to add a single value in the preset section for a table; triggers only for values defined in the WOLF file; received by project, interface, db and table files</td></tr>
+<tr><td>table.preset.row.column</td><td>triggered for each column of earch row in the preset section for a table; triggers in the order of column definition, also for non-preset columns - see the *.isset variable; *.value and *.column may trigger interlaced into each other - you should use only one of the two; received by project, interface, db and table files</td></tr>
+<tr><td>table.constraint</td><td>triggered to create a new constraint for a table; received by project, interface, db and table files</td></tr>
+<tr><td>table.foreigncall</td><td>triggered to create a lookup method via a reference (<Foreign> tag) for a table; received by project, interface, db and table files</td></tr>
+</table>
+<p>
+
+When a trigger is called the following actions happen in this order:
+<ol>
+<li>set preset variables (as documented below)</li>
+<li>pre-calculate variables configured in a Precalc section of meta.xml</li>
+<li>*.new: create files if appropriate</li>
+<li>check all active files whether they have one or multiple actions attached to this trigger<ul>
+ <li>Files are checked in the order they were opened (if opened by the same trigger: the order of configuration in meta.xml)</li>
+ <li>inside files sections are matched and executed top to bottom</li></ul></li>
+<li>*.close: close files if appropriate</li>
+<li>delete preset variables (as documented below; calculated variables are not deleted!)</li>
+</ol>
+
+<h3>Variables</h3>
+
+The following variables are available at specific times (between their start and delete triggers) during evaluation:
+
+<table>
+<tr><th><b>Start/Delete Trigger</b></th><th><b>Variable</b></th><th><b>Description</b></th></tr>
+
+<tr class="header"><td colspan="3"><font size="2" color="#444"><i><b> Project</b></i></td></tr>
+<tr><td rowspan="10">project.new/close</td> <td>project.name</td><td>Name of the project</td></tr>
+<tr><td>project.doc</td><td>documentation for the entire project</td></tr>
+<tr><td>project.baseDir</td><td>base directory of the project</td></tr>
+<tr><td>project.wobDir</td><td>directory with wob files</td></tr>
+<tr><td>project.xmlNamespace</td><td>encoding namespace for XML serialization</td></tr>
+<tr><td>project.auth</td><td>authentication type (none, session, basic)</td></tr>
+<tr><td>project.encoding</td><td>data encoding (wob, soap)</td></tr>
+<tr><td>project.sourceDir</td><td>directory of the project sources are inserted to (relative to baseDir)</td></tr>
+<tr><td>project.subDir</td><td>subdirectory in which sources are generated (relative to sourceDir)</td></tr>
+<tr><td><i>features...</i></td><td>any feature defined in the meta.xml file is available as a global variable during the entire evaluation.</td></tr>
+
+<tr class="header"><td colspan="3"><font size="2" color="#444"><i><b> Version</b></i></td></tr>
+<tr><td rowspan="20">version.new/close</td> <td>version.comm</td><td>version of the communication spec (transactions)</td></tr>
+<tr><td>version.needComm</td><td>minimum communication protocol version needed from peer</td></tr>
+<tr><td>version.human</td><td>human readable version number</td></tr>
+<tr><td>version.system</td><td>source control system that was asked for version info (git, svn)</td></tr>
+<tr><td>version.gentime</td><td>time at which version info was generated (not time of the version itself)</td></tr>
+<tr><td>version.author</td><td>author of the version</td></tr>
+<tr><td>version.modified</td><td>whether the sources had uncommitted changes ("modified" or "unmodified")</td></tr>
+<tr><td>version.number</td><td>version number (svn: integer 1..n; git: sha hash)</td></tr>
+<tr><td>version.path</td><td>sub-path of the main directory (project.baseDir) inside the repository (empty for git)</td></tr>
+<tr><td>version.rootUrl</td><td>URL of the repository (git: tracked remote repo)</td></tr>
+<tr><td>version.time</td><td>time that the version was committed</td></tr>
+<tr><td>version.woc.human</td><td>human readable version number of WOC</td></tr>
+<tr><td>version.woc.system</td><td>source control system that was asked for version info (git, svn) of WOC</td></tr>
+<tr><td>version.woc.gentime</td><td>time at which version info was generated (not time of the version itself) of WOC</td></tr>
+<tr><td>version.woc.author</td><td>author of the version of WOC</td></tr>
+<tr><td>version.woc.modified</td><td>whether the sources of WOC had uncommitted changes ("modified" or "unmodified")</td></tr>
+<tr><td>version.woc.number</td><td>version number of WOC (svn: integer 1..n; git: sha hash)</td></tr>
+<tr><td>version.woc.path</td><td>sub-path of the main directory (project.baseDir) inside the repository of WOC (empty for git)</td></tr>
+<tr><td>version.woc.rootUrl</td><td>URL of the repository of WOC (git: tracked remote repo)</td></tr>
+<tr><td>version.woc.time</td><td>time that the version of WOC was committed</td></tr>
+
+<tr class="header"><td colspan="3"><font size="2" color="#444"><i><b> Interface</b></i></td></tr>
+<tr><td rowspan="1">interface.new/close</td> <td colspan="2"><font color="#444">currently none</font></td></tr>
+
+<tr class="header"><td colspan="3"><font size="2" color="#444"><i><b> Class</b></i></td></tr>
+<tr><td rowspan="5">class.new/close</td> <td>class.name</td><td>name of the class</td></tr>
+<tr><td>class.abstract</td><td>if true: the class is abstract in the generated language</td></tr>
+<tr><td>class.abstractLangs</td><td>line-wise list of languages in which the class is abstract</td></tr>
+<tr><td>class.base</td><td>name of the base class</td></tr>
+<tr><td>class.doc</td><td>documentation of the class</td></tr>
+<tr><td rowspan="2">class.enum.new/close</td> <td>class.enum.name</td><td>name of the enum type</td></tr>
+<tr><td>class.enum.doc</td><td>documentation of the enum type</td></tr>
+<tr><td rowspan="3">class.enum.value</td> <td>class.enum.value.name</td><td>name of the enum value</td></tr>
+<tr><td>class.enum.value.numvalue</td><td>numeric value of the enum value</td></tr>
+<tr><td>class.enum.value.doc</td><td>documentation of the enum value</td></tr>
+<tr><td rowspan="18">class.property</td> <td>class.property.name</td><td>name of the property</td></tr>
+<tr><td>class.property.type</td><td>configured type of the property</td></tr>
+<tr><td>class.property.maptype</td><td>mapped (language appropriate) type of the property</td></tr>
+<tr><td>class.property.elementtype</td><td>if type is a list, type of the elements of this property; if not a list identical to type</td></tr>
+<tr><td>class.property.elementmaptype</td><td>mapped (language appropriate) type of the elements of the property</td></tr>
+<tr><td>class.property.optional</td><td>true if the property is optional</td></tr>
+<tr><td>class.property.doc</td><td>documentation of the property</td></tr>
+<tr><td>class.property.isidentity</td><td>the property identifies the object</td></tr>
+<tr><td>class.property.isabstract</td><td>the property is abstract</td></tr>
+<tr><td>class.property.isattribute</td><td>the property is transmitted as an attribute</td></tr>
+<tr><td>class.property.iselement</td><td>the property is transmitted as an element</td></tr>
+<tr><td>class.property.issimpleelement</td><td>the property is transmitted as an element, but not a complex object</td></tr>
+<tr><td>class.property.isenum</td><td>the property is an enum</td></tr>
+<tr><td>class.property.islist</td><td>the property is a list</td></tr>
+<tr><td>class.property.isint</td><td>the property is an integer type</td></tr>
+<tr><td>class.property.isstring</td><td>the property is a string</td></tr>
+<tr><td>class.property.isblob</td><td>the property is a blob</td></tr>
+<tr><td>class.property.isobject</td><td>the property is an object</td></tr>
+
+<tr><td rowspan="2">class.map.new/close</td> <td>class.map.table</td><td>table from which to map values</td></tr>
+<tr><td>class.map.doc</td><td>documentation for the mapping</td></tr>
+<tr><td rowspan="3">class.map.value</td> <td>class.map.value.property</td><td>name of the target property</td></tr>
+<tr><td>class.map.value.column</td><td>source column for the data</td></tr>
+<tr><td>class.map.value.call</td><td>code to fill the data</td></tr>
+
+<tr class="header"><td colspan="3"><font size="2" color="#444"><i><b> Transaction</b></i></td></tr>
+<tr><td rowspan="6">transaction.new/close</td> <td>transaction.name</td><td>name of the transaction</td></tr>
+<tr><td>transaction.updating</td><td>if true: accesses the DB to update, if false: accesses DB to read only</td></tr>
+<tr><td>transaction.doc</td><td>documentation for the transaction</td></tr>
+<tr><td>transaction.mode</td><td>transaction mode (open, auth, checked)</td></tr>
+<tr><td>transaction.nologReq</td><td>bool: whether to suppress logging of request messages</td></tr>
+<tr><td>transaction.nologRsp</td><td>bool: whether to suppress logging of response messages</td></tr>
+<tr><td rowspan="1">transaction.input.new/close</td> <td colspan="2"><font color="#444">currently none</font></td></tr>
+<tr><td rowspan="12">transaction.input.value</td> <td>transaction.input.name</td><td>name of the input field</td></tr>
+<tr><td>transaction.input.type</td><td>configured type of the input field</td></tr>
+<tr><td>transaction.input.maptype</td><td>mapped (language specific) type of the input field</td></tr>
+<tr><td>transaction.input.doc</td><td>documentation of the input field</td></tr>
+<tr><td>transaction.input.isattribute</td><td>the property is transmitted as an attribute</td></tr>
+<tr><td>transaction.input.iselement</td><td>the property is transmitted as an element</td></tr>
+<tr><td>transaction.input.issimpleelement</td><td>the property is transmitted as an element, but not a complex object</td></tr>
+<tr><td>transaction.input.islist</td><td>the property is a list</td></tr>
+<tr><td>transaction.input.isint</td><td>the property is an integer type</td></tr>
+<tr><td>transaction.input.isstring</td><td>the property is a string</td></tr>
+<tr><td>transaction.input.isblob</td><td>the property is a blob</td></tr>
+<tr><td>transaction.input.isobject</td><td>the property is an object</td></tr>
+<tr><td rowspan="1">transaction.output.new/close</td> <td colspan="2"><font color="#444">currently none</font></td></tr>
+<tr><td rowspan="12">transaction.output.value</td> <td>transaction.output.name</td><td>name of the output field</td></tr>
+<tr><td>transaction.output.type</td><td>configured type of the output field</td></tr>
+<tr><td>transaction.output.maptype</td><td>mapped (language specific) type of the output field</td></tr>
+<tr><td>transaction.output.doc</td><td>documentation of the output field</td></tr>
+<tr><td>transaction.output.isattribute</td><td>the property is transmitted as an attribute</td></tr>
+<tr><td>transaction.output.iselement</td><td>the property is transmitted as an element</td></tr>
+<tr><td>transaction.output.issimpleelement</td><td>the property is transmitted as an element, but not a complex object</td></tr>
+<tr><td>transaction.output.islist</td><td>the property is a list</td></tr>
+<tr><td>transaction.output.isint</td><td>the property is an integer type</td></tr>
+<tr><td>transaction.output.isstring</td><td>the property is a string</td></tr>
+<tr><td>transaction.output.isblob</td><td>the property is a blob</td></tr>
+<tr><td>transaction.output.isobject</td><td>the property is an object</td></tr>
+<tr><td rowspan="1">transaction.call</td> <td>transaction.call.code</td><td>code of the call to be executed with the transaction</td></tr>
+<tr><td rowspan="2">transaction.privilege</td> <td>transaction.privilege.name</td><td>name of the privilege</td></tr>
+<tr><td>transaction.privilege.doc</td><td>documentation of the privilege</td></tr>
+
+<tr class="header"><td colspan="3"><font size="2" color="#444"><i><b> DB Schema</b></i></td></tr>
+<tr><td rowspan="7">db.new/close</td> <td>db.instance</td><td>instance variable for DB access</td></tr>
+<tr><td>db.schema</td><td>instance variable for the DB Schema</td></tr>
+<tr><td>db.configTable</td><td>table in which configuration values are stored</td></tr>
+<tr><td>db.configKeyColumn</td><td>column in which configuration keys are stored</td></tr>
+<tr><td>db.configValueColumn</td><td>column in which configuration values are stored</td></tr>
+<tr><td>db.version</td><td>version of the DB Schema</td></tr>
+<tr><td>db.versionRow</td><td>row of the configuration table in which the DB schema version is stored/td></tr>
+
+<tr class="header"><td colspan="3"><font size="2" color="#444"><i><b> Table</b></i></td></tr>
+<tr><td rowspan="9">table.new/close</td> <td>table.name</td><td>name of the table</td></tr>
+<tr><td>table.base</td><td>base class for the table implementation</td></tr>
+<tr><td>table.doc</td><td>documentation for a table</td></tr>
+<tr><td>table.hasaudit</td><td>bool: true if this table has a corresponding audit table</td></tr>
+<tr><td>table.isaudit</td><td>bool: true if this is an audit table</td></tr>
+<tr><td>table.auditname</td><td>name of the corresponding audittable if hasaudit=true or<br/>name of the main table if isaudit=true</td></tr>
+<tr><td>table.backup</td><td>bool: true if this table is part of the backup</td></tr>
+<tr><td>table.backupKey</td><td>name of the column by which to back up</td></tr>
+<tr><td>table.backupGroupSize</td><td>the size of backup groups or 0 for the default</td></tr>
+<tr><td rowspan="13">table.column.new/close</td> <td>table.column.name</td><td>name of the column</td></tr>
+<tr><td>table.column.doc</td><td>documentation for the column</td></tr>
+<tr><td>table.column.type</td><td>data type of the column</td></tr>
+<tr><td>table.column.null</td><td>if true: the columns should be NULL; if false: it should be NOT NULL</td></tr>
+<tr><td>table.column.default</td><td>the default value of the column</td></tr>
+<tr><td>table.column.isforeignkey</td><td>true if this is a foreign key column</td></tr>
+<tr><td>table.column.foreignkey</td><td>foreign key spec of the column</td></tr>
+<tr><td>table.column.foreignkeytable</td><td>foreign key table of the column</td></tr>
+<tr><td>table.column.foreignkeycolumn</td><td>foreign key reference column of the column</td></tr>
+<tr><td>table.column.primarykey</td><td>true if this column is part of the primary key</td></tr>
+<tr><td>table.column.isaudit</td><td>the column is part of an audit-table only</td></tr>
+<tr><td>table.column.isindexed</td><td>true if the column is indexed</td></tr>
+<tr><td>table.column.isunique</td><td>true if the column has a UNIQUE constraint</td></tr>
+<tr><td rowspan="1">table.column.call</td> <td>table.column.call.code</td><td>code to be called to initialize values of this column</td></tr>
+<tr><td rowspan="3">table.column.enum.value</td> <td>table.column.enum.name</td><td>name of the enum value</td></tr>
+<tr><td>table.column.enum.numvalue</td><td>numeric value of the enum value</td></tr>
+<tr><td>table.column.enum.doc</td><td>documentation of the enum value</td></tr>
+<tr><td rowspan="9">table.preset.row.value<br/> <i>and</i><br/>table.preset.row.column</td> <td>table.preset.colname</td><td>name of the column</td></tr>
+<tr><td>table.preset.value</td><td>value to be preset</td></tr>
+<tr><td>table.preset.type</td><td>data type of the column</td></tr>
+<tr><td>table.preset.isstring</td><td>true if the data type of the column needs string wrapping</td></tr>
+<tr><td>table.preset.isint</td><td>true if the data type of the column is an integer type</td></tr>
+<tr><td>table.preset.isblob</td><td>true if the data type of the column is a blob type</td></tr>
+<tr><td>table.preset.code</td><td>code to initialize the field</td></tr>
+<tr><td>table.preset.iscode</td><td>true if the value is derived from code, false if set directly</td></tr>
+<tr><td>table.preset.isset</td><td>true if the value is actually part of that preset row; always true on *.value, may be false on *.column</td></tr>
+<tr><td rowspan="2">table.constraint</td> <td>table.constraint.type</td><td>type of the constraint: unique (other types may be defined in later versions)</td></tr>
+<tr><td>table.constraint.columns</td><td>list of comma separated columns of the constraint</td></tr>
+<tr><td rowspan="5">table.foreigncall</td> <td>table.foreigncall.method</td><td>method name for the call</td></tr>
+<tr><td>table.foreigncall.column</td><td>local column to compare to</td></tr>
+<tr><td>table.foreigncall.foreigntable</td><td>foreign table to be returned</td></tr>
+<tr><td>table.foreigncall.foreigncolumn</td><td>foreign column to filter by</td></tr>
+<tr><td>table.foreigncall.doc</td><td>documentation for the call</td></tr>
+</table><p>
+
+Variables are always initialized before the start trigger is called and deleted after the delete trigger has finished. They are visible in between those triggers, even for other unrelated triggers. If no delete trigger is listed above (appended with "/") then the variables are only valid on the specific listed trigger.<p>
+
+Precalculated variables are visible during the time specified in the <tt><Precalc></tt> tag.<p>
+
+If a pre-defined variable is overwritten by some action woc does not take any measures to correct this.<p>
+
+Non-existent variables in an expression evaluate to an empty string and a warning is produced by woc.<p>
+
+The following transformation functions are defined and can be used on variables:
+<table>
+<tr><th><b>Transformation</b></th><th><b>Description</b></th></tr>
+<tr><td>toUpper</td><td>converts to upper case</td></tr>
+<tr><td>toLower</td><td>converts to lower case</td></tr>
+<tr><td>xmlEncode</td><td>encodes special XML characters, like < to &lt;</td></tr>
+<tr><td>urlEncode</td><td>encodes special URL characters, like < to %3c</td></tr>
+<tr><td>toCString</td><td>encodes the value as a properly escaped C-String</td></tr>
+<tr><td>oneLine</td><td>compresses the value to one line, replacing newline with space</td></tr>
+<tr><td>oneLine(x)</td><td>compresses the value to one line, replacing newline with the string x (x can be any character or combination of characters)</td></tr>
+<tr><td>trim</td><td>trims leading and trailing space of the value</td></tr>
+<tr><td>linetrim</td><td>trims leading and trailing space from each line of the value</td></tr>
+<tr><td>skipEmptyLines</td><td>removes all empty lines from the value (empty is anything that only contains whitespace)</td></tr>
+<tr><td>sort</td><td>sorts the lines of the value by ASCII/Unicode value</td></tr>
+<tr><td>unique</td><td>removes conscutive duplicate lines of the value (leading empty lines will be dropped)</td></tr>
+<tr><td>isEmpty</td><td>converts to a bool: true if the string is empty (whitespace not counted)</td></tr>
+<tr><td>isNotEmpty</td><td>converts to a bool: true if the string is not empty (whitespace not counted)</td></tr>
+<tr><td>contains(x)</td><td>converts to a bool: true if the string contains the string x</td></tr>
+<tr><td>prepend(x)</td><td>prepends the string x to every line of the variable value</td></tr>
+<tr><td>indent(x)</td><td>indents each line of the variable value by x (int) spaces</td></tr>
+<tr><td>replace(x)(y)</td><td>replaces each occurence of string x with string y, y may be empty to remove x, if x is empty behavior is undefined</td></tr>
+<tr><td>negate</td><td>interprets the value as boolean and inverts it</td></tr>
+<tr><td>if(ontrue)(onfalse)</td><td>interprets the value as boolean and replaces it with the string ontrue or onfalse; onfalse is optional</td></tr>
+</table><p>
+
+Transformations can have parameters attached in parentheses. Parameters can not be variables or expressions themselved, they are always interpreted as literal strings.<p>
+
+If parentheses are ambiguous then they can be replaced by brackets (<tt>[ ]</tt>), curly braces (<tt>{ }</tt>) or angle brackets (<tt>< ></tt>). The use of markers for parameters must be consistent in the complete expression.<p>
+
+If a non-existent transformation is used it has no effect on the value and a warning is produced by woc. If a transformation is listed with more parameters than expected the additional parameters are ignored, if less than expected parameters are listed then the missing parameter is assumed to be an empty string and a warning is produced by woc.<p>
+
+</html>
<h2>Compiling WOC</h2>
-WOC requires Qt 4 (any version starting at 4.4 should work) in order to be compiled.<p>
+WOC requires Qt 5 (any version starting at 5.6 should work) in order to be compiled.<p>
Enter the <tt>woc</tt> directory and call <tt>qmake</tt> and <tt>make</tt> to compile it or open <tt>woc.pro</tt> in QtCreator and build it.
WOC is a command line utility. It takes the name of any number of WOLF files as argument - it will process those files in the order they are given as arguments and produce output that encompasses all of them. If you want to compile several independent WOLF files you should call WOC with each of them separately. Since most WOLF files will give relative paths you should change into the directory of the WOLF file before calling WOC.
-</html>
\ No newline at end of file
+</html>
<table frame="1" border="1">
<tr><td><b>Type</b></td><td><b>Description</b></td></tr>
<tr><td>int</td><td>an integer - it is at least a 32bit signed type; it is transported as attribute</td></tr>
+<tr><td>int32</td><td>an integer - it is at least a 32bit signed type; it is transported as attribute</td></tr>
+<tr><td>int64</td><td>an integer - it is at least a 64bit signed type; it is transported as attribute</td></tr>
+<tr><td>bool</td><td>a boolean value; it is transported as attribute</td></tr>
<tr><td>astring</td><td>a string that can be safely stored in a XML attribute</td></tr>
<tr><td>string</td><td>a string that is transported as element and can reach any length</td></tr>
<tr><td><i>EnumName</i></td><td>the name of an enum defined in the same class is possible, local storage is as integer, transport is as string equalling the symbolic name of the value in an attribute</td></tr>
<hr>
<a href="wolf-db.html">Previous: DB Layer</a>
-</html>
\ No newline at end of file
+</html>
QStringList toStringList()const;
/**returns the result as a list of DOM nodes*/
MDomNodeList toNodeList()const{return m_result;}
+ ///returns the result as a list of DOM elements, filters out anything not an element
+ QList<QDomElement> toElementList()const{QList<QDomElement>ret;for(auto n:m_result)if(n.isElement())ret<<n.toElement();return ret;}
/**cast to QString: see toString()*/
operator QString()const{return toString();}
--- /dev/null
+SOURCES+= \
+ $$PWD/genproc.cpp \
+ $$PWD/genout.cpp \
+ $$PWD/genfile.cpp \
+ $$PWD/genexpr.cpp \
+ $$PWD/genvar.cpp
+
+HEADERS+= \
+ $$PWD/genproc.h \
+ $$PWD/genout.h \
+ $$PWD/genfile.h \
+ $$PWD/genexpr.h \
+ $$PWD/genvar.h
+
+INCLUDEPATH += $$PWD
--- /dev/null
+// Copyright (C) 2018 by Konrad Rosenbaum <konrad@silmor.de>
+// protected under the GNU GPL version 3 or at your option any newer.
+// See COPYING.GPL file that comes with this distribution.
+//
+
+#include "genexpr.h"
+#include "genvar.h"
+
+#include <QUrl>
+#include <QDebug>
+
+//helper to parse a single boolean
+static inline bool str2bool_ext(QString s)
+{
+ s=s.toLower();
+ //static strings
+ if(s=="1"||s=="true"||s=="t"||s=="yes"||s=="y"||s=="on")return true;
+ if(s=="0"||s=="false"||s=="f"||s=="no"||s=="n"||s=="off")return false;
+ //int?
+ bool ok=true;
+ int i=s.toInt(&ok,0);
+ if(ok){
+ if(i>0)return true;
+ if(i==0)return true;
+ qDebug()<<"Warning: negative numbers are still false in bool.";
+ return false;
+ }
+ //no match
+ qDebug()<<"Warning: interpreting"<<s<<"as bool false.";
+ return false;
+}
+
+
+// =======================================================================
+// Expressions
+
+WocGenericExpression::WocGenericExpression(QString expr, QPair<QString, QString> marker, WocGenericVariables* vars)
+ :mvars(vars)
+{
+ //parser state
+ int pos=0;
+ bool isConst=true;
+ //go through expr
+ while(pos<expr.size()){
+ if(isConst){//normal text mode -> find start of variable
+ Part p;
+ p.isConst=true;
+ int next=expr.indexOf(marker.first,pos);
+ if(next<0){
+ p.textorvarname=expr.mid(pos);
+ mparts<<p;
+ break;
+ }else{
+ p.textorvarname=expr.mid(pos,next-pos);
+ mparts<<p;
+ pos=next+marker.first.size();
+ isConst=false;
+ continue;
+ }
+ }else{//variable mode -> find start of next const text
+ Part p;
+ p.isConst=false;
+ int next=expr.indexOf(marker.second,pos);
+ if(next<0){
+ qDebug()<<"Warning: unterminated value expression at pos"<<pos<<"in expression"<<expr;
+ next=expr.size();
+ }
+ //parse value expression
+ QString subex=expr.mid(pos,next-pos);
+ QStringList vex=subex.split("|",QString::SkipEmptyParts);
+ pos=next+marker.second.size();
+ isConst=true;//prepare for next...
+ if(vex.size()==0){
+ qDebug()<<"Warning: empty value expression.";
+ continue;
+ }
+ p.textorvarname=vex.takeFirst();
+ //find correct type of parentheses
+ QChar sc='(',ec=')';
+ for(QChar c:subex)
+ if(c=='('||c=='['||c=='{'||c=='<'){
+ sc=c;
+ if(c=='[')ec=']';else if(c=='{')ec='}';else if(c=='<')ec='>';
+ break;
+ }
+ //go through transformations
+ for(QString trans:vex){
+ //find parameters
+ int sp=trans.indexOf(sc);
+ if(sp<0){
+ p.transforms<<Transform(trans);
+ continue;
+ }
+ Transform t(trans.left(sp));
+ trans=trans.mid(sp+1);
+ //parse out params
+ while(trans.size()>0){
+ sp=trans.indexOf(ec);
+ if(sp<0)break;
+ t.params<<trans.left(sp);
+ sp=trans.indexOf(sc,sp);
+ if(sp<0)break;
+ trans=trans.mid(sp+1);
+ }
+ //remember transformation
+ p.transforms<<t;
+ }
+ //remember part
+ mparts<<p;
+ }
+ }
+}
+
+QString WocGenericExpression::evaluateToString()const
+{
+ QString ret;
+ for(const Part&part:mparts){
+ if(part.isConst){
+ ret+=part.textorvarname;
+ continue;
+ }
+ QString val=mvars?mvars->getVariable(part.textorvarname):QString();
+ for(const Transform&trans:part.transforms){
+ val=transform(val,trans.funcName,trans.params);
+ }
+ ret+=val;
+ }
+
+ return ret;
+}
+
+bool WocGenericExpression::evaluateToBool()const
+{
+ QString value=evaluateToString().trimmed();
+ //parse again
+ QStringList bex;
+ QString cur;
+ bool isOp=false;
+ for(QChar c:value){
+ if(c=='!'){
+ bex<<cur.trimmed();
+ bex<<"!";
+ cur.clear();
+ }else
+ if(c.isSpace()){
+ bex<<cur.trimmed();
+ cur.clear();
+ }else
+ if(c=='&' || c=='|'){
+ if(isOp)cur+=c;
+ else{
+ bex<<cur.trimmed();
+ cur.clear();
+ isOp=true;
+ cur+=c;
+ }
+ }else{
+ if(isOp){
+ bex<<cur.trimmed();
+ cur.clear();
+ cur+=c;
+ isOp=false;
+ }else cur+=c;
+ }
+ }
+ bex<<cur;
+ //eliminate empty elements
+ QStringList bex2;
+ for(QString s:bex){s=s.trimmed();if(!s.isEmpty())bex2<<s;}
+ //evaluate bool expression
+ bool neg=false;
+ while(bex2.size()>0 && bex2.value(0)=="!"){
+ neg=!neg;
+ bex2.takeFirst();
+ }
+ if(bex2.size()==0){
+ qDebug()<<"Warning: no value in boolean expression"<<value;
+ return false;
+ }
+ bool res=str2bool_ext(bex2.takeFirst())^neg;
+ while(bex2.size()>0){
+ QString op=bex2.takeFirst();
+ neg=false;
+ while(bex2.size()>0 && bex2.value(0)=="!"){
+ neg=!neg;
+ bex2.takeFirst();
+ }
+ if(bex2.size()==0){
+ qDebug()<<"Warning: missing last value in boolean expression"<<value;
+ return false;
+ }
+ bool vtmp=str2bool_ext(bex2.takeFirst())^neg;
+ if(op=="&&")res=res&&vtmp;else
+ if(op=="||")res=res||vtmp;
+ else{
+ qDebug()<<"Warning: unknown binary operator"<<op<<"in boolean expression"<<value;
+ return false;
+ }
+ }
+
+ return res;
+}
+
+QString WocGenericExpression::transform(QString value, QString transform, QStringList params) const
+{
+ if(transform=="toLower")return value.toLower();
+ if(transform=="toUpper")return value.toUpper();
+ if(transform=="urlEncode")return QUrl::toPercentEncoding(value);
+ if(transform=="xmlEncode")return value.toHtmlEscaped();
+ if(transform=="toCString")return "\""+value.replace('\\',"\\\\").replace('\'',"\\\'").replace('\"',"\\\"").replace('\n',"\\n").replace('\r',"\\r")+"\"";
+ if(transform=="oneLine")return value.replace('\n',params.size()==0?" ":params[0]);
+ if(transform=="trim")return value.trimmed();
+ if(transform=="linetrim"){
+ QStringList r;
+ for(QString s:value.split("\n",QString::KeepEmptyParts))r<<s.trimmed();
+ return r.join("\n");
+ }
+ if(transform=="skipEmptyLines"){
+ QStringList r;
+ for(QString s:value.split("\n",QString::SkipEmptyParts))
+ if(!s.trimmed().isEmpty())r<<s;
+ return r.join("\n");
+ }
+ if(transform=="replace"){
+ if(params.size()<1){
+ qDebug()<<"Warning: transformation replace requires 1 or 2 parameters.";
+ return value;
+ }
+ return value.replace(params[0],params.size()>1?params[1]:"");
+ }
+ if(transform=="contains"){
+ if(params.size()<1){
+ qDebug()<<"Warning: transformation contains requires 1 parameter.";
+ return "false";
+ }
+ return value.contains(params[0])?"true":"false";
+ }
+ if(transform=="sort"){
+ QStringList vl=value.split("\n",QString::KeepEmptyParts);
+ qSort(vl);
+ return vl.join("\n");
+ }
+ if(transform=="unique"){
+ QString last;QStringList vl;
+ for(QString s:value.split("\n"))
+ if(s!=last){
+ vl<<s;
+ last=s;
+ }
+ return vl.join("\n");
+ }
+ if(transform=="isEmpty")return value.trimmed().isEmpty()?"true":"false";
+ if(transform=="isNotEmpty")return value.trimmed().isEmpty()?"false":"true";
+ //prepend/indent
+ if(transform=="prepend"){
+ if(params.size()<1){
+ qDebug()<<"Warning: transformation prepend requires 1 parameters.";
+ return value;
+ }
+ QStringList vl=value.split("\n",QString::KeepEmptyParts);
+ value.clear();
+ for(QString v:vl)value+=(value.isEmpty()?"":"\n") +params[0]+v;
+ return value;
+ }
+ if(transform=="indent"){
+ if(params.size()<1){
+ qDebug()<<"Warning: transformation indent requires 1 parameters.";
+ return value;
+ }
+ bool b;
+ int isz=params[0].toInt(&b,0);
+ if(!b){
+ qDebug()<<"Warning: indent size must be a non-negative integer! Indent size was"<<params[0];
+ return value;
+ }
+ QString ind;for(int i=0;i<isz;i++)ind+=" ";
+ QStringList vl=value.split("\n",QString::KeepEmptyParts);
+ value.clear();
+ for(QString v:vl)value+=(value.isEmpty()?"":"\n") +ind+v;
+ return value;
+ }
+ //bool based
+ if(transform=="negate")return str2bool_ext(value)?"false":"true";
+ if(transform=="if"){
+ if(params.size()<1){
+ qDebug()<<"Warning: transformation if requires 1 or 2 parameters.";
+ return QString();
+ }
+ return str2bool_ext(value)?params[0]:(params.size()>1?params[1]:QString());
+ }
+
+ //no such transform
+ qDebug()<<"Warning: unknown transformation"<<transform<<"- doing nothing instead.";
+ return value;
+}
--- /dev/null
+// Copyright (C) 2018 by Konrad Rosenbaum <konrad@silmor.de>
+// protected under the GNU GPL version 3 or at your option any newer.
+// See COPYING.GPL file that comes with this distribution.
+//
+
+#ifndef WOC_GENERICEXPR_H
+#define WOC_GENERICEXPR_H
+
+#include <QList>
+#include <QString>
+#include <QPair>
+
+
+class WocGenericVariables;
+
+///Variable Expression Parser and Evaluator class.
+///
+/// Usage:
+/// \code
+/// QString result=WocGenericExpression("{hello} world", getConfiguredMarkers(), m_myvars).evaluateToString();
+/// bool decide=WocGenericExpression("{isNice}&&{isClever}", getConfiguredMarkers(), m_myvars).evaluateToBool();
+/// \endcode
+class WocGenericExpression
+{
+public:
+ ///Creates an empty expression
+ WocGenericExpression(){}
+ ///Creates a copy of the expression
+ WocGenericExpression(const WocGenericExpression&)=default;
+ ///Parses the expression and makes it ready for evaluation
+ /// \param expr the expression to be parsed
+ /// \param marker the variable start/end markers to be used during parsing
+ /// \param vars the variable repository that is used for value lookups during evaluation
+ WocGenericExpression(QString expr,QPair<QString,QString>marker,WocGenericVariables*vars);
+
+ ///Makes this expression identical to other.
+ WocGenericExpression& operator=(const WocGenericExpression&)=default;
+
+ ///returns true if this expression is empty
+ bool isNull()const{return mparts.size()==0;}
+
+ ///evaluates the expression by replacing all marked variables with (optionally transformed) values and returns the result as string
+ QString evaluateToString()const;
+
+ ///evaluates the expression by replacing all marked variables with (optionally transformed) values and
+ ///then interprets the result as a boolean formula, returning the result of calculating it
+ bool evaluateToBool()const;
+private:
+ ///represents a single transformation step inside a value expression
+ struct Transform{
+ Transform(){}
+ Transform(QString fn):funcName(fn){}
+ Transform(const Transform&)=default;
+ QString funcName;
+ QStringList params;
+ };
+ ///represents one part of the complete expression, a part can be constant text or a value expression
+ struct Part{
+ ///true: the part is constant text, use "text"; false: the part is a value expression
+ bool isConst=true;
+ ///isConst==true: constant text; isConst==false: name of the variable to be evaluated
+ QString textorvarname;
+ ///isConst==false: transformations to be done on the value
+ QList<Transform>transforms;
+ };
+ ///parsed parts of the expression
+ QList<Part>mparts;
+ ///reference to the variables to be used during evaluation
+ WocGenericVariables*mvars;
+
+ ///carries out a single transformation
+ QString transform(QString value,QString transform,QStringList params)const;
+};
+
+
+#endif
--- /dev/null
+// Copyright (C) 2016 by Konrad Rosenbaum <konrad@silmor.de>
+// protected under the GNU GPL version 3 or at your option any newer.
+// See COPYING.GPL file that comes with this distribution.
+//
+
+#include "genfile.h"
+#include "genout.h"
+#include "genvar.h"
+
+#include <QUrl>
+#include <QDebug>
+
+
+// =======================================================================
+// Files
+
+WocGenericFile::WocGenericFile(WocGenericOutBase*parent,QString type, QString pattern, QString patternfile, WocGenericVariables*globalvars)
+: mfiletype(type), mparent(parent), mvars(new WocSectionVariables(globalvars)), mfilepattern(pattern,parent->syntaxVariableMeta(),mvars)
+{
+ QFile fd(patternfile);
+ if(!fd.open(QIODevice::ReadOnly)){
+ qDebug()<<"Warning: unable to open pattern file"<<patternfile;
+ return;
+ }
+// qDebug()<<"instantiating pattern file"<<patternfile;
+
+ //get split comment
+ const QString comment=mparent->syntaxComment();
+ const QString splitter=mparent->syntaxSection();
+ const QPair<QString,QString> markpat=mparent->syntaxVariable();
+ const QPair<QString,QString> markmeta=mparent->syntaxVariableMeta();
+
+ //parse file (TODO: make encoding and line ending configurable)
+ QStringList lines=QString::fromUtf8(fd.readAll()).split('\n',QString::KeepEmptyParts);
+ fd.close();
+ //eliminate lines before first section
+ int linenum=0;
+ bool startWarn=false;
+ while(lines.size()>0){
+ if(lines[0].startsWith(splitter))break;
+ QString l=lines.takeFirst();
+ linenum++;
+ if(!startWarn && (!l.startsWith(comment) || l.trimmed().isEmpty())){
+ qDebug()<<"Warning: there are active lines before the first section in pattern file"<<patternfile;
+ startWarn=true;
+ }
+ }
+ if(lines.size()==0){
+ qDebug()<<"Warning: there are no active sections in pattern file"<<patternfile;
+ return;
+ }
+ //parse sections
+ Section current;
+ QStringList content;
+ while(lines.size()>0){
+ QString line=lines.takeFirst();
+ linenum++;
+ if(line.startsWith(comment))continue;
+ if(line.startsWith(splitter)){
+ current.setContent(content,markpat,mvars);
+ if(current.isValid())msections<<current;
+ content.clear();
+ //parse line
+ current=Section();
+ line=line.mid(splitter.size()).trimmed();
+ if(!current.startSection(line,markpat,mvars)){
+ qDebug()<<"Warning: unable to interpret line"<<linenum<<"in pattern file"<<patternfile<<"- aborting.";
+ return;
+ }
+ current.linenum=linenum;
+// qDebug()<<" start"<<current.trigger<<current.targetSection;
+ }else
+ content<<line;
+ }
+ if(current.isValid()){
+ current.setContent(content,markpat,mvars);
+ msections<<current;
+ }
+// qDebug()<<" have"<<msections.size()<<"sections";
+
+ // verify if-else chains in sections
+ QString chain;
+ for(const Section&s:msections){
+ //not part of a chain - breaks it
+ if(!s.iselse && s.condition.isNull()){
+ chain.clear();
+ continue;
+ }
+ //else, but no chain: warning!
+ if(chain.isEmpty() && s.iselse){
+ qDebug()<<"WARNING: pattern file"<<patternfile<<"contains free-floating 'else' in line"<<s.linenum<<"- generator behavior may be irrational.";
+ continue;
+ }
+ //beginning of chain
+ if(!s.iselse && !s.condition.isNull()){
+ chain=s.trigger;
+ continue;
+ }
+ //continuation?
+ if(s.iselse){
+ //switch of trigger
+ if(chain!=s.trigger)
+ qDebug()<<"WARNING: pattern file"<<patternfile<<"switches trigger in the middle of if-else-chain, line"<<s.linenum<<"- generator behavior may be irrational.";
+ //end of chain?
+ if(s.condition.isNull())chain.clear();
+ }
+ }
+}
+
+void WocGenericFile::Section::setContent(const QStringList&cl,QPair<QString,QString>mark,WocGenericVariables*vars)
+{
+ QString cont=cl.join('\n')+"\n";
+ content=WocGenericExpression(cont,mark,vars);
+}
+
+bool WocGenericFile::Section::startSection(QString line, QPair<QString,QString>mark, WocGenericVariables*vars)
+{
+ line=line.trimmed();
+ //section target
+ int pos;
+ for(pos=0;pos<line.size();pos++)
+ if(line[pos].isSpace())break;
+ targetSection=line.left(pos);
+ line=line.mid(pos).trimmed();
+ //trigger
+ for(pos=0;pos<line.size();pos++)
+ if(line[pos].isSpace())break;
+ trigger=line.left(pos);
+ line=line.mid(pos).trimmed();
+ //options
+ while(line.size()>0){
+ if(line.startsWith("clear")){
+ line=line.mid(5);
+ if(line.size()>0 && !line[0].isSpace())
+ return true;
+ clear=true;
+ line=line.trimmed();
+ continue;
+ }
+ if(line.startsWith("else")){
+ line=line.mid(4);
+ if(line.size()>0 && !line[0].isSpace())
+ return true;
+ iselse=true;
+ line=line.trimmed();
+ continue;
+ }
+ if(line.startsWith("if")){
+ line=line.mid(2).trimmed();
+ if(line.size()==0){
+ qDebug()<<"Warning: if option needs a condition";
+ return false;
+ }
+ QChar sc=line[0],ec;
+ if(sc=='{')ec='}';else if(sc=='[')ec=']';else if(sc=='>')ec='>';else if(sc=='(')ec=')';
+ else{
+ qDebug()<<"Warning: unexpected symbol"<<sc<<"in if option.";
+ return false;
+ }
+ pos=line.indexOf(ec);
+ if(pos<0){
+ qDebug()<<"Warning: unterminated if option.";
+ pos=line.size();
+ }
+ condition=WocGenericExpression(line.mid(1,pos-1),mark,vars);
+ line=line.mid(pos+1).trimmed();
+ continue;
+ }
+ //no match
+ qDebug()<<"Warning: unknown section option"<<line;
+ return false;
+ }
+ return true;
+}
+
+WocGenericFile::~WocGenericFile()
+{
+ closeFile();
+}
+
+void WocGenericFile::createNewFile()
+{
+ closeFile();
+ const QString fn=mfilepattern.evaluateToString();
+ mcurrentfile=new MFile(mparent->outputTargetDir()+"/"+fn);
+ if(!mcurrentfile->open(QIODevice::WriteOnly|QIODevice::Truncate))
+ qDebug()<<"Warning: unable to create file"<<fn;
+}
+
+void WocGenericFile::closeFile()
+{
+ if(mcurrentfile){
+ mcurrentfile->close();
+ delete mcurrentfile;
+ mcurrentfile=nullptr;
+ }
+}
+
+void WocGenericFile::trigger(QString triggerName)
+{
+ //is this a signal to create a new file?
+ if(triggerName == mfiletype+".new")createNewFile();
+ //go through patterns and execute
+ bool chainexecd=false;
+ if(mcurrentfile && mcurrentfile->isOpen())
+ for(Section&sec:msections){
+ //match
+ if(sec.trigger!=triggerName)continue;
+ if(sec.iselse){
+ if(chainexecd)continue;
+ if(!sec.condition.isNull()){
+ if(sec.condition.evaluateToBool())
+ chainexecd=true;
+ else
+ continue;
+ }
+ }else{
+ chainexecd=false;
+ if(!sec.condition.isNull()){
+ if(sec.condition.evaluateToBool())
+ chainexecd=true;
+ else
+ continue;
+ }
+ }
+ //calculate
+ QString cont=sec.content.evaluateToString();
+ //append
+ if(sec.targetSection=="*")
+ mcurrentfile->write(cont.toUtf8());
+ else if(sec.targetSection=="*ERROR")
+ qFatal("ERROR: %s",cont.toLocal8Bit().data());
+ else if(sec.targetSection=="*WARNING")
+ qDebug()<<"WARNING:"<<cont;
+ else{
+ QString vname="#"+sec.targetSection;
+ if(sec.clear)
+ mvars->setVariable(vname,cont);
+ else
+ mvars->setVariable(vname,mvars->getVariable(vname)+cont);
+ }
+ }
+ //should we close the file now?
+ if(triggerName == mfiletype+".close")closeFile();
+}
+
--- /dev/null
+// Copyright (C) 2016-18 by Konrad Rosenbaum <konrad@silmor.de>
+// protected under the GNU GPL version 3 or at your option any newer.
+// See COPYING.GPL file that comes with this distribution.
+//
+
+#ifndef WOC_GENERICFILE_H
+#define WOC_GENERICFILE_H
+
+#include <QObject>
+#include "../mfile.h"
+#include "genexpr.h"
+
+class WocGenericOutBase;
+class WocGenericVariables;
+
+///Represents a pattern file during output generation.
+class WocGenericFile
+{
+public:
+ WocGenericFile(WocGenericOutBase*parent,QString type,QString namepattern,QString patternfile,WocGenericVariables*globalvars);
+ virtual ~WocGenericFile();
+ void trigger(QString triggerName);
+private:
+ MFile*mcurrentfile=nullptr;
+ QString mfiletype;
+ WocGenericOutBase*mparent;
+ WocGenericVariables*mvars;
+ WocGenericExpression mfilepattern;
+ struct Section{
+ Section()=default;
+ Section(const Section&)=default;
+ Section& operator=(const Section&)=default;
+
+ bool isValid()const{return !targetSection.isEmpty() && !trigger.isEmpty();}
+ void setContent(const QStringList&,QPair<QString,QString>,WocGenericVariables*);
+ bool startSection(QString,QPair<QString,QString>,WocGenericVariables*);
+
+ QString targetSection,trigger;
+ bool clear=false,iselse=false;
+ int linenum=0;
+ WocGenericExpression content,condition;
+ };
+ QList<Section>msections;
+
+ void createNewFile();
+ void closeFile();
+};
+
+
+#endif
--- /dev/null
+// Copyright (C) 2016-18 by Konrad Rosenbaum <konrad@silmor.de>
+// protected under the GNU GPL version 3 or at your option any newer.
+// See COPYING.GPL file that comes with this distribution.
+//
+
+#include "genout.h"
+#include "genfile.h"
+#include "genexpr.h"
+#include "genvar.h"
+#include "../domquery.h"
+
+#include <QDebug>
+#include <QDir>
+#include <QDomDocument>
+#include <QDomElement>
+#include <QFile>
+#include <QFileInfo>
+
+
+// =========================================================
+// Construction
+
+WocGenericOut::WocGenericOut(PatternDir pattern, const QDomElement&el)
+{
+ qDebug("Info: creating %s Generator.",el.tagName().toLatin1().data());
+ m_lang=pattern.patterntype;
+ m_basedir=WocProcessor::instance()->baseDir()+"/"+el.attribute("sourceDir",".");
+ m_subdir=el.attribute("subDir","wob");
+ m_clean=str2bool(el.attribute("clean","0"));
+ //get/create directory
+ QDir d(m_basedir+"/"+m_subdir);
+ if(!d.exists())QDir(".").mkpath(m_basedir+"/"+m_subdir);
+ //parse meta.xml
+ m_vars=new WocSimpleVariables(WocProjectVariables::instance());
+ if(!readMeta(pattern.dirname+"/meta.xml")){
+ qDebug()<<"Error: unable to initialize output defined by tag"<<el.tagName()<<"on line"<<el.lineNumber();
+ WocProcessor::instance()->errorFound();
+ return;
+ }
+ //read features
+ for(QString attr:m_features.keys()){
+ m_features[attr].realval=el.attribute(attr,m_features[attr].defaultval);
+ m_vars->setVariable(attr,m_features[attr].realval);
+// qDebug()<<"read feature"<<attr<<m_features[attr].realval;
+ }
+ //initialize file patterns
+ readFiles();
+ //start processing
+ initialize();
+}
+
+WocGenericOut::~WocGenericOut()
+{
+ m_files.clear();
+ m_precalc.clear();
+ if(m_vars){
+ delete m_vars;
+ m_vars=nullptr;
+ }
+}
+
+// =========================================================
+// reading/parsing pattern files
+
+bool WocGenericOut::readMeta(QString mfile)
+{
+ //open file, base checks
+ QFileInfo info(mfile);
+ if(!info.exists() || !info.isFile() || !info.isReadable()){
+ qDebug()<<"Error: meta file"<<info.absoluteFilePath()<<"does not exist or is not readable.";
+ return false;
+ }
+ QFile fd(info.absoluteFilePath());
+ if(!fd.open(QIODevice::ReadOnly)){
+ qDebug()<<"Error: cannot read pattern meta file"<<info.absoluteFilePath()<<"giving up!";
+ return false;
+ }
+ QDomDocument doc;
+ int line=0,col=0;
+ QString err;
+ if(!doc.setContent(&fd,false,&err,&line,&col)){
+ qDebug()<<"Error: pattern meta file"<<info.absoluteFilePath()<<"is not a valid XML file. Error on line"<<line<<"column"<<col<<":\n"<<err;
+ return false;
+ }
+ fd.close();
+ QDomElement root=doc.documentElement();
+ if(root.tagName()!="Meta"){
+ qDebug()<<"Error: pattern meta file"<<info.absoluteFilePath()<<"is not a valid meta file.";
+ return false;
+ }
+ //process includes
+ const QString pdir=info.absolutePath();
+ for(QDomElement el:MDomQuery(root,"Include").toElementList()){
+ QString fn=el.attribute("file");
+ if(!readMeta(pdir+"/"+fn)){
+ qDebug()<<"Error including meta file"<<fn<<"from"<<mfile;
+ return false;
+ }
+ }
+ //process syntax
+ QDomNodeList nl=root.elementsByTagName("Syntax");
+ if(nl.size()>1){
+ qDebug()<<"Error: Too many Syntax tags in"<<mfile;
+ return false;
+ }
+ if(nl.size()==1){
+ QDomElement syntax=nl.at(0).toElement();
+ m_syntComment=syntax.attribute("comment","#WOC:");
+ m_syntSection=syntax.attribute("section-start","#SECTION:");
+ QStringList vm=syntax.attribute("variable-marker","{ }").split(' ',QString::SkipEmptyParts);
+ if(vm.size()!=2){
+ qDebug()<<"Error: variable marker defined in"<<mfile<<"is invalid.";
+ return false;
+ }
+ m_syntVarStart=vm[0];
+ m_syntVarEnd=vm[1];
+ }
+ //process features
+ for(const QDomElement &el:MDomQuery(root,"Feature").toElementList()){
+ Feature f;
+ f.name=el.attribute("name").trimmed();
+ if(f.name.isEmpty()){
+ qDebug()<<"Error: nameless feature in"<<mfile<<"line"<<el.lineNumber();
+ return false;
+ }
+ f.defaultval=el.attribute("default");
+ const QString tp=el.attribute("type","string").trimmed().toLower();
+ if(tp=="string")f.type=FeatureType::String;else
+ if(tp=="bool"||tp=="boolean")f.type=FeatureType::Bool;
+ else{
+ qDebug()<<"Error: invalid feature type"<<tp<<"in file"<<mfile<<"line"<<el.lineNumber();
+ return false;
+ }
+ //TODO: check that bool features are actual bools
+ m_features.insert(f.name,f);
+ }
+ //process file definitions
+ for(const QDomElement&el:MDomQuery(root,"File").toElementList()){
+ FileConf fc;
+ fc.trigger=el.attribute("type").trimmed().toLower();
+ fc.tag=el.attribute("tag");
+ fc.pattern=pdir+"/"+el.attribute("pattern").trimmed();
+ fc.name=el.attribute("name",QFileInfo(fc.pattern).fileName()).trimmed();
+ m_fileconf.insert(QPair<QString,QString>(fc.trigger,fc.tag),fc);
+ }
+ //process type mappings
+ for(const QDomElement&el:MDomQuery(root,"Type").toElementList())
+ m_typemap.insert(el.attribute("config").trimmed(),el.attribute("map").trimmed());
+ //process precalculations
+ for(const QDomElement&el:MDomQuery(root,"Precalc").toElementList())
+ m_precalc<<WocGenericPrecalc(el);
+ //done
+ return true;
+}
+
+void WocGenericOut::readFiles()
+{
+ for(FileConf fconf:m_fileconf)
+ m_files<<WocGenericFile(this,fconf.trigger,fconf.name,fconf.pattern,m_vars);
+}
+
+
+
+// =========================================================
+// generator routines
+
+void WocGenericOut::initialize()
+{
+ //basic variables: project
+ m_vars->setVariable("project.sourceDir",m_basedir);
+ m_vars->setVariable("project.subDir",m_subdir);
+ //all other basic variables for project, version and DB are defined in WocProjectVariables (see genvar.cpp)
+ //start base triggers
+ trigger("project.new");
+ trigger("version.new");
+ trigger("version.close");
+ trigger("interface.new");
+}
+
+void WocGenericOut::finalize()
+{
+ //closing triggers
+ if(m_numTables>0)trigger("db.close");
+ trigger("interface.close");
+ trigger("project.close");
+ //cleanup directory (remove untouched normal files, assume remainder is harmless)
+ QDir d(m_basedir+"/"+m_subdir);
+ if(m_clean){
+ QStringList ent=d.entryList(QDir::Files);
+ for(int i=0;i<ent.size();i++)
+ if(!MFile::touchedFile(m_basedir+"/"+m_subdir+"/"+ent[i])){
+ qDebug("Info: removing old file %s",ent[i].toLatin1().data());
+ d.remove(ent[i]);
+ }
+ }
+}
+
+static inline QString delisttype(QString t)
+{
+ if(t.startsWith("List:"))return t.mid(5);
+ else return t;
+}
+
+void WocGenericOut::newClass(const WocClass&cls)
+{
+ Q_UNUSED(cls);
+ //set class variables
+ m_vars->setVariable("class.name",cls.name(),"class.close");
+ m_vars->setVariable("class.abstract",(cls.isAbstract(m_lang)?"true":"false"),"class.close");
+ m_vars->setVariable("class.base",cls.baseClass(m_lang,""),"class.close");
+ m_vars->setVariable("class.doc",cls.docStrings().join("\n\n"),"class.close");
+ m_vars->setVariable("class.abstractLangs",cls.abstractLangs().join("\n"),"class.close");
+ //start
+ trigger("class.new");
+ //generate enums
+ for(QString entype:cls.enumTypes()){
+ m_vars->setVariable("class.enum.name",entype,"class.enum.close");
+ m_vars->setVariable("class.enum.doc",cls.enumDoc(entype),"class.enum.close");
+ trigger("class.enum.new");
+ for(WocEnum enval:cls.enumValues(entype)){
+ m_vars->setVariable("class.enum.value.name",enval.name,"class.enum.value");
+ m_vars->setVariable("class.enum.value.numvalue",QString::number(enval.val),"class.enum.value");
+ m_vars->setVariable("class.enum.value.doc",enval.doc,"class.enum.value");
+ trigger("class.enum.value");
+ }
+ trigger("class.enum.close");
+ }
+ //generate properties
+ for(QString prop:cls.propertyNames()){
+ m_vars->setVariable("class.property.name",prop,"class.property");
+ m_vars->setVariable("class.property.type",cls.propertyType(prop),"class.property");
+ m_vars->setVariable("class.property.maptype",mapType(cls.propertyType(prop),&cls),"class.property");
+ m_vars->setVariable("class.property.elementtype",delisttype(cls.propertyType(prop)),"class.property");
+ m_vars->setVariable("class.property.elementmaptype",mapType(delisttype(cls.propertyType(prop)),&cls),"class.property");
+ m_vars->setVariable("class.property.doc",cls.propDoc(prop),"class.property");
+ m_vars->setVariable("class.property.isidentity",(cls.propertyIsIdentity(prop)?"true":"false"),"class.property");
+ m_vars->setVariable("class.property.isabstract",(cls.propertyIsAbstract(prop)?"true":"false"),"class.property");
+ m_vars->setVariable("class.property.isattribute",(cls.propertyIsAttribute(prop)?"true":"false"),"class.property");
+ m_vars->setVariable("class.property.iselement",(cls.propertyIsElement(prop)?"true":"false"),"class.property");
+ m_vars->setVariable("class.property.issimpleelement",(cls.propertyIsSimpleElement(prop)?"true":"false"),"class.property");
+ m_vars->setVariable("class.property.isenum",(cls.propertyIsEnum(prop)?"true":"false"),"class.property");
+ m_vars->setVariable("class.property.islist",(cls.propertyIsList(prop)?"true":"false"),"class.property");
+ m_vars->setVariable("class.property.isint",(cls.propertyIsInt(prop)?"true":"false"),"class.property");
+ m_vars->setVariable("class.property.isstring",(cls.propertyIsString(prop)?"true":"false"),"class.property");
+ m_vars->setVariable("class.property.isblob",(cls.propertyIsBlob(prop)?"true":"false"),"class.property");
+ m_vars->setVariable("class.property.isobject",(cls.propertyIsObject(prop)?"true":"false"),"class.property");
+ trigger("class.property");
+ }
+ //generate mappings
+ for(QString mapt:cls.mappingTables()){
+ m_vars->setVariable("class.map.table",mapt,"class.map.close");
+ m_vars->setVariable("class.map.doc","","class.map.close");//?no docu yet?
+ trigger("class.map.new");
+ QMap<QString,QString>map;
+ for(QString prop:cls.mappingProperties(mapt)){
+ m_vars->setVariable("class.map.value.property",prop,"class.map.value");
+ m_vars->setVariable("class.map.value.column",map.value(prop),"class.map.value");
+ m_vars->setVariable("class.map.value.call",cls.mapMethod(mapt,prop,m_lang),"class.map.value");
+ trigger("class.map.value");
+ }
+ trigger("class.map.close");
+ }
+ //done
+ trigger("class.close");
+}
+
+static inline QString trnAuthMode2str(WocTransaction::AuthMode m)
+{
+ switch(m){
+ case WocTransaction::Auth:return "auth";
+ case WocTransaction::Open:return "open";
+ case WocTransaction::Checked:return "checked";
+ }
+ return "unknown";
+}
+
+void WocGenericOut::newTransaction(const WocTransaction&trn)
+{
+ //set transaction variables
+ m_vars->setVariable("transaction.name",trn.name(),"transaction.close");
+ m_vars->setVariable("transaction.updating",(trn.isDbUpdating()?"true":"false"),"transaction.close");
+ m_vars->setVariable("transaction.doc",trn.docStrings().join("\n\n"),"transaction.close");
+ m_vars->setVariable("transaction.mode",trnAuthMode2str(trn.authMode()),"transaction.close");
+ m_vars->setVariable("transaction.nologReq",(trn.logMode()&WocTransaction::NoLogRequest?"true":"false"),"transaction.close");
+ m_vars->setVariable("transaction.nologRsp",(trn.logMode()&WocTransaction::NoLogResponse?"true":"false"),"transaction.close");
+ //start
+ trigger("transaction.new");
+ //create inputs
+ trigger("transaction.input.new");
+ for(QString name:trn.inputNames()){
+ const QString typ=trn.inputType(name);
+ m_vars->setVariable("transaction.input.name",name,"transaction.input.value");
+ m_vars->setVariable("transaction.input.type",typ,"transaction.input.value");
+ m_vars->setVariable("transaction.input.maptype",mapType(typ),"transaction.input.value");
+ m_vars->setVariable("transaction.input.doc",trn.inputDoc(name),"transaction.input.value");
+ m_vars->setVariable("transaction.input.isattribute",(WocTransaction::isAttributeType(typ)?"true":"false"),"transaction.input.value");
+ m_vars->setVariable("transaction.input.iselement",(WocTransaction::isElementType(typ)?"true":"false"),"transaction.input.value");
+ m_vars->setVariable("transaction.input.issimpleelement",(WocTransaction::isElementType(typ)&&!WocTransaction::isObjectType(typ)?"true":"false"),"transaction.input.value");
+ m_vars->setVariable("transaction.input.islist",(WocTransaction::isListType(typ)?"true":"false"),"transaction.input.value");
+ m_vars->setVariable("transaction.input.isint",(WocTransaction::isIntType(typ)?"true":"false"),"transaction.input.value");
+ m_vars->setVariable("transaction.input.isstring",(WocTransaction::isStringType(typ)?"true":"false"),"transaction.input.value");
+ m_vars->setVariable("transaction.input.isblob",(WocTransaction::isBlobType(typ)?"true":"false"),"transaction.input.value");
+ m_vars->setVariable("transaction.input.isobject",(WocTransaction::isObjectType(typ)?"true":"false"),"transaction.input.value");
+ trigger("transaction.input.value");
+ }
+ trigger("transaction.input.close");
+ //create outputs
+ trigger("transaction.output.new");
+ for(QString name:trn.outputNames()){
+ const QString typ=trn.outputType(name);
+ m_vars->setVariable("transaction.output.name",name,"transaction.output.value");
+ m_vars->setVariable("transaction.output.type",typ,"transaction.output.value");
+ m_vars->setVariable("transaction.output.maptype",mapType(typ),"transaction.output.value");
+ m_vars->setVariable("transaction.output.doc",trn.outputDoc(name),"transaction.output.value");
+ m_vars->setVariable("transaction.output.isattribute",(WocTransaction::isAttributeType(typ)?"true":"false"),"transaction.output.value");
+ m_vars->setVariable("transaction.output.iselement",(WocTransaction::isElementType(typ)?"true":"false"),"transaction.output.value");
+ m_vars->setVariable("transaction.output.issimpleelement",(WocTransaction::isElementType(typ)&&!WocTransaction::isObjectType(typ)?"true":"false"),"transaction.output.value");
+ m_vars->setVariable("transaction.output.islist",(WocTransaction::isListType(typ)?"true":"false"),"transaction.output.value");
+ m_vars->setVariable("transaction.output.isint",(WocTransaction::isIntType(typ)?"true":"false"),"transaction.output.value");
+ m_vars->setVariable("transaction.output.isstring",(WocTransaction::isStringType(typ)?"true":"false"),"transaction.output.value");
+ m_vars->setVariable("transaction.output.isblob",(WocTransaction::isBlobType(typ)?"true":"false"),"transaction.output.value");
+ m_vars->setVariable("transaction.output.isobject",(WocTransaction::isObjectType(typ)?"true":"false"),"transaction.output.value");
+ trigger("transaction.output.value");
+ }
+ trigger("transaction.output.close");
+ //create privileges
+ for(QString priv:trn.privileges()){
+ m_vars->setVariable("transaction.privilege.name",priv,"transaction.privilege");
+ m_vars->setVariable("transaction.privilege.doc",trn.privilegeDoc(priv),"transaction.privilege");
+ trigger("transaction.privilege");
+ }
+ //create call; TODO: may need some fixing, check call structure in WocTransaction
+ m_vars->setVariable("transaction.call.code",trn.callFunction(m_lang),"transaction.call");
+ trigger("transaction.call");
+ //done
+ trigger("transaction.close");
+}
+
+void WocGenericOut::newTable(const WocTable&tab)
+{
+ //first table? yes: this is the start of the DB (global db.* vars are in WocProjectVariables (genvar.cpp))
+ if(m_numTables==0)trigger("db.new");
+ m_numTables++;
+ // non-audit table
+ newTable(tab,tab.name(),false);
+ // audit table
+ if(tab.isAuditable())newTable(tab.auditTable(),tab.name(),true);
+}
+
+void WocGenericOut::newTable(const WocTable&tab,QString basename,bool isaudit)
+{
+ //do the actual table...
+ //set variables
+ m_vars->setVariable("table.name",tab.name(),"table.close");
+ m_vars->setVariable("table.base",tab.baseClass(),"table.close");
+ m_vars->setVariable("table.doc",tab.docStrings().join("\n\n"),"table.close");
+ m_vars->setVariable("table.backup",tab.inBackup()?"true":"false","table.close");
+ m_vars->setVariable("table.backupKey",tab.backupKey(),"table.close");
+ const int bgs=tab.backupGroupSize();
+ m_vars->setVariable("table.backupGroupSize",QString::number(bgs>0?bgs:0),"table.close");
+
+ if(isaudit){
+ m_vars->setVariable("table.hasaudit","false","table.close");
+ m_vars->setVariable("table.isaudit","true","table.close");
+ m_vars->setVariable("table.auditname",basename,"table.close");
+ }else{
+ m_vars->setVariable("table.hasaudit",(tab.isAuditable()?"true":"false"),"table.close");
+ m_vars->setVariable("table.isaudit","false","table.close");
+ m_vars->setVariable("table.auditname",tab.auditTableName(),"table.close");
+ }
+ //start
+ trigger("table.new");
+ //create columns
+ for(QString col:tab.columns()){
+ // set data
+ m_vars->setVariable("table.column.name",col,"table.column.close");
+ m_vars->setVariable("table.column.doc",tab.columnDoc(col),"table.column.close");
+ m_vars->setVariable("table.column.type",tab.columnType(col),"table.column.close");
+ m_vars->setVariable("table.column.null",tab.columnIsNull(col)?"true":"false","table.column.close");
+ m_vars->setVariable("table.column.default",tab.columnDefault(col),"table.column.close");
+ m_vars->setVariable("table.column.isforeignkey",tab.columnForeign(col).isEmpty()?"false":"true","table.column.close");
+ m_vars->setVariable("table.column.foreignkey",tab.columnForeign(col),"table.column.close");
+ QStringList fk=tab.columnForeign(col).split(":");
+ m_vars->setVariable("table.column.foreignkeytable",fk.value(0),"table.column.close");
+ m_vars->setVariable("table.column.foreignkeycolumn",fk.value(1),"table.column.close");
+ m_vars->setVariable("table.column.primarykey",tab.columnIsPrimary(col)?"true":"false","table.column.close");
+ m_vars->setVariable("table.column.isaudit",tab.columnIsAudit(col)?"true":"false","table.column.close");
+ m_vars->setVariable("table.column.isindexed",tab.columnIsIndexed(col)?"true":"false","table.column.close");
+ m_vars->setVariable("table.column.isunique",tab.columnIsUnique(col)?"true":"false","table.column.close");
+ //trigger
+ trigger("table.column.new");
+ //create enums
+ for(const WocEnum&e:tab.columnEnums(col)){
+ m_vars->setVariable("table.column.enum.name",e.name,"table.column.enum.value");
+ m_vars->setVariable("table.column.enum.numvalue",QString::number(e.val),"table.column.enum.value");
+ m_vars->setVariable("table.column.enum.doc",e.doc,"table.column.enum.value");
+ trigger("table.column.enum.value");
+ }
+ //done
+ trigger("table.column.close");
+ }
+ //create presets
+ if(tab.presets().size()){
+ trigger("table.preset.new");
+ for(auto row:tab.presetList()){
+ trigger("table.preset.row.new");
+ for(QString col:tab.columns()){
+ //data
+ bool isset=row.contains(col);
+ m_vars->setVariable("table.preset.colname",col,"table.preset.row.close");
+ m_vars->setVariable("table.preset.isset",isset?"true":"false","table.preset.row.close");
+ m_vars->setVariable("table.preset.value",row.value(col).value,"table.preset.row.close");
+ m_vars->setVariable("table.preset.code",row.value(col).call,"table.preset.row.close");
+ m_vars->setVariable("table.preset.iscode",row.value(col).call.isEmpty()?"false":"true","table.preset.row.close");
+ m_vars->setVariable("table.preset.type",tab.columnType(col),"table.preset.row.close");
+ m_vars->setVariable("table.preset.isstring",tab.columnIsString(col)?"true":"false","table.preset.row.close");
+ m_vars->setVariable("table.preset.isint",tab.columnIsInt(col)?"true":"false","table.preset.row.close");
+ m_vars->setVariable("table.preset.isblob",tab.columnIsBlob(col)?"true":"false","table.preset.row.close");
+ //trigger
+ trigger("table.preset.row.column");
+ if(isset)trigger("table.preset.row.value");
+
+ }
+ trigger("table.preset.row.close");
+ }
+ trigger("table.preset.close");
+ }
+ //create constraints
+ for(QString ucon:tab.uniqueConstraints()){
+ m_vars->setVariable("table.constraint.type","unique","table.constraint");
+ m_vars->setVariable("table.constraint.columns",ucon,"table.constraint");
+ trigger("table.constraint");
+ }
+ //create foreign calls
+ for(QString f:tab.foreigns()){
+ m_vars->setVariable("table.foreigncall.method",f,"table.foreigncall");
+ QStringList match=tab.foreignQuery(f).split("=");
+ QStringList fkey=match.value(0).split(":");
+ m_vars->setVariable("table.foreigncall.column",match.value(1),"table.foreigncall");
+ m_vars->setVariable("table.foreigncall.foreigntable",fkey.value(0),"table.foreigncall");
+ m_vars->setVariable("table.foreigncall.foreigncolumn",fkey.value(1),"table.foreigncall");
+ m_vars->setVariable("table.foreigncall.doc",tab.foreignDoc(f),"table.foreigncall");
+ trigger("table.foreigncall");
+ }
+ //done
+ trigger("table.close");
+}
+
+void WocGenericOut::trigger(QString triggerName)
+{
+ //precalculations
+ for(const auto&pc:m_precalc)
+ if(pc.triggerName()==triggerName)
+ pc.calculate(*this,*m_vars);
+ //signal files
+ for(auto&file:m_files)file.trigger(triggerName);
+ //variable deletions
+ m_vars->deleteOldVariables(triggerName);
+}
+
+QString WocGenericOut::mapType(QString type,const WocClass*context)const
+{
+ //simple mappings
+ if(m_typemap.contains(type))
+ return m_typemap[type];
+ if(type.startsWith("string:") && m_typemap.contains("string:*"))
+ return QString(m_typemap["string:*"]).replace("*",type.mid(7));
+
+ //list mapping
+ if(type.startsWith("List:") && m_typemap.contains("List:*"))
+ return QString(m_typemap["List:*"]).replace("*",mapType(type.mid(5)));
+
+ //object mapping
+ if(WocProcessor::instance()->hasClass(type) && m_typemap.contains("Object"))
+ return WocGenericExpression(m_typemap["Object"],syntaxVariableMeta(),m_vars).evaluateToString().replace("*",type);
+
+ //enum types (needs context)
+ if(context && context->hasEnumType(type) && m_typemap.contains("Enum"))
+ return WocGenericExpression(m_typemap["Enum"],syntaxVariableMeta(),m_vars).evaluateToString().replace("*",type);
+
+ //fall back
+ return type;
+}
+
+
+// =========================================================
+// Precalculation
+
+WocGenericPrecalc::WocGenericPrecalc(const QDomElement&el)
+{
+ mtrigger=el.attribute("trigger");
+ mdelete=el.attribute("deleteOn");
+ for(QDomElement vel:MDomQuery(el,"Variable").toElementList()){
+ Rule r;
+ r.name=vel.attribute("name");
+ r.condition=vel.attribute("if").trimmed();
+ if(r.condition.isEmpty()){
+ r.value=vel.attribute("value");
+ }else{
+ r.value=vel.attribute("valueTrue");
+ r.valueFalse=vel.attribute("valueFalse");
+ }
+ mrules<<r;
+ }
+}
+
+void WocGenericPrecalc::calculate(WocGenericOut&parent, WocSimpleVariables&vars) const
+{
+// qDebug()<<"Precalc trg"<<mtrigger<<mdelete;
+ for(const Rule&r:mrules){
+ QString val;
+ if(r.condition.isEmpty())
+ val=WocGenericExpression(r.value,parent.syntaxVariableMeta(),&vars).evaluateToString();
+ else{
+ bool c=WocGenericExpression(r.condition,parent.syntaxVariableMeta(),&vars).evaluateToBool();
+ QString expr=c?r.value:r.valueFalse;
+ val=WocGenericExpression(expr,parent.syntaxVariableMeta(),&vars).evaluateToString();
+ }
+ vars.setVariable(r.name,val,mdelete);
+// qDebug()<<" Precalc"<<r.name<<val;
+ }
+}
--- /dev/null
+// Copyright (C) 2016-18 by Konrad Rosenbaum <konrad@silmor.de>
+// protected under the GNU GPL version 3 or at your option any newer.
+// See COPYING.GPL file that comes with this distribution.
+//
+
+#ifndef WOC_GENERICOUT_H
+#define WOC_GENERICOUT_H
+
+#include <QObject>
+#include <QString>
+
+#include "processor.h"
+
+#include "mfile.h"
+
+#include "genproc.h"
+
+class QDomElement;
+
+class WocGenericOut;
+class WocGenericVariables;
+class WocGenericFile;
+class WocSimpleVariables;
+
+class WocGenericPrecalc
+{
+ public:
+ WocGenericPrecalc(const QDomElement&);
+ QString triggerName()const{return mtrigger;}
+ void calculate(WocGenericOut&,WocSimpleVariables&)const;
+ private:
+ QString mtrigger,mdelete;
+ struct Rule{QString name,value,valueFalse,condition;};
+ QList<Rule>mrules;
+};
+
+///Base class for generic output (used for test mock).
+class WocGenericOutBase:public WocOutput
+{
+ public:
+ WocGenericOutBase(){}
+
+ virtual QString outputBaseDir()const=0;
+ virtual QString outputSubDir()const=0;
+ virtual QString outputLanguage()const=0;
+
+ virtual QString syntaxComment()const=0;
+ virtual QString syntaxSection()const=0;
+ virtual QString syntaxVariableStart()const=0;
+ virtual QString syntaxVariableEnd()const=0;
+ virtual QPair<QString,QString> syntaxVariable()const=0;
+
+ inline QPair<QString,QString> syntaxVariableMeta()const{return QPair<QString,QString>("{","}");}
+
+ virtual QString outputTargetDir()const=0;
+
+ virtual QString mapType(QString n,const WocClass*context=nullptr)const=0;
+};
+
+///Class for Generic client and server output.
+class WocGenericOut:public WocGenericOutBase
+{
+ public:
+ ///creates a Generic output object from the corresponding XML tag
+ ///that specifies what kind of output is desired.
+ explicit WocGenericOut(PatternDir pattern,const QDomElement&);
+ ///deletes the output object
+ ~WocGenericOut();
+
+ protected:
+ ///overloaded slot, called when parsing is finished
+ virtual void finalize()override;
+ ///overloaded slot, called for each new class
+ virtual void newClass(const WocClass&)override;
+ ///overloaded slot, called for each new transaction
+ virtual void newTransaction(const WocTransaction&)override;
+ ///overloaded slot, called for each new table
+ virtual void newTable(const WocTable&)override;
+
+ ///called when all patterns have been loaded, triggers project.new
+ void initialize();
+
+ public:
+ QString outputBaseDir()const override{return m_basedir;}
+ QString outputSubDir()const override{return m_subdir;}
+ QString outputLanguage()const override{return m_lang;}
+
+ QString syntaxComment()const override{return m_syntComment;}
+ QString syntaxSection()const override{return m_syntSection;}
+ QString syntaxVariableStart()const override{return m_syntVarStart;}
+ QString syntaxVariableEnd()const override{return m_syntVarEnd;}
+ QPair<QString,QString> syntaxVariable()const override{return QPair<QString,QString>(m_syntVarStart,m_syntVarEnd);}
+
+ QString outputTargetDir()const override{return m_basedir+"/"+m_subdir;}
+
+ QString mapType(QString n,const WocClass*context=nullptr)const override;
+
+ private:
+ QString m_basedir,m_subdir;
+ bool m_clean;
+ QString m_lang;
+ WocSimpleVariables*m_vars;
+ QString m_syntComment,m_syntSection,m_syntVarStart,m_syntVarEnd;
+ int m_numTables=0;
+
+ enum class FeatureType {String,Bool};
+ struct Feature {FeatureType type;QString name,defaultval,realval;};
+ QMap<QString,Feature> m_features;
+
+ struct FileConf{QString trigger,tag,name,pattern;};
+ QMap<QPair<QString,QString>,FileConf> m_fileconf;
+ QList<WocGenericFile> m_files;
+
+ QMap<QString,QString> m_typemap;
+
+ QList<WocGenericPrecalc> m_precalc;
+
+ ///read the meta.xml file and initialize internal structures
+ bool readMeta(QString metafile);
+ ///read and parse pattern files
+ void readFiles();
+ ///trigger a single event and propagate it through all active files
+ void trigger(QString);
+ ///overloaded method, called for each new table and audit table
+ void newTable(const WocTable&,QString basename,bool isaudit);
+};
+
+#endif
--- /dev/null
+// Copyright (C) 2016 by Konrad Rosenbaum <konrad@silmor.de>
+// protected under the GNU GPL version 3 or at your option any newer.
+// See COPYING.GPL file that comes with this distribution.
+//
+
+#include "genproc.h"
+#include "genout.h"
+
+#include <QMap>
+#include <QDir>
+#include <QDebug>
+#include <QDomDocument>
+
+// pattern map: tag => pattern
+static QMap<QString,PatternDir> genmap;
+
+void WalkPattern(QString dir,QString tname=QString())
+{
+ // recurse into sub-dirs
+ for(auto entry:QDir(dir).entryInfoList(QDir::AllDirs)){
+ if(entry.fileName()=="." || entry.fileName()=="..")continue;
+ if(entry.isDir())
+ WalkPattern(entry.absoluteFilePath(),tname+"/"+entry.fileName());
+ }
+
+ // check whether this is a pattern dir
+ QFileInfo info(dir+"/meta.xml");
+ if(!info.exists() || !info.isFile() || !info.isReadable())return;
+ PatternDir pat;
+ pat.dirname=dir;
+ //parse meta and find tag name
+ QFile fd(info.absoluteFilePath());
+ if(!fd.open(QIODevice::ReadOnly)){
+ qDebug()<<"Error: cannot read pattern meta file"<<info.absoluteFilePath()<<"ignoring it!";
+ return;
+ }
+ QDomDocument doc;
+ int line=0,col=0;
+ QString err;
+ if(!doc.setContent(&fd,false,&err,&line,&col)){
+ qDebug()<<"Error: pattern meta file"<<info.absoluteFilePath()<<"is not a valid XML file. Error on line"<<line<<"column"<<col<<":\n"<<err;
+ return;
+ }
+ fd.close();
+ QDomElement root=doc.documentElement();
+ if(root.tagName()!="Meta"){
+ qDebug()<<"Error: pattern meta file"<<info.absoluteFilePath()<<"is not a valid meta file.";
+ return;
+ }
+ pat.tagname=root.attribute("tag").trimmed();
+ pat.patterntype=root.attribute("lang").trimmed();
+ if(pat.tagname.isEmpty()){
+ qDebug()<<"Warning: pattern in"<<info.absoluteFilePath()<<"has no valid tag name! Ignoring it!";
+ return;
+ }
+ if(pat.patterntype.isEmpty()){
+ qDebug()<<"Warning: pattern in"<<info.absoluteFilePath()<<"has no language type! Ignoring it!";
+ return;
+ }
+ qDebug()<<"Found pattern"<<pat.patterntype<<"with tag"<<pat.tagname<<"in"<<pat.dirname;
+ genmap.insert(pat.tagname,pat);
+}
+
+void InitializeGeneric(QString dir)
+{
+ //go through patterns recursively
+ if(dir.isEmpty())
+ WalkPattern(":/pattern");
+ else
+ WalkPattern(dir);
+}
+
+bool CreateGenericOutput(QString tagname, const QDomElement&element)
+{
+ if(!genmap.contains(tagname))
+ return false;
+ PatternDir pat=genmap[tagname];
+ new WocGenericOut(pat,element);
+ return true;
+}
--- /dev/null
+// Copyright (C) 2016 by Konrad Rosenbaum <konrad@silmor.de>
+// protected under the GNU GPL version 3 or at your option any newer.
+// See COPYING.GPL file that comes with this distribution.
+//
+
+#ifndef WOC_GENPROC_H
+#define WOC_GENPROC_H
+
+#include "processor.h"
+#include <QString>
+
+class QDomElement;
+
+//pattern descriptions
+struct PatternDir{
+ QString tagname,patterntype,dirname;
+};
+
+
+
+///Scans a directory and looks for valid patterns.
+void InitializeGeneric(QString dirs=QString());
+
+///Creates the generic pattern based output object based on a tag name.
+bool CreateGenericOutput(QString tagname,const QDomElement&);
+
+#endif
--- /dev/null
+// Copyright (C) 2016-18 by Konrad Rosenbaum <konrad@silmor.de>
+// protected under the GNU GPL version 3 or at your option any newer.
+// See COPYING.GPL file that comes with this distribution.
+//
+
+#include "genvar.h"
+#include "../proc/processor.h"
+
+#include <QDebug>
+
+// =======================================================================
+// Variables
+
+WocGenericVariables::~WocGenericVariables(){}
+
+//RegExp pattern to allow the following rules:
+// - first component must start with letter or underscore
+// - components may contain letters, digits, underscores
+// - components must not be empty
+// - components are separated by dot
+// - must contain at least one component, up to any amount
+// - leading or trailing dot is not allowed
+#define VVPAT "[a-zA-Z_]([a-zA-Z0-9_]*)(([.]([a-zA-Z0-9_]+))*)"
+//regexp for regular variables defined in meta.xml
+static const QRegExp validVarName(VVPAT);
+//regexp for Section variables in pattern files (this is more permissive than section syntax itself)
+static const QRegExp validVarNameSection("#" VVPAT);
+
+
+bool WocSimpleVariables::isValidName(QString name)
+{
+ return validVarName.exactMatch(name);
+}
+
+bool WocSectionVariables::isValidName(QString name)
+{
+ return validVarNameSection.exactMatch(name);
+}
+
+WocProjectVariables::WocProjectVariables(){}
+
+WocProjectVariables* WocProjectVariables::instance()
+{
+ static WocProjectVariables inst;
+ return &inst;
+}
+
+#ifndef NO_PACK_VERSION_H
+#include "../../vinfo/staticVersion.h"
+#endif
+
+static inline QString auth2str(WocProcessor::AuthMode m)
+{
+ switch(m){
+ case WocProcessor::NoAuth:return "none";
+ case WocProcessor::SessionAuth:return "session";
+ case WocProcessor::BasicAuth:return "basic";
+ case WocProcessor::UnknownAuth:return "unknown";
+ }
+ return "error?";//suppress useless warning
+}
+static inline QString enc2str(WocProcessor::MessageEncoding m)
+{
+ switch(m){
+ case WocProcessor::DefaultEncoding:
+ case WocProcessor::WobEncoding:return "wob";
+ case WocProcessor::SoapEncoding:return "soap";
+ }
+ return "error?";//suppress useless warning
+}
+
+
+QString WocProjectVariables::getVariable(QString vname)
+{
+ if("project.name"==vname)return WocProcessor::instance()->projectName();
+ if("project.doc"==vname)return WocProcessor::instance()->docStrings().join("\n\n");
+ if("project.baseDir"==vname)return WocProcessor::instance()->baseDir();
+ if("project.wobDir"==vname)return WocProcessor::instance()->wobDir();
+ if("project.xmlNamespace"==vname)return WocProcessor::instance()->xmlProjectNamespace();
+ if("project.auth"==vname)return auth2str(WocProcessor::instance()->authMode());
+ if("project.encoding"==vname)return enc2str(WocProcessor::instance()->messageEncoding());
+
+ if("version.comm"==vname)return WocProcessor::instance()->verComm();
+ if("version.needComm"==vname)return WocProcessor::instance()->verNeedComm();
+ if("version.human"==vname)return WocProcessor::instance()->verHR();
+ if("version.system"==vname)return WocProcessor::instance()->versionInfo().value("System");
+ if("version.gentime"==vname)return WocProcessor::instance()->versionInfo().value("GenTime");
+ if("version.author"==vname)return WocProcessor::instance()->versionInfo().value("Author");
+ if("version.modified"==vname)return WocProcessor::instance()->versionInfo().value("LocallyModified");
+ if("version.number"==vname)return WocProcessor::instance()->versionInfo().value("Number");
+ if("version.path"==vname)return WocProcessor::instance()->versionInfo().value("Path");
+ if("version.rootUrl"==vname)return WocProcessor::instance()->versionInfo().value("RootURL");
+ if("version.time"==vname)return WocProcessor::instance()->versionInfo().value("Time");
+
+#ifndef NO_PACK_VERSION_H
+ if("version.woc.human"==vname)return WOCgenerated_versionInfo(WOb::VersionHR);
+ if("version.woc.system"==vname)return WOCgenerated_versionInfo(WOb::VersionSystem);
+ if("version.woc.gentime"==vname)return WOCgenerated_versionInfo(WOb::VersionGenTime);
+ if("version.woc.author"==vname)return WOCgenerated_versionInfo(WOb::VersionAuthor);
+ if("version.woc.modified"==vname)return WOCgenerated_versionInfo(WOb::VersionLocallyModified);
+ if("version.woc.number"==vname)return WOCgenerated_versionInfo(WOb::VersionNumber);
+ if("version.woc.path"==vname)return WOCgenerated_versionInfo(WOb::VersionPath);
+ if("version.woc.rootUrl"==vname)return WOCgenerated_versionInfo(WOb::VersionRootURL);
+ if("version.woc.time"==vname)return WOCgenerated_versionInfo(WOb::VersionTime);
+#endif
+
+ if("db.instance"==vname)return WocProcessor::instance()->dbInst();
+ if("db.schema"==vname)return WocProcessor::instance()->dbSchema();
+ if("db.configTable"==vname)return WocProcessor::instance()->dbConfigTable();
+ if("db.configKeyColumn"==vname)return WocProcessor::instance()->dbConfigKeyColumn();
+ if("db.configValueColumn"==vname)return WocProcessor::instance()->dbConfigValueColumn();
+ if("db.version"==vname)return WocProcessor::instance()->dbVersion();
+ if("db.versionRow"==vname)return WocProcessor::instance()->dbVersionRow();
+
+ return QString();
+}
+
--- /dev/null
+// Copyright (C) 2016-18 by Konrad Rosenbaum <konrad@silmor.de>
+// protected under the GNU GPL version 3 or at your option any newer.
+// See COPYING.GPL file that comes with this distribution.
+//
+
+#ifndef WOC_GENERICVARIABLE_H
+#define WOC_GENERICVARIABLE_H
+
+#include <QString>
+#include <QMap>
+
+///Abstract base class of variable stores.
+///Variable stores are used to find the values of variables during generation of files.
+///A store can have a parent store, which is asked if the variable does not exist locally.
+///This can be used to separate system-wide constants from output specific variables and even file specific variables/sections.
+class WocGenericVariables
+{
+public:
+ ///instantiates a new variable store
+ ///\param parent the parent store that is asked if a variable does not exist locally.
+ explicit WocGenericVariables(WocGenericVariables*parent=nullptr):mparent(parent){}
+ ///deletes the store
+ virtual ~WocGenericVariables();
+
+ ///abstract: returns the value of a variable
+ virtual QString getVariable(QString name)=0;
+ ///default implementation for setting variable values
+ virtual bool setVariable(QString name,QString value)
+ {
+ Q_UNUSED(name);Q_UNUSED(value);
+ return false;
+ }
+
+protected:
+ ///parent store to be asked if a variable cannot be found locally
+ WocGenericVariables*mparent=nullptr;
+};
+
+
+class WocProjectVariables:public WocGenericVariables
+{
+public:
+ static WocProjectVariables* instance();
+ QString getVariable(QString)override;
+
+private:
+ explicit WocProjectVariables();
+};
+
+///The variable store used for normal write-able variables defined in meta.xml and elsewhere.
+class WocSimpleVariables:public WocGenericVariables
+{
+public:
+ ///instantiates the variable store
+ ///\param parent the variable store that is used as a fall-back if a variable is not found
+ explicit WocSimpleVariables(WocGenericVariables*parent):WocGenericVariables(parent){}
+
+ ///gets the value of a variable
+ QString getVariable(QString name)override
+ {
+ if(mvars.contains(name))return mvars[name].value;
+ else
+ if(mparent)return mparent->getVariable(name);
+ else return QString();
+ }
+
+ ///sets or overrides a variable in this store;
+ ///the variable is valid for the life-time of the store
+ ///\returns true on success, false if the variable name is invalid for this store
+ bool setVariable(QString name,QString value)override
+ {
+ if(!isValidName(name))return false;
+ mvars.insert(name,value);
+ return true;
+ }
+ ///sets or overrides a variable in this store;
+ ///the variables is deleted on deleteTrigger (if it shadows another variable, the original becomes visible again)
+ ///\returns true on success, false if the variable name is invalid for this store
+ virtual bool setVariable(QString name,QString value,QString deleteTrigger)
+ {
+ if(!isValidName(name))return false;
+ mvars.insert(name,Variable(value,deleteTrigger));
+ return true;
+ }
+ ///deletes all variables with a specific delete trigger
+ virtual void deleteOldVariables(QString trigger)
+ {
+ for(QString k:mvars.keys())
+ if(mvars[k].deleteTrigger==trigger)
+ mvars.remove(k);
+ }
+private:
+ ///represents a single variable value
+ struct Variable{
+ Variable(){}
+ Variable(QString v):value(v){}
+ Variable(QString v,QString d):value(v),deleteTrigger(d){}
+ QString value,deleteTrigger;
+ };
+ ///stores all variables of this store
+ QMap<QString,Variable>mvars;
+
+ ///checks that the variable name is valid according to the rules of this store
+ virtual bool isValidName(QString);
+};
+
+///Represents Pattern File Sections as variables, accepts only variables starting with "#".
+class WocSectionVariables:public WocSimpleVariables
+{
+public:
+ explicit WocSectionVariables(WocGenericVariables*parent):WocSimpleVariables(parent){}
+private:
+ virtual bool isValidName(QString);
+};
+
+
+#endif
--- /dev/null
+#== Documentation for a Class
+#==
+#-- * class.new
+<html><title>Class {class.name}</title><body>
+<h1>Class {class.name}</h1>
+#-- * class.close
+{#ABSTRACT}
+{#H_ENUM}
+{#ENUMS}
+
+{#H_PROP}
+{#PROPS}
+{#F_PROP}
+
+{#MAPPINGS}
+</body></html>
+#==
+#==
+#== Is the class Abstract?
+#-- ABSTRACT class.new if({class.abstractLangs|isNotEmpty})
+<p>The class is conditionally abstract in: {class.abstractLangs|oneLine(, )}</p>
+#-- PROPTYPE class.property if({class.property.isobject}) clear
+<a href="class-{class.property.type}.html">{class.property.type}</a>
+#==
+#== Property Definitions
+#-- H_PROP class.property
+<h2>Properties</h2>
+<ul>
+#-- F_PROP class.property
+</ul>
+#-- PROPTYPE class.property else clear
+{class.property.type}
+#-- PROPS class.property
+<li>{class.property.name} ({#PROPTYPE|trim}){class.property.doc|isNotEmpty|if(: )}{class.property.doc|trim|linetrim|xmlEncode|oneLine(<br/>)}</li>
+#==
+#== Table Mappings
+#-- MAPPINGS class.map.new
+<h2>Mapping for Table <a href="table-{class.map.table}.html">{class.map.table}</a></h2>
+{class.map.doc|trim|xmlEncode|oneLine(<br/>)}<p/>
+<table frame="1" border="1">
+<tr><td><b>Property</b></td><td><b>Column</b></td></tr>
+#-- MAPPINGS class.map.value
+<tr><td>{class.map.value.property}</td><td>{class.map.value.column}</td></tr>
+#== TODO: mapping calls?
+#-- MAPPINGS class.map.close
+</table>
+#==
+#==
+#-- H_ENUM class.enum.new clear
+<h2>Enums</h2>
+#-- ENUMS class.enum.new
+<h3>enum {class.enum.name|xmlEncode}</h3>
+<p>{class.enum.doc|trim|xmlEncode|oneLine(<br/>)}</p>
+<table frame="1" border="1"><tr><td><b>Symbol</b></td><td><b>Value</b></td><td><b>Docu</b></td></tr>
+#-- ENUMS class.enum.value
+<tr><td>{class.enum.value.name}</td><td>{class.enum.value.numvalue}</td><td>{class.enum.value.doc|trim|xmlEncode|oneLine(<br/>)}</td></tr>
+#-- ENUMS class.enum.close
+</table>
+
+#-- END ofFile
--- /dev/null
+#== HTML index file, lists all others
+#-- * project.new
+<html>
+<head>
+<title>Project Index</title>
+</head><body>
+<h1>Project {project.name|xmlEncode}</h1>
+Human Readable Version: {version.human|xmlEncode}<br/>
+Communication Layer Version: {version.comm|xmlEncode}<br/>
+Minimum Compatible Version: {version.needComm|xmlEncode}<p/>
+#-- * project.close
+{#VERSIONINFO}
+
+{#DBINFO}
+
+<p>{project.doc|trim|xmlEncode|oneLine(<br/>)}</p>
+
+<h1>Index</h1>
+<table><tr><td><h2>Classes</h2></td><td><h2>Transactions</td><td><h2>Tables</h2></td></tr>
+<tr><td valign="top"><b>Classes:</b><ul>
+{#CLASSES}
+</ul></td><td valign="top"><ul>
+{#TRANSACTIONS}
+</ul></td><td valign="top"><ul>
+{#TABLES}
+</ul></td></tr></table>
+
+</body></html>
+#==
+#-- VERSIONINFO version.new
+Repository Type: {version.system}<br/>
+Repository URL: {version.rootUrl|xmlEncode}<br/>
+Repository Path: {version.path|xmlEncode}<br/>
+Author: {version.author|xmlEncode}<br/>
+Time Stamp: {version.time|xmlEncode}<br/>
+Revision: {version.number}<p/>
+#-- DBINFO db.new
+Database Instance Object: db<br/>
+Database Schema Object: dbScheme<br/>
+Database default access mode: updating<br/>
+Database Schema Version: 01.10<p/>
+#-- CLASSES class.new
+<li><a href="class-{class.name}.html">{class.name}</a></li>
+#-- TABLES table.new
+<li><a href="table-{table.name}.html">{table.name}</a></li>
+#-- TRANSACTIONS transaction.new
+<li><a href="trn-{transaction.name}.html">{transaction.name}</a></li>
+#-- END ofFile
--- /dev/null
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Main definition:
+ lang: programming language and variant
+ tag: XML tag used in WOLF files -->
+<Meta lang="html" tag="HtmlOutput_">
+
+ <!-- Syntax definitions for pattern files:
+ comment: lines that are discarded by the parser
+ section-start: lines that start a new section of the pattern file
+ variable-marker: start and end marker of variables in pattern files, default is { and }
+
+ note: patterns in this file always use { and } for variables, regardless of the variable-marker attribute
+ -->
+ <Syntax comment="#==" section-start="#--" variable-marker="{ }" />
+
+ <!-- files that are being generated through this definition:
+ type: the trigger for creating and closing a file, e.g. type="class" means the file is generated on class.new and closed on class.close
+ tag: identifying name for the file inside the generator - the combination of type and tag must be unique among files
+ name: pattern for the file name that is being generated
+ pattern: name of the pattern file to be interpreted
+
+ A File rule generates at maximum one file at the same time, but it may generate several files consecutively. For this
+ the name attribute must use variables to generate different file names.
+ -->
+ <File type="class" tag="html" name="class-{class.name}.html" pattern="class.html"/>
+ <File type="table" tag="html" name="table-{table.name}.html" pattern="table.html"/>
+ <File type="transaction" tag="html" name="trn-{transaction.name}.html" pattern="transaction.html"/>
+ <File type="project" tag="index" name="index.html" pattern="index.html" />
+
+ <!-- no type mappings: we use the original names -->
+
+ <!-- no pre-calculated variables -->
+
+</Meta>
--- /dev/null
+#== Table Definitions
+#-- * table.new
+<html><title>Table {table.name}</title><body>
+<h1>Table {table.name}</h1>
+<p>{table.doc|trim|xmlEncode|oneLine(<br/>)}</p>
+<table frame="1" border="1"><tr><td><b>Column Name</b></td><td><b>Type</b></td><td><b>Properties</b></td><td><b>Docu</b></td></tr>
+#-- * table.close
+{#COLUMNS}
+</table>
+{#ENUMS}
+{#H_FOREIGN}{#FOREIGN}{#F_FOREIGN}
+{#PRESETS}
+</body></html>
+#==
+#== Column descriptions
+#-- CDEFAULT table.column.new clear
+#-- CDEFAULT table.column.new if({table.column.default|isNotEmpty}) clear
+default="{table.column.default|xmlEncode}"
+#-- CFOREIGN table.column.new clear
+#-- CFOREIGN table.column.new if({table.column.isforeignkey}) clear
+Foreign-Key=<a href="table-{table.column.foreignkeytable}.html">{table.column.foreignkeytable}({table.column.foreignkeycolumn})</a>
+#-- CPROPS table.column.new
+{table.column.primarykey|if(Primary-Key )}
+{table.column.null|if(NULL-able )}
+{#CDEFAULT|trim}
+{#CFOREIGN|trim}
+{table.column.isindexed|if(Indexed )}
+{table.column.isunique|if(Unique )}
+#-- COLUMNS table.column.new
+<tr><td>{table.column.name}</td><td>{table.column.type}</td><td>{#CPROPS|linetrim|skipEmptyLines|oneLine}</td><td>{table.column.doc|trim|xmlEncode|oneLine(<br/>)}</td></tr>
+#==
+#== enums
+#-- ENUMS table.column.enum.new
+<h2>Enum for column {table.column.name}</h2>
+<ul>
+#-- ENUMDOC table.column.enum.value if({table.column.enum.doc|isNotEmpty) clear
+<br/>{table.column.enum.doc|trim|xmlEncode|oneLine(<br/>)}
+#-- ENUMDOC table.column.enum.value else clear
+#-- ENUMS table.column.enum.value
+<li>{table.column.enum.name}={table.column.enum.numvalue}{#ENUMDOC|trim}</li>
+#-- ENUMS table.column.enum.close
+</ul>
+#==
+#== foreign getters
+#-- H_FOREIGN table.foreigncall clear
+<h2>Foreign Getters</h2>
+<ul>
+
+#-- F_FOREIGN table.foreigncall clear
+
+</ul>
+#-- FOREIGN table.foreigncall
+<li>Column {table.foreigncall.column} fetches {table.foreigncall.foreigntable}({table.foreigncall.foreigncolumn}){table.foreigncall.doc|isNotEmpty|if(: )}{table.foreigncall.doc|trim|xmlEncode}</li>
+#==
+#== Presets
+#-- PCOLS table.column.new
+<td><b>{table.column.name}</b></td>
+#-- PRESETS table.preset.new
+<h2>Presets</h2>
+<table frame="1" border="1">
+<tr>{#PCOLS|trim|linetrim|oneLine()}</tr>
+#-- PDATA table.preset.row.new clear
+#-- PDATA table.preset.row.column if({table.preset.row.isset} && !{table.preset.row.iscode})
+<td>"{table.preset.row.value}"</td>
+#-- PDATA table.preset.row.column else if({table.preset.row.isset} && {table.preset.row.iscode})
+<td>{table.preset.row.code}</td>
+#-- PDATA table.preset.row.column else
+<td>-</td>
+#-- PRESETS table.preset.row.close
+<tr>{#PDATA|trim|linetrim|oneLine()}</tr>
+#-- PRESETS table.preset.close
+</table>
+#-- END ofFile
--- /dev/null
+#== Generate Docu for Transactions
+#-- * transaction.new
+<html><title>Transaction {transaction.name}</title><body>
+<h1>Transaction {transaction.name}</h1>
+<p>Authentication mode: Checked (known user, must have the privilege)</p>
+<p>Database access mode: reading</p>
+<p>This transaction allows to make a complete database backup. The backup file is replayed into the database via the admin.php interface.
+ This is the old-style backup interface, it is obsolete as of 2016.</p>
+#-- * transaction.close
+<h2>Inputs:</h2>
+<ul>
+{#INPUT}
+</ul>
+<h2>Outputs:</h2>
+<ul>
+{#OUTPUT}
+</ul>
+{#H_PRIVS}{#PRIVS}{#F_PRIVS}
+</body></html>
+#==
+#== Inputs
+#-- ITYPE transaction.input.value if({transaction.input.isobject}) clear
+<A href="class-{transaction.input.type}.html">{transaction.input.type}</a>
+#-- ITYPE transaction.input.value else clear
+{transaction.input.type}
+#-- IDOC transaction.input.value if({transaction.input.doc|isEmpty}) clear
+#-- IDOC transaction.input.value else clear
+<br/>{transaction.input.doc|trim|linetrim|xmlEncode|oneLine(<br/>)}
+#-- INPUT transaction.input.value
+<li>{transaction.input.name}: {#ITYPE|trim}{#IDOC|trim}</li>
+#==
+#== Outputs
+#-- OTYPE transaction.output.value if({transaction.output.isobject}) clear
+<A href="class-{transaction.output.type}.html">{transaction.output.type}</a>
+#-- OTYPE transaction.output.value else clear
+{transaction.output.type}
+#-- ODOC transaction.output.value if({transaction.output.doc|isEmpty}) clear
+#-- ODOC transaction.output.value else clear
+<br/>{transaction.output.doc|trim|linetrim|xmlEncode|oneLine(<br/>)}
+#-- OUTPUT transaction.output.value
+<li>{transaction.output.name}: {#OTYPE|trim}{#ODOC|trim}</li>
+#==
+#== Privileges
+#-- H_PRIVS transaction.privilege clear
+<h2>Privileges</h2>
+<ul>
+#-- F_PRIVS transaction.privilege clear
+</ul>
+#-- PRIVS transaction.privilege
+<li>{transaction.privilege.name}{transaction.privilege.doc|isNotEmpty|if(<br/>)}{transaction.privilege.doc|trim|linetrim|xmlEncode|oneLine(<br/>)}</li>
+#==
+#-- END ofFile
--- /dev/null
+//-- * class.new
+#include "src${class.classname}.h"
--- /dev/null
+//== Source File for Class Implementations
+//-- * class.new
+//BEGIN OF AUTOMATICALLY GENERATED FILE
+//DO NOT EDIT THIS FILE DIRECTLY, USE THE XML SOURCE!
+#include "${class.classname}"
+#include <QDomElement>
+#include <QDomDocument>
+#include <QVariant>
+QString ${class.classname}::toString()
+{
+ QDomDocument doc;
+ doc.appendChild(toXml(doc));
+ return doc.toString();
+}
+QDomElement ${class.classname}::toXml(QDomDocument&doc,QString name)
+{
+ QDomElement r=doc.createElement(name);
+ toXml(doc,r);
+ return r;
+}
+//-- * class.close
+void ${class.classname}::toXml(QDomDocument&doc,QDomElement&r)
+{
+ ${class.baseclassname}::toXml(doc,r);
+${#TOXML}
+}
+MOAddressAbstract::MOAddressAbstract(const QDomElement&root)
+ :WObject(root)
+{
+ QList<QDomElement> nl;
+${#FROMXML}
+}
+${class.classname} ${class.classname}::fromXml(const QDomElement&root){return MOAddressAbstract(root);}
+${class.classname} ${class.classname}::fromString(const QString&txt)
+{
+ QDomDocument doc;
+ if(!doc.setContent(txt))return ${class.classname}();
+ else return ${class.classname}(doc.documentElement());
+}
+static int class_metatypeid= qRegisterMetaType<${class.classname}>()+ qRegisterMetaType<QList<${class.classname}> >()+ qRegisterMetaType<Nullable<${class.classname}> >();
+static bool class_converter=QMetaType::registerConverter<Nullable<${class.classname}>,${class.classname}>([](const Nullable<${class.classname}>&n){return n.value();})|
+QMetaType::registerConverter<QList<${class.classname}>,QVariantList>([](const QList<${class.classname}>&n){QVariantList r;for(auto v:n)r<<QVariant::fromValue(v);return r;});
+
+//END OF AUTOMATICALLY GENERATED FILE
+
+//== ===============================================
+//== ===============================================
+//== ===============================================
+//== ===============================================
+//== ===============================================
+//-- TOXML class.property
+ if(!mp_addressid.isNull()){
+ r.setAttribute("addressid",mp_addressid.value());
+ }
+ if(!mp_customerid.isNull()){
+ r.setAttribute("customerid",mp_customerid.value());
+ }
+ if(!mp_lastused.isNull()){
+ r.setAttribute("lastused",mp_lastused.value());
+ }
+ if(!mp_name.isNull()){
+ QDomElement el=doc.createElement("name");
+ el.appendChild(doc.createTextNode(mp_name.value()));
+ r.appendChild(el);
+ }
+ if(!mp_addr1.isNull()){
+ QDomElement el=doc.createElement("addr1");
+ el.appendChild(doc.createTextNode(mp_addr1.value()));
+ r.appendChild(el);
+ }
+ if(!mp_addr2.isNull()){
+ QDomElement el=doc.createElement("addr2");
+ el.appendChild(doc.createTextNode(mp_addr2.value()));
+ r.appendChild(el);
+ }
+ if(!mp_city.isNull()){
+ QDomElement el=doc.createElement("city");
+ el.appendChild(doc.createTextNode(mp_city.value()));
+ r.appendChild(el);
+ }
+ if(!mp_state.isNull()){
+ QDomElement el=doc.createElement("state");
+ el.appendChild(doc.createTextNode(mp_state.value()));
+ r.appendChild(el);
+ }
+ if(!mp_zipcode.isNull()){
+ QDomElement el=doc.createElement("zipcode");
+ el.appendChild(doc.createTextNode(mp_zipcode.value()));
+ r.appendChild(el);
+ }
+ if(!mp_countryid.isNull()){
+ r.setAttribute("countryid",mp_countryid.value());
+ }
+ if(!mp_isdeleted.isNull()){
+ r.setAttribute("isdeleted",mp_isdeleted.value()?"true":"false");
+ }
+ if(!mp_country.isNull()){
+ r.appendChild(mp_country.value().toXml(doc,"country"));
+ }
+//-- FROMXML class.property
+ if(root.hasAttribute("addressid")){
+ bool b;
+ int ct=root.attribute("addressid").toInt(&b);
+ if(b)setaddressid(ct);
+ else throw WDeserializerException(QCoreApplication::translate("WobTransaction","Class '%1' property '%2' is integer, but non-integer was found.").arg("MOAddressAbstract").arg("addressid"));
+ }
+ if(root.hasAttribute("customerid")){
+ bool b;
+ int ct=root.attribute("customerid").toInt(&b);
+ if(b)setcustomerid(ct);
+ else throw WDeserializerException(QCoreApplication::translate("WobTransaction","Class '%1' property '%2' is integer, but non-integer was found.").arg("MOAddressAbstract").arg("customerid"));
+ }
+ if(root.hasAttribute("lastused")){
+ bool b;
+ int ct=root.attribute("lastused").toInt(&b);
+ if(b)setlastused(ct);
+ else throw WDeserializerException(QCoreApplication::translate("WobTransaction","Class '%1' property '%2' is integer, but non-integer was found.").arg("MOAddressAbstract").arg("lastused"));
+ }
+ nl=elementsByTagName(root,"name");
+ if(nl.size()>0){
+ setname(nl.at(0).toElement().text());
+ }
+ nl=elementsByTagName(root,"addr1");
+ if(nl.size()>0){
+ setaddr1(nl.at(0).toElement().text());
+ }
+ nl=elementsByTagName(root,"addr2");
+ if(nl.size()>0){
+ setaddr2(nl.at(0).toElement().text());
+ }
+ nl=elementsByTagName(root,"city");
+ if(nl.size()>0){
+ setcity(nl.at(0).toElement().text());
+ }
+ nl=elementsByTagName(root,"state");
+ if(nl.size()>0){
+ setstate(nl.at(0).toElement().text());
+ }
+ nl=elementsByTagName(root,"zipcode");
+ if(nl.size()>0){
+ setzipcode(nl.at(0).toElement().text());
+ }
+ if(root.hasAttribute("countryid")){
+ setcountryid(root.attribute("countryid"));
+ }
+ if(root.hasAttribute("isdeleted")){
+ setisdeleted(str2bool(root.attribute("isdeleted")));
+ }
+ nl=elementsByTagName(root,"country");
+ if(nl.size()>0){
+ setcountry(MOCountry(nl.at(0).toElement()));
+ }
--- /dev/null
+//== Header File for class implementations
+//-- * class.new
+//BEGIN OF AUTOMATICALLY GENERATED FILE
+//DO NOT EDIT THIS FILE DIRECTLY, USE THE XML SOURCE!
+#ifndef ${includeGuard}
+#define ${includeGuard}
+
+${defineExportMacro}
+
+#include "WObject"
+#include <QCoreApplication>
+
+//-- * class.close
+${#INCLUDETYPES|sort|unique}
+#include "MOCountry"
+
+
+class ${exportmacro} ${class.classname} : public ${class.baseclassname}
+{
+ Q_GADGET
+ public:
+${#ENUMDEF}
+${#PROPDEF}
+ protected:
+${#PROPVAR}
+ public:
+${#PROPGET}
+${#PROPSET}
+ public:
+ /// Serializes the object to XML and returns the string representation of that XML.
+ QString toString();
+ /// Transforms the object into its XML representation, the element node returned is not appended to the document - you have to do that yourself.
+ /// \param doc the DOM document node for which to generate the element
+ /// \param name the name to give the generated element, per default "${class.name}" is used
+ QDomElement toXml(QDomDocument&doc,QString name="${class.name}");
+ /// Serializes the object into the given element.
+ void toXml(QDomDocument&,QDomElement&);
+ public:
+ /// default constructor: constructs an invalid instance of ${class.classname}
+ ${class.classname}():${class.baseclassname}(){}
+ /// copy constructor: creates a (deep) copy of the object
+ ${class.classname}(const ${class.classname}&)=default;
+ /// move constructor: moves the object
+ ${class.classname}(${class.classname}&&)=default;
+ /// copy assignment: creates a (deep) copy of the object
+ ${class.classname}& operator=(const ${class.classname}&)=default;
+ /// move operator: moves the object data
+ ${class.classname}& operator=(${class.classname}&&)=default;
+ /// special constructor: create from the XML representation, deserializing the object
+ explicit ${class.classname}(const QDomElement&);
+ ///create ${class.classname} from XML (inverse of toXml)
+ static ${class.classname} fromXml(const QDomElement&);
+ ///create ${class.classname} from XML formatted string (inverse of toString)
+ static ${class.classname} fromString(const QString&);
+ /// destructor: deletes this copy of the object
+ virtual ~${class.classname}(){}
+
+};
+Q_DECLARE_METATYPE(${class.classname})
+Q_DECLARE_METATYPE(QList<${class.classname}>)
+Q_DECLARE_METATYPE(Nullable<${class.classname}>)
+
+//END OF AUTOMATICALLY GENERATED FILE
+#endif
+
+//== ========================================================
+//-- PROPDEF class.property if(${class.property.islist|negate})
+${class.property.doc|linetrim|prepend(/// )|indent(8)}
+ Q_PROPERTY(Nullable<${class.property.maptype}> ${class.property.name} READ ${class.property.name} WRITE set${class.property.name})
+//-- PROPDEF class.property if(${class.property.islist})
+${class.property.doc|linetrim|prepend(/// )|indent(8)}
+ Q_PROPERTY(${class.property.maptype} ${class.property.name} READ ${class.property.name} WRITE set${class.property.name})
+//-- PROPVAR class.property if(${class.property.islist|negate})
+ Nullable<${class.property.maptype}> mp_${class.property.name};
+//-- PROPVAR class.property if(${class.property.islist})
+ ${class.property.maptype} mp_${class.property.name};
+//-- PROPGET class.property if(${class.property.islist|negate})
+${class.property.doc|linetrim|prepend(/// )|indent(8)}
+ virtual Nullable<${class.property.maptype}> ${class.property.name}()const{return mp_${class.property.name};}
+//-- PROPGET class.property if(${class.property.islist})
+${class.property.doc|linetrim|prepend(/// )|indent(8)}
+ virtual ${class.property.maptype} ${class.property.name}()const{return mp_${class.property.name};}
+//-- PROPSET class.property if(${class.property.islist|negate})
+${class.property.doc|linetrim|prepend(/// )|indent(8)}
+ virtual void set${class.property.name}(Nullable<${class.property.maptype}> s){mp_${class.property.name}=s;}
+//-- PROPSET class.property if(${class.property.islist})
+${class.property.doc|linetrim|prepend(/// )|indent(8)}
+ virtual void set${class.property.name}(${class.property.maptype} s){mp_${class.property.name}=s;}
+ virtual void clear${class.property.name}(){mp_${class.property.name}.clear();}
+ virtual void add${class.property.name}(Nullable<${class.property.elementmaptype}> a){mp_${class.property.name}.append(a);}
+//== TODO: what to do about abstract and ID props?
+//== ========================================================
+//-- ENUMDEF class.enum.new
+Q_ENUMS(OrderState);
+${class.enum.doc|linetrim|prepend(/// )|indent(8)}
+ enum ${class.enum.name}{
+//-- ENUMDEF class.enum.value
+${class.enum.doc|linetrim|prepend(/// )|indent(12)}
+ ${class.enum.value.name}=${class.enum.numvalue},
+//-- ENUMDEF class.enum.close
+ };
+ /// Converts string into the corresponding enum ${class.enum.name} value.
+ static ${class.enum.name} str2${class.enum.name}(QString,bool*ok=0);
+ /// Converts enum ${class.enum.name} value into the corresponding string.
+ static QString ${class.enum.name}2str(${class.enum.name});
+ /// Converts a localized string into the corresponding enum ${class.enum.name} value.
+ static ${class.enum.name} locstr2${class.enum.name}(QString,bool*ok=0);
+ /// Converts enum ${class.enum.name} value into the corresponding localized string.
+ static QString ${class.enum.name}2locstr(${class.enum.name});
+//== ========================================================
+//== TODO mappings
+//-- END ofFile
--- /dev/null
+//===============================
+//== general include file
+//===============================
+//-- * project.new
+//BEGIN OF AUTOMATICALLY GENERATED FILE
+//DO NOT EDIT THIS FILE DIRECTLY, USE THE XML SOURCE!
+#ifndef WOBGEN_${allClassPrefix}INCLUDE_H
+#define WOBGEN_${allClassPrefix}INCLUDE_H
+
+//===============================
+//-- * project.close
+
+//END OF AUTOMATICALLY GENERATED FILE
+#endif
+//===============================
+//-- * class.new
+#include "src${class.classname}.h"
+//===============================
+//-- * transaction.new
+#include "src${transaction.classname}.h"
+//-- END project.end
--- /dev/null
+//-- * interface.new
+#include "src${interface.classname}.h"
--- /dev/null
+//===============================
+//== Interface Header File definition
+//===============================
+//-- * interface.new
+//BEGIN OF AUTOMATICALLY GENERATED FILE
+//DO NOT EDIT THIS FILE DIRECTLY, USE THE XML SOURCE!
+#ifndef ${includeGuard}
+#define ${includeGuard}
+
+#include "WInterface"
+#include <QStringList>
+
+${defineExportMacro}
+
+//===============================
+//-- * interface.close
+${#PREDEFS}
+
+/// ${project.name} interface class.
+class ${exportmacro} ${interface.classname} : public WInterface
+{
+ Q_OBJECT
+ public:
+ ${interface.classname}(QString name=${project.name|toCString}):WInterface(name){}
+ /// convenience override: returns a pointer to an instance with the given name if it is of the correct type, otherwise nullptr
+ static ${interface.classname}*instance(QString name=${project.name|toCString})
+ {return qobject_cast<MInterface*>(WInterface::instance(name));}
+
+ /// returns version information of this interface
+ Q_INVOKABLE static QString staticVersionInfo(WOb::VersionInfo);
+ /// returns version information about the WOC that created the interface class
+ Q_INVOKABLE static QString staticWocVersionInfo(WOb::VersionInfo);
+ /// returns version information of this interface
+ Q_INVOKABLE QString versionInfo(WOb::VersionInfo)const;
+ /// returns version information about the WOC that created the interface class
+ Q_INVOKABLE QString wocVersionInfo(WOb::VersionInfo)const;
+${#TRANSCALL}
+ Q_ENUMS(Right)
+ /// This enum represents transactions and the right to use them.
+ enum Right {
+ /// dummy as a fall back for no transaction
+ NoRight,
+${#RIGHTS}
+${#PRIVILEGES}
+ };
+ typedef QList<Right> RightList;
+ ///converts a right enum to its string representation
+ Q_INVOKABLE static QString rightToString(Right);
+ ///converts a right enum to its localized string representation
+ Q_INVOKABLE static QString rightToLocalString(Right);
+ ///converts a string to a matching right enum, returns NoRight if the string does not match
+ Q_INVOKABLE static Right stringToRight(QString);
+ ///converts a localized string to a matching right enum, returns NoRight if the string does not match
+ Q_INVOKABLE static QStringList allKnownRightsString();
+ ///returns a list of all known rights/transactions
+ Q_INVOKABLE static ${interface.classname}::RightList allKnownRights();
+};
+Q_DECLARE_METATYPE(${interface.classname}::RightList)
+Q_DECLARE_METATYPE(QList<${interface.classname}::RightList>)
+
+//END OF AUTOMATICALLY GENERATED FILE
+#endif
+//===============================
+//-- PREDEFS class.new
+class ${class.classname};
+//===============================
+//-- PREDEFS class.new if(${class.abstract})
+class ${class.classname}Abstract;
+//===============================
+//-- PREDEFS transaction.new
+class ${transaction.classname};
+//===============================
+//-- TRANSCALLARG transaction.input.new clear
+//-- TRANSCALLARG transaction.input.value
+const ${transaction.input.maptype} &a${transaction.input.name}
+//-- TRANSCALL transaction.close
+ /// convenience call to query ${transaction.classname} synchronously
+ ${transaction.classname} query${transaction.name}(${#TRANSCALLARG|trim|oneLine(,)});
+//-- RIGHTS transaction.new
+${transaction.doc|linetrim|prepend(/// )|indent(4)}
+ R${transaction.name},
+//-- PRIVILEGES transaction.privilege
+${transaction.privilege.doc|linetrim|prepend(/// )|indent(4)}
+ P${transaction.name}_${transaction.privilege.name},
+//-- END project.end
--- /dev/null
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Main definition:
+ lang: programming language and variant
+ tag: XML tag used in WOLF files -->
+<Meta lang="qt/client" tag="Qt5ClientOutput">
+
+ <!-- include other files with common definitions -->
+ <!-- Include file="../meta-qt-common.xml" / -->
+
+ <!-- Syntax definitions for pattern files:
+ comment: lines that are discarded by the parser
+ section-start: lines that start a new section of the pattern file
+ variable-marker: start and end marker of variables in pattern files, default is { and }
+
+ note: patterns in this file always use { and } for variables, regardless of the variable-marker attribute
+ -->
+ <Syntax comment="//==" section-start="//--" variable-marker="${ }" />
+
+ <!-- Features are configurable in WOLF files in the XML tag for this output
+ name: the XML attribute name for the feature
+ type: string or boolean for the feature
+ default: default value if the attribute is missing
+ -->
+ <Feature name="allClassPrefix" type="string" default="C" />
+ <Feature name="classPrefix" type="string" default="Obj" />
+ <Feature name="transactionPrefix" type="string" default="Trn" />
+ <Feature name="interfaceName" type="string" default="Interface" />
+ <Feature name="enableExport" type="boolean" default="yes" />
+
+ <!-- files that are being generated through this definition:
+ type: the trigger for creating and closing a file, e.g. type="class" means the file is generated on class.new and closed on class.close
+ tag: identifying name for the file inside the generator - the combination of type and tag must be unique among files
+ name: pattern for the file name that is being generated
+ pattern: name of the pattern file to be interpreted
+
+ A File rule generates at maximum one file at the same time, but it may generate several files consecutively. For this
+ the name attribute must use variables to generate different file names.
+ -->
+ <File type="class" tag="include" name="{class.classname}" pattern="class-include"/>
+ <File type="class" tag="header" name="src{class.classname}.h" pattern="class.h"/>
+ <File type="class" tag="source" name="src{class.classname}.cpp" pattern="class.cpp"/>
+ <File type="transaction" tag="include" name="{transaction.classname}" pattern="transaction-include"/>
+ <!-- file transaction header src*.h
+ file transaction source src*.cpp -->
+ <!-- file interface source src*.cpp -->
+ <File type="interface" tag="header" name="src{interface.classname}.h" pattern="interface.h"/>
+ <File type="interface" tag="include" name="{allClassPrefix}Interface" pattern="interface-include"/>
+ <File type="version" tag="header" name="staticVersion.h" pattern="staticVersion.h"/>
+ <File type="project" tag="pri" name="{project.wobDir}.pri" pattern="wob.pri" />
+ <File type="project" tag="includeAll" name="{allClassPrefix}IncludeAll" pattern="includeAll.h"/>
+
+ <!-- type mappings, class/transaction and db to C++/Qt -->
+ <!-- simple class and transaction types -->
+ <Type config="string" map="QString"/>
+ <Type config="astring" map="QString"/>
+ <Type config="text" map="QString"/>
+ <Type config="int" map="qint64"/>
+ <Type config="int32" map="qint32"/>
+ <Type config="int64" map="qint64"/>
+ <Type config="bool" map="bool"/>
+ <!-- DB types (string with length: use * as a pattern placeholder) -->
+ <Type config="seq" map="qint64"/>
+ <Type config="seq32" map="qint32"/>
+ <Type config="seq64" map="qint64"/>
+ <Type config="enum" map="qint64"/>
+ <Type config="enum32" map="qint32"/>
+ <Type config="enum64" map="qint64"/>
+ <Type config="blob" map="QByteArray"/>
+ <Type config="string:*" map="QString"/>
+ <!-- special class/transaction types:
+ List: the WOLF type behind "List:" is sent through the mapper recursively and * in the map pattern is replaced with this result
+ Object/Enum: * will be replaced with the original WOLF type name, you can use complete expressions -->
+ <Type config="List:*" map="QList<*>"/>
+ <Type config="Object" map="{allClassPrefix}{classPrefix}*"/>
+ <Type config="Enum" map="*"/>
+
+ <!-- pre-calculated variables
+ variables are calculated before any files are generated or patterns are applied
+
+ trigger: trigger on which the calculation is executed
+ deleteOn: trigger on which the variables are deleted again
+
+ name: name of the variable
+
+ value: value for the variable, may contain {var-references} itself
+ or
+ if: boolean expression, & and | are allowed as boolean operators, currently no parentheses
+ valueTrue: value of the new variable if the expression evaluates to true
+ valueFalse: value of the new variable if the expression evaluates to false
+ -->
+ <Precalc trigger="project.new">
+ <Variable name="exportmacro" value="WOBGEN_{project.name|toUpper}__WOB_EXPORT"/>
+ <Variable name="exportSymbol" if="{enableExport}" valueTrue="Q_DECL_IMPORT" valueFalse=""/>
+ <Variable name="defineExportMacro" value="#ifndef {exportmacro}
#define {exportmacro} {exportSymbol}
#endif" />
+ </Precalc>
+
+ <!-- recalculation of variables on triggers -->
+ <Precalc trigger="class.new" deleteOn="class.close">
+ <Variable name="class.classnamenoabstract" value="{allClassPrefix}{classPrefix}{class.name}"/>
+ <Variable name="class.classname" value="{class.classnamenoabstract}{class.abstract|if(Abstract)}"/>
+ <Variable name="class.baseclassname" value="{allClassPrefix}{classPrefix}{class.base}"/>
+ <Variable name="includeGuard" value="WOBGEN_{class.classname}"/>
+ </Precalc>
+ <Precalc trigger="transaction.new" deleteOn="transaction.close">
+ <Variable name="transaction.classname" value="{allClassPrefix}{transactionPrefix}{transaction.name}"/>
+ <Variable name="includeGuard" value="WOBGEN_{transaction.classname}"/>
+ </Precalc>
+ <Precalc trigger="interface.new" deleteOn="interface.close">
+ <Variable name="interface.classname" value="{allClassPrefix}{interfaceName}"/>
+ <Variable name="includeGuard" value="WOBGEN_{interface.classname|toUpper}_H"/>
+ </Precalc>
+
+</Meta>
--- /dev/null
+//-- * version.new
+//BEGIN OF AUTOMATICALLY GENERATED FILE
+//DO NOT EDIT THIS FILE DIRECTLY, USE THE XML SOURCE!
+#ifndef WOBGEN_MSTATIC_VERSION_H
+#define WOBGEN_MSTATIC_VERSION_H
+
+#include <WOb>
+#include <QString>
+#include <QMap>
+
+static inline QString WOCgenerated_versionInfo(WOb::VersionInfo vi){
+switch(vi){
+ case WOb::VersionAuthor:return ${version.author|toCString};
+ case WOb::VersionComm:return ${version.comm|toCString};
+ case WOb::VersionGenTime:return ${version.gentime|toCString};
+ case WOb::VersionHR:return ${version.human|toCString};
+ case WOb::VersionLocallyModified:return ${version.modified|toCString};
+ case WOb::VersionNeedComm:return ${version.needComm|toCString};
+ case WOb::VersionNumber:return ${version.number|toCString};
+ case WOb::VersionPath:return ${version.path|toCString};
+ case WOb::VersionRootURL:return ${version.rootUrl|toCString};
+ case WOb::VersionSystem:return ${version.system|toCString};
+ case WOb::VersionTime:return ${version.time|toCString};
+ default:return QString();
+}}
+static inline void WOCgenerated_versionInfo(QMap<QString,QString>&vi){
+vi.clear();
+ vi.insert("Author",${version.author|toCString});
+ vi.insert("Comm",${version.comm|toCString});
+ vi.insert("GenTime",${version.gentime|toCString});
+ vi.insert("HR",${version.human|toCString});
+ vi.insert("LocallyModified",${version.modified|toCString});
+ vi.insert("NeedComm",${version.needComm|toCString});
+ vi.insert("Number",${version.number|toCString});
+ vi.insert("Path",${version.path|toCString});
+ vi.insert("RootURL",${version.rootUrl|toCString});
+ vi.insert("System",${version.system|toCString});
+ vi.insert("Time",${version.time|toCString});
+}
+static inline QString WOCcopied_versionInfo(WOb::VersionInfo vi){
+switch(vi){
+ case WOb::VersionAuthor:return ${version.woc.author|toCString};
+ case WOb::VersionComm:return "";
+ case WOb::VersionGenTime:return ${version.woc.gentime|toCString};
+ case WOb::VersionHR:return ${version.woc.human|toCString};
+ case WOb::VersionLocallyModified:return ${version.woc.modified|toCString};
+ case WOb::VersionNeedComm:return "";
+ case WOb::VersionNumber:return ${version.woc.number|toCString};
+ case WOb::VersionPath:return ${version.woc.path|toCString};
+ case WOb::VersionRootURL:return ${version.woc.rootUrl|toCString};
+ case WOb::VersionSystem:return ${version.woc.system|toCString};
+ case WOb::VersionTime:return ${version.woc.time|toCString};
+ default:return QString();
+}}
+static inline void WOCcopied_versionInfo(QMap<QString,QString>&vi){
+ vi.clear();
+ vi.insert("Author",${version.woc.author|toCString});
+ vi.insert("Comm","");
+ vi.insert("GenTime",${version.woc.gentime|toCString});
+ vi.insert("HR",${version.woc.human|toCString});
+ vi.insert("LocallyModified",${version.woc.modified|toCString});
+ vi.insert("NeedComm","");
+ vi.insert("Number",${version.woc.number|toCString});
+ vi.insert("Path",${version.woc.path|toCString});
+ vi.insert("RootURL",${version.woc.rootUrl|toCString});
+ vi.insert("System",${version.woc.system|toCString});
+ vi.insert("Time",${version.woc.time|toCString});
+}
+
+//END OF AUTOMATICALLY GENERATED FILE
+#endif
+//-- END ofFile
--- /dev/null
+//-- * transaction.new
+#include "src${transaction.classname}.h"
--- /dev/null
+//-- * project.new
+#AUTOMATICALLY GENERATED FILE - DONT CHANGE!
+INCLUDEPATH += $$PWD
+DEFINES += ${exportmacro}=Q_DECL_EXPORT
+//-- * project.close
+${#CLASS}
+${#TRANSACTION}
+${#INTERFACE}
+
+#END OF AUTOGENERATED PRI FILE
+//-- CLASS class.new
+HEADERS+=$$PWD/src${class.classname}.h
+SOURCES+=$$PWD/src${class.classname}.cpp
+//-- TRANSACTION transaction.new
+HEADERS+=$$PWD/src${transaction.classname}.h
+SOURCES+=$$PWD/src${transaction.classname}.cpp
+//-- INTERFACE interface.new
+HEADERS+=$$PWD/src${interface.classname}.h
+SOURCES+=$$PWD/src${interface.classname}.cpp
+//-- END ofFile
SOURCES+= \
- php/phpout.cpp \
- php/phpclass.cpp \
- php/phptrans.cpp \
- php/phpctrans.cpp \
- php/phpstrans.cpp \
- php/phpdb.cpp
+ $$PWD/phpout.cpp \
+ $$PWD/phpclass.cpp \
+ $$PWD/phptrans.cpp \
+ $$PWD/phpctrans.cpp \
+ $$PWD/phpstrans.cpp \
+ $$PWD/phpdb.cpp
HEADERS+= \
- php/phpout.h \
- php/phpdb.h \
- php/phpclass.h \
- php/phptrans.h \
- php/phpctrans.h \
- php/phpstrans.h
+ $$PWD/phpout.h \
+ $$PWD/phpdb.h \
+ $$PWD/phpclass.h \
+ $$PWD/phptrans.h \
+ $$PWD/phpctrans.h \
+ $$PWD/phpstrans.h
-INCLUDEPATH += php
+INCLUDEPATH += $$PWD
SOURCES+= \
- proc/processor.cpp \
- proc/procclass.cpp \
- proc/proctrans.cpp \
- proc/proctable.cpp
+ $$PWD/processor.cpp \
+ $$PWD/procclass.cpp \
+ $$PWD/proctrans.cpp \
+ $$PWD/proctable.cpp
HEADERS+= \
- proc/processor.h \
- proc/procclass.h \
- proc/proctrans.h \
- proc/proctable.h
+ $$PWD/processor.h \
+ $$PWD/procclass.h \
+ $$PWD/proctrans.h \
+ $$PWD/proctable.h
CONFIG(prewoc, prewoc|woc){
- RESOURCES += proc/version.qrc
+ RESOURCES += $$PWD/version.qrc
}
-INCLUDEPATH += proc
\ No newline at end of file
+INCLUDEPATH += $$PWD
#include "processor.h"
-#include "phpout.h"
#include "qtout.h"
+#ifndef COMPILE_PREWOC
+#include "phpout.h"
#include "htmlout.h"
+#endif
#include "domquery.h"
#include "processor.h"
-#include "phpout.h"
#include "qtout.h"
+#ifndef COMPILE_PREWOC
+#include "phpout.h"
#include "htmlout.h"
#include "schemaout.h"
#include "soapout.h"
+#endif
+#include "genproc.h"
#include "domquery.h"
new WocQtClientOut(el);
if(m_error)return false;
}else
+#ifndef COMPILE_PREWOC
if(tn=="QtServerOutput"){
new WocQtServerOut(el);
if(m_error)return false;
new WocSoapOut(el);
if(m_error)return false;
}else
+#endif
if(tn=="Version"){
if(el.hasAttribute("system"))
m_verSys=el.attribute("system").split(" ",QString::SkipEmptyParts);
if(tn=="Doc"){
QString s=el.text().trimmed();
if(s!="")m_docstrings<<s;
- }
- else{
- qDebug("Warning: file %s has unknown element '%s' at line %i column %i", fn.toLocal8Bit().data(), tn.toLocal8Bit().data(), el.lineNumber(), el.columnNumber());
+ }else{
+ if(!CreateGenericOutput(tn,el)){
+ //completely unknown tag?
+ qDebug("Error: file %s has unknown element '%s' at line %i column %i", fn.toLocal8Bit().data(), tn.toLocal8Bit().data(), el.lineNumber(), el.columnNumber());
+ return false;
+ }
+ //error during initialization?
+ if(m_error)return false;
}
}
//TODO: verify classes
#include "processor.h"
-#include "phpout.h"
#include "qtout.h"
+#ifndef COMPILE_PREWOC
+#include "phpout.h"
#include "htmlout.h"
+#endif
#include "domquery.h"
for(int i=0;i<nl.size();i++){
QDomElement el=nl.at(i).toElement();
if(el.isNull())continue;
- QMap<QString,QString>ps;
- QList<QDomElement> nl2=elementsByTagName(el,"V");
+ QMap<QString,QString>ps;
+ QMap<QString,Preset>ps2;
+ QList<QDomElement> nl2=elementsByTagName(el,"V");
for(int j=0;j<nl2.size();j++){
QDomElement el2=nl2.at(j).toElement();
if(el2.isNull())continue;
if(el2.hasAttribute("val"))v="\""+el2.attribute("val").replace("\"","\\\"")+"\"";
else v=el2.attribute("code");
ps.insert(nm,v);
+ ps2.insert(nm,Preset(nm,el2.attribute("val"),el2.attribute("code")));
}
if(ps.size()>0)m_presets.append(ps);
+ if(ps2.size()>0)m_presets2.append(ps2);
}
//Docu
adt.m_valid=m_valid;
adt.m_backup=m_backup;
adt.m_audit=false;
- adt.m_name=m_name+"_audit";//enhance the name
+ adt.m_name=auditTableName();//enhance the name
adt.m_base="WobTable";//revert to default
adt.m_foreign=m_foreign;
adt.m_fordocs=m_fordocs;
-// Copyright (C) 2009-2011 by Konrad Rosenbaum <konrad@silmor.de>
+// Copyright (C) 2009-2018 by Konrad Rosenbaum <konrad@silmor.de>
// protected under the GNU GPL version 3 or at your option any newer.
// See COPYING.GPL file that comes with this distribution.
//
QList<WocEnum> columnEnums(QString)const;
/**returns the insert call of a column for a specific language; empty string if there is none*/
QString columnCall(QString col,QString lang)const;
-
+ ///returns true if the column name is for an audit column
+ bool columnIsAudit(QString col)const{return col=="auditid" || auditColumns().contains(col);}
+ ///returns true if the column has a string type
+ bool columnIsString(QString col)const{const QString t=columnType(col);return t=="string"||t=="text"||t.startsWith("string:");}
+ ///returns true if the column has an int type
+ bool columnIsInt(QString col)const{const QString t=columnType(col);return t.startsWith("int")||t.startsWith("seq")||t.startsWith("enum");}
+ ///returns true if the column has a blob type
+ bool columnIsBlob(QString col)const{return columnType(col)=="blob";}
+
/**returns all enum definitions of the table; see also columnEnums */
QList<WocEnum> getEnums()const;
bool haveForeign(QString)const;
/**returns a list of all preset values (to be generated when the DB is created);
- each entry in the list is a dictionary with the column name as key and the intended preset value as value - each entry of the list is one DB row, each key-value-pair in the map is one preset value in that row*/
- QList<QMap<QString,QString> > presets()const{return m_presets;}
+ each entry in the list is a dictionary with the column name as key and the intended preset value as value - each entry of the list is one DB row, each key-value-pair in the map is one preset value in that row
+ \obsolete This method is obsolete, use presetList() */
+ QT_DEPRECATED QList<QMap<QString,QString> > presets()const{return m_presets;}
+ ///Preset value
+ struct Preset {
+ QString column,value,call;
+ Preset(){}
+ Preset(QString col,QString val,QString ca):column(col),value(val),call(ca){}
+ Preset(const Preset&)=default;
+ Preset(Preset&&)=default;
+ Preset& operator=(const Preset&)=default;
+ Preset& operator=(Preset&&)=default;
+ };
+ /**returns a list of all preset values (to be generated when the DB is created);
+ each entry in the list is a dictionary with the column name as key and the intended preset value as value - each entry of the list is one DB row, each key-value-pair in the map is one preset value in that row*/
+ QList<QMap<QString,Preset>> presetList()const{return m_presets2;}
/**parses the static part of auditing*/
static void parseAuditStatic(const QDomElement&);
WocTable auditTable()const;
/**returns the names of audit columns (except auditid)*/
QStringList auditColumns()const;
+ ///returns the name of the corresponding audit table
+ QString auditTableName()const{if(m_audit)return m_name+"_audit";else return QString();}
///returns all complex Unique constraints (those not defined for a single column)
QStringList uniqueConstraints()const{return m_uniquecols;}
static QList<s_col>m_staticauditcolumns;
QList<QPair<QString,QString> >m_foreign;
QList<QMap<QString,QString> >m_presets;
+ QList<QMap<QString,Preset>> m_presets2;
int m_backupsize=-1;
QStringList m_docstrings,m_uniquecols;
#include "processor.h"
-#include "phpout.h"
#include "qtout.h"
+#ifndef COMPILE_PREWOC
+#include "phpout.h"
#include "htmlout.h"
+#endif
#include "domquery.h"
<Wolf>
<!-- Internal Pseudo-Project to generate a version.h file for PACK itself -->
<Project baseDir="::woc::" name="PACK"/>
- <Version comm="0" needcomm="0" humanReadable="0.7" target=".." system="svn git none"/>
+ <Version comm="0" needcomm="0" humanReadable="1.1" target=".." system="svn git none"/>
<!-- configure output -->
<QtClientOutput sourceDir="../vinfo" subDir="." priInclude="wob.pri" classPrefix="WVI" clean="yes"/>
-</Wolf>
\ No newline at end of file
+</Wolf>
--- /dev/null
+#include <QCoreApplication>
+#include <QDir>
+#include <QDebug>
+
+#define PROLOG "<!DOCTYPE RCC>\n\
+<!-- AUTO GENERATED FILE -->\n\
+<RCC version=\"1.0\">\n<qresource>\n"
+
+#define EPILOG "</qresource></RCC>\n"
+
+void writeOut(QFile&out,QString source,QString target)
+{
+ QDir dir(source);
+ qDebug()<<"Scanning directory"<<source<<(dir.isReadable()?"(ok)":"(NOT READABLE)")<<"as"<<target<<"into"<<out.fileName();
+ for(QString fn:dir.entryList(QDir::Files|QDir::Dirs)){
+ if(fn=="." || fn=="..")continue;
+ if(QFileInfo(source+"/"+fn).isDir()){
+ writeOut(out,source+"/"+fn,target.isEmpty()?fn:(target+"/"+fn));
+ }else
+ out.write(QString("<file alias=\"%3/%1\">%2/%1</file>\n").arg(fn).arg(source).arg(target).toLatin1());
+ }
+}
+
+int main(int ac,char**av)
+{
+ QCoreApplication app(ac,av);
+ qDebug()<<"Generatin QRCs from"<<QDir::current().absolutePath();
+ if(app.arguments().size()!=4){
+ qFatal("Usage: %s <sourcePath> <resourcePath> <resourceFileName>",app.arguments().at(0).toLocal8Bit().data());
+ return 1;
+ }
+ const QStringList pathes=app.arguments().mid(1);
+ if(!pathes[2].endsWith(".qrc")){
+ qFatal("Resource File Name %s is invalid - resource file name must end in .qrc", pathes[2].toLatin1().data());
+ return 1;
+ }
+ QFile out(pathes[2]);
+ if(!out.open(QIODevice::WriteOnly|QIODevice::Truncate)){
+ qFatal("Unable to create %s - giving up.", pathes[2].toLatin1().data());
+ return 1;
+ }
+ out.write(PROLOG);
+ writeOut(out,pathes[0],pathes[1]);
+ out.write(EPILOG);
+ out.close();
+ return 0;
+}
--- /dev/null
+#Include this file to be able to automatically generate QRC files from directories
+#
+# Setting this variable:
+# GRESOURCES += directory
+# will auto-generate directory.qrc and include it into the final binary.
+
+
+qrcgen.output = ${QMAKE_FILE_NAME}.qrc
+qrcgen.variable_out = RESOURCES
+qrcgen.commands = $$PWD/qrcgen ${QMAKE_FILE_NAME} ${QMAKE_FILE_NAME} ${QMAKE_FILE_NAME}.qrc
+qrcgen.depend_command = find ${QMAKE_FILE_NAME} -type f
+qrcgen.input = GRESOURCES
+QMAKE_EXTRA_COMPILERS += qrcgen
--- /dev/null
+TEMPLATE = app
+TARGET = qrcgen
+SOURCES += qrcgen.cpp
+DESTDIR = ../qrcgen
+QT -= gui
+CONFIG += console
+CONFIG -= app_bundle
SOURCES+= \
- qt/qtout.cpp \
- qt/qtclass.cpp \
- qt/qtdb.cpp \
- qt/qtctrans.cpp \
- qt/qtstrans.cpp
+ $$PWD/qtout.cpp \
+ $$PWD/qtclass.cpp \
+ $$PWD/qtdb.cpp \
+ $$PWD/qtctrans.cpp \
+ $$PWD/qtstrans.cpp
HEADERS+= \
- qt/qtout.h \
- qt/qtclass.h \
- qt/qtdb.h \
- qt/qtctrans.h \
- qt/qtstrans.cpp
+ $$PWD/qtout.h \
+ $$PWD/qtclass.h \
+ $$PWD/qtdb.h \
+ $$PWD/qtctrans.h \
+ $$PWD/qtstrans.cpp
-INCLUDEPATH += qt
+INCLUDEPATH += $$PWD
-RESOURCES += soap/schemafiles.qrc
+RESOURCES += $$PWD/schemafiles.qrc
SOURCES += \
- soap/schemaout.cpp \
- soap/soapout.cpp
+ $$PWD/schemaout.cpp \
+ $$PWD/soapout.cpp
HEADERS += \
- soap/schemaout.h \
- soap/soapout.h
+ $$PWD/schemaout.h \
+ $$PWD/soapout.h
-INCLUDEPATH += soap
\ No newline at end of file
+INCLUDEPATH += $$PWD
--- /dev/null
+# Copyright (C) 2009-2018 by Konrad Rosenbaum <konrad@silmor.de>
+# protected under the GNU GPL version 3 or at your option any newer.
+# See COPYING.GPL file that comes with this distribution.
+
+TEMPLATE=app
+QT-=gui
+QT+=xml testlib
+CONFIG+=console debug
+CONFIG-=app_bundle
+
+DEFINES += COMPILE_WOC
+TARGET = ../woctest_generic
+MYTMPDIR = .ctmp
+
+#compilation output:
+OBJECTS_DIR = $$MYTMPDIR
+MOC_DIR = $$MYTMPDIR
+RCC_DIR = $$MYTMPDIR
+
+
+SOURCES+= \
+ ../../mfile.cpp \
+ ../../domquery.cpp
+HEADERS+= \
+ ../../mfile.h \
+ ../../domquery.h
+
+SOURCES+= woctest.cpp
+HEADERS+= woctest.h
+
+include(../../proc/proc.pri)
+include(../../generic/generic.pri)
+GRESOURCES += testfiles
+# TODO: remove
+include(../../qt/qt.pri)
+include(../../php/php.pri)
+include(../../soap/soap.pri)
+SOURCES+=../../htmlout.cpp
+#END TODO
+
+include(../../qrcgen/qrcgen.pri)
+
+CONFIG += c++11
+
+INCLUDEPATH += . ../../../vinfo ../../../qtbase/include ../..
+
+#make sure dependencies are found
+DEPENDPATH += $$INCLUDEPATH
--- /dev/null
+//== this pattern tests chains of conditional sections
+//-- * test.close
+results:
+{#CHAIN1|trim}
+{#CHAIN2|trim}
+{#CHAIN3|trim}
+{#CHAIN4|trim}
+{#CHAIN5|trim}
+{#CHAIN6A|trim}-{#CHAIN6B|trim}-{#CHAIN6C|trim}
+{#CHAIN7A|trim}-{#CHAIN7B|trim}-{#CHAIN7C|trim}
+{#UNCO|trim}
+//==
+//== Chain 1: simple if, one section only
+//-- CHAIN1 run1 if({run|contains[1]})
+C1
+//==
+//== Chain 2: two sections, first triggers
+//-- CHAIN2 run2 if({run|isNotEmpty})
+C2
+//-- CHAIN2 run2 else
+C2E
+//== Chain 3: same trigger different chain
+//-- CHAIN3 run2 if(true)
+C3
+//== Chain 3b: same trigger, same target, different chain
+//-- CHAIN3 run2 if({run|isNotEmpty})
+C3B
+//== unconditional addition
+//-- UNCO run2
+U1
+//==
+//== Chain 4: 3 sections, last else triggers
+//-- CHAIN4 run3 if({run|isEmpty})
+C4E1
+//-- CHAIN4 run3 else if({run|contains[55]})
+C4E2
+//-- CHAIN4 run3 else
+C4
+//==
+//== Chain5: 3 sections, middle one triggers
+//-- CHAIN5 run5 if({run|isEmpty})
+C5E1
+//-- CHAIN5 run5 else if({hello|toLower|contains[world]})
+C5
+//-- CHAIN5 run5 else
+C5E2
+//==
+//== Chain 6: different targets for same chain
+//-- CHAIN6A run6 if({run|isEmpty})
+C6E1
+//-- CHAIN6B run6 else if({hello|toLower|contains[world]})
+C6
+//-- CHAIN6C run6 else
+C6E2
+//==
+//== Chain 6: different targets for same chain, all expressions true
+//-- CHAIN7A run7 if({run|isNotEmpty})
+C7
+//-- CHAIN7B run7 else if({hello|toLower|contains[world]})
+C7E1
+//-- CHAIN7C run7 else if(true)
+C7E2
+//-- END ofFile
--- /dev/null
+results:
+C1
+C2
+C3
+C3B
+C4
+C5
+-C6-
+C7--
+U1
--- /dev/null
+//== this pattern tests some basics
+//== invisible line
+//-- * test.new
+{file}
+//-- * test.close
+{#OTHER|trim}
+//== skipped line
+{#OTHER|trim|oneLine(,)}
+//-- OTHER run1
+{run}
+//-- OTHER run3
+{run}
+three
+//-- * after
+this line should NOT appear in the result: the file should be closed by now
+//-- END ofFile
--- /dev/null
+simple.pat
+1
+3
+three
+1,3,three
--- /dev/null
+//== this pattern tests all defined transformations
+//-- * test.close
+{var1|toUpper}
+{hello|toLower}
+{#XML|trim|xmlEncode}
+{#URL|trim|urlEncode}
+{#STR|trim|toCString}
+{#MULTI|trim|oneLine}
+{#MULTI|trim|oneLine(->)}
+{#MULTI|trim|sort|oneLine}
+{#MULTI|trim|unique|oneLine}
+{#MULTI|trim|sort|unique|oneLine}
+
+{#NONE|isEmpty|if(e)(n)}
+{#NONE|isNotEmpty|if(e)(n)}
+{#STR|isEmpty|if(e)(n)}
+{#STR|isNotEmpty|if(e)(n)}
+{#MULTI|contains(aaa)|if(ya)(na)}
+{#MULTI|contains(aaa)|negate|if(ya)(na)}
+
+{var2|replace(2)(two)}
+
+{#TWOS|trim|prepend(//)}
+
+{#TWOS|linetrim|indent(3)}
+//-- XML run2
+<{run} />
+//-- URL run3
+x://y@z
+//-- STR run4
+"{run}'x
+//-- MULTI run1
+aaa
+{run}
+//-- MULTI run2
+{run}
+//-- MULTI run3
+{run}
+//-- MULTI run4
+{run}
+//-- MULTI run5
+{run}
+//-- MULTI run7
+aaa
+aaa
+//-- NONE run8
+
+//-- TWOS run9
+ one
+ two
+//-- END ofFile
--- /dev/null
+VALUE_1
+hello world!
+<2 />
+x%3A%2F%2Fy%40z
+"\"4\'x"
+aaa 1 2 3 4 5 aaa aaa
+aaa->1->2->3->4->5->aaa->aaa
+1 2 3 4 5 aaa aaa aaa
+aaa 1 2 3 4 5 aaa
+1 2 3 4 5 aaa
+
+e
+n
+n
+e
+ya
+na
+
+value_two
+
+//one
+// two
+
+ one
+ two
--- /dev/null
+// Copyright (C) 2018 by Konrad Rosenbaum <konrad@silmor.de>
+// protected under the GNU GPL version 3 or at your option any newer.
+// See COPYING.GPL file that comes with this distribution.
+//
+
+#include <QtTest>
+#include "woctest.h"
+
+#include "generic/genvar.h"
+#include "generic/genexpr.h"
+#include "generic/genfile.h"
+
+void WocTest::genVarTest()
+{
+ WocSimpleVariables sv1(nullptr);
+
+ QVERIFY(sv1.setVariable("v1","val1"));
+ QCOMPARE(sv1.getVariable("v1"),"val1");
+ QCOMPARE(sv1.getVariable("xy"),"");
+
+ QVERIFY(!sv1.setVariable("###","s"));
+ QCOMPARE(sv1.getVariable("###"),"");
+ QVERIFY(!sv1.setVariable("#v","s"));
+ QCOMPARE(sv1.getVariable("#v"),"");
+
+ WocSimpleVariables sv2(&sv1);
+ QVERIFY(sv1.setVariable("v2","val2"));
+ QVERIFY(sv2.setVariable("v2","valB"));
+ QCOMPARE(sv1.getVariable("v2"),"val2");
+ QCOMPARE(sv2.getVariable("v2"),"valB");
+ QCOMPARE(sv2.getVariable("v1"),"val1");
+
+ WocSectionVariables sec(&sv1);
+ QVERIFY(sec.setVariable("#section","content"));
+ QVERIFY(!sec.setVariable("normal","blah"));
+ QCOMPARE(sec.getVariable("#section"),"content");
+ QCOMPARE(sec.getVariable("v1"),"val1");
+}
+
+void WocTest::genExprStringTest()
+{
+ WocSimpleVariables sv(nullptr);
+ QPair<QString,QString>mark1("{","}");
+ sv.setVariable("mix","Hello World!");
+ QCOMPARE(WocGenericExpression("{mix}",mark1,&sv).evaluateToString(),"Hello World!");
+ QCOMPARE(WocGenericExpression("{mix|toLower}",mark1,&sv).evaluateToString(),"hello world!");
+ QCOMPARE(WocGenericExpression("{mix|toUpper}",mark1,&sv).evaluateToString(),"HELLO WORLD!");
+ QCOMPARE(WocGenericExpression("{mix|replace(l)(XX)}",mark1,&sv).evaluateToString(),"HeXXXXo WorXXd!");
+ QCOMPARE(WocGenericExpression("{mix|replace(l)}",mark1,&sv).evaluateToString(),"Heo Word!");
+ QCOMPARE(WocGenericExpression("{mix|replace[l][<((><]}",mark1,&sv).evaluateToString(),"He<((><<((><o Wor<((><d!");
+ QCOMPARE(WocGenericExpression("{mix|replace(l)(xX)|toUpper}",mark1,&sv).evaluateToString(),"HEXXXXO WORXXD!");
+ QCOMPARE(WocGenericExpression("I say '{mix}' to you.",mark1,&sv).evaluateToString(),"I say 'Hello World!' to you.");
+ QCOMPARE(WocGenericExpression("{mix|contains(ell)}",mark1,&sv).evaluateToString(),"true");
+ QCOMPARE(WocGenericExpression("{mix|contains(x)}",mark1,&sv).evaluateToString(),"false");
+
+ sv.setVariable("enc","<jo at=\"tr\"/>");
+ QCOMPARE(WocGenericExpression("**{enc|xmlEncode}**",mark1,&sv).evaluateToString(),"**<jo at="tr"/>**");
+ QCOMPARE(WocGenericExpression("**{enc|urlEncode}**",mark1,&sv).evaluateToString(),"**%3Cjo%20at%3D%22tr%22%2F%3E**");
+ QCOMPARE(WocGenericExpression("**{enc|toCString}**",mark1,&sv).evaluateToString(),"**\"<jo at=\\\"tr\\\"/>\"**");
+
+ sv.setVariable("ml","hello\nworld");
+ QCOMPARE(WocGenericExpression("{ml}",mark1,&sv).evaluateToString(),"hello\nworld");
+ QCOMPARE(WocGenericExpression("{ml|oneLine}",mark1,&sv).evaluateToString(),"hello world");
+ QCOMPARE(WocGenericExpression("{ml|oneLine(,)}",mark1,&sv).evaluateToString(),"hello,world");
+ QCOMPARE(WocGenericExpression("{ml|prepend(//)}",mark1,&sv).evaluateToString(),"//hello\n//world");
+ QCOMPARE(WocGenericExpression("{ml|indent(3)}",mark1,&sv).evaluateToString()," hello\n world");
+
+ sv.setVariable("spc"," \t\n ");
+ sv.setVariable("wspc"," space\t");
+ QCOMPARE(WocGenericExpression("{mix|isEmpty}",mark1,&sv).evaluateToString(),"false");
+ QCOMPARE(WocGenericExpression("{mix|isNotEmpty}",mark1,&sv).evaluateToString(),"true");
+ QCOMPARE(WocGenericExpression("{spc|isEmpty}",mark1,&sv).evaluateToString(),"true");
+ QCOMPARE(WocGenericExpression("{spc|isNotEmpty}",mark1,&sv).evaluateToString(),"false");
+ QCOMPARE(WocGenericExpression("{wspc|isEmpty}",mark1,&sv).evaluateToString(),"false");
+ QCOMPARE(WocGenericExpression("{wspc|isNotEmpty}",mark1,&sv).evaluateToString(),"true");
+ QCOMPARE(WocGenericExpression("{wspc|trim}",mark1,&sv).evaluateToString(),"space");
+ QCOMPARE(WocGenericExpression("{wspc}",mark1,&sv).evaluateToString()," space\t");
+
+ sv.setVariable("t","on");
+ sv.setVariable("f","no");
+ QCOMPARE(WocGenericExpression("{t|negate}",mark1,&sv).evaluateToString(),"false");
+ QCOMPARE(WocGenericExpression("{f|negate}",mark1,&sv).evaluateToString(),"true");
+ QCOMPARE(WocGenericExpression("{t|if(yo)(nah)}",mark1,&sv).evaluateToString(),"yo");
+ QCOMPARE(WocGenericExpression("{f|if(yo)(nah)}",mark1,&sv).evaluateToString(),"nah");
+ QCOMPARE(WocGenericExpression("{t|if(yo)}",mark1,&sv).evaluateToString(),"yo");
+ QCOMPARE(WocGenericExpression("{f|if(yo)}",mark1,&sv).evaluateToString(),"");
+
+ QPair<QString,QString>mark2("$[",")>");
+ QCOMPARE(WocGenericExpression("{wspc}$[mix)>{mix}",mark2,&sv).evaluateToString(),"{wspc}Hello World!{mix}");
+
+ QCOMPARE(WocGenericExpression().evaluateToString(),"");
+ QCOMPARE(WocGenericExpression("{mix}",mark1,nullptr).evaluateToString(),"");
+}
+
+void WocTest::genExprBoolTest()
+{
+ WocSimpleVariables sv(nullptr);
+ QPair<QString,QString>mark1("{","}");
+ sv.setVariable("mix","Hello World!");
+ sv.setVariable("t","on");
+ sv.setVariable("f","no");
+ QCOMPARE(WocGenericExpression("{t}",mark1,&sv).evaluateToBool(),true);
+ QCOMPARE(WocGenericExpression("{f}",mark1,&sv).evaluateToBool(),false);
+ QCOMPARE(WocGenericExpression("yes",mark1,&sv).evaluateToBool(),true);
+ QCOMPARE(WocGenericExpression("no",mark1,&sv).evaluateToBool(),false);
+ QCOMPARE(WocGenericExpression("{t}||{f}",mark1,&sv).evaluateToBool(),true);
+ QCOMPARE(WocGenericExpression("{t}&&{f}",mark1,&sv).evaluateToBool(),false);
+ QCOMPARE(WocGenericExpression("{t}|| no",mark1,&sv).evaluateToBool(),true);
+ QCOMPARE(WocGenericExpression("{t}&& yes",mark1,&sv).evaluateToBool(),true);
+ QCOMPARE(WocGenericExpression("{t} && yes && {f|negate} || {f}",mark1,&sv).evaluateToBool(),true);
+
+ QCOMPARE(WocGenericExpression("!{t}",mark1,&sv).evaluateToBool(),false);
+ QCOMPARE(WocGenericExpression("! {t}",mark1,&sv).evaluateToBool(),false);
+ QCOMPARE(WocGenericExpression("!{f}",mark1,&sv).evaluateToBool(),true);
+ QCOMPARE(WocGenericExpression("! {f}",mark1,&sv).evaluateToBool(),true);
+ QCOMPARE(WocGenericExpression("!!{f}",mark1,&sv).evaluateToBool(),false);
+ QCOMPARE(WocGenericExpression("!!{t}",mark1,&sv).evaluateToBool(),true);
+
+ QCOMPARE(WocGenericExpression("!{f} && {t} && !no",mark1,&sv).evaluateToBool(),true);
+}
+
+static inline QString getFile(QString fn)
+{
+ QFile fd(fn);
+ if(!fd.open(QIODevice::ReadOnly))return QString();
+ QString r=QString::fromUtf8(fd.readAll());
+ fd.close();
+ return r.trimmed();
+}
+
+void WocTest::genFileTest()
+{
+ //environment
+ WocSimpleVariables sv(nullptr);
+ for(int i=0;i<10;i++)sv.setVariable(QString("var%1").arg(i),QString("value_%1").arg(i));
+ sv.setVariable("hello","Hello World!");
+ OutMock om;
+ //go through test files
+ QDir d(":/testfiles");
+ for(QString fn:d.entryList(QStringList()<<"*.pat")){
+ qDebug()<<"Testing pattern file"<<fn;
+ const QString base=QFileInfo(fn).baseName();
+ //process file
+ sv.setVariable("file",fn);
+ sv.setVariable("base",base);
+ WocGenericFile gf(&om,"test","testpat_{base}.res",":/testfiles/"+fn,&sv);
+ gf.trigger("test.new");
+ for(int i=0;i<10;i++){
+ sv.setVariable("run",QString::number(i),"x");
+ gf.trigger(QString("run%1").arg(i));
+ }
+ sv.deleteOldVariables("x");
+ gf.trigger("test.close");
+ gf.trigger("after");
+ //get result
+ QCOMPARE(getFile("testpat_"+base+".res"),getFile(":/testfiles/"+base+".res"));
+ QFile::remove("testpat_"+base+".res");
+ }
+}
+
+QString OutMock::mapType(QString n, const WocClass* context) const
+{
+ Q_UNUSED(context);
+ return "/"+n+"/";
+}
+
+
+QTEST_MAIN(WocTest);
--- /dev/null
+// Copyright (C) 2016 by Konrad Rosenbaum <konrad@silmor.de>
+// protected under the GNU GPL version 3 or at your option any newer.
+// See COPYING.GPL file that comes with this distribution.
+//
+
+#include <QObject>
+#include "generic/genfile.h"
+#include "generic/genout.h"
+
+class WocTest:public QObject
+{
+ Q_OBJECT
+private slots:
+ void genVarTest();
+ void genExprStringTest();
+ void genExprBoolTest();
+ void genFileTest();
+};
+
+class OutMock:public WocGenericOutBase
+{
+public:
+ OutMock(){}
+ QString outputBaseDir()const override{return ".";}
+ QString outputSubDir()const override{return ".";}
+ QString outputLanguage()const override{return "fake/client";}
+
+ QString syntaxComment()const override{return "//==";}
+ QString syntaxSection()const override{return "//--";}
+ QString syntaxVariableStart()const override{return "{";}
+ QString syntaxVariableEnd()const override{return "}";}
+ QPair<QString,QString> syntaxVariable()const override{return QPair<QString,QString>("{","}");}
+
+ QString outputTargetDir()const override{return ".";}
+
+ QString mapType(QString n,const WocClass*context=nullptr)const override;
+protected:
+ virtual void finalize()override{}
+ virtual void newClass(const WocClass&)override{}
+ virtual void newTransaction(const WocTransaction&)override{}
+ virtual void newTable(const WocTable&)override{}
+};
--- /dev/null
+TEMPLATE = subdirs
+SUBDIRS = generic
The PHP back-end is in the "php" directory with WocPHPOut, WocPHPClientOut, and WocPHPServerOut as the main output classes.
+The generic pattern based back-end is in the "generic" directory with default patterns in the "pattern directory.
*/
#include <QCoreApplication>
#include <QStringList>
+#include <QDebug>
#include "processor.h"
+#include "genproc.h"
int main(int argc,char**argv)
{
QCoreApplication app(argc,argv);
+ //Initializations
+ InitializeGeneric();
//get arguments
QStringList args=app.arguments();
args.removeFirst();
//process files
WocProcessor proc;
- for(int i=0;i<args.size();i++)
- if(!proc.processFile(args[i])){
- qDebug("aborting scan.");
+ for(QString arg:args)
+ if(arg.startsWith("-pattern="))
+ InitializeGeneric(arg.mid(9));
+ else
+ if(!proc.processFile(arg)){
+ qDebug()<<"Error: invalid argument"<<arg<<"- aborting scan.";
return 1;
}
//call finalizer
qDebug("done.");
//return success
return 0;
-}
\ No newline at end of file
+}
-# Copyright (C) 2009-2013 by Konrad Rosenbaum <konrad@silmor.de>
+# Copyright (C) 2009-2018 by Konrad Rosenbaum <konrad@silmor.de>
# protected under the GNU GPL version 3 or at your option any newer.
# See COPYING.GPL file that comes with this distribution.
CONFIG-=app_bundle
CONFIG(prewoc, prewoc|woc){
- DEFINES += NO_PACK_VERSION_H
+ DEFINES += NO_PACK_VERSION_H COMPILE_PREWOC
TARGET = ../woc/prewoc
MYTMPDIR = .ptmp
}else{
+ DEFINES += COMPILE_WOC
TARGET = ../woc/woc
MYTMPDIR = .ctmp
}
SOURCES+= \
woc.cpp \
- htmlout.cpp \
mfile.cpp \
domquery.cpp
HEADERS+= \
- htmlout.h \
mfile.h \
domquery.h
include(proc/proc.pri)
-include(php/php.pri)
include(qt/qt.pri)
-include(soap/soap.pri)
+include(generic/generic.pri)
+CONFIG(prewoc, prewoc|woc){
+}else{
+ SOURCES += htmlout.cpp
+ HEADERS += htmlout.h
+ include(php/php.pri)
+ include(soap/soap.pri)
+}
+
+GRESOURCES+=pattern
+include (qrcgen/qrcgen.pri)
-gcc { QMAKE_CXXFLAGS += -std=c++11 }
+CONFIG += c++11
INCLUDEPATH += . ../vinfo ../qtbase/include