QiQu Snippets 1 Deletion of duplicated entries
Problem |
Assume you build a list of all needed imports to generate a java source file.
Different code parts that are generated share some of the imports. Those imports
could be necessary because of your generator input. So there is no way to define
the necessary imports directly in the script or template. Therfore, inside the
QiQu-script, you maintain a list with the needed imports.
How to you ensure,
that the same import is not contained more than once? |
Approach |
A possible approach could be to check everytime you add an import to the list
if it is already present. However, that is kind of clumsy, since you have to do this
everywhere you add imports to the list. A better way is to remove dublicated entry
at the of the script. |
Solution |
<For NodeRef="$outImportlist" XPath="./import" IteratorEleRef="$outImportIterator">
<Set Value="'false'" Ref="firstSet"/>
<For NodeRef="$outImportlist" XPath="./import[@value='#$outImportIterator.value#']" IteratorEleRef="$outToDelete">
<!-- If this is not the first entry, we delete it -->
<If Condition="equals(firstSet,'true')">
<Delete Ref="$outToDelete"/>
</If>
<!-- keep the first entry -->
<If Condition="equals(firstSet,'false')">
<Set Value="'true'" Ref="firstSet"/>
</If>
</For>
</For>
|
Explanation |
We assume we have list with <import value="ch.aloba..."> elements. Those
entries are all contained under one parent node. The first loop iterates over all
of this entries. As next, we create a boolean variable "firstSet" and set it to false.
Next we use a second loop to loop over all import-entries with the same value.
When the loop is running the first time, the variable "firstSet" is false,
so the second if will be true. Inside this second if, the variable "firstSet" is
set to true. For the next iterations of the second loop, the variable "firstSet"
will result to true, so all other entries with the same value will be removed from
the list.
|
2 Building the full name out of a tree structure
Problem |
When XMI from a model is generated, every package part is a node. And somewhere
there in, you have your class entries. For example, the structure to represent the
class ch.aloba.qiqu.scripts.commands.AddEle would result in structure like this
(note: this is simplified and not actually real XMI):
<model>
<package name="ch">
<package name="aloba">
<pacakage name="qiqu">
<package name="commands">
<class name="AddEle"/>
</package>
</pacakage>
</package>
</package>
</model>
When generating java sources, you often need the full qualified class name of a class.
How can you add an attribute to every class entry, which contains the
full qualified class name?
|
Approach |
The main problem is the needed number of Xpath-calls. It would be possible,
to select all classes, navigate upwards till the root is found and connect the
name pieces together. However, if you have a lot of classes, that will not
perform well. So, another approach has to be used.
Instead of creating the fqn directly for every class instance, we loop over the
packages and create the fqn for them. Building the fqn for a package is pretty
simple: we take the fqn from the parent, add a '.' and add the package name. Done.
When iterating over elements in a For-loop, the elements are in the same order,
as they appear in the XML-document. This means a child-package element will not
appear before its parent.
|
Solution |
<QiQuScript>
<LoadDoc FileName="'model/input/model.xml'" NewDocRef="$inModelRoot"/>
<!-- create an empty fqn attribute in the root node (model element) -->
<SelectFirst NodeRef="$inModelRoot" XPath="//model" SelectedEleRef="$root"/>
<Set Ref="$root.fqn" Value="''"/>
<For NodeRef="$inModelRoot" XPath="//package" IteratorEleRef="$inPackage">
<!-- Select the parent -->
<SelectFirst NodeRef="$inPackage" XPath=".." SelectedEleRef="$parentPackage"/>
<!-- create fqn with parents fqn an the own name -->
<Set Ref="$inPackage.fqn" Value="$parentPackage.fqn+'.'+$inPackage.name"/>
<!-- the fqn of the packages under model will start with a '.' so we remove that -->
<Set Ref="$inPackage.fqn" Value="replace($inPackage.fqn, '^[.]', '')"/>
<!-- set the fqn in all direct class-elements of the package -->
<For NodeRef="$inPackage" XPath="./class" IteratorEleRef="$classInPackage">
<Set Ref="$classInPackage.fqn" Value="$inPackage.fqn + '.' + $classInPackage.name"/>
</For>
</For>
<SaveDoc FileName="'model/output/model.xml'" DocRef="$inModelRoot"/>
</QiQuScript>
|
Explanation |
After loading the document, we create a "fqn" attribute in the model
element itself. This way, the following loop becomes more simple. The main
loop iterates over all package elements. Inside the loop, the parent element
is selected. Afterwards, the fqn is set: parents.fqn + '.' + name. The direct
packages under the model element will start with a '.', so we fix this with a
simple replace and the appropriate regular expression to select a beginning '.'.
Now, the correct fqn is contained in the package. All that is left to do, is to
iterate over all direct class elements and copy the fqn and add the class name. |
3 Manage the settings for several generation targets in a QiQu-Gui
Problem |
You created a generator with a dialog to enter the settings for a project.
Now, you'd like to use the generator for several projects, without the need to
copy several instances of the generator. |
Approach |
Define start dialog, in which you can chose the project. Depending on the chosen
project, you load the configuration for the generator-settings dialog from different
directories. |
Solution |
In order to do that, we need a couple of things. First, let us define a simple
dialog in order to choose the project. Save the file under 'scripts/gui/dlgSelectProject.xml'.
<propertydialog title="Select Project">
<widgetlist name="default">
<widget type="text" propertyname="newProject" startvalue="" name="New"/>
<widget type="label" name="Select the Project" propertyname="" startvalue=""/>
<widget type="combo" name="Project" propertyname="selectedProject" startvalue="default"
helptext="Select the Project">
</widget>
</widgetlist>
</propertydialog>
Next, we need a another dialog, which will contain the setting for the generator.
We simply use an example from the Property-Dialog tutorial. Save this file
under 'scripts/gui/dlgGeneratorSettings.xml'.
<propertydialog title="Your QiQu-Generator">
<tablist name="mainTabs">
<tab name="Checks">
<widgetlist name="checkboxes">
<widget type="check" name="Check1" propertyname="check1" startvalue="true"/>
<widget type="check" name="Check2" propertyname="check2" startvalue="true"/>
</widgetlist>
</tab>
<tab name="Texts">
<widgetlist name="texts">
<widget type="text" name="Text1" propertyname="text1" startvalue="Text 1"/>
<widget type="text" name="Text2" propertyname="text2" startvalue="Text 2"/>
</widgetlist>
</tab>
</tablist>
</propertydialog>
Now create the file 'settings/projectlist.xml' with the following content:
<projectlist>
<project name="default"/>
</projectlist>
The following script has to be placed under 'script/projectSelector.qiq'.
<QiQuScript>
<Set Ref="SettingsPath" Value="'settings/'"/>
<Set Ref="projectSelectorDlg" Value="'scripts/gui/dlgSelectProject.xml'"/>
<Set Ref="projectSelectorDlgProps" Value="SettingsPath + 'selectproject.properties'"/>
<Set Ref="Projectlist" Value="SettingsPath + 'projectlist.xml'"/>
<!-- Load the projectlist -->
<LoadDoc FileName="Projectlist" NewDocRef="$projectlist"/>
<!-- load the dialog definiton for the Select Project dialog -->
<LoadDoc FileName="projectSelectorDlg" NewDocRef="$dlgProjectSelector"/>
<!-- select the entry, which defines the combo box inside the Select Project dialog -->
<SelectFirst NodeRef="$dlgProjectSelector" XPath="//widget[@name='Project']"
SelectedEleRef="$projectNode"/>
<!-- for every entry in the projectlist.xml create an appropriate entry in the combo box -->
<For NodeRef="$projectlist" XPath="//project" IteratorEleRef="$projectname">
<CreateEle NodeRef="$projectNode" EleName="'item'" NewEleRef="$newItem"/>
<Set Ref="$newItem.value" Value="$projectname.name"/>
</For>
<!-- Show the Select Project dialog -->
<ShowPropertyDialog GuiNodeRef="$dlgProjectSelector" PropFileName="projectSelectorDlgProps"/>
<!-- cancelbutton is set to true, if the cancel button is pressed inside the dialog -->
<If Condition="equals(cancelbutton , 'true')">
<Exit />
</If>
<!-- if the user has entered a new project, the 'newProject' property will not be empty -->
<If Condition="not(equals('' , newProject))">
<!-- the property 'selectProject' contains the selected entry in the combo box -->
<!-- in the case of a new project, we overwrite this property -->
<Set Ref="selectedProject" Value="newProject"/>
<!-- when a new project is entered, it has to be added to the projectlist.xml -->
<!-- the next four lines will handle that -->
<SelectFirst NodeRef="$projectlist" XPath="//projectlist" SelectedEleRef="$projectNode"/>
<CreateEle NodeRef="$projectNode" EleName="'project'" NewEleRef="$newproject"/>
<Set Ref="$newproject.name" Value="newProject"/>
<SaveDoc FileName="Projectlist" DocRef="$projectlist"/>
<!-- when a new project was entered, this is stored in the dialog property file -->
<!-- the next time the dialog is openend, the entry reappears. We don't want that -->
<!-- so this entry is cleared. -->
<LoadDoc FileName="projectSelectorDlgProps" NewDocRef="$dlgworkspaceprops"/>
<SelectFirst NodeRef="$dlgworkspaceprops" XPath="//widget[@name='newProject']"
SelectedEleRef="$projectnew"/>
<Set Ref="$projectnew.value" Value="''"/>
<SaveDoc FileName="projectSelectorDlgProps" DocRef="$dlgworkspaceprops"/>
</If>
<!-- now the dialog definition for the 'Generator Settings' is loaded -->
<Set Ref="generatorSettingsDlg" Value="'scripts/gui/dlgGeneratorSettings.xml'"/>
<LoadDoc FileName="generatorSettingsDlg" NewDocRef="$dlgGeneratorSettings"/>
<!-- We want the projectname to appear in the title of the window -->
<!-- so the title is adapted -->
<SelectFirst NodeRef="$dlgGeneratorSettings" XPath="//propertydialog"
SelectedEleRef="$titleElement"/>
<Set Ref="$titleElement.title" Value="$titleElement.title + ' - ' + selectedProject"/>
<!-- next we open the dialog. The 'PropFileName' (the file that will contain the values of -->
<!-- the dialog. The path contains the name of the project. -->
<ShowPropertyDialog GuiNodeRef="$dlgGeneratorSettings"
PropFileName="SettingsPath + selectedProject + '/generatorsettings.properties'"/>
<If Condition="equals(cancelbutton , 'true')">
<Exit />
</If>
</QiQuScript>
Your file structure should look like the one in the picture below
|
Explanation |
When the script is started, the 'Select Project' dialog will appear. If a new
project should be created, the name has to be entered in the Text-field. After
pressing ok, the new entry is added to the projectlist.xml in the 'settings'
directory. Therfore, when the script is started again, the newly added entry
will appear in the dropdown-list, since all entries in the projectlist.xml are
added.
After closing the 'Select Project' dialog by pressing ok, the definition for
the 'Generator Settings' dialog is loaded. Note: we change the title of the dialog
with the line:
<Set Ref="$titleElement.title" Value="$titleElement.title + ' - ' + selectedProject"/>
so that the title also contains the selected project name.
In order to distinguish the settings for different projects, the projects
name is simply added to the directory path. So every project setting is stored in a
different path.
|
4 Climbing up the tree
Problem |
There is a selected element, and in the script, you need to climp up the whole
over the parent elements.
How can you climb up in the xml tree structure to a
certain point? |
Approach |
Use a while loop and a "climb" reference. Climb up, till a certain level or the
top is reached
|
Solution |
<QiQuScript>
<LoadDoc FileName="'model/input/model.xml'" NewDocRef="$inModelRoot"/>
<!-- Select start Element -->
<SelectFirst NodeRef="$inModelRoot" XPath="//class" SelectedEleRef="$startElement"/>
<!-- set climb-iterator to start -->
<SelectFirst NodeRef="$startElement" XPath="." SelectedEleRef="$climbElement"/>
<While Condition="exists($climbElement)">
<EchoEle EleRef="$climbElement"/>
<!-- set climb-iteartor to parent of climb-iterator -->
<SelectFirst NodeRef="$climbElement" XPath=".." SelectedEleRef="$climbElement"/>
</While>
</QiQuScript>
|
Explanation |
The interesting part is the 'While'-loop. As long as the '$climbElement'
exists, the loop is looping. Therefore, you need to set the initial position
before the 'While'-loop is called. In the 'While'-loop itself, the next parent
Element is selected with the 'SelectFirst'-command. In order to do that,
you need the appropriate XPath-expression, which is in this case '..'. |
5 Transform to a "qiqu-style" xml format
Problem |
If xml-file should be used as inputs for velocity by the "qiqu-way"
(having realtimepreview and code-assist in the qiqu-velocity-editor),
the xml needs to follow the rule, that elements with the same name must
be grouped under a parent-element. The parents element name must start with
the name of the childelements followed by 'list'.
For example, buddies of a person must be grouped under a buddylist element:
<person name="aPerson">
<buddylist>
<buddy name="buddy1"/>
<buddy name="buddy2"/>
</buddylist>
</person>
How can we transform a "none-qiqu-style" xml-file to a "qiqu-style" xml-file?
|
Approach |
Iterate over a list with childElementnames, that must be grouped under a
grouping parent element. For every child element, check if the grouping parent
element already exists. If it doesn't exist, create the grouping element. Move
every child element to its grouping element.
|
Solution |
In order to test the script, create the following xml and save it under 'model/input/original.xml':
<data>
<person name="person1">
<buddy buddyName="buddy1-1"/>
<buddy buddyName="buddy1-2"/>
<buddy buddyName="buddy1-3"/>
<adress name="adress1-1"/>
<adress name="adress1-2"/>
<adress name="adress1-3"/>
</person>
<person name="person2">
<buddy buddyName="buddy2-1"/>
<buddy buddyName="buddy2-2"/>
<buddy buddyName="buddy2-3"/>
<adress name="adress2-1"/>
<adress name="adress2-2"/>
<adress name="adress2-3"/>
</person>
</data>
The generic transformer script is shown below.
<QiQuScript>
<Set Ref="childList" Value="'person,buddy,adress'"/>
<LoadDoc FileName="'model/input/original.xml'" NewDocRef="$toTransform"/>
<ForString Input="childList" Delimiter="','" IteratorRef="child">
<!-- define the name of the group element -->
<Set Ref="groupElementName" Value="child + 'list'"/>
<For NodeRef="$toTransform" XPath="//#child#" IteratorEleRef="$childElement">
<SelectFirst NodeRef="$childElement" XPath=".." SelectedEleRef="$parentNode"/>
<SelectFirst NodeRef="$parentNode" XPath="./#groupElementName#" SelectedEleRef="$groupElement"/>
<!-- check if the groupElement exists - if not, create it-->
<If Condition="not(exists($groupElement))">
<CreateEle NodeRef="$parentNode" EleName="groupElementName" NewEleRef="$groupElement"/>
</If>
<!-- now, the group element exists -->
<!-- create a copy of the chile element under the groupelement -->
<CreateEle NodeRef="$groupElement" EleName="child" EleRef="$childElement" NewEleRef="$movedChild"/>
<!-- and remove the old childelement -->
<Delete Ref="$childElement"/>
</For>
</ForString>
<SaveDoc FileName="'model/output/transformed.xml'" DocRef="$toTransform"/>
</QiQuScript>
|
Explanation |
First, a comma separated list with the child nodes is created. By using the
"ForString" command, we loop over this list. The "For"-command selects all elements
of the current childname (defined in the 'child' property). Note, that we create
the Xpath-expression dynamically. Also note, that all child elements are selected,
regardless under what parent they are.
The first thing we do in the "For"-loop is to
select the parent of the childElement. Then we select appropriate group-element
for the current child. If it does not exist, it is created afterwards. This happens
actually once for every parent node (e.g. 'adresslist' is created once for 'person1'
and once for 'person2').
By using the "CreateEle" command, we create a new Element
under the groupElement. Since we have defined the attribute "EleRef", our the content
of the selected child is actually copied. The only thing that is left to do for us,
is to delete the original child-element.
|
|